diff --git a/build.config.ts b/build.config.ts index e5110529b..46ad68c73 100644 --- a/build.config.ts +++ b/build.config.ts @@ -21,4 +21,5 @@ export default defineBuildConfig({ declaration: false, }, ], + externals: ["mongodb"], }); diff --git a/src/drivers/azure-app-configuration.ts b/src/drivers/azure-app-configuration.ts index 934df4631..6c1a86f76 100644 --- a/src/drivers/azure-app-configuration.ts +++ b/src/drivers/azure-app-configuration.ts @@ -68,6 +68,7 @@ export default defineDriver((opts: AzureAppConfigurationOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: getClient, async hasItem(key) { try { await getClient().getConfigurationSetting({ diff --git a/src/drivers/azure-cosmos.ts b/src/drivers/azure-cosmos.ts index f4920cf9a..9983318a4 100644 --- a/src/drivers/azure-cosmos.ts +++ b/src/drivers/azure-cosmos.ts @@ -86,6 +86,7 @@ export default defineDriver((opts: AzureCosmosOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: getCosmosClient, async hasItem(key) { const item = await (await getCosmosClient()) .item(key) diff --git a/src/drivers/azure-key-vault.ts b/src/drivers/azure-key-vault.ts index be53d1a65..973f4894b 100644 --- a/src/drivers/azure-key-vault.ts +++ b/src/drivers/azure-key-vault.ts @@ -45,6 +45,7 @@ export default defineDriver((opts: AzureKeyVaultOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: getKeyVaultClient, async hasItem(key) { try { await getKeyVaultClient().getSecret(encode(key)); diff --git a/src/drivers/azure-storage-blob.ts b/src/drivers/azure-storage-blob.ts index a872980c0..346235278 100644 --- a/src/drivers/azure-storage-blob.ts +++ b/src/drivers/azure-storage-blob.ts @@ -81,6 +81,7 @@ export default defineDriver((opts: AzureStorageBlobOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: getContainerClient, async hasItem(key) { return await getContainerClient().getBlockBlobClient(key).exists(); }, diff --git a/src/drivers/azure-storage-table.ts b/src/drivers/azure-storage-table.ts index 293071178..21cfbe822 100644 --- a/src/drivers/azure-storage-table.ts +++ b/src/drivers/azure-storage-table.ts @@ -105,6 +105,7 @@ export default defineDriver((opts: AzureStorageTableOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: getClient, async hasItem(key) { try { await getClient().getEntity(partitionKey, key); diff --git a/src/drivers/capacitor-preferences.ts b/src/drivers/capacitor-preferences.ts index c46db61a5..aade7a8c7 100644 --- a/src/drivers/capacitor-preferences.ts +++ b/src/drivers/capacitor-preferences.ts @@ -8,43 +8,46 @@ export interface CapacitorPreferencesOptions { base?: string; } -export default defineDriver((opts) => { - const base = normalizeKey(opts?.base || ""); - const resolveKey = (key: string) => joinKeys(base, key); +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 })) - ); - }, - }; -}); + return { + name: DRIVER_NAME, + options: opts, + getInstance: () => Preferences, + 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/src/drivers/cloudflare-kv-binding.ts b/src/drivers/cloudflare-kv-binding.ts index 22d9ca204..23c4a043f 100644 --- a/src/drivers/cloudflare-kv-binding.ts +++ b/src/drivers/cloudflare-kv-binding.ts @@ -25,6 +25,7 @@ export default defineDriver((opts: KVOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: () => getKVBinding(opts.binding), async hasItem(key) { key = r(key); const binding = getKVBinding(opts.binding); diff --git a/src/drivers/cloudflare-r2-binding.ts b/src/drivers/cloudflare-r2-binding.ts index 8e56a1f7b..49934cc4f 100644 --- a/src/drivers/cloudflare-r2-binding.ts +++ b/src/drivers/cloudflare-r2-binding.ts @@ -25,6 +25,7 @@ export default defineDriver((opts: CloudflareR2Options = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: () => getR2Binding(opts.binding), async hasItem(key) { key = r(key); const binding = getR2Binding(opts.binding); diff --git a/src/drivers/localstorage.ts b/src/drivers/localstorage.ts index 60e70b907..5f1120a75 100644 --- a/src/drivers/localstorage.ts +++ b/src/drivers/localstorage.ts @@ -32,6 +32,7 @@ export default defineDriver((opts: LocalStorageOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: () => opts.localStorage!, hasItem(key) { return Object.prototype.hasOwnProperty.call(opts.localStorage!, r(key)); }, diff --git a/src/drivers/lru-cache.ts b/src/drivers/lru-cache.ts index 6d5f5cf48..294bea8a0 100644 --- a/src/drivers/lru-cache.ts +++ b/src/drivers/lru-cache.ts @@ -25,6 +25,7 @@ export default defineDriver((opts: LRUDriverOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: () => cache, hasItem(key) { return cache.has(key); }, diff --git a/src/drivers/memory.ts b/src/drivers/memory.ts index 2cb310320..3f5601d05 100644 --- a/src/drivers/memory.ts +++ b/src/drivers/memory.ts @@ -2,12 +2,12 @@ import { defineDriver } from "./utils"; const DRIVER_NAME = "memory"; -export default defineDriver(() => { +export default defineDriver>(() => { const data = new Map(); return { name: DRIVER_NAME, - options: {}, + getInstance: () => data, hasItem(key) { return data.has(key); }, diff --git a/src/drivers/mongodb.ts b/src/drivers/mongodb.ts index 8e43d7801..ae5746485 100644 --- a/src/drivers/mongodb.ts +++ b/src/drivers/mongodb.ts @@ -39,6 +39,7 @@ export default defineDriver((opts: MongoDbOptions) => { return { name: DRIVER_NAME, options: opts, + getInstance: getMongoCollection, async hasItem(key) { const result = await getMongoCollection().findOne({ key }); return !!result; diff --git a/src/drivers/netlify-blobs.ts b/src/drivers/netlify-blobs.ts index dbf814b7d..62d713ed4 100644 --- a/src/drivers/netlify-blobs.ts +++ b/src/drivers/netlify-blobs.ts @@ -42,73 +42,73 @@ export type NetlifyStoreOptions = | NetlifyDeployStoreOptions | NetlifyNamedStoreOptions; -export default defineDriver( - ({ deployScoped, name, ...opts }: NetlifyStoreOptions) => { - let store: Store; +export default defineDriver((options: NetlifyStoreOptions) => { + const { deployScoped, name, ...opts } = options; + let store: Store; - const getClient = () => { - if (!store) { - if (deployScoped) { - if (name) { - throw createError( - DRIVER_NAME, - "deploy-scoped stores cannot have a name" - ); - } - store = getDeployStore({ fetch, ...opts }); - } else { - if (!name) { - throw createRequiredError(DRIVER_NAME, "name"); - } - // Ensures that reserved characters are encoded - store = getStore({ name: encodeURIComponent(name), fetch, ...opts }); + const getClient = () => { + if (!store) { + if (deployScoped) { + if (name) { + throw createError( + DRIVER_NAME, + "deploy-scoped stores cannot have a name" + ); } + store = getDeployStore({ fetch, ...options }); + } else { + if (!name) { + throw createRequiredError(DRIVER_NAME, "name"); + } + // Ensures that reserved characters are encoded + store = getStore({ name: encodeURIComponent(name), fetch, ...opts }); } - return store; - }; + } + return store; + }; - return { - name: DRIVER_NAME, - options: {}, - async hasItem(key) { - return getClient().getMetadata(key).then(Boolean); - }, - getItem: (key, tops?: GetOptions) => { - // @ts-expect-error has trouble with the overloaded types - return getClient().get(key, tops); - }, - getMeta(key) { - return getClient().getMetadata(key); - }, - getItemRaw(key, topts?: GetOptions) { - // @ts-expect-error has trouble with the overloaded types - return getClient().get(key, { type: topts?.type ?? "arrayBuffer" }); - }, - setItem(key, value, topts?: SetOptions) { - return getClient().set(key, value, topts); - }, - setItemRaw(key, value: string | ArrayBuffer | Blob, topts?: SetOptions) { - return getClient().set(key, value, topts); - }, - removeItem(key) { - return getClient().delete(key); - }, - async getKeys( - base?: string, - tops?: Omit - ) { - return (await getClient().list({ ...tops, prefix: base })).blobs.map( - (item) => item.key - ); - }, - async clear(base?: string) { - const client = getClient(); - return Promise.allSettled( - (await client.list({ prefix: base })).blobs.map((item) => - client.delete(item.key) - ) - ).then(() => {}); - }, - }; - } -); + return { + name: DRIVER_NAME, + options, + getInstance: getClient, + async hasItem(key) { + return getClient().getMetadata(key).then(Boolean); + }, + getItem: (key, tops?: GetOptions) => { + // @ts-expect-error has trouble with the overloaded types + return getClient().get(key, tops); + }, + getMeta(key) { + return getClient().getMetadata(key); + }, + getItemRaw(key, topts?: GetOptions) { + // @ts-expect-error has trouble with the overloaded types + return getClient().get(key, { type: topts?.type ?? "arrayBuffer" }); + }, + setItem(key, value, topts?: SetOptions) { + return getClient().set(key, value, topts); + }, + setItemRaw(key, value: string | ArrayBuffer | Blob, topts?: SetOptions) { + return getClient().set(key, value, topts); + }, + removeItem(key) { + return getClient().delete(key); + }, + async getKeys( + base?: string, + tops?: Omit + ) { + return (await getClient().list({ ...tops, prefix: base })).blobs.map( + (item) => item.key + ); + }, + async clear(base?: string) { + const client = getClient(); + return Promise.allSettled( + (await client.list({ prefix: base })).blobs.map((item) => + client.delete(item.key) + ) + ).then(() => {}); + }, + }; +}); diff --git a/src/drivers/planetscale.ts b/src/drivers/planetscale.ts index 76d6fb18e..7f145db3c 100644 --- a/src/drivers/planetscale.ts +++ b/src/drivers/planetscale.ts @@ -50,6 +50,7 @@ export default defineDriver((opts: PlanetscaleDriverOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: getConnection, hasItem: async (key) => { const res = await getConnection().execute( `SELECT EXISTS (SELECT 1 FROM ${opts.table} WHERE id = :key) as value;`, diff --git a/src/drivers/redis.ts b/src/drivers/redis.ts index 0694ba880..aee7444ac 100644 --- a/src/drivers/redis.ts +++ b/src/drivers/redis.ts @@ -35,7 +35,7 @@ export interface RedisOptions extends _RedisOptions { const DRIVER_NAME = "redis"; -export default defineDriver((opts: RedisOptions = {}) => { +export default defineDriver((opts: RedisOptions) => { let redisClient: Redis | Cluster; const getRedisClient = () => { if (redisClient) { @@ -58,6 +58,7 @@ export default defineDriver((opts: RedisOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: getRedisClient, async hasItem(key) { return Boolean(await getRedisClient().exists(p(key))); }, diff --git a/src/drivers/session-storage.ts b/src/drivers/session-storage.ts index 46e7eb1a2..b5adfd15f 100644 --- a/src/drivers/session-storage.ts +++ b/src/drivers/session-storage.ts @@ -32,6 +32,7 @@ export default defineDriver((opts: SessionStorageOptions = {}) => { return { name: DRIVER_NAME, options: opts, + getInstance: () => opts.sessionStorage!, hasItem(key) { return Object.prototype.hasOwnProperty.call(opts.sessionStorage, r(key)); }, diff --git a/src/drivers/utils/index.ts b/src/drivers/utils/index.ts index 86e507f02..0aa3a1389 100644 --- a/src/drivers/utils/index.ts +++ b/src/drivers/utils/index.ts @@ -1,11 +1,13 @@ import type { Driver } from "../.."; -type DriverFactory = (opts: T) => Driver; +type DriverFactory = ( + opts: OptionsT +) => Driver; interface ErrorOptions {} -export function defineDriver( - factory: DriverFactory -): DriverFactory { +export function defineDriver( + factory: DriverFactory +): DriverFactory { return factory; } diff --git a/src/drivers/vercel-kv.ts b/src/drivers/vercel-kv.ts index e18a9bd02..c151e5b7c 100644 --- a/src/drivers/vercel-kv.ts +++ b/src/drivers/vercel-kv.ts @@ -23,7 +23,7 @@ export interface VercelKVOptions extends Partial { const DRIVER_NAME = "vercel-kv"; -export default defineDriver((opts) => { +export default defineDriver((opts) => { const base = normalizeKey(opts?.base); const r = (...keys: string[]) => joinKeys(base, ...keys); @@ -56,13 +56,16 @@ export default defineDriver((opts) => { ); } } - _client = createClient(opts as RedisConfigNodejs); + _client = createClient( + opts as VercelKVOptions & { url: string; token: string } + ); } return _client; }; return { name: DRIVER_NAME, + getInstance: getClient, hasItem(key) { return getClient().exists(r(key)).then(Boolean); }, diff --git a/src/types.ts b/src/types.ts index 6a57293de..b408bbd70 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,9 +16,10 @@ export interface StorageMeta { export type TransactionOptions = Record; -export interface Driver { +export interface Driver { name?: string; - options?: any; + options?: OptionsT; + getInstance?: () => InstanceT; hasItem: (key: string, opts: TransactionOptions) => MaybePromise; getItem: ( key: string, diff --git a/test/drivers/redis.test.ts b/test/drivers/redis.test.ts index b8e08f0f4..6b5637166 100644 --- a/test/drivers/redis.test.ts +++ b/test/drivers/redis.test.ts @@ -1,17 +1,19 @@ import { describe, vi, it, expect } from "vitest"; import * as ioredis from "ioredis-mock"; -import driver from "../../src/drivers/redis"; +import redisDriver from "../../src/drivers/redis"; import { testDriver } from "./utils"; vi.mock("ioredis", () => ioredis); describe("drivers: redis", () => { + const driver = redisDriver({ + base: "test:", + url: "ioredis://localhost:6379/0", + lazyConnect: false, + }); + testDriver({ - driver: driver({ - base: "test:", - url: "ioredis://localhost:6379/0", - lazyConnect: false, - }), + driver, additionalTests() { it("verify stored keys", async () => { const client = new ioredis.default("ioredis://localhost:6379/0"); @@ -38,6 +40,10 @@ describe("drivers: redis", () => { `); await client.disconnect(); }); + + it("exposes instance", () => { + expect(driver.getInstance()).toBeInstanceOf(ioredis.default); + }); }, }); }); diff --git a/tsconfig.json b/tsconfig.json index e9588d9f9..50585e0e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "module": "ESNext", "moduleResolution": "Node", "esModuleInterop": true, - "strict": true + "strict": true, + "skipLibCheck": true }, "include": ["src"] }