Skip to content
Snippets Groups Projects
index.ts 3.69 KiB
Newer Older
Aidan's avatar
Aidan committed
import { encode, decode } from '@borderless/base64';
Aidan's avatar
Aidan committed
import { registerPlugin } from '@capacitor/core';
Aidan's avatar
Aidan committed
import { AbstractIterator, AbstractLevel } from 'abstract-level';
import type {
  AbstractDatabaseOptions,
  AbstractIteratorOptions,
  NodeCallback,
} from 'abstract-level';
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
import type { MobileLevelPluginT } from './definitions';
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
const MobileLevelPlugin = registerPlugin<MobileLevelPluginT>('MobileLevel', {});
Aidan's avatar
Aidan committed

export * from './definitions';
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
class DummyIterator extends AbstractIterator<
  MobileLevel,
  Uint8Array,
  Uint8Array
> {
  private readonly options: AbstractIteratorOptions<Uint8Array, Uint8Array>;
  readonly location: string;

  constructor(
    db: MobileLevel,
    location: string,
    options: AbstractIteratorOptions<Uint8Array, Uint8Array>,
  ) {
    super(db, options);
    this.options = options;
    this.location = location;
  }

  // Note: if called by _all() then size can be Infinity. This is an internal
  // detail; by design AbstractIterator.nextv() does not support Infinity.
  _nextv() {
    return;
  }

  _next() {
    return;
  }

  _all(options: unknown, callback: NodeCallback<[Uint8Array, Uint8Array][]>) {
    if (options)
      throw new Error('options not supported, feel free to submit a PR');

    const { gt, gte, lt, lte, ...restOptions } = this.options;
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
    const encodedOptions = {
      gt: gt ? encode(gt) : undefined,
      gte: gte ? encode(gte) : undefined,
      lt: lt ? encode(lt) : undefined,
      lte: lte ? encode(lte) : undefined,
      ...restOptions,
    };
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
    MobileLevelPlugin.Iterator(encodedOptions).then(({ results }) => {
      const decodedResults = results.map(
        ([key, value]): [Uint8Array, Uint8Array] => [
          decode(key),
          decode(value),
        ],
      );
Aidan's avatar
Aidan committed

Aidan's avatar
Aidan committed
      callback(null, decodedResults);
    });
Aidan's avatar
Aidan committed
  }

Aidan's avatar
Aidan committed
  _seek() {
    return;
Aidan's avatar
Aidan committed
  }
}

export class MobileLevel extends AbstractLevel<
  Uint8Array,
  Uint8Array,
  Uint8Array
> {
  public readonly name: string;

  constructor(
    name: string,
    options: AbstractDatabaseOptions<Uint8Array, Uint8Array> | undefined,
  ) {
Aidan's avatar
Aidan committed
    super(
      {
        encodings: { view: true },
        // TODO: It technically supports snapshots, I think?
        snapshots: false,
      },
      options,
    );
Aidan's avatar
Aidan committed

    this.name = name;
  }

Aidan's avatar
Aidan committed
  async _open(): Promise<void> {
    await MobileLevelPlugin.Open({
      path: this.name,
    });
Aidan's avatar
Aidan committed
  }

  async _get(key: Uint8Array): Promise<Uint8Array | null> {
    const encodedKey = encode(key);

Aidan's avatar
Aidan committed
    const { value } = await MobileLevelPlugin.Get({ key: encodedKey });
Aidan's avatar
Aidan committed

    if (value === null) return null;
    return decode(value);
  }

  async _put(key: Uint8Array, value: Uint8Array): Promise<void> {
    const encodedKey = encode(key);
    const encodedValue = encode(value);

Aidan's avatar
Aidan committed
    await MobileLevelPlugin.Put({ key: encodedKey, value: encodedValue });
Aidan's avatar
Aidan committed
  }

  async _delete(key: Uint8Array): Promise<void> {
    const encodedKey = encode(key);

Aidan's avatar
Aidan committed
    await MobileLevelPlugin.Delete({ key: encodedKey });
Aidan's avatar
Aidan committed
  }

  async _close(): Promise<void> {
Aidan's avatar
Aidan committed
    await MobileLevelPlugin.Close();
Aidan's avatar
Aidan committed
  }

  async _batch(
    operations: {
      type: 'put' | 'delete';
      key: Uint8Array;
      value?: Uint8Array;
    }[],
  ): Promise<void> {
    const encodedOperations = operations.map(({ type, key, value }) => ({
      type,
      key: encode(key),
      value: value ? encode(value) : undefined,
    }));

Aidan's avatar
Aidan committed
    await MobileLevelPlugin.Batch({ operations: encodedOperations });
Aidan's avatar
Aidan committed
  }

Aidan's avatar
Aidan committed
  _iterator(options: {
    gt?: Uint8Array;
    gte?: Uint8Array;
    lt?: Uint8Array;
    lte?: Uint8Array;
Aidan's avatar
Aidan committed
    limit?: number;
    reverse?: boolean;
Aidan's avatar
Aidan committed
  }): DummyIterator {
    return new DummyIterator(this, this.name, options);
Aidan's avatar
Aidan committed
  }

  async _clear(): Promise<void> {
Aidan's avatar
Aidan committed
    await MobileLevelPlugin.Clear();
Aidan's avatar
Aidan committed
  }
}