Skip to content

Commit

Permalink
feat: strongly type extended drivers
Browse files Browse the repository at this point in the history
Currently, a driver can define its own method signatures but they will
be lost thanks to `DriverFactory` returning a `Driver` rather than the
original type.

This means things like the netlify driver never actually exposed their
extended types (e.g. special options in `getKeys`).

With these changes, a driver can implement `Driver` but add its own
types on top which are then exposed.
  • Loading branch information
43081j committed Dec 29, 2024
1 parent 990e776 commit 65689d8
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 225 deletions.
82 changes: 40 additions & 42 deletions src/drivers/capacitor-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,44 @@ export interface CapacitorPreferencesOptions {
base?: string;
}

export default defineDriver<CapacitorPreferencesOptions, typeof Preferences>(
(opts) => {
const base = normalizeKey(opts?.base || "");
const resolveKey = (key: string) => joinKeys(base, key);
export default defineDriver((opts: CapacitorPreferencesOptions) => {
const base = normalizeKey(opts?.base || "");
const resolveKey = (key: string) => joinKeys(base, 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 }))
);
},
};
}
);
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 }))
);
},
};
});
2 changes: 1 addition & 1 deletion src/drivers/cloudflare-kv-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ type CloudflareAuthorizationHeaders =

const DRIVER_NAME = "cloudflare-kv-http";

export default defineDriver<KVHTTPOptions>((opts) => {
export default defineDriver((opts: KVHTTPOptions) => {
if (!opts.accountId) {
throw createRequiredError(DRIVER_NAME, "accountId");
}
Expand Down
28 changes: 13 additions & 15 deletions src/drivers/deno-kv-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ export interface DenoKvNodeOptions {

const DRIVER_NAME = "deno-kv-node";

export default defineDriver<DenoKvNodeOptions, Kv | Promise<Kv>>(
(opts: DenoKvNodeOptions = {}) => {
const baseDriver = denoKV({
...opts,
openKv: () => openKv(opts.path, opts.openKvOptions),
});
return {
...baseDriver,
getInstance() {
return baseDriver.getInstance!() as Promise<Kv>;
},
name: DRIVER_NAME,
};
}
);
export default defineDriver((opts: DenoKvNodeOptions = {}) => {
const baseDriver = denoKV({
...opts,
openKv: () => openKv(opts.path, opts.openKvOptions),
});
return {
...baseDriver,
getInstance() {
return baseDriver.getInstance!() as Promise<Kv>;
},

Check warning on line 24 in src/drivers/deno-kv-node.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv-node.ts#L23-L24

Added lines #L23 - L24 were not covered by tests
name: DRIVER_NAME,
};
});
180 changes: 88 additions & 92 deletions src/drivers/deno-kv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,100 +12,96 @@ export interface DenoKvOptions {

const DRIVER_NAME = "deno-kv";

export default defineDriver<DenoKvOptions, Promise<Deno.Kv | Kv>>(
(opts: DenoKvOptions = {}) => {
const basePrefix: KvKey = opts.base
? normalizeKey(opts.base).split(":")
: [];
export default defineDriver((opts: DenoKvOptions = {}) => {
const basePrefix: KvKey = opts.base ? normalizeKey(opts.base).split(":") : [];

const r = (key: string = ""): KvKey =>
[...basePrefix, ...key.split(":")].filter(Boolean);
const r = (key: string = ""): KvKey =>
[...basePrefix, ...key.split(":")].filter(Boolean);

let _kv: Promise<Kv | Deno.Kv> | undefined;
const getKv = () => {
if (_kv) {
return _kv;
let _kv: Promise<Kv | Deno.Kv> | undefined;
const getKv = () => {
if (_kv) {
return _kv;
}
if (opts.openKv) {
_kv = opts.openKv();
} else {
if (!globalThis.Deno) {
throw createError(
DRIVER_NAME,
"Missing global `Deno`. Are you running in Deno? (hint: use `deno-kv-node` driver for Node.js)"
);

Check warning on line 33 in src/drivers/deno-kv.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv.ts#L29-L33

Added lines #L29 - L33 were not covered by tests
}
if (opts.openKv) {
_kv = opts.openKv();
} else {
if (!globalThis.Deno) {
throw createError(
DRIVER_NAME,
"Missing global `Deno`. Are you running in Deno? (hint: use `deno-kv-node` driver for Node.js)"
);
}
if (!Deno.openKv) {
throw createError(
DRIVER_NAME,
"Missing `Deno.openKv`. Are you running Deno with --unstable-kv?"
);
}
_kv = Deno.openKv(opts.path);
if (!Deno.openKv) {
throw createError(
DRIVER_NAME,
"Missing `Deno.openKv`. Are you running Deno with --unstable-kv?"
);

Check warning on line 39 in src/drivers/deno-kv.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv.ts#L35-L39

Added lines #L35 - L39 were not covered by tests
}
return _kv;
};
_kv = Deno.openKv(opts.path);

Check warning on line 41 in src/drivers/deno-kv.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv.ts#L41

Added line #L41 was not covered by tests
}
return _kv;
};

return {
name: DRIVER_NAME,
getInstance() {
return getKv();
},
async hasItem(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return !!value.value;
},
async getItem(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return value.value;
},
async getItemRaw(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return value.value;
},
async setItem(key, value) {
const kv = await getKv();
await kv.set(r(key), value);
},
async setItemRaw(key, value) {
const kv = await getKv();
await kv.set(r(key), value);
},
async removeItem(key) {
const kv = await getKv();
await kv.delete(r(key));
},
async getKeys(base) {
const kv = await getKv();
const keys: string[] = [];
for await (const entry of kv.list({ prefix: r(base) })) {
keys.push(
(basePrefix.length > 0
? entry.key.slice(basePrefix.length)
: entry.key
).join(":")
);
}
return keys;
},
async clear(base) {
const kv = await getKv();
const batch = kv.atomic();
for await (const entry of kv.list({ prefix: r(base) })) {
batch.delete(entry.key as KvKey);
}
await batch.commit();
},
async dispose() {
if (_kv) {
const kv = await _kv;
await kv.close();
_kv = undefined;
}
},
};
}
);
return {
name: DRIVER_NAME,
getInstance() {
return getKv();
},

Check warning on line 50 in src/drivers/deno-kv.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv.ts#L49-L50

Added lines #L49 - L50 were not covered by tests
async hasItem(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return !!value.value;
},
async getItem(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return value.value;
},
async getItemRaw(key) {
const kv = await getKv();
const value = await kv.get(r(key));
return value.value;
},
async setItem(key, value) {
const kv = await getKv();
await kv.set(r(key), value);
},
async setItemRaw(key, value) {
const kv = await getKv();
await kv.set(r(key), value);
},
async removeItem(key) {
const kv = await getKv();
await kv.delete(r(key));
},
async getKeys(base) {
const kv = await getKv();
const keys: string[] = [];
for await (const entry of kv.list({ prefix: r(base) })) {
keys.push(
(basePrefix.length > 0
? entry.key.slice(basePrefix.length)
: entry.key

Check warning on line 85 in src/drivers/deno-kv.ts

View check run for this annotation

Codecov / codecov/patch

src/drivers/deno-kv.ts#L85

Added line #L85 was not covered by tests
).join(":")
);
}
return keys;
},
async clear(base) {
const kv = await getKv();
const batch = kv.atomic();
for await (const entry of kv.list({ prefix: r(base) })) {
batch.delete(entry.key as KvKey);
}
await batch.commit();
},
async dispose() {
if (_kv) {
const kv = await _kv;
await kv.close();
_kv = undefined;
}
},
};
});
2 changes: 1 addition & 1 deletion src/drivers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const defaultOptions: GithubOptions = {

const DRIVER_NAME = "github";

export default defineDriver<GithubOptions>((_opts) => {
export default defineDriver((_opts: GithubOptions) => {
const opts: GithubOptions = { ...defaultOptions, ..._opts };
const rawUrl = joinURL(opts.cdnURL!, opts.repo, opts.branch!, opts.dir!);

Expand Down
2 changes: 1 addition & 1 deletion src/drivers/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineDriver } from "./utils";

const DRIVER_NAME = "memory";

export default defineDriver<void, Map<string, any>>(() => {
export default defineDriver(() => {
const data = new Map<string, any>();

return {
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/null.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineDriver } from "./utils";

const DRIVER_NAME = "null";

export default defineDriver<void>(() => {
export default defineDriver(() => {
return {
name: DRIVER_NAME,
hasItem() {
Expand Down
2 changes: 1 addition & 1 deletion src/drivers/session-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const DRIVER_NAME = "session-storage";

export default defineDriver((opts: SessionStorageOptions = {}) => {
return {
name: DRIVER_NAME,
...localstorage({
windowKey: "sessionStorage",
...opts,
}),
name: DRIVER_NAME,
};
});
2 changes: 1 addition & 1 deletion src/drivers/uploadthing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface UploadThingOptions extends UTApiOptions {

const DRIVER_NAME = "uploadthing";

export default defineDriver<UploadThingOptions, UTApi>((opts = {}) => {
export default defineDriver((opts: UploadThingOptions = {}) => {
let client: UTApi;

const base = opts.base ? normalizeKey(opts.base) : "";
Expand Down
Loading

0 comments on commit 65689d8

Please sign in to comment.