diff --git a/src/drivers/deno-kv.ts b/src/drivers/deno-kv.ts index 0b1808077..497d39d8e 100644 --- a/src/drivers/deno-kv.ts +++ b/src/drivers/deno-kv.ts @@ -12,6 +12,7 @@ export interface DenoKvOptions { */ ttl?: number; } + interface DenoKVSetOptions { /** * TTL in seconds. @@ -75,12 +76,12 @@ export default defineDriver>( const value = await kv.get(r(key)); return value.value; }, - async setItem(key, value, tOptions: DenoKVSetOptions) { + async setItem(key, value, tOptions?: DenoKVSetOptions) { const ttl = normalizeTTL(tOptions?.ttl ?? opts?.ttl); const kv = await getKv(); await kv.set(r(key), value, { expireIn: ttl }); }, - async setItemRaw(key, value, tOptions: DenoKVSetOptions) { + async setItemRaw(key, value, tOptions?: DenoKVSetOptions) { const ttl = normalizeTTL(tOptions?.ttl ?? opts?.ttl); const kv = await getKv(); await kv.set(r(key), value, { expireIn: ttl }); diff --git a/src/drivers/netlify-blobs.ts b/src/drivers/netlify-blobs.ts index a8eb59bdf..b390b890d 100644 --- a/src/drivers/netlify-blobs.ts +++ b/src/drivers/netlify-blobs.ts @@ -1,5 +1,4 @@ import { createError, createRequiredError, defineDriver } from "./utils"; -import type { GetKeysOptions } from ".."; import { getStore, getDeployStore } from "@netlify/blobs"; import type { Store, @@ -46,7 +45,20 @@ export interface NetlifyNamedStoreOptions deployScoped?: false; } -export default defineDriver((options: NetlifyStoreOptions) => { +export interface NetlifyGetKeysOptions + extends Omit { + maxDepth?: number; +} + +export default defineDriver< + NetlifyStoreOptions, + Store, + { + setOptions: SetOptions; + getOptions: GetOptions; + getKeysOptions: NetlifyGetKeysOptions; + } +>((options: NetlifyStoreOptions) => { const { deployScoped, name, ...opts } = options; let store: Store; @@ -107,10 +119,7 @@ export default defineDriver((options: NetlifyStoreOptions) => { removeItem(key) { return getClient().delete(key); }, - async getKeys( - base?: string, - tops?: GetKeysOptions & Omit - ) { + async getKeys(base?: string, tops?: NetlifyGetKeysOptions) { return (await getClient().list({ ...tops, prefix: base })).blobs.map( (item) => item.key ); diff --git a/src/drivers/utils/index.ts b/src/drivers/utils/index.ts index 16e584a3a..96d02508f 100644 --- a/src/drivers/utils/index.ts +++ b/src/drivers/utils/index.ts @@ -1,13 +1,23 @@ -import type { Driver } from "../.."; +import type { Driver, DriverMethodOptionsMap } from "../.."; -type DriverFactory = ( +type DriverFactory< + OptionsT, + InstanceT, + DriverMethodOptionsMapT extends DriverMethodOptionsMap, + // Partial here, to not required typed options to be optional in type argument +> = ( opts: OptionsT -) => Driver; +) => Driver>; interface ErrorOptions {} -export function defineDriver( - factory: DriverFactory -): DriverFactory { +export function defineDriver< + OptionsT = any, + InstanceT = never, + DriverMethodOptionsMapT extends + DriverMethodOptionsMap = DriverMethodOptionsMap, +>( + factory: DriverFactory +): DriverFactory { return factory; } diff --git a/src/storage.ts b/src/storage.ts index eb6578471..525dee4ff 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -26,13 +26,14 @@ interface StorageCTX { watchListeners: ((event: WatchEvent, key: string) => void)[]; } -export interface CreateStorageOptions { - driver?: Driver; +export interface CreateStorageOptions { + driver?: DriverT; } -export function createStorage( - options: CreateStorageOptions = {} -): Storage { +export function createStorage< + T extends StorageValue, + DriverT extends Driver = Driver, +>(options: CreateStorageOptions = {}): Storage { const context: StorageCTX = { mounts: { "": options.driver || memory() }, mountpoints: [""], diff --git a/src/types.ts b/src/types.ts index d347dbd06..23d32d513 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,58 +15,91 @@ export interface StorageMeta { [key: string]: StorageValue | Date | undefined; } -// TODO: type ttl export type TransactionOptions = Record; -export type GetKeysOptions = TransactionOptions & { - maxDepth?: number; -}; - export interface DriverFlags { maxDepth?: boolean; ttl?: boolean; } -export interface Driver { +export type DriverMethodOptionsMap< + GetOptionsT = TransactionOptions, + SetOptionsT = TransactionOptions, + HasOptionsT = TransactionOptions, + RemoveOptionsT = TransactionOptions, + GetKeysOptionsT = TransactionOptions, +> = { + getOptions?: GetOptionsT; + setOptions?: SetOptionsT; + hasOptions?: HasOptionsT; + removeOptions?: RemoveOptionsT; + getKeysOptions?: GetKeysOptionsT; +}; + +export interface Driver< + OptionsT = any, + InstanceT = any, + DriverMethodOptionsMapT extends + DriverMethodOptionsMap = DriverMethodOptionsMap, +> { name?: string; flags?: DriverFlags; options?: OptionsT; getInstance?: () => InstanceT; - hasItem: (key: string, opts: TransactionOptions) => MaybePromise; + hasItem: ( + key: string, + opts: DriverMethodOptionsMapT["hasOptions"] + ) => MaybePromise; getItem: ( key: string, - opts?: TransactionOptions + opts?: DriverMethodOptionsMapT["getOptions"] ) => MaybePromise; /** @experimental */ getItems?: ( - items: { key: string; options?: TransactionOptions }[], - commonOptions?: TransactionOptions + items: { key: string; options?: DriverMethodOptionsMapT["getOptions"] }[], + commonOptions?: DriverMethodOptionsMapT["getOptions"] ) => MaybePromise<{ key: string; value: StorageValue }[]>; /** @experimental */ - getItemRaw?: (key: string, opts: TransactionOptions) => MaybePromise; + getItemRaw?: ( + key: string, + opts: DriverMethodOptionsMapT["getOptions"] + ) => MaybePromise; setItem?: ( key: string, value: string, - opts: TransactionOptions + opts: DriverMethodOptionsMapT["setOptions"] ) => MaybePromise; /** @experimental */ setItems?: ( - items: { key: string; value: string; options?: TransactionOptions }[], - commonOptions?: TransactionOptions + items: { + key: string; + value: string; + options?: DriverMethodOptionsMapT["setOptions"]; + }[], + commonOptions?: DriverMethodOptionsMapT["setOptions"] ) => MaybePromise; /** @experimental */ setItemRaw?: ( key: string, value: any, - opts: TransactionOptions + opts: DriverMethodOptionsMapT["setOptions"] + ) => MaybePromise; + removeItem?: ( + key: string, + opts: DriverMethodOptionsMapT["removeOptions"] ) => MaybePromise; - removeItem?: (key: string, opts: TransactionOptions) => MaybePromise; getMeta?: ( key: string, - opts: TransactionOptions + opts: DriverMethodOptionsMapT["getOptions"] ) => MaybePromise; - getKeys: (base: string, opts: GetKeysOptions) => MaybePromise; - clear?: (base: string, opts: TransactionOptions) => MaybePromise; + getKeys: ( + base: string, + opts: DriverMethodOptionsMapT["getKeysOptions"] + ) => MaybePromise; + clear?: ( + base: string, + opts: DriverMethodOptionsMapT["removeOptions"] + ) => MaybePromise; dispose?: () => MaybePromise; watch?: (callback: WatchCallback) => MaybePromise; } @@ -83,38 +116,68 @@ type StorageItemType = K extends keyof StorageItemMap ? StorageValue : T; -export interface Storage { +type StorageMethodOptions = + DriverT extends Driver + ? MethodOptionsT + : never; + +// if options type is not set it is unknown and we default back to TransactionOptions +type SetOptionsType = + unknown extends StorageMethodOptions["setOptions"] + ? TransactionOptions + : StorageMethodOptions["setOptions"] | undefined; +type GetOptionsType = + unknown extends StorageMethodOptions["getOptions"] + ? TransactionOptions + : StorageMethodOptions["getOptions"]; +type RemoveOptionsType = + unknown extends StorageMethodOptions["removeOptions"] + ? TransactionOptions + : StorageMethodOptions["removeOptions"]; +type GetKeysOptionsType = + unknown extends StorageMethodOptions["getKeysOptions"] + ? TransactionOptions + : StorageMethodOptions["getKeysOptions"]; +type HasOptionsType = + unknown extends StorageMethodOptions["hasOptions"] + ? TransactionOptions + : StorageMethodOptions["hasOptions"]; + +export interface Storage< + T extends StorageValue = StorageValue, + DriverT extends Driver = Driver, +> { // Item hasItem< U extends Extract, K extends keyof StorageItemMap, >( key: K, - opts?: TransactionOptions + opts?: HasOptionsType ): Promise; - hasItem(key: string, opts?: TransactionOptions): Promise; + hasItem(key: string, opts?: HasOptionsType): Promise; getItem< U extends Extract, K extends string & keyof StorageItemMap, >( key: K, - ops?: TransactionOptions + opts?: GetOptionsType ): Promise | null>; getItem>( key: string, - opts?: TransactionOptions + opts?: GetOptionsType ): Promise; /** @experimental */ getItems: ( - items: (string | { key: string; options?: TransactionOptions })[], - commonOptions?: TransactionOptions + items: (string | { key: string; options?: GetOptionsType })[], + commonOptions?: GetOptionsType ) => Promise<{ key: string; value: U }[]>; /** @experimental See https://github.com/unjs/unstorage/issues/142 */ getItemRaw: ( key: string, - opts?: TransactionOptions + opts?: GetOptionsType ) => Promise | null>; setItem< @@ -123,24 +186,24 @@ export interface Storage { >( key: K, value: StorageItemType, - opts?: TransactionOptions + opts?: SetOptionsType ): Promise; setItem( key: string, value: U, - opts?: TransactionOptions + opts?: SetOptionsType ): Promise; /** @experimental */ setItems: ( - items: { key: string; value: U; options?: TransactionOptions }[], - commonOptions?: TransactionOptions + items: { key: string; value: U; options?: SetOptionsType }[], + commonOptions?: SetOptionsType ) => Promise; /** @experimental See https://github.com/unjs/unstorage/issues/142 */ setItemRaw: ( key: string, value: MaybeDefined, - opts?: TransactionOptions + opts?: SetOptionsType ) => Promise; removeItem< @@ -149,13 +212,13 @@ export interface Storage { >( key: K, opts?: - | (TransactionOptions & { removeMeta?: boolean }) + | (RemoveOptionsType & { removeMeta?: boolean }) | boolean /* legacy: removeMeta */ ): Promise; removeItem( key: string, opts?: - | (TransactionOptions & { removeMeta?: boolean }) + | (RemoveOptionsType & { removeMeta?: boolean }) | boolean /* legacy: removeMeta */ ): Promise; @@ -163,19 +226,22 @@ export interface Storage { getMeta: ( key: string, opts?: - | (TransactionOptions & { nativeOnly?: boolean }) + | (GetOptionsType & { nativeOnly?: boolean }) | boolean /* legacy: nativeOnly */ ) => MaybePromise; setMeta: ( key: string, value: StorageMeta, - opts?: TransactionOptions + opts?: SetOptionsType ) => Promise; - removeMeta: (key: string, opts?: TransactionOptions) => Promise; + removeMeta: (key: string, opts?: RemoveOptionsType) => Promise; // Keys - getKeys: (base?: string, opts?: GetKeysOptions) => Promise; + getKeys: ( + base?: string, + opts?: GetKeysOptionsType + ) => Promise; // Utils - clear: (base?: string, opts?: TransactionOptions) => Promise; + clear: (base?: string, opts?: RemoveOptionsType) => Promise; dispose: () => Promise; watch: (callback: WatchCallback) => Promise; unwatch: () => Promise; @@ -189,9 +255,9 @@ export interface Storage { ) => { base: string; driver: Driver }[]; // Aliases keys: Storage["getKeys"]; - get: Storage["getItem"]; - set: Storage["setItem"]; - has: Storage["hasItem"]; - del: Storage["removeItem"]; - remove: Storage["removeItem"]; + get: Storage["getItem"]; + set: Storage["setItem"]; + has: Storage["hasItem"]; + del: Storage["removeItem"]; + remove: Storage["removeItem"]; } diff --git a/test/drivers/utils.ts b/test/drivers/utils.ts index 9edd7fbc7..92eaaffa7 100644 --- a/test/drivers/utils.ts +++ b/test/drivers/utils.ts @@ -12,7 +12,7 @@ export interface TestContext { } export interface TestOptions { - driver: Driver | (() => Driver); + driver: Driver | (() => Driver); noKeysSupport?: boolean; additionalTests?: (ctx: TestContext) => void; }