Skip to content

Commit

Permalink
feat(url): new loadFromUrl(url, {drivers}) function
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoine Monnet committed Jan 30, 2025
1 parent 50cc28a commit 4a3b618
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 0 deletions.
12 changes: 12 additions & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ export default defineBuildConfig({
ext: "cjs",
declaration: false,
},
{
input: "src/loader/",
outDir: "dist/loader",
format: "esm",
},
{
input: "src/loader/",
outDir: "dist/loader",
format: "cjs",
ext: "cjs",
declaration: false,
},
],
externals: ["mongodb", "unstorage", /unstorage\/drivers\//],
});
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"types": "./dist/server.d.ts",
"import": "./dist/server.mjs",
"require": "./dist/server.cjs"
},
"./loader/*": {
"types": "./dist/loader/*.d.ts",
"import": "./dist/loader/*.mjs",
"require": "./dist/loader/*.cjs"
}
},
"main": "./dist/index.cjs",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./storage";
export * from "./types";
export * from "./utils";
export * from "./loader";

export { defineDriver } from "./drivers/utils";

Expand Down
19 changes: 19 additions & 0 deletions src/loader/_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function coerceQuery(query: Record<string, string | string[]>) {
return Object.fromEntries(
Object.entries(query).map(([key, value]: [string, string | string[]]) => {
return [key, coerceValue(value)];
})
);
}

function coerceValue(value: string | string[]): any {
if (Array.isArray(value)) return value.map((v) => coerceValue(v));
else if (["true", "false"].includes(value.toLowerCase()))
return Boolean(value);
else if (value != "" && !Number.isNaN(Number(value))) return Number(value);
try {
return JSON.parse(value);
} catch {
return value;
}
}
38 changes: 38 additions & 0 deletions src/loader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { parseQuery } from "ufo";
import { createError } from "../drivers/utils";
import { coerceQuery } from "./_utils";
import type { Driver, DriverFactory, AsyncDriverFactory } from "../types";

const RE = /^(?<scheme>[^:]+):(?<base>[^?]*)?(\?(?<query>.*))?$/;

export async function loadFromUrl(
url: string,
factories: Record<
string,
DriverFactory<any, any> | AsyncDriverFactory<any, any>
>
): Promise<Driver<any, any>> {
const match = url.match(RE);
if (!match?.groups) throw createError("load-from-url", `invalid url ${url}`);

const { scheme, base, query } = match.groups as {
scheme: string;
base?: string;
query?: string;
};

const factory = factories[scheme];
if (!factory)
throw createError(
"load-from-url",
`no driver handle scheme for url ${url}`
);

const opts = {
base,
scheme,
...coerceQuery(parseQuery(query)),
};

return await factory(opts);
}
50 changes: 50 additions & 0 deletions test/loader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, expect } from "vitest";
import { defineDriver } from "../src";
import { loadFromUrl } from "../src/loader";

interface Options {
scheme: string;
base?: string;
string?: string;
number?: number;
boolean?: boolean;
array?: string[];
object?: Record<string, string>;
}

const test = defineDriver<Options, any>((options: Options) => ({
name: "test",
options: options,
hasItem: () => false,
getItem: () => null,
getKeys: () => [],
}));

describe("loader", () => {
it("invalid url", () => {
expect(async () =>
loadFromUrl("not-a-url", { proto: test })
).rejects.toThrowError("invalid url");
});

it("missing driver", () => {
expect(async () =>
loadFromUrl("no:", { proto: test })
).rejects.toThrowError("no driver handle scheme for url");
});

it("load driver", async () => {
const driver = await loadFromUrl(
'proto:abc?string=def&number=1&boolean=true&array=[2,3,4]&object={"h":5,"i":6,"j":"7"}',
{ proto: test }
);
expect(driver.name).toBe("test");
expect(driver.options.scheme).toBe("proto");
expect(driver.options.base).toBe("abc");
expect(driver.options.string).toBe("def");
expect(driver.options.number).toBe(1);
expect(driver.options.boolean).toBe(true);
expect(driver.options.array).toStrictEqual([2, 3, 4]);
expect(driver.options.object).toStrictEqual({ h: 5, i: 6, j: "7" });
});
});

0 comments on commit 4a3b618

Please sign in to comment.