mirror of
https://github.com/unjs/unstorage.git
synced 2025-12-08 21:26:09 +00:00
feat: expose underlying client instance (#446)
This commit is contained in:
parent
486a400813
commit
d23cba502d
@ -21,4 +21,5 @@ export default defineBuildConfig({
|
||||
declaration: false,
|
||||
},
|
||||
],
|
||||
externals: ["mongodb"],
|
||||
});
|
||||
|
||||
@ -68,6 +68,7 @@ export default defineDriver((opts: AzureAppConfigurationOptions = {}) => {
|
||||
return {
|
||||
name: DRIVER_NAME,
|
||||
options: opts,
|
||||
getInstance: getClient,
|
||||
async hasItem(key) {
|
||||
try {
|
||||
await getClient().getConfigurationSetting({
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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();
|
||||
},
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 }))
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
},
|
||||
|
||||
@ -25,6 +25,7 @@ export default defineDriver((opts: LRUDriverOptions = {}) => {
|
||||
return {
|
||||
name: DRIVER_NAME,
|
||||
options: opts,
|
||||
getInstance: () => cache,
|
||||
hasItem(key) {
|
||||
return cache.has(key);
|
||||
},
|
||||
|
||||
@ -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);
|
||||
},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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(() => {});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@ -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;`,
|
||||
|
||||
@ -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)));
|
||||
},
|
||||
|
||||
@ -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));
|
||||
},
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
},
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user