feat: expose underlying client instance (#446)

This commit is contained in:
Hash Brown 2024-05-16 20:37:47 +08:00 committed by GitHub
parent 486a400813
commit d23cba502d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 153 additions and 123 deletions

View File

@ -21,4 +21,5 @@ export default defineBuildConfig({
declaration: false,
},
],
externals: ["mongodb"],
});

View File

@ -68,6 +68,7 @@ export default defineDriver((opts: AzureAppConfigurationOptions = {}) => {
return {
name: DRIVER_NAME,
options: opts,
getInstance: getClient,
async hasItem(key) {
try {
await getClient().getConfigurationSetting({

View File

@ -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)

View File

@ -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));

View File

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

View File

@ -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);

View File

@ -8,43 +8,46 @@ export interface CapacitorPreferencesOptions {
base?: string;
}
export default defineDriver<CapacitorPreferencesOptions>((opts) => {
const base = normalizeKey(opts?.base || "");
const resolveKey = (key: string) => joinKeys(base, key);
export default defineDriver<CapacitorPreferencesOptions, typeof Preferences>(
(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 }))
);
},
};
}
);

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -25,6 +25,7 @@ export default defineDriver((opts: LRUDriverOptions = {}) => {
return {
name: DRIVER_NAME,
options: opts,
getInstance: () => cache,
hasItem(key) {
return cache.has(key);
},

View File

@ -2,12 +2,12 @@ import { defineDriver } from "./utils";
const DRIVER_NAME = "memory";
export default defineDriver<void>(() => {
export default defineDriver<void, Map<string, any>>(() => {
const data = new Map<string, any>();
return {
name: DRIVER_NAME,
options: {},
getInstance: () => data,
hasItem(key) {
return data.has(key);
},

View File

@ -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;

View File

@ -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<ListOptions, "prefix" | "paginate">
) {
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<ListOptions, "prefix" | "paginate">
) {
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(() => {});
},
};
});

View File

@ -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;`,

View File

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

View File

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

View File

@ -1,11 +1,13 @@
import type { Driver } from "../..";
type DriverFactory<T> = (opts: T) => Driver;
type DriverFactory<OptionsT, InstanceT> = (
opts: OptionsT
) => Driver<OptionsT, InstanceT>;
interface ErrorOptions {}
export function defineDriver<T = any>(
factory: DriverFactory<T>
): DriverFactory<T> {
export function defineDriver<OptionsT = any, InstanceT = never>(
factory: DriverFactory<OptionsT, InstanceT>
): DriverFactory<OptionsT, InstanceT> {
return factory;
}

View File

@ -23,7 +23,7 @@ export interface VercelKVOptions extends Partial<RedisConfigNodejs> {
const DRIVER_NAME = "vercel-kv";
export default defineDriver<VercelKVOptions>((opts) => {
export default defineDriver<VercelKVOptions, VercelKV>((opts) => {
const base = normalizeKey(opts?.base);
const r = (...keys: string[]) => joinKeys(base, ...keys);
@ -56,13 +56,16 @@ export default defineDriver<VercelKVOptions>((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);
},

View File

@ -16,9 +16,10 @@ export interface StorageMeta {
export type TransactionOptions = Record<string, any>;
export interface Driver {
export interface Driver<OptionsT = any, InstanceT = any> {
name?: string;
options?: any;
options?: OptionsT;
getInstance?: () => InstanceT;
hasItem: (key: string, opts: TransactionOptions) => MaybePromise<boolean>;
getItem: (
key: string,

View File

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

View File

@ -4,7 +4,8 @@
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true
"strict": true,
"skipLibCheck": true
},
"include": ["src"]
}