Skip to content
Snippets Groups Projects
index.ts 4.48 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,
} 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

type Callback = (err?: Error | null, value?: any) => void;

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: never, callback: Callback) {
Aidan's avatar
Aidan committed
    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

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

        callback(null, decodedResults);
      })
      .catch(err => callback(err, null));
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;
  }

  _open(_options: never, callback: Callback) {
    MobileLevelPlugin.open({
Aidan's avatar
Aidan committed
      dbName: this.name,
    })
      .then(() => callback())
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }

  _get(key: Uint8Array, _options: never, callback: Callback) {
Aidan's avatar
Aidan committed
    const encodedKey = encode(key);

    MobileLevelPlugin.get({
Aidan's avatar
Aidan committed
      dbName: this.name,
      key: encodedKey,
    })
      .then(({ value }) => {
        if (value === null) {
          callback(null, null);
        } else {
          callback(null, decode(value));
        }
      })
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }

  _put(key: Uint8Array, value: Uint8Array, _options: never, callback: Callback) {
Aidan's avatar
Aidan committed
    const encodedKey = encode(key);
    const encodedValue = encode(value);

    MobileLevelPlugin.put({
Aidan's avatar
Aidan committed
      key: encodedKey,
      value: encodedValue,
      dbName: this.name,
    })
      .then(() => callback())
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }

  _del(key: Uint8Array, _options: never, callback: Callback) {
Aidan's avatar
Aidan committed
    const encodedKey = encode(key);

    MobileLevelPlugin.delete({ key: encodedKey, dbName: this.name })
      .then(() => callback())
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }

  _close(callback: Callback) {
    MobileLevelPlugin.close({
Aidan's avatar
Aidan committed
      dbName: this.name,
    })
      .then(() => callback())
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }

Aidan's avatar
Aidan committed
    operations: {
      type: 'put' | 'delete';
      key: Uint8Array;
      value?: Uint8Array;
    }[],
    _options: never, 
    callback: Callback,
  ) {
Aidan's avatar
Aidan committed
    const encodedOperations = operations.map(({ type, key, value }) => ({
      type,
      key: encode(key),
      value: value ? encode(value) : undefined,
    }));

    MobileLevelPlugin.batch({
Aidan's avatar
Aidan committed
      operations: encodedOperations,
      dbName: this.name,
    })
      .then(() => callback())
      .catch(err => callback(err, null));
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
  }

  _clear(_options: never, callback: Callback) {
    MobileLevelPlugin.clear({ dbName: this.name })
      .then(() => callback())
      .catch(err => callback(err, null));
Aidan's avatar
Aidan committed
  }
}