diff --git a/docs/content/6.drivers/capacitor-preferences.md b/docs/content/6.drivers/capacitor-preferences.md new file mode 100644 index 00000000..a696f9e5 --- /dev/null +++ b/docs/content/6.drivers/capacitor-preferences.md @@ -0,0 +1,41 @@ +# Capacitor Preferences + +Stores data via [Capacitor Preferences API](https://capacitorjs.com/docs/apis/preferences) on mobile devices or the local storage on the web. + +To use this driver, you need to install and sync `@capacitor/preferences` inside your capacitor project: + +::code-group + +```sh [npm] +npm install @capacitor/preferences +npx cap sync +``` + +```sh [Yarn] +yarn add @capacitor/preferences +npx cap sync +``` + +```sh [pnpm] +pnpm add @capacitor/preferences +pnpm cap sync +``` + +:: + +Usage: + +```js +import { createStorage } from "unstorage"; +import capacitorPreferences from "unstorage/drivers/capacitor-preferences"; + +const storage = createStorage({ + driver: capacitorPreferences({ + base: "test", + }), +}); +``` + +**Options:** + +- `base`: Add `${base}:` to all keys to avoid collision diff --git a/package.json b/package.json index 8d2a4e60..edbd8777 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@azure/identity": "^3.2.4", "@azure/keyvault-secrets": "^4.7.0", "@azure/storage-blob": "^12.15.0", + "@capacitor/preferences": "^5.0.6", "@cloudflare/workers-types": "^4.20230801.0", "@planetscale/database": "^1.10.0", "@types/ioredis-mock": "^8.2.2", @@ -101,6 +102,7 @@ "@azure/identity": "^3.2.3", "@azure/keyvault-secrets": "^4.7.0", "@azure/storage-blob": "^12.14.0", + "@capacitor/preferences": "^5.0.0", "idb-keyval": "^6.2.1", "@planetscale/database": "^1.8.0", "@upstash/redis": "^1.22.0", @@ -125,6 +127,9 @@ "@azure/storage-blob": { "optional": true }, + "@capacitor/preferences": { + "optional": true + }, "@planetscale/database": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f67f9fce..16953f70 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ devDependencies: '@azure/storage-blob': specifier: ^12.15.0 version: 12.15.0 + '@capacitor/preferences': + specifier: ^5.0.6 + version: 5.0.6(@capacitor/core@5.2.2) '@cloudflare/workers-types': specifier: ^4.20230801.0 version: 4.20230801.0 @@ -1158,6 +1161,20 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true + /@capacitor/core@5.2.2: + resolution: {integrity: sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==} + dependencies: + tslib: 2.6.1 + dev: true + + /@capacitor/preferences@5.0.6(@capacitor/core@5.2.2): + resolution: {integrity: sha512-aDe4wGTVSAIue6XXdUFgyz7SGszxK/Ptt/iWTydMpzc1PlZXw1XTTnciM+S+SLLNZFzXlkpXT3wMnh9t0DojUA==} + peerDependencies: + '@capacitor/core': ^5.0.0 + dependencies: + '@capacitor/core': 5.2.2 + dev: true + /@cloudflare/workers-types@4.20230801.0: resolution: {integrity: sha512-RzRUR+J/T3h58qbTZHYntYsnZXu3JnrlZIhqP2hhdyfoZAZ/+ko4wX0foAqlYHi+kXWaWtySHBuMcx6ec6TXlQ==} dev: true diff --git a/src/drivers/capacitor-preferences.ts b/src/drivers/capacitor-preferences.ts new file mode 100644 index 00000000..c46db61a --- /dev/null +++ b/src/drivers/capacitor-preferences.ts @@ -0,0 +1,50 @@ +import { Preferences } from "@capacitor/preferences"; + +import { defineDriver, joinKeys, normalizeKey } from "./utils"; + +const DRIVER_NAME = "capacitor-preferences"; + +export interface CapacitorPreferencesOptions { + base?: string; +} + +export default defineDriver((opts) => { + const base = normalizeKey(opts?.base || ""); + const resolveKey = (key: string) => joinKeys(base, key); + + return { + name: DRIVER_NAME, + options: opts, + hasItem(key) { + return Preferences.keys().then((r) => r.keys.includes(resolveKey(key))); + }, + getItem(key) { + return Preferences.get({ key: resolveKey(key) }).then((r) => r.value); + }, + getItemRaw(key) { + return Preferences.get({ key: resolveKey(key) }).then((r) => r.value); + }, + setItem(key, value) { + return Preferences.set({ key: resolveKey(key), value }); + }, + setItemRaw(key, value) { + return Preferences.set({ key: resolveKey(key), value }); + }, + removeItem(key) { + return Preferences.remove({ key: resolveKey(key) }); + }, + async getKeys() { + const { keys } = await Preferences.keys(); + return keys.map((key) => key.slice(base.length)); + }, + async clear(prefix) { + const { keys } = await Preferences.keys(); + const _prefix = resolveKey(prefix || ""); + await Promise.all( + keys + .filter((key) => key.startsWith(_prefix)) + .map((key) => Preferences.remove({ key })) + ); + }, + }; +}); diff --git a/test/drivers/capacitor-preferences.test.ts b/test/drivers/capacitor-preferences.test.ts new file mode 100644 index 00000000..0466f3b1 --- /dev/null +++ b/test/drivers/capacitor-preferences.test.ts @@ -0,0 +1,40 @@ +import { describe, vi } from "vitest"; +import driver from "../../src/drivers/capacitor-preferences"; +import { testDriver } from "./utils"; +import { afterEach } from "node:test"; + +vi.mock("@capacitor/preferences", () => { + const data = new Map(); + + const keys = vi.fn(() => Promise.resolve({ keys: Array.from(data.keys()) })); + const get = vi.fn(({ key }) => + Promise.resolve({ value: data.get(key) ?? null }) + ); + const set = vi.fn(({ key, value }) => Promise.resolve(data.set(key, value))); + const remove = vi.fn(({ key }) => Promise.resolve(data.delete(key))); + const clear = vi.fn(() => Promise.resolve(data.clear())); + + return { + Preferences: { + keys, + get, + set, + remove, + clear, + }, + }; +}); + +describe("drivers: capacitor-preferences", () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + testDriver({ + driver: driver({}), + }); + + testDriver({ + driver: driver({ base: "test" }), + }); +});