Skip to content
Snippets Groups Projects
index.ts 3.3 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 { AbstractLevel } from 'abstract-level';
import type { AbstractDatabaseOptions } 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

interface DatabaseOptions<K, V> extends AbstractDatabaseOptions<K, V> {
  /**
   * Prefix for the `IDBDatabase` name. Can be set to an empty string.
   *
   * @defaultValue `'level-js-'`
   */
  prefix?: string;

  /**
   * The version to open the `IDBDatabase` with.
   *
   * @defaultValue `1`
   */
  version?: number | string;

  /**
   * An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
   */
  createIfMissing?: boolean;

  /**
   * An {@link AbstractLevel} option that has no effect on {@link BrowserLevel}.
   */
  errorIfExists?: boolean;
}

const textEncoder = new TextEncoder();

class DummyIterator {
  public readonly results: [Uint8Array, Uint8Array][];

  constructor(results: [Uint8Array, Uint8Array][]) {
    this.results = results;
  }

  all(): [Uint8Array, Uint8Array][] {
    return this.results;
  }
}

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

  constructor(
    name: string,
    options: AbstractDatabaseOptions<Uint8Array, Uint8Array> | undefined,
  ) {
    super({}, options);

    this.name = name;

    if (typeof location !== 'string') {
      throw new Error('constructor requires a location string argument');
    }
  }

  async _open(options: unknown): Promise<void> {
    if (options)
      throw new Error('options not supported, feel free to submit a PR');
    await MobileLevelPlugin.open({ path: this.name });
  }

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

    const { value } = await MobileLevelPlugin.get({ key: encodedKey });

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

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

    await MobileLevelPlugin.put({ key: encodedKey, value: encodedValue });
  }

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

    await MobileLevelPlugin.delete({ key: encodedKey });
  }

  async _close(): Promise<void> {
    await MobileLevelPlugin.close();
  }

  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,
    }));

    await MobileLevelPlugin.batch({ operations: encodedOperations });
  }

  async _iterator(options: {
    gt?: string;
    gte?: string;
    lt?: string;
    lte?: string;
    limit?: number;
    reverse?: boolean;
  }): Promise<{ results: [key: Uint8Array, value: Uint8Array][] }> {
    const { results } = await MobileLevelPlugin.iterator(options);

    return {
      results: results.map(([key, value]) => [decode(key), decode(value)]),
    };
  }

  async _clear(): Promise<void> {
    await MobileLevelPlugin.clear();
  }
}