From 6c2e5795962a21a2294776636bfb72ccadb16d8c Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 09:02:18 +0200 Subject: [PATCH 01/39] connection: rewrote query execution, batching, added generic sqlite, added node/bun sqlite, aligned repo/mutator results --- app/__test__/App.spec.ts | 2 +- app/__test__/api/DataApi.spec.ts | 2 +- app/__test__/data/DataController.spec.ts | 103 +++------ app/__test__/data/data.test.ts | 13 +- app/__test__/data/mutation.simple.test.ts | 8 +- app/__test__/data/specs/Mutator.spec.ts | 7 +- app/__test__/data/specs/Repository.spec.ts | 154 +++---------- app/__test__/helper.ts | 5 +- app/package.json | 5 +- .../connection/BunSqliteConnection.spec.ts | 12 + .../bun/connection/BunSqliteConnection.ts | 41 ++++ app/src/adapter/bun/test.ts | 3 +- .../cloudflare/connection/D1Connection.ts | 51 ++--- .../node/connection/NodeSqliteConnection.ts | 46 ++++ .../NodeSqliteConnection.vi-test.ts | 11 + ...native-spec.ts => node.adapter.vi-test.ts} | 10 +- app/src/adapter/node/test.ts | 3 +- app/src/adapter/node/vitest.ts | 50 ++++ app/src/core/test/index.ts | 1 + app/src/data/api/DataController.ts | 53 +---- app/src/data/connection/Connection.ts | 94 +++++--- .../data/connection/connection-test-suite.ts | 187 +++++++++++++++ .../sqlite/GenericSqliteConnection.ts | 37 +++ .../sqlite/LibsqlConnection.spec.ts | 11 + .../connection/sqlite/LibsqlConnection.ts | 70 ++---- .../connection/sqlite/SqliteConnection.ts | 43 +++- .../sqlite/SqliteLocalConnection.ts | 33 +-- app/src/data/entities/EntityManager.ts | 3 +- app/src/data/entities/Result.ts | 126 ++++++++++ app/src/data/entities/index.ts | 2 +- .../data/entities/{ => mutation}/Mutator.ts | 80 +++---- .../data/entities/mutation/MutatorResult.ts | 33 +++ app/src/data/entities/query/Repository.ts | 215 ++++-------------- .../data/entities/query/RepositoryResult.ts | 105 +++++++++ app/src/data/relations/ManyToOneRelation.ts | 2 +- app/src/data/relations/RelationMutator.ts | 2 +- app/src/modules/server/SystemController.ts | 5 + app/vite.dev.ts | 4 + app/vitest.config.ts | 2 +- bun.lock | 5 +- 40 files changed, 990 insertions(+), 649 deletions(-) create mode 100644 app/src/adapter/bun/connection/BunSqliteConnection.spec.ts create mode 100644 app/src/adapter/bun/connection/BunSqliteConnection.ts create mode 100644 app/src/adapter/node/connection/NodeSqliteConnection.ts create mode 100644 app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts rename app/src/adapter/node/{node.adapter.native-spec.ts => node.adapter.vi-test.ts} (57%) create mode 100644 app/src/adapter/node/vitest.ts create mode 100644 app/src/data/connection/connection-test-suite.ts create mode 100644 app/src/data/connection/sqlite/GenericSqliteConnection.ts create mode 100644 app/src/data/connection/sqlite/LibsqlConnection.spec.ts create mode 100644 app/src/data/entities/Result.ts rename app/src/data/entities/{ => mutation}/Mutator.ts (84%) create mode 100644 app/src/data/entities/mutation/MutatorResult.ts create mode 100644 app/src/data/entities/query/RepositoryResult.ts diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 79fdc51b..3b36c853 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, describe, expect, test } from "bun:test"; +import { afterEach, describe, test } from "bun:test"; import { App } from "../src"; import { getDummyConnection } from "./helper"; diff --git a/app/__test__/api/DataApi.spec.ts b/app/__test__/api/DataApi.spec.ts index 51786ca8..5be5e1e6 100644 --- a/app/__test__/api/DataApi.spec.ts +++ b/app/__test__/api/DataApi.spec.ts @@ -153,7 +153,7 @@ describe("DataApi", () => { const oneBy = api.readOneBy("posts", { where: { title: "baz" }, select: ["title"] }); const oneByRes = await oneBy; expect(oneByRes.data).toEqual({ title: "baz" } as any); - expect(oneByRes.body.meta.count).toEqual(1); + expect(oneByRes.body.meta.items).toEqual(1); }); it("exists/count", async () => { diff --git a/app/__test__/data/DataController.spec.ts b/app/__test__/data/DataController.spec.ts index 21ae226f..96c30c6c 100644 --- a/app/__test__/data/DataController.spec.ts +++ b/app/__test__/data/DataController.spec.ts @@ -7,13 +7,13 @@ import { type EntityData, EntityManager, ManyToOneRelation, - type MutatorResponse, - type RepositoryResponse, TextField, } from "../../src/data"; import { DataController } from "../../src/data/api/DataController"; import { dataConfigSchema } from "../../src/data/data-schema"; import { disableConsoleLog, enableConsoleLog, getDummyConnection } from "../helper"; +import type { RepositoryResultJSON } from "data/entities/query/RepositoryResult"; +import type { MutatorResultJSON } from "data/entities/mutation/MutatorResult"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); beforeAll(() => disableConsoleLog(["log", "warn"])); @@ -21,52 +21,6 @@ afterAll(async () => (await afterAllCleanup()) && enableConsoleLog()); const dataConfig = parse(dataConfigSchema, {}); describe("[data] DataController", async () => { - test("repoResult", async () => { - const em = new EntityManager([], dummyConnection); - const ctx: any = { em, guard: new Guard() }; - const controller = new DataController(ctx, dataConfig); - - const res = controller.repoResult({ - entity: null as any, - data: [] as any, - sql: "", - parameters: [] as any, - result: [] as any, - meta: { - total: 0, - count: 0, - items: 0, - }, - }); - - expect(res).toEqual({ - meta: { - total: 0, - count: 0, - items: 0, - }, - data: [], - }); - }); - - test("mutatorResult", async () => { - const em = new EntityManager([], dummyConnection); - const ctx: any = { em, guard: new Guard() }; - const controller = new DataController(ctx, dataConfig); - - const res = controller.mutatorResult({ - entity: null as any, - data: [] as any, - sql: "", - parameters: [] as any, - result: [] as any, - }); - - expect(res).toEqual({ - data: [], - }); - }); - describe("getController", async () => { const users = new Entity("users", [ new TextField("name", { required: true }), @@ -120,8 +74,7 @@ describe("[data] DataController", async () => { method: "POST", body: JSON.stringify(_user), }); - //console.log("res", { _user }, res); - const result = (await res.json()) as MutatorResponse; + const result = (await res.json()) as MutatorResultJSON; const { id, ...data } = result.data as any; expect(res.status).toBe(201); @@ -135,7 +88,7 @@ describe("[data] DataController", async () => { method: "POST", body: JSON.stringify(_post), }); - const result = (await res.json()) as MutatorResponse; + const result = (await res.json()) as MutatorResultJSON; const { id, ...data } = result.data as any; expect(res.status).toBe(201); @@ -146,13 +99,13 @@ describe("[data] DataController", async () => { test("/:entity (read many)", async () => { const res = await app.request("/entity/users"); - const data = (await res.json()) as RepositoryResponse; + const data = (await res.json()) as RepositoryResultJSON; - expect(data.meta.total).toBe(3); - expect(data.meta.count).toBe(3); + //expect(data.meta.total).toBe(3); + //expect(data.meta.count).toBe(3); expect(data.meta.items).toBe(3); expect(data.data.length).toBe(3); - expect(data.data[0].name).toBe("foo"); + expect(data.data[0]?.name).toBe("foo"); }); test("/:entity/query (func query)", async () => { @@ -165,33 +118,32 @@ describe("[data] DataController", async () => { where: { bio: { $isnull: 1 } }, }), }); - const data = (await res.json()) as RepositoryResponse; + const data = (await res.json()) as RepositoryResultJSON; - expect(data.meta.total).toBe(3); - expect(data.meta.count).toBe(1); + //expect(data.meta.total).toBe(3); + //expect(data.meta.count).toBe(1); expect(data.meta.items).toBe(1); expect(data.data.length).toBe(1); - expect(data.data[0].name).toBe("bar"); + expect(data.data[0]?.name).toBe("bar"); }); test("/:entity (read many, paginated)", async () => { const res = await app.request("/entity/users?limit=1&offset=2"); - const data = (await res.json()) as RepositoryResponse; + const data = (await res.json()) as RepositoryResultJSON; - expect(data.meta.total).toBe(3); - expect(data.meta.count).toBe(3); + //expect(data.meta.total).toBe(3); + //expect(data.meta.count).toBe(3); expect(data.meta.items).toBe(1); expect(data.data.length).toBe(1); - expect(data.data[0].name).toBe("baz"); + expect(data.data[0]?.name).toBe("baz"); }); test("/:entity/:id (read one)", async () => { const res = await app.request("/entity/users/3"); - const data = (await res.json()) as RepositoryResponse; - console.log("data", data); + const data = (await res.json()) as RepositoryResultJSON; - expect(data.meta.total).toBe(3); - expect(data.meta.count).toBe(1); + //expect(data.meta.total).toBe(3); + //expect(data.meta.count).toBe(1); expect(data.meta.items).toBe(1); expect(data.data).toEqual({ id: 3, ...fixtures.users[2] }); }); @@ -201,7 +153,7 @@ describe("[data] DataController", async () => { method: "PATCH", body: JSON.stringify({ name: "new name" }), }); - const { data } = (await res.json()) as MutatorResponse; + const { data } = (await res.json()) as MutatorResultJSON; expect(res.ok).toBe(true); expect(data as any).toEqual({ id: 3, ...fixtures.users[2], name: "new name" }); @@ -209,27 +161,26 @@ describe("[data] DataController", async () => { test("/:entity/:id/:reference (read references)", async () => { const res = await app.request("/entity/users/1/posts"); - const data = (await res.json()) as RepositoryResponse; - console.log("data", data); + const data = (await res.json()) as RepositoryResultJSON; - expect(data.meta.total).toBe(2); - expect(data.meta.count).toBe(1); + //expect(data.meta.total).toBe(2); + //expect(data.meta.count).toBe(1); expect(data.meta.items).toBe(1); expect(data.data.length).toBe(1); - expect(data.data[0].content).toBe("post 1"); + expect(data.data[0]?.content).toBe("post 1"); }); test("/:entity/:id (delete one)", async () => { const res = await app.request("/entity/posts/2", { method: "DELETE", }); - const { data } = (await res.json()) as RepositoryResponse; + const { data } = (await res.json()) as RepositoryResultJSON; expect(data).toEqual({ id: 2, ...fixtures.posts[1] }); // verify const res2 = await app.request("/entity/posts"); - const data2 = (await res2.json()) as RepositoryResponse; - expect(data2.meta.total).toBe(1); + const data2 = (await res2.json()) as RepositoryResultJSON; + //expect(data2.meta.total).toBe(1); }); }); }); diff --git a/app/__test__/data/data.test.ts b/app/__test__/data/data.test.ts index 79b43016..8032167f 100644 --- a/app/__test__/data/data.test.ts +++ b/app/__test__/data/data.test.ts @@ -34,19 +34,12 @@ describe("some tests", async () => { test("findId", async () => { const query = await em.repository(users).findId(1); - /*const { result, total, count, time } = query; - console.log("query", query.result, { - result, - total, - count, - time, - });*/ expect(query.sql).toBe( 'select "users"."id" as "id", "users"."username" as "username", "users"."email" as "email" from "users" where "id" = ? limit ?', ); expect(query.parameters).toEqual([1, 1]); - expect(query.result).toEqual([]); + expect(query.data).toBeUndefined(); }); test("findMany", async () => { @@ -56,7 +49,7 @@ describe("some tests", async () => { 'select "users"."id" as "id", "users"."username" as "username", "users"."email" as "email" from "users" order by "users"."id" asc limit ? offset ?', ); expect(query.parameters).toEqual([10, 0]); - expect(query.result).toEqual([]); + expect(query.data).toEqual([]); }); test("findMany with number", async () => { @@ -66,7 +59,7 @@ describe("some tests", async () => { 'select "posts"."id" as "id", "posts"."title" as "title", "posts"."content" as "content", "posts"."created_at" as "created_at", "posts"."likes" as "likes" from "posts" order by "posts"."id" asc limit ? offset ?', ); expect(query.parameters).toEqual([10, 0]); - expect(query.result).toEqual([]); + expect(query.data).toEqual([]); }); test("try adding an existing field name", async () => { diff --git a/app/__test__/data/mutation.simple.test.ts b/app/__test__/data/mutation.simple.test.ts index f4257339..93b17da2 100644 --- a/app/__test__/data/mutation.simple.test.ts +++ b/app/__test__/data/mutation.simple.test.ts @@ -45,7 +45,7 @@ describe("Mutator simple", async () => { }, }); - expect(query.result).toEqual([{ id: 1, label: "test", count: 1 }]); + expect(query.data).toEqual([{ id: 1, label: "test", count: 1 }]); }); test("update inserted row", async () => { @@ -87,7 +87,7 @@ describe("Mutator simple", async () => { expect(mutation.data).toEqual({ id, label: "new label", count: 100 }); const query2 = await em.repository(items).findId(id); - expect(query2.result.length).toBe(0); + expect(query2.data).toBeUndefined(); }); test("validation: insert incomplete row", async () => { @@ -177,13 +177,13 @@ describe("Mutator simple", async () => { }); test("insertMany", async () => { - const oldCount = (await em.repo(items).count()).count; + const oldCount = (await em.repo(items).count()).data.count; const inserts = [{ label: "insert 1" }, { label: "insert 2" }]; const { data } = await em.mutator(items).insertMany(inserts); expect(data.length).toBe(2); expect(data.map((d) => ({ label: d.label }))).toEqual(inserts); - const newCount = (await em.repo(items).count()).count; + const newCount = (await em.repo(items).count()).data.count; expect(newCount).toBe(oldCount + inserts.length); const { data: data2 } = await em.repo(items).findMany({ offset: oldCount }); diff --git a/app/__test__/data/specs/Mutator.spec.ts b/app/__test__/data/specs/Mutator.spec.ts index 7110956d..d7f09e02 100644 --- a/app/__test__/data/specs/Mutator.spec.ts +++ b/app/__test__/data/specs/Mutator.spec.ts @@ -1,4 +1,4 @@ -import { afterAll, describe, expect, test } from "bun:test"; +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import type { EventManager } from "../../../src/core/events"; import { Entity, @@ -12,11 +12,14 @@ import { TextField, } from "../../../src/data"; import * as proto from "../../../src/data/prototype"; -import { getDummyConnection } from "../helper"; +import { getDummyConnection, disableConsoleLog, enableConsoleLog } from "../../helper"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); afterAll(afterAllCleanup); +beforeAll(() => disableConsoleLog(["log", "warn"])); +afterAll(async () => (await afterAllCleanup()) && enableConsoleLog()); + describe("[data] Mutator (base)", async () => { const entity = new Entity("items", [ new TextField("label", { required: true }), diff --git a/app/__test__/data/specs/Repository.spec.ts b/app/__test__/data/specs/Repository.spec.ts index 54254d45..23f094c2 100644 --- a/app/__test__/data/specs/Repository.spec.ts +++ b/app/__test__/data/specs/Repository.spec.ts @@ -26,120 +26,6 @@ async function sleep(ms: number) { } describe("[Repository]", async () => { - test.skip("bulk", async () => { - //const connection = dummyConnection; - //const connection = getLocalLibsqlConnection(); - const credentials = null as any; // @todo: determine what to do here - const connection = new LibsqlConnection(credentials); - - const em = new EntityManager([], connection); - /*const emLibsql = new EntityManager([], { - url: connection.url.replace("https", "libsql"), - authToken: connection.authToken, - });*/ - const table = "posts"; - - const client = connection.getClient(); - if (!client) { - console.log("Cannot perform test without libsql connection"); - return; - } - - const conn = em.connection.kysely; - const selectQ = (e: E) => e.selectFrom(table).selectAll().limit(2); - const countQ = (e: E) => e.selectFrom(table).select(e.fn.count("*").as("count")); - - async function executeTransaction(em: EntityManager) { - return await em.connection.kysely.transaction().execute(async (e) => { - const res = await selectQ(e).execute(); - const count = await countQ(e).execute(); - - return [res, count]; - }); - } - - async function executeBatch(em: EntityManager) { - const queries = [selectQ(conn), countQ(conn)]; - return await em.connection.batchQuery(queries); - } - - async function executeSingleKysely(em: EntityManager) { - const res = await selectQ(conn).execute(); - const count = await countQ(conn).execute(); - return [res, count]; - } - - async function executeSingleClient(em: EntityManager) { - const q1 = selectQ(conn).compile(); - const res = await client.execute({ - sql: q1.sql, - args: q1.parameters as any, - }); - - const q2 = countQ(conn).compile(); - const count = await client.execute({ - sql: q2.sql, - args: q2.parameters as any, - }); - return [res, count]; - } - - const transaction = await executeTransaction(em); - const batch = await executeBatch(em); - - expect(batch).toEqual(transaction as any); - - const testperf = false; - if (testperf) { - const times = 5; - - const exec = async ( - name: string, - fn: (em: EntityManager) => Promise, - em: EntityManager, - ) => { - const res = await Perf.execute(() => fn(em), times); - await sleep(1000); - const info = { - name, - total: res.total.toFixed(2), - avg: (res.total / times).toFixed(2), - first: res.marks[0].time.toFixed(2), - last: res.marks[res.marks.length - 1].time.toFixed(2), - }; - console.log(info.name, info, res.marks); - return info; - }; - - const data: any[] = []; - data.push(await exec("transaction.http", executeTransaction, em)); - data.push(await exec("bulk.http", executeBatch, em)); - data.push(await exec("singleKy.http", executeSingleKysely, em)); - data.push(await exec("singleCl.http", executeSingleClient, em)); - - /*data.push(await exec("transaction.libsql", executeTransaction, emLibsql)); - data.push(await exec("bulk.libsql", executeBatch, emLibsql)); - data.push(await exec("singleKy.libsql", executeSingleKysely, emLibsql)); - data.push(await exec("singleCl.libsql", executeSingleClient, emLibsql));*/ - - console.table(data); - /** - * ┌───┬────────────────────┬────────┬────────┬────────┬────────┐ - * │ │ name │ total │ avg │ first │ last │ - * ├───┼────────────────────┼────────┼────────┼────────┼────────┤ - * │ 0 │ transaction.http │ 681.29 │ 136.26 │ 136.46 │ 396.09 │ - * │ 1 │ bulk.http │ 164.82 │ 32.96 │ 32.95 │ 99.91 │ - * │ 2 │ singleKy.http │ 330.01 │ 66.00 │ 65.86 │ 195.41 │ - * │ 3 │ singleCl.http │ 326.17 │ 65.23 │ 61.32 │ 198.08 │ - * │ 4 │ transaction.libsql │ 856.79 │ 171.36 │ 132.31 │ 595.24 │ - * │ 5 │ bulk.libsql │ 180.63 │ 36.13 │ 35.39 │ 107.71 │ - * │ 6 │ singleKy.libsql │ 347.11 │ 69.42 │ 65.00 │ 207.14 │ - * │ 7 │ singleCl.libsql │ 328.60 │ 65.72 │ 62.19 │ 195.04 │ - * └───┴────────────────────┴────────┴────────┴────────┴────────┘ - */ - } - }); - test("count & exists", async () => { const items = new Entity("items", [new TextField("label")]); const em = new EntityManager([items], dummyConnection); @@ -160,25 +46,44 @@ describe("[Repository]", async () => { // count all const res = await em.repository(items).count(); expect(res.sql).toBe('select count(*) as "count" from "items"'); - expect(res.count).toBe(3); + expect(res.data.count).toBe(3); + + // + { + const res = await em.repository(items).findMany(); + expect(res.count).toBeUndefined(); + } + + { + const res = await em + .repository(items, { + includeCounts: true, + }) + .findMany(); + expect(res.count).toBe(3); + } // count filtered - const res2 = await em.repository(items).count({ label: { $in: ["a", "b"] } }); + const res2 = await em + .repository(items, { + includeCounts: true, + }) + .count({ label: { $in: ["a", "b"] } }); expect(res2.sql).toBe('select count(*) as "count" from "items" where "label" in (?, ?)'); expect(res2.parameters).toEqual(["a", "b"]); - expect(res2.count).toBe(2); + expect(res2.data.count).toBe(2); // check exists const res3 = await em.repository(items).exists({ label: "a" }); - expect(res3.exists).toBe(true); + expect(res3.data.exists).toBe(true); const res4 = await em.repository(items).exists({ label: "d" }); - expect(res4.exists).toBe(false); + expect(res4.data.exists).toBe(false); // for now, allow empty filter const res5 = await em.repository(items).exists({}); - expect(res5.exists).toBe(true); + expect(res5.data.exists).toBe(true); }); test("option: silent", async () => { @@ -191,6 +96,9 @@ describe("[Repository]", async () => { // should throw because table doesn't exist expect(em.repo("items").findMany({})).rejects.toThrow(/no such table/); // should silently return empty result + em.repo("items", { silent: true }) + .findMany({}) + .then((r) => r.data); expect( em .repo("items", { silent: true }) @@ -209,16 +117,16 @@ describe("[Repository]", async () => { expect( em - .repo("items") + .repo("items", { includeCounts: true }) .findMany({}) - .then((r) => [r.meta.count, r.meta.total]), + .then((r) => [r.count, r.total]), ).resolves.toEqual([0, 0]); expect( em .repo("items", { includeCounts: false }) .findMany({}) - .then((r) => [r.meta.count, r.meta.total]), + .then((r) => [r.count, r.total]), ).resolves.toEqual([undefined, undefined]); }); }); diff --git a/app/__test__/helper.ts b/app/__test__/helper.ts index 16b8b8ee..ba09d4cb 100644 --- a/app/__test__/helper.ts +++ b/app/__test__/helper.ts @@ -38,14 +38,15 @@ export function getLocalLibsqlConnection() { return { url: "http://127.0.0.1:8080" }; } -type ConsoleSeverity = "log" | "warn" | "error"; +type ConsoleSeverity = "debug" | "log" | "warn" | "error"; const _oldConsoles = { + debug: console.debug, log: console.log, warn: console.warn, error: console.error, }; -export function disableConsoleLog(severities: ConsoleSeverity[] = ["log", "warn"]) { +export function disableConsoleLog(severities: ConsoleSeverity[] = ["debug", "log", "warn"]) { severities.forEach((severity) => { console[severity] = () => null; }); diff --git a/app/package.json b/app/package.json index fc458c81..c8d326e4 100644 --- a/app/package.json +++ b/app/package.json @@ -76,6 +76,7 @@ "devDependencies": { "@aws-sdk/client-s3": "^3.758.0", "@bluwy/giget-core": "^0.1.2", + "@cloudflare/workers-types": "^4.20250606.0", "@dagrejs/dagre": "^1.1.4", "@hono/typebox-validator": "^0.3.3", "@hono/vite-dev-server": "^0.19.1", @@ -102,6 +103,7 @@ "jsdom": "^26.0.0", "jsonv-ts": "^0.1.0", "kysely-d1": "^0.3.0", + "kysely-generic-sqlite": "^1.2.1", "open": "^10.1.0", "openapi-types": "^12.1.3", "picocolors": "^1.1.1", @@ -124,8 +126,7 @@ "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.9", - "wouter": "^3.6.0", - "@cloudflare/workers-types": "^4.20250606.0" + "wouter": "^3.6.0" }, "optionalDependencies": { "@hono/node-server": "^1.14.3" diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts new file mode 100644 index 00000000..5099fbce --- /dev/null +++ b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts @@ -0,0 +1,12 @@ +import { connectionTestSuite } from "data/connection/connection-test-suite"; +import { bunSqlite } from "./BunSqliteConnection"; +import { bunTestRunner } from "adapter/bun/test"; +import { describe } from "bun:test"; +import { Database } from "bun:sqlite"; + +describe("BunSqliteConnection", () => { + connectionTestSuite(bunTestRunner, { + makeConnection: () => bunSqlite({ database: new Database(":memory:") }), + rawDialectDetails: [], + }); +}); diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts new file mode 100644 index 00000000..dc5638f9 --- /dev/null +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -0,0 +1,41 @@ +import type { Database } from "bun:sqlite"; +import { + buildQueryFn, + GenericSqliteConnection, + parseBigInt, + type IGenericSqlite, +} from "data/connection/sqlite/GenericSqliteConnection"; + +export type BunSqliteConnectionConfig = { + database: Database; +}; + +function bunSqliteExecutor(db: Database, cache: boolean): IGenericSqlite { + const fn = cache ? "query" : "prepare"; + const getStmt = (sql: string) => db[fn](sql); + + return { + db, + query: buildQueryFn({ + all: (sql, parameters) => getStmt(sql).all(...(parameters || [])), + run: (sql, parameters) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...(parameters || [])); + return { + insertId: parseBigInt(lastInsertRowid), + numAffectedRows: parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + }; +} + +export function bunSqlite(config: BunSqliteConnectionConfig) { + return new GenericSqliteConnection( + config.database, + () => bunSqliteExecutor(config.database, false), + { + name: "bun-sqlite", + }, + ); +} diff --git a/app/src/adapter/bun/test.ts b/app/src/adapter/bun/test.ts index 7bd314a6..1cb6ca5b 100644 --- a/app/src/adapter/bun/test.ts +++ b/app/src/adapter/bun/test.ts @@ -1,6 +1,7 @@ -import { expect, test, mock } from "bun:test"; +import { expect, test, mock, describe } from "bun:test"; export const bunTestRunner = { + describe, expect, test, mock, diff --git a/app/src/adapter/cloudflare/connection/D1Connection.ts b/app/src/adapter/cloudflare/connection/D1Connection.ts index ddf6be84..d7eb5a07 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.ts @@ -1,65 +1,42 @@ /// -import { KyselyPluginRunner, SqliteConnection, SqliteIntrospector } from "bknd/data"; -import type { QB } from "data/connection/Connection"; -import { type DatabaseIntrospector, Kysely, ParseJSONResultsPlugin } from "kysely"; +import { SqliteConnection } from "bknd/data"; +import type { ConnQuery, ConnQueryResults } from "data/connection/Connection"; import { D1Dialect } from "kysely-d1"; export type D1ConnectionConfig = { binding: DB; }; -class CustomD1Dialect extends D1Dialect { - override createIntrospector(db: Kysely): DatabaseIntrospector { - return new SqliteIntrospector(db, { - excludeTables: ["_cf_KV", "_cf_METADATA"], - }); - } -} - export class D1Connection< DB extends D1Database | D1DatabaseSession = D1Database, -> extends SqliteConnection { +> extends SqliteConnection { + override name = "sqlite-d1"; + protected override readonly supported = { batching: true, + softscans: false, }; constructor(private config: D1ConnectionConfig) { - const plugins = [new ParseJSONResultsPlugin()]; - - const kysely = new Kysely({ - dialect: new CustomD1Dialect({ database: config.binding as D1Database }), - plugins, + super({ + excludeTables: ["_cf_KV", "_cf_METADATA"], + dialect: D1Dialect, + dialectArgs: [{ database: config.binding as D1Database }], }); - super(kysely, {}, plugins); } - get client(): DB { - return this.config.binding; - } + override async executeQueries(...qbs: O): Promise> { + const compiled = this.getCompiled(...qbs); - protected override async batch( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { const db = this.config.binding; const res = await db.batch( - queries.map((q) => { - const { sql, parameters } = q.compile(); + compiled.map(({ sql, parameters }) => { return db.prepare(sql).bind(...parameters); }), ); - // let it run through plugins - const kyselyPlugins = new KyselyPluginRunner(this.plugins); - const data: any = []; - for (const r of res) { - const rows = await kyselyPlugins.transformResultRows(r.results); - data.push(rows); - } - - return data; + return this.withTransformedRows(res, "results") as any; } } diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts new file mode 100644 index 00000000..b6faad44 --- /dev/null +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -0,0 +1,46 @@ +import { + buildQueryFn, + GenericSqliteConnection, + parseBigInt, + type IGenericSqlite, +} from "../../../data/connection/sqlite/GenericSqliteConnection"; +import type { DatabaseSync } from "node:sqlite"; + +export type NodeSqliteConnectionConfig = { + database: DatabaseSync; +}; + +function nodeSqliteExecutor(db: DatabaseSync): IGenericSqlite { + const getStmt = (sql: string) => { + const stmt = db.prepare(sql); + //stmt.setReadBigInts(true); + return stmt; + }; + + return { + db, + query: buildQueryFn({ + all: (sql, parameters = []) => getStmt(sql).all(...parameters), + run: (sql, parameters = []) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); + return { + insertId: parseBigInt(lastInsertRowid), + numAffectedRows: parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + iterator: (isSelect, sql, parameters = []) => { + if (!isSelect) { + throw new Error("Only support select in stream()"); + } + return getStmt(sql).iterate(...parameters) as any; + }, + }; +} + +export function nodeSqlite(config: NodeSqliteConnectionConfig) { + return new GenericSqliteConnection(config.database, () => nodeSqliteExecutor(config.database), { + name: "node-sqlite", + }); +} diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts new file mode 100644 index 00000000..62ee9cbd --- /dev/null +++ b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts @@ -0,0 +1,11 @@ +import { nodeSqlite } from "./NodeSqliteConnection"; +import { DatabaseSync } from "node:sqlite"; +import { connectionTestSuite } from "data/connection/connection-test-suite"; +import { describe, test, expect } from "vitest"; + +describe("NodeSqliteConnection", () => { + connectionTestSuite({ describe, test, expect } as any, { + makeConnection: () => nodeSqlite({ database: new DatabaseSync(":memory:") }), + rawDialectDetails: [], + }); +}); diff --git a/app/src/adapter/node/node.adapter.native-spec.ts b/app/src/adapter/node/node.adapter.vi-test.ts similarity index 57% rename from app/src/adapter/node/node.adapter.native-spec.ts rename to app/src/adapter/node/node.adapter.vi-test.ts index 62dcc1b5..31cdb317 100644 --- a/app/src/adapter/node/node.adapter.native-spec.ts +++ b/app/src/adapter/node/node.adapter.vi-test.ts @@ -1,14 +1,14 @@ -import { describe, before, after } from "node:test"; +import { describe, beforeAll, afterAll } from "vitest"; import * as node from "./node.adapter"; import { adapterTestSuite } from "adapter/adapter-test-suite"; -import { nodeTestRunner } from "adapter/node/test"; +import { viTestRunner } from "adapter/node/vitest"; import { disableConsoleLog, enableConsoleLog } from "core/utils"; -before(() => disableConsoleLog()); -after(enableConsoleLog); +beforeAll(() => disableConsoleLog()); +afterAll(enableConsoleLog); describe("node adapter", () => { - adapterTestSuite(nodeTestRunner, { + adapterTestSuite(viTestRunner, { makeApp: node.createApp, makeHandler: node.createHandler, }); diff --git a/app/src/adapter/node/test.ts b/app/src/adapter/node/test.ts index 992cbeea..5a634ae3 100644 --- a/app/src/adapter/node/test.ts +++ b/app/src/adapter/node/test.ts @@ -1,5 +1,5 @@ import nodeAssert from "node:assert/strict"; -import { test } from "node:test"; +import { test, describe } from "node:test"; import type { Matcher, Test, TestFn, TestRunner } from "core/test"; // Track mock function calls @@ -85,6 +85,7 @@ nodeTest.skipIf = (condition: boolean): Test => { }; export const nodeTestRunner: TestRunner = { + describe, test: nodeTest, mock: createMockFunction, expect: (actual?: T, failMsg?: string) => ({ diff --git a/app/src/adapter/node/vitest.ts b/app/src/adapter/node/vitest.ts new file mode 100644 index 00000000..569be7a3 --- /dev/null +++ b/app/src/adapter/node/vitest.ts @@ -0,0 +1,50 @@ +import type { TestFn, TestRunner, Test } from "core/test"; +import { describe, test, expect, vi } from "vitest"; + +function vitestTest(label: string, fn: TestFn, options?: any) { + return test(label, fn as any); +} +vitestTest.if = (condition: boolean): Test => { + if (condition) { + return vitestTest; + } + return (() => {}) as any; +}; +vitestTest.skip = (label: string, fn: TestFn) => { + return test.skip(label, fn as any); +}; +vitestTest.skipIf = (condition: boolean): Test => { + if (condition) { + return (() => {}) as any; + } + return vitestTest; +}; + +const vitestExpect = (actual: T, parentFailMsg?: string) => { + return { + toEqual: (expected: T, failMsg = parentFailMsg) => { + expect(actual, failMsg).toEqual(expected); + }, + toBe: (expected: T, failMsg = parentFailMsg) => { + expect(actual, failMsg).toBe(expected); + }, + toBeString: () => expect(typeof actual, parentFailMsg).toBe("string"), + toBeUndefined: () => expect(actual, parentFailMsg).toBeUndefined(), + toBeDefined: () => expect(actual, parentFailMsg).toBeDefined(), + toBeOneOf: (expected: T | Array | Iterable, failMsg = parentFailMsg) => { + const e = Array.isArray(expected) ? expected : [expected]; + expect(actual, failMsg).toBeOneOf(e); + }, + toHaveBeenCalled: () => expect(actual, parentFailMsg).toHaveBeenCalled(), + toHaveBeenCalledTimes: (expected: number, failMsg = parentFailMsg) => { + expect(actual, failMsg).toHaveBeenCalledTimes(expected); + }, + }; +}; + +export const viTestRunner: TestRunner = { + describe, + test: vitestTest, + expect: vitestExpect as any, + mock: (fn) => vi.fn(fn), +}; diff --git a/app/src/core/test/index.ts b/app/src/core/test/index.ts index ca1ffbae..c731938e 100644 --- a/app/src/core/test/index.ts +++ b/app/src/core/test/index.ts @@ -16,6 +16,7 @@ export interface Test { skipIf: (condition: boolean) => (label: string, fn: TestFn) => void; } export type TestRunner = { + describe: (label: string, asyncFn: () => Promise) => void; test: Test; mock: any>(fn: T) => T | any; expect: ( diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index bc86a8df..de3e7156 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -3,9 +3,7 @@ import { DataPermissions, type EntityData, type EntityManager, - type MutatorResponse, type RepoQuery, - type RepositoryResponse, repoQuery, } from "data"; import type { Handler } from "hono/types"; @@ -32,33 +30,6 @@ export class DataController extends Controller { return this.ctx.guard; } - repoResult = RepositoryResponse>( - res: T, - ): Pick { - let meta: Partial = {}; - - if ("meta" in res) { - const { query, ...rest } = res.meta; - meta = rest; - if (isDebug()) meta.query = query; - } - - const template = { data: res.data, meta }; - - // @todo: this works but it breaks in FE (need to improve DataTable) - // filter empty - return Object.fromEntries( - Object.entries(template).filter(([_, v]) => typeof v !== "undefined" && v !== null), - ) as any; - } - - mutatorResult(res: MutatorResponse | MutatorResponse) { - const template = { data: res.data }; - - // filter empty - return Object.fromEntries(Object.entries(template).filter(([_, v]) => v !== undefined)); - } - entityExists(entity: string) { try { return !!this.em.entity(entity); @@ -257,7 +228,7 @@ export class DataController extends Controller { const where = c.req.valid("json") as any; const result = await this.em.repository(entity).count(where); - return c.json({ entity, count: result.count }); + return c.json({ entity, ...result.data }); }, ); @@ -279,7 +250,7 @@ export class DataController extends Controller { const where = c.req.valid("json") as any; const result = await this.em.repository(entity).exists(where); - return c.json({ entity, exists: result.exists }); + return c.json({ entity, ...result.data }); }, ); @@ -318,7 +289,7 @@ export class DataController extends Controller { const options = c.req.valid("query") as RepoQuery; const result = await this.em.repository(entity).findMany(options); - return c.json(this.repoResult(result), { status: result.data ? 200 : 404 }); + return c.json(result, { status: result.data ? 200 : 404 }); }, ); @@ -347,7 +318,7 @@ export class DataController extends Controller { const options = c.req.valid("query") as RepoQuery; const result = await this.em.repository(entity).findId(id, options); - return c.json(this.repoResult(result), { status: result.data ? 200 : 404 }); + return c.json(result, { status: result.data ? 200 : 404 }); }, ); @@ -380,7 +351,7 @@ export class DataController extends Controller { .repository(entity) .findManyByReference(id, reference, options); - return c.json(this.repoResult(result), { status: result.data ? 200 : 404 }); + return c.json(result, { status: result.data ? 200 : 404 }); }, ); @@ -414,7 +385,7 @@ export class DataController extends Controller { const options = (await c.req.json()) as RepoQuery; const result = await this.em.repository(entity).findMany(options); - return c.json(this.repoResult(result), { status: result.data ? 200 : 404 }); + return c.json(result, { status: result.data ? 200 : 404 }); }, ); @@ -440,11 +411,11 @@ export class DataController extends Controller { if (Array.isArray(body)) { const result = await this.em.mutator(entity).insertMany(body); - return c.json(this.mutatorResult(result), 201); + return c.json(result, 201); } const result = await this.em.mutator(entity).insertOne(body); - return c.json(this.mutatorResult(result), 201); + return c.json(result, 201); }, ); @@ -475,7 +446,7 @@ export class DataController extends Controller { }; const result = await this.em.mutator(entity).updateWhere(update, where); - return c.json(this.mutatorResult(result)); + return c.json(result); }, ); @@ -497,7 +468,7 @@ export class DataController extends Controller { const body = (await c.req.json()) as EntityData; const result = await this.em.mutator(entity).updateOne(id, body); - return c.json(this.mutatorResult(result)); + return c.json(result); }, ); @@ -517,7 +488,7 @@ export class DataController extends Controller { } const result = await this.em.mutator(entity).deleteOne(id); - return c.json(this.mutatorResult(result)); + return c.json(result); }, ); @@ -539,7 +510,7 @@ export class DataController extends Controller { const where = (await c.req.json()) as RepoQuery["where"]; const result = await this.em.mutator(entity).deleteWhere(where); - return c.json(this.mutatorResult(result)); + return c.json(result); }, ); diff --git a/app/src/data/connection/Connection.ts b/app/src/data/connection/Connection.ts index 814c03d7..fab8e368 100644 --- a/app/src/data/connection/Connection.ts +++ b/app/src/data/connection/Connection.ts @@ -2,12 +2,15 @@ import { type AliasableExpression, type ColumnBuilderCallback, type ColumnDataType, + type Compilable, + type CompiledQuery, type DatabaseIntrospector, type Dialect, type Expression, type Kysely, type KyselyPlugin, type OnModifyForeignAction, + type QueryResult, type RawBuilder, type SelectQueryBuilder, type SelectQueryNode, @@ -15,7 +18,8 @@ import { sql, } from "kysely"; import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector"; -import type { Constructor } from "core"; +import type { Constructor, DB } from "core"; +import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; export type QB = SelectQueryBuilder; @@ -75,22 +79,44 @@ export type DbFunctions = { >; }; +export type ConnQuery = CompiledQuery | Compilable; + +export type ConnQueryResult = T extends CompiledQuery + ? QueryResult + : T extends Compilable + ? QueryResult + : never; + +export type ConnQueryResults = { + [K in keyof T]: ConnQueryResult; +}; + const CONN_SYMBOL = Symbol.for("bknd:connection"); -export abstract class Connection { +export type Features = { + batching: boolean; + softscans: boolean; +}; + +export abstract class Connection { + abstract name: string; protected initialized = false; - kysely: Kysely; - protected readonly supported = { + protected pluginRunner: KyselyPluginRunner; + protected readonly supported: Partial = { batching: false, + softscans: true, }; + kysely: Kysely; + client!: Client; constructor( - kysely: Kysely, + kysely: Kysely, public fn: Partial = {}, protected plugins: KyselyPlugin[] = [], ) { this.kysely = kysely; this[CONN_SYMBOL] = true; + this.pluginRunner = new KyselyPluginRunner(plugins); } // @todo: consider moving constructor logic here, required by sqlocal @@ -121,30 +147,46 @@ export abstract class Connection { return res.rows.length > 0; } - protected async batch( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { - throw new Error("Batching not supported"); + protected async transformResultRows(result: any[]): Promise { + return await this.pluginRunner.transformResultRows(result); } - async batchQuery( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { - // bypass if no client support - if (!this.supports("batching")) { - const data: any = []; - for (const q of queries) { - const result = await q.execute(); - data.push(result); - } - return data; - } + /** + * Execute a query and return the result including all metadata + * returned from the dialect. + */ + async executeQueries(...qbs: O): Promise> { + return Promise.all(qbs.map(async (qb) => await this.kysely.executeQuery(qb))) as any; + } - return await this.batch(queries); + async executeQuery(qb: O): Promise> { + const res = await this.executeQueries(qb); + return res[0] as any; + } + + protected getCompiled(...qbs: ConnQuery[]): CompiledQuery[] { + return qbs.map((qb) => { + if ("compile" in qb) { + return qb.compile(); + } + return qb; + }); + } + + protected async withTransformedRows< + Key extends string = "rows", + O extends { [K in Key]: any[] }[] = [], + >(result: O, _key?: Key): Promise { + return (await Promise.all( + result.map(async (row) => { + const key = _key ?? "rows"; + const { [key]: rows, ...r } = row; + return { + ...r, + rows: await this.transformResultRows(rows), + }; + }), + )) as any; } protected validateFieldSpecType(type: string): type is FieldSpec["type"] { diff --git a/app/src/data/connection/connection-test-suite.ts b/app/src/data/connection/connection-test-suite.ts new file mode 100644 index 00000000..5305337d --- /dev/null +++ b/app/src/data/connection/connection-test-suite.ts @@ -0,0 +1,187 @@ +import type { TestRunner } from "core/test"; +import { Connection, type FieldSpec } from "./Connection"; + +export function connectionTestSuite( + testRunner: TestRunner, + { + makeConnection, + rawDialectDetails, + }: { + makeConnection: () => Connection; + rawDialectDetails: string[]; + }, +) { + const { test, expect, describe } = testRunner; + + test("pings", async () => { + const connection = makeConnection(); + const res = await connection.ping(); + expect(res).toBe(true); + }); + + test("initializes", async () => { + const connection = makeConnection(); + await connection.init(); + // @ts-expect-error + expect(connection.initialized).toBe(true); + expect(connection.client).toBeDefined(); + }); + + test("isConnection", async () => { + const connection = makeConnection(); + expect(Connection.isConnection(connection)).toBe(true); + }); + + test("getFieldSchema", async () => { + const c = makeConnection(); + const specToNode = (spec: FieldSpec) => { + // @ts-expect-error + const schema = c.kysely.schema.createTable("test").addColumn(...c.getFieldSchema(spec)); + return schema.toOperationNode(); + }; + + { + // primary + const node = specToNode({ + type: "integer", + name: "id", + primary: true, + }); + const col = node.columns[0]!; + expect(col.primaryKey).toBe(true); + expect(col.notNull).toBe(true); + } + + { + // normal + const node = specToNode({ + type: "text", + name: "text", + }); + const col = node.columns[0]!; + expect(!col.primaryKey).toBe(true); + expect(!col.notNull).toBe(true); + } + + { + // nullable (expect to be same as normal) + const node = specToNode({ + type: "text", + name: "text", + nullable: true, + }); + const col = node.columns[0]!; + expect(!col.primaryKey).toBe(true); + expect(!col.notNull).toBe(true); + } + }); + + describe("schema", async () => { + const connection = makeConnection(); + const fields = [ + { + type: "integer", + name: "id", + primary: true, + }, + { + type: "text", + name: "text", + }, + { + type: "json", + name: "json", + }, + ] as const satisfies FieldSpec[]; + + let b = connection.kysely.schema.createTable("test"); + for (const field of fields) { + // @ts-expect-error + b = b.addColumn(...connection.getFieldSchema(field)); + } + await b.execute(); + + // add index + await connection.kysely.schema.createIndex("test_index").on("test").columns(["id"]).execute(); + + test("executes query", async () => { + await connection.kysely + .insertInto("test") + .values({ id: 1, text: "test", json: JSON.stringify({ a: 1 }) }) + .execute(); + + const expected = { id: 1, text: "test", json: { a: 1 } }; + + const qb = connection.kysely.selectFrom("test").selectAll(); + const res = await connection.executeQuery(qb); + expect(res.rows).toEqual([expected]); + expect(rawDialectDetails.every((detail) => detail in res)).toBe(true); + + { + const res = await connection.executeQueries(qb, qb); + expect(res.length).toBe(2); + res.map((r) => { + expect(r.rows).toEqual([expected]); + expect(rawDialectDetails.every((detail) => detail in r)).toBe(true); + }); + } + }); + + test("introspects", async () => { + const tables = await connection.getIntrospector().getTables({ + withInternalKyselyTables: false, + }); + const clean = tables.map((t) => ({ + ...t, + columns: t.columns.map((c) => ({ + ...c, + dataType: undefined, + })), + })); + + expect(clean).toEqual([ + { + name: "test", + isView: false, + columns: [ + { + name: "id", + dataType: undefined, + isNullable: false, + isAutoIncrementing: true, + hasDefaultValue: false, + }, + { + name: "text", + dataType: undefined, + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + { + name: "json", + dataType: undefined, + isNullable: true, + isAutoIncrementing: false, + hasDefaultValue: false, + }, + ], + }, + ]); + }); + + expect(await connection.getIntrospector().getIndices()).toEqual([ + { + name: "test_index", + table: "test", + isUnique: false, + columns: [ + { + name: "id", + order: 0, + }, + ], + }, + ]); + }); +} diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts new file mode 100644 index 00000000..37071810 --- /dev/null +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -0,0 +1,37 @@ +import type { KyselyPlugin } from "kysely"; +import { + type IGenericSqlite, + type OnCreateConnection, + type Promisable, + parseBigInt, + buildQueryFn, + GenericSqliteDialect, +} from "kysely-generic-sqlite"; +import { SqliteConnection } from "./SqliteConnection"; + +export type GenericSqliteConnectionConfig = { + name: string; + additionalPlugins?: KyselyPlugin[]; + excludeTables?: string[]; + onCreateConnection?: OnCreateConnection; +}; + +export { parseBigInt, buildQueryFn, GenericSqliteDialect, type IGenericSqlite }; + +export class GenericSqliteConnection extends SqliteConnection { + override name = "generic-sqlite"; + + constructor( + db: DB, + executor: () => Promisable, + config?: GenericSqliteConnectionConfig, + ) { + super({ + dialect: GenericSqliteDialect, + dialectArgs: [executor, config?.onCreateConnection], + additionalPlugins: config?.additionalPlugins, + excludeTables: config?.excludeTables, + }); + this.client = db; + } +} diff --git a/app/src/data/connection/sqlite/LibsqlConnection.spec.ts b/app/src/data/connection/sqlite/LibsqlConnection.spec.ts new file mode 100644 index 00000000..dde5a84b --- /dev/null +++ b/app/src/data/connection/sqlite/LibsqlConnection.spec.ts @@ -0,0 +1,11 @@ +import { connectionTestSuite } from "../connection-test-suite"; +import { LibsqlConnection } from "./LibsqlConnection"; +import { bunTestRunner } from "adapter/bun/test"; +import { describe } from "bun:test"; + +describe("LibsqlConnection", () => { + connectionTestSuite(bunTestRunner, { + makeConnection: () => new LibsqlConnection({ url: ":memory:" }), + rawDialectDetails: ["rowsAffected", "lastInsertRowid"], + }); +}); diff --git a/app/src/data/connection/sqlite/LibsqlConnection.ts b/app/src/data/connection/sqlite/LibsqlConnection.ts index 210f8cf9..cf0ff587 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/LibsqlConnection.ts @@ -1,40 +1,26 @@ -import { type Client, type Config, type InStatement, createClient } from "@libsql/client"; +import { createClient, type Client, type Config, type InStatement } from "@libsql/client"; import { LibsqlDialect } from "@libsql/kysely-libsql"; -import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; -import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; -import { type DatabaseIntrospector, Kysely, ParseJSONResultsPlugin } from "kysely"; -import type { QB } from "../Connection"; -import { SqliteConnection } from "./SqliteConnection"; -import { SqliteIntrospector } from "./SqliteIntrospector"; import { $console } from "core"; +import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; +import type { ConnQuery, ConnQueryResults } from "../Connection"; +import { SqliteConnection } from "./SqliteConnection"; export const LIBSQL_PROTOCOLS = ["wss", "https", "libsql"] as const; export type LibSqlCredentials = Config & { protocol?: (typeof LIBSQL_PROTOCOLS)[number]; }; -const plugins = [new FilterNumericKeysPlugin(), new ParseJSONResultsPlugin()]; - -class CustomLibsqlDialect extends LibsqlDialect { - override createIntrospector(db: Kysely): DatabaseIntrospector { - return new SqliteIntrospector(db, { - excludeTables: ["libsql_wasm_func_table"], - plugins, - }); - } -} - -export class LibsqlConnection extends SqliteConnection { - private client: Client; +export class LibsqlConnection extends SqliteConnection { + override name = "libsql"; protected override readonly supported = { batching: true, + softscans: true, }; constructor(client: Client); constructor(credentials: LibSqlCredentials); constructor(clientOrCredentials: Client | LibSqlCredentials) { let client: Client; - let batching_enabled = true; if (clientOrCredentials && "url" in clientOrCredentials) { let { url, authToken, protocol } = clientOrCredentials; if (protocol && LIBSQL_PROTOCOLS.includes(protocol)) { @@ -48,45 +34,25 @@ export class LibsqlConnection extends SqliteConnection { client = clientOrCredentials; } - const kysely = new Kysely({ - // @ts-expect-error libsql has type issues - dialect: new CustomLibsqlDialect({ client }), - plugins, + super({ + excludeTables: ["libsql_wasm_func_table"], + dialect: LibsqlDialect, + dialectArgs: [{ client }], + additionalPlugins: [new FilterNumericKeysPlugin()], }); - super(kysely, {}, plugins); this.client = client; - this.supported.batching = batching_enabled; } - getClient(): Client { - return this.client; - } - - protected override async batch( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { - const stms: InStatement[] = queries.map((q) => { - const compiled = q.compile(); + override async executeQueries(...qbs: O): Promise> { + const compiled = this.getCompiled(...qbs); + const stms: InStatement[] = compiled.map((q) => { return { - sql: compiled.sql, - args: compiled.parameters as any[], + sql: q.sql, + args: q.parameters as any[], }; }); - const res = await this.client.batch(stms); - - // let it run through plugins - const kyselyPlugins = new KyselyPluginRunner(this.plugins); - - const data: any = []; - for (const r of res) { - const rows = await kyselyPlugins.transformResultRows(r.rows); - data.push(rows); - } - - return data; + return this.withTransformedRows(await this.client.batch(stms)) as any; } } diff --git a/app/src/data/connection/sqlite/SqliteConnection.ts b/app/src/data/connection/sqlite/SqliteConnection.ts index 33a3fd86..705046dd 100644 --- a/app/src/data/connection/sqlite/SqliteConnection.ts +++ b/app/src/data/connection/sqlite/SqliteConnection.ts @@ -1,16 +1,49 @@ -import type { ColumnDataType, ColumnDefinitionBuilder, Kysely, KyselyPlugin } from "kysely"; +import { + ParseJSONResultsPlugin, + type ColumnDataType, + type ColumnDefinitionBuilder, + type Dialect, + Kysely, + type KyselyPlugin, +} from "kysely"; import { jsonArrayFrom, jsonBuildObject, jsonObjectFrom } from "kysely/helpers/sqlite"; import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } from "../Connection"; +import type { Constructor } from "core"; +import { customIntrospector } from "../Connection"; +import { SqliteIntrospector } from "./SqliteIntrospector"; + +export type SqliteConnectionConfig< + CustomDialect extends Constructor = Constructor, +> = { + excludeTables?: string[]; + dialect: CustomDialect; + dialectArgs?: ConstructorParameters; + additionalPlugins?: KyselyPlugin[]; + customFn?: Partial; +}; + +export abstract class SqliteConnection extends Connection { + override name = "sqlite"; + + constructor(config: SqliteConnectionConfig) { + const { excludeTables, dialect, dialectArgs = [], additionalPlugins } = config; + const plugins = [new ParseJSONResultsPlugin(), ...(additionalPlugins ?? [])]; + + const kysely = new Kysely({ + dialect: customIntrospector(dialect, SqliteIntrospector, { + excludeTables, + plugins, + }).create(...dialectArgs), + plugins, + }); -export class SqliteConnection extends Connection { - constructor(kysely: Kysely, fn: Partial = {}, plugins: KyselyPlugin[] = []) { super( kysely, { - ...fn, jsonArrayFrom, jsonObjectFrom, jsonBuildObject, + ...(config.customFn ?? {}), }, plugins, ); @@ -43,7 +76,7 @@ export class SqliteConnection extends Connection { if (spec.onUpdate) relCol = relCol.onUpdate(spec.onUpdate); return relCol; } - return spec.nullable ? col : col.notNull(); + return col; }, ] as const; } diff --git a/app/src/data/connection/sqlite/SqliteLocalConnection.ts b/app/src/data/connection/sqlite/SqliteLocalConnection.ts index a92577bf..34d98452 100644 --- a/app/src/data/connection/sqlite/SqliteLocalConnection.ts +++ b/app/src/data/connection/sqlite/SqliteLocalConnection.ts @@ -1,31 +1,14 @@ -import { - type DatabaseIntrospector, - Kysely, - ParseJSONResultsPlugin, - type SqliteDatabase, - SqliteDialect, -} from "kysely"; +import { type SqliteDatabase, SqliteDialect } from "kysely"; import { SqliteConnection } from "./SqliteConnection"; -import { SqliteIntrospector } from "./SqliteIntrospector"; -const plugins = [new ParseJSONResultsPlugin()]; +export class SqliteLocalConnection extends SqliteConnection { + override name = "sqlite-local"; -class CustomSqliteDialect extends SqliteDialect { - override createIntrospector(db: Kysely): DatabaseIntrospector { - return new SqliteIntrospector(db, { - excludeTables: ["test_table"], - plugins, + constructor(database: SqliteDatabase) { + super({ + dialect: SqliteDialect, + dialectArgs: [{ database }], }); - } -} - -export class SqliteLocalConnection extends SqliteConnection { - constructor(private database: SqliteDatabase) { - const kysely = new Kysely({ - dialect: new CustomSqliteDialect({ database }), - plugins, - }); - - super(kysely, {}, plugins); + this.client = database; } } diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index e4e71c22..0401d4de 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -207,8 +207,9 @@ export class EntityManager { repository( entity: E, + opts: Omit = {}, ): Repository> { - return this.repo(entity); + return this.repo(entity, opts); } repo( diff --git a/app/src/data/entities/Result.ts b/app/src/data/entities/Result.ts new file mode 100644 index 00000000..a37cbd77 --- /dev/null +++ b/app/src/data/entities/Result.ts @@ -0,0 +1,126 @@ +import { isDebug } from "core"; +import { pick } from "core/utils"; +import type { Connection } from "data/connection"; +import type { + Compilable, + CompiledQuery, + QueryResult as KyselyQueryResult, + SelectQueryBuilder, +} from "kysely"; + +export type ResultHydrator = (rows: T[]) => any; +export type ResultOptions = { + hydrator?: ResultHydrator; + beforeExecute?: (compiled: CompiledQuery) => void | Promise; + onError?: (error: Error) => void | Promise; + single?: boolean; +}; +export type ResultJSON = { + data: T; + meta: { + items: number; + time: number; + sql?: string; + parameters?: any[]; + [key: string]: any; + }; +}; + +export interface QueryResult extends Omit, "rows"> { + time: number; + items: number; + data: T; + rows: unknown[]; + sql: string; + parameters: any[]; + count?: number; + total?: number; +} + +export class Result { + results: QueryResult[] = []; + time: number = 0; + + constructor( + protected conn: Connection, + protected options: ResultOptions = {}, + ) {} + + get(): QueryResult { + if (!this.results) { + throw new Error("Result not executed"); + } + + if (Array.isArray(this.results)) { + return (this.results ?? []) as any; + } + + return this.results[0] as any; + } + + first(): QueryResult { + const res = this.get(); + const first = Array.isArray(res) ? res[0] : res; + return first ?? ({} as any); + } + + get sql() { + return this.first().sql; + } + + get parameters() { + return this.first().parameters; + } + + get data() { + if (this.options.single) { + return this.first().data?.[0]; + } + + return this.first().data ?? []; + } + + async execute(qb: Compilable | Compilable[]) { + const qbs = Array.isArray(qb) ? qb : [qb]; + + for (const qb of qbs) { + const compiled = qb.compile(); + await this.options.beforeExecute?.(compiled); + try { + const start = performance.now(); + const res = await this.conn.executeQuery(compiled); + this.time = Number.parseFloat((performance.now() - start).toFixed(2)); + this.results.push({ + ...res, + data: this.options.hydrator?.(res.rows as T[]), + items: res.rows.length, + time: this.time, + sql: compiled.sql, + parameters: [...compiled.parameters], + }); + } catch (e) { + if (this.options.onError) { + await this.options.onError(e as Error); + } else { + throw e; + } + } + } + + return this; + } + + protected additionalMetaKeys(): string[] { + return []; + } + + toJSON(): ResultJSON { + const { rows, data, ...metaRaw } = this.first(); + const keys = isDebug() ? ["items", "time", "sql", "parameters"] : ["items", "time"]; + const meta = pick(metaRaw, [...keys, ...this.additionalMetaKeys()] as any); + return { + data: this.data, + meta, + }; + } +} diff --git a/app/src/data/entities/index.ts b/app/src/data/entities/index.ts index efe64468..eb5f3b23 100644 --- a/app/src/data/entities/index.ts +++ b/app/src/data/entities/index.ts @@ -1,6 +1,6 @@ export * from "./Entity"; export * from "./EntityManager"; -export * from "./Mutator"; +export * from "./mutation/Mutator"; export * from "./query/Repository"; export * from "./query/WhereBuilder"; export * from "./query/WithBuilder"; diff --git a/app/src/data/entities/Mutator.ts b/app/src/data/entities/mutation/Mutator.ts similarity index 84% rename from app/src/data/entities/Mutator.ts rename to app/src/data/entities/mutation/Mutator.ts index 7dcb2dd2..bf8cebec 100644 --- a/app/src/data/entities/Mutator.ts +++ b/app/src/data/entities/mutation/Mutator.ts @@ -1,12 +1,13 @@ import { $console, type DB as DefaultDB, type PrimaryFieldType } from "core"; import { type EmitsEvents, EventManager } from "core/events"; import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely"; -import { type TActionContext, WhereBuilder } from ".."; -import type { Entity, EntityData, EntityManager } from "../entities"; -import { InvalidSearchParamsException } from "../errors"; -import { MutatorEvents } from "../events"; -import { RelationMutator } from "../relations"; -import type { RepoQuery } from "../server/query"; +import { type TActionContext, WhereBuilder } from "../.."; +import type { Entity, EntityData, EntityManager } from "../../entities"; +import { InvalidSearchParamsException } from "../../errors"; +import { MutatorEvents } from "../../events"; +import { RelationMutator } from "../../relations"; +import type { RepoQuery } from "../../server/query"; +import { MutatorResult, type MutatorResultOptions } from "./MutatorResult"; type MutatorQB = | InsertQueryBuilder @@ -17,14 +18,6 @@ type MutatorUpdateOrDelete = | UpdateQueryBuilder | DeleteQueryBuilder; -export type MutatorResponse = { - entity: Entity; - sql: string; - parameters: any[]; - result: EntityData[]; - data: T; -}; - export class Mutator< TBD extends object = DefaultDB, TB extends keyof TBD = any, @@ -103,35 +96,18 @@ export class Mutator< return validatedData as Given; } - protected async many(qb: MutatorQB): Promise { - const entity = this.entity; - const { sql, parameters } = qb.compile(); - - try { - const result = await qb.execute(); - - const data = this.em.hydrate(entity.name, result) as EntityData[]; - - return { - entity, - sql, - parameters: [...parameters], - result: result, - data, - }; - } catch (e) { - // @todo: redact - $console.error("[Error in query]", sql); - throw e; - } + protected async performQuery( + qb: MutatorQB, + opts?: MutatorResultOptions, + ): Promise> { + const result = new MutatorResult(this.em, this.entity, { + silent: false, + ...opts, + }); + return (await result.execute(qb)) as any; } - protected async single(qb: MutatorQB): Promise> { - const { data, ...response } = await this.many(qb); - return { ...response, data: data[0]! }; - } - - async insertOne(data: Input): Promise> { + async insertOne(data: Input): Promise> { const entity = this.entity; if (entity.type === "system" && this.__unstable_disable_system_entity_creation) { throw new Error(`Creation of system entity "${entity.name}" is disabled`); @@ -174,7 +150,7 @@ export class Mutator< .values(validatedData) .returning(entity.getSelect()); - const res = await this.single(query); + const res = await this.performQuery(query, { single: true }); await this.emgr.emit( new Mutator.Events.MutatorInsertAfter({ entity, data: res.data, changed: validatedData }), @@ -183,7 +159,7 @@ export class Mutator< return res as any; } - async updateOne(id: PrimaryFieldType, data: Partial): Promise> { + async updateOne(id: PrimaryFieldType, data: Partial): Promise> { const entity = this.entity; if (!id) { throw new Error("ID must be provided for update"); @@ -206,7 +182,7 @@ export class Mutator< .where(entity.id().name, "=", id) .returning(entity.getSelect()); - const res = await this.single(query); + const res = await this.performQuery(query, { single: true }); await this.emgr.emit( new Mutator.Events.MutatorUpdateAfter({ @@ -220,7 +196,7 @@ export class Mutator< return res as any; } - async deleteOne(id: PrimaryFieldType): Promise> { + async deleteOne(id: PrimaryFieldType): Promise> { const entity = this.entity; if (!id) { throw new Error("ID must be provided for deletion"); @@ -233,7 +209,7 @@ export class Mutator< .where(entity.id().name, "=", id) .returning(entity.getSelect()); - const res = await this.single(query); + const res = await this.performQuery(query, { single: true }); await this.emgr.emit( new Mutator.Events.MutatorDeleteAfter({ entity, entityId: id, data: res.data }), @@ -286,7 +262,7 @@ export class Mutator< } // @todo: decide whether entries should be deleted all at once or one by one (for events) - async deleteWhere(where: RepoQuery["where"]): Promise> { + async deleteWhere(where: RepoQuery["where"]): Promise> { const entity = this.entity; // @todo: add a way to delete all by adding force? @@ -298,13 +274,13 @@ export class Mutator< entity.getSelect(), ); - return (await this.many(qb)) as any; + return await this.performQuery(qb); } async updateWhere( data: Partial, where: RepoQuery["where"], - ): Promise> { + ): Promise> { const entity = this.entity; const validatedData = await this.getValidatedData(data, "update"); @@ -317,10 +293,10 @@ export class Mutator< .set(validatedData as any) .returning(entity.getSelect()); - return (await this.many(query)) as any; + return await this.performQuery(query); } - async insertMany(data: Input[]): Promise> { + async insertMany(data: Input[]): Promise> { const entity = this.entity; if (entity.type === "system" && this.__unstable_disable_system_entity_creation) { throw new Error(`Creation of system entity "${entity.name}" is disabled`); @@ -352,6 +328,6 @@ export class Mutator< .values(validated) .returning(entity.getSelect()); - return (await this.many(query)) as any; + return await this.performQuery(query); } } diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts new file mode 100644 index 00000000..88cf4355 --- /dev/null +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -0,0 +1,33 @@ +import { $console } from "core/console"; +import type { Entity, EntityData } from "../Entity"; +import type { EntityManager } from "../EntityManager"; +import { Result, type ResultJSON, type ResultOptions } from "../Result"; + +export type MutatorResultOptions = ResultOptions & { + silent?: boolean; +}; + +export type MutatorResultJSON = ResultJSON; + +export class MutatorResult extends Result { + constructor( + protected em: EntityManager, + public entity: Entity, + options?: MutatorResultOptions, + ) { + super(em.connection, { + hydrator: (rows) => em.hydrate(entity.name, rows as any), + beforeExecute: (compiled) => { + if (!options?.silent) { + $console.debug(`[Mutation]\n${compiled.sql}\n`, compiled.parameters); + } + }, + onError: (error) => { + if (!options?.silent) { + $console.error("[ERROR] Mutator:", error.message); + } + }, + ...options, + }); + } +} diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index 625e7013..ce39c6aa 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -13,37 +13,11 @@ import { WithBuilder, } from "../index"; import { JoinBuilder } from "./JoinBuilder"; -import { ensureInt } from "core/utils"; +import { RepositoryResult, type RepositoryResultOptions } from "./RepositoryResult"; +import type { ResultOptions } from "../Result"; export type RepositoryQB = SelectQueryBuilder; -export type RepositoryRawResponse = { - sql: string; - parameters: any[]; - result: EntityData[]; -}; -export type RepositoryResponse = RepositoryRawResponse & { - entity: Entity; - data: T; - meta: { - items: number; - total?: number; - count?: number; - time?: number; - query?: { - sql: string; - parameters: readonly any[]; - }; - }; -}; - -export type RepositoryCountResponse = RepositoryRawResponse & { - count: number; -}; -export type RepositoryExistsResponse = RepositoryRawResponse & { - exists: boolean; -}; - export type RepositoryOptions = { silent?: boolean; includeCounts?: boolean; @@ -182,126 +156,18 @@ export class Repository { - const entity = this.entity; - const compiled = qb.compile(); - - const payload = { - entity, - sql: compiled.sql, - parameters: [...compiled.parameters], - result: [], - data: [], - meta: { - total: 0, - count: 0, - items: 0, - time: 0, - query: { sql: compiled.sql, parameters: compiled.parameters }, - }, - }; - - // don't batch (add counts) if `includeCounts` is set to false - // or when explicitly set to true and batching is not supported - if ( - this.options?.includeCounts === false || - (this.options?.includeCounts === true && !this.em.connection.supports("batching")) - ) { - const start = performance.now(); - const res = await this.executeQb(qb); - const time = Number.parseFloat((performance.now() - start).toFixed(2)); - const result = res.result ?? []; - const data = this.em.hydrate(entity.name, result); - - return { - ...payload, - result, - data, - meta: { - ...payload.meta, - total: undefined, - count: undefined, - items: data.length, - time, - }, - }; - } - - if (this.options?.silent !== true) { - $console.debug(`Repository: query\n${compiled.sql}\n`, compiled.parameters); - } - - const selector = (as = "count") => this.conn.fn.countAll().as(as); - const countQuery = qb - .clearSelect() - .select(selector()) - .clearLimit() - .clearOffset() - .clearGroupBy() - .clearOrderBy(); - const totalQuery = this.conn.selectFrom(entity.name).select(selector()); - - try { - const start = performance.now(); - const [_count, _total, result] = await this.em.connection.batchQuery([ - countQuery, - totalQuery, - qb, - ]); - - const time = Number.parseFloat((performance.now() - start).toFixed(2)); - const data = this.em.hydrate(entity.name, result); - - return { - ...payload, - result, - data, - meta: { - ...payload.meta, - // parsing is important since pg returns string - total: ensureInt(_total[0]?.count), - count: ensureInt(_count[0]?.count), - items: result.length, - time, - }, - }; - } catch (e) { - if (this.options?.silent !== true) { - if (e instanceof Error) { - $console.error("[ERROR] Repository.performQuery", e.message); - } - - throw e; - } else { - return payload; - } - } + protected async performQuery( + qb: RepositoryQB, + opts?: RepositoryResultOptions, + execOpts?: { includeCounts?: boolean }, + ): Promise> { + const result = new RepositoryResult(this.em, this.entity, { + silent: this.options.silent, + ...opts, + }); + return (await result.execute(qb, { + includeCounts: execOpts?.includeCounts ?? this.options.includeCounts, + })) as any; } private async triggerFindBefore(entity: Entity, options: RepoQuery): Promise { @@ -319,7 +185,7 @@ export class Repository { if (options.limit === 1) { await this.emgr.emit( - new Repository.Events.RepositoryFindOneAfter({ entity, options, data: data[0]! }), + new Repository.Events.RepositoryFindOneAfter({ entity, options, data }), ); } else { await this.emgr.emit( @@ -331,12 +197,11 @@ export class Repository> { + ): Promise> { await this.triggerFindBefore(this.entity, options); - const { data, ...response } = await this.performQuery(qb); - - await this.triggerFindAfter(this.entity, options, data); - return { ...response, data: data[0]! }; + const result = await this.performQuery(qb, { single: true }); + await this.triggerFindAfter(this.entity, options, result.data); + return result as any; } addOptionsToQueryBuilder( @@ -413,7 +278,7 @@ export class Repository>, - ): Promise> { + ): Promise> { const { qb, options } = this.buildQuery( { ..._options, @@ -429,7 +294,7 @@ export class Repository>, - ): Promise> { + ): Promise> { const { qb, options } = this.buildQuery({ ..._options, where, @@ -439,7 +304,7 @@ export class Repository): Promise> { + async findMany(_options?: Partial): Promise> { const { qb, options } = this.buildQuery(_options); await this.triggerFindBefore(this.entity, options); @@ -454,7 +319,7 @@ export class Repository>, - ): Promise> { + ): Promise> { const entity = this.entity; const listable_relations = this.em.relations.listableRelationsOf(entity); const relation = listable_relations.find((r) => r.ref(reference).reference === reference); @@ -482,10 +347,10 @@ export class Repository { + async count(where?: RepoQuery["where"]): Promise> { const entity = this.entity; const options = this.getValidOptions({ where }); @@ -497,17 +362,18 @@ export class Repository ({ count: rows[0]?.count ?? 0 }), + }, + { includeCounts: false }, + ); } - async exists(where: Required["where"]): Promise { + async exists( + where: Required["where"], + ): Promise> { const entity = this.entity; const options = this.getValidOptions({ where }); @@ -517,13 +383,8 @@ export class Repository 0, - }; + return await this.performQuery(qb, { + hydrator: (rows) => ({ exists: rows[0]?.count > 0 }), + }); } } diff --git a/app/src/data/entities/query/RepositoryResult.ts b/app/src/data/entities/query/RepositoryResult.ts new file mode 100644 index 00000000..fee0dff6 --- /dev/null +++ b/app/src/data/entities/query/RepositoryResult.ts @@ -0,0 +1,105 @@ +import { $console } from "core/console"; +import type { Entity, EntityData } from "../Entity"; +import type { EntityManager } from "../EntityManager"; +import { Result, type ResultJSON, type ResultOptions } from "../Result"; +import type { Compilable, SelectQueryBuilder } from "kysely"; +import { ensureInt } from "core/utils"; + +export type RepositoryResultOptions = ResultOptions & { + silent?: boolean; +}; + +export type RepositoryResultJSON = ResultJSON; + +export class RepositoryResult extends Result { + constructor( + protected em: EntityManager, + public entity: Entity, + options?: RepositoryResultOptions, + ) { + super(em.connection, { + hydrator: (rows) => em.hydrate(entity.name, rows as any), + beforeExecute: (compiled) => { + if (!options?.silent) { + $console.debug(`Query:\n${compiled.sql}\n`, compiled.parameters); + } + }, + onError: (error) => { + if (options?.silent !== true) { + $console.error("Repository:", String(error)); + throw error; + } + }, + ...options, + }); + } + + private shouldIncludeCounts(intent?: boolean) { + if (intent === undefined) return this.conn.supports("softscans"); + return intent; + } + + override async execute( + qb: SelectQueryBuilder, + opts?: { includeCounts?: boolean }, + ) { + const includeCounts = this.shouldIncludeCounts(opts?.includeCounts); + + if (includeCounts) { + const selector = (as = "count") => this.conn.kysely.fn.countAll().as(as); + const countQuery = qb + .clearSelect() + .select(selector()) + .clearLimit() + .clearOffset() + .clearGroupBy() + .clearOrderBy(); + const totalQuery = this.conn.kysely.selectFrom(this.entity.name).select(selector()); + + const compiled = qb.compile(); + this.options.beforeExecute?.(compiled); + + try { + const start = performance.now(); + const [main, count, total] = await this.em.connection.executeQueries( + compiled, + countQuery, + totalQuery, + ); + this.time = Number.parseFloat((performance.now() - start).toFixed(2)); + this.results.push({ + ...main, + data: this.options.hydrator?.(main.rows as T[]), + items: main.rows.length, + count: ensureInt(count.rows[0]?.count ?? 0), + total: ensureInt(total.rows[0]?.count ?? 0), + time: this.time, + sql: compiled.sql, + parameters: [...compiled.parameters], + }); + } catch (e) { + if (this.options.onError) { + await this.options.onError(e as Error); + } else { + throw e; + } + } + + return this; + } + + return await super.execute(qb); + } + + get count() { + return this.first().count; + } + + get total() { + return this.first().total; + } + + protected override additionalMetaKeys(): string[] { + return ["count", "total"]; + } +} diff --git a/app/src/data/relations/ManyToOneRelation.ts b/app/src/data/relations/ManyToOneRelation.ts index 7fde72ab..c9a1e0b1 100644 --- a/app/src/data/relations/ManyToOneRelation.ts +++ b/app/src/data/relations/ManyToOneRelation.ts @@ -208,7 +208,7 @@ export class ManyToOneRelation extends EntityRelation=0.26" } }, "sha512-/Bs3/Uktn04nQ9g/4oSphLMEtSHkS5+j5hbKjK5gMqXQfqr/v3V3FKtoN4pLTmo2W35hNdrIpQnBukGL1zZc6g=="], + "kysely-neon": ["kysely-neon@1.3.0", "", { "peerDependencies": { "@neondatabase/serverless": "^0.4.3", "kysely": "0.x.x", "ws": "^8.13.0" }, "optionalPeers": ["ws"] }, "sha512-CIIlbmqpIXVJDdBEYtEOwbmALag0jmqYrGfBeM4cHKb9AgBGs+X1SvXUZ8TqkDacQEqEZN2XtsDoUkcMIISjHw=="], "kysely-postgres-js": ["kysely-postgres-js@2.0.0", "", { "peerDependencies": { "kysely": ">= 0.24.0 < 1", "postgres": ">= 3.4.0 < 4" } }, "sha512-R1tWx6/x3tSatWvsmbHJxpBZYhNNxcnMw52QzZaHKg7ZOWtHib4iZyEaw4gb2hNKVctWQ3jfMxZT/ZaEMK6kBQ=="], From 046c1d21b1222be4cac7297d29314cc60e8b733c Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 09:12:40 +0200 Subject: [PATCH 02/39] fixed tests --- app/__test__/data/specs/Repository.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__test__/data/specs/Repository.spec.ts b/app/__test__/data/specs/Repository.spec.ts index 23f094c2..46bbf7ba 100644 --- a/app/__test__/data/specs/Repository.spec.ts +++ b/app/__test__/data/specs/Repository.spec.ts @@ -51,7 +51,7 @@ describe("[Repository]", async () => { // { const res = await em.repository(items).findMany(); - expect(res.count).toBeUndefined(); + expect(res.count).toBe(3); } { From 8b4b63b3cd4b5753d83da9905e6d64cc5b6713ad Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 15:29:53 +0200 Subject: [PATCH 03/39] feat: improved abilities of plugins, moved schema fns to ctx --- app/__test__/App.spec.ts | 96 +++++++++++++++-- app/__test__/app/App.spec.ts | 1 + app/__test__/modules/Module.spec.ts | 27 +++-- app/__test__/modules/module-test-suite.ts | 7 +- app/src/App.ts | 77 +++++++++++--- app/src/auth/AppAuth.ts | 6 +- app/src/auth/api/AuthController.ts | 2 +- app/src/core/index.ts | 11 +- app/src/core/types.ts | 2 + app/src/data/api/DataController.ts | 2 +- app/src/data/relations/RelationAccessor.ts | 9 ++ app/src/index.ts | 2 + app/src/media/AppMedia.ts | 2 +- app/src/media/api/MediaController.ts | 2 +- app/src/modules/Module.ts | 90 +--------------- app/src/modules/ModuleHelper.ts | 113 +++++++++++++++++++++ app/src/modules/ModuleManager.ts | 14 ++- 17 files changed, 330 insertions(+), 133 deletions(-) create mode 100644 app/src/modules/ModuleHelper.ts diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 79fdc51b..93c4f6fa 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -1,6 +1,9 @@ import { afterAll, afterEach, describe, expect, test } from "bun:test"; import { App } from "../src"; import { getDummyConnection } from "./helper"; +import { Hono } from "hono"; +import * as proto from "../src/data/prototype"; +import { pick } from "lodash-es"; const { dummyConnection, afterAllCleanup } = getDummyConnection(); afterEach(afterAllCleanup); @@ -10,18 +13,91 @@ describe("App tests", async () => { const app = new App(dummyConnection); await app.build(); - //expect(await app.data?.em.ping()).toBeTrue(); + expect(await app.em.ping()).toBeTrue(); }); - /*test.only("what", async () => { - const app = new App(dummyConnection, { - auth: { - enabled: true, + test("plugins", async () => { + const called: string[] = []; + const app = App.create({ + initialConfig: { + auth: { + enabled: true, + }, + }, + options: { + plugins: [ + (app) => { + expect(app).toBeDefined(); + expect(app).toBeInstanceOf(App); + return { + name: "test", + schema: () => { + called.push("schema"); + return proto.em( + { + posts: proto.entity("posts", { + title: proto.text(), + }), + comments: proto.entity("comments", { + content: proto.text(), + }), + users: proto.entity("users", { + email_verified: proto.boolean(), + }), + }, + (fn, s) => { + fn.relation(s.comments).manyToOne(s.posts); + fn.index(s.posts).on(["title"]); + }, + ); + }, + beforeBuild: async () => { + called.push("beforeBuild"); + }, + onBuilt: async () => { + called.push("onBuilt"); + }, + onServerInit: async (server) => { + called.push("onServerInit"); + expect(server).toBeDefined(); + expect(server).toBeInstanceOf(Hono); + }, + onFirstBoot: async () => { + called.push("onFirstBoot"); + }, + }; + }, + ], }, }); - await app.module.auth.build(); - await app.module.data.build(); - console.log(app.em.entities.map((e) => e.name)); - console.log(await app.em.schema().getDiff()); - });*/ + + await app.build(); + + expect(app.em.entities.map((e) => e.name)).toEqual(["users", "posts", "comments"]); + expect(app.em.indices.map((i) => i.name)).toEqual([ + "idx_unique_users_email", + "idx_users_strategy", + "idx_users_strategy_value", + "idx_posts_title", + ]); + expect( + app.em.relations.all.map((r) => pick(r.toJSON(), ["type", "source", "target"])), + ).toEqual([ + { + type: "n:1", + source: "comments", + target: "posts", + }, + ]); + expect(called).toEqual([ + "onServerInit", + "beforeBuild", + "onServerInit", + "schema", + "onFirstBoot", + "onBuilt", + ]); + expect(app.plugins).toHaveLength(1); + expect(app.plugins.map((p) => p.name)).toEqual(["test"]); + }); }); diff --git a/app/__test__/app/App.spec.ts b/app/__test__/app/App.spec.ts index 860258ad..6b7aebb0 100644 --- a/app/__test__/app/App.spec.ts +++ b/app/__test__/app/App.spec.ts @@ -20,6 +20,7 @@ describe("App", () => { "guard", "flags", "logger", + "helper", ]); }, }, diff --git a/app/__test__/modules/Module.spec.ts b/app/__test__/modules/Module.spec.ts index 8cca811b..380591dd 100644 --- a/app/__test__/modules/Module.spec.ts +++ b/app/__test__/modules/Module.spec.ts @@ -4,6 +4,7 @@ import { type TSchema, Type } from "@sinclair/typebox"; import { EntityManager, em, entity, index, text } from "../../src/data"; import { DummyConnection } from "../../src/data/connection/DummyConnection"; import { Module } from "../../src/modules/Module"; +import { ModuleHelper } from "modules/ModuleHelper"; function createModule(schema: Schema) { class TestModule extends Module { @@ -46,9 +47,9 @@ describe("Module", async () => { } prt = { - ensureEntity: this.ensureEntity.bind(this), - ensureIndex: this.ensureIndex.bind(this), - ensureSchema: this.ensureSchema.bind(this), + ensureEntity: this.ctx.helper.ensureEntity.bind(this.ctx.helper), + ensureIndex: this.ctx.helper.ensureIndex.bind(this.ctx.helper), + ensureSchema: this.ctx.helper.ensureSchema.bind(this.ctx.helper), }; get em() { @@ -63,7 +64,11 @@ describe("Module", async () => { _em.relations, _em.indices, ); - return new M({} as any, { em, flags: Module.ctx_flags } as any); + const ctx = { + em, + flags: Module.ctx_flags, + }; + return new M({} as any, { ...ctx, helper: new ModuleHelper(ctx as any) } as any); } function flat(_em: EntityManager) { return { @@ -143,14 +148,9 @@ describe("Module", async () => { // this should only add the field "important" m.prt.ensureEntity( - entity( - "u", - { - important: text(), - }, - undefined, - "system", - ), + entity("u", { + important: text(), + }), ); expect(m.ctx.flags.sync_required).toBe(true); @@ -159,8 +159,7 @@ describe("Module", async () => { { name: "u", fields: ["id", "name", "important"], - // ensured type must be present - type: "system", + type: "regular", }, { name: "p", diff --git a/app/__test__/modules/module-test-suite.ts b/app/__test__/modules/module-test-suite.ts index 4ad7e5da..610dc289 100644 --- a/app/__test__/modules/module-test-suite.ts +++ b/app/__test__/modules/module-test-suite.ts @@ -8,10 +8,11 @@ import { Default, stripMark } from "../../src/core/utils"; import { EntityManager } from "../../src/data"; import { Module, type ModuleBuildContext } from "../../src/modules/Module"; import { getDummyConnection } from "../helper"; +import { ModuleHelper } from "modules/ModuleHelper"; export function makeCtx(overrides?: Partial): ModuleBuildContext { const { dummyConnection } = getDummyConnection(); - return { + const ctx = { connection: dummyConnection, server: new Hono(), em: new EntityManager([], dummyConnection), @@ -21,6 +22,10 @@ export function makeCtx(overrides?: Partial): ModuleBuildCon logger: new DebugLogger(false), ...overrides, }; + return { + ...ctx, + helper: new ModuleHelper(ctx as any), + } as any; } export function moduleTestSuite(module: { new (): Module }) { diff --git a/app/src/App.ts b/app/src/App.ts index 956229c2..ea72feb8 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -1,7 +1,7 @@ import type { CreateUserPayload } from "auth/AppAuth"; import { $console } from "core"; import { Event } from "core/events"; -import { Connection, type LibSqlCredentials, LibsqlConnection } from "data"; +import { Connection, type LibSqlCredentials, LibsqlConnection, type em as prototypeEm } from "data"; import type { Hono } from "hono"; import { ModuleManager, @@ -14,12 +14,21 @@ import { import * as SystemPermissions from "modules/permissions"; import { AdminController, type AdminControllerOptions } from "modules/server/AdminController"; import { SystemController } from "modules/server/SystemController"; +import type { MaybePromise } from "core/types"; +import type { ServerEnv } from "modules/Controller"; // biome-ignore format: must be here import { Api, type ApiOptions } from "Api"; -import type { ServerEnv } from "modules/Controller"; -export type AppPlugin = (app: App) => Promise | void; +export type AppPluginConfig = { + name: string; + schema?: () => MaybePromise | void>; + beforeBuild?: () => MaybePromise; + onBuilt?: () => MaybePromise; + onServerInit?: (server: Hono) => MaybePromise; + onFirstBoot?: () => MaybePromise; +}; +export type AppPlugin = (app: App) => AppPluginConfig; abstract class AppEvent extends Event<{ app: App } & A> {} export class AppConfigUpdatedEvent extends AppEvent { @@ -73,9 +82,9 @@ export class App { modules: ModuleManager; adminController?: AdminController; _id: string = crypto.randomUUID(); + plugins: AppPluginConfig[]; private trigger_first_boot = false; - private plugins: AppPlugin[]; private _building: boolean = false; constructor( @@ -83,13 +92,14 @@ export class App { _initialConfig?: InitialModuleConfigs, private options?: AppOptions, ) { - this.plugins = options?.plugins ?? []; + this.plugins = (options?.plugins ?? []).map((plugin) => plugin(this)); this.modules = new ModuleManager(connection, { ...(options?.manager ?? {}), initial: _initialConfig, onUpdated: this.onUpdated.bind(this), onFirstBoot: this.onFirstBoot.bind(this), onServerInit: this.onServerInit.bind(this), + onModulesBuilt: this.onModulesBuilt.bind(this), }); this.modules.ctx().emgr.registerEvents(AppEvents); } @@ -98,6 +108,32 @@ export class App { return this.modules.ctx().emgr; } + protected async runPlugins( + key: Key, + ...args: any[] + ): Promise<{ name: string; result: any }[]> { + const results: { name: string; result: any }[] = []; + for (const plugin of this.plugins) { + try { + if (key in plugin && plugin[key]) { + const fn = plugin[key]; + if (fn && typeof fn === "function") { + $console.debug(`[Plugin:${plugin.name}] ${key}`); + // @ts-expect-error + const result = await fn(...args); + results.push({ + name: plugin.name, + result, + }); + } + } + } catch (e) { + $console.warn(`[Plugin:${plugin.name}] error running "${key}"`, String(e)); + } + } + return results as any; + } + async build(options?: { sync?: boolean; fetch?: boolean; forceBuild?: boolean }) { // prevent multiple concurrent builds if (this._building) { @@ -106,6 +142,8 @@ export class App { } if (!options?.forceBuild) return; } + + await this.runPlugins("beforeBuild"); this._building = true; if (options?.sync) this.modules.ctx().flags.sync_required = true; @@ -117,13 +155,10 @@ export class App { guard.registerPermissions(Object.values(SystemPermissions)); server.route("/api/system", new SystemController(this).getController()); - // load plugins - if (this.plugins.length > 0) { - await Promise.all(this.plugins.map((plugin) => plugin(this))); - } - + // emit built event $console.log("App built"); await this.emgr.emit(new AppBuiltEvent({ app: this })); + await this.runPlugins("onBuilt"); // first boot is set from ModuleManager when there wasn't a config table if (this.trigger_first_boot) { @@ -223,12 +258,13 @@ export class App { await this.emgr.emit(new AppConfigUpdatedEvent({ app: this })); } - async onFirstBoot() { + protected async onFirstBoot() { $console.log("App first boot"); this.trigger_first_boot = true; + await this.runPlugins("onFirstBoot"); } - async onServerInit(server: Hono) { + protected async onServerInit(server: Hono) { server.use(async (c, next) => { c.set("app", this); await this.emgr.emit(new AppRequest({ app: this, request: c.req.raw })); @@ -258,6 +294,23 @@ export class App { if (this.options?.manager?.onServerInit) { this.options.manager.onServerInit(server); } + + await this.runPlugins("onServerInit", server); + } + + protected async onModulesBuilt(ctx: ModuleBuildContext) { + const results = (await this.runPlugins("schema")) as { + name: string; + result: ReturnType; + }[]; + if (results.length > 0) { + for (const { name, result } of results) { + if (result) { + $console.log(`[Plugin:${name}] schema`); + ctx.helper.ensureSchema(result); + } + } + } } } diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index 898c9f2c..973332e1 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -140,7 +140,7 @@ export class AppAuth extends Module { registerEntities() { const users = this.getUsersEntity(true); - this.ensureSchema( + this.ctx.helper.ensureSchema( em( { [users.name as "users"]: users, @@ -153,13 +153,13 @@ export class AppAuth extends Module { try { const roles = Object.keys(this.config.roles ?? {}); - this.replaceEntityField(users, "role", enumm({ enum: roles })); + this.ctx.helper.replaceEntityField(users, "role", enumm({ enum: roles })); } catch (e) {} try { // also keep disabled strategies as a choice const strategies = Object.keys(this.config.strategies ?? {}); - this.replaceEntityField(users, "strategy", enumm({ enum: strategies })); + this.ctx.helper.replaceEntityField(users, "strategy", enumm({ enum: strategies })); } catch (e) {} } diff --git a/app/src/auth/api/AuthController.ts b/app/src/auth/api/AuthController.ts index 1af39565..1f2b85de 100644 --- a/app/src/auth/api/AuthController.ts +++ b/app/src/auth/api/AuthController.ts @@ -184,6 +184,6 @@ export class AuthController extends Controller { this.registerStrategyActions(strategy, hono); } - return hono.all("*", (c) => c.notFound()); + return hono; } } diff --git a/app/src/core/index.ts b/app/src/core/index.ts index 9ff5370b..ae33dd65 100644 --- a/app/src/core/index.ts +++ b/app/src/core/index.ts @@ -26,7 +26,16 @@ export { } from "./object/query/query"; export { Registry, type Constructor } from "./registry/Registry"; export { getFlashMessage } from "./server/flash"; -export { s, jsc, describeRoute } from "./object/schema"; +export { + s, + parse, + jsc, + describeRoute, + schemaToSpec, + openAPISpecs, + type ParseOptions, + InvalidSchemaError, +} from "./object/schema"; export * from "./console"; export * from "./events"; diff --git a/app/src/core/types.ts b/app/src/core/types.ts index cfb32e47..1751766d 100644 --- a/app/src/core/types.ts +++ b/app/src/core/types.ts @@ -2,3 +2,5 @@ export interface Serializable { toJSON(): Json; fromJSON(json: Json): Class; } + +export type MaybePromise = T | Promise; diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index bc86a8df..086274f7 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -225,7 +225,7 @@ export class DataController extends Controller { }, ); - return hono.all("*", (c) => c.notFound()); + return hono; } private getEntityRoutes() { diff --git a/app/src/data/relations/RelationAccessor.ts b/app/src/data/relations/RelationAccessor.ts index dbd51e76..4c474b52 100644 --- a/app/src/data/relations/RelationAccessor.ts +++ b/app/src/data/relations/RelationAccessor.ts @@ -12,6 +12,15 @@ export class RelationAccessor { return this._relations; } + exists(relation: EntityRelation): boolean { + return this._relations.some( + (r) => + r.source.entity.name === relation.source.entity.name && + r.target.entity.name === relation.target.entity.name && + r.type === relation.type, + ); + } + /** * Searches for the relations of [entity_name] */ diff --git a/app/src/index.ts b/app/src/index.ts index b47e5658..8e75d169 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -18,6 +18,8 @@ export { type InitialModuleConfigs, } from "./modules/ModuleManager"; +export type { ServerEnv } from "modules/Controller"; + export * as middlewares from "modules/middlewares"; export { registries } from "modules/registries"; diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index 51c75860..18b536e8 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -51,7 +51,7 @@ export class AppMedia extends Module { this.ctx.server.route(this.basepath, new MediaController(this).getController()); const media = this.getMediaEntity(true); - this.ensureSchema( + this.ctx.helper.ensureSchema( em({ [media.name as "media"]: media }, ({ index }, { media }) => { index(media).on(["path"], true).on(["reference"]).on(["entity_id"]); }), diff --git a/app/src/media/api/MediaController.ts b/app/src/media/api/MediaController.ts index cdcbe5a6..66215746 100644 --- a/app/src/media/api/MediaController.ts +++ b/app/src/media/api/MediaController.ts @@ -297,6 +297,6 @@ export class MediaController extends Controller { }, ); - return hono.all("*", (c) => c.notFound()); + return hono; } } diff --git a/app/src/modules/Module.ts b/app/src/modules/Module.ts index 249fe082..d4164973 100644 --- a/app/src/modules/Module.ts +++ b/app/src/modules/Module.ts @@ -2,19 +2,10 @@ import type { Guard } from "auth"; import { type DebugLogger, SchemaObject } from "core"; import type { EventManager } from "core/events"; import type { Static, TSchema } from "core/utils"; -import { - type Connection, - type EntityIndex, - type EntityManager, - type Field, - FieldPrototype, - make, - type em as prototypeEm, -} from "data"; -import { Entity } from "data"; +import type { Connection, EntityManager } from "data"; import type { Hono } from "hono"; -import { isEqual } from "lodash-es"; import type { ServerEnv } from "modules/Controller"; +import type { ModuleHelper } from "./ModuleHelper"; export type ModuleBuildContext = { connection: Connection; @@ -24,6 +15,7 @@ export type ModuleBuildContext = { guard: Guard; logger: DebugLogger; flags: (typeof Module)["ctx_flags"]; + helper: ModuleHelper; }; export abstract class Module> { @@ -141,80 +133,4 @@ export abstract class Module> { return this.config; } - - protected ensureEntity(entity: Entity) { - const instance = this.ctx.em.entity(entity.name, true); - - // check fields - if (!instance) { - this.ctx.em.addEntity(entity); - this.ctx.flags.sync_required = true; - return; - } - - // if exists, check all fields required are there - // @todo: check if the field also equal - for (const field of entity.fields) { - const instanceField = instance.field(field.name); - if (!instanceField) { - instance.addField(field); - this.ctx.flags.sync_required = true; - } else { - const changes = this.setEntityFieldConfigs(field, instanceField); - if (changes > 0) { - this.ctx.flags.sync_required = true; - } - } - } - - // replace entity (mainly to keep the ensured type) - this.ctx.em.__replaceEntity( - new Entity(instance.name, instance.fields, instance.config, entity.type), - ); - } - - protected ensureIndex(index: EntityIndex) { - if (!this.ctx.em.hasIndex(index)) { - this.ctx.em.addIndex(index); - this.ctx.flags.sync_required = true; - } - } - - protected ensureSchema>(schema: Schema): Schema { - Object.values(schema.entities ?? {}).forEach(this.ensureEntity.bind(this)); - schema.indices?.forEach(this.ensureIndex.bind(this)); - - return schema; - } - - protected setEntityFieldConfigs( - parent: Field, - child: Field, - props: string[] = ["hidden", "fillable", "required"], - ) { - let changes = 0; - for (const prop of props) { - if (!isEqual(child.config[prop], parent.config[prop])) { - child.config[prop] = parent.config[prop]; - changes++; - } - } - return changes; - } - - protected replaceEntityField( - _entity: string | Entity, - field: Field | string, - _newField: Field | FieldPrototype, - ) { - const entity = this.ctx.em.entity(_entity); - const name = typeof field === "string" ? field : field.name; - const newField = - _newField instanceof FieldPrototype ? make(name, _newField as any) : _newField; - - // ensure keeping vital config - this.setEntityFieldConfigs(entity.field(name)!, newField); - - entity.__replaceField(name, newField); - } } diff --git a/app/src/modules/ModuleHelper.ts b/app/src/modules/ModuleHelper.ts new file mode 100644 index 00000000..5088510d --- /dev/null +++ b/app/src/modules/ModuleHelper.ts @@ -0,0 +1,113 @@ +import { + type EntityIndex, + type EntityRelation, + type Field, + type em as prototypeEm, + FieldPrototype, + make, + Entity, + entityTypes, +} from "data"; +import { isEqual } from "lodash-es"; +import type { ModuleBuildContext } from "./Module"; + +export class ModuleHelper { + constructor(protected ctx: Omit) {} + + get em() { + return this.ctx.em; + } + + get flags() { + return this.ctx.flags; + } + + ensureEntity(entity: Entity) { + const instance = this.em.entity(entity.name, true); + + // check fields + if (!instance) { + this.em.addEntity(entity); + this.flags.sync_required = true; + return; + } + + // if exists, check all fields required are there + // @todo: potentially identify system and generated entities and take that as instance + // @todo: check if the field also equal + for (const field of entity.fields) { + const instanceField = instance.field(field.name); + if (!instanceField) { + instance.addField(field); + this.flags.sync_required = true; + } else { + const changes = this.setEntityFieldConfigs(field, instanceField); + if (changes > 0) { + this.flags.sync_required = true; + } + } + } + + // if type is different, keep the highest + if (instance.type !== entity.type) { + const instance_i = entityTypes.indexOf(instance.type); + const entity_i = entityTypes.indexOf(entity.type); + const type = entity_i > instance_i ? entity.type : instance.type; + + this.em.__replaceEntity(new Entity(instance.name, instance.fields, instance.config, type)); + } + } + + ensureIndex(index: EntityIndex) { + if (!this.em.hasIndex(index)) { + this.em.addIndex(index); + this.flags.sync_required = true; + } + } + + ensureRelation(relation: EntityRelation) { + if (!this.em.relations.exists(relation)) { + this.em.addRelation(relation); + this.flags.sync_required = true; + } + } + + ensureSchema>(schema: Schema): Schema { + Object.values(schema.entities ?? {}).forEach(this.ensureEntity.bind(this)); + schema.indices?.forEach(this.ensureIndex.bind(this)); + schema.relations?.forEach(this.ensureRelation.bind(this)); + + return schema; + } + + setEntityFieldConfigs( + parent: Field, + child: Field, + props: string[] = ["hidden", "fillable", "required"], + ) { + let changes = 0; + for (const prop of props) { + if (!isEqual(child.config[prop], parent.config[prop])) { + child.config[prop] = parent.config[prop]; + changes++; + } + } + return changes; + } + + replaceEntityField( + _entity: string | Entity, + field: Field | string, + _newField: Field | FieldPrototype, + ) { + const entity = this.em.entity(_entity); + const name = typeof field === "string" ? field : field.name; + const newField = + _newField instanceof FieldPrototype ? make(name, _newField as any) : _newField; + + // ensure keeping vital config + this.setEntityFieldConfigs(entity.field(name)!, newField); + + entity.__replaceField(name, newField); + } +} diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index 022e3140..86030dfa 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -34,6 +34,7 @@ import { AppMedia } from "../media/AppMedia"; import type { ServerEnv } from "./Controller"; import { Module, type ModuleBuildContext } from "./Module"; import * as tbbox from "@sinclair/typebox"; +import { ModuleHelper } from "./ModuleHelper"; const { Type } = tbbox; export type { ModuleBuildContext }; @@ -92,6 +93,8 @@ export type ModuleManagerOptions = { trustFetched?: boolean; // runs when initial config provided on a fresh database seed?: (ctx: ModuleBuildContext) => Promise; + // called right after modules are built, before finish + onModulesBuilt?: (ctx: ModuleBuildContext) => Promise; /** @deprecated */ verbosity?: Verbosity; }; @@ -267,7 +270,7 @@ export class ModuleManager { this.guard = new Guard(); } - return { + const ctx = { connection: this.connection, server: this.server, em: this.em, @@ -276,6 +279,11 @@ export class ModuleManager { flags: Module.ctx_flags, logger: this.logger, }; + + return { + ...ctx, + helper: new ModuleHelper(ctx), + }; } private async fetch(): Promise { @@ -549,6 +557,10 @@ export class ModuleManager { this._built = state.built = true; this.logger.log("modules built", ctx.flags); + if (this.options?.onModulesBuilt) { + await this.options.onModulesBuilt(ctx); + } + if (options?.ignoreFlags !== true) { if (ctx.flags.sync_required) { ctx.flags.sync_required = false; From fe5ccd420608f1fab6c85ed7b0458a2eae0cc6a7 Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 17:00:06 +0200 Subject: [PATCH 04/39] refactor and move cloudflare image transformation plugin --- app/src/adapter/cloudflare/index.ts | 4 + .../plugins/image-optimization.plugin.ts | 86 +++++++++++++++++++ .../cloudflare/image-optimization-plugin.ts | 83 ------------------ 3 files changed, 90 insertions(+), 83 deletions(-) create mode 100644 app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts delete mode 100644 app/src/plugins/cloudflare/image-optimization-plugin.ts diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 60e6a774..57daed1e 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -1,4 +1,5 @@ import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection"; +import { ImageOptimizationPlugin } from "./plugins/image-optimization.plugin"; export * from "./cloudflare-workers.adapter"; export { makeApp, getFresh } from "./modes/fresh"; @@ -13,6 +14,9 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; +export const plugins = { + imageOptimization: ImageOptimizationPlugin, +}; export function d1(config: D1ConnectionConfig) { return new D1Connection(config); diff --git a/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts b/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts new file mode 100644 index 00000000..db9130a5 --- /dev/null +++ b/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts @@ -0,0 +1,86 @@ +import type { App, AppPlugin } from "bknd"; + +export type ImageOptimizationPluginOptions = { + accessUrl?: string; + resolvePath?: string; + autoFormat?: boolean; + devBypass?: string; +}; + +export function ImageOptimizationPlugin({ + accessUrl = "/_plugin/image/optimize", + resolvePath = "/api/media/file", + autoFormat = true, + devBypass, +}: ImageOptimizationPluginOptions = {}): AppPlugin { + const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; + if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { + throw new Error(`Disallowed accessUrl: ${accessUrl}`); + } + + return (app: App) => ({ + name: "cf-image-optimization", + onBuilt: () => { + app.server.get(`${accessUrl}/:path{.+$}`, async (c) => { + const request = c.req.raw; + const url = new URL(request.url); + + if (devBypass) { + return c.redirect(devBypass + url.pathname + url.search, 302); + } + + const storage = app.module.media?.storage; + if (!storage) { + throw new Error("No media storage configured"); + } + + const path = c.req.param("path"); + if (!path) { + throw new Error("No url provided"); + } + + const imageURL = `${url.origin}${resolvePath}/${path}`; + const metadata = await storage.objectMetadata(path); + + // Cloudflare-specific options are in the cf object. + const params = Object.fromEntries(url.searchParams.entries()); + const options: RequestInitCfPropertiesImage = {}; + + // Copy parameters from query string to request options. + // You can implement various different parameters here. + if ("fit" in params) options.fit = params.fit as any; + if ("width" in params) options.width = Number.parseInt(params.width); + if ("height" in params) options.height = Number.parseInt(params.height); + if ("quality" in params) options.quality = Number.parseInt(params.quality); + + // Your Worker is responsible for automatic format negotiation. Check the Accept header. + if (autoFormat) { + const accept = request.headers.get("Accept")!; + if (/image\/avif/.test(accept)) { + options.format = "avif"; + } else if (/image\/webp/.test(accept)) { + options.format = "webp"; + } + } + + // Build a request that passes through request headers + const imageRequest = new Request(imageURL, { + headers: request.headers, + }); + + // Returning fetch() with resizing options will pass through response with the resized image. + const res = await fetch(imageRequest, { cf: { image: options } }); + + return new Response(res.body, { + status: res.status, + statusText: res.statusText, + headers: { + "Cache-Control": "public, max-age=600", + "Content-Type": metadata.type, + "Content-Length": metadata.size.toString(), + }, + }); + }); + }, + }); +} diff --git a/app/src/plugins/cloudflare/image-optimization-plugin.ts b/app/src/plugins/cloudflare/image-optimization-plugin.ts deleted file mode 100644 index d611279a..00000000 --- a/app/src/plugins/cloudflare/image-optimization-plugin.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { App } from "../../App"; - -export type ImageOptimizationPluginOptions = { - accessUrl?: string; - resolvePath?: string; - autoFormat?: boolean; - devBypass?: string; -}; - -export function ImageOptimizationPlugin({ - accessUrl = "/_plugin/image/optimize", - resolvePath = "/api/media/file", - autoFormat = true, - devBypass, -}: ImageOptimizationPluginOptions = {}) { - const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; - if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { - throw new Error(`Disallowed accessUrl: ${accessUrl}`); - } - - return (app: App) => { - app.module.server.client.get(`${accessUrl}/:path{.+$}`, async (c) => { - const request = c.req.raw; - const url = new URL(request.url); - - if (devBypass) { - return c.redirect(devBypass + url.pathname + url.search, 302); - } - - const storage = app.module.media?.storage; - if (!storage) { - throw new Error("No media storage configured"); - } - - const path = c.req.param("path"); - if (!path) { - throw new Error("No url provided"); - } - - const imageURL = `${url.origin}${resolvePath}/${path}`; - const metadata = await storage.objectMetadata(path); - - // Cloudflare-specific options are in the cf object. - const params = Object.fromEntries(url.searchParams.entries()); - const options: RequestInitCfPropertiesImage = {}; - - // Copy parameters from query string to request options. - // You can implement various different parameters here. - if ("fit" in params) options.fit = params.fit as any; - if ("width" in params) options.width = Number.parseInt(params.width); - if ("height" in params) options.height = Number.parseInt(params.height); - if ("quality" in params) options.quality = Number.parseInt(params.quality); - - // Your Worker is responsible for automatic format negotiation. Check the Accept header. - if (autoFormat) { - const accept = request.headers.get("Accept")!; - if (/image\/avif/.test(accept)) { - options.format = "avif"; - } else if (/image\/webp/.test(accept)) { - options.format = "webp"; - } - } - - // Build a request that passes through request headers - const imageRequest = new Request(imageURL, { - headers: request.headers, - }); - - // Returning fetch() with resizing options will pass through response with the resized image. - const res = await fetch(imageRequest, { cf: { image: options } }); - - return new Response(res.body, { - status: res.status, - statusText: res.statusText, - headers: { - "Cache-Control": "public, max-age=600", - "Content-Type": metadata.type, - "Content-Length": metadata.size.toString(), - }, - }); - }); - }; -} From 8517c9b90ba1f27cfbff590d93bb212d27266195 Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 19:58:18 +0200 Subject: [PATCH 05/39] added a few initial plugins --- app/__test__/App.spec.ts | 4 +++ app/build.ts | 1 + app/package.json | 22 ++++++++++++ app/src/App.ts | 2 ++ app/src/adapter/cloudflare/index.ts | 4 --- app/src/data/entities/EntityTypescript.ts | 2 +- app/src/modules/server/SystemController.ts | 1 + .../cloudflare}/image-optimization.plugin.ts | 6 ++-- app/src/plugins/dev/show-routes.plugin.ts | 18 ++++++++++ app/src/plugins/dev/sync-config.plugin.ts | 35 +++++++++++++++++++ app/src/plugins/dev/sync-types.plugin.ts | 31 ++++++++++++++++ app/src/plugins/index.ts | 7 ++++ 12 files changed, 125 insertions(+), 8 deletions(-) rename app/src/{adapter/cloudflare/plugins => plugins/cloudflare}/image-optimization.plugin.ts (95%) create mode 100644 app/src/plugins/dev/show-routes.plugin.ts create mode 100644 app/src/plugins/dev/sync-config.plugin.ts create mode 100644 app/src/plugins/dev/sync-types.plugin.ts create mode 100644 app/src/plugins/index.ts diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 93c4f6fa..2eb070fc 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -51,6 +51,9 @@ describe("App tests", async () => { }, ); }, + onBoot: async () => { + called.push("onBoot"); + }, beforeBuild: async () => { called.push("beforeBuild"); }, @@ -90,6 +93,7 @@ describe("App tests", async () => { }, ]); expect(called).toEqual([ + "onBoot", "onServerInit", "beforeBuild", "onServerInit", diff --git a/app/build.ts b/app/build.ts index 698778b3..44f704a7 100644 --- a/app/build.ts +++ b/app/build.ts @@ -78,6 +78,7 @@ async function buildApi() { "src/core/utils/index.ts", "src/data/index.ts", "src/media/index.ts", + "src/plugins/index.ts", ], outDir: "dist", external: [...external], diff --git a/app/package.json b/app/package.json index 9ca0d897..a7e90507 100644 --- a/app/package.json +++ b/app/package.json @@ -183,6 +183,11 @@ "import": "./dist/media/index.js", "require": "./dist/media/index.js" }, + "./plugins": { + "types": "./dist/types/plugins/index.d.ts", + "import": "./dist/plugins/index.js", + "require": "./dist/plugins/index.js" + }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", @@ -231,6 +236,23 @@ "./dist/styles.css": "./dist/ui/styles.css", "./dist/manifest.json": "./dist/static/.vite/manifest.json" }, + "typesVersions": { + "*": { + "data": ["./dist/types/data/index.d.ts"], + "core": ["./dist/types/core/index.d.ts"], + "utils": ["./dist/types/core/utils/index.d.ts"], + "cli": ["./dist/types/cli/index.d.ts"], + "media": ["./dist/types/media/index.d.ts"], + "plugins": ["./dist/types/plugins/index.d.ts"], + "adapter": ["./dist/types/adapter/index.d.ts"], + "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], + "adapter/vite": ["./dist/types/adapter/vite/index.d.ts"], + "adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"], + "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], + "adapter/bun": ["./dist/types/adapter/bun/index.d.ts"], + "adapter/node": ["./dist/types/adapter/node/index.d.ts"] + } + }, "publishConfig": { "access": "public" }, diff --git a/app/src/App.ts b/app/src/App.ts index ea72feb8..628bfc32 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -27,6 +27,7 @@ export type AppPluginConfig = { onBuilt?: () => MaybePromise; onServerInit?: (server: Hono) => MaybePromise; onFirstBoot?: () => MaybePromise; + onBoot?: () => MaybePromise; }; export type AppPlugin = (app: App) => AppPluginConfig; @@ -93,6 +94,7 @@ export class App { private options?: AppOptions, ) { this.plugins = (options?.plugins ?? []).map((plugin) => plugin(this)); + this.runPlugins("onBoot"); this.modules = new ModuleManager(connection, { ...(options?.manager ?? {}), initial: _initialConfig, diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 57daed1e..60e6a774 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -1,5 +1,4 @@ import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection"; -import { ImageOptimizationPlugin } from "./plugins/image-optimization.plugin"; export * from "./cloudflare-workers.adapter"; export { makeApp, getFresh } from "./modes/fresh"; @@ -14,9 +13,6 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; -export const plugins = { - imageOptimization: ImageOptimizationPlugin, -}; export function d1(config: D1ConnectionConfig) { return new D1Connection(config); diff --git a/app/src/data/entities/EntityTypescript.ts b/app/src/data/entities/EntityTypescript.ts index 26c1df91..b0aa89ea 100644 --- a/app/src/data/entities/EntityTypescript.ts +++ b/app/src/data/entities/EntityTypescript.ts @@ -56,7 +56,7 @@ export class EntityTypescript { return this.em.entities.map((e) => e.toTypes()); } - protected getTab(count = 1) { + getTab(count = 1) { return this.options.indentChar.repeat(this.options.indentWidth).repeat(count); } diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index f457e477..47933811 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -317,6 +317,7 @@ export class SystemController extends Controller { local: datetimeStringLocal(), utc: datetimeStringUTC(), }, + plugins: this.app.plugins.map((p) => p.name), }), ); diff --git a/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts b/app/src/plugins/cloudflare/image-optimization.plugin.ts similarity index 95% rename from app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts rename to app/src/plugins/cloudflare/image-optimization.plugin.ts index db9130a5..f0239771 100644 --- a/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts +++ b/app/src/plugins/cloudflare/image-optimization.plugin.ts @@ -1,18 +1,18 @@ import type { App, AppPlugin } from "bknd"; -export type ImageOptimizationPluginOptions = { +export type CloudflareImageOptimizationOptions = { accessUrl?: string; resolvePath?: string; autoFormat?: boolean; devBypass?: string; }; -export function ImageOptimizationPlugin({ +export function cloudflareImageOptimization({ accessUrl = "/_plugin/image/optimize", resolvePath = "/api/media/file", autoFormat = true, devBypass, -}: ImageOptimizationPluginOptions = {}): AppPlugin { +}: CloudflareImageOptimizationOptions = {}): AppPlugin { const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { throw new Error(`Disallowed accessUrl: ${accessUrl}`); diff --git a/app/src/plugins/dev/show-routes.plugin.ts b/app/src/plugins/dev/show-routes.plugin.ts new file mode 100644 index 00000000..dcb75bf9 --- /dev/null +++ b/app/src/plugins/dev/show-routes.plugin.ts @@ -0,0 +1,18 @@ +import type { App, AppPlugin } from "bknd"; +import { showRoutes as showRoutesHono } from "hono/dev"; + +export type ShowRoutesOptions = { + once?: boolean; +}; + +export function showRoutes({ once = false }: ShowRoutesOptions = {}): AppPlugin { + let shown = false; + return (app: App) => ({ + name: "bknd-show-routes", + onBuilt: () => { + if (once && shown) return; + shown = true; + showRoutesHono(app.server); + }, + }); +} diff --git a/app/src/plugins/dev/sync-config.plugin.ts b/app/src/plugins/dev/sync-config.plugin.ts new file mode 100644 index 00000000..24d84d38 --- /dev/null +++ b/app/src/plugins/dev/sync-config.plugin.ts @@ -0,0 +1,35 @@ +import { App, type AppConfig, type AppPlugin } from "bknd"; + +export type SyncConfigOptions = { + enabled?: boolean; + includeSecrets?: boolean; + write: (config: AppConfig) => Promise; +}; + +export function syncConfig({ + enabled = true, + includeSecrets = false, + write, +}: SyncConfigOptions): AppPlugin { + let firstBoot = true; + return (app: App) => ({ + name: "bknd-sync-config", + onBuilt: async () => { + if (!enabled) return; + app.emgr.onEvent( + App.Events.AppConfigUpdatedEvent, + async () => { + await write?.(app.toJSON(includeSecrets)); + }, + { + id: "sync-config", + }, + ); + + if (firstBoot) { + firstBoot = false; + await write?.(app.toJSON(true)); + } + }, + }); +} diff --git a/app/src/plugins/dev/sync-types.plugin.ts b/app/src/plugins/dev/sync-types.plugin.ts new file mode 100644 index 00000000..484f8b65 --- /dev/null +++ b/app/src/plugins/dev/sync-types.plugin.ts @@ -0,0 +1,31 @@ +import { App, type AppPlugin } from "bknd"; +import { EntityTypescript } from "data/entities/EntityTypescript"; + +export type SyncTypesOptions = { + enabled?: boolean; + write: (et: EntityTypescript) => Promise; +}; + +export function syncTypes({ enabled = true, write }: SyncTypesOptions): AppPlugin { + let firstBoot = true; + return (app: App) => ({ + name: "bknd-sync-types", + onBuilt: async () => { + if (!enabled) return; + app.emgr.onEvent( + App.Events.AppConfigUpdatedEvent, + async () => { + await write?.(new EntityTypescript(app.em)); + }, + { + id: "sync-types", + }, + ); + + if (firstBoot) { + firstBoot = false; + await write?.(new EntityTypescript(app.em)); + } + }, + }); +} diff --git a/app/src/plugins/index.ts b/app/src/plugins/index.ts new file mode 100644 index 00000000..ee7a31ab --- /dev/null +++ b/app/src/plugins/index.ts @@ -0,0 +1,7 @@ +export { + cloudflareImageOptimization, + type CloudflareImageOptimizationOptions, +} from "./cloudflare/image-optimization.plugin"; +export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin"; +export { syncConfig, type SyncConfigOptions } from "./dev/sync-config.plugin"; +export { syncTypes, type SyncTypesOptions } from "./dev/sync-types.plugin"; From 28e277afe1f7a00a10780f6bd798005e98c32789 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 08:30:54 +0200 Subject: [PATCH 06/39] updated cf image optimization plugin --- app/src/index.ts | 1 + app/src/plugins/cloudflare/image-optimization.plugin.ts | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/src/index.ts b/app/src/index.ts index 8e75d169..f9c1e636 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -19,6 +19,7 @@ export { } from "./modules/ModuleManager"; export type { ServerEnv } from "modules/Controller"; +export type { BkndConfig } from "bknd/adapter"; export * as middlewares from "modules/middlewares"; export { registries } from "modules/registries"; diff --git a/app/src/plugins/cloudflare/image-optimization.plugin.ts b/app/src/plugins/cloudflare/image-optimization.plugin.ts index f0239771..86fb93b1 100644 --- a/app/src/plugins/cloudflare/image-optimization.plugin.ts +++ b/app/src/plugins/cloudflare/image-optimization.plugin.ts @@ -4,14 +4,12 @@ export type CloudflareImageOptimizationOptions = { accessUrl?: string; resolvePath?: string; autoFormat?: boolean; - devBypass?: string; }; export function cloudflareImageOptimization({ accessUrl = "/_plugin/image/optimize", resolvePath = "/api/media/file", autoFormat = true, - devBypass, }: CloudflareImageOptimizationOptions = {}): AppPlugin { const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { @@ -25,10 +23,6 @@ export function cloudflareImageOptimization({ const request = c.req.raw; const url = new URL(request.url); - if (devBypass) { - return c.redirect(devBypass + url.pathname + url.search, 302); - } - const storage = app.module.media?.storage; if (!storage) { throw new Error("No media storage configured"); From bbb7bfb7a187d6c35eaa59ce44e8a5dd22e9a972 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 11:09:47 +0200 Subject: [PATCH 07/39] feat: adding env aware endpoint to obtain sqlite connection, remove libsql hard dependency --- app/build.ts | 29 ++++++++++- app/package.json | 19 +++++++- app/src/App.ts | 45 ++++------------- app/src/adapter/bun/bun.adapter.ts | 26 ++++++++-- .../bun/connection/BunSqliteConnection.ts | 25 ++++++---- app/src/adapter/bun/index.ts | 2 + app/src/adapter/cloudflare/modes/durable.ts | 2 +- app/src/adapter/index.ts | 2 + .../node/connection/NodeSqliteConnection.ts | 17 +++++-- app/src/adapter/node/index.ts | 19 +------- app/src/adapter/node/node.adapter.ts | 22 ++++++++- app/src/adapter/node/storage/index.ts | 17 +++++++ app/src/adapter/sqlite/bun.ts | 8 ++++ app/src/adapter/sqlite/edge.ts | 8 ++++ app/src/adapter/sqlite/node.ts | 8 ++++ app/src/adapter/sqlite/types.ts | 3 ++ app/src/core/utils/runtime.ts | 8 ++++ app/src/data/api/DataApi.ts | 29 +++++------ app/src/data/connection/DummyConnection.ts | 2 + app/src/data/connection/index.ts | 4 +- .../connection/sqlite/LibsqlConnection.ts | 42 ++++++++-------- app/src/data/entities/index.ts | 2 + .../data/entities/mutation/MutatorResult.ts | 2 +- app/src/media/api/MediaController.ts | 8 +++- app/src/modules/ModuleManager.ts | 3 +- app/src/ui/client/api/use-entity.ts | 8 ++-- app/vite.dev.ts | 48 +++++++++---------- bun.lock | 39 ++++++++++----- 28 files changed, 288 insertions(+), 159 deletions(-) create mode 100644 app/src/adapter/node/storage/index.ts create mode 100644 app/src/adapter/sqlite/bun.ts create mode 100644 app/src/adapter/sqlite/edge.ts create mode 100644 app/src/adapter/sqlite/node.ts create mode 100644 app/src/adapter/sqlite/types.ts diff --git a/app/build.ts b/app/build.ts index 698778b3..f5262353 100644 --- a/app/build.ts +++ b/app/build.ts @@ -244,7 +244,11 @@ async function buildAdapters() { // specific adatpers await tsup.build(baseConfig("react-router")); - await tsup.build(baseConfig("bun")); + await tsup.build( + baseConfig("bun", { + external: [/^bun\:.*/], + }), + ); await tsup.build(baseConfig("astro")); await tsup.build(baseConfig("aws")); await tsup.build( @@ -267,6 +271,29 @@ async function buildAdapters() { ...baseConfig("node"), platform: "node", }); + + await tsup.build({ + ...baseConfig("sqlite/edge"), + entry: ["src/adapter/sqlite/edge.ts"], + outDir: "dist/adapter/sqlite", + metafile: false, + }); + + await tsup.build({ + ...baseConfig("sqlite/node"), + entry: ["src/adapter/sqlite/node.ts"], + outDir: "dist/adapter/sqlite", + platform: "node", + metafile: false, + }); + + await tsup.build({ + ...baseConfig("sqlite/bun"), + entry: ["src/adapter/sqlite/bun.ts"], + outDir: "dist/adapter/sqlite", + metafile: false, + external: [/^bun\:.*/], + }); } await buildApi(); diff --git a/app/package.json b/app/package.json index c8d326e4..516d1d90 100644 --- a/app/package.json +++ b/app/package.json @@ -50,7 +50,6 @@ "@codemirror/lang-json": "^6.0.1", "@hello-pangea/dnd": "^18.0.1", "@hono/swagger-ui": "^0.5.1", - "@libsql/client": "^0.15.2", "@mantine/core": "^7.17.1", "@mantine/hooks": "^7.17.1", "@sinclair/typebox": "0.34.30", @@ -61,11 +60,11 @@ "bcryptjs": "^3.0.2", "dayjs": "^1.11.13", "fast-xml-parser": "^5.0.8", + "hono": "^4.7.11", "json-schema-form-react": "^0.0.2", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", "kysely": "^0.27.6", - "hono": "^4.7.11", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", "object-path-immutable": "^4.1.2", @@ -74,6 +73,7 @@ "uuid": "^11.1.0" }, "devDependencies": { + "@libsql/client": "^0.15.9", "@aws-sdk/client-s3": "^3.758.0", "@bluwy/giget-core": "^0.1.2", "@cloudflare/workers-types": "^4.20250606.0", @@ -104,6 +104,7 @@ "jsonv-ts": "^0.1.0", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", + "libsql-stateless-easy": "^1.8.0", "open": "^10.1.0", "openapi-types": "^12.1.3", "picocolors": "^1.1.1", @@ -184,6 +185,20 @@ "import": "./dist/media/index.js", "require": "./dist/media/index.js" }, + "./adapter/sqlite": { + "types": "./dist/types/adapter/sqlite/edge.d.ts", + "import": { + "workerd": "./dist/adapter/sqlite/edge.js", + "edge-light": "./dist/adapter/sqlite/edge.js", + "netlify": "./dist/adapter/sqlite/edge.js", + "vercel": "./dist/adapter/sqlite/edge.js", + "browser": "./dist/adapter/sqlite/edge.js", + "bun": "./dist/adapter/sqlite/bun.js", + "node": "./dist/adapter/sqlite/node.js", + "default": "./dist/adapter/sqlite/node.js" + }, + "require": "./dist/adapter/sqlite/node.js" + }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", diff --git a/app/src/App.ts b/app/src/App.ts index 956229c2..2ad76dff 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -1,7 +1,7 @@ import type { CreateUserPayload } from "auth/AppAuth"; import { $console } from "core"; import { Event } from "core/events"; -import { Connection, type LibSqlCredentials, LibsqlConnection } from "data"; +import { Connection } from "data/connection/Connection"; import type { Hono } from "hono"; import { ModuleManager, @@ -14,10 +14,10 @@ import { import * as SystemPermissions from "modules/permissions"; import { AdminController, type AdminControllerOptions } from "modules/server/AdminController"; import { SystemController } from "modules/server/SystemController"; +import type { ServerEnv } from "modules/Controller"; // biome-ignore format: must be here import { Api, type ApiOptions } from "Api"; -import type { ServerEnv } from "modules/Controller"; export type AppPlugin = (app: App) => Promise | void; @@ -51,15 +51,8 @@ export type AppOptions = { manager?: Omit; asyncEventsMode?: "sync" | "async" | "none"; }; -export type CreateAppConfig = { - connection?: - | Connection - | { - // @deprecated - type: "libsql"; - config: LibSqlCredentials; - } - | LibSqlCredentials; +export type CreateAppConfig = { + connection?: C | { url: string }; initialConfig?: InitialModuleConfigs; options?: AppOptions; }; @@ -67,7 +60,7 @@ export type CreateAppConfig = { export type AppConfig = InitialModuleConfigs; export type LocalApiOptions = Request | ApiOptions; -export class App { +export class App { static readonly Events = AppEvents; modules: ModuleManager; @@ -79,7 +72,7 @@ export class App { private _building: boolean = false; constructor( - private connection: Connection, + public connection: C, _initialConfig?: InitialModuleConfigs, private options?: AppOptions, ) { @@ -262,31 +255,9 @@ export class App { } export function createApp(config: CreateAppConfig = {}) { - let connection: Connection | undefined = undefined; - - try { - if (Connection.isConnection(config.connection)) { - connection = config.connection; - } else if (typeof config.connection === "object") { - if ("type" in config.connection) { - $console.warn( - "Using deprecated connection type 'libsql', use the 'config' object directly.", - ); - connection = new LibsqlConnection(config.connection.config); - } else { - connection = new LibsqlConnection(config.connection); - } - } else { - connection = new LibsqlConnection({ url: ":memory:" }); - $console.warn("No connection provided, using in-memory database"); - } - } catch (e) { - $console.error("Could not create connection", e); - } - - if (!connection) { + if (!config.connection || !Connection.isConnection(config.connection)) { throw new Error("Invalid connection"); } - return new App(connection, config.initialConfig, config.options); + return new App(config.connection, config.initialConfig, config.options); } diff --git a/app/src/adapter/bun/bun.adapter.ts b/app/src/adapter/bun/bun.adapter.ts index 44fb795f..07b778ae 100644 --- a/app/src/adapter/bun/bun.adapter.ts +++ b/app/src/adapter/bun/bun.adapter.ts @@ -1,11 +1,18 @@ /// import path from "node:path"; -import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; -import { registerLocalMediaAdapter } from "bknd/adapter/node"; +import { + type RuntimeBkndConfig, + createRuntimeApp, + type RuntimeOptions, + Connection, +} from "bknd/adapter"; +import { registerLocalMediaAdapter } from "."; import { config } from "bknd/core"; import type { ServeOptions } from "bun"; import { serveStatic } from "hono/bun"; +import { sqlite } from "bknd/adapter/sqlite"; +import type { App } from "App"; type BunEnv = Bun.Env; export type BunBkndConfig = RuntimeBkndConfig & Omit; @@ -18,9 +25,17 @@ export async function createApp( const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static"); registerLocalMediaAdapter(); + let connection: Connection | undefined; + if (Connection.isConnection(config.connection)) { + connection = config.connection; + } else { + connection = sqlite(config.connection ?? { url: ":memory:" }); + } + return await createRuntimeApp( { ...config, + connection, serveStatic: serveStatic({ root }), }, args ?? (process.env as Env), @@ -33,8 +48,11 @@ export function createHandler( args: Env = {} as Env, opts?: RuntimeOptions, ) { + let app: App | undefined; return async (req: Request) => { - const app = await createApp(config, args ?? (process.env as Env), opts); + if (!app) { + app = await createApp(config, args ?? (process.env as Env), opts); + } return app.fetch(req); }; } @@ -72,5 +90,5 @@ export function serve( ), }); - console.log(`Server is running on http://localhost:${port}`); + console.info(`Server is running on http://localhost:${port}`); } diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index dc5638f9..7f124d1a 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -1,4 +1,4 @@ -import type { Database } from "bun:sqlite"; +import { Database } from "bun:sqlite"; import { buildQueryFn, GenericSqliteConnection, @@ -30,12 +30,19 @@ function bunSqliteExecutor(db: Database, cache: boolean): IGenericSqlite bunSqliteExecutor(config.database, false), - { - name: "bun-sqlite", - }, - ); +export function bunSqlite(config?: BunSqliteConnectionConfig | { url: string }) { + let database: Database; + if (config) { + if ("database" in config) { + database = config.database; + } else { + database = new Database(config.url); + } + } else { + database = new Database(":memory:"); + } + + return new GenericSqliteConnection(database, () => bunSqliteExecutor(database, false), { + name: "bun-sqlite", + }); } diff --git a/app/src/adapter/bun/index.ts b/app/src/adapter/bun/index.ts index c95cfb4f..5f851352 100644 --- a/app/src/adapter/bun/index.ts +++ b/app/src/adapter/bun/index.ts @@ -1 +1,3 @@ export * from "./bun.adapter"; +export * from "../node/storage"; +export * from "./connection/BunSqliteConnection"; diff --git a/app/src/adapter/cloudflare/modes/durable.ts b/app/src/adapter/cloudflare/modes/durable.ts index 310fd244..25dd0b42 100644 --- a/app/src/adapter/cloudflare/modes/durable.ts +++ b/app/src/adapter/cloudflare/modes/durable.ts @@ -64,7 +64,7 @@ export class DurableBkndApp extends DurableObject { "type" in config.connection && config.connection.type === "libsql" ) { - config.connection.config.protocol = "wss"; + //config.connection.config.protocol = "wss"; } this.app = await createRuntimeApp({ diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 84fb8c51..569afd6e 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -3,6 +3,8 @@ import { config as $config } from "bknd/core"; import type { MiddlewareHandler } from "hono"; import type { AdminControllerOptions } from "modules/server/AdminController"; +export { Connection } from "bknd/data"; + export type BkndConfig = CreateAppConfig & { app?: CreateAppConfig | ((args: Args) => CreateAppConfig); onBuilt?: (app: App) => Promise; diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index b6faad44..8fdc6306 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -4,7 +4,7 @@ import { parseBigInt, type IGenericSqlite, } from "../../../data/connection/sqlite/GenericSqliteConnection"; -import type { DatabaseSync } from "node:sqlite"; +import { DatabaseSync } from "node:sqlite"; export type NodeSqliteConnectionConfig = { database: DatabaseSync; @@ -39,8 +39,19 @@ function nodeSqliteExecutor(db: DatabaseSync): IGenericSqlite { }; } -export function nodeSqlite(config: NodeSqliteConnectionConfig) { - return new GenericSqliteConnection(config.database, () => nodeSqliteExecutor(config.database), { +export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }) { + let database: DatabaseSync; + if (config) { + if ("database" in config) { + database = config.database; + } else { + database = new DatabaseSync(config.url); + } + } else { + database = new DatabaseSync(":memory:"); + } + + return new GenericSqliteConnection(database, () => nodeSqliteExecutor(database), { name: "node-sqlite", }); } diff --git a/app/src/adapter/node/index.ts b/app/src/adapter/node/index.ts index 16a6b2af..b430450f 100644 --- a/app/src/adapter/node/index.ts +++ b/app/src/adapter/node/index.ts @@ -1,18 +1,3 @@ -import { registries } from "bknd"; -import { type LocalAdapterConfig, StorageLocalAdapter } from "./storage/StorageLocalAdapter"; - export * from "./node.adapter"; -export { StorageLocalAdapter, type LocalAdapterConfig }; - -let registered = false; -export function registerLocalMediaAdapter() { - if (!registered) { - registries.media.register("local", StorageLocalAdapter); - registered = true; - } - - return (config: Partial = {}) => { - const adapter = new StorageLocalAdapter(config); - return adapter.toJSON(true); - }; -} +export * from "./storage"; +export * from "./connection/NodeSqliteConnection"; diff --git a/app/src/adapter/node/node.adapter.ts b/app/src/adapter/node/node.adapter.ts index ed078008..02aa829b 100644 --- a/app/src/adapter/node/node.adapter.ts +++ b/app/src/adapter/node/node.adapter.ts @@ -2,9 +2,16 @@ import path from "node:path"; import { serve as honoServe } from "@hono/node-server"; import { serveStatic } from "@hono/node-server/serve-static"; import { registerLocalMediaAdapter } from "adapter/node/index"; -import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; +import { + type RuntimeBkndConfig, + createRuntimeApp, + type RuntimeOptions, + Connection, +} from "bknd/adapter"; import { config as $config } from "bknd/core"; import { $console } from "core"; +import { sqlite } from "bknd/adapter/sqlite"; +import type { App } from "App"; type NodeEnv = NodeJS.ProcessEnv; export type NodeBkndConfig = RuntimeBkndConfig & { @@ -28,10 +35,18 @@ export async function createApp( console.warn("relativeDistPath is deprecated, please use distPath instead"); } + let connection: Connection | undefined; + if (Connection.isConnection(config.connection)) { + connection = config.connection; + } else { + connection = sqlite(config.connection ?? { url: ":memory:" }); + } + registerLocalMediaAdapter(); return await createRuntimeApp( { ...config, + connection, serveStatic: serveStatic({ root }), }, // @ts-ignore @@ -45,8 +60,11 @@ export function createHandler( args: Env = {} as Env, opts?: RuntimeOptions, ) { + let app: App | undefined; return async (req: Request) => { - const app = await createApp(config, args ?? (process.env as Env), opts); + if (!app) { + app = await createApp(config, args ?? (process.env as Env), opts); + } return app.fetch(req); }; } diff --git a/app/src/adapter/node/storage/index.ts b/app/src/adapter/node/storage/index.ts new file mode 100644 index 00000000..549bf702 --- /dev/null +++ b/app/src/adapter/node/storage/index.ts @@ -0,0 +1,17 @@ +import { registries } from "bknd"; +import { type LocalAdapterConfig, StorageLocalAdapter } from "./StorageLocalAdapter"; + +export * from "./StorageLocalAdapter"; + +let registered = false; +export function registerLocalMediaAdapter() { + if (!registered) { + registries.media.register("local", StorageLocalAdapter); + registered = true; + } + + return (config: Partial = {}) => { + const adapter = new StorageLocalAdapter(config); + return adapter.toJSON(true); + }; +} diff --git a/app/src/adapter/sqlite/bun.ts b/app/src/adapter/sqlite/bun.ts new file mode 100644 index 00000000..328186cb --- /dev/null +++ b/app/src/adapter/sqlite/bun.ts @@ -0,0 +1,8 @@ +import type { Connection } from "bknd/data"; +import { $console } from "bknd/core"; +import { bunSqlite } from "../bun/connection/BunSqliteConnection"; + +export function sqlite(config: { url: string }): Connection { + $console.info("Using bun-sqlite", config); + return bunSqlite(config); +} diff --git a/app/src/adapter/sqlite/edge.ts b/app/src/adapter/sqlite/edge.ts new file mode 100644 index 00000000..996d7f58 --- /dev/null +++ b/app/src/adapter/sqlite/edge.ts @@ -0,0 +1,8 @@ +import { $console } from "bknd/core"; +import type { Connection } from "bknd/data"; +import { libsql } from "../../data/connection/sqlite/LibsqlConnection"; + +export function sqlite(config: { url: string }): Connection { + $console.info("Using libsql", config); + return libsql(config); +} diff --git a/app/src/adapter/sqlite/node.ts b/app/src/adapter/sqlite/node.ts new file mode 100644 index 00000000..1643cce6 --- /dev/null +++ b/app/src/adapter/sqlite/node.ts @@ -0,0 +1,8 @@ +import { $console } from "bknd/core"; +import type { Connection } from "bknd/data"; +import { nodeSqlite } from "../node/connection/NodeSqliteConnection"; + +export function sqlite(config: { url: string }): Connection { + $console.info("Using node-sqlite", config); + return nodeSqlite(config); +} diff --git a/app/src/adapter/sqlite/types.ts b/app/src/adapter/sqlite/types.ts new file mode 100644 index 00000000..2a0a5e58 --- /dev/null +++ b/app/src/adapter/sqlite/types.ts @@ -0,0 +1,3 @@ +import type { Connection } from "bknd/data"; + +export type SqliteConnection = (config: { url: string }) => Connection; diff --git a/app/src/core/utils/runtime.ts b/app/src/core/utils/runtime.ts index b90f6706..0772abd2 100644 --- a/app/src/core/utils/runtime.ts +++ b/app/src/core/utils/runtime.ts @@ -48,6 +48,14 @@ export function isNode() { } } +export function isBun() { + try { + return typeof Bun !== "undefined"; + } catch (e) { + return false; + } +} + export function invariant(condition: boolean | any, message: string) { if (!condition) { throw new Error(message); diff --git a/app/src/data/api/DataApi.ts b/app/src/data/api/DataApi.ts index 6103220c..bd670e86 100644 --- a/app/src/data/api/DataApi.ts +++ b/app/src/data/api/DataApi.ts @@ -1,5 +1,5 @@ import type { DB } from "core"; -import type { EntityData, RepoQueryIn, RepositoryResponse } from "data"; +import type { EntityData, RepoQueryIn, RepositoryResultJSON } from "data"; import type { Insertable, Selectable, Updateable } from "kysely"; import { type BaseModuleApiOptions, ModuleApi, type PrimaryFieldType } from "modules"; import type { FetchPromise, ResponseObject } from "modules/ModuleApi"; @@ -32,10 +32,7 @@ export class DataApi extends ModuleApi { query: Omit = {}, ) { type Data = E extends keyof DB ? Selectable : EntityData; - return this.get, "meta" | "data">>( - ["entity", entity as any, id], - query, - ); + return this.get>(["entity", entity as any, id], query); } readOneBy( @@ -43,7 +40,7 @@ export class DataApi extends ModuleApi { query: Omit = {}, ) { type Data = E extends keyof DB ? Selectable : EntityData; - type T = Pick, "meta" | "data">; + type T = RepositoryResultJSON; return this.readMany(entity, { ...query, limit: 1, @@ -53,7 +50,7 @@ export class DataApi extends ModuleApi { readMany(entity: E, query: RepoQueryIn = {}) { type Data = E extends keyof DB ? Selectable : EntityData; - type T = Pick, "meta" | "data">; + type T = RepositoryResultJSON; const input = query ?? this.options.defaultQuery; const req = this.get(["entity", entity as any], input); @@ -72,7 +69,7 @@ export class DataApi extends ModuleApi { query: RepoQueryIn = {}, ) { type Data = R extends keyof DB ? Selectable : EntityData; - return this.get, "meta" | "data">>( + return this.get>( ["entity", entity as any, id, reference], query ?? this.options.defaultQuery, ); @@ -83,7 +80,7 @@ export class DataApi extends ModuleApi { input: Insertable, ) { type Data = E extends keyof DB ? Selectable : EntityData; - return this.post>(["entity", entity as any], input); + return this.post>(["entity", entity as any], input); } createMany( @@ -94,7 +91,7 @@ export class DataApi extends ModuleApi { throw new Error("input is required"); } type Data = E extends keyof DB ? Selectable : EntityData; - return this.post>(["entity", entity as any], input); + return this.post>(["entity", entity as any], input); } updateOne( @@ -104,7 +101,7 @@ export class DataApi extends ModuleApi { ) { if (!id) throw new Error("ID is required"); type Data = E extends keyof DB ? Selectable : EntityData; - return this.patch>(["entity", entity as any, id], input); + return this.patch>(["entity", entity as any, id], input); } updateMany( @@ -114,7 +111,7 @@ export class DataApi extends ModuleApi { ) { this.requireObjectSet(where); type Data = E extends keyof DB ? Selectable : EntityData; - return this.patch>(["entity", entity as any], { + return this.patch>(["entity", entity as any], { update, where, }); @@ -123,24 +120,24 @@ export class DataApi extends ModuleApi { deleteOne(entity: E, id: PrimaryFieldType) { if (!id) throw new Error("ID is required"); type Data = E extends keyof DB ? Selectable : EntityData; - return this.delete>(["entity", entity as any, id]); + return this.delete>(["entity", entity as any, id]); } deleteMany(entity: E, where: RepoQueryIn["where"]) { this.requireObjectSet(where); type Data = E extends keyof DB ? Selectable : EntityData; - return this.delete>(["entity", entity as any], where); + return this.delete>(["entity", entity as any], where); } count(entity: E, where: RepoQueryIn["where"] = {}) { - return this.post>( + return this.post>( ["entity", entity as any, "fn", "count"], where, ); } exists(entity: E, where: RepoQueryIn["where"] = {}) { - return this.post>( + return this.post>( ["entity", entity as any, "fn", "exists"], where, ); diff --git a/app/src/data/connection/DummyConnection.ts b/app/src/data/connection/DummyConnection.ts index d04d0afe..aa7eb9df 100644 --- a/app/src/data/connection/DummyConnection.ts +++ b/app/src/data/connection/DummyConnection.ts @@ -1,6 +1,8 @@ import { Connection, type FieldSpec, type SchemaResponse } from "./Connection"; export class DummyConnection extends Connection { + override name: string = "dummy"; + protected override readonly supported = { batching: true, }; diff --git a/app/src/data/connection/index.ts b/app/src/data/connection/index.ts index ce8c7ff8..9bd285ed 100644 --- a/app/src/data/connection/index.ts +++ b/app/src/data/connection/index.ts @@ -5,11 +5,13 @@ export { type IndexSpec, type DbFunctions, type SchemaResponse, + type ConnQuery, + type ConnQueryResults, customIntrospector, } from "./Connection"; // sqlite -export { LibsqlConnection, type LibSqlCredentials } from "./sqlite/LibsqlConnection"; +//export { libsql, LibsqlConnection, type LibSqlCredentials } from "./sqlite/LibsqlConnection"; export { SqliteConnection } from "./sqlite/SqliteConnection"; export { SqliteIntrospector } from "./sqlite/SqliteIntrospector"; export { SqliteLocalConnection } from "./sqlite/SqliteLocalConnection"; diff --git a/app/src/data/connection/sqlite/LibsqlConnection.ts b/app/src/data/connection/sqlite/LibsqlConnection.ts index cf0ff587..e6833eb1 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/LibsqlConnection.ts @@ -1,15 +1,29 @@ -import { createClient, type Client, type Config, type InStatement } from "@libsql/client"; +import type { Client, Config, InStatement } from "@libsql/client"; +import { createClient } from "libsql-stateless-easy"; import { LibsqlDialect } from "@libsql/kysely-libsql"; -import { $console } from "core"; import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; -import type { ConnQuery, ConnQueryResults } from "../Connection"; -import { SqliteConnection } from "./SqliteConnection"; +import { type ConnQuery, type ConnQueryResults, SqliteConnection } from "bknd/data"; export const LIBSQL_PROTOCOLS = ["wss", "https", "libsql"] as const; export type LibSqlCredentials = Config & { protocol?: (typeof LIBSQL_PROTOCOLS)[number]; }; +function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { + if (clientOrCredentials && "url" in clientOrCredentials) { + let { url, authToken, protocol } = clientOrCredentials; + if (protocol && LIBSQL_PROTOCOLS.includes(protocol)) { + console.info("changing protocol to", protocol); + const [, rest] = url.split("://"); + url = `${protocol}://${rest}`; + } + + return createClient({ url, authToken }); + } + + return clientOrCredentials as Client; +} + export class LibsqlConnection extends SqliteConnection { override name = "libsql"; protected override readonly supported = { @@ -17,22 +31,8 @@ export class LibsqlConnection extends SqliteConnection { softscans: true, }; - constructor(client: Client); - constructor(credentials: LibSqlCredentials); constructor(clientOrCredentials: Client | LibSqlCredentials) { - let client: Client; - if (clientOrCredentials && "url" in clientOrCredentials) { - let { url, authToken, protocol } = clientOrCredentials; - if (protocol && LIBSQL_PROTOCOLS.includes(protocol)) { - $console.log("changing protocol to", protocol); - const [, rest] = url.split("://"); - url = `${protocol}://${rest}`; - } - - client = createClient({ url, authToken }); - } else { - client = clientOrCredentials; - } + const client = getClient(clientOrCredentials); super({ excludeTables: ["libsql_wasm_func_table"], @@ -56,3 +56,7 @@ export class LibsqlConnection extends SqliteConnection { return this.withTransformedRows(await this.client.batch(stms)) as any; } } + +export function libsql(credentials: LibSqlCredentials): LibsqlConnection { + return new LibsqlConnection(credentials); +} diff --git a/app/src/data/entities/index.ts b/app/src/data/entities/index.ts index eb5f3b23..5beca373 100644 --- a/app/src/data/entities/index.ts +++ b/app/src/data/entities/index.ts @@ -4,3 +4,5 @@ export * from "./mutation/Mutator"; export * from "./query/Repository"; export * from "./query/WhereBuilder"; export * from "./query/WithBuilder"; +export * from "./query/RepositoryResult"; +export * from "./mutation/MutatorResult"; diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index 88cf4355..25ae7f66 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -19,7 +19,7 @@ export class MutatorResult extends Result { hydrator: (rows) => em.hydrate(entity.name, rows as any), beforeExecute: (compiled) => { if (!options?.silent) { - $console.debug(`[Mutation]\n${compiled.sql}\n`, compiled.parameters); + $console.debug(`[Mutation]\n${compiled.sql}\n`); } }, onError: (error) => { diff --git a/app/src/media/api/MediaController.ts b/app/src/media/api/MediaController.ts index cdcbe5a6..9a70e911 100644 --- a/app/src/media/api/MediaController.ts +++ b/app/src/media/api/MediaController.ts @@ -216,7 +216,9 @@ export class MediaController extends Controller { const paths_to_delete: string[] = []; if (max_items) { const { overwrite } = c.req.valid("query"); - const { count } = await this.media.em.repository(media_entity).count(mediaRef); + const { + data: { count }, + } = await this.media.em.repository(media_entity).count(mediaRef); // if there are more than or equal to max items if (count >= max_items) { @@ -255,7 +257,9 @@ export class MediaController extends Controller { } // check if entity exists in database - const { exists } = await this.media.em.repository(entity).exists({ id: entity_id }); + const { + data: { exists }, + } = await this.media.em.repository(entity).exists({ id: entity_id }); if (!exists) { return c.json( { error: `Entity "${entity_name}" with ID "${entity_id}" doesn't exist found` }, diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index 022e3140..d933cebb 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -234,7 +234,8 @@ export class ModuleManager { } private get db() { - return this.connection.kysely as Kysely<{ table: ConfigTable }>; + // @todo: check why this is neccessary + return this.connection.kysely as unknown as Kysely<{ table: ConfigTable }>; } // @todo: add indices for: version, type diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index 3995e56b..4219c412 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -1,7 +1,7 @@ import type { DB, PrimaryFieldType } from "core"; import { objectTransform } from "core/utils/objects"; import { encodeSearch } from "core/utils/reqres"; -import type { EntityData, RepoQueryIn, RepositoryResponse } from "data"; +import type { EntityData, RepoQueryIn, RepositoryResult } from "data"; import type { Insertable, Selectable, Updateable } from "kysely"; import type { FetchPromise, ModuleApi, ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, type SWRResponse, mutate } from "swr"; @@ -28,15 +28,13 @@ interface UseEntityReturn< Entity extends keyof DB | string, Id extends PrimaryFieldType | undefined, Data = Entity extends keyof DB ? DB[Entity] : EntityData, - Response = ResponseObject>>, + Response = ResponseObject>>, > { create: (input: Insertable) => Promise; read: ( query?: RepoQueryIn, ) => Promise< - ResponseObject< - RepositoryResponse[] : Selectable> - > + ResponseObject[] : Selectable>> >; update: Id extends undefined ? (input: Updateable, id: Id) => Promise diff --git a/app/vite.dev.ts b/app/vite.dev.ts index 2ce51c1d..913d4881 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -3,30 +3,33 @@ import { serveStatic } from "@hono/node-server/serve-static"; import { showRoutes } from "hono/dev"; import { App, registries } from "./src"; import { StorageLocalAdapter } from "./src/adapter/node"; -import { EntityManager, LibsqlConnection } from "data"; +import type { Connection } from "./src/data/connection/Connection"; import { __bknd } from "modules/ModuleManager"; +import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; +import { libsql } from "./src/data/connection/sqlite/LibsqlConnection"; import { $console } from "core"; -//import { DatabaseSync } from "node:sqlite"; -//import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; registries.media.register("local", StorageLocalAdapter); const example = import.meta.env.VITE_EXAMPLE; +const dbUrl = example ? `file:.configs/${example}.db` : import.meta.env.VITE_DB_URL; -const credentials = example - ? { - url: `file:.configs/${example}.db`, - } - : import.meta.env.VITE_DB_URL - ? { - url: import.meta.env.VITE_DB_URL!, - authToken: import.meta.env.VITE_DB_TOKEN!, - } - : { - url: ":memory:", - }; +let connection: Connection; +if (dbUrl) { + connection = nodeSqlite({ url: dbUrl }); + $console.debug("Using node-sqlite connection", dbUrl); +} else if (import.meta.env.VITE_DB_LIBSQL_URL) { + connection = libsql({ + url: import.meta.env.VITE_DB_LIBSQL_URL!, + authToken: import.meta.env.VITE_DB_LIBSQL_TOKEN!, + }); + $console.debug("Using libsql connection", import.meta.env.VITE_DB_URL); +} else { + connection = nodeSqlite(); + $console.debug("No connection provided, using in-memory database"); +} -if (example) { +/* if (example) { const { version, ...config } = JSON.parse(await readFile(`.configs/${example}.json`, "utf-8")); // create db with config @@ -50,7 +53,7 @@ if (example) { json: config, }); } -} +} */ let app: App; const recreate = import.meta.env.VITE_APP_FRESH === "1"; @@ -60,8 +63,7 @@ export default { async fetch(request: Request) { if (!app || recreate) { app = App.create({ - connection: credentials, - //connection: nodeSqlite({ database: new DatabaseSync(":memory:") }), + connection, }); app.emgr.onEvent( App.Events.AppBuiltEvent, @@ -78,15 +80,11 @@ export default { // log routes if (firstStart) { firstStart = false; - // biome-ignore lint/suspicious/noConsoleLog: - console.log("[DB]", credentials); if (import.meta.env.VITE_SHOW_ROUTES === "1") { - // biome-ignore lint/suspicious/noConsoleLog: - console.log("\n[APP ROUTES]"); + console.info("\n[APP ROUTES]"); showRoutes(app.server); - // biome-ignore lint/suspicious/noConsoleLog: - console.log("-------\n"); + console.info("-------\n"); } } } diff --git a/bun.lock b/bun.lock index 7a0bb689..56fe68e5 100644 --- a/bun.lock +++ b/bun.lock @@ -34,7 +34,7 @@ "@codemirror/lang-json": "^6.0.1", "@hello-pangea/dnd": "^18.0.1", "@hono/swagger-ui": "^0.5.1", - "@libsql/client": "^0.15.2", + "@libsql/client": "^0.15.9", "@mantine/core": "^7.17.1", "@mantine/hooks": "^7.17.1", "@sinclair/typebox": "0.34.30", @@ -88,6 +88,7 @@ "jsonv-ts": "^0.1.0", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", + "libsql-stateless-easy": "^1.8.0", "open": "^10.1.0", "openapi-types": "^12.1.3", "picocolors": "^1.1.1", @@ -755,13 +756,13 @@ "@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="], - "@libsql/client": ["@libsql/client@0.15.2", "", { "dependencies": { "@libsql/core": "^0.15.2", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.5.4", "promise-limit": "^2.7.0" } }, "sha512-D0No4jqDj5I+buvEyFajBugohzJXCBt9aRHCEXGrJS/9obnAO2z18Os3xgyPsWX0Yw4NQfSYaayRdowqkssmXA=="], + "@libsql/client": ["@libsql/client@0.15.9", "", { "dependencies": { "@libsql/core": "^0.15.9", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.5.13", "promise-limit": "^2.7.0" } }, "sha512-VT3do0a0vwYVaNcp/y05ikkKS3OrFR5UeEf5SUuYZVgKVl1Nc1k9ajoYSsOid8AD/vlhLDB5yFQaV4HmT/OB9w=="], - "@libsql/core": ["@libsql/core@0.15.2", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-+UIN0OlzWa54MqnHbtaJ3FEJj6k2VrwrjX1sSSxzYlM+dWuadjMwOVp7gHpSYJGKWw0RQWLGge4fbW4TCvIm3A=="], + "@libsql/core": ["@libsql/core@0.15.9", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw=="], - "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4PnRdklaQg27vAZxtQgKl+xBHimCH2KRgKId+h63gkAtz5yFTMmX+Q4Ez804T1BgrZuB5ujIvueEEuust2ceSQ=="], + "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ASz/EAMLDLx3oq9PVvZ4zBXXHbz2TxtxUwX2xpTRFR4V4uSHAN07+jpLu3aK5HUBLuv58z7+GjaL5w/cyjR28Q=="], - "@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-r+Z3UXQWxluXKA5cPj5KciNsmSXVTnq9/tmDczngJrogyXwdbbSShYkzov5M+YBlUCKv2VCbNnfxxoIqQnV9Gg=="], + "@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-kzglniv1difkq8opusSXM7u9H0WoEPeKxw0ixIfcGfvlCVMJ+t9UNtXmyNHW68ljdllje6a4C6c94iPmIYafYA=="], "@libsql/hrana-client": ["@libsql/hrana-client@0.7.0", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.3.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw=="], @@ -771,15 +772,19 @@ "@libsql/kysely-libsql": ["@libsql/kysely-libsql@0.4.1", "", { "dependencies": { "@libsql/client": "^0.8.0" }, "peerDependencies": { "kysely": "*" } }, "sha512-mCTa6OWgoME8LNu22COM6XjKBmcMAvNtIO6DYM10jSAFq779fVlrTKQEmXIB8TwJVU65dA5jGCpT8gkDdWS0HQ=="], - "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-QmGXa3TGM6URe7vCOqdvr4Koay+4h5D6y4gdhnPCvXNYrRHgpq5OwEafP9GFalbO32Y1ppLY4enO2LwY0k63Qw=="], + "@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.13", "", { "os": "linux", "cpu": "arm" }, "sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA=="], - "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-cx4/7/xUjgNbiRsghRHujSvIqaTNFQC7Oo1gkGXGsh8hBwkdXr1QdOpeitq745sl6OlbInRrW2C7B2juxX3hcQ=="], + "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.13", "", { "os": "linux", "cpu": "arm" }, "sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA=="], - "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-oPrE9Zyqd7fElS9uCGW2jn55cautD+gDIflfyF5+W/QYzll5OJ2vyMBZOBgdNopuZHrmHYihbespJn3t0WJDJg=="], + "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-/wCxVdrwl1ee6D6LEjwl+w4SxuLm5UL9Kb1LD5n0bBGs0q+49ChdPPh7tp175iRgkcrTgl23emymvt1yj3KxVQ=="], - "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-XzyVdVe43MexkAaHzUvsi4tpPhfSDn3UndIYFrIu0lYkkiz4oKjTK7Iq96j2bcOeJv0pBGxiv+8Z9I6yp/aI2A=="], + "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-xnVAbZIanUgX57XqeI5sNaDnVilp0Di5syCLSEo+bRyBobe/1IAeehNZpyVbCy91U2N6rH1C/mZU7jicVI9x+A=="], - "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xWQyAQEsX+odBrMSXTpm3WOFeoJIX7QncCkaZcsaqdEFueOdNDIdcKAQKMoNlwtj1rCxE72RK4byw/Bflf6Jgg=="], + "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.13", "", { "os": "linux", "cpu": "x64" }, "sha512-/mfMRxcQAI9f8t7tU3QZyh25lXgXKzgin9B9TOSnchD73PWtsVhlyfA6qOCfjQl5kr4sHscdXD5Yb3KIoUgrpQ=="], + + "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.13", "", { "os": "linux", "cpu": "x64" }, "sha512-rdefPTpQCVwUjIQYbDLMv3qpd5MdrT0IeD0UZPGqhT9AWU8nJSQoj2lfyIDAWEz7PPOVCY4jHuEn7FS2sw9kRA=="], + + "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.13", "", { "os": "win32", "cpu": "x64" }, "sha512-aNcmDrD1Ws+dNZIv9ECbxBQumqB9MlSVEykwfXJpqv/593nABb8Ttg5nAGUPtnADyaGDTrGvPPP81d/KsKho4Q=="], "@mantine/core": ["@mantine/core@7.17.1", "", { "dependencies": { "@floating-ui/react": "^0.26.28", "clsx": "^2.1.1", "react-number-format": "^5.4.3", "react-remove-scroll": "^2.6.2", "react-textarea-autosize": "8.5.6", "type-fest": "^4.27.0" }, "peerDependencies": { "@mantine/hooks": "7.17.1", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "sha512-V8O3Ftq4la4I4wNDkTfH4Slkt/pCEU32pTE/DkO46zua0VFxfOAJeLjaol0s11//T+bXx82DtjMsd9APWPuFhA=="], @@ -1235,7 +1240,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], - "@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], + "@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -2571,7 +2576,11 @@ "levn": ["levn@0.3.0", "", { "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA=="], - "libsql": ["libsql@0.5.4", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.4", "@libsql/darwin-x64": "0.5.4", "@libsql/linux-arm64-gnu": "0.5.4", "@libsql/linux-arm64-musl": "0.5.4", "@libsql/linux-x64-gnu": "0.5.4", "@libsql/linux-x64-musl": "0.5.4", "@libsql/win32-x64-msvc": "0.5.4" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-GEFeWca4SDAQFxjHWJBE6GK52LEtSskiujbG3rqmmeTO9t4sfSBKIURNLLpKDDF7fb7jmTuuRkDAn9BZGITQNw=="], + "libsql": ["libsql@0.5.13", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.13", "@libsql/darwin-x64": "0.5.13", "@libsql/linux-arm-gnueabihf": "0.5.13", "@libsql/linux-arm-musleabihf": "0.5.13", "@libsql/linux-arm64-gnu": "0.5.13", "@libsql/linux-arm64-musl": "0.5.13", "@libsql/linux-x64-gnu": "0.5.13", "@libsql/linux-x64-musl": "0.5.13", "@libsql/win32-x64-msvc": "0.5.13" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg=="], + + "libsql-stateless": ["libsql-stateless@2.9.1", "", {}, "sha512-vxq2eyQoKYjGl2J6vYpZ4+rfX9zLckUswbchbgcaeiV/d6Do7T77NHWkiS57KywJRJdbxxoZUS404Nx9AfUDzQ=="], + + "libsql-stateless-easy": ["libsql-stateless-easy@1.8.0", "", { "dependencies": { "libsql-stateless": "2.9.1" } }, "sha512-0Hm2bW0Ntx3X8BAnX+qUUNMRr/GfmJj7HfVkhBDkCld8flSaIO6bwqDAUX6DGLH32KOANmk1dM18VSGjx2BLyQ=="], "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], @@ -3867,6 +3876,8 @@ "@babel/runtime/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "@bknd/postgres/@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], + "@bundled-es-modules/cookie/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], @@ -4081,7 +4092,7 @@ "@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "@types/bun/bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], + "@types/bun/bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="], "@types/jest/jest-diff": ["jest-diff@25.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" } }, "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A=="], @@ -4729,6 +4740,8 @@ "@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="], + "@bknd/postgres/@types/bun/bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], + "@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], From f8d2a9090ee3250e0de9c012f8d7a40affbcc5a7 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 14:38:30 +0200 Subject: [PATCH 08/39] fixing tests, move node tests to vitest --- app/__test__/adapter/adapter.test.ts | 25 +++- app/__test__/app/App.spec.ts | 2 +- app/__test__/app/repro.spec.ts | 3 +- .../integration/config.integration.test.ts | 2 +- app/__test__/media/MediaController.spec.ts | 3 +- app/__test__/modules/AppAuth.spec.ts | 2 +- app/__test__/modules/AppMedia.spec.ts | 3 +- app/package.json | 7 +- app/src/adapter/bun/bun.adapter.ts | 16 +-- .../cloudflare-workers.adapter.spec.ts | 40 ++++--- app/src/adapter/cloudflare/config.ts | 87 ++++++++------ .../storage/StorageR2Adapter.native-spec.ts | 32 ----- .../storage/StorageR2Adapter.vitest.ts | 32 +++++ app/src/adapter/cloudflare/vitest.config.ts | 14 +++ app/src/adapter/index.ts | 18 ++- app/src/adapter/node/node.adapter.spec.ts | 6 +- app/src/adapter/node/node.adapter.ts | 18 +-- ...-spec.ts => StorageLocalAdapter.vitest.ts} | 6 +- app/src/adapter/sqlite/bun.ts | 2 - app/src/adapter/sqlite/edge.ts | 2 - app/src/adapter/sqlite/node.ts | 2 - app/src/core/test/utils.ts | 12 ++ .../sqlite/GenericSqliteConnection.ts | 12 ++ .../sqlite/LibsqlConnection.spec.ts | 3 +- app/src/modules/ModuleManager.ts | 25 ++-- app/vitest.config.ts | 28 +++-- bun.lock | 113 +++++++++++++++++- 27 files changed, 341 insertions(+), 174 deletions(-) delete mode 100644 app/src/adapter/cloudflare/storage/StorageR2Adapter.native-spec.ts create mode 100644 app/src/adapter/cloudflare/storage/StorageR2Adapter.vitest.ts create mode 100644 app/src/adapter/cloudflare/vitest.config.ts rename app/src/adapter/node/storage/{StorageLocalAdapter.native-spec.ts => StorageLocalAdapter.vitest.ts} (79%) create mode 100644 app/src/core/test/utils.ts diff --git a/app/__test__/adapter/adapter.test.ts b/app/__test__/adapter/adapter.test.ts index 734fb084..f7d623c9 100644 --- a/app/__test__/adapter/adapter.test.ts +++ b/app/__test__/adapter/adapter.test.ts @@ -3,21 +3,36 @@ import * as adapter from "adapter"; import { disableConsoleLog, enableConsoleLog } from "core/utils"; import { adapterTestSuite } from "adapter/adapter-test-suite"; import { bunTestRunner } from "adapter/bun/test"; +import { omitKeys } from "core/utils"; beforeAll(disableConsoleLog); afterAll(enableConsoleLog); describe("adapter", () => { it("makes config", () => { - expect(adapter.makeConfig({})).toEqual({}); - expect(adapter.makeConfig({}, { env: { TEST: "test" } })).toEqual({}); + expect(omitKeys(adapter.makeConfig({}), ["connection"])).toEqual({}); + expect(omitKeys(adapter.makeConfig({}, { env: { TEST: "test" } }), ["connection"])).toEqual( + {}, + ); // merges everything returned from `app` with the config - expect(adapter.makeConfig({ app: (a) => a as any }, { env: { TEST: "test" } })).toEqual({ - env: { TEST: "test" }, - } as any); + expect( + omitKeys( + adapter.makeConfig( + { app: (a) => ({ initialConfig: { server: { cors: { origin: a.env.TEST } } } }) }, + { env: { TEST: "test" } }, + ), + ["connection"], + ), + ).toEqual({ + initialConfig: { server: { cors: { origin: "test" } } }, + }); }); + /* it.only("...", async () => { + const app = await adapter.createAdapterApp(); + }); */ + it("reuses apps correctly", async () => { const id = crypto.randomUUID(); diff --git a/app/__test__/app/App.spec.ts b/app/__test__/app/App.spec.ts index 860258ad..a6eb3de7 100644 --- a/app/__test__/app/App.spec.ts +++ b/app/__test__/app/App.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, mock, test } from "bun:test"; import type { ModuleBuildContext } from "../../src"; -import { App, createApp } from "../../src/App"; +import { App, createApp } from "core/test/utils"; import * as proto from "../../src/data/prototype"; describe("App", () => { diff --git a/app/__test__/app/repro.spec.ts b/app/__test__/app/repro.spec.ts index b27aa510..be2da387 100644 --- a/app/__test__/app/repro.spec.ts +++ b/app/__test__/app/repro.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { createApp, registries } from "../../src"; +import { registries } from "../../src"; +import { createApp } from "core/test/utils"; import * as proto from "../../src/data/prototype"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; diff --git a/app/__test__/integration/config.integration.test.ts b/app/__test__/integration/config.integration.test.ts index f370e69e..7fde411d 100644 --- a/app/__test__/integration/config.integration.test.ts +++ b/app/__test__/integration/config.integration.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { createApp } from "../../src"; +import { createApp } from "core/test/utils"; import { Api } from "../../src/Api"; describe("integration config", () => { diff --git a/app/__test__/media/MediaController.spec.ts b/app/__test__/media/MediaController.spec.ts index d0e56c9e..64780724 100644 --- a/app/__test__/media/MediaController.spec.ts +++ b/app/__test__/media/MediaController.spec.ts @@ -1,7 +1,8 @@ /// import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { createApp, registries } from "../../src"; +import { registries } from "../../src"; +import { createApp } from "core/test/utils"; import { mergeObject, randomString } from "../../src/core/utils"; import type { TAppMediaConfig } from "../../src/media/media-schema"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; diff --git a/app/__test__/modules/AppAuth.spec.ts b/app/__test__/modules/AppAuth.spec.ts index 50c38f21..ddbaf3ba 100644 --- a/app/__test__/modules/AppAuth.spec.ts +++ b/app/__test__/modules/AppAuth.spec.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test"; -import { createApp } from "../../src"; +import { createApp } from "core/test/utils"; import { AuthController } from "../../src/auth/api/AuthController"; import { em, entity, make, text } from "../../src/data"; import { AppAuth, type ModuleBuildContext } from "../../src/modules"; diff --git a/app/__test__/modules/AppMedia.spec.ts b/app/__test__/modules/AppMedia.spec.ts index 8e6b5b2d..bbcc0c29 100644 --- a/app/__test__/modules/AppMedia.spec.ts +++ b/app/__test__/modules/AppMedia.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { createApp, registries } from "../../src"; +import { registries } from "../../src"; +import { createApp } from "core/test/utils"; import { em, entity, text } from "../../src/data"; import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter"; import { AppMedia } from "../../src/modules"; diff --git a/app/package.json b/app/package.json index 516d1d90..0e93a59a 100644 --- a/app/package.json +++ b/app/package.json @@ -31,11 +31,9 @@ "test": "ALL_TESTS=1 bun test --bail", "test:all": "bun run test && bun run test:node", "test:bun": "ALL_TESTS=1 bun test --bail", - "test:node": "tsx --test $(find . -type f -name '*.native-spec.ts')", + "test:node": "vitest run", "test:adapters": "bun test src/adapter/**/*.adapter.spec.ts --bail", "test:coverage": "ALL_TESTS=1 bun test --bail --coverage", - "test:vitest": "vitest run", - "test:vitest:watch": "vitest", "test:vitest:coverage": "vitest run --coverage", "test:e2e": "playwright test", "test:e2e:adapters": "bun run e2e/adapters.ts", @@ -73,14 +71,15 @@ "uuid": "^11.1.0" }, "devDependencies": { - "@libsql/client": "^0.15.9", "@aws-sdk/client-s3": "^3.758.0", "@bluwy/giget-core": "^0.1.2", + "@cloudflare/vitest-pool-workers": "^0.8.38", "@cloudflare/workers-types": "^4.20250606.0", "@dagrejs/dagre": "^1.1.4", "@hono/typebox-validator": "^0.3.3", "@hono/vite-dev-server": "^0.19.1", "@hookform/resolvers": "^4.1.3", + "@libsql/client": "^0.15.9", "@libsql/kysely-libsql": "^0.4.1", "@mantine/modals": "^7.17.1", "@mantine/notifications": "^7.17.1", diff --git a/app/src/adapter/bun/bun.adapter.ts b/app/src/adapter/bun/bun.adapter.ts index 07b778ae..5d7c1482 100644 --- a/app/src/adapter/bun/bun.adapter.ts +++ b/app/src/adapter/bun/bun.adapter.ts @@ -1,17 +1,11 @@ /// import path from "node:path"; -import { - type RuntimeBkndConfig, - createRuntimeApp, - type RuntimeOptions, - Connection, -} from "bknd/adapter"; +import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; import { registerLocalMediaAdapter } from "."; import { config } from "bknd/core"; import type { ServeOptions } from "bun"; import { serveStatic } from "hono/bun"; -import { sqlite } from "bknd/adapter/sqlite"; import type { App } from "App"; type BunEnv = Bun.Env; @@ -25,17 +19,9 @@ export async function createApp( const root = path.resolve(distPath ?? "./node_modules/bknd/dist", "static"); registerLocalMediaAdapter(); - let connection: Connection | undefined; - if (Connection.isConnection(config.connection)) { - connection = config.connection; - } else { - connection = sqlite(config.connection ?? { url: ":memory:" }); - } - return await createRuntimeApp( { ...config, - connection, serveStatic: serveStatic({ root }), }, args ?? (process.env as Env), diff --git a/app/src/adapter/cloudflare/cloudflare-workers.adapter.spec.ts b/app/src/adapter/cloudflare/cloudflare-workers.adapter.spec.ts index 0c51acb8..5cdde1aa 100644 --- a/app/src/adapter/cloudflare/cloudflare-workers.adapter.spec.ts +++ b/app/src/adapter/cloudflare/cloudflare-workers.adapter.spec.ts @@ -13,30 +13,32 @@ describe("cf adapter", () => { const DB_URL = ":memory:"; const $ctx = (env?: any, request?: Request, ctx?: ExecutionContext) => ({ request: request ?? (null as any), - env: env ?? { DB_URL }, + env: env ?? { url: DB_URL }, ctx: ctx ?? (null as any), }); it("makes config", async () => { - expect( - makeConfig( - { - connection: { url: DB_URL }, - }, - $ctx({ DB_URL }), - ), - ).toEqual({ connection: { url: DB_URL } }); + const staticConfig = makeConfig( + { + connection: { url: DB_URL }, + initialConfig: { data: { basepath: DB_URL } }, + }, + $ctx({ DB_URL }), + ); + expect(staticConfig.initialConfig).toEqual({ data: { basepath: DB_URL } }); + expect(staticConfig.connection).toBeDefined(); - expect( - makeConfig( - { - app: (env) => ({ - connection: { url: env.DB_URL }, - }), - }, - $ctx({ DB_URL }), - ), - ).toEqual({ connection: { url: DB_URL } }); + const dynamicConfig = makeConfig( + { + app: (env) => ({ + initialConfig: { data: { basepath: env.DB_URL } }, + connection: { url: env.DB_URL }, + }), + }, + $ctx({ DB_URL }), + ); + expect(dynamicConfig.initialConfig).toEqual({ data: { basepath: DB_URL } }); + expect(dynamicConfig.connection).toBeDefined(); }); adapterTestSuite>(bunTestRunner, { diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index d6e75b1c..6ae89d78 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -9,6 +9,7 @@ import { makeConfig as makeAdapterConfig } from "bknd/adapter"; import type { Context, ExecutionContext } from "hono"; import { $console } from "core"; import { setCookie } from "hono/cookie"; +import { sqlite } from "bknd/adapter/sqlite"; export const constants = { exec_async_event_id: "cf_register_waituntil", @@ -98,54 +99,70 @@ export function makeConfig( const appConfig = makeAdapterConfig(config, args?.env); - if (args?.env) { - const bindings = config.bindings?.(args?.env); + // if connection instance is given, don't do anything + // other than checking if D1 session is defined + if (D1Connection.isConnection(appConfig.connection)) { + if (config.d1?.session) { + // we cannot guarantee that db was opened with session + throw new Error( + "D1 session don't work when D1 is directly given as connection. Define it in bindings instead.", + ); + } + // if connection is given, try to open with unified sqlite adapter + } else if (appConfig.connection) { + appConfig.connection = sqlite(appConfig.connection); + // if connection is not given, but env is set + // try to make D1 from bindings + } else if (args?.env) { + const bindings = config.bindings?.(args?.env); const sessionHelper = d1SessionHelper(config); const sessionId = sessionHelper.get(args.request); let session: D1DatabaseSession | undefined; + let db: D1Database | undefined; - if (!appConfig.connection) { - let db: D1Database | undefined; - if (bindings?.db) { - $console.log("Using database from bindings"); - db = bindings.db; - } else if (Object.keys(args).length > 0) { - const binding = getBinding(args.env, "D1Database"); - if (binding) { - $console.log(`Using database from env "${binding.key}"`); - db = binding.value; - } - } + // if db is given in bindings, use it + if (bindings?.db) { + $console.log("Using database from bindings"); + db = bindings.db; - if (db) { - if (config.d1?.session) { - session = db.withSession(sessionId ?? config.d1?.first); - appConfig.connection = new D1Connection({ binding: session }); - } else { - appConfig.connection = new D1Connection({ binding: db }); - } - } else { - throw new Error("No database connection given"); + // scan for D1Database in args + } else { + const binding = getBinding(args.env, "D1Database"); + if (binding) { + $console.log(`Using database from env "${binding.key}"`); + db = binding.value; } } - if (config.d1?.session) { - appConfig.options = { - ...appConfig.options, - manager: { - ...appConfig.options?.manager, - onServerInit: (server) => { - server.use(async (c, next) => { - sessionHelper.set(c, session); - await next(); - }); + // if db is found, check if session is requested + if (db) { + if (config.d1?.session) { + session = db.withSession(sessionId ?? config.d1?.first); + + appConfig.connection = new D1Connection({ binding: session }); + appConfig.options = { + ...appConfig.options, + manager: { + ...appConfig.options?.manager, + onServerInit: (server) => { + server.use(async (c, next) => { + sessionHelper.set(c, session); + await next(); + }); + }, }, - }, - }; + }; + } else { + appConfig.connection = new D1Connection({ binding: db }); + } } } + if (!D1Connection.isConnection(appConfig.connection)) { + throw new Error("Couldn't find database connection"); + } + return appConfig; } diff --git a/app/src/adapter/cloudflare/storage/StorageR2Adapter.native-spec.ts b/app/src/adapter/cloudflare/storage/StorageR2Adapter.native-spec.ts deleted file mode 100644 index ffa98af1..00000000 --- a/app/src/adapter/cloudflare/storage/StorageR2Adapter.native-spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createWriteStream, readFileSync } from "node:fs"; -import { test } from "node:test"; -import { Miniflare } from "miniflare"; -import { StorageR2Adapter } from "./StorageR2Adapter"; -import { adapterTestSuite } from "media"; -import { nodeTestRunner } from "adapter/node/test"; -import path from "node:path"; - -// https://github.com/nodejs/node/issues/44372#issuecomment-1736530480 -console.log = async (message: any) => { - const tty = createWriteStream("/dev/tty"); - const msg = typeof message === "string" ? message : JSON.stringify(message, null, 2); - return tty.write(`${msg}\n`); -}; - -test("StorageR2Adapter", async () => { - const mf = new Miniflare({ - modules: true, - script: "export default { async fetch() { return new Response(null); } }", - r2Buckets: ["BUCKET"], - }); - - const bucket = (await mf.getR2Bucket("BUCKET")) as unknown as R2Bucket; - const adapter = new StorageR2Adapter(bucket); - - const basePath = path.resolve(import.meta.dirname, "../../../../__test__/_assets"); - const buffer = readFileSync(path.join(basePath, "image.png")); - const file = new File([buffer], "image.png", { type: "image/png" }); - - await adapterTestSuite(nodeTestRunner, adapter, file); - await mf.dispose(); -}); diff --git a/app/src/adapter/cloudflare/storage/StorageR2Adapter.vitest.ts b/app/src/adapter/cloudflare/storage/StorageR2Adapter.vitest.ts new file mode 100644 index 00000000..437ec9bc --- /dev/null +++ b/app/src/adapter/cloudflare/storage/StorageR2Adapter.vitest.ts @@ -0,0 +1,32 @@ +import { readFileSync } from "node:fs"; +import { Miniflare } from "miniflare"; +import { StorageR2Adapter } from "./StorageR2Adapter"; +import { adapterTestSuite } from "media/storage/adapters/adapter-test-suite"; +import path from "node:path"; +import { describe, afterAll, test, expect } from "vitest"; +import { viTestRunner } from "adapter/node/vitest"; + +let mf: Miniflare | undefined; +describe("StorageR2Adapter", async () => { + mf = new Miniflare({ + modules: true, + script: "export default { async fetch() { return new Response(null); } }", + r2Buckets: ["BUCKET"], + }); + const bucket = (await mf?.getR2Bucket("BUCKET")) as unknown as R2Bucket; + + test("test", () => { + expect(bucket).toBeDefined(); + }); + const adapter = new StorageR2Adapter(bucket); + + const basePath = path.resolve(import.meta.dirname, "../../../../__test__/_assets"); + const buffer = readFileSync(path.join(basePath, "image.png")); + const file = new File([buffer], "image.png", { type: "image/png" }); + + await adapterTestSuite(viTestRunner, adapter, file); +}); + +afterAll(async () => { + await mf?.dispose(); +}); diff --git a/app/src/adapter/cloudflare/vitest.config.ts b/app/src/adapter/cloudflare/vitest.config.ts new file mode 100644 index 00000000..590ee4f1 --- /dev/null +++ b/app/src/adapter/cloudflare/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + miniflare: { + compatibilityDate: "2025-06-04", + }, + }, + }, + include: ["**/*.vi-test.ts", "**/*.vitest.ts"], + }, +}); diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 569afd6e..abd2f8ec 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -1,7 +1,8 @@ import { App, type CreateAppConfig } from "bknd"; -import { config as $config } from "bknd/core"; +import { config as $config, $console } from "bknd/core"; import type { MiddlewareHandler } from "hono"; import type { AdminControllerOptions } from "modules/server/AdminController"; +import { Connection } from "bknd/data"; export { Connection } from "bknd/data"; @@ -61,7 +62,20 @@ export async function createAdapterApp { adapterTestSuite(bunTestRunner, { - makeApp: node.createApp, - makeHandler: node.createHandler, + makeApp: createApp, + makeHandler: createHandler, }); }); diff --git a/app/src/adapter/node/node.adapter.ts b/app/src/adapter/node/node.adapter.ts index 02aa829b..9d9d5d5a 100644 --- a/app/src/adapter/node/node.adapter.ts +++ b/app/src/adapter/node/node.adapter.ts @@ -1,16 +1,10 @@ import path from "node:path"; import { serve as honoServe } from "@hono/node-server"; import { serveStatic } from "@hono/node-server/serve-static"; -import { registerLocalMediaAdapter } from "adapter/node/index"; -import { - type RuntimeBkndConfig, - createRuntimeApp, - type RuntimeOptions, - Connection, -} from "bknd/adapter"; +import { registerLocalMediaAdapter } from "adapter/node/storage"; +import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; import { config as $config } from "bknd/core"; import { $console } from "core"; -import { sqlite } from "bknd/adapter/sqlite"; import type { App } from "App"; type NodeEnv = NodeJS.ProcessEnv; @@ -35,18 +29,10 @@ export async function createApp( console.warn("relativeDistPath is deprecated, please use distPath instead"); } - let connection: Connection | undefined; - if (Connection.isConnection(config.connection)) { - connection = config.connection; - } else { - connection = sqlite(config.connection ?? { url: ":memory:" }); - } - registerLocalMediaAdapter(); return await createRuntimeApp( { ...config, - connection, serveStatic: serveStatic({ root }), }, // @ts-ignore diff --git a/app/src/adapter/node/storage/StorageLocalAdapter.native-spec.ts b/app/src/adapter/node/storage/StorageLocalAdapter.vitest.ts similarity index 79% rename from app/src/adapter/node/storage/StorageLocalAdapter.native-spec.ts rename to app/src/adapter/node/storage/StorageLocalAdapter.vitest.ts index d237a534..3d13bd5f 100644 --- a/app/src/adapter/node/storage/StorageLocalAdapter.native-spec.ts +++ b/app/src/adapter/node/storage/StorageLocalAdapter.vitest.ts @@ -1,5 +1,5 @@ -import { describe } from "node:test"; -import { nodeTestRunner } from "adapter/node/test"; +import { describe } from "vitest"; +import { viTestRunner } from "adapter/node/vitest"; import { StorageLocalAdapter } from "adapter/node"; import { adapterTestSuite } from "media/storage/adapters/adapter-test-suite"; import { readFileSync } from "node:fs"; @@ -14,5 +14,5 @@ describe("StorageLocalAdapter (node)", async () => { path: path.join(basePath, "tmp"), }); - await adapterTestSuite(nodeTestRunner, adapter, file); + await adapterTestSuite(viTestRunner, adapter, file); }); diff --git a/app/src/adapter/sqlite/bun.ts b/app/src/adapter/sqlite/bun.ts index 328186cb..eaf02c4b 100644 --- a/app/src/adapter/sqlite/bun.ts +++ b/app/src/adapter/sqlite/bun.ts @@ -1,8 +1,6 @@ import type { Connection } from "bknd/data"; -import { $console } from "bknd/core"; import { bunSqlite } from "../bun/connection/BunSqliteConnection"; export function sqlite(config: { url: string }): Connection { - $console.info("Using bun-sqlite", config); return bunSqlite(config); } diff --git a/app/src/adapter/sqlite/edge.ts b/app/src/adapter/sqlite/edge.ts index 996d7f58..97b09ec7 100644 --- a/app/src/adapter/sqlite/edge.ts +++ b/app/src/adapter/sqlite/edge.ts @@ -1,8 +1,6 @@ -import { $console } from "bknd/core"; import type { Connection } from "bknd/data"; import { libsql } from "../../data/connection/sqlite/LibsqlConnection"; export function sqlite(config: { url: string }): Connection { - $console.info("Using libsql", config); return libsql(config); } diff --git a/app/src/adapter/sqlite/node.ts b/app/src/adapter/sqlite/node.ts index 1643cce6..7bc6ebda 100644 --- a/app/src/adapter/sqlite/node.ts +++ b/app/src/adapter/sqlite/node.ts @@ -1,8 +1,6 @@ -import { $console } from "bknd/core"; import type { Connection } from "bknd/data"; import { nodeSqlite } from "../node/connection/NodeSqliteConnection"; export function sqlite(config: { url: string }): Connection { - $console.info("Using node-sqlite", config); return nodeSqlite(config); } diff --git a/app/src/core/test/utils.ts b/app/src/core/test/utils.ts new file mode 100644 index 00000000..d4cefa97 --- /dev/null +++ b/app/src/core/test/utils.ts @@ -0,0 +1,12 @@ +import { createApp as createAppInternal, type CreateAppConfig } from "App"; +import { bunSqlite } from "adapter/bun/connection/BunSqliteConnection"; +import { Connection } from "data/connection/Connection"; + +export { App } from "App"; + +export function createApp({ connection, ...config }: CreateAppConfig = {}) { + return createAppInternal({ + ...config, + connection: Connection.isConnection(connection) ? connection : bunSqlite(connection as any), + }); +} diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts index 37071810..d9abc5f8 100644 --- a/app/src/data/connection/sqlite/GenericSqliteConnection.ts +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -8,12 +8,14 @@ import { GenericSqliteDialect, } from "kysely-generic-sqlite"; import { SqliteConnection } from "./SqliteConnection"; +import type { Features } from "../Connection"; export type GenericSqliteConnectionConfig = { name: string; additionalPlugins?: KyselyPlugin[]; excludeTables?: string[]; onCreateConnection?: OnCreateConnection; + supports?: Partial; }; export { parseBigInt, buildQueryFn, GenericSqliteDialect, type IGenericSqlite }; @@ -33,5 +35,15 @@ export class GenericSqliteConnection extends SqliteConnection excludeTables: config?.excludeTables, }); this.client = db; + if (config?.name) { + this.name = config.name; + } + if (config?.supports) { + for (const [key, value] of Object.entries(config.supports)) { + if (value) { + this.supported[key] = value; + } + } + } } } diff --git a/app/src/data/connection/sqlite/LibsqlConnection.spec.ts b/app/src/data/connection/sqlite/LibsqlConnection.spec.ts index dde5a84b..d6f14d10 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.spec.ts +++ b/app/src/data/connection/sqlite/LibsqlConnection.spec.ts @@ -2,10 +2,11 @@ import { connectionTestSuite } from "../connection-test-suite"; import { LibsqlConnection } from "./LibsqlConnection"; import { bunTestRunner } from "adapter/bun/test"; import { describe } from "bun:test"; +import { createClient } from "@libsql/client"; describe("LibsqlConnection", () => { connectionTestSuite(bunTestRunner, { - makeConnection: () => new LibsqlConnection({ url: ":memory:" }), + makeConnection: () => new LibsqlConnection(createClient({ url: ":memory:" })), rawDialectDetails: ["rowsAffected", "lastInsertRowid"], }); }); diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index d933cebb..3380934b 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -11,16 +11,9 @@ import { stripMark, transformObject, } from "core/utils"; -import { - type Connection, - EntityManager, - type Schema, - datetime, - entity, - enumm, - jsonSchema, - number, -} from "data"; +import type { Connection, Schema } from "data"; +import { EntityManager } from "data/entities/EntityManager"; +import * as proto from "data/prototype"; import { TransformPersistFailedException } from "data/errors"; import { Hono } from "hono"; import type { Kysely } from "kysely"; @@ -116,12 +109,12 @@ const configJsonSchema = Type.Union([ }), ), ]); -export const __bknd = entity(TABLE_NAME, { - version: number().required(), - type: enumm({ enum: ["config", "diff", "backup"] }).required(), - json: jsonSchema({ schema: configJsonSchema }).required(), - created_at: datetime(), - updated_at: datetime(), +export const __bknd = proto.entity(TABLE_NAME, { + version: proto.number().required(), + type: proto.enumm({ enum: ["config", "diff", "backup"] }).required(), + json: proto.jsonSchema({ schema: configJsonSchema }).required(), + created_at: proto.datetime(), + updated_at: proto.datetime(), }); type ConfigTable2 = Schema; interface T_INTERNAL_EM { diff --git a/app/vitest.config.ts b/app/vitest.config.ts index f86248ac..adc92b76 100644 --- a/app/vitest.config.ts +++ b/app/vitest.config.ts @@ -1,18 +1,26 @@ import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import tsconfigPaths from "vite-tsconfig-paths"; - +import path from "node:path"; export default defineConfig({ - plugins: [react(), tsconfigPaths()], + plugins: [tsconfigPaths()], test: { - globals: true, - environment: "jsdom", - setupFiles: ["./__test__/vitest/setup.ts"], + projects: ["**/*.vitest.config.ts"], include: ["**/*.vi-test.ts", "**/*.vitest.ts"], - coverage: { - provider: "v8", - reporter: ["text", "json", "html"], - exclude: ["node_modules/", "**/*.d.ts", "**/*.test.ts", "**/*.config.ts"], - }, }, }); + +// export defineConfig({ +// plugins: [tsconfigPaths()], +// test: { +// globals: true, +// environment: "jsdom", +// setupFiles: ["./__test__/vitest/setup.ts"], +// include: ["**/*.vi-test.ts", "**/*.vitest.ts"], +// coverage: { +// provider: "v8", +// reporter: ["text", "json", "html"], +// exclude: ["node_modules/", "**/*.d.ts", "**/*.test.ts", "**/*.config.ts"], +// }, +// }, +// }); diff --git a/bun.lock b/bun.lock index 56fe68e5..f442cdd6 100644 --- a/bun.lock +++ b/bun.lock @@ -34,7 +34,6 @@ "@codemirror/lang-json": "^6.0.1", "@hello-pangea/dnd": "^18.0.1", "@hono/swagger-ui": "^0.5.1", - "@libsql/client": "^0.15.9", "@mantine/core": "^7.17.1", "@mantine/hooks": "^7.17.1", "@sinclair/typebox": "0.34.30", @@ -60,11 +59,13 @@ "devDependencies": { "@aws-sdk/client-s3": "^3.758.0", "@bluwy/giget-core": "^0.1.2", + "@cloudflare/vitest-pool-workers": "^0.8.38", "@cloudflare/workers-types": "^4.20250606.0", "@dagrejs/dagre": "^1.1.4", "@hono/typebox-validator": "^0.3.3", "@hono/vite-dev-server": "^0.19.1", "@hookform/resolvers": "^4.1.3", + "@libsql/client": "^0.15.9", "@libsql/kysely-libsql": "^0.4.1", "@mantine/modals": "^7.17.1", "@mantine/notifications": "^7.17.1", @@ -520,6 +521,10 @@ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg=="], + + "@cloudflare/vitest-pool-workers": ["@cloudflare/vitest-pool-workers@0.8.38", "", { "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^4.3.0", "miniflare": "4.20250604.1", "semver": "^7.7.1", "wrangler": "4.20.0", "zod": "^3.22.3" }, "peerDependencies": { "@vitest/runner": "2.0.x - 3.2.x", "@vitest/snapshot": "2.0.x - 3.2.x", "vitest": "2.0.x - 3.2.x" } }, "sha512-/eeHW5RbU/eWy5UHOZlYh+7J4DTC5GmMW33Iz7GfKAPBgsNHUqx0/AQotjb4qqB3jW/QvsIQM+jZofmMfdPSZw=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250224.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-sBbaAF2vgQ9+T50ik1ihekdepStBp0w4fvNghBfXIw1iWqfNWnypcjDMmi/7JhXJt2uBxBrSlXCvE5H7Gz+kbw=="], "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250224.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-naetGefgjAaDbEacpwaVruJXNwxmRRL7v3ppStgEiqAlPmTpQ/Edjn2SQ284QwOw3MvaVPHrWcaTBupUpkqCyg=="], @@ -1592,6 +1597,8 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "birpc": ["birpc@0.2.14", "", {}, "sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA=="], + "bknd": ["bknd@workspace:app"], "bknd-cli": ["bknd-cli@workspace:packages/cli"], @@ -1682,6 +1689,8 @@ "ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + "class-utils": ["class-utils@0.3.6", "", { "dependencies": { "arr-union": "^3.1.0", "define-property": "^0.2.5", "isobject": "^3.0.0", "static-extend": "^0.1.1" } }, "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg=="], "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="], @@ -1886,6 +1895,8 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "devalue": ["devalue@4.3.3", "", {}, "sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg=="], + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], @@ -2052,6 +2063,8 @@ "express-rate-limit": ["express-rate-limit@5.5.1", "", {}, "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg=="], + "exsolve": ["exsolve@1.0.5", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="], @@ -3882,6 +3895,12 @@ "@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], + "@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], + + "@cloudflare/vitest-pool-workers/miniflare": ["miniflare@4.20250604.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250604.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-HJQ9YhH0F0fI73Vsdy3PNVau+PfHZYK7trI5WJEcbfl5HzqhMU0DRNtA/G5EXQgiumkjrmbW4Zh1DVTtsqICPg=="], + + "@cloudflare/vitest-pool-workers/wrangler": ["wrangler@4.20.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250604.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250604.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250604.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-gxMLaSnYp3VLdGPZu4fc/9UlB7PnSVwni25v32NM9szG2yTt+gx5RunWzmoLplplIfEMkBuV3wA47vccNu7zcA=="], + "@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -4744,6 +4763,22 @@ "@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], + "@cloudflare/unenv-preset/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "@cloudflare/unenv-preset/unenv/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "@cloudflare/vitest-pool-workers/miniflare/workerd": ["workerd@1.20250604.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250604.0", "@cloudflare/workerd-darwin-arm64": "1.20250604.0", "@cloudflare/workerd-linux-64": "1.20250604.0", "@cloudflare/workerd-linux-arm64": "1.20250604.0", "@cloudflare/workerd-windows-64": "1.20250604.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-sHz9R1sxPpnyq3ptrI/5I96sYTMA2+Ljm75oJDbmEcZQwNyezpu9Emerzt3kzzjCJQqtdscGOidWv4RKGZXzAA=="], + + "@cloudflare/vitest-pool-workers/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + + "@cloudflare/vitest-pool-workers/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + + "@cloudflare/vitest-pool-workers/wrangler/unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd": ["workerd@1.20250604.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250604.0", "@cloudflare/workerd-darwin-arm64": "1.20250604.0", "@cloudflare/workerd-linux-64": "1.20250604.0", "@cloudflare/workerd-linux-arm64": "1.20250604.0", "@cloudflare/workerd-windows-64": "1.20250604.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-sHz9R1sxPpnyq3ptrI/5I96sYTMA2+Ljm75oJDbmEcZQwNyezpu9Emerzt3kzzjCJQqtdscGOidWv4RKGZXzAA=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], @@ -5290,6 +5325,82 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250604.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PI6AWAzhHg75KVhYkSWFBf3HKCHstpaKg4nrx6LYZaEvz0TaTz+JQpYU2fNAgGFmVsK5xEzwFTGh3DAVAKONPw=="], + + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250604.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hOiZZSop7QRQgGERtTIy9eU5GvPpIsgE2/BDsUdHMl7OBZ7QLniqvgDzLNDzj0aTkCldm9Yl/Z+C7aUgRdOccw=="], + + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250604.0", "", { "os": "linux", "cpu": "x64" }, "sha512-S0R9r7U4nv9qejYygQj01hArC4KUbQQ4u29rvegR0MGoXZY8AHIEuJxon0kE7r7aWFJxvl4W3tOH+5hwW51LYw=="], + + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250604.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BTFU/rXpNy03wpeueI2P7q1vVjbg2V6mCyyFGqDqMn2gSVYXH1G0zFNolV13PQXa0HgaqM6oYnqtAxluqbA+kQ=="], + + "@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250604.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tW/U9/qDmDZBeoEVcK5skb2uouVAMXMzt7o/uGvaIFLeZsQkOp4NBmvoQQd+nbOc7nVCJIwFoSMokd89AhzCkA=="], + + "@cloudflare/vitest-pool-workers/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/unenv/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250604.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PI6AWAzhHg75KVhYkSWFBf3HKCHstpaKg4nrx6LYZaEvz0TaTz+JQpYU2fNAgGFmVsK5xEzwFTGh3DAVAKONPw=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250604.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hOiZZSop7QRQgGERtTIy9eU5GvPpIsgE2/BDsUdHMl7OBZ7QLniqvgDzLNDzj0aTkCldm9Yl/Z+C7aUgRdOccw=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250604.0", "", { "os": "linux", "cpu": "x64" }, "sha512-S0R9r7U4nv9qejYygQj01hArC4KUbQQ4u29rvegR0MGoXZY8AHIEuJxon0kE7r7aWFJxvl4W3tOH+5hwW51LYw=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250604.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BTFU/rXpNy03wpeueI2P7q1vVjbg2V6mCyyFGqDqMn2gSVYXH1G0zFNolV13PQXa0HgaqM6oYnqtAxluqbA+kQ=="], + + "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250604.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tW/U9/qDmDZBeoEVcK5skb2uouVAMXMzt7o/uGvaIFLeZsQkOp4NBmvoQQd+nbOc7nVCJIwFoSMokd89AhzCkA=="], + "@jest/core/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@jest/reporters/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], From 85d8542a2ee369642949c3c5467e2d56847faf4e Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 14:44:55 +0200 Subject: [PATCH 09/39] test with node setup pre step --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bf85f1b..13f989af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + - name: Setup Bun uses: oven-sh/setup-bun@v1 with: From 03c4c41d2d75e4b29e683315765ad54d5e1d9130 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 15:58:06 +0200 Subject: [PATCH 10/39] cli now uses adapter logic, require node v22 --- app/package.json | 3 + app/src/cli/commands/run/platform.ts | 7 +- app/src/cli/commands/run/run.ts | 34 +- app/src/cli/commands/user.ts | 2 +- app/src/cli/utils/sys.ts | 8 - app/src/data/schema/SchemaManager.ts | 2 +- bun.lock | 947 +++++---------------------- package.json | 22 +- 8 files changed, 173 insertions(+), 852 deletions(-) diff --git a/app/package.json b/app/package.json index 0e93a59a..ba48e601 100644 --- a/app/package.json +++ b/app/package.json @@ -13,6 +13,9 @@ "bugs": { "url": "https://github.com/bknd-io/bknd/issues" }, + "engines": { + "node": ">=22" + }, "scripts": { "dev": "BKND_CLI_LOG_LEVEL=debug vite", "build": "NODE_ENV=production bun run build.ts --minify --types", diff --git a/app/src/cli/commands/run/platform.ts b/app/src/cli/commands/run/platform.ts index caccc6c5..c3a41103 100644 --- a/app/src/cli/commands/run/platform.ts +++ b/app/src/cli/commands/run/platform.ts @@ -1,6 +1,5 @@ import path from "node:path"; -import type { Config } from "@libsql/client/node"; -import { $console, config } from "core"; +import { $console } from "core"; import type { MiddlewareHandler } from "hono"; import open from "open"; import { fileExists, getRelativeDistPath } from "../../utils/sys"; @@ -27,10 +26,6 @@ export async function serveStatic(server: Platform): Promise } } -export async function attachServeStatic(app: any, platform: Platform) { - app.module.server.client.get(config.server.assets_path + "*", await serveStatic(platform)); -} - export async function startServer( server: Platform, app: App, diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index c8a14d00..de2bf40a 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -1,6 +1,6 @@ import type { Config } from "@libsql/client/node"; -import { App, type CreateAppConfig } from "App"; -import { StorageLocalAdapter } from "adapter/node"; +import type { App, CreateAppConfig } from "App"; +import { StorageLocalAdapter } from "adapter/node/storage"; import type { CliBkndConfig, CliCommand } from "cli/types"; import { Option } from "commander"; import { colorizeConsole, config } from "core"; @@ -11,19 +11,19 @@ import path from "node:path"; import { PLATFORMS, type Platform, - attachServeStatic, getConfigPath, getConnectionCredentialsFromEnv, + serveStatic, startServer, } from "./platform"; -import { makeConfig } from "adapter"; -import { isBun as $isBun } from "cli/utils/sys"; +import { createRuntimeApp, makeConfig } from "adapter"; +import { isBun } from "core/utils"; const env_files = [".env", ".dev.vars"]; dotenv.config({ path: env_files.map((file) => path.resolve(process.cwd(), file)), }); -const isBun = $isBun(); +const is_bun = isBun(); export const run: CliCommand = (program) => { program @@ -52,7 +52,7 @@ export const run: CliCommand = (program) => { .addOption( new Option("--server ", "server type") .choices(PLATFORMS) - .default(isBun ? "bun" : "node"), + .default(is_bun ? "bun" : "node"), ) .addOption(new Option("--no-open", "don't open browser window on start")) .action(action); @@ -72,23 +72,9 @@ type MakeAppConfig = { }; async function makeApp(config: MakeAppConfig) { - const app = App.create({ connection: config.connection }); - - app.emgr.onEvent( - App.Events.AppBuiltEvent, - async () => { - if (config.onBuilt) { - await config.onBuilt(app); - } - - await attachServeStatic(app, config.server?.platform ?? "node"); - app.registerAdminController(); - }, - "sync", - ); - - await app.build(); - return app; + return await createRuntimeApp({ + serveStatic: await serveStatic(config.server?.platform ?? "node"), + }); } export async function makeConfigApp(_config: CliBkndConfig, platform?: Platform) { diff --git a/app/src/cli/commands/user.ts b/app/src/cli/commands/user.ts index 6c66acc4..0a85d9b2 100644 --- a/app/src/cli/commands/user.ts +++ b/app/src/cli/commands/user.ts @@ -11,7 +11,7 @@ import type { CliCommand } from "cli/types"; import { Argument } from "commander"; import { $console } from "core"; import c from "picocolors"; -import { isBun } from "cli/utils/sys"; +import { isBun } from "core/utils"; export const user: CliCommand = (program) => { program diff --git a/app/src/cli/utils/sys.ts b/app/src/cli/utils/sys.ts index 381937f6..b4536a5c 100644 --- a/app/src/cli/utils/sys.ts +++ b/app/src/cli/utils/sys.ts @@ -4,14 +4,6 @@ import { readFile, writeFile as nodeWriteFile } from "node:fs/promises"; import path from "node:path"; import url from "node:url"; -export function isBun(): boolean { - try { - return typeof Bun !== "undefined"; - } catch (e) { - return false; - } -} - export function getRootPath() { const _path = path.dirname(url.fileURLToPath(import.meta.url)); // because of "src", local needs one more level up diff --git a/app/src/data/schema/SchemaManager.ts b/app/src/data/schema/SchemaManager.ts index b991c67d..ab5fbda6 100644 --- a/app/src/data/schema/SchemaManager.ts +++ b/app/src/data/schema/SchemaManager.ts @@ -333,7 +333,7 @@ export class SchemaManager { if (config.force) { try { - $console.log("[SchemaManager]", sql); + $console.debug("[SchemaManager]", sql); await qb.execute(); } catch (e) { throw new Error(`Failed to execute query: ${sql}: ${(e as any).message}`); diff --git a/bun.lock b/bun.lock index f442cdd6..b9929871 100644 --- a/bun.lock +++ b/bun.lock @@ -5,23 +5,12 @@ "name": "bknd", "devDependencies": { "@biomejs/biome": "1.9.4", - "@clack/prompts": "^0.10.0", "@tsconfig/strictest": "^2.0.5", "@types/lodash-es": "^4.17.12", "bun-types": "^1.1.18", - "dotenv": "^16.4.5", - "esbuild": "^0.23.0", - "esbuild-plugin-tsc": "^0.4.0", "miniflare": "^3.20240806.0", - "mitata": "^0.1.11", - "picocolors": "^1.0.1", - "semver": "^7.6.2", - "sql-formatter": "^15.3.2", - "tsd": "^0.31.1", - "tsup": "^8.1.0", "typescript": "^5.5.3", "verdaccio": "^5.32.1", - "wrangler": "^3.108.1", }, }, "app": { @@ -515,11 +504,7 @@ "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="], - "@clack/core": ["@clack/core@0.4.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA=="], - - "@clack/prompts": ["@clack/prompts@0.10.0", "", { "dependencies": { "@clack/core": "0.4.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-H3rCl6CwW1NdQt9rE3n373t7o5cthPv7yUoxF2ytZvyvlJv89C5RYMJu83Hed8ODgys5vpBU0GKxIRG83jd8NQ=="], - - "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250508.0" }, "optionalPeers": ["workerd"] }, "sha512-MtUgNl+QkQyhQvv5bbWP+BpBC1N0me4CHHuP2H4ktmOMKdB/6kkz/lo+zqiA4mEazb4y+1cwyNjVrQ2DWeE4mg=="], @@ -583,59 +568,55 @@ "@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="], - "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.23.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.23.1", "", { "os": "android", "cpu": "arm" }, "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.23.1", "", { "os": "android", "cpu": "arm64" }, "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.23.1", "", { "os": "android", "cpu": "x64" }, "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.23.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.23.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.23.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.23.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.23.1", "", { "os": "linux", "cpu": "arm" }, "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.23.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.23.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.23.1", "", { "os": "linux", "cpu": "none" }, "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.23.1", "", { "os": "linux", "cpu": "none" }, "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.23.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.23.1", "", { "os": "linux", "cpu": "none" }, "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.23.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.23.1", "", { "os": "linux", "cpu": "x64" }, "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.1", "", { "os": "none", "cpu": "arm64" }, "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.23.1", "", { "os": "none", "cpu": "x64" }, "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.23.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.23.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.23.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.23.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.23.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.23.1", "", { "os": "win32", "cpu": "x64" }, "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], @@ -725,8 +706,6 @@ "@jest/reporters": ["@jest/reporters@25.5.1", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^25.5.0", "@jest/test-result": "^25.5.0", "@jest/transform": "^25.5.1", "@jest/types": "^25.5.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", "graceful-fs": "^4.2.4", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^4.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", "jest-haste-map": "^25.5.1", "jest-resolve": "^25.5.1", "jest-util": "^25.5.0", "jest-worker": "^25.5.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^3.1.0", "terminal-link": "^2.0.0", "v8-to-istanbul": "^4.1.3" }, "optionalDependencies": { "node-notifier": "^6.0.0" } }, "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw=="], - "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], - "@jest/source-map": ["@jest/source-map@25.5.0", "", { "dependencies": { "callsites": "^3.0.0", "graceful-fs": "^4.2.4", "source-map": "^0.6.0" } }, "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ=="], "@jest/test-result": ["@jest/test-result@25.5.0", "", { "dependencies": { "@jest/console": "^25.5.0", "@jest/types": "^25.5.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A=="], @@ -1233,8 +1212,6 @@ "@tsconfig/strictest": ["@tsconfig/strictest@2.0.5", "", {}, "sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg=="], - "@tsd/typescript": ["@tsd/typescript@5.4.5", "", {}, "sha512-saiCxzHRhUrRxQV2JhH580aQUZiKQUXI38FcAcikcfOomAil4G4lxT0RfrrKywoAYP/rqAdYXYmNRLppcd+hQQ=="], - "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -1261,8 +1238,6 @@ "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], - "@types/eslint": ["@types/eslint@7.29.0", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng=="], - "@types/eslint-visitor-keys": ["@types/eslint-visitor-keys@1.0.0", "", {}, "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag=="], "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], @@ -1285,8 +1260,6 @@ "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], - "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], - "@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="], "@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="], @@ -1499,8 +1472,6 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - "arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], - "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], @@ -1663,8 +1634,6 @@ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "camelcase-keys": ["camelcase-keys@6.2.2", "", { "dependencies": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" } }, "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg=="], - "caniuse-lite": ["caniuse-lite@1.0.30001703", "", {}, "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ=="], "capture-exit": ["capture-exit@2.0.0", "", { "dependencies": { "rsvp": "^4.8.4" } }, "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g=="], @@ -1751,8 +1720,6 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - "confusing-browser-globals": ["confusing-browser-globals@1.0.11", "", {}, "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA=="], "consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="], @@ -1845,9 +1812,7 @@ "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], - - "decamelize-keys": ["decamelize-keys@1.1.1", "", { "dependencies": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" } }, "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg=="], + "decamelize": ["decamelize@6.0.0", "", {}, "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA=="], "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], @@ -1889,7 +1854,7 @@ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], - "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], + "detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], @@ -1897,7 +1862,7 @@ "devalue": ["devalue@4.3.3", "", {}, "sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg=="], - "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + "diff-sequences": ["diff-sequences@25.2.6", "", {}, "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg=="], "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], @@ -1981,15 +1946,13 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "esbuild": ["esbuild@0.23.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.23.1", "@esbuild/android-arm": "0.23.1", "@esbuild/android-arm64": "0.23.1", "@esbuild/android-x64": "0.23.1", "@esbuild/darwin-arm64": "0.23.1", "@esbuild/darwin-x64": "0.23.1", "@esbuild/freebsd-arm64": "0.23.1", "@esbuild/freebsd-x64": "0.23.1", "@esbuild/linux-arm": "0.23.1", "@esbuild/linux-arm64": "0.23.1", "@esbuild/linux-ia32": "0.23.1", "@esbuild/linux-loong64": "0.23.1", "@esbuild/linux-mips64el": "0.23.1", "@esbuild/linux-ppc64": "0.23.1", "@esbuild/linux-riscv64": "0.23.1", "@esbuild/linux-s390x": "0.23.1", "@esbuild/linux-x64": "0.23.1", "@esbuild/netbsd-x64": "0.23.1", "@esbuild/openbsd-arm64": "0.23.1", "@esbuild/openbsd-x64": "0.23.1", "@esbuild/sunos-x64": "0.23.1", "@esbuild/win32-arm64": "0.23.1", "@esbuild/win32-ia32": "0.23.1", "@esbuild/win32-x64": "0.23.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg=="], - - "esbuild-plugin-tsc": ["esbuild-plugin-tsc@0.4.0", "", { "dependencies": { "strip-comments": "^2.0.1" }, "peerDependencies": { "typescript": "^4.0.0 || ^5.0.0" } }, "sha512-q9gWIovt1nkwchMLc2zhyksaiHOv3kDK4b0AUol8lkMCRhJ1zavgfb2fad6BKp7FT9rh/OHmEBXVjczLoi/0yw=="], + "esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], "escodegen": ["escodegen@1.14.3", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw=="], @@ -1999,8 +1962,6 @@ "eslint-config-react-app": ["eslint-config-react-app@5.2.1", "", { "dependencies": { "confusing-browser-globals": "^1.0.9" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "2.x", "@typescript-eslint/parser": "2.x", "babel-eslint": "10.x", "eslint": "6.x", "eslint-plugin-flowtype": "3.x || 4.x", "eslint-plugin-import": "2.x", "eslint-plugin-jsx-a11y": "6.x", "eslint-plugin-react": "7.x", "eslint-plugin-react-hooks": "1.x || 2.x" } }, "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ=="], - "eslint-formatter-pretty": ["eslint-formatter-pretty@4.1.0", "", { "dependencies": { "@types/eslint": "^7.2.13", "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", "eslint-rule-docs": "^1.1.5", "log-symbols": "^4.0.0", "plur": "^4.0.0", "string-width": "^4.2.0", "supports-hyperlinks": "^2.0.0" } }, "sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ=="], - "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], "eslint-module-utils": ["eslint-module-utils@2.12.0", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg=="], @@ -2017,8 +1978,6 @@ "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@2.5.1", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g=="], - "eslint-rule-docs": ["eslint-rule-docs@1.1.235", "", {}, "sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A=="], - "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], "eslint-utils": ["eslint-utils@1.4.3", "", { "dependencies": { "eslint-visitor-keys": "^1.1.0" } }, "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q=="], @@ -2197,7 +2156,7 @@ "getpass": ["getpass@0.1.7", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng=="], - "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -2231,8 +2190,6 @@ "har-validator": ["har-validator@5.1.5", "", { "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w=="], - "hard-rejection": ["hard-rejection@2.1.0", "", {}, "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA=="], - "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -2257,7 +2214,7 @@ "hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="], - "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="], @@ -2285,7 +2242,7 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "ignore": ["ignore@4.0.6", "", {}, "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="], "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], @@ -2315,8 +2272,6 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "irregular-plurals": ["irregular-plurals@3.5.0", "", {}, "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ=="], - "is-accessor-descriptor": ["is-accessor-descriptor@1.0.1", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA=="], "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], @@ -2409,8 +2364,6 @@ "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], - "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -2451,7 +2404,7 @@ "jest-config": ["jest-config@25.5.4", "", { "dependencies": { "@babel/core": "^7.1.0", "@jest/test-sequencer": "^25.5.4", "@jest/types": "^25.5.0", "babel-jest": "^25.5.1", "chalk": "^3.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", "jest-environment-jsdom": "^25.5.0", "jest-environment-node": "^25.5.0", "jest-get-type": "^25.2.6", "jest-jasmine2": "^25.5.4", "jest-regex-util": "^25.2.6", "jest-resolve": "^25.5.1", "jest-util": "^25.5.0", "jest-validate": "^25.5.0", "micromatch": "^4.0.2", "pretty-format": "^25.5.0", "realpath-native": "^2.0.0" } }, "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg=="], - "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], + "jest-diff": ["jest-diff@25.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" } }, "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A=="], "jest-docblock": ["jest-docblock@25.3.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg=="], @@ -2461,7 +2414,7 @@ "jest-environment-node": ["jest-environment-node@25.5.0", "", { "dependencies": { "@jest/environment": "^25.5.0", "@jest/fake-timers": "^25.5.0", "@jest/types": "^25.5.0", "jest-mock": "^25.5.0", "jest-util": "^25.5.0", "semver": "^6.3.0" } }, "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA=="], - "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], + "jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], "jest-haste-map": ["jest-haste-map@25.5.1", "", { "dependencies": { "@jest/types": "^25.5.0", "@types/graceful-fs": "^4.1.2", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.4", "jest-serializer": "^25.5.0", "jest-util": "^25.5.0", "jest-worker": "^25.5.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", "which": "^2.0.2" }, "optionalDependencies": { "fsevents": "^2.1.2" } }, "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ=="], @@ -2661,7 +2614,7 @@ "lodash.zip": ["lodash.zip@4.2.0", "", {}, "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg=="], - "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "log-symbols": ["log-symbols@3.0.0", "", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="], "log-update": ["log-update@2.3.0", "", { "dependencies": { "ansi-escapes": "^3.0.0", "cli-cursor": "^2.0.0", "wrap-ansi": "^3.0.1" } }, "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg=="], @@ -2695,8 +2648,6 @@ "map-cache": ["map-cache@0.2.2", "", {}, "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg=="], - "map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="], - "map-visit": ["map-visit@1.0.0", "", { "dependencies": { "object-visit": "^1.0.0" } }, "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w=="], "markdown-to-jsx": ["markdown-to-jsx@7.7.4", "", { "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-1bSfXyBKi+EYS3YY+e0Csuxf8oZ3decdfhOav/Z7Wrk89tjudyL5FOmwZQUoy0/qVXGUl+6Q3s2SWtpDEWITfQ=="], @@ -2705,8 +2656,6 @@ "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], - "meow": ["meow@9.0.0", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize": "^1.2.0", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ=="], - "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], @@ -2733,22 +2682,16 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minimist-options": ["minimist-options@4.1.0", "", { "dependencies": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", "kind-of": "^6.0.3" } }, "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A=="], - "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], - "mitata": ["mitata@0.1.14", "", {}, "sha512-8kRs0l636eT4jj68PFXOR2D5xl4m56T478g16SzUPOYgkzQU+xaw62guAQxzBPm+SXb15GQi1cCpDxJfkr4CSA=="], - "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], "mixin-deep": ["mixin-deep@1.3.2", "", { "dependencies": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" } }, "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA=="], "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="], - "moo": ["moo@0.5.2", "", {}, "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], @@ -2803,7 +2746,7 @@ "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], - "normalize-package-data": ["normalize-package-data@3.0.3", "", { "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" } }, "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA=="], + "normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -2847,7 +2790,7 @@ "obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="], - "ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "on-exit-leak-free": ["on-exit-leak-free@0.2.0", "", {}, "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg=="], @@ -2917,7 +2860,7 @@ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "path-to-regexp": ["path-to-regexp@0.1.10", "", {}, "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="], "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], @@ -2965,8 +2908,6 @@ "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "pkginfo": ["pkginfo@0.4.1", "", {}, "sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ=="], "playwright": ["playwright@1.51.1", "", { "dependencies": { "playwright-core": "1.51.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw=="], @@ -2975,8 +2916,6 @@ "plimit-lit": ["plimit-lit@1.6.1", "", { "dependencies": { "queue-lit": "^1.5.1" } }, "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA=="], - "plur": ["plur@4.0.0", "", { "dependencies": { "irregular-plurals": "^3.2.0" } }, "sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg=="], - "pn": ["pn@1.1.0", "", {}, "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="], "posix-character-classes": ["posix-character-classes@0.1.1", "", {}, "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg=="], @@ -3021,7 +2960,7 @@ "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], - "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + "pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], @@ -3069,8 +3008,6 @@ "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], - "quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], - "radix-ui": ["radix-ui@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-accessible-icon": "1.1.2", "@radix-ui/react-accordion": "1.2.3", "@radix-ui/react-alert-dialog": "1.1.6", "@radix-ui/react-aspect-ratio": "1.1.2", "@radix-ui/react-avatar": "1.1.3", "@radix-ui/react-checkbox": "1.1.4", "@radix-ui/react-collapsible": "1.1.3", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-context-menu": "2.2.6", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-dropdown-menu": "2.1.6", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-form": "0.1.2", "@radix-ui/react-hover-card": "1.1.6", "@radix-ui/react-label": "2.1.2", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-menubar": "1.1.6", "@radix-ui/react-navigation-menu": "1.2.5", "@radix-ui/react-popover": "1.1.6", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-progress": "1.1.2", "@radix-ui/react-radio-group": "1.2.3", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-scroll-area": "1.2.3", "@radix-ui/react-select": "2.1.6", "@radix-ui/react-separator": "1.1.2", "@radix-ui/react-slider": "1.2.3", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-switch": "1.1.3", "@radix-ui/react-tabs": "1.1.3", "@radix-ui/react-toast": "1.2.6", "@radix-ui/react-toggle": "1.1.2", "@radix-ui/react-toggle-group": "1.1.2", "@radix-ui/react-toolbar": "1.1.2", "@radix-ui/react-tooltip": "1.1.8", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-W8L6soM1vQnIXVvVa31AkQhoZBDPwVoNHhT13R3aB9Qq7ARYIUS9DLaCopRBsbTdZm1NEEPx3rnq659CiNOBDw=="], "raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="], @@ -3199,11 +3136,7 @@ "rimraf": ["rimraf@2.4.5", "", { "dependencies": { "glob": "^6.0.1" }, "bin": { "rimraf": "./bin.js" } }, "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ=="], - "rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], - - "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], - - "rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="], + "rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], "rollup-plugin-sourcemaps": ["rollup-plugin-sourcemaps@0.6.3", "", { "dependencies": { "@rollup/pluginutils": "^3.0.9", "source-map-resolve": "^0.6.0" }, "peerDependencies": { "@types/node": ">=10.0.0", "rollup": ">=0.31.2" }, "optionalPeers": ["@types/node"] }, "sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw=="], @@ -3249,7 +3182,7 @@ "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], - "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], "send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], @@ -3417,8 +3350,6 @@ "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - "strip-comments": ["strip-comments@2.0.1", "", {}, "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="], - "strip-eof": ["strip-eof@1.0.0", "", {}, "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q=="], "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], @@ -3527,8 +3458,6 @@ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], - "trim-newlines": ["trim-newlines@3.0.1", "", {}, "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw=="], - "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], @@ -3541,8 +3470,6 @@ "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], - "tsd": ["tsd@0.31.2", "", { "dependencies": { "@tsd/typescript": "~5.4.3", "eslint-formatter-pretty": "^4.1.0", "globby": "^11.0.1", "jest-diff": "^29.0.3", "meow": "^9.0.0", "path-exists": "^4.0.0", "read-pkg-up": "^7.0.0" }, "bin": { "tsd": "dist/cli.js" } }, "sha512-VplBAQwvYrHzVihtzXiUVXu5bGcr7uH1juQZ1lmKgkuGNGT+FechUCqmx9/zk7wibcqR2xaNEwCkDyKh+VVZnQ=="], - "tsdx": ["tsdx@0.14.1", "", { "dependencies": { "@babel/core": "^7.4.4", "@babel/helper-module-imports": "^7.0.0", "@babel/parser": "^7.11.5", "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/preset-env": "^7.11.0", "@babel/traverse": "^7.11.5", "@rollup/plugin-babel": "^5.1.0", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-replace": "^2.2.1", "@types/jest": "^25.2.1", "@typescript-eslint/eslint-plugin": "^2.12.0", "@typescript-eslint/parser": "^2.12.0", "ansi-escapes": "^4.2.1", "asyncro": "^3.0.0", "babel-eslint": "^10.0.3", "babel-plugin-annotate-pure-calls": "^0.4.0", "babel-plugin-dev-expression": "^0.2.1", "babel-plugin-macros": "^2.6.1", "babel-plugin-polyfill-regenerator": "^0.0.4", "babel-plugin-transform-rename-import": "^2.3.0", "camelcase": "^6.0.0", "chalk": "^4.0.0", "enquirer": "^2.3.4", "eslint": "^6.1.0", "eslint-config-prettier": "^6.0.0", "eslint-config-react-app": "^5.2.1", "eslint-plugin-flowtype": "^3.13.0", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.0", "eslint-plugin-react": "^7.14.3", "eslint-plugin-react-hooks": "^2.2.0", "execa": "^4.0.3", "fs-extra": "^9.0.0", "jest": "^25.3.0", "jest-watch-typeahead": "^0.5.0", "jpjs": "^1.2.1", "lodash.merge": "^4.6.2", "ora": "^4.0.3", "pascal-case": "^3.1.1", "prettier": "^1.19.1", "progress-estimator": "^0.2.2", "regenerator-runtime": "^0.13.7", "rollup": "^1.32.1", "rollup-plugin-sourcemaps": "^0.6.2", "rollup-plugin-terser": "^5.1.2", "rollup-plugin-typescript2": "^0.27.3", "sade": "^1.4.2", "semver": "^7.1.1", "shelljs": "^0.8.3", "tiny-glob": "^0.2.6", "ts-jest": "^25.3.1", "tslib": "^1.9.3", "typescript": "^3.7.3" }, "bin": { "tsdx": "dist/index.js" } }, "sha512-keHmFdCL2kx5nYFlBdbE3639HQ2v9iGedAFAajobrUTH2wfX0nLPdDhbHv+GHLQZqf0c5ur1XteE8ek/+Eyj5w=="], "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], @@ -3581,7 +3508,7 @@ "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], - "ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="], + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], @@ -3591,7 +3518,7 @@ "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - "unenv": ["unenv@2.0.0-rc.1", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg=="], + "unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], "unfetch": ["unfetch@4.2.0", "", {}, "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA=="], @@ -3737,7 +3664,7 @@ "wouter": ["wouter@3.6.0", "", { "dependencies": { "mitt": "^3.0.1", "regexparam": "^3.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-l11eR4urCc+CbY8+pV8HKFHxEqMgffss9aVB1XwiSkLDtH3cI6XpCa50cOzREzL0KwQqrwCVE5dCyeNcCgFpPg=="], - "wrangler": ["wrangler@3.112.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250214.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.1", "workerd": "1.20250214.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250214.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-PNQWGze3ODlWwG33LPr8kNhbht3eB3L9fogv+fapk2fjaqj0kNweRapkwmvtz46ojcqWzsxmTe4nOC0hIVUfPA=="], + "wrangler": ["wrangler@4.20.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250604.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250604.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250604.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-gxMLaSnYp3VLdGPZu4fc/9UlB7PnSVwni25v32NM9szG2yTt+gx5RunWzmoLplplIfEMkBuV3wA47vccNu7zcA=="], "wrap-ansi": ["wrap-ansi@3.0.1", "", { "dependencies": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0" } }, "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ=="], @@ -3767,7 +3694,7 @@ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - "yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], @@ -3895,11 +3822,9 @@ "@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], - "@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], - "@cloudflare/vitest-pool-workers/miniflare": ["miniflare@4.20250604.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250604.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-HJQ9YhH0F0fI73Vsdy3PNVau+PfHZYK7trI5WJEcbfl5HzqhMU0DRNtA/G5EXQgiumkjrmbW4Zh1DVTtsqICPg=="], - "@cloudflare/vitest-pool-workers/wrangler": ["wrangler@4.20.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250604.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250604.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250604.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-gxMLaSnYp3VLdGPZu4fc/9UlB7PnSVwni25v32NM9szG2yTt+gx5RunWzmoLplplIfEMkBuV3wA47vccNu7zcA=="], + "@cloudflare/vitest-pool-workers/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], @@ -3933,16 +3858,12 @@ "@jest/reporters/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "@jest/reporters/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "@jest/reporters/istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="], "@jest/reporters/jest-worker": ["jest-worker@25.5.0", "", { "dependencies": { "merge-stream": "^2.0.0", "supports-color": "^7.0.0" } }, "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw=="], "@jest/reporters/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], - "@jest/source-map/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "@jest/transform/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -3963,6 +3884,8 @@ "@plasmicapp/query/swr": ["swr@1.3.0", "", { "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="], + "@puppeteer/browsers/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "@remix-run/node/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], "@remix-run/node/undici": ["undici@6.21.1", "", {}, "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="], @@ -3975,28 +3898,14 @@ "@remix-run/web-fetch/mrmime": ["mrmime@1.0.1", "", {}, "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw=="], - "@rollup/plugin-babel/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - - "@rollup/plugin-commonjs/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "@rollup/plugin-commonjs/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], - "@rollup/plugin-commonjs/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - - "@rollup/plugin-json/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - - "@rollup/plugin-node-resolve/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - "@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], - "@rollup/plugin-replace/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - "@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], "@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "@rollup/pluginutils/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], - "@sagold/json-query/@sagold/json-pointer": ["@sagold/json-pointer@5.1.2", "", {}, "sha512-+wAhJZBXa6MNxRScg6tkqEbChEHMgVZAhTHVJ60Y7sbtXtu9XA49KfUkdWlS2x78D6H9nryiKePiYozumauPfA=="], "@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -4113,15 +4022,11 @@ "@types/bun/bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="], - "@types/jest/jest-diff": ["jest-diff@25.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" } }, "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A=="], - - "@types/jest/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="], "@typescript-eslint/experimental-utils/eslint-utils": ["eslint-utils@2.1.0", "", { "dependencies": { "eslint-visitor-keys": "^1.1.0" } }, "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg=="], - "@typescript-eslint/typescript-estree/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "@verdaccio/auth/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], @@ -4131,8 +4036,6 @@ "@verdaccio/config/minimatch": ["minimatch@7.4.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw=="], - "@verdaccio/core/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], - "@verdaccio/loaders/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "@verdaccio/local-storage-legacy/async": ["async@3.2.4", "", {}, "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="], @@ -4161,14 +4064,14 @@ "@verdaccio/utils/minimatch": ["minimatch@7.4.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw=="], - "@verdaccio/utils/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], - "@vitest/browser/ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], "@vitest/coverage-v8/vitest": ["vitest@3.0.9", "", { "dependencies": { "@vitest/expect": "3.0.9", "@vitest/mocker": "3.0.9", "@vitest/pretty-format": "^3.0.9", "@vitest/runner": "3.0.9", "@vitest/snapshot": "3.0.9", "@vitest/spy": "3.0.9", "@vitest/utils": "3.0.9", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.0.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.0.9", "@vitest/ui": "3.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ=="], "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "@wdio/config/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "@wdio/logger/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], "@wdio/logger/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], @@ -4177,8 +4080,6 @@ "@wdio/types/@types/node": ["@types/node@20.17.24", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA=="], - "@wdio/utils/decamelize": ["decamelize@6.0.0", "", {}, "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA=="], - "@xata.io/kysely/@xata.io/client": ["@xata.io/client@0.30.1", "", { "peerDependencies": { "typescript": ">=4.5" } }, "sha512-dAzDPHmIfenVIpF39m1elmW5ngjWu2mO8ZqJBN7dmYdXr98uhPANfLdVZnc3mUNG+NH37LqY1dSO862hIo2oRw=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -4191,6 +4092,8 @@ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "archiver-utils/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "aria-hidden/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "ast-types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -4225,8 +4128,6 @@ "browserify-zlib/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], - "camelcase-keys/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "cheerio/undici": ["undici@6.21.1", "", {}, "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="], "class-utils/define-property": ["define-property@0.2.5", "", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA=="], @@ -4237,16 +4138,12 @@ "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="], - "degenerator/escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], "domexception/webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="], "duplexify/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "edgedriver/decamelize": ["decamelize@6.0.0", "", {}, "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA=="], - "edgedriver/fast-xml-parser": ["fast-xml-parser@4.5.3", "", { "dependencies": { "strnum": "^1.1.1" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig=="], "edgedriver/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -4269,8 +4166,6 @@ "eslint/globals": ["globals@12.4.0", "", { "dependencies": { "type-fest": "^0.8.1" } }, "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg=="], - "eslint/ignore": ["ignore@4.0.6", "", {}, "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg=="], - "eslint/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], "eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -4317,22 +4212,16 @@ "expand-brackets/extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], - "expect/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - "express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "express/path-to-regexp": ["path-to-regexp@0.1.10", "", {}, "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="], - "external-editor/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "extglob/define-property": ["define-property@1.0.0", "", { "dependencies": { "is-descriptor": "^1.0.0" } }, "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA=="], "extglob/extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], - "figures/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "find-cache-dir/make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], @@ -4345,8 +4234,6 @@ "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "geckodriver/decamelize": ["decamelize@6.0.0", "", {}, "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA=="], - "geckodriver/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "geckodriver/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], @@ -4357,7 +4244,9 @@ "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -4367,8 +4256,6 @@ "has-values/kind-of": ["kind-of@4.0.0", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw=="], - "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "ip-address/jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], @@ -4389,18 +4276,10 @@ "jest-config/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-config/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "jest-config/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "jest-config/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], + "jest-diff/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "jest-each/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-each/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "jest-each/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "jest-environment-jsdom/jsdom": ["jsdom@15.2.1", "", { "dependencies": { "abab": "^2.0.0", "acorn": "^7.1.0", "acorn-globals": "^4.3.2", "array-equal": "^1.0.0", "cssom": "^0.4.1", "cssstyle": "^2.0.0", "data-urls": "^1.1.0", "domexception": "^1.0.1", "escodegen": "^1.11.1", "html-encoding-sniffer": "^1.0.2", "nwsapi": "^2.2.0", "parse5": "5.1.0", "pn": "^1.1.0", "request": "^2.88.0", "request-promise-native": "^1.0.7", "saxes": "^3.1.9", "symbol-tree": "^3.2.2", "tough-cookie": "^3.0.1", "w3c-hr-time": "^1.0.1", "w3c-xmlserializer": "^1.1.2", "webidl-conversions": "^4.0.2", "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", "whatwg-url": "^7.0.0", "ws": "^7.0.0", "xml-name-validator": "^3.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g=="], "jest-environment-node/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -4409,20 +4288,8 @@ "jest-jasmine2/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-jasmine2/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - - "jest-leak-detector/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "jest-leak-detector/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "jest-matcher-utils/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-matcher-utils/jest-diff": ["jest-diff@25.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" } }, "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A=="], - - "jest-matcher-utils/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "jest-matcher-utils/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "jest-message-util/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "jest-resolve/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -4433,22 +4300,14 @@ "jest-runtime/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-runtime/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], "jest-runtime/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], "jest-snapshot/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-snapshot/jest-diff": ["jest-diff@25.5.0", "", { "dependencies": { "chalk": "^3.0.0", "diff-sequences": "^25.2.6", "jest-get-type": "^25.2.6", "pretty-format": "^25.5.0" } }, "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A=="], - - "jest-snapshot/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - "jest-snapshot/make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="], - "jest-snapshot/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "jest-snapshot/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "jest-util/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -4459,10 +4318,6 @@ "jest-validate/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "jest-validate/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "jest-validate/pretty-format": ["pretty-format@25.5.0", "", { "dependencies": { "@jest/types": "^25.5.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" } }, "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ=="], - "jest-watch-typeahead/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "jest-watcher/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -4471,28 +4326,32 @@ "jsdom/ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="], + "jsonwebtoken/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], + "lightningcss/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], "locate-app/type-fest": ["type-fest@4.26.0", "", {}, "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw=="], + "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], + "log-update/ansi-escapes": ["ansi-escapes@3.2.0", "", {}, "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="], "log-update/cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="], "lower-case/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "meow/type-fest": ["type-fest@0.18.1", "", {}, "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw=="], + "make-dir/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="], - "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "msw/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "mv/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "nearley/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], @@ -4509,14 +4368,14 @@ "node-notifier/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + "object-copy/define-property": ["define-property@0.2.5", "", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA=="], "object-copy/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], "ora/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "ora/log-symbols": ["log-symbols@3.0.0", "", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="], - "pascal-case/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -4527,7 +4386,7 @@ "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "progress-estimator/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], @@ -4549,8 +4408,6 @@ "react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "read-pkg/normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], - "read-pkg/type-fest": ["type-fest@0.6.0", "", {}, "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="], "read-pkg-up/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], @@ -4575,11 +4432,7 @@ "rimraf/glob": ["glob@6.0.4", "", { "dependencies": { "inflight": "^1.0.4", "inherits": "2", "minimatch": "2 || 3", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A=="], - "rollup-plugin-inject/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], - - "rollup-plugin-inject/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], - - "rollup-plugin-terser/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], + "rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], "rollup-plugin-typescript2/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -4613,7 +4466,9 @@ "set-value/is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], - "shelljs/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "sharp/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], + + "sharp/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], @@ -4655,6 +4510,8 @@ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "table/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], "table/string-width": ["string-width@3.1.0", "", { "dependencies": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } }, "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w=="], @@ -4663,6 +4520,8 @@ "terser/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "test-exclude/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "tinyglobby/fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="], @@ -4673,21 +4532,15 @@ "ts-jest/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "ts-jest/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - "tsc-alias/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], - "tsdx/rollup": ["rollup@1.32.1", "", { "dependencies": { "@types/estree": "*", "@types/node": "*", "acorn": "^7.1.0" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A=="], + "tsdx/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "tsdx/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], - "tsup/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], - - "tsx/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], - - "unenv/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "tsup/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], "union-value/is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], @@ -4701,8 +4554,6 @@ "v8-to-istanbul/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "verdaccio/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], - "verdaccio-audit/express": ["express@4.21.0", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng=="], "verdaccio-audit/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], @@ -4713,7 +4564,7 @@ "verdaccio-htpasswd/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], - "vite/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], "vite/tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], @@ -4731,11 +4582,13 @@ "which-builtin-type/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], - "wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], + "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], - "wrangler/miniflare": ["miniflare@3.20250214.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250214.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-t+lT4p2lbOcKv4PS3sx1F/wcDAlbEYZCO2VooLp4H7JErWWYIi9yjD3UillC3CGOpiBahVg5nrPCoFltZf6UlA=="], + "wrangler/miniflare": ["miniflare@4.20250604.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250604.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-HJQ9YhH0F0fI73Vsdy3PNVau+PfHZYK7trI5WJEcbfl5HzqhMU0DRNtA/G5EXQgiumkjrmbW4Zh1DVTtsqICPg=="], - "wrangler/workerd": ["workerd@1.20250214.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250214.0", "@cloudflare/workerd-darwin-arm64": "1.20250214.0", "@cloudflare/workerd-linux-64": "1.20250214.0", "@cloudflare/workerd-linux-arm64": "1.20250214.0", "@cloudflare/workerd-windows-64": "1.20250214.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-QWcqXZLiMpV12wiaVnb3nLmfs/g4ZsFQq2mX85z546r3AX4CTIkXl0VP50W3CwqLADej3PGYiRDOTelDOwVG1g=="], + "wrangler/path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "wrangler/workerd": ["workerd@1.20250604.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250604.0", "@cloudflare/workerd-darwin-arm64": "1.20250604.0", "@cloudflare/workerd-linux-64": "1.20250604.0", "@cloudflare/workerd-linux-arm64": "1.20250604.0", "@cloudflare/workerd-windows-64": "1.20250604.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-sHz9R1sxPpnyq3ptrI/5I96sYTMA2+Ljm75oJDbmEcZQwNyezpu9Emerzt3kzzjCJQqtdscGOidWv4RKGZXzAA=="], "wrap-ansi/string-width": ["string-width@2.1.1", "", { "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" } }, "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw=="], @@ -4745,6 +4598,10 @@ "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "yargs-parser/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4763,66 +4620,26 @@ "@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], - "@cloudflare/unenv-preset/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - - "@cloudflare/unenv-preset/unenv/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - "@cloudflare/vitest-pool-workers/miniflare/workerd": ["workerd@1.20250604.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250604.0", "@cloudflare/workerd-darwin-arm64": "1.20250604.0", "@cloudflare/workerd-linux-64": "1.20250604.0", "@cloudflare/workerd-linux-arm64": "1.20250604.0", "@cloudflare/workerd-windows-64": "1.20250604.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-sHz9R1sxPpnyq3ptrI/5I96sYTMA2+Ljm75oJDbmEcZQwNyezpu9Emerzt3kzzjCJQqtdscGOidWv4RKGZXzAA=="], "@cloudflare/vitest-pool-workers/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], - "@cloudflare/vitest-pool-workers/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], - - "@cloudflare/vitest-pool-workers/wrangler/unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd": ["workerd@1.20250604.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250604.0", "@cloudflare/workerd-darwin-arm64": "1.20250604.0", "@cloudflare/workerd-linux-64": "1.20250604.0", "@cloudflare/workerd-linux-arm64": "1.20250604.0", "@cloudflare/workerd-windows-64": "1.20250604.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-sHz9R1sxPpnyq3ptrI/5I96sYTMA2+Ljm75oJDbmEcZQwNyezpu9Emerzt3kzzjCJQqtdscGOidWv4RKGZXzAA=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "@jest/core/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "@jest/reporters/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "@libsql/kysely-libsql/@libsql/client/@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="], "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client": ["@libsql/hrana-client@0.6.2", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.2.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw=="], "@libsql/kysely-libsql/@libsql/client/libsql": ["libsql@0.3.19", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2", "libsql": "^0.3.15" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.3.19", "@libsql/darwin-x64": "0.3.19", "@libsql/linux-arm64-gnu": "0.3.19", "@libsql/linux-arm64-musl": "0.3.19", "@libsql/linux-x64-gnu": "0.3.19", "@libsql/linux-x64-musl": "0.3.19", "@libsql/win32-x64-msvc": "0.3.19" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA=="], - "@rollup/plugin-babel/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "@rollup/plugin-commonjs/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "@rollup/plugin-commonjs/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "@rollup/plugin-json/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "@rollup/plugin-node-resolve/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "@rollup/plugin-replace/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "@rollup/pluginutils/rollup/@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], - - "@rollup/pluginutils/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - "@testing-library/dom/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], - "@types/jest/jest-diff/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - - "@types/jest/jest-diff/diff-sequences": ["diff-sequences@25.2.6", "", {}, "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg=="], - - "@types/jest/jest-diff/jest-get-type": ["jest-get-type@25.2.6", "", {}, "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig=="], - - "@types/jest/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "@types/pg/pg-types/postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="], "@types/pg/pg-types/postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="], @@ -4831,8 +4648,6 @@ "@types/pg/pg-types/postgres-interval": ["postgres-interval@3.0.0", "", {}, "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw=="], - "@typescript-eslint/typescript-estree/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "@verdaccio/local-storage-legacy/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], "@verdaccio/logger/pino/on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], @@ -4853,18 +4668,22 @@ "@verdaccio/middleware/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "@verdaccio/middleware/express/path-to-regexp": ["path-to-regexp@0.1.10", "", {}, "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="], - "@vitest/coverage-v8/vitest/vite": ["vite@6.2.1", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q=="], + "@wdio/config/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "@wdio/logger/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "@wdio/repl/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "@wdio/types/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], + "archiver-utils/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "babel-plugin-istanbul/test-exclude/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], + "bknd-cli/@libsql/client/@libsql/core": ["@libsql/core@0.14.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q=="], "bknd-cli/@libsql/client/libsql": ["libsql@0.4.7", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.4.7", "@libsql/darwin-x64": "0.4.7", "@libsql/linux-arm64-gnu": "0.4.7", "@libsql/linux-arm64-musl": "0.4.7", "@libsql/linux-x64-gnu": "0.4.7", "@libsql/linux-x64-musl": "0.4.7", "@libsql/win32-x64-msvc": "0.4.7" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw=="], @@ -4893,8 +4712,6 @@ "eslint/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - "eslint/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - "eslint/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], "eslint/cross-spawn/path-key": ["path-key@2.0.1", "", {}, "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw=="], @@ -4927,26 +4744,20 @@ "find-cache-dir/make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "flat-cache/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "geckodriver/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "har-validator/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "has-values/is-number/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], "jest-cli/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "jest-cli/yargs/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "jest-cli/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], - "jest-cli/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - - "jest-config/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "jest-config/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "jest-each/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "jest-environment-jsdom/jsdom/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], "jest-environment-jsdom/jsdom/cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="], @@ -4975,30 +4786,14 @@ "jest-environment-jsdom/jsdom/xml-name-validator": ["xml-name-validator@3.0.0", "", {}, "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="], - "jest-jasmine2/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "jest-leak-detector/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "jest-matcher-utils/jest-diff/diff-sequences": ["diff-sequences@25.2.6", "", {}, "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg=="], - - "jest-matcher-utils/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "jest-runtime/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "jest-runtime/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "jest-runtime/yargs/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "jest-runtime/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], - "jest-runtime/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - - "jest-snapshot/jest-diff/diff-sequences": ["diff-sequences@25.2.6", "", {}, "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg=="], - - "jest-snapshot/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "jest-util/make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "jest-validate/pretty-format/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - "jest-worker/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], "jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -5009,6 +4804,10 @@ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], + + "log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], + "log-update/cli-cursor/restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="], "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -5019,28 +4818,18 @@ "object-copy/define-property/is-descriptor": ["is-descriptor@0.1.7", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg=="], - "ora/log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], - "peek-stream/duplexify/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "progress-estimator/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - "progress-estimator/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - "progress-estimator/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], "pumpify/duplexify/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="], - - "read-pkg/normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "request/http-signature/jsprim": ["jsprim@1.4.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw=="], "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "rollup-plugin-terser/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - "rollup-plugin-typescript2/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "rollup-plugin-typescript2/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], @@ -5061,8 +4850,6 @@ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "shelljs/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "slice-ansi/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], "snapdragon/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -5079,6 +4866,8 @@ "string-length/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], + "sucrase/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "table/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "table/string-width/emoji-regex": ["emoji-regex@7.0.3", "", {}, "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="], @@ -5087,112 +4876,14 @@ "table/string-width/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], + "test-exclude/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "through2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "ts-jest/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "tsc-alias/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "tsdx/rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - - "tsup/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "tsup/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "tsup/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "tsup/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "tsup/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "tsup/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "tsup/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "tsup/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "tsup/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "tsup/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "tsup/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "tsup/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "tsup/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "tsup/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "tsup/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "tsup/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "tsup/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "tsup/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "tsup/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "tsup/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "tsup/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "tsup/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "tsup/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "tsup/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - "unset-value/has-value/has-values": ["has-values@0.1.4", "", {}, "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ=="], "unset-value/has-value/isobject": ["isobject@2.1.0", "", { "dependencies": { "isarray": "1.0.0" } }, "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA=="], @@ -5201,119 +4892,77 @@ "verdaccio-audit/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "verdaccio-audit/express/path-to-regexp": ["path-to-regexp@0.1.10", "", {}, "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="], - "verdaccio-audit/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - "vite-node/vite/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "vite-node/vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - - "vitest/vite/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "vitest/vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], "webdriver/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "webdriverio/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], - "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], + "wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], - "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], + "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], - "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], + "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], - "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], + "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], - "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], + "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], - "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], + "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], - "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], + "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], - "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], + "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], - "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], + "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], - "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], + "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], - "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], + "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], - "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], + "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], - "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], + "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], - "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], + "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], - "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], + "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], - "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], + "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], - "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], + "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], - "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], + "wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], - "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], + "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], - "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], + "wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], - "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], + "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], - "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], + "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], - "wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250214.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cDvvedWDc5zrgDnuXe2qYcz/TwBvzmweO55C7XpPuAWJ9Oqxv81PkdekYxD8mH989aQ/GI5YD0Fe6fDYlM+T3Q=="], + "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], - "wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250214.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NytCvRveVzu0mRKo+tvZo3d/gCUway3B2ZVqSi/TS6NXDGBYIJo7g6s3BnTLS74kgyzeDOjhu9j/RBJBS809qw=="], + "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], - "wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250214.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pQ7+aHNHj8SiYEs4d/6cNoimE5xGeCMfgU1yfDFtA9YGN9Aj2BITZgOWPec+HW7ZkOy9oWlNrO6EvVjGgB4tbQ=="], + "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], - "wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250214.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vhlfah6Yd9ny1npNQjNgElLIjR6OFdEbuR3LCfbLDCwzWEBFhIf7yC+Tpp/a0Hq7kLz3sLdktaP7xl3PJhyOjA=="], + "wrangler/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], - "wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250214.0", "", { "os": "win32", "cpu": "x64" }, "sha512-GMwMyFbkjBKjYJoKDhGX8nuL4Gqe3IbVnVWf2Q6086CValyIknupk5J6uQWGw2EBU3RGO3x4trDXT5WphQJZDQ=="], + "wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250604.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PI6AWAzhHg75KVhYkSWFBf3HKCHstpaKg4nrx6LYZaEvz0TaTz+JQpYU2fNAgGFmVsK5xEzwFTGh3DAVAKONPw=="], + + "wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250604.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hOiZZSop7QRQgGERtTIy9eU5GvPpIsgE2/BDsUdHMl7OBZ7QLniqvgDzLNDzj0aTkCldm9Yl/Z+C7aUgRdOccw=="], + + "wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250604.0", "", { "os": "linux", "cpu": "x64" }, "sha512-S0R9r7U4nv9qejYygQj01hArC4KUbQQ4u29rvegR0MGoXZY8AHIEuJxon0kE7r7aWFJxvl4W3tOH+5hwW51LYw=="], + + "wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250604.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BTFU/rXpNy03wpeueI2P7q1vVjbg2V6mCyyFGqDqMn2gSVYXH1G0zFNolV13PQXa0HgaqM6oYnqtAxluqbA+kQ=="], + + "wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250604.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tW/U9/qDmDZBeoEVcK5skb2uouVAMXMzt7o/uGvaIFLeZsQkOp4NBmvoQQd+nbOc7nVCJIwFoSMokd89AhzCkA=="], "wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@2.0.0", "", {}, "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="], @@ -5337,74 +4986,6 @@ "@cloudflare/vitest-pool-workers/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], - - "@cloudflare/vitest-pool-workers/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/unenv/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250604.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PI6AWAzhHg75KVhYkSWFBf3HKCHstpaKg4nrx6LYZaEvz0TaTz+JQpYU2fNAgGFmVsK5xEzwFTGh3DAVAKONPw=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250604.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hOiZZSop7QRQgGERtTIy9eU5GvPpIsgE2/BDsUdHMl7OBZ7QLniqvgDzLNDzj0aTkCldm9Yl/Z+C7aUgRdOccw=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250604.0", "", { "os": "linux", "cpu": "x64" }, "sha512-S0R9r7U4nv9qejYygQj01hArC4KUbQQ4u29rvegR0MGoXZY8AHIEuJxon0kE7r7aWFJxvl4W3tOH+5hwW51LYw=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250604.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BTFU/rXpNy03wpeueI2P7q1vVjbg2V6mCyyFGqDqMn2gSVYXH1G0zFNolV13PQXa0HgaqM6oYnqtAxluqbA+kQ=="], - - "@cloudflare/vitest-pool-workers/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250604.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tW/U9/qDmDZBeoEVcK5skb2uouVAMXMzt7o/uGvaIFLeZsQkOp4NBmvoQQd+nbOc7nVCJIwFoSMokd89AhzCkA=="], - - "@jest/core/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "@jest/reporters/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/@libsql/isomorphic-fetch": ["@libsql/isomorphic-fetch@0.2.5", "", {}, "sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg=="], "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -5423,15 +5004,11 @@ "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.3.19", "", { "os": "win32", "cpu": "x64" }, "sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg=="], - "@libsql/kysely-libsql/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], - - "@rollup/plugin-commonjs/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - - "@typescript-eslint/typescript-estree/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "@verdaccio/middleware/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "@vitest/coverage-v8/vitest/vite/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "@vitest/coverage-v8/vitest/vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], + + "babel-plugin-istanbul/test-exclude/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "bknd-cli/@libsql/client/libsql/@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.4.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg=="], @@ -5447,9 +5024,7 @@ "bknd-cli/@libsql/client/libsql/@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.4.7", "", { "os": "win32", "cpu": "x64" }, "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw=="], - "bknd-cli/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="], - - "bknd/vitest/vite/esbuild": ["esbuild@0.25.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.1", "@esbuild/android-arm": "0.25.1", "@esbuild/android-arm64": "0.25.1", "@esbuild/android-x64": "0.25.1", "@esbuild/darwin-arm64": "0.25.1", "@esbuild/darwin-x64": "0.25.1", "@esbuild/freebsd-arm64": "0.25.1", "@esbuild/freebsd-x64": "0.25.1", "@esbuild/linux-arm": "0.25.1", "@esbuild/linux-arm64": "0.25.1", "@esbuild/linux-ia32": "0.25.1", "@esbuild/linux-loong64": "0.25.1", "@esbuild/linux-mips64el": "0.25.1", "@esbuild/linux-ppc64": "0.25.1", "@esbuild/linux-riscv64": "0.25.1", "@esbuild/linux-s390x": "0.25.1", "@esbuild/linux-x64": "0.25.1", "@esbuild/netbsd-arm64": "0.25.1", "@esbuild/netbsd-x64": "0.25.1", "@esbuild/openbsd-arm64": "0.25.1", "@esbuild/openbsd-x64": "0.25.1", "@esbuild/sunos-x64": "0.25.1", "@esbuild/win32-arm64": "0.25.1", "@esbuild/win32-ia32": "0.25.1", "@esbuild/win32-x64": "0.25.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ=="], + "bknd/vitest/vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], "eslint/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], @@ -5457,34 +5032,22 @@ "eslint/cross-spawn/shebang-command/shebang-regex": ["shebang-regex@1.0.0", "", {}, "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="], - "flat-cache/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - "jest-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "jest-cli/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - - "jest-config/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "jest-environment-jsdom/jsdom/cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], "jest-environment-jsdom/jsdom/whatwg-encoding/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "jest-environment-jsdom/jsdom/whatwg-url/tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="], - "jest-runtime/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "jest-runtime/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "jest-runtime/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], + + "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], "log-update/cli-cursor/restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="], - "ora/log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "ora/log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], - - "ora/log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="], - "peek-stream/duplexify/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "peek-stream/duplexify/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], @@ -5513,8 +5076,6 @@ "sane/micromatch/braces/fill-range": ["fill-range@4.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" } }, "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ=="], - "shelljs/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "table/string-width/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], @@ -5523,210 +5084,14 @@ "verdaccio-audit/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "vite-node/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "vite-node/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "vite-node/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "vite-node/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "vite-node/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "vite-node/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "vite-node/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "vite-node/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "vite-node/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "vite-node/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "vite-node/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "vite-node/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "vite-node/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "vite-node/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "vite-node/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "vite-node/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "vite-node/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "vite-node/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "vite-node/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "vite-node/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "vite-node/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "vite-node/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "vite-node/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "vite-node/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - - "vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - - "@jest/core/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "@vitest/coverage-v8/vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], - - "bknd/vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.1", "", { "os": "android", "cpu": "arm" }, "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q=="], - - "bknd/vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.1", "", { "os": "android", "cpu": "arm64" }, "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA=="], - - "bknd/vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.1", "", { "os": "android", "cpu": "x64" }, "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw=="], - - "bknd/vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA=="], - - "bknd/vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A=="], - - "bknd/vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.1", "", { "os": "linux", "cpu": "arm" }, "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.1", "", { "os": "linux", "cpu": "none" }, "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA=="], - - "bknd/vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.1", "", { "os": "none", "cpu": "x64" }, "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA=="], - - "bknd/vitest/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg=="], - - "bknd/vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw=="], - - "bknd/vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg=="], - - "bknd/vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ=="], - - "bknd/vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A=="], - - "bknd/vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg=="], + "wrangler/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "eslint/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - "flat-cache/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "log-update/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], - "ora/log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - - "ora/log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="], - "progress-estimator/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "sane/execa/cross-spawn/shebang-command/shebang-regex": ["shebang-regex@1.0.0", "", {}, "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="], @@ -5737,8 +5102,6 @@ "sane/micromatch/braces/fill-range/to-regex-range": ["to-regex-range@2.1.1", "", { "dependencies": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" } }, "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg=="], - "ora/log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - "sane/micromatch/braces/fill-range/is-number/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], } } diff --git a/package.json b/package.json index 6339feea..2ed921d4 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,7 @@ "sideEffects": false, "type": "module", "scripts": { - "test": "ALL_TESTS=1 bun test --bail", - "test:coverage": "bun test --coverage", - "types": "bun run --filter './packages/**' types", - "build": "bun run clean:dist && bun run --cwd app build:all && bun build:packages", - "build:packages": "bun run --filter './packages/{cli,plasmic}' build", - "git:pre-commit": "bun run test", "updater": "bun x npm-check-updates -ui", - "clean:dist": "find packages -name 'dist' -type d -exec rm -rf {} +", "ci": "find . -name 'node_modules' -type d -exec rm -rf {} + && bun install", "npm:local": "verdaccio --config verdaccio.yml", "format": "bunx biome format --write ./app", @@ -20,26 +13,15 @@ "dependencies": {}, "devDependencies": { "@biomejs/biome": "1.9.4", - "@clack/prompts": "^0.10.0", "@tsconfig/strictest": "^2.0.5", "@types/lodash-es": "^4.17.12", "bun-types": "^1.1.18", - "dotenv": "^16.4.5", - "esbuild": "^0.23.0", - "esbuild-plugin-tsc": "^0.4.0", "miniflare": "^3.20240806.0", - "mitata": "^0.1.11", - "picocolors": "^1.0.1", - "semver": "^7.6.2", - "sql-formatter": "^15.3.2", - "tsd": "^0.31.1", - "tsup": "^8.1.0", "typescript": "^5.5.3", - "verdaccio": "^5.32.1", - "wrangler": "^3.108.1" + "verdaccio": "^5.32.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=22" }, "workspaces": ["app", "packages/*"] } From 2239333f08378352c5edea16c216ccb778d7e3b1 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 17:18:29 +0200 Subject: [PATCH 11/39] updated docs on databases --- app/src/cli/commands/run/run.ts | 8 +- docs/integration/aws.mdx | 9 +- docs/integration/bun.mdx | 3 +- docs/integration/docker.mdx | 8 +- docs/start.mdx | 12 +- docs/usage/database.mdx | 206 ++++++++++++++++++++++---------- 6 files changed, 160 insertions(+), 86 deletions(-) diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index de2bf40a..2d6f7512 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -44,11 +44,10 @@ export const run: CliCommand = (program) => { ) .addOption(new Option("-c, --config ", "config file")) .addOption( - new Option("--db-url ", "database url, can be any valid libsql url").conflicts( + new Option("--db-url ", "database url, can be any valid sqlite url").conflicts( "config", ), ) - .addOption(new Option("--db-token ", "database token").conflicts("config")) .addOption( new Option("--server ", "server type") .choices(PLATFORMS) @@ -90,7 +89,6 @@ type RunOptions = { memory?: boolean; config?: string; dbUrl?: string; - dbToken?: string; server: Platform; open?: boolean; }; @@ -102,9 +100,7 @@ export async function makeAppFromEnv(options: Partial = {}) { // first start from arguments if given if (options.dbUrl) { console.info("Using connection from", c.cyan("--db-url")); - const connection = options.dbUrl - ? { url: options.dbUrl, authToken: options.dbToken } - : undefined; + const connection = options.dbUrl ? { url: options.dbUrl } : undefined; app = await makeApp({ connection, server: { platform: options.server } }); // check configuration file to be present diff --git a/docs/integration/aws.mdx b/docs/integration/aws.mdx index 13185715..043241f6 100644 --- a/docs/integration/aws.mdx +++ b/docs/integration/aws.mdx @@ -27,12 +27,13 @@ To serve the API, you can use the `serveLambda` function of the AWS Lambda adapt ```tsx index.mjs import { serveLambda } from "bknd/adapter/aws"; +import { libsql } from "bknd/data"; export const handler = serveLambda({ - connection: { - url: process.env.DB_URL!, - authToken: process.env.DB_AUTH_TOKEN! - } + connection: libsql({ + url: "libsql://your-database-url.turso.io", + authToken: "your-auth-token", + }), }); ``` Although the runtime would support database as a file, we don't recommend it. You'd need to also bundle the native dependencies which increases the deployment size and cold start time. Instead, we recommend you to use [LibSQL on Turso](/usage/database#sqlite-using-libsql-on-turso). diff --git a/docs/integration/bun.mdx b/docs/integration/bun.mdx index ec791013..ecac98b2 100644 --- a/docs/integration/bun.mdx +++ b/docs/integration/bun.mdx @@ -34,8 +34,7 @@ import { serve } from "bknd/adapter/bun"; // if the configuration is omitted, it uses an in-memory database serve({ connection: { - url: process.env.DB_URL!, - authToken: process.env.DB_AUTH_TOKEN! + url: "file:data.db" } }); ``` diff --git a/docs/integration/docker.mdx b/docs/integration/docker.mdx index c535580a..387c2516 100644 --- a/docs/integration/docker.mdx +++ b/docs/integration/docker.mdx @@ -18,9 +18,7 @@ docker build -t bknd . If you want to override the bknd version used, you can pass a `VERSION` build argument: ```bash docker build --build-arg VERSION= -t bknd . -```` - -```bash +``` ## Running the Docker container To run the Docker container, run the following command: @@ -34,10 +32,6 @@ You can pass the same CLI arguments (see [Using the CLI](https://docs.bknd.io/cl ```bash docker run -p 1337:1337 -e ARGS="--db-url file:/data/data.db" bknd ``` -Or connect to a remote turso database: -```bash -docker run -p 1337:1337 -e ARGS="--db-url libsql://.turso.io --db-token " bknd -``` To mount the data directory to the host, you can use the `-v` flag: ```bash diff --git a/docs/start.mdx b/docs/start.mdx index e1b65760..a53e8e9a 100644 --- a/docs/start.mdx +++ b/docs/start.mdx @@ -7,6 +7,12 @@ import { Stackblitz, examples } from "/snippets/stackblitz.mdx" Glad you're here! **bknd** is a lightweight, infrastructure agnostic and feature-rich backend that runs in any JavaScript environment. +- Instant backend with full REST API +- Built on Web Standards for maximum compatibility +- Multiple run modes (standalone, runtime, framework) +- Official API and React SDK with type-safety +- React elements for auto-configured authentication and media components + ## Preview Here is a preview of **bknd** in StackBlitz: @@ -96,12 +102,12 @@ The following databases are currently supported. Request a new integration if yo {libsql}} + title="SQLite" + icon={
{sqlite}
} href="/usage/database#database" /> {turso}} href="/usage/database#sqlite-using-libsql-on-turso" /> diff --git a/docs/usage/database.mdx b/docs/usage/database.mdx index 3e677cee..3183dab9 100644 --- a/docs/usage/database.mdx +++ b/docs/usage/database.mdx @@ -3,53 +3,132 @@ title: 'Database' description: 'Choosing the right database configuration' --- -In order to use **bknd**, you need to prepare access information to your database and install the dependencies. Connections to the database are managed using Kysely. Therefore, all [its dialects](https://kysely.dev/docs/dialects) are theoretically supported. +In order to use **bknd**, you need to prepare access information to your database and potentially install additional dependencies. Connections to the database are managed using Kysely. Therefore, all [its dialects](https://kysely.dev/docs/dialects) are theoretically supported. +Currently supported and tested databases are: +- SQLite (embedded): Node.js SQLite, Bun SQLite, LibSQL, SQLocal +- SQLite (remote): Turso, Cloudflare D1 +- Postgres: Vanilla Postgres, Supabase, Neon, Xata -## Database -### SQLite in-memory -The easiest to get started is using SQLite in-memory. When serving the API in the "Integrations", -the function accepts an object with connection details. To use an in-memory database, you can either omit the object completely or explicitly use it as follows: -```json -{ - "url": ":memory:" -} +By default, bknd will try to use a SQLite database in-memory. Depending on your runtime, a different SQLite implementation will be used. + +## Defining the connection +There are mainly 3 ways to define the connection to your database, when +1. creating an app using `App.create()` or `createApp()` +2. creating an app using a [Framework or Runtime adapter](/integration/introduction) +3. starting a quick instance using the [CLI](/usage/cli#using-configuration-file-bknd-config) + +When creating an app using `App.create()` or `createApp()`, you can pass a connection object in the configuration object. + +```typescript app.ts +import { createApp } from "bknd"; +import { sqlite } from "bknd/adapter/sqlite"; + +// a connection is required when creating an app like this +const app = createApp({ + connection: sqlite({ url: ":memory:" }), +}); ``` -### SQLite as file -Just like the in-memory option, using a file is just as easy: +When using an adapter, or using the CLI, bknd will automatically try to use a SQLite implementation depending on the runtime: -```json -{ - "url": "file:" -} -``` -Please note that using SQLite as a file is only supported in server environments. -### SQLite using LibSQL -Turso offers a SQLite-fork called LibSQL that runs a server around your SQLite database. To -point **bknd** to a local instance of LibSQL, [install Turso's CLI](https://docs.turso.tech/cli/introduction) and run the following command: -```bash -turso dev +```javascript app.js +import { serve } from "bknd/adapter/node"; + +serve({ + // connection is optional, but recommended + connection: { url: "file:data.db" }, +}); ``` -The command will yield a URL. Use it in the connection object: -```json -{ - "url": "http://localhost:8080" -} +You can also pass a connection instance to the `connection` property to explictly use a specific connection. + +```javascript app.js +import { serve } from "bknd/adapter/node"; +import { sqlite } from "bknd/adapter/sqlite"; + +serve({ + connection: sqlite({ url: "file:data.db" }), +}); ``` -### SQLite using LibSQL on Turso -If you want to use LibSQL on Turso, [sign up for a free account](https://turso.tech/), create a database and point your -connection object to your new database: -```json -{ - "url": "libsql://your-database-url.turso.io", - "authToken": "your-auth-token" -} + +If you're using [`bknd.config.*`](/extending/config), you can specify the connection on the exported object. + +```typescript bknd.config.ts +import type { BkndConfig } from "bknd"; + +export default { + connection: { url: "file:data.db" }, +} as const satisfies BkndConfig; ``` +Throughout the documentation, it is assumed you use `bknd.config.ts` to define your connection. + +## SQLite +### Using config object + +The `sqlite` adapter is automatically resolved based on the runtime. + +| Runtime | Adapter | In-Memory | File | Remote | +| ------- | ------- | --------- | ---- | ------ | +| Node.js | `node:sqlite` | ✅ | ✅ | ❌ | +| Bun | `bun:sqlite` | ✅ | ✅ | ❌ | +| Cloudflare Worker/Browser/Edge | `libsql` | 🟠 | 🟠 | ✅ | + +The bundled version of the `libsql` connection only works with remote databases. However, you can pass in a `Client` from `@libsql/client`, see [LibSQL](#libsql) for more details. + +```typescript bknd.config.ts +import type { BkndConfig } from "bknd"; + +// no connection is required, bknd will use a SQLite database in-memory +// this does not work on edge environments! +export default {} as const satisfies BkndConfig; + +// or explicitly in-memory +export default { + connection: { url: ":memory:" }, +} as const satisfies BkndConfig; + +// or explicitly as a file +export default { + connection: { url: "file:" }, +} as const satisfies BkndConfig; +``` + + +### LibSQL +Turso offers a SQLite-fork called LibSQL that runs a server around your SQLite database. The edge-version of the adapter is included in the bundle (remote only): + +```typescript bknd.config.ts +import type { BkndConfig } from "bknd"; +import { libsql } from "bknd/data"; + +export default { + connection: libsql({ + url: "libsql://your-database-url.turso.io", + authToken: "your-auth-token", + }), +} as const satisfies BkndConfig; +``` + +If you wish to use LibSQL as file, in-memory or make use of [Embedded Replicas](https://docs.turso.tech/features/embedded-replicas/introduction), you have to pass in the `Client` from `@libsql/client`: + +```typescript bknd.config.ts +import type { BkndConfig } from "bknd"; +import { libsql } from "bknd/data"; +import { createClient } from "@libsql/client"; + +export default { + connection: libsql(createClient({ + url: "libsql://your-database-url.turso.io", + authToken: "your-auth-token", + })), +} as const satisfies BkndConfig; +``` + + ### Cloudflare D1 Using the [Cloudflare Adapter](/integration/cloudflare), you can choose to use a D1 database binding. To do so, you only need to add a D1 database to your `wrangler.toml` and it'll pick up automatically. @@ -63,7 +142,29 @@ export default serve({ }); ``` -### PostgreSQL + +### SQLocal +To use bknd with `sqlocal` for a offline expierence, you need to install the `@bknd/sqlocal` package. You can do so by running the following command: + +```bash +npm install @bknd/sqlocal +``` + +This package uses `sqlocal` under the hood. Consult the [sqlocal documentation](https://sqlocal.dallashoffman.com/guide/setup) for connection options: + +```js +import { createApp } from "bknd"; +import { SQLocalConnection } from "@bknd/sqlocal"; + +const app = createApp({ + connection: new SQLocalConnection({ + databasePath: ":localStorage:", + verbose: true, + }) +}); +``` + +## PostgreSQL To use bknd with Postgres, you need to install the `@bknd/postgres` package. You can do so by running the following command: ```bash @@ -72,7 +173,7 @@ npm install @bknd/postgres You can connect to your Postgres database using `pg` or `postgres` dialects. Additionally, you may also define your custom connection. -#### Using `pg` +### Using `pg` To establish a connection to your database, you can use any connection options available on the [`pg`](https://node-postgres.com/apis/client) package. @@ -91,7 +192,7 @@ const config = { serve(config); ``` -#### Using `postgres` +### Using `postgres` To establish a connection to your database, you can use any connection options available on the [`postgres`](https://github.com/porsager/postgres) package. @@ -104,7 +205,7 @@ serve({ }); ``` -#### Using custom connection +### Using custom connection Several Postgres hosting providers offer their own clients to connect to their database, e.g. suitable for serverless environments. @@ -148,31 +249,8 @@ serve({ }); ``` - -### SQLocal -To use bknd with `sqlocal` for a offline expierence, you need to install the `@bknd/sqlocal` package. You can do so by running the following command: - -```bash -npm install @bknd/sqlocal -``` - -This package uses `sqlocal` under the hood. Consult the [sqlocal documentation](https://sqlocal.dallashoffman.com/guide/setup) for connection options: - -```js -import { createApp } from "bknd"; -import { SQLocalConnection } from "@bknd/sqlocal"; - -const app = createApp({ - connection: new SQLocalConnection({ - databasePath: ":localStorage:", - verbose: true, - }) -}); -``` - -### Custom Connection -Any bknd app instantiation accepts as connection either `undefined`, a connection object like -described above, or an class instance that extends from `Connection`: +## Custom Connection +Creating a custom connection is as easy as extending the `Connection` class and passing constructing a Kysely instance. ```ts import { createApp } from "bknd"; From af6d1960b95c89e843783376e3e1aa21e2eb8516 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 17:27:58 +0200 Subject: [PATCH 12/39] fix tests --- app/__test__/App.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 5aa9ef14..e611304b 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -1,5 +1,5 @@ -import { afterEach, describe, test } from "bun:test"; -import { App } from "../src"; +import { afterEach, describe, test, expect } from "bun:test"; +import { App, createApp } from "core/test/utils"; import { getDummyConnection } from "./helper"; import { Hono } from "hono"; import * as proto from "../src/data/prototype"; @@ -18,7 +18,7 @@ describe("App tests", async () => { test("plugins", async () => { const called: string[] = []; - const app = App.create({ + const app = createApp({ initialConfig: { auth: { enabled: true, From 2ada4e9f200c010d69f85eb80e44187ab8ddd601 Mon Sep 17 00:00:00 2001 From: dswbx Date: Fri, 13 Jun 2025 21:15:29 +0200 Subject: [PATCH 13/39] various fixes: refactored imports, introduced fromDriver/toDriver to improve compat --- app/package.json | 12 +++---- app/src/adapter/index.ts | 5 +-- app/src/cli/commands/run/run.ts | 3 +- app/src/data/connection/Connection.ts | 9 +++++ .../data/connection/connection-test-suite.ts | 2 ++ .../connection/sqlite/LibsqlConnection.ts | 2 +- .../connection/sqlite/SqliteConnection.ts | 22 ++++++++++++ app/src/data/data-schema.ts | 10 ++---- app/src/data/entities/EntityManager.ts | 4 +++ app/src/data/entities/mutation/Mutator.ts | 14 ++++++-- .../data/entities/mutation/MutatorResult.ts | 6 +++- app/src/data/entities/query/Repository.ts | 6 ++-- app/src/data/server/query.ts | 2 +- app/src/media/MediaField.ts | 2 +- app/vite.dev.ts | 36 +++++++++++++------ 15 files changed, 100 insertions(+), 35 deletions(-) diff --git a/app/package.json b/app/package.json index 18b9e5e8..2d858792 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.14.0", + "version": "0.15.0-rc.0", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { @@ -201,11 +201,11 @@ }, "require": "./dist/adapter/sqlite/node.js" }, - "./plugins": { - "types": "./dist/types/plugins/index.d.ts", - "import": "./dist/plugins/index.js", - "require": "./dist/plugins/index.js" - }, + "./plugins": { + "types": "./dist/types/plugins/index.d.ts", + "import": "./dist/plugins/index.js", + "require": "./dist/plugins/index.js" + }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index abd2f8ec..75540407 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -69,8 +69,9 @@ export async function createAdapterApp = {}) { let app: App | undefined = undefined; // first start from arguments if given if (options.dbUrl) { - console.info("Using connection from", c.cyan("--db-url")); + console.info("Using connection from", c.cyan("--db-url"), c.cyan(options.dbUrl)); const connection = options.dbUrl ? { url: options.dbUrl } : undefined; app = await makeApp({ connection, server: { platform: options.server } }); diff --git a/app/src/data/connection/Connection.ts b/app/src/data/connection/Connection.ts index fab8e368..cd807b74 100644 --- a/app/src/data/connection/Connection.ts +++ b/app/src/data/connection/Connection.ts @@ -20,6 +20,7 @@ import { import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector"; import type { Constructor, DB } from "core"; import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; +import type { Field } from "data/fields/Field"; export type QB = SelectQueryBuilder; @@ -200,6 +201,14 @@ export abstract class Connection { abstract getFieldSchema(spec: FieldSpec, strict?: boolean): SchemaResponse; + toDriver(value: unknown, field: Field): unknown { + return value; + } + + fromDriver(value: any, field: Field): unknown { + return value; + } + async close(): Promise { // no-op by default } diff --git a/app/src/data/connection/connection-test-suite.ts b/app/src/data/connection/connection-test-suite.ts index 5305337d..6d6f84dc 100644 --- a/app/src/data/connection/connection-test-suite.ts +++ b/app/src/data/connection/connection-test-suite.ts @@ -1,6 +1,8 @@ import type { TestRunner } from "core/test"; import { Connection, type FieldSpec } from "./Connection"; +// @todo: add various datatypes: string, number, boolean, object, array, null, undefined, date, etc. + export function connectionTestSuite( testRunner: TestRunner, { diff --git a/app/src/data/connection/sqlite/LibsqlConnection.ts b/app/src/data/connection/sqlite/LibsqlConnection.ts index e6833eb1..cf68962a 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/LibsqlConnection.ts @@ -57,6 +57,6 @@ export class LibsqlConnection extends SqliteConnection { } } -export function libsql(credentials: LibSqlCredentials): LibsqlConnection { +export function libsql(credentials: Client | LibSqlCredentials): LibsqlConnection { return new LibsqlConnection(credentials); } diff --git a/app/src/data/connection/sqlite/SqliteConnection.ts b/app/src/data/connection/sqlite/SqliteConnection.ts index 705046dd..d435c6ce 100644 --- a/app/src/data/connection/sqlite/SqliteConnection.ts +++ b/app/src/data/connection/sqlite/SqliteConnection.ts @@ -11,7 +11,9 @@ import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } fro import type { Constructor } from "core"; import { customIntrospector } from "../Connection"; import { SqliteIntrospector } from "./SqliteIntrospector"; +import type { Field } from "data/fields/Field"; +// @todo: add pragmas export type SqliteConnectionConfig< CustomDialect extends Constructor = Constructor, > = { @@ -80,4 +82,24 @@ export abstract class SqliteConnection extends Connection { row[key] = field.getDefault(); } + // transform from driver + value = this.connection.fromDriver(value, field); + + // transform from field row[key] = field.transformRetrieve(value as any); } catch (e: any) { throw new TransformRetrieveFailedException( diff --git a/app/src/data/entities/mutation/Mutator.ts b/app/src/data/entities/mutation/Mutator.ts index bf8cebec..a270c7a7 100644 --- a/app/src/data/entities/mutation/Mutator.ts +++ b/app/src/data/entities/mutation/Mutator.ts @@ -1,13 +1,15 @@ -import { $console, type DB as DefaultDB, type PrimaryFieldType } from "core"; +import type { DB as DefaultDB, PrimaryFieldType } from "core"; import { type EmitsEvents, EventManager } from "core/events"; import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely"; -import { type TActionContext, WhereBuilder } from "../.."; +import type { TActionContext } from "../.."; +import { WhereBuilder } from "../query/WhereBuilder"; import type { Entity, EntityData, EntityManager } from "../../entities"; import { InvalidSearchParamsException } from "../../errors"; import { MutatorEvents } from "../../events"; import { RelationMutator } from "../../relations"; import type { RepoQuery } from "../../server/query"; import { MutatorResult, type MutatorResultOptions } from "./MutatorResult"; +import { transformObject } from "core/utils"; type MutatorQB = | InsertQueryBuilder @@ -86,7 +88,11 @@ export class Mutator< throw new Error(`Field "${key}" is not fillable on entity "${entity.name}"`); } + // transform from field validatedData[key] = await field.transformPersist(data[key], this.em, context); + + // transform to driver + validatedData[key] = this.em.connection.toDriver(validatedData[key], field); } if (Object.keys(validatedData).length === 0) { @@ -283,6 +289,10 @@ export class Mutator< ): Promise> { const entity = this.entity; const validatedData = await this.getValidatedData(data, "update"); + console.log("updateWhere", { + entity, + validatedData, + }); // @todo: add a way to delete all by adding force? if (!where || typeof where !== "object" || Object.keys(where).length === 0) { diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index 25ae7f66..b5ad0834 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -5,6 +5,7 @@ import { Result, type ResultJSON, type ResultOptions } from "../Result"; export type MutatorResultOptions = ResultOptions & { silent?: boolean; + logParams?: boolean; }; export type MutatorResultJSON = ResultJSON; @@ -19,7 +20,10 @@ export class MutatorResult extends Result { hydrator: (rows) => em.hydrate(entity.name, rows as any), beforeExecute: (compiled) => { if (!options?.silent) { - $console.debug(`[Mutation]\n${compiled.sql}\n`); + $console.debug( + `[Mutation]\n${compiled.sql}\n`, + options?.logParams ? compiled.parameters : undefined, + ); } }, onError: (error) => { diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index ce39c6aa..f41bd8bc 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -246,8 +246,10 @@ export class Repository Date: Sat, 14 Jun 2025 07:08:46 +0200 Subject: [PATCH 14/39] fixing tests --- app/__test__/data/specs/WithBuilder.spec.ts | 16 ++++++++-------- app/src/data/connection/connection-test-suite.ts | 1 + .../data/connection/sqlite/SqliteConnection.ts | 3 --- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/__test__/data/specs/WithBuilder.spec.ts b/app/__test__/data/specs/WithBuilder.spec.ts index db0273c6..568cf1fb 100644 --- a/app/__test__/data/specs/WithBuilder.spec.ts +++ b/app/__test__/data/specs/WithBuilder.spec.ts @@ -89,9 +89,9 @@ describe("[data] WithBuilder", async () => { const res2 = qb2.compile(); expect(res2.sql).toBe( - 'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username") from (select "users"."id" as "id", "users"."username" as "username" from "users" as "author" where "author"."id" = "posts"."author_id" order by "users"."id" asc limit ? offset ?) as obj) as "author" from "posts"', + 'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username") from (select "users"."id" as "id", "users"."username" as "username" from "users" as "author" where "author"."id" = "posts"."author_id" order by "users"."id" asc limit ?) as obj) as "author" from "posts"', ); - expect(res2.parameters).toEqual([1, 0]); + expect(res2.parameters).toEqual([1]); }); test("test with empty join", async () => { @@ -194,9 +194,9 @@ describe("[data] WithBuilder", async () => { ); const res = qb.compile(); expect(res.sql).toBe( - 'select (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" order by "media"."id" asc limit ? offset ?) as obj) as "single" from "categories"', + 'select (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" order by "media"."id" asc limit ?) as obj) as "single" from "categories"', ); - expect(res.parameters).toEqual(["categories.single", 1, 0]); + expect(res.parameters).toEqual(["categories.single", 1]); const qb2 = WithBuilder.addClause( em, @@ -273,9 +273,9 @@ describe("[data] WithBuilder", async () => { //prettyPrintQb(qb); expect(qb.compile().sql).toBe( - 'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username", \'avatar\', "obj"."avatar") from (select "users"."id" as "id", "users"."username" as "username", (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "users"."id" = "media"."entity_id" order by "media"."id" asc limit ? offset ?) as obj) as "avatar" from "users" as "users" where "users"."id" = "posts"."users_id" order by "users"."username" asc limit ? offset ?) as obj) as "users" from "posts"', + 'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username", \'avatar\', "obj"."avatar") from (select "users"."id" as "id", "users"."username" as "username", (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "users"."id" = "media"."entity_id" order by "media"."id" asc limit ?) as obj) as "avatar" from "users" as "users" where "users"."id" = "posts"."users_id" order by "users"."username" asc limit ?) as obj) as "users" from "posts"', ); - expect(qb.compile().parameters).toEqual(["users.avatar", 1, 0, 1, 0]); + expect(qb.compile().parameters).toEqual(["users.avatar", 1, 1]); }); test("compiles with many", async () => { @@ -315,9 +315,9 @@ describe("[data] WithBuilder", async () => { ); expect(qb.compile().sql).toBe( - 'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'posts_id\', "agg"."posts_id", \'users_id\', "agg"."users_id", \'users\', "agg"."users")), \'[]\') from (select "comments"."id" as "id", "comments"."posts_id" as "posts_id", "comments"."users_id" as "users_id", (select json_object(\'username\', "obj"."username") from (select "users"."username" as "username" from "users" as "users" where "users"."id" = "comments"."users_id" order by "users"."id" asc limit ? offset ?) as obj) as "users" from "comments" as "comments" where "comments"."posts_id" = "posts"."id" order by "comments"."id" asc limit ? offset ?) as agg) as "comments" from "posts"', + 'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'posts_id\', "agg"."posts_id", \'users_id\', "agg"."users_id", \'users\', "agg"."users")), \'[]\') from (select "comments"."id" as "id", "comments"."posts_id" as "posts_id", "comments"."users_id" as "users_id", (select json_object(\'username\', "obj"."username") from (select "users"."username" as "username" from "users" as "users" where "users"."id" = "comments"."users_id" order by "users"."id" asc limit ?) as obj) as "users" from "comments" as "comments" where "comments"."posts_id" = "posts"."id" order by "comments"."id" asc limit ? offset ?) as agg) as "comments" from "posts"', ); - expect(qb.compile().parameters).toEqual([1, 0, 12, 0]); + expect(qb.compile().parameters).toEqual([1, 12, 0]); }); test("returns correct result", async () => { diff --git a/app/src/data/connection/connection-test-suite.ts b/app/src/data/connection/connection-test-suite.ts index 6d6f84dc..59bba0ee 100644 --- a/app/src/data/connection/connection-test-suite.ts +++ b/app/src/data/connection/connection-test-suite.ts @@ -2,6 +2,7 @@ import type { TestRunner } from "core/test"; import { Connection, type FieldSpec } from "./Connection"; // @todo: add various datatypes: string, number, boolean, object, array, null, undefined, date, etc. +// @todo: add toDriver/fromDriver tests on all types and fields export function connectionTestSuite( testRunner: TestRunner, diff --git a/app/src/data/connection/sqlite/SqliteConnection.ts b/app/src/data/connection/sqlite/SqliteConnection.ts index d435c6ce..523a8244 100644 --- a/app/src/data/connection/sqlite/SqliteConnection.ts +++ b/app/src/data/connection/sqlite/SqliteConnection.ts @@ -97,9 +97,6 @@ export abstract class SqliteConnection extends Connection Date: Sat, 14 Jun 2025 07:22:44 +0200 Subject: [PATCH 15/39] change app plugins to be a map to prevent duplicates and provide easier access --- app/__test__/App.spec.ts | 4 +-- app/src/App.ts | 27 ++++++++++--------- .../bun/connection/BunSqliteConnection.ts | 1 + app/src/modules/server/SystemController.ts | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index e611304b..8b6b6722 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -101,7 +101,7 @@ describe("App tests", async () => { "onFirstBoot", "onBuilt", ]); - expect(app.plugins).toHaveLength(1); - expect(app.plugins.map((p) => p.name)).toEqual(["test"]); + expect(app.plugins.size).toBe(1); + expect(Array.from(app.plugins.keys())).toEqual(["test"]); }); }); diff --git a/app/src/App.ts b/app/src/App.ts index 85c782f4..b1f475fa 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -62,8 +62,8 @@ export type AppOptions = { manager?: Omit; asyncEventsMode?: "sync" | "async" | "none"; }; -export type CreateAppConfig = { - connection?: C | { url: string }; +export type CreateAppConfig = { + connection?: Connection | { url: string }; initialConfig?: InitialModuleConfigs; options?: AppOptions; }; @@ -71,23 +71,26 @@ export type CreateAppConfig = { export type AppConfig = InitialModuleConfigs; export type LocalApiOptions = Request | ApiOptions; -export class App { +export class App { static readonly Events = AppEvents; modules: ModuleManager; adminController?: AdminController; _id: string = crypto.randomUUID(); - plugins: AppPluginConfig[]; + plugins: Map = new Map(); private trigger_first_boot = false; private _building: boolean = false; constructor( - public connection: C, + public connection: Connection, _initialConfig?: InitialModuleConfigs, private options?: AppOptions, ) { - this.plugins = (options?.plugins ?? []).map((plugin) => plugin(this)); + for (const plugin of options?.plugins ?? []) { + const config = plugin(this); + this.plugins.set(config.name, config); + } this.runPlugins("onBoot"); this.modules = new ModuleManager(connection, { ...(options?.manager ?? {}), @@ -109,22 +112,22 @@ export class App { ...args: any[] ): Promise<{ name: string; result: any }[]> { const results: { name: string; result: any }[] = []; - for (const plugin of this.plugins) { + for (const [name, config] of this.plugins) { try { - if (key in plugin && plugin[key]) { - const fn = plugin[key]; + if (key in config && config[key]) { + const fn = config[key]; if (fn && typeof fn === "function") { - $console.debug(`[Plugin:${plugin.name}] ${key}`); + $console.debug(`[Plugin:${name}] ${key}`); // @ts-expect-error const result = await fn(...args); results.push({ - name: plugin.name, + name, result, }); } } } catch (e) { - $console.warn(`[Plugin:${plugin.name}] error running "${key}"`, String(e)); + $console.warn(`[Plugin:${name}] error running "${key}"`, String(e)); } } return results as any; diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index 7f124d1a..f884249b 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -6,6 +6,7 @@ import { type IGenericSqlite, } from "data/connection/sqlite/GenericSqliteConnection"; +export type BunSqliteConnection = GenericSqliteConnection; export type BunSqliteConnectionConfig = { database: Database; }; diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index 99d52043..ec078317 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -322,7 +322,7 @@ export class SystemController extends Controller { local: datetimeStringLocal(), utc: datetimeStringUTC(), }, - plugins: this.app.plugins.map((p) => p.name), + plugins: Array.from(this.app.plugins.keys()), }), ); From 6b3ac9e6e2a81eb3c08ddd4371d416089d94158a Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 14 Jun 2025 08:06:05 +0200 Subject: [PATCH 16/39] fix toDriver mutation convertion not respecting default values, react re-renders on navigation, mutator result logging --- app/package.json | 2 +- app/src/data/entities/mutation/Mutator.ts | 15 +++++++-------- app/src/data/entities/mutation/MutatorResult.ts | 5 ++++- app/src/ui/routes/data/data.$entity.$id.tsx | 12 +++++++++++- app/src/ui/routes/data/data.$entity.index.tsx | 4 ++++ 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/package.json b/app/package.json index 2d858792..749208e8 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.0", + "version": "0.15.0-rc.2", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/data/entities/mutation/Mutator.ts b/app/src/data/entities/mutation/Mutator.ts index a270c7a7..2e01cfe1 100644 --- a/app/src/data/entities/mutation/Mutator.ts +++ b/app/src/data/entities/mutation/Mutator.ts @@ -125,10 +125,13 @@ export class Mutator< // if listener returned, take what's returned const _data = result.returned ? result.params.data : data; - let validatedData = { - ...entity.getDefaultObject(), - ...(await this.getValidatedData(_data, "create")), - }; + let validatedData = await this.getValidatedData( + { + ...entity.getDefaultObject(), + ..._data, + }, + "create", + ); // check if required fields are present const required = entity.getRequiredFields(); @@ -289,10 +292,6 @@ export class Mutator< ): Promise> { const entity = this.entity; const validatedData = await this.getValidatedData(data, "update"); - console.log("updateWhere", { - entity, - validatedData, - }); // @todo: add a way to delete all by adding force? if (!where || typeof where !== "object" || Object.keys(where).length === 0) { diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index b5ad0834..05da0172 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -2,6 +2,7 @@ import { $console } from "core/console"; import type { Entity, EntityData } from "../Entity"; import type { EntityManager } from "../EntityManager"; import { Result, type ResultJSON, type ResultOptions } from "../Result"; +import { isDebug } from "core"; export type MutatorResultOptions = ResultOptions & { silent?: boolean; @@ -16,13 +17,15 @@ export class MutatorResult extends Result { public entity: Entity, options?: MutatorResultOptions, ) { + const logParams = options?.logParams === undefined ? isDebug() : options.logParams; + super(em.connection, { hydrator: (rows) => em.hydrate(entity.name, rows as any), beforeExecute: (compiled) => { if (!options?.silent) { $console.debug( `[Mutation]\n${compiled.sql}\n`, - options?.logParams ? compiled.parameters : undefined, + logParams ? compiled.parameters : undefined, ); } }, diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index be8bc345..978573ab 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -19,6 +19,10 @@ import { EntityTable2 } from "ui/modules/data/components/EntityTable2"; import { useEntityForm } from "ui/modules/data/hooks/useEntityForm"; export function DataEntityUpdate({ params }) { + return ; +} + +function DataEntityUpdateImpl({ params }) { const { $data, relations } = useBkndData(); const entity = $data.entity(params.entity as string); if (!entity) { @@ -240,7 +244,12 @@ function EntityDetailRelations({ })} />
- +
); @@ -257,6 +266,7 @@ function EntityDetailInner({ }) { const other = relation.other(entity); const [navigate] = useNavigate(); + const [search, setSearch] = useState({ select: other.entity.getSelect(undefined, "table"), sort: other.entity.getDefaultSort(), diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index dfb3edef..7b23feed 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -26,6 +26,10 @@ const searchSchema = s.partialObject({ const PER_PAGE_OPTIONS = [5, 10, 25, 50, 100]; export function DataEntityList({ params }) { + return ; +} + +function DataEntityListImpl({ params }) { const { $data } = useBkndData(); const entity = $data.entity(params.entity as string); if (!entity) { From 3338804c3457fbc218842b430ba5f64f8e4b4749 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 14 Jun 2025 16:58:38 +0200 Subject: [PATCH 17/39] simplify and export generic sqlite functions --- .../bun/connection/BunSqliteConnection.ts | 54 ++++++-------- .../node/connection/NodeSqliteConnection.ts | 73 ++++++++----------- app/src/data/connection/index.ts | 1 - .../sqlite/GenericSqliteConnection.ts | 20 ++++- app/src/data/index.ts | 7 ++ 5 files changed, 81 insertions(+), 74 deletions(-) diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index f884249b..f4f3f1fd 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -1,9 +1,7 @@ import { Database } from "bun:sqlite"; import { - buildQueryFn, - GenericSqliteConnection, - parseBigInt, - type IGenericSqlite, + genericSqlite, + type GenericSqliteConnection, } from "data/connection/sqlite/GenericSqliteConnection"; export type BunSqliteConnection = GenericSqliteConnection; @@ -11,39 +9,35 @@ export type BunSqliteConnectionConfig = { database: Database; }; -function bunSqliteExecutor(db: Database, cache: boolean): IGenericSqlite { - const fn = cache ? "query" : "prepare"; - const getStmt = (sql: string) => db[fn](sql); - - return { - db, - query: buildQueryFn({ - all: (sql, parameters) => getStmt(sql).all(...(parameters || [])), - run: (sql, parameters) => { - const { changes, lastInsertRowid } = getStmt(sql).run(...(parameters || [])); - return { - insertId: parseBigInt(lastInsertRowid), - numAffectedRows: parseBigInt(changes), - }; - }, - }), - close: () => db.close(), - }; -} - export function bunSqlite(config?: BunSqliteConnectionConfig | { url: string }) { - let database: Database; + let db: Database; if (config) { if ("database" in config) { - database = config.database; + db = config.database; } else { - database = new Database(config.url); + db = new Database(config.url); } } else { - database = new Database(":memory:"); + db = new Database(":memory:"); } - return new GenericSqliteConnection(database, () => bunSqliteExecutor(database, false), { - name: "bun-sqlite", + return genericSqlite("bun-sqlite", db, (utils) => { + //const fn = cache ? "query" : "prepare"; + const getStmt = (sql: string) => db.prepare(sql); + + return { + db, + query: utils.buildQueryFn({ + all: (sql, parameters) => getStmt(sql).all(...(parameters || [])), + run: (sql, parameters) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...(parameters || [])); + return { + insertId: utils.parseBigInt(lastInsertRowid), + numAffectedRows: utils.parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + }; }); } diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index 8fdc6306..60ad9dfe 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -1,57 +1,48 @@ -import { - buildQueryFn, - GenericSqliteConnection, - parseBigInt, - type IGenericSqlite, -} from "../../../data/connection/sqlite/GenericSqliteConnection"; +import { genericSqlite } from "data/connection/sqlite/GenericSqliteConnection"; import { DatabaseSync } from "node:sqlite"; export type NodeSqliteConnectionConfig = { database: DatabaseSync; }; -function nodeSqliteExecutor(db: DatabaseSync): IGenericSqlite { - const getStmt = (sql: string) => { - const stmt = db.prepare(sql); - //stmt.setReadBigInts(true); - return stmt; - }; - - return { - db, - query: buildQueryFn({ - all: (sql, parameters = []) => getStmt(sql).all(...parameters), - run: (sql, parameters = []) => { - const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); - return { - insertId: parseBigInt(lastInsertRowid), - numAffectedRows: parseBigInt(changes), - }; - }, - }), - close: () => db.close(), - iterator: (isSelect, sql, parameters = []) => { - if (!isSelect) { - throw new Error("Only support select in stream()"); - } - return getStmt(sql).iterate(...parameters) as any; - }, - }; -} - export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }) { - let database: DatabaseSync; + let db: DatabaseSync; if (config) { if ("database" in config) { - database = config.database; + db = config.database; } else { - database = new DatabaseSync(config.url); + db = new DatabaseSync(config.url); } } else { - database = new DatabaseSync(":memory:"); + db = new DatabaseSync(":memory:"); } - return new GenericSqliteConnection(database, () => nodeSqliteExecutor(database), { - name: "node-sqlite", + return genericSqlite("node-sqlite", db, (utils) => { + const getStmt = (sql: string) => { + const stmt = db.prepare(sql); + //stmt.setReadBigInts(true); + return stmt; + }; + + return { + db, + query: utils.buildQueryFn({ + all: (sql, parameters = []) => getStmt(sql).all(...parameters), + run: (sql, parameters = []) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); + return { + insertId: utils.parseBigInt(lastInsertRowid), + numAffectedRows: utils.parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + iterator: (isSelect, sql, parameters = []) => { + if (!isSelect) { + throw new Error("Only support select in stream()"); + } + return getStmt(sql).iterate(...parameters) as any; + }, + }; }); } diff --git a/app/src/data/connection/index.ts b/app/src/data/connection/index.ts index 9bd285ed..a55d1351 100644 --- a/app/src/data/connection/index.ts +++ b/app/src/data/connection/index.ts @@ -11,7 +11,6 @@ export { } from "./Connection"; // sqlite -//export { libsql, LibsqlConnection, type LibSqlCredentials } from "./sqlite/LibsqlConnection"; export { SqliteConnection } from "./sqlite/SqliteConnection"; export { SqliteIntrospector } from "./sqlite/SqliteIntrospector"; export { SqliteLocalConnection } from "./sqlite/SqliteLocalConnection"; diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts index d9abc5f8..5bd45438 100644 --- a/app/src/data/connection/sqlite/GenericSqliteConnection.ts +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -10,6 +10,7 @@ import { import { SqliteConnection } from "./SqliteConnection"; import type { Features } from "../Connection"; +export type { IGenericSqlite }; export type GenericSqliteConnectionConfig = { name: string; additionalPlugins?: KyselyPlugin[]; @@ -18,8 +19,6 @@ export type GenericSqliteConnectionConfig = { supports?: Partial; }; -export { parseBigInt, buildQueryFn, GenericSqliteDialect, type IGenericSqlite }; - export class GenericSqliteConnection extends SqliteConnection { override name = "generic-sqlite"; @@ -47,3 +46,20 @@ export class GenericSqliteConnection extends SqliteConnection } } } + +export function genericSqlite( + name: string, + db: DB, + executor: (utils: typeof genericSqliteUtils) => Promisable>, + config?: GenericSqliteConnectionConfig, +) { + return new GenericSqliteConnection(db, () => executor(genericSqliteUtils), { + name, + ...config, + }); +} + +export const genericSqliteUtils = { + parseBigInt, + buildQueryFn, +}; diff --git a/app/src/data/index.ts b/app/src/data/index.ts index 5069fcec..9fb5a35d 100644 --- a/app/src/data/index.ts +++ b/app/src/data/index.ts @@ -29,3 +29,10 @@ export { MutatorEvents, RepositoryEvents }; export * as DataPermissions from "./permissions"; export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; + +export { libsql } from "./connection/sqlite/LibsqlConnection"; +export { + genericSqlite, + genericSqliteUtils, + type GenericSqliteConnection, +} from "./connection/sqlite/GenericSqliteConnection"; From b87696a0dbb6a3e2b55fb7db67585f337977ea0a Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 14 Jun 2025 16:59:03 +0200 Subject: [PATCH 18/39] init app resources --- app/src/App.ts | 5 + app/src/adapter/cloudflare/drivers/cache.ts | 45 +++++++ .../cloudflare/drivers/cache.vitest.ts | 34 +++++ .../drivers/cache/cache-driver-test-suite.ts | 72 ++++++++++ app/src/core/drivers/cache/in-memory.spec.ts | 52 ++++++++ app/src/core/drivers/cache/in-memory.ts | 123 ++++++++++++++++++ app/src/core/drivers/cache/index.ts | 32 +++++ app/src/core/drivers/email/index.ts | 13 ++ app/src/core/drivers/email/mailchannels.ts | 116 +++++++++++++++++ app/src/core/drivers/email/resend.ts | 72 ++++++++++ app/src/core/drivers/email/ses.ts | 89 +++++++++++++ app/src/core/drivers/index.ts | 5 + bun.lock | 2 +- 13 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 app/src/adapter/cloudflare/drivers/cache.ts create mode 100644 app/src/adapter/cloudflare/drivers/cache.vitest.ts create mode 100644 app/src/core/drivers/cache/cache-driver-test-suite.ts create mode 100644 app/src/core/drivers/cache/in-memory.spec.ts create mode 100644 app/src/core/drivers/cache/in-memory.ts create mode 100644 app/src/core/drivers/cache/index.ts create mode 100644 app/src/core/drivers/email/index.ts create mode 100644 app/src/core/drivers/email/mailchannels.ts create mode 100644 app/src/core/drivers/email/resend.ts create mode 100644 app/src/core/drivers/email/ses.ts create mode 100644 app/src/core/drivers/index.ts diff --git a/app/src/App.ts b/app/src/App.ts index b1f475fa..c12cdf30 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -17,6 +17,7 @@ import { AdminController, type AdminControllerOptions } from "modules/server/Adm import { SystemController } from "modules/server/SystemController"; import type { MaybePromise } from "core/types"; import type { ServerEnv } from "modules/Controller"; +import type { IEmailDriver, ICacheDriver } from "core/drivers"; // biome-ignore format: must be here import { Api, type ApiOptions } from "Api"; @@ -61,6 +62,10 @@ export type AppOptions = { seed?: (ctx: ModuleBuildContext & { app: App }) => Promise; manager?: Omit; asyncEventsMode?: "sync" | "async" | "none"; + drivers?: { + email?: IEmailDriver; + cache?: ICacheDriver; + }; }; export type CreateAppConfig = { connection?: Connection | { url: string }; diff --git a/app/src/adapter/cloudflare/drivers/cache.ts b/app/src/adapter/cloudflare/drivers/cache.ts new file mode 100644 index 00000000..329d407d --- /dev/null +++ b/app/src/adapter/cloudflare/drivers/cache.ts @@ -0,0 +1,45 @@ +import type { ICacheDriver } from "core/drivers"; + +interface WorkersKVCacheOptions { + // default time-to-live in seconds + defaultTTL?: number; + // prefix for the cache key + cachePrefix?: string; +} + +export class WorkersKVCacheDriver implements ICacheDriver { + protected readonly kv: KVNamespace; + protected readonly defaultTTL?: number; + protected readonly cachePrefix: string; + + constructor(kv: KVNamespace, options: WorkersKVCacheOptions = {}) { + this.kv = kv; + this.cachePrefix = options.cachePrefix ?? ""; + this.defaultTTL = options.defaultTTL; + } + + protected getKey(key: string): string { + return this.cachePrefix + key; + } + + async get(key: string): Promise { + const value = await this.kv.get(this.getKey(key)); + return value === null ? undefined : value; + } + + async set(key: string, value: string, ttl?: number): Promise { + let expirationTtl = ttl ?? this.defaultTTL; + if (expirationTtl) { + expirationTtl = Math.max(expirationTtl, 60); + } + await this.kv.put(this.getKey(key), value, { expirationTtl: expirationTtl }); + } + + async del(key: string): Promise { + await this.kv.delete(this.getKey(key)); + } +} + +export const cacheWorkersKV = (kv: KVNamespace, options?: WorkersKVCacheOptions) => { + return new WorkersKVCacheDriver(kv, options); +}; diff --git a/app/src/adapter/cloudflare/drivers/cache.vitest.ts b/app/src/adapter/cloudflare/drivers/cache.vitest.ts new file mode 100644 index 00000000..d9a856df --- /dev/null +++ b/app/src/adapter/cloudflare/drivers/cache.vitest.ts @@ -0,0 +1,34 @@ +import { describe, vi, afterAll, beforeAll } from "vitest"; +import { cacheWorkersKV } from "./cache"; +import { viTestRunner } from "adapter/node/vitest"; +import { cacheDriverTestSuite } from "core/drivers/cache/cache-driver-test-suite"; +import { Miniflare } from "miniflare"; + +describe("cacheWorkersKV", async () => { + beforeAll(() => { + vi.useFakeTimers(); + }); + afterAll(() => { + vi.restoreAllMocks(); + }); + + const mf = new Miniflare({ + modules: true, + script: "export default { async fetch() { return new Response(null); } }", + kvNamespaces: ["KV"], + }); + + const kv = (await mf.getKVNamespace("KV")) as unknown as KVNamespace; + + cacheDriverTestSuite(viTestRunner, { + makeCache: () => cacheWorkersKV(kv), + setTime: (ms: number) => { + vi.advanceTimersByTime(ms); + }, + options: { + minTTL: 60, + // doesn't work with miniflare + skipTTL: true, + }, + }); +}); diff --git a/app/src/core/drivers/cache/cache-driver-test-suite.ts b/app/src/core/drivers/cache/cache-driver-test-suite.ts new file mode 100644 index 00000000..ce38d56f --- /dev/null +++ b/app/src/core/drivers/cache/cache-driver-test-suite.ts @@ -0,0 +1,72 @@ +import type { TestRunner } from "core/test"; +import type { ICacheDriver } from "./index"; + +export function cacheDriverTestSuite( + testRunner: TestRunner, + { + makeCache, + setTime, + options, + }: { + makeCache: () => ICacheDriver; + setTime: (ms: number) => void; + options?: { + minTTL?: number; + skipTTL?: boolean; + }; + }, +) { + const { test, expect } = testRunner; + const minTTL = options?.minTTL ?? 0; + + test("get within ttl", async () => { + const cache = makeCache(); + await cache.set("ttl", "bar", minTTL + 2); // 2 second TTL + setTime(minTTL * 1000 + 1000); // advance by 1 second + expect(await cache.get("ttl")).toBe("bar"); + }); + + test("set and get returns value", async () => { + const cache = makeCache(); + await cache.set("value", "bar"); + expect(await cache.get("value")).toBe("bar"); + }); + + test("get returns undefined for missing key", async () => { + const cache = makeCache(); + expect(await cache.get("missing" + Math.random())).toBeUndefined(); + }); + + test("delete removes value", async () => { + const cache = makeCache(); + await cache.set("delete", "bar"); + await cache.del("delete"); + expect(await cache.get("delete")).toBeUndefined(); + }); + + test("set overwrites value", async () => { + const cache = makeCache(); + await cache.set("overwrite", "bar"); + await cache.set("overwrite", "baz"); + expect(await cache.get("overwrite")).toBe("baz"); + }); + + test("set with ttl expires", async () => { + const cache = makeCache(); + await cache.set("expire", "bar", minTTL + 1); // 1 second TTL + expect(await cache.get("expire")).toBe("bar"); + // advance time + setTime(minTTL * 1000 * 2000); + if (options?.skipTTL) { + await cache.del("expire"); + } + expect(await cache.get("expire")).toBeUndefined(); + }); + test("set without ttl does not expire", async () => { + const cache = makeCache(); + await cache.set("ttl0", "bar"); + expect(await cache.get("ttl0")).toBe("bar"); + setTime(1000); + expect(await cache.get("ttl0")).toBe("bar"); + }); +} diff --git a/app/src/core/drivers/cache/in-memory.spec.ts b/app/src/core/drivers/cache/in-memory.spec.ts new file mode 100644 index 00000000..af1486e9 --- /dev/null +++ b/app/src/core/drivers/cache/in-memory.spec.ts @@ -0,0 +1,52 @@ +import { cacheDriverTestSuite } from "./cache-driver-test-suite"; +import { cacheMemory } from "./in-memory"; +import { bunTestRunner } from "adapter/bun/test"; +import { setSystemTime, afterAll, beforeAll, test, expect, describe } from "bun:test"; + +let baseTime = Date.now(); + +beforeAll(() => { + baseTime = Date.now(); + setSystemTime(new Date(baseTime)); +}); + +afterAll(() => { + setSystemTime(); // Reset to real time +}); + +describe("InMemoryCacheDriver", () => { + cacheDriverTestSuite(bunTestRunner, { + makeCache: () => cacheMemory(), + setTime: (ms: number) => { + setSystemTime(new Date(baseTime + ms)); + }, + }); + + test("evicts least recently used entries by byte size", async () => { + // maxSize = 20 bytes for this test + const cache = cacheMemory({ maxSize: 20 }); + // each key and value is 1 char = 1 byte (ASCII) + // totals to 2 bytes each + await cache.set("a", "1"); + await cache.set("b", "2"); + await cache.set("c", "3"); + await cache.set("d", "4"); + await cache.set("e", "5"); + // total: 10 bytes + // now add a large value to force eviction + await cache.set("big", "1234567890"); + // should evict least recently used entries until it fits + // only "big" and possibly one other small entry should remain + expect(await cache.get("big")).toBe("1234567890"); + // the oldest keys should be evicted + expect(await cache.get("a")).toBeUndefined(); + expect(await cache.get("b")).toBeUndefined(); + // the most recent small keys may or may not remain depending on eviction order + }); + + test("throws if entry is too large to ever fit", async () => { + const cache = cacheMemory({ maxSize: 5 }); + // key: 3, value: 10 = 13 bytes + expect(cache.set("big", "1234567890")).rejects.toThrow(); + }); +}); diff --git a/app/src/core/drivers/cache/in-memory.ts b/app/src/core/drivers/cache/in-memory.ts new file mode 100644 index 00000000..45aaece5 --- /dev/null +++ b/app/src/core/drivers/cache/in-memory.ts @@ -0,0 +1,123 @@ +import type { ICacheDriver } from "./index"; + +interface InMemoryCacheOptions { + // maximum total size in bytes for all keys and values + maxSize?: number; + // default time-to-live in seconds + defaultTTL?: number; +} + +interface CacheEntry { + value: string; + // timestamp in ms, or null for no expiry + expiresAt: number | null; + // size in bytes of this entry (key + value) + size: number; +} + +function byteLength(str: string): number { + return new TextEncoder().encode(str).length; +} + +export class InMemoryCacheDriver implements ICacheDriver { + protected cache: Map; + protected maxSize: number; + protected defaultTTL: number; + protected currentSize: number; + + constructor(options: InMemoryCacheOptions = {}) { + this.maxSize = options.maxSize ?? 1024 * 1024 * 10; // 10MB default + this.defaultTTL = options.defaultTTL ?? 60 * 60; // 1 hour default + this.cache = new Map(); + this.currentSize = 0; + } + + protected now(): number { + return Date.now(); + } + + protected isExpired(entry: CacheEntry): boolean { + return entry.expiresAt !== null && entry.expiresAt <= this.now(); + } + + protected setEntry(key: string, entry: CacheEntry) { + const oldEntry = this.cache.get(key); + const oldSize = oldEntry ? oldEntry.size : 0; + let projectedSize = this.currentSize - oldSize + entry.size; + + // if the entry itself is too large, throw + if (entry.size > this.maxSize) { + throw new Error( + `InMemoryCacheDriver: entry too large (entry: ${entry.size}, max: ${this.maxSize})`, + ); + } + + // evict LRU until it fits + while (projectedSize > this.maxSize && this.cache.size > 0) { + // remove least recently used (first inserted) + const lruKey = this.cache.keys().next().value; + if (typeof lruKey === "string") { + const lruEntry = this.cache.get(lruKey); + if (lruEntry) { + this.currentSize -= lruEntry.size; + } + this.cache.delete(lruKey); + projectedSize = this.currentSize - oldSize + entry.size; + } else { + break; + } + } + + if (projectedSize > this.maxSize) { + throw new Error( + `InMemoryCacheDriver: maxSize exceeded after eviction (attempted: ${projectedSize}, max: ${this.maxSize})`, + ); + } + + if (oldEntry) { + this.currentSize -= oldSize; + } + this.cache.delete(key); // Remove to update order (for LRU) + this.cache.set(key, entry); + this.currentSize += entry.size; + } + + async get(key: string): Promise { + const entry = this.cache.get(key); + if (!entry) return; + if (this.isExpired(entry)) { + this.cache.delete(key); + this.currentSize -= entry.size; + return; + } + // mark as recently used + this.cache.delete(key); + this.cache.set(key, entry); + return entry.value; + } + + async set(key: string, value: string, ttl?: number): Promise { + const expiresAt = + ttl === undefined + ? this.defaultTTL > 0 + ? this.now() + this.defaultTTL * 1000 + : null + : ttl > 0 + ? this.now() + ttl * 1000 + : null; + const size = byteLength(key) + byteLength(value); + this.setEntry(key, { value, expiresAt, size }); + } + + async del(key: string): Promise { + const entry = this.cache.get(key); + if (entry) { + this.currentSize -= entry.size; + this.cache.delete(key); + } + } +} + +export const cacheMemory = (options?: InMemoryCacheOptions) => { + return new InMemoryCacheDriver(options); +}; diff --git a/app/src/core/drivers/cache/index.ts b/app/src/core/drivers/cache/index.ts new file mode 100644 index 00000000..51c104d4 --- /dev/null +++ b/app/src/core/drivers/cache/index.ts @@ -0,0 +1,32 @@ +/** + * Interface for cache driver implementations + * Defines standard methods for interacting with a cache storage system + */ +export interface ICacheDriver { + /** + * Retrieves a value from the cache by its key + * + * @param key unique identifier for the cached value + * @returns resolves to the cached string value or undefined if not found + */ + get(key: string): Promise; + + /** + * Stores a value in the cache with an optional time-to-live + * + * @param key unique identifier for storing the value + * @param value string value to cache + * @param ttl optional time-to-live in seconds before the value expires + * @throws if the value cannot be stored + */ + set(key: string, value: string, ttl?: number): Promise; + + /** + * Removes a value from the cache + * + * @param key unique identifier of the value to delete + */ + del(key: string): Promise; +} + +export { cacheDriverTestSuite } from "./cache-driver-test-suite"; diff --git a/app/src/core/drivers/email/index.ts b/app/src/core/drivers/email/index.ts new file mode 100644 index 00000000..646942cd --- /dev/null +++ b/app/src/core/drivers/email/index.ts @@ -0,0 +1,13 @@ +export type TEmailResponse = { + success: boolean; + data?: Data; +}; + +export interface IEmailDriver { + send( + to: string, + subject: string, + body: string | { text: string; html: string }, + options?: Options, + ): Promise>; +} diff --git a/app/src/core/drivers/email/mailchannels.ts b/app/src/core/drivers/email/mailchannels.ts new file mode 100644 index 00000000..1df79680 --- /dev/null +++ b/app/src/core/drivers/email/mailchannels.ts @@ -0,0 +1,116 @@ +import { mergeObject, type RecursivePartial } from "core/utils"; +import type { IEmailDriver } from "./index"; + +export type MailchannelsEmailOptions = { + apiKey: string; + host?: string; + from?: { email: string; name: string }; +}; + +export type Recipient = { + email: string; + name?: string; +}; + +export type MailchannelsSendOptions = RecursivePartial<{ + attachments: Array<{ + content: string; + filename: string; + type: string; + }>; + campaign_id: string; + content: Array<{ + template_type?: string; + type: string; + value: string; + }>; + dkim_domain: string; + dkim_private_key: string; + dkim_selector: string; + from: Recipient; + headers: {}; + personalizations: Array<{ + bcc: Array; + cc: Array; + dkim_domain: string; + dkim_private_key: string; + dkim_selector: string; + dynamic_template_data: {}; + from: Recipient; + headers: {}; + reply_to: Recipient; + subject: string; + to: Array; + }>; + reply_to: Recipient; + subject: string; + tracking_settings: { + click_tracking: { + enable: boolean; + }; + open_tracking: { + enable: boolean; + }; + }; + transactional: boolean; +}>; + +export type MailchannelsEmailResponse = { + request_id: string; + results: Array<{ + index: number; + message_id: string; + reason: string; + status: string; + }>; +}; + +export const mailchannelsEmail = ( + config: MailchannelsEmailOptions, +): IEmailDriver => { + const host = config.host ?? "https://api.mailchannels.net/tx/v1/send"; + const from = config.from ?? { email: "onboarding@mailchannels.net", name: "Mailchannels" }; + return { + send: async ( + to: string, + subject: string, + body: string | { text: string; html: string }, + options?: MailchannelsSendOptions, + ) => { + const payload: MailchannelsSendOptions = mergeObject( + { + from, + subject, + content: + typeof body === "string" + ? [{ type: "text/html", value: body }] + : [ + { type: "text/plain", value: body.text }, + { type: "text/html", value: body.html }, + ], + personalizations: [ + { + to: [{ email: to }], + }, + ], + }, + options, + ); + + const res = await fetch(host, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Api-Key": config.apiKey, + }, + body: JSON.stringify({ ...payload, ...options }), + }); + + if (res.ok) { + const data = (await res.json()) as MailchannelsEmailResponse; + return { success: true, data }; + } + return { success: false }; + }, + }; +}; diff --git a/app/src/core/drivers/email/resend.ts b/app/src/core/drivers/email/resend.ts new file mode 100644 index 00000000..8cd79c9f --- /dev/null +++ b/app/src/core/drivers/email/resend.ts @@ -0,0 +1,72 @@ +import type { IEmailDriver } from "./index"; + +export type ResendEmailOptions = { + apiKey: string; + host?: string; + from?: string; +}; + +export type ResendEmailSendOptions = { + bcc?: string | string[]; + cc?: string | string[]; + reply_to?: string | string[]; + scheduled_at?: string; + headers?: Record; + attachments?: { + content: Buffer | string; + filename: string; + path: string; + content_type: string; + }[]; + tags?: { + name: string; + value: string; + }[]; +}; + +export type ResendEmailResponse = { + id: string; +}; + +export const resendEmail = ( + config: ResendEmailOptions, +): IEmailDriver => { + const host = config.host ?? "https://api.resend.com/emails"; + const from = config.from ?? "Acme "; + return { + send: async ( + to: string, + subject: string, + body: string | { text: string; html: string }, + options?: ResendEmailSendOptions, + ) => { + const payload: any = { + from, + to, + subject, + }; + + if (typeof body === "string") { + payload.html = body; + } else { + payload.html = body.html; + payload.text = body.text; + } + + const res = await fetch(host, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${config.apiKey}`, + }, + body: JSON.stringify({ ...payload, ...options }), + }); + + if (res.ok) { + const data = (await res.json()) as ResendEmailResponse; + return { success: true, data }; + } + return { success: false }; + }, + }; +}; diff --git a/app/src/core/drivers/email/ses.ts b/app/src/core/drivers/email/ses.ts new file mode 100644 index 00000000..8748b663 --- /dev/null +++ b/app/src/core/drivers/email/ses.ts @@ -0,0 +1,89 @@ +import type { IEmailDriver } from "./index"; +import { AwsClient } from "aws4fetch"; + +export type SesEmailOptions = { + region: string; + accessKeyId: string; + secretAccessKey: string; + from: string; +}; + +export type SesSendOptions = { + cc?: string[]; + bcc?: string[]; + replyTo?: string[]; +}; + +export type SesEmailResponse = { + MessageId?: string; + status: number; + body: string; +}; + +export const sesEmail = ( + config: SesEmailOptions, +): IEmailDriver => { + const endpoint = `https://email.${config.region}.amazonaws.com/`; + const from = config.from; + const aws = new AwsClient({ + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + service: "ses", + region: config.region, + }); + return { + send: async ( + to: string, + subject: string, + body: string | { text: string; html: string }, + options?: SesSendOptions, + ) => { + // build SES SendEmail params (x-www-form-urlencoded) + const params: Record = { + Action: "SendEmail", + Version: "2010-12-01", + Source: from, + "Destination.ToAddresses.member.1": to, + "Message.Subject.Data": subject, + }; + if (typeof body === "string") { + params["Message.Body.Html.Data"] = body; + } else { + params["Message.Body.Html.Data"] = body.html; + params["Message.Body.Text.Data"] = body.text; + } + if (options?.cc) { + options.cc.forEach((cc, i) => { + params[`Destination.CcAddresses.member.${i + 1}`] = cc; + }); + } + if (options?.bcc) { + options.bcc.forEach((bcc, i) => { + params[`Destination.BccAddresses.member.${i + 1}`] = bcc; + }); + } + if (options?.replyTo) { + options.replyTo.forEach((reply, i) => { + params[`ReplyToAddresses.member.${i + 1}`] = reply; + }); + } + const formBody = Object.entries(params) + .map(([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v)) + .join("&"); + const res = await aws.fetch(endpoint, { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: formBody, + }); + const text = await res.text(); + // try to extract MessageId from XML response + let MessageId: string | undefined = undefined; + const match = text.match(/([^<]+)<\/MessageId>/); + if (match) MessageId = match[1]; + return { + success: res.ok, + data: { MessageId, status: res.status, body: text }, + }; + }, + }; +}; diff --git a/app/src/core/drivers/index.ts b/app/src/core/drivers/index.ts new file mode 100644 index 00000000..a587df16 --- /dev/null +++ b/app/src/core/drivers/index.ts @@ -0,0 +1,5 @@ +export type { ICacheDriver } from "./cache"; +export { cacheMemory } from "./cache/in-memory"; + +export type { IEmailDriver } from "./email"; +export { resendEmail } from "./email/resend"; diff --git a/bun.lock b/bun.lock index b9929871..7a373522 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.14.0-rc.2", + "version": "0.15.0-rc.2", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", From a9f367aa2c42b08dd3bbccd02c476cab4385df1c Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 17 Jun 2025 13:18:13 +0200 Subject: [PATCH 19/39] updated build to exclude libsql --- app/build.ts | 1 + app/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.ts b/app/build.ts index bce7e50e..49bdf95d 100644 --- a/app/build.ts +++ b/app/build.ts @@ -229,6 +229,7 @@ function baseConfig(adapter: string, overrides: Partial = {}): tsu /^@?(hono|libsql).*?/, /^(bknd|react|next|node).*?/, /.*\.(html)$/, + ...external, ...(Array.isArray(overrides.external) ? overrides.external : []), ], }; diff --git a/app/package.json b/app/package.json index 749208e8..0fc88193 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.2", + "version": "0.15.0-rc.3", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { From f5ceffd80e632b815648bcf91ae978bd8bf0afe3 Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 17 Jun 2025 13:34:31 +0200 Subject: [PATCH 20/39] fix tests --- app/src/core/drivers/cache/cache-driver-test-suite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/core/drivers/cache/cache-driver-test-suite.ts b/app/src/core/drivers/cache/cache-driver-test-suite.ts index ce38d56f..dcaa9c38 100644 --- a/app/src/core/drivers/cache/cache-driver-test-suite.ts +++ b/app/src/core/drivers/cache/cache-driver-test-suite.ts @@ -17,7 +17,7 @@ export function cacheDriverTestSuite( }, ) { const { test, expect } = testRunner; - const minTTL = options?.minTTL ?? 0; + const minTTL = options?.minTTL ?? 1; test("get within ttl", async () => { const cache = makeCache(); From aaa97ed113a4cc561435ae10e25fd0f112d1602a Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 17 Jun 2025 19:51:12 +0200 Subject: [PATCH 21/39] finalize initial app resources/drivers --- app/__test__/App.spec.ts | 38 +++++++++ app/src/App.ts | 12 ++- app/src/core/drivers/cache/in-memory.spec.ts | 8 +- app/src/core/drivers/cache/in-memory.ts | 2 +- app/src/core/drivers/email/index.ts | 27 +++++-- .../core/drivers/email/mailchannels.spec.ts | 20 +++++ app/src/core/drivers/email/mailchannels.ts | 13 ++-- app/src/core/drivers/email/resend.spec.ts | 21 +++++ app/src/core/drivers/email/resend.ts | 8 +- app/src/core/drivers/email/ses.ts | 78 ++++++++++--------- app/src/core/drivers/index.ts | 4 +- app/src/core/index.ts | 1 + 12 files changed, 170 insertions(+), 62 deletions(-) create mode 100644 app/src/core/drivers/email/mailchannels.spec.ts create mode 100644 app/src/core/drivers/email/resend.spec.ts diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 8b6b6722..0b456e14 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -104,4 +104,42 @@ describe("App tests", async () => { expect(app.plugins.size).toBe(1); expect(Array.from(app.plugins.keys())).toEqual(["test"]); }); + + test.only("drivers", async () => { + const called: string[] = []; + const app = new App(dummyConnection, undefined, { + drivers: { + email: { + send: async (to, subject, body) => { + called.push("email.send"); + return { + id: "", + }; + }, + }, + cache: { + get: async (key) => { + called.push("cache.get"); + return ""; + }, + set: async (key, value, ttl) => { + called.push("cache.set"); + }, + del: async (key) => { + called.push("cache.del"); + }, + }, + }, + }); + await app.build(); + + expect(app.drivers.cache).toBeDefined(); + expect(app.drivers.email).toBeDefined(); + await app.drivers.email.send("", "", ""); + await app.drivers.cache.get(""); + await app.drivers.cache.set("", "", 0); + await app.drivers.cache.del(""); + + expect(called).toEqual(["email.send", "cache.get", "cache.set", "cache.del"]); + }); }); diff --git a/app/src/App.ts b/app/src/App.ts index c12cdf30..223711ad 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -76,24 +76,30 @@ export type CreateAppConfig = { export type AppConfig = InitialModuleConfigs; export type LocalApiOptions = Request | ApiOptions; -export class App { +export class App { static readonly Events = AppEvents; modules: ModuleManager; adminController?: AdminController; _id: string = crypto.randomUUID(); plugins: Map = new Map(); + drivers: Options["drivers"] = {}; private trigger_first_boot = false; private _building: boolean = false; constructor( - public connection: Connection, + public connection: C, _initialConfig?: InitialModuleConfigs, - private options?: AppOptions, + private options?: Options, ) { + this.drivers = options?.drivers ?? {}; + for (const plugin of options?.plugins ?? []) { const config = plugin(this); + if (this.plugins.has(config.name)) { + throw new Error(`Plugin ${config.name} already registered`); + } this.plugins.set(config.name, config); } this.runPlugins("onBoot"); diff --git a/app/src/core/drivers/cache/in-memory.spec.ts b/app/src/core/drivers/cache/in-memory.spec.ts index af1486e9..3bde9a8d 100644 --- a/app/src/core/drivers/cache/in-memory.spec.ts +++ b/app/src/core/drivers/cache/in-memory.spec.ts @@ -1,5 +1,5 @@ import { cacheDriverTestSuite } from "./cache-driver-test-suite"; -import { cacheMemory } from "./in-memory"; +import { memoryCache } from "./in-memory"; import { bunTestRunner } from "adapter/bun/test"; import { setSystemTime, afterAll, beforeAll, test, expect, describe } from "bun:test"; @@ -16,7 +16,7 @@ afterAll(() => { describe("InMemoryCacheDriver", () => { cacheDriverTestSuite(bunTestRunner, { - makeCache: () => cacheMemory(), + makeCache: () => memoryCache(), setTime: (ms: number) => { setSystemTime(new Date(baseTime + ms)); }, @@ -24,7 +24,7 @@ describe("InMemoryCacheDriver", () => { test("evicts least recently used entries by byte size", async () => { // maxSize = 20 bytes for this test - const cache = cacheMemory({ maxSize: 20 }); + const cache = memoryCache({ maxSize: 20 }); // each key and value is 1 char = 1 byte (ASCII) // totals to 2 bytes each await cache.set("a", "1"); @@ -45,7 +45,7 @@ describe("InMemoryCacheDriver", () => { }); test("throws if entry is too large to ever fit", async () => { - const cache = cacheMemory({ maxSize: 5 }); + const cache = memoryCache({ maxSize: 5 }); // key: 3, value: 10 = 13 bytes expect(cache.set("big", "1234567890")).rejects.toThrow(); }); diff --git a/app/src/core/drivers/cache/in-memory.ts b/app/src/core/drivers/cache/in-memory.ts index 45aaece5..9ff7ef74 100644 --- a/app/src/core/drivers/cache/in-memory.ts +++ b/app/src/core/drivers/cache/in-memory.ts @@ -118,6 +118,6 @@ export class InMemoryCacheDriver implements ICacheDriver { } } -export const cacheMemory = (options?: InMemoryCacheOptions) => { +export const memoryCache = (options?: InMemoryCacheOptions) => { return new InMemoryCacheDriver(options); }; diff --git a/app/src/core/drivers/email/index.ts b/app/src/core/drivers/email/index.ts index 646942cd..494276df 100644 --- a/app/src/core/drivers/email/index.ts +++ b/app/src/core/drivers/email/index.ts @@ -1,13 +1,28 @@ -export type TEmailResponse = { - success: boolean; - data?: Data; -}; - export interface IEmailDriver { send( to: string, subject: string, body: string | { text: string; html: string }, options?: Options, - ): Promise>; + ): Promise; } + +import type { BkndConfig } from "bknd"; +import { resendEmail, memoryCache } from "bknd/core"; + +export default { + onBuilt: async (app) => { + app.server.get("/send-email", async (c) => { + if (await app.drivers?.email?.send("test@test.com", "Test", "Test")) { + return c.text("success"); + } + return c.text("failed"); + }); + }, + options: { + drivers: { + email: resendEmail({ apiKey: "..." }), + cache: memoryCache(), + }, + }, +} as const satisfies BkndConfig; diff --git a/app/src/core/drivers/email/mailchannels.spec.ts b/app/src/core/drivers/email/mailchannels.spec.ts new file mode 100644 index 00000000..5281856b --- /dev/null +++ b/app/src/core/drivers/email/mailchannels.spec.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "bun:test"; +import { mailchannelsEmail } from "./mailchannels"; + +const ALL_TESTS = !!process.env.ALL_TESTS; + +describe.skipIf(ALL_TESTS)("mailchannels", () => { + it("should throw on failed", async () => { + const driver = mailchannelsEmail({ apiKey: "invalid" } as any); + expect(driver.send("foo@bar.com", "Test", "Test")).rejects.toThrow(); + }); + + it("should send an email", async () => { + const driver = mailchannelsEmail({ + apiKey: process.env.MAILCHANNELS_API_KEY!, + from: { email: "accounts@bknd.io", name: "Dennis Senn" }, + }); + const response = await driver.send("ds@bknd.io", "Test", "Test"); + expect(response).toBeDefined(); + }); +}); diff --git a/app/src/core/drivers/email/mailchannels.ts b/app/src/core/drivers/email/mailchannels.ts index 1df79680..7478ef5c 100644 --- a/app/src/core/drivers/email/mailchannels.ts +++ b/app/src/core/drivers/email/mailchannels.ts @@ -94,7 +94,7 @@ export const mailchannelsEmail = ( }, ], }, - options, + options ?? {}, ); const res = await fetch(host, { @@ -103,14 +103,15 @@ export const mailchannelsEmail = ( "Content-Type": "application/json", "X-Api-Key": config.apiKey, }, - body: JSON.stringify({ ...payload, ...options }), + body: JSON.stringify(payload), }); + const data = (await res.json()) as MailchannelsEmailResponse; - if (res.ok) { - const data = (await res.json()) as MailchannelsEmailResponse; - return { success: true, data }; + if (data?.results.length === 0 || data.results?.[0]?.status !== "sent") { + throw new Error(data.results?.[0]?.reason ?? "Unknown error"); } - return { success: false }; + + return (await res.json()) as MailchannelsEmailResponse; }, }; }; diff --git a/app/src/core/drivers/email/resend.spec.ts b/app/src/core/drivers/email/resend.spec.ts new file mode 100644 index 00000000..5c1cfcfd --- /dev/null +++ b/app/src/core/drivers/email/resend.spec.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from "bun:test"; +import { resendEmail } from "./resend"; + +const ALL_TESTS = !!process.env.ALL_TESTS; + +describe.skipIf(ALL_TESTS)("resend", () => { + it.only("should throw on failed", async () => { + const driver = resendEmail({ apiKey: "invalid" } as any); + expect(driver.send("foo@bar.com", "Test", "Test")).rejects.toThrow(); + }); + + it("should send an email", async () => { + const driver = resendEmail({ + apiKey: process.env.RESEND_API_KEY!, + from: "BKND ", + }); + const response = await driver.send("help@bknd.io", "Test", "Test"); + expect(response).toBeDefined(); + expect(response.id).toBeDefined(); + }); +}); diff --git a/app/src/core/drivers/email/resend.ts b/app/src/core/drivers/email/resend.ts index 8cd79c9f..7ac74845 100644 --- a/app/src/core/drivers/email/resend.ts +++ b/app/src/core/drivers/email/resend.ts @@ -62,11 +62,11 @@ export const resendEmail = ( body: JSON.stringify({ ...payload, ...options }), }); - if (res.ok) { - const data = (await res.json()) as ResendEmailResponse; - return { success: true, data }; + if (!res.ok) { + throw new Error(await res.text()); } - return { success: false }; + + return (await res.json()) as ResendEmailResponse; }, }; }; diff --git a/app/src/core/drivers/email/ses.ts b/app/src/core/drivers/email/ses.ts index 8748b663..1de194ef 100644 --- a/app/src/core/drivers/email/ses.ts +++ b/app/src/core/drivers/email/ses.ts @@ -15,7 +15,7 @@ export type SesSendOptions = { }; export type SesEmailResponse = { - MessageId?: string; + MessageId: string; status: number; body: string; }; @@ -23,7 +23,7 @@ export type SesEmailResponse = { export const sesEmail = ( config: SesEmailOptions, ): IEmailDriver => { - const endpoint = `https://email.${config.region}.amazonaws.com/`; + const endpoint = `https://email.${config.region}.amazonaws.com/v2/email/outbound-emails`; const from = config.from; const aws = new AwsClient({ accessKeyId: config.accessKeyId, @@ -38,52 +38,56 @@ export const sesEmail = ( body: string | { text: string; html: string }, options?: SesSendOptions, ) => { - // build SES SendEmail params (x-www-form-urlencoded) - const params: Record = { - Action: "SendEmail", - Version: "2010-12-01", - Source: from, - "Destination.ToAddresses.member.1": to, - "Message.Subject.Data": subject, + // SES v2 SendEmail JSON payload + const payload: any = { + FromEmailAddress: from, + Destination: { + ToAddresses: [to], + }, + Content: { + Simple: { + Subject: { Data: subject, Charset: "UTF-8" }, + Body: {}, + }, + }, }; if (typeof body === "string") { - params["Message.Body.Html.Data"] = body; + payload.Content.Simple.Body.Html = { Data: body, Charset: "UTF-8" }; } else { - params["Message.Body.Html.Data"] = body.html; - params["Message.Body.Text.Data"] = body.text; + if (body.html) payload.Content.Simple.Body.Html = { Data: body.html, Charset: "UTF-8" }; + if (body.text) payload.Content.Simple.Body.Text = { Data: body.text, Charset: "UTF-8" }; } - if (options?.cc) { - options.cc.forEach((cc, i) => { - params[`Destination.CcAddresses.member.${i + 1}`] = cc; - }); + if (options?.cc && options.cc.length > 0) { + payload.Destination.CcAddresses = options.cc; } - if (options?.bcc) { - options.bcc.forEach((bcc, i) => { - params[`Destination.BccAddresses.member.${i + 1}`] = bcc; - }); + if (options?.bcc && options.bcc.length > 0) { + payload.Destination.BccAddresses = options.bcc; } - if (options?.replyTo) { - options.replyTo.forEach((reply, i) => { - params[`ReplyToAddresses.member.${i + 1}`] = reply; - }); + if (options?.replyTo && options.replyTo.length > 0) { + payload.ReplyToAddresses = options.replyTo; } - const formBody = Object.entries(params) - .map(([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v)) - .join("&"); const res = await aws.fetch(endpoint, { method: "POST", - headers: { "content-type": "application/x-www-form-urlencoded" }, - body: formBody, + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), }); const text = await res.text(); - // try to extract MessageId from XML response - let MessageId: string | undefined = undefined; - const match = text.match(/([^<]+)<\/MessageId>/); - if (match) MessageId = match[1]; - return { - success: res.ok, - data: { MessageId, status: res.status, body: text }, - }; + if (!res.ok) { + // SES v2 returns JSON error body + let errorMsg = text; + try { + const err = JSON.parse(text); + errorMsg = err.message || err.Message || text; + } catch {} + throw new Error(`SES SendEmail failed: ${errorMsg}`); + } + // parse MessageId from JSON response + let MessageId: string = ""; + try { + const data = JSON.parse(text); + MessageId = data.MessageId; + } catch {} + return { MessageId, status: res.status, body: text }; }, }; }; diff --git a/app/src/core/drivers/index.ts b/app/src/core/drivers/index.ts index a587df16..da356b77 100644 --- a/app/src/core/drivers/index.ts +++ b/app/src/core/drivers/index.ts @@ -1,5 +1,7 @@ export type { ICacheDriver } from "./cache"; -export { cacheMemory } from "./cache/in-memory"; +export { memoryCache } from "./cache/in-memory"; export type { IEmailDriver } from "./email"; export { resendEmail } from "./email/resend"; +export { sesEmail } from "./email/ses"; +export { mailchannelsEmail } from "./email/mailchannels"; diff --git a/app/src/core/index.ts b/app/src/core/index.ts index ae33dd65..a0e96e63 100644 --- a/app/src/core/index.ts +++ b/app/src/core/index.ts @@ -37,6 +37,7 @@ export { InvalidSchemaError, } from "./object/schema"; +export * from "./drivers"; export * from "./console"; export * from "./events"; From c86f4c12b7199b4ef6c01d6e337ea84989372caa Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 18 Jun 2025 07:56:58 +0200 Subject: [PATCH 22/39] admin: add options such as logo return path when served static --- app/src/modules/server/AdminController.tsx | 11 +++++++++-- app/src/ui/Admin.tsx | 8 ++++++-- app/src/ui/client/BkndProvider.tsx | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index 6dd39e7f..aa109ee4 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -16,6 +16,8 @@ export type AdminBkndWindowContext = { user?: TApiUser; logout_route: string; admin_basepath: string; + logo_return_path?: string; + theme?: "dark" | "light" | "system"; }; // @todo: add migration to remove admin path from config @@ -26,6 +28,8 @@ export type AdminControllerOptions = { html?: string; forceDev?: boolean | { mainPath: string }; debugRerenders?: boolean; + theme?: "dark" | "light" | "system"; + logoReturnPath?: string; }; export class AdminController extends Controller { @@ -46,6 +50,8 @@ export class AdminController extends Controller { basepath: this._options.basepath ?? "/", adminBasepath: this._options.adminBasepath ?? "", assetsPath: this._options.assetsPath ?? config.server.assets_path, + theme: this._options.theme ?? "system", + logo_return_path: this._options.logoReturnPath ?? "/", }; } @@ -108,10 +114,12 @@ export class AdminController extends Controller { }, }), async (c) => { - const obj = { + const obj: AdminBkndWindowContext = { user: c.get("auth")?.user, logout_route: authRoutes.logout, admin_basepath: this.options.adminBasepath, + theme: this.options.theme, + logo_return_path: this.options.logoReturnPath, }; const html = await this.getHtml(obj); if (!html) { @@ -172,7 +180,6 @@ export class AdminController extends Controller { return this.options.html as string; } - const configs = this.app.modules.configs(); const isProd = !isDebug() && !this.options.forceDev; const mainPath = typeof this.options.forceDev === "object" && "mainPath" in this.options.forceDev diff --git a/app/src/ui/Admin.tsx b/app/src/ui/Admin.tsx index 1c73a589..86b4e8ed 100644 --- a/app/src/ui/Admin.tsx +++ b/app/src/ui/Admin.tsx @@ -5,7 +5,7 @@ import { BkndProvider, type BkndAdminOptions } from "ui/client/bknd"; import { useTheme } from "ui/client/use-theme"; import { Logo } from "ui/components/display/Logo"; import * as AppShell from "ui/layouts/AppShell/AppShell"; -import { ClientProvider, type ClientProviderProps } from "./client"; +import { ClientProvider, useBkndWindowContext, type ClientProviderProps } from "./client"; import { createMantineTheme } from "./lib/mantine/theme"; import { Routes } from "./routes"; @@ -18,7 +18,7 @@ export type BkndAdminProps = { export default function Admin({ baseUrl: baseUrlOverride, withProvider = false, - config, + config: _config = {}, }: BkndAdminProps) { const { theme } = useTheme(); const Provider = ({ children }: any) => @@ -32,6 +32,10 @@ export default function Admin({ ) : ( children ); + const config = { + ..._config, + ...useBkndWindowContext(), + }; const BkndWrapper = ({ children }: { children: ReactNode }) => ( }> diff --git a/app/src/ui/client/BkndProvider.tsx b/app/src/ui/client/BkndProvider.tsx index 15672c89..4e938d18 100644 --- a/app/src/ui/client/BkndProvider.tsx +++ b/app/src/ui/client/BkndProvider.tsx @@ -9,8 +9,8 @@ import { Message } from "ui/components/display/Message"; import { useNavigate } from "ui/lib/routes"; export type BkndAdminOptions = { - logo_return_path?: string; basepath?: string; + logo_return_path?: string; theme?: AppTheme; }; type BkndContext = { From b2086c4da7f36fe1dbc5ff2916429023a87d7fb1 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 18 Jun 2025 10:31:40 +0200 Subject: [PATCH 23/39] fix: plugin schema reconciliation --- app/src/data/entities/Entity.ts | 10 ++++++++++ app/src/data/entities/EntityManager.ts | 6 +++--- app/src/modules/ModuleHelper.ts | 7 ++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/data/entities/Entity.ts b/app/src/data/entities/Entity.ts index cdcbce61..48f23b97 100644 --- a/app/src/data/entities/Entity.ts +++ b/app/src/data/entities/Entity.ts @@ -44,6 +44,8 @@ export type EntityJSON = ReturnType; export const entityTypes = ["regular", "system", "generated"] as const; export type TEntityType = (typeof entityTypes)[number]; +const ENTITY_SYMBOL = Symbol.for("bknd:entity"); + /** * @todo: add check for adding fields (primary and relation not allowed) * @todo: add option to disallow api deletes (or api actions in general) @@ -89,6 +91,14 @@ export class Entity< } if (type) this.type = type; + this[ENTITY_SYMBOL] = true; + } + + // this is currently required as there could be multiple variants + // we need to migrate to a mono repo + static isEntity(e: unknown): e is Entity { + if (!e) return false; + return e[ENTITY_SYMBOL] === true; } static create(args: { diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 0ab634a9..67571708 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -118,12 +118,12 @@ export class EntityManager { ): Silent extends true ? Entity | undefined : Entity { // make sure to always retrieve by name const entity = this.entities.find((entity) => - e instanceof Entity ? entity.name === e.name : entity.name === e, + Entity.isEntity(e) ? entity.name === e.name : entity.name === e, ); if (!entity) { if (silent === true) return undefined as any; - throw new EntityNotDefinedException(e instanceof Entity ? e.name : (e as string)); + throw new EntityNotDefinedException(Entity.isEntity(e) ? e.name : (e as string)); } return entity; @@ -236,7 +236,7 @@ export class EntityManager { } getIndicesOf(_entity: Entity | string): EntityIndex[] { - const entity = _entity instanceof Entity ? _entity : this.entity(_entity); + const entity = Entity.isEntity(_entity) ? _entity : this.entity(_entity); return this.indices.filter((index) => index.entity.name === entity.name); } diff --git a/app/src/modules/ModuleHelper.ts b/app/src/modules/ModuleHelper.ts index 5088510d..8561ebe3 100644 --- a/app/src/modules/ModuleHelper.ts +++ b/app/src/modules/ModuleHelper.ts @@ -66,9 +66,14 @@ export class ModuleHelper { } ensureRelation(relation: EntityRelation) { - if (!this.em.relations.exists(relation)) { + try { + // most reliable way at the moment this.em.addRelation(relation); this.flags.sync_required = true; + } catch (e) {} + + // @todo: improve this function, seems like it still doesn't catch all cases + if (!this.em.relations.exists(relation)) { } } From 57ae2f333c7f3932a3b993526f0a5dde84d8c891 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 25 Jun 2025 09:35:47 +0200 Subject: [PATCH 24/39] inlined libsql dialect, rewrote d1 to use generic sqlite --- app/package.json | 3 +- .../cloudflare/connection/D1Connection.ts | 96 ++++++++---- .../connection/D1Connection.vitest.ts | 54 +++++++ .../cloudflare/connection/D1Dialect.ts | 138 +++++++++++++++++ .../node/connection/NodeSqliteConnection.ts | 59 ++++--- app/src/adapter/sqlite/edge.ts | 2 +- app/src/adapter/sqlite/types.ts | 3 - .../sqlite/GenericSqliteConnection.ts | 45 +++++- .../{ => libsql}/LibsqlConnection.spec.ts | 2 +- .../sqlite/{ => libsql}/LibsqlConnection.ts | 15 +- .../connection/sqlite/libsql/LibsqlDialect.ts | 145 ++++++++++++++++++ app/src/data/entities/query/Repository.ts | 2 +- app/src/data/index.ts | 2 +- app/src/data/server/query.ts | 4 +- app/vite.dev.ts | 2 +- bun.lock | 39 +---- 16 files changed, 491 insertions(+), 120 deletions(-) create mode 100644 app/src/adapter/cloudflare/connection/D1Connection.vitest.ts create mode 100644 app/src/adapter/cloudflare/connection/D1Dialect.ts delete mode 100644 app/src/adapter/sqlite/types.ts rename app/src/data/connection/sqlite/{ => libsql}/LibsqlConnection.spec.ts (86%) rename app/src/data/connection/sqlite/{ => libsql}/LibsqlConnection.ts (76%) create mode 100644 app/src/data/connection/sqlite/libsql/LibsqlDialect.ts diff --git a/app/package.json b/app/package.json index 0fc88193..68320258 100644 --- a/app/package.json +++ b/app/package.json @@ -65,6 +65,7 @@ "json-schema-form-react": "^0.0.2", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", + "jsonv-ts": "^0.1.0", "kysely": "^0.27.6", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", @@ -82,7 +83,6 @@ "@hono/vite-dev-server": "^0.19.1", "@hookform/resolvers": "^4.1.3", "@libsql/client": "^0.15.9", - "@libsql/kysely-libsql": "^0.4.1", "@mantine/modals": "^7.17.1", "@mantine/notifications": "^7.17.1", "@playwright/test": "^1.51.1", @@ -102,7 +102,6 @@ "dotenv": "^16.4.7", "jotai": "^2.12.2", "jsdom": "^26.0.0", - "jsonv-ts": "^0.1.0", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", "libsql-stateless-easy": "^1.8.0", diff --git a/app/src/adapter/cloudflare/connection/D1Connection.ts b/app/src/adapter/cloudflare/connection/D1Connection.ts index d7eb5a07..2f3ccfa4 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.ts @@ -1,42 +1,76 @@ /// -import { SqliteConnection } from "bknd/data"; -import type { ConnQuery, ConnQueryResults } from "data/connection/Connection"; -import { D1Dialect } from "kysely-d1"; +import { + genericSqlite, + type GenericSqliteConnection, +} from "data/connection/sqlite/GenericSqliteConnection"; +import type { QueryResult } from "kysely"; + +export type D1SqliteConnection = GenericSqliteConnection; export type D1ConnectionConfig = { binding: DB; }; -export class D1Connection< - DB extends D1Database | D1DatabaseSession = D1Database, -> extends SqliteConnection { - override name = "sqlite-d1"; +export function d1Sqlite(config: D1ConnectionConfig) { + const db = config.binding; - protected override readonly supported = { - batching: true, - softscans: false, - }; + return genericSqlite( + "d1-sqlite", + db, + (utils) => { + const getStmt = (sql: string, parameters?: any[] | readonly any[]) => + db.prepare(sql).bind(...(parameters || [])); - constructor(private config: D1ConnectionConfig) { - super({ + const mapResult = (res: D1Result): QueryResult => { + if (res.error) { + throw new Error(res.error); + } + + const numAffectedRows = + res.meta.changes > 0 ? utils.parseBigInt(res.meta.changes) : undefined; + const insertId = res.meta.last_row_id + ? utils.parseBigInt(res.meta.last_row_id) + : undefined; + + return { + insertId, + numAffectedRows, + rows: res.results, + // @ts-ignore + meta: res.meta, + }; + }; + + return { + db, + batch: async (stmts) => { + const res = await db.batch( + stmts.map(({ sql, parameters }) => { + return getStmt(sql, parameters); + }), + ); + return res.map(mapResult); + }, + query: utils.buildQueryFn({ + all: async (sql, parameters) => { + const prep = getStmt(sql, parameters); + return mapResult(await prep.all()).rows; + }, + run: async (sql, parameters) => { + const prep = getStmt(sql, parameters); + return mapResult(await prep.run()); + }, + }), + close: () => {}, + }; + }, + { + supports: { + batching: true, + softscans: false, + }, excludeTables: ["_cf_KV", "_cf_METADATA"], - dialect: D1Dialect, - dialectArgs: [{ database: config.binding as D1Database }], - }); - } - - override async executeQueries(...qbs: O): Promise> { - const compiled = this.getCompiled(...qbs); - - const db = this.config.binding; - - const res = await db.batch( - compiled.map(({ sql, parameters }) => { - return db.prepare(sql).bind(...parameters); - }), - ); - - return this.withTransformedRows(res, "results") as any; - } + }, + ); } diff --git a/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts b/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts new file mode 100644 index 00000000..762e0ea2 --- /dev/null +++ b/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts @@ -0,0 +1,54 @@ +import { describe, test, expect } from "vitest"; + +import { viTestRunner } from "adapter/node/vitest"; +import { connectionTestSuite } from "data/connection/connection-test-suite"; +import { Miniflare } from "miniflare"; +import { d1Sqlite } from "./D1Connection"; +import { sql } from "kysely"; + +describe("d1Sqlite", async () => { + const mf = new Miniflare({ + modules: true, + script: "export default { async fetch() { return new Response(null); } }", + d1Databases: ["DB"], + }); + const binding = (await mf.getD1Database("DB")) as D1Database; + + test("connection", async () => { + const conn = d1Sqlite({ binding }); + expect(conn.supports("batching")).toBe(true); + expect(conn.supports("softscans")).toBe(false); + }); + + test("query details", async () => { + const conn = d1Sqlite({ binding }); + + const res = await conn.executeQuery(sql`select 1`.compile(conn.kysely)); + expect(res.rows).toEqual([{ "1": 1 }]); + expect(res.numAffectedRows).toBe(undefined); + expect(res.insertId).toBe(undefined); + // @ts-expect-error + expect(res.meta.changed_db).toBe(false); + // @ts-expect-error + expect(res.meta.rows_read).toBe(0); + + const batchResult = await conn.executeQueries( + sql`select 1`.compile(conn.kysely), + sql`select 2`.compile(conn.kysely), + ); + + // rewrite to get index + for (const [index, result] of batchResult.entries()) { + expect(result.rows).toEqual([{ [String(index + 1)]: index + 1 }]); + expect(result.numAffectedRows).toBe(undefined); + expect(result.insertId).toBe(undefined); + // @ts-expect-error + expect(result.meta.changed_db).toBe(false); + } + }); + + connectionTestSuite(viTestRunner, { + makeConnection: () => d1Sqlite({ binding }), + rawDialectDetails: [], + }); +}); diff --git a/app/src/adapter/cloudflare/connection/D1Dialect.ts b/app/src/adapter/cloudflare/connection/D1Dialect.ts new file mode 100644 index 00000000..e3637f90 --- /dev/null +++ b/app/src/adapter/cloudflare/connection/D1Dialect.ts @@ -0,0 +1,138 @@ +import { + SqliteAdapter, + SqliteIntrospector, + SqliteQueryCompiler, + type CompiledQuery, + type DatabaseConnection, + type DatabaseIntrospector, + type Dialect, + type Driver, + type Kysely, + type QueryCompiler, + type QueryResult, +} from "kysely"; + +/** + * Config for the D1 dialect. Pass your D1 instance to this object that you bound in `wrangler.toml`. + */ +export interface D1DialectConfig { + database: D1Database; +} + +/** + * D1 dialect that adds support for [Cloudflare D1][0] in [Kysely][1]. + * The constructor takes the instance of your D1 database that you bound in `wrangler.toml`. + * + * ```typescript + * new D1Dialect({ + * database: env.DB, + * }) + * ``` + * + * [0]: https://blog.cloudflare.com/introducing-d1/ + * [1]: https://github.com/koskimas/kysely + */ +export class D1Dialect implements Dialect { + #config: D1DialectConfig; + + constructor(config: D1DialectConfig) { + this.#config = config; + } + + createAdapter() { + return new SqliteAdapter(); + } + + createDriver(): Driver { + return new D1Driver(this.#config); + } + + createQueryCompiler(): QueryCompiler { + return new SqliteQueryCompiler(); + } + + createIntrospector(db: Kysely): DatabaseIntrospector { + return new SqliteIntrospector(db); + } +} + +class D1Driver implements Driver { + #config: D1DialectConfig; + + constructor(config: D1DialectConfig) { + this.#config = config; + } + + async init(): Promise {} + + async acquireConnection(): Promise { + return new D1Connection(this.#config); + } + + async beginTransaction(conn: D1Connection): Promise { + return await conn.beginTransaction(); + } + + async commitTransaction(conn: D1Connection): Promise { + return await conn.commitTransaction(); + } + + async rollbackTransaction(conn: D1Connection): Promise { + return await conn.rollbackTransaction(); + } + + async releaseConnection(_conn: D1Connection): Promise {} + + async destroy(): Promise {} +} + +class D1Connection implements DatabaseConnection { + #config: D1DialectConfig; + + constructor(config: D1DialectConfig) { + this.#config = config; + } + + async executeQuery(compiledQuery: CompiledQuery): Promise> { + const results = await this.#config.database + .prepare(compiledQuery.sql) + .bind(...compiledQuery.parameters) + .all(); + if (results.error) { + throw new Error(results.error); + } + + const numAffectedRows = results.meta.changes > 0 ? BigInt(results.meta.changes) : undefined; + + return { + insertId: + results.meta.last_row_id === undefined || results.meta.last_row_id === null + ? undefined + : BigInt(results.meta.last_row_id), + rows: (results?.results as O[]) || [], + numAffectedRows, + // @ts-ignore deprecated in kysely >= 0.23, keep for backward compatibility. + numUpdatedOrDeletedRows: numAffectedRows, + }; + } + + async beginTransaction() { + throw new Error("Transactions are not supported yet."); + } + + async commitTransaction() { + throw new Error("Transactions are not supported yet."); + } + + async rollbackTransaction() { + throw new Error("Transactions are not supported yet."); + } + + // biome-ignore lint/correctness/useYield: + async *streamQuery( + _compiledQuery: CompiledQuery, + _chunkSize: number, + ): AsyncIterableIterator> { + throw new Error("D1 Driver does not support streaming"); + } +} diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index 60ad9dfe..fb298faf 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -17,32 +17,41 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string } db = new DatabaseSync(":memory:"); } - return genericSqlite("node-sqlite", db, (utils) => { - const getStmt = (sql: string) => { - const stmt = db.prepare(sql); - //stmt.setReadBigInts(true); - return stmt; - }; + return genericSqlite( + "node-sqlite", + db, + (utils) => { + const getStmt = (sql: string) => { + const stmt = db.prepare(sql); + //stmt.setReadBigInts(true); + return stmt; + }; - return { - db, - query: utils.buildQueryFn({ - all: (sql, parameters = []) => getStmt(sql).all(...parameters), - run: (sql, parameters = []) => { - const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); - return { - insertId: utils.parseBigInt(lastInsertRowid), - numAffectedRows: utils.parseBigInt(changes), - }; + return { + db, + query: utils.buildQueryFn({ + all: (sql, parameters = []) => getStmt(sql).all(...parameters), + run: (sql, parameters = []) => { + const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); + return { + insertId: utils.parseBigInt(lastInsertRowid), + numAffectedRows: utils.parseBigInt(changes), + }; + }, + }), + close: () => db.close(), + iterator: (isSelect, sql, parameters = []) => { + if (!isSelect) { + throw new Error("Only support select in stream()"); + } + return getStmt(sql).iterate(...parameters) as any; }, - }), - close: () => db.close(), - iterator: (isSelect, sql, parameters = []) => { - if (!isSelect) { - throw new Error("Only support select in stream()"); - } - return getStmt(sql).iterate(...parameters) as any; + }; + }, + { + supports: { + batching: false, }, - }; - }); + }, + ); } diff --git a/app/src/adapter/sqlite/edge.ts b/app/src/adapter/sqlite/edge.ts index 97b09ec7..f1de953f 100644 --- a/app/src/adapter/sqlite/edge.ts +++ b/app/src/adapter/sqlite/edge.ts @@ -1,5 +1,5 @@ import type { Connection } from "bknd/data"; -import { libsql } from "../../data/connection/sqlite/LibsqlConnection"; +import { libsql } from "../../data/connection/sqlite/libsql/LibsqlConnection"; export function sqlite(config: { url: string }): Connection { return libsql(config); diff --git a/app/src/adapter/sqlite/types.ts b/app/src/adapter/sqlite/types.ts deleted file mode 100644 index 2a0a5e58..00000000 --- a/app/src/adapter/sqlite/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Connection } from "bknd/data"; - -export type SqliteConnection = (config: { url: string }) => Connection; diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts index 5bd45438..03709cc3 100644 --- a/app/src/data/connection/sqlite/GenericSqliteConnection.ts +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -1,4 +1,4 @@ -import type { KyselyPlugin } from "kysely"; +import type { KyselyPlugin, QueryResult } from "kysely"; import { type IGenericSqlite, type OnCreateConnection, @@ -8,11 +8,16 @@ import { GenericSqliteDialect, } from "kysely-generic-sqlite"; import { SqliteConnection } from "./SqliteConnection"; -import type { Features } from "../Connection"; +import type { ConnQuery, ConnQueryResults, Features } from "../Connection"; export type { IGenericSqlite }; +export type TStatement = { sql: string; parameters?: any[] | readonly any[] }; +export interface IGenericCustomSqlite extends IGenericSqlite { + batch?: (stmts: TStatement[]) => Promisable[]>; +} + export type GenericSqliteConnectionConfig = { - name: string; + name?: string; additionalPlugins?: KyselyPlugin[]; excludeTables?: string[]; onCreateConnection?: OnCreateConnection; @@ -21,10 +26,11 @@ export type GenericSqliteConnectionConfig = { export class GenericSqliteConnection extends SqliteConnection { override name = "generic-sqlite"; + #executor: IGenericCustomSqlite | undefined; constructor( - db: DB, - executor: () => Promisable, + public db: DB, + private executor: () => Promisable>, config?: GenericSqliteConnectionConfig, ) { super({ @@ -39,18 +45,43 @@ export class GenericSqliteConnection extends SqliteConnection } if (config?.supports) { for (const [key, value] of Object.entries(config.supports)) { - if (value) { + if (value !== undefined) { this.supported[key] = value; } } } } + private async getExecutor() { + if (!this.#executor) { + this.#executor = await this.executor(); + } + return this.#executor; + } + + override async executeQueries(...qbs: O): Promise> { + const executor = await this.getExecutor(); + if (!executor.batch) { + console.warn("Batching is not supported by this database"); + return super.executeQueries(...qbs); + } + + const compiled = this.getCompiled(...qbs); + const stms: TStatement[] = compiled.map((q) => { + return { + sql: q.sql, + parameters: q.parameters as any[], + }; + }); + + const results = await executor.batch(stms); + return this.withTransformedRows(results) as any; + } } export function genericSqlite( name: string, db: DB, - executor: (utils: typeof genericSqliteUtils) => Promisable>, + executor: (utils: typeof genericSqliteUtils) => Promisable>, config?: GenericSqliteConnectionConfig, ) { return new GenericSqliteConnection(db, () => executor(genericSqliteUtils), { diff --git a/app/src/data/connection/sqlite/LibsqlConnection.spec.ts b/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts similarity index 86% rename from app/src/data/connection/sqlite/LibsqlConnection.spec.ts rename to app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts index d6f14d10..bd15fbb2 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.spec.ts +++ b/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts @@ -1,4 +1,4 @@ -import { connectionTestSuite } from "../connection-test-suite"; +import { connectionTestSuite } from "../../connection-test-suite"; import { LibsqlConnection } from "./LibsqlConnection"; import { bunTestRunner } from "adapter/bun/test"; import { describe } from "bun:test"; diff --git a/app/src/data/connection/sqlite/LibsqlConnection.ts b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts similarity index 76% rename from app/src/data/connection/sqlite/LibsqlConnection.ts rename to app/src/data/connection/sqlite/libsql/LibsqlConnection.ts index cf68962a..19181989 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts @@ -1,23 +1,14 @@ import type { Client, Config, InStatement } from "@libsql/client"; import { createClient } from "libsql-stateless-easy"; -import { LibsqlDialect } from "@libsql/kysely-libsql"; +import { LibsqlDialect } from "./LibsqlDialect"; import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; import { type ConnQuery, type ConnQueryResults, SqliteConnection } from "bknd/data"; -export const LIBSQL_PROTOCOLS = ["wss", "https", "libsql"] as const; -export type LibSqlCredentials = Config & { - protocol?: (typeof LIBSQL_PROTOCOLS)[number]; -}; +export type LibSqlCredentials = Config; function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { if (clientOrCredentials && "url" in clientOrCredentials) { - let { url, authToken, protocol } = clientOrCredentials; - if (protocol && LIBSQL_PROTOCOLS.includes(protocol)) { - console.info("changing protocol to", protocol); - const [, rest] = url.split("://"); - url = `${protocol}://${rest}`; - } - + const { url, authToken } = clientOrCredentials; return createClient({ url, authToken }); } diff --git a/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts b/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts new file mode 100644 index 00000000..1a88c3d9 --- /dev/null +++ b/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts @@ -0,0 +1,145 @@ +import type { Client, Transaction, InValue } from "@libsql/client"; +import { + SqliteAdapter, + SqliteIntrospector, + SqliteQueryCompiler, + type Kysely, + type Dialect, + type DialectAdapter, + type Driver, + type DatabaseIntrospector, + type QueryCompiler, + type TransactionSettings, + type DatabaseConnection, + type QueryResult, + type CompiledQuery, +} from "kysely"; + +export type LibsqlDialectConfig = { + client: Client; +}; + +export class LibsqlDialect implements Dialect { + #config: LibsqlDialectConfig; + + constructor(config: LibsqlDialectConfig) { + this.#config = config; + } + + createAdapter(): DialectAdapter { + return new SqliteAdapter(); + } + + createDriver(): Driver { + let client: Client; + let closeClient: boolean; + if ("client" in this.#config) { + client = this.#config.client; + closeClient = false; + } else { + throw new Error("Please specify either `client` or `url` in the LibsqlDialect config"); + } + + return new LibsqlDriver(client, closeClient); + } + + createIntrospector(db: Kysely): DatabaseIntrospector { + return new SqliteIntrospector(db); + } + + createQueryCompiler(): QueryCompiler { + return new SqliteQueryCompiler(); + } +} + +export class LibsqlDriver implements Driver { + client: Client; + #closeClient: boolean; + + constructor(client: Client, closeClient: boolean) { + this.client = client; + this.#closeClient = closeClient; + } + + async init(): Promise {} + + async acquireConnection(): Promise { + return new LibsqlConnection(this.client); + } + + async beginTransaction( + connection: LibsqlConnection, + _settings: TransactionSettings, + ): Promise { + await connection.beginTransaction(); + } + + async commitTransaction(connection: LibsqlConnection): Promise { + await connection.commitTransaction(); + } + + async rollbackTransaction(connection: LibsqlConnection): Promise { + await connection.rollbackTransaction(); + } + + async releaseConnection(_conn: LibsqlConnection): Promise {} + + async destroy(): Promise { + if (this.#closeClient) { + this.client.close(); + } + } +} + +export class LibsqlConnection implements DatabaseConnection { + client: Client; + #transaction?: Transaction; + + constructor(client: Client) { + this.client = client; + } + + async executeQuery(compiledQuery: CompiledQuery): Promise> { + const target = this.#transaction ?? this.client; + const result = await target.execute({ + sql: compiledQuery.sql, + args: compiledQuery.parameters as Array, + }); + return { + insertId: result.lastInsertRowid, + numAffectedRows: BigInt(result.rowsAffected), + rows: result.rows as Array, + }; + } + + async beginTransaction() { + if (this.#transaction) { + throw new Error("Transaction already in progress"); + } + this.#transaction = await this.client.transaction(); + } + + async commitTransaction() { + if (!this.#transaction) { + throw new Error("No transaction to commit"); + } + await this.#transaction.commit(); + this.#transaction = undefined; + } + + async rollbackTransaction() { + if (!this.#transaction) { + throw new Error("No transaction to rollback"); + } + await this.#transaction.rollback(); + this.#transaction = undefined; + } + + // biome-ignore lint/correctness/useYield: + async *streamQuery( + _compiledQuery: CompiledQuery, + _chunkSize: number, + ): AsyncIterableIterator> { + throw new Error("Libsql Driver does not support streaming yet"); + } +} diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index f41bd8bc..bd0e6472 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -57,7 +57,7 @@ export class Repository): RepoQuery { const entity = this.entity; // @todo: if not cloned deep, it will keep references and error if multiple requests come in const validated = { diff --git a/app/src/data/index.ts b/app/src/data/index.ts index 9fb5a35d..32e914d3 100644 --- a/app/src/data/index.ts +++ b/app/src/data/index.ts @@ -30,7 +30,7 @@ export * as DataPermissions from "./permissions"; export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; -export { libsql } from "./connection/sqlite/LibsqlConnection"; +export { libsql } from "./connection/sqlite/libsql/LibsqlConnection"; export { genericSqlite, genericSqliteUtils, diff --git a/app/src/data/server/query.ts b/app/src/data/server/query.ts index b4ebc9e3..f0703395 100644 --- a/app/src/data/server/query.ts +++ b/app/src/data/server/query.ts @@ -150,4 +150,6 @@ export type RepoQueryIn = { join?: string[]; where?: WhereQuery; }; -export type RepoQuery = s.StaticCoerced; +export type RepoQuery = s.StaticCoerced & { + sort: SortSchema; +}; diff --git a/app/vite.dev.ts b/app/vite.dev.ts index 9181dc92..c1c91bc3 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -6,7 +6,7 @@ import { StorageLocalAdapter } from "./src/adapter/node"; import type { Connection } from "./src/data/connection/Connection"; import { __bknd } from "modules/ModuleManager"; import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; -import { libsql } from "./src/data/connection/sqlite/LibsqlConnection"; +import { libsql } from "./src/data/connection/sqlite/libsql/LibsqlConnection"; import { $console } from "core"; import { createClient } from "@libsql/client"; diff --git a/bun.lock b/bun.lock index 7a373522..434b81f2 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.15.0-rc.2", + "version": "0.15.0-rc.3", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", @@ -37,13 +37,13 @@ "json-schema-form-react": "^0.0.2", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", + "jsonv-ts": "^0.1.0", "kysely": "^0.27.6", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", "object-path-immutable": "^4.1.2", "radix-ui": "^1.1.3", "swr": "^2.3.3", - "uuid": "^11.1.0", }, "devDependencies": { "@aws-sdk/client-s3": "^3.758.0", @@ -55,7 +55,6 @@ "@hono/vite-dev-server": "^0.19.1", "@hookform/resolvers": "^4.1.3", "@libsql/client": "^0.15.9", - "@libsql/kysely-libsql": "^0.4.1", "@mantine/modals": "^7.17.1", "@mantine/notifications": "^7.17.1", "@playwright/test": "^1.51.1", @@ -75,7 +74,6 @@ "dotenv": "^16.4.7", "jotai": "^2.12.2", "jsdom": "^26.0.0", - "jsonv-ts": "^0.1.0", "kysely-d1": "^0.3.0", "kysely-generic-sqlite": "^1.2.1", "libsql-stateless-easy": "^1.8.0", @@ -98,6 +96,7 @@ "tsc-alias": "^1.8.11", "tsup": "^8.4.0", "tsx": "^4.19.3", + "uuid": "^11.1.0", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.0.9", @@ -754,8 +753,6 @@ "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="], - "@libsql/kysely-libsql": ["@libsql/kysely-libsql@0.4.1", "", { "dependencies": { "@libsql/client": "^0.8.0" }, "peerDependencies": { "kysely": "*" } }, "sha512-mCTa6OWgoME8LNu22COM6XjKBmcMAvNtIO6DYM10jSAFq779fVlrTKQEmXIB8TwJVU65dA5jGCpT8gkDdWS0HQ=="], - "@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.13", "", { "os": "linux", "cpu": "arm" }, "sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA=="], "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.13", "", { "os": "linux", "cpu": "arm" }, "sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA=="], @@ -1222,7 +1219,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], - "@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="], + "@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="], "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], @@ -3878,8 +3875,6 @@ "@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "@libsql/kysely-libsql/@libsql/client": ["@libsql/client@0.8.1", "", { "dependencies": { "@libsql/core": "^0.8.1", "@libsql/hrana-client": "^0.6.2", "js-base64": "^3.7.5", "libsql": "^0.3.10", "promise-limit": "^2.7.0" } }, "sha512-xGg0F4iTDFpeBZ0r4pA6icGsYa5rG6RAG+i/iLDnpCAnSuTqEWMDdPlVseiq4Z/91lWI9jvvKKiKpovqJ1kZWA=="], - "@neondatabase/serverless/@types/pg": ["@types/pg@8.6.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw=="], "@plasmicapp/query/swr": ["swr@1.3.0", "", { "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="], @@ -4020,7 +4015,7 @@ "@testing-library/jest-dom/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], - "@types/bun/bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="], + "@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="], "@types/pg/pg-types": ["pg-types@4.0.2", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng=="], @@ -4630,12 +4625,6 @@ "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "@libsql/kysely-libsql/@libsql/client/@libsql/core": ["@libsql/core@0.8.1", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-u6nrj6HZMTPsgJ9EBhLzO2uhqhlHQJQmVHV+0yFLvfGf3oSP8w7TjZCNUgu1G8jHISx6KFi7bmcrdXW9lRt++A=="], - - "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client": ["@libsql/hrana-client@0.6.2", "", { "dependencies": { "@libsql/isomorphic-fetch": "^0.2.1", "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5", "node-fetch": "^3.3.2" } }, "sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw=="], - - "@libsql/kysely-libsql/@libsql/client/libsql": ["libsql@0.3.19", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2", "libsql": "^0.3.15" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.3.19", "@libsql/darwin-x64": "0.3.19", "@libsql/linux-arm64-gnu": "0.3.19", "@libsql/linux-arm64-musl": "0.3.19", "@libsql/linux-x64-gnu": "0.3.19", "@libsql/linux-x64-musl": "0.3.19", "@libsql/win32-x64-msvc": "0.3.19" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA=="], - "@testing-library/dom/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], @@ -4986,24 +4975,6 @@ "@cloudflare/vitest-pool-workers/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/@libsql/isomorphic-fetch": ["@libsql/isomorphic-fetch@0.2.5", "", {}, "sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg=="], - - "@libsql/kysely-libsql/@libsql/client/@libsql/hrana-client/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.3.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/darwin-x64": ["@libsql/darwin-x64@0.3.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.3.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.3.19", "", { "os": "linux", "cpu": "x64" }, "sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ=="], - - "@libsql/kysely-libsql/@libsql/client/libsql/@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.3.19", "", { "os": "win32", "cpu": "x64" }, "sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg=="], - "@verdaccio/middleware/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "@vitest/coverage-v8/vitest/vite/rollup": ["rollup@4.35.0", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.35.0", "@rollup/rollup-android-arm64": "4.35.0", "@rollup/rollup-darwin-arm64": "4.35.0", "@rollup/rollup-darwin-x64": "4.35.0", "@rollup/rollup-freebsd-arm64": "4.35.0", "@rollup/rollup-freebsd-x64": "4.35.0", "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", "@rollup/rollup-linux-arm-musleabihf": "4.35.0", "@rollup/rollup-linux-arm64-gnu": "4.35.0", "@rollup/rollup-linux-arm64-musl": "4.35.0", "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", "@rollup/rollup-linux-riscv64-gnu": "4.35.0", "@rollup/rollup-linux-s390x-gnu": "4.35.0", "@rollup/rollup-linux-x64-gnu": "4.35.0", "@rollup/rollup-linux-x64-musl": "4.35.0", "@rollup/rollup-win32-arm64-msvc": "4.35.0", "@rollup/rollup-win32-ia32-msvc": "4.35.0", "@rollup/rollup-win32-x64-msvc": "4.35.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg=="], From a9f3a582ebf2180562a57230d5a1ba326b16b91a Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 25 Jun 2025 09:42:25 +0200 Subject: [PATCH 25/39] fix tests and imports --- app/package.json | 14 +++++++------- app/src/adapter/cloudflare/config.ts | 14 +++++++++----- .../adapter/cloudflare/connection/D1Connection.ts | 4 +++- app/src/adapter/cloudflare/index.ts | 11 +++++++---- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/app/package.json b/app/package.json index 68320258..4748fb65 100644 --- a/app/package.json +++ b/app/package.json @@ -186,6 +186,11 @@ "import": "./dist/media/index.js", "require": "./dist/media/index.js" }, + "./plugins": { + "types": "./dist/types/plugins/index.d.ts", + "import": "./dist/plugins/index.js", + "require": "./dist/plugins/index.js" + }, "./adapter/sqlite": { "types": "./dist/types/adapter/sqlite/edge.d.ts", "import": { @@ -200,11 +205,6 @@ }, "require": "./dist/adapter/sqlite/node.js" }, - "./plugins": { - "types": "./dist/types/plugins/index.d.ts", - "import": "./dist/plugins/index.js", - "require": "./dist/plugins/index.js" - }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", @@ -261,14 +261,14 @@ "cli": ["./dist/types/cli/index.d.ts"], "media": ["./dist/types/media/index.d.ts"], "plugins": ["./dist/types/plugins/index.d.ts"], - "sqlite": ["./dist/types/adapter/sqlite/edge.d.ts"], "adapter": ["./dist/types/adapter/index.d.ts"], "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], "adapter/vite": ["./dist/types/adapter/vite/index.d.ts"], "adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"], "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], "adapter/bun": ["./dist/types/adapter/bun/index.d.ts"], - "adapter/node": ["./dist/types/adapter/node/index.d.ts"] + "adapter/node": ["./dist/types/adapter/node/index.d.ts"], + "adapter/sqlite": ["./dist/types/adapter/sqlite/edge.d.ts"] } }, "publishConfig": { diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index 33a81e01..d67a2bf2 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -2,7 +2,8 @@ import { registerMedia } from "./storage/StorageR2Adapter"; import { getBinding } from "./bindings"; -import { D1Connection } from "./connection/D1Connection"; +import { d1Sqlite } from "./connection/D1Connection"; +import { Connection } from "bknd/data"; import type { CloudflareBkndConfig, CloudflareEnv } from "."; import { App } from "bknd"; import { makeConfig as makeAdapterConfig } from "bknd/adapter"; @@ -101,7 +102,7 @@ export function makeConfig( // if connection instance is given, don't do anything // other than checking if D1 session is defined - if (D1Connection.isConnection(appConfig.connection)) { + if (Connection.isConnection(appConfig.connection)) { if (config.d1?.session) { // we cannot guarantee that db was opened with session throw new Error( @@ -139,8 +140,11 @@ export function makeConfig( if (db) { if (config.d1?.session) { session = db.withSession(sessionId ?? config.d1?.first); + if (!session) { + throw new Error("Couldn't create session"); + } - appConfig.connection = new D1Connection({ binding: session }); + appConfig.connection = d1Sqlite({ binding: session }); appConfig.options = { ...appConfig.options, manager: { @@ -154,12 +158,12 @@ export function makeConfig( }, }; } else { - appConfig.connection = new D1Connection({ binding: db }); + appConfig.connection = d1Sqlite({ binding: db }); } } } - if (!D1Connection.isConnection(appConfig.connection)) { + if (!Connection.isConnection(appConfig.connection)) { throw new Error("Couldn't find database connection"); } diff --git a/app/src/adapter/cloudflare/connection/D1Connection.ts b/app/src/adapter/cloudflare/connection/D1Connection.ts index 2f3ccfa4..b2344e11 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.ts @@ -12,7 +12,9 @@ export type D1ConnectionConfig) { +export function d1Sqlite( + config: D1ConnectionConfig, +) { const db = config.binding; return genericSqlite( diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 60e6a774..26044ba5 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -1,10 +1,10 @@ -import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection"; +import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection"; export * from "./cloudflare-workers.adapter"; export { makeApp, getFresh } from "./modes/fresh"; export { getCached } from "./modes/cached"; export { DurableBkndApp, getDurable } from "./modes/durable"; -export { D1Connection, type D1ConnectionConfig }; +export { d1Sqlite, type D1ConnectionConfig }; export { getBinding, getBindings, @@ -14,6 +14,9 @@ export { } from "./bindings"; export { constants } from "./config"; -export function d1(config: D1ConnectionConfig) { - return new D1Connection(config); +// for compatibility with old code +export function d1( + config: D1ConnectionConfig, +) { + return d1Sqlite(config); } From d41fd5541f84c22e1eeb529030a9d82d02d79ee2 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 14:02:33 +0200 Subject: [PATCH 26/39] rewrite libsql and cloudflare sqlite's to use the generic adapter --- .../connection/BunSqliteConnection.spec.ts | 5 +- app/src/adapter/bun/test.ts | 5 +- .../connection/D1Connection.vitest.ts | 65 ++-- .../cloudflare/connection/D1Dialect.ts | 138 --------- .../cloudflare/connection/DoConnection.ts | 83 +++++ .../connection/DoConnection.vitest.ts | 92 ++++++ .../NodeSqliteConnection.vi-test.ts | 10 +- app/src/adapter/node/test.ts | 5 +- app/src/adapter/node/vitest.ts | 5 +- app/src/core/test/index.ts | 7 +- .../data/connection/connection-test-suite.ts | 283 ++++++++++++++---- .../sqlite/GenericSqliteConnection.ts | 2 +- .../connection/sqlite/SqliteIntrospector.ts | 52 ++-- .../sqlite/libsql/LibsqlConnection.spec.ts | 9 +- .../sqlite/libsql/LibsqlConnection.ts | 85 +++--- .../connection/sqlite/libsql/LibsqlDialect.ts | 145 --------- 16 files changed, 533 insertions(+), 458 deletions(-) delete mode 100644 app/src/adapter/cloudflare/connection/D1Dialect.ts create mode 100644 app/src/adapter/cloudflare/connection/DoConnection.ts create mode 100644 app/src/adapter/cloudflare/connection/DoConnection.vitest.ts delete mode 100644 app/src/data/connection/sqlite/libsql/LibsqlDialect.ts diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts index 5099fbce..2b242a1b 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.spec.ts @@ -6,7 +6,10 @@ import { Database } from "bun:sqlite"; describe("BunSqliteConnection", () => { connectionTestSuite(bunTestRunner, { - makeConnection: () => bunSqlite({ database: new Database(":memory:") }), + makeConnection: () => ({ + connection: bunSqlite({ database: new Database(":memory:") }), + dispose: async () => {}, + }), rawDialectDetails: [], }); }); diff --git a/app/src/adapter/bun/test.ts b/app/src/adapter/bun/test.ts index 1cb6ca5b..4d453d77 100644 --- a/app/src/adapter/bun/test.ts +++ b/app/src/adapter/bun/test.ts @@ -1,8 +1,11 @@ -import { expect, test, mock, describe } from "bun:test"; +import { expect, test, mock, describe, beforeEach, afterEach, afterAll } from "bun:test"; export const bunTestRunner = { describe, expect, test, mock, + beforeEach, + afterEach, + afterAll, }; diff --git a/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts b/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts index 762e0ea2..a535e484 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.vitest.ts @@ -4,51 +4,30 @@ import { viTestRunner } from "adapter/node/vitest"; import { connectionTestSuite } from "data/connection/connection-test-suite"; import { Miniflare } from "miniflare"; import { d1Sqlite } from "./D1Connection"; -import { sql } from "kysely"; describe("d1Sqlite", async () => { - const mf = new Miniflare({ - modules: true, - script: "export default { async fetch() { return new Response(null); } }", - d1Databases: ["DB"], - }); - const binding = (await mf.getD1Database("DB")) as D1Database; - - test("connection", async () => { - const conn = d1Sqlite({ binding }); - expect(conn.supports("batching")).toBe(true); - expect(conn.supports("softscans")).toBe(false); - }); - - test("query details", async () => { - const conn = d1Sqlite({ binding }); - - const res = await conn.executeQuery(sql`select 1`.compile(conn.kysely)); - expect(res.rows).toEqual([{ "1": 1 }]); - expect(res.numAffectedRows).toBe(undefined); - expect(res.insertId).toBe(undefined); - // @ts-expect-error - expect(res.meta.changed_db).toBe(false); - // @ts-expect-error - expect(res.meta.rows_read).toBe(0); - - const batchResult = await conn.executeQueries( - sql`select 1`.compile(conn.kysely), - sql`select 2`.compile(conn.kysely), - ); - - // rewrite to get index - for (const [index, result] of batchResult.entries()) { - expect(result.rows).toEqual([{ [String(index + 1)]: index + 1 }]); - expect(result.numAffectedRows).toBe(undefined); - expect(result.insertId).toBe(undefined); - // @ts-expect-error - expect(result.meta.changed_db).toBe(false); - } - }); - connectionTestSuite(viTestRunner, { - makeConnection: () => d1Sqlite({ binding }), - rawDialectDetails: [], + makeConnection: async () => { + const mf = new Miniflare({ + modules: true, + script: "export default { async fetch() { return new Response(null); } }", + d1Databases: ["DB"], + }); + + const binding = (await mf.getD1Database("DB")) as D1Database; + return { + connection: d1Sqlite({ binding }), + dispose: () => mf.dispose(), + }; + }, + rawDialectDetails: [ + "meta.served_by", + "meta.duration", + "meta.changes", + "meta.changed_db", + "meta.size_after", + "meta.rows_read", + "meta.rows_written", + ], }); }); diff --git a/app/src/adapter/cloudflare/connection/D1Dialect.ts b/app/src/adapter/cloudflare/connection/D1Dialect.ts deleted file mode 100644 index e3637f90..00000000 --- a/app/src/adapter/cloudflare/connection/D1Dialect.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - SqliteAdapter, - SqliteIntrospector, - SqliteQueryCompiler, - type CompiledQuery, - type DatabaseConnection, - type DatabaseIntrospector, - type Dialect, - type Driver, - type Kysely, - type QueryCompiler, - type QueryResult, -} from "kysely"; - -/** - * Config for the D1 dialect. Pass your D1 instance to this object that you bound in `wrangler.toml`. - */ -export interface D1DialectConfig { - database: D1Database; -} - -/** - * D1 dialect that adds support for [Cloudflare D1][0] in [Kysely][1]. - * The constructor takes the instance of your D1 database that you bound in `wrangler.toml`. - * - * ```typescript - * new D1Dialect({ - * database: env.DB, - * }) - * ``` - * - * [0]: https://blog.cloudflare.com/introducing-d1/ - * [1]: https://github.com/koskimas/kysely - */ -export class D1Dialect implements Dialect { - #config: D1DialectConfig; - - constructor(config: D1DialectConfig) { - this.#config = config; - } - - createAdapter() { - return new SqliteAdapter(); - } - - createDriver(): Driver { - return new D1Driver(this.#config); - } - - createQueryCompiler(): QueryCompiler { - return new SqliteQueryCompiler(); - } - - createIntrospector(db: Kysely): DatabaseIntrospector { - return new SqliteIntrospector(db); - } -} - -class D1Driver implements Driver { - #config: D1DialectConfig; - - constructor(config: D1DialectConfig) { - this.#config = config; - } - - async init(): Promise {} - - async acquireConnection(): Promise { - return new D1Connection(this.#config); - } - - async beginTransaction(conn: D1Connection): Promise { - return await conn.beginTransaction(); - } - - async commitTransaction(conn: D1Connection): Promise { - return await conn.commitTransaction(); - } - - async rollbackTransaction(conn: D1Connection): Promise { - return await conn.rollbackTransaction(); - } - - async releaseConnection(_conn: D1Connection): Promise {} - - async destroy(): Promise {} -} - -class D1Connection implements DatabaseConnection { - #config: D1DialectConfig; - - constructor(config: D1DialectConfig) { - this.#config = config; - } - - async executeQuery(compiledQuery: CompiledQuery): Promise> { - const results = await this.#config.database - .prepare(compiledQuery.sql) - .bind(...compiledQuery.parameters) - .all(); - if (results.error) { - throw new Error(results.error); - } - - const numAffectedRows = results.meta.changes > 0 ? BigInt(results.meta.changes) : undefined; - - return { - insertId: - results.meta.last_row_id === undefined || results.meta.last_row_id === null - ? undefined - : BigInt(results.meta.last_row_id), - rows: (results?.results as O[]) || [], - numAffectedRows, - // @ts-ignore deprecated in kysely >= 0.23, keep for backward compatibility. - numUpdatedOrDeletedRows: numAffectedRows, - }; - } - - async beginTransaction() { - throw new Error("Transactions are not supported yet."); - } - - async commitTransaction() { - throw new Error("Transactions are not supported yet."); - } - - async rollbackTransaction() { - throw new Error("Transactions are not supported yet."); - } - - // biome-ignore lint/correctness/useYield: - async *streamQuery( - _compiledQuery: CompiledQuery, - _chunkSize: number, - ): AsyncIterableIterator> { - throw new Error("D1 Driver does not support streaming"); - } -} diff --git a/app/src/adapter/cloudflare/connection/DoConnection.ts b/app/src/adapter/cloudflare/connection/DoConnection.ts new file mode 100644 index 00000000..c7c8500e --- /dev/null +++ b/app/src/adapter/cloudflare/connection/DoConnection.ts @@ -0,0 +1,83 @@ +/// + +import { + genericSqlite, + type GenericSqliteConnection, +} from "data/connection/sqlite/GenericSqliteConnection"; +import type { QueryResult } from "kysely"; + +export type D1SqliteConnection = GenericSqliteConnection; +export type DurableObjecSql = DurableObjectState["storage"]["sql"]; + +export type D1ConnectionConfig = + | DurableObjectState + | { + sql: DB; + }; + +export function doSqlite(config: D1ConnectionConfig) { + const db = "sql" in config ? config.sql : config.storage.sql; + + return genericSqlite( + "do-sqlite", + db, + (utils) => { + // must be async to work with the miniflare mock + const getStmt = async (sql: string, parameters?: any[] | readonly any[]) => + await db.exec(sql, ...(parameters || [])); + + const mapResult = ( + cursor: SqlStorageCursor>, + ): QueryResult => { + const numAffectedRows = + cursor.rowsWritten > 0 ? utils.parseBigInt(cursor.rowsWritten) : undefined; + const insertId = undefined; + + const obj = { + insertId, + numAffectedRows, + rows: cursor.toArray() || [], + // @ts-ignore + meta: { + rowsWritten: cursor.rowsWritten, + rowsRead: cursor.rowsRead, + databaseSize: db.databaseSize, + }, + }; + //console.info("mapResult", obj); + return obj; + }; + + return { + db, + batch: async (stmts) => { + // @todo: maybe wrap in a transaction? + // because d1 implicitly does a transaction on batch + return Promise.all( + stmts.map(async (stmt) => { + return mapResult(await getStmt(stmt.sql, stmt.parameters)); + }), + ); + }, + query: utils.buildQueryFn({ + all: async (sql, parameters) => { + const prep = getStmt(sql, parameters); + return mapResult(await prep).rows; + }, + run: async (sql, parameters) => { + const prep = getStmt(sql, parameters); + return mapResult(await prep); + }, + }), + close: () => {}, + }; + }, + { + supports: { + batching: true, + softscans: false, + }, + excludeTables: ["_cf_KV", "_cf_METADATA"], + }, + ); +} diff --git a/app/src/adapter/cloudflare/connection/DoConnection.vitest.ts b/app/src/adapter/cloudflare/connection/DoConnection.vitest.ts new file mode 100644 index 00000000..695046ed --- /dev/null +++ b/app/src/adapter/cloudflare/connection/DoConnection.vitest.ts @@ -0,0 +1,92 @@ +/// + +import { describe, test, expect } from "vitest"; + +import { viTestRunner } from "adapter/node/vitest"; +import { connectionTestSuite } from "data/connection/connection-test-suite"; +import { Miniflare } from "miniflare"; +import { doSqlite } from "./DoConnection"; + +const script = ` +import { DurableObject } from "cloudflare:workers"; + +export class TestObject extends DurableObject { + constructor(ctx, env) { + super(ctx, env); + this.storage = ctx.storage; + } + + async exec(sql, ...parameters) { + //return { sql, parameters } + const cursor = this.storage.sql.exec(sql, ...parameters); + return { + rows: cursor.toArray() || [], + rowsWritten: cursor.rowsWritten, + rowsRead: cursor.rowsRead, + databaseSize: this.storage.sql.databaseSize, + } + } + + async databaseSize() { + return this.storage.sql.databaseSize; + } +} + +export default { + async fetch(request, env) { + const stub = env.TEST_OBJECT.get(env.TEST_OBJECT.idFromName("test")); + return stub.fetch(request); + } +} +`; + +describe("doSqlite", async () => { + connectionTestSuite(viTestRunner, { + makeConnection: async () => { + const mf = new Miniflare({ + modules: true, + durableObjects: { TEST_OBJECT: { className: "TestObject", useSQLite: true } }, + script, + }); + + const ns = await mf.getDurableObjectNamespace("TEST_OBJECT"); + const id = ns.idFromName("test"); + const stub = ns.get(id) as unknown as DurableObjectStub< + Rpc.DurableObjectBranded & { + exec: (sql: string, ...parameters: any[]) => Promise; + } + >; + + const stubs: any[] = []; + const mock = { + databaseSize: 0, + exec: async function (sql: string, ...parameters: any[]) { + // @ts-ignore + const result = (await stub.exec(sql, ...parameters)) as any; + this.databaseSize = result.databaseSize; + stubs.push(result); + return { + toArray: () => result.rows, + rowsWritten: result.rowsWritten, + rowsRead: result.rowsRead, + }; + }, + }; + + return { + connection: doSqlite({ sql: mock as any }), + dispose: async () => { + await Promise.all( + stubs.map((stub) => { + try { + return stub[Symbol.dispose](); + } catch (e) {} + }), + ); + await mf.dispose(); + }, + }; + }, + rawDialectDetails: ["meta.rowsWritten", "meta.rowsRead", "meta.databaseSize"], + }); +}); diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts index 62ee9cbd..2cb91493 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.vi-test.ts @@ -1,11 +1,15 @@ import { nodeSqlite } from "./NodeSqliteConnection"; import { DatabaseSync } from "node:sqlite"; import { connectionTestSuite } from "data/connection/connection-test-suite"; -import { describe, test, expect } from "vitest"; +import { describe } from "vitest"; +import { viTestRunner } from "../vitest"; describe("NodeSqliteConnection", () => { - connectionTestSuite({ describe, test, expect } as any, { - makeConnection: () => nodeSqlite({ database: new DatabaseSync(":memory:") }), + connectionTestSuite(viTestRunner, { + makeConnection: () => ({ + connection: nodeSqlite({ database: new DatabaseSync(":memory:") }), + dispose: async () => {}, + }), rawDialectDetails: [], }); }); diff --git a/app/src/adapter/node/test.ts b/app/src/adapter/node/test.ts index 5a634ae3..3c78f25c 100644 --- a/app/src/adapter/node/test.ts +++ b/app/src/adapter/node/test.ts @@ -1,5 +1,5 @@ import nodeAssert from "node:assert/strict"; -import { test, describe } from "node:test"; +import { test, describe, beforeEach, afterEach } from "node:test"; import type { Matcher, Test, TestFn, TestRunner } from "core/test"; // Track mock function calls @@ -97,4 +97,7 @@ export const nodeTestRunner: TestRunner = { reject: (r) => nodeTestMatcher(r, failMsg), }), }), + beforeEach: beforeEach, + afterEach: afterEach, + afterAll: () => {}, }; diff --git a/app/src/adapter/node/vitest.ts b/app/src/adapter/node/vitest.ts index 569be7a3..8f6988ee 100644 --- a/app/src/adapter/node/vitest.ts +++ b/app/src/adapter/node/vitest.ts @@ -1,5 +1,5 @@ import type { TestFn, TestRunner, Test } from "core/test"; -import { describe, test, expect, vi } from "vitest"; +import { describe, test, expect, vi, beforeEach, afterEach, afterAll } from "vitest"; function vitestTest(label: string, fn: TestFn, options?: any) { return test(label, fn as any); @@ -47,4 +47,7 @@ export const viTestRunner: TestRunner = { test: vitestTest, expect: vitestExpect as any, mock: (fn) => vi.fn(fn), + beforeEach: beforeEach, + afterEach: afterEach, + afterAll: afterAll, }; diff --git a/app/src/core/test/index.ts b/app/src/core/test/index.ts index c731938e..4e9bfef9 100644 --- a/app/src/core/test/index.ts +++ b/app/src/core/test/index.ts @@ -1,3 +1,5 @@ +import type { MaybePromise } from "core/types"; + export type Matcher = { toEqual: (expected: T, failMsg?: string) => void; toBe: (expected: T, failMsg?: string) => void; @@ -16,7 +18,7 @@ export interface Test { skipIf: (condition: boolean) => (label: string, fn: TestFn) => void; } export type TestRunner = { - describe: (label: string, asyncFn: () => Promise) => void; + describe: (label: string, asyncFn: () => MaybePromise) => void; test: Test; mock: any>(fn: T) => T | any; expect: ( @@ -26,6 +28,9 @@ export type TestRunner = { resolves: Matcher>; rejects: Matcher>; }; + beforeEach: (fn: () => MaybePromise) => void; + afterEach: (fn: () => MaybePromise) => void; + afterAll: (fn: () => MaybePromise) => void; }; export async function retry( diff --git a/app/src/data/connection/connection-test-suite.ts b/app/src/data/connection/connection-test-suite.ts index 59bba0ee..af1eeba4 100644 --- a/app/src/data/connection/connection-test-suite.ts +++ b/app/src/data/connection/connection-test-suite.ts @@ -1,5 +1,9 @@ import type { TestRunner } from "core/test"; import { Connection, type FieldSpec } from "./Connection"; +import { getPath } from "core/utils"; +import * as proto from "data/prototype"; +import { createApp } from "App"; +import type { MaybePromise } from "core/types"; // @todo: add various datatypes: string, number, boolean, object, array, null, undefined, date, etc. // @todo: add toDriver/fromDriver tests on all types and fields @@ -10,77 +14,92 @@ export function connectionTestSuite( makeConnection, rawDialectDetails, }: { - makeConnection: () => Connection; + makeConnection: () => MaybePromise<{ + connection: Connection; + dispose: () => MaybePromise; + }>; rawDialectDetails: string[]; }, ) { - const { test, expect, describe } = testRunner; + const { test, expect, describe, beforeEach, afterEach, afterAll } = testRunner; - test("pings", async () => { - const connection = makeConnection(); - const res = await connection.ping(); - expect(res).toBe(true); - }); + describe("base", () => { + let ctx: Awaited>; + beforeEach(async () => { + ctx = await makeConnection(); + }); + afterEach(async () => { + await ctx.dispose(); + }); - test("initializes", async () => { - const connection = makeConnection(); - await connection.init(); - // @ts-expect-error - expect(connection.initialized).toBe(true); - expect(connection.client).toBeDefined(); - }); + test("pings", async () => { + const res = await ctx.connection.ping(); + expect(res).toBe(true); + }); - test("isConnection", async () => { - const connection = makeConnection(); - expect(Connection.isConnection(connection)).toBe(true); - }); - - test("getFieldSchema", async () => { - const c = makeConnection(); - const specToNode = (spec: FieldSpec) => { + test("initializes", async () => { + await ctx.connection.init(); // @ts-expect-error - const schema = c.kysely.schema.createTable("test").addColumn(...c.getFieldSchema(spec)); - return schema.toOperationNode(); - }; + expect(ctx.connection.initialized).toBe(true); + expect(ctx.connection.client).toBeDefined(); + }); - { - // primary - const node = specToNode({ - type: "integer", - name: "id", - primary: true, - }); - const col = node.columns[0]!; - expect(col.primaryKey).toBe(true); - expect(col.notNull).toBe(true); - } + test("isConnection", async () => { + expect(Connection.isConnection(ctx.connection)).toBe(true); + }); - { - // normal - const node = specToNode({ - type: "text", - name: "text", - }); - const col = node.columns[0]!; - expect(!col.primaryKey).toBe(true); - expect(!col.notNull).toBe(true); - } + test("getFieldSchema", async () => { + const specToNode = (spec: FieldSpec) => { + const schema = ctx.connection.kysely.schema + .createTable("test") + // @ts-expect-error + .addColumn(...ctx.connection.getFieldSchema(spec)); + return schema.toOperationNode(); + }; - { - // nullable (expect to be same as normal) - const node = specToNode({ - type: "text", - name: "text", - nullable: true, - }); - const col = node.columns[0]!; - expect(!col.primaryKey).toBe(true); - expect(!col.notNull).toBe(true); - } + { + // primary + const node = specToNode({ + type: "integer", + name: "id", + primary: true, + }); + const col = node.columns[0]!; + expect(col.primaryKey).toBe(true); + expect(col.notNull).toBe(true); + } + + { + // normal + const node = specToNode({ + type: "text", + name: "text", + }); + const col = node.columns[0]!; + expect(!col.primaryKey).toBe(true); + expect(!col.notNull).toBe(true); + } + + { + // nullable (expect to be same as normal) + const node = specToNode({ + type: "text", + name: "text", + nullable: true, + }); + const col = node.columns[0]!; + expect(!col.primaryKey).toBe(true); + expect(!col.notNull).toBe(true); + } + }); }); describe("schema", async () => { - const connection = makeConnection(); + const { connection, dispose } = await makeConnection(); + afterAll(async () => { + await dispose(); + }); + const fields = [ { type: "integer", @@ -118,14 +137,16 @@ export function connectionTestSuite( const qb = connection.kysely.selectFrom("test").selectAll(); const res = await connection.executeQuery(qb); expect(res.rows).toEqual([expected]); - expect(rawDialectDetails.every((detail) => detail in res)).toBe(true); + expect(rawDialectDetails.every((detail) => getPath(res, detail) !== undefined)).toBe(true); { const res = await connection.executeQueries(qb, qb); expect(res.length).toBe(2); res.map((r) => { expect(r.rows).toEqual([expected]); - expect(rawDialectDetails.every((detail) => detail in r)).toBe(true); + expect(rawDialectDetails.every((detail) => getPath(r, detail) !== undefined)).toBe( + true, + ); }); } }); @@ -187,4 +208,146 @@ export function connectionTestSuite( }, ]); }); + + describe("integration", async () => { + let ctx: Awaited>; + beforeEach(async () => { + ctx = await makeConnection(); + }); + afterEach(async () => { + await ctx.dispose(); + }); + + test("should create app and ping", async () => { + const app = createApp({ + connection: ctx.connection, + }); + await app.build(); + + expect(app.version()).toBeDefined(); + expect(await app.em.ping()).toBe(true); + }); + + test("should create a basic schema", async () => { + const schema = proto.em( + { + posts: proto.entity("posts", { + title: proto.text().required(), + content: proto.text(), + }), + comments: proto.entity("comments", { + content: proto.text(), + }), + }, + (fns, s) => { + fns.relation(s.comments).manyToOne(s.posts); + fns.index(s.posts).on(["title"], true); + }, + ); + + const app = createApp({ + connection: ctx.connection, + initialConfig: { + data: schema.toJSON(), + }, + }); + + await app.build(); + + expect(app.em.entities.length).toBe(2); + expect(app.em.entities.map((e) => e.name)).toEqual(["posts", "comments"]); + + const api = app.getApi(); + + expect( + ( + await api.data.createMany("posts", [ + { + title: "Hello", + content: "World", + }, + { + title: "Hello 2", + content: "World 2", + }, + ]) + ).data, + ).toEqual([ + { + id: 1, + title: "Hello", + content: "World", + }, + { + id: 2, + title: "Hello 2", + content: "World 2", + }, + ] as any); + + // try to create an existing + expect( + ( + await api.data.createOne("posts", { + title: "Hello", + }) + ).ok, + ).toBe(false); + + // add a comment to a post + await api.data.createOne("comments", { + content: "Hello", + posts_id: 1, + }); + + // and then query using a `with` property + const result = await api.data.readMany("posts", { with: ["comments"] }); + expect(result.length).toBe(2); + expect(result[0]?.comments?.length).toBe(1); + expect(result[0]?.comments?.[0]?.content).toBe("Hello"); + expect(result[1]?.comments?.length).toBe(0); + }); + + test("should support uuid", async () => { + const schema = proto.em( + { + posts: proto.entity( + "posts", + { + title: proto.text().required(), + content: proto.text(), + }, + { + primary_format: "uuid", + }, + ), + comments: proto.entity("comments", { + content: proto.text(), + }), + }, + (fns, s) => { + fns.relation(s.comments).manyToOne(s.posts); + fns.index(s.posts).on(["title"], true); + }, + ); + + const app = createApp({ + connection: ctx.connection, + initialConfig: { + data: schema.toJSON(), + }, + }); + + await app.build(); + const config = app.toJSON(); + // @ts-expect-error + expect(config.data.entities?.posts.fields?.id.config?.format).toBe("uuid"); + + const em = app.em; + const mutator = em.mutator(em.entity("posts")); + const data = await mutator.insertOne({ title: "Hello", content: "World" }); + expect(data.data.id).toBeString(); + expect(String(data.data.id).length).toBe(36); + }); + }); } diff --git a/app/src/data/connection/sqlite/GenericSqliteConnection.ts b/app/src/data/connection/sqlite/GenericSqliteConnection.ts index 03709cc3..98a584b0 100644 --- a/app/src/data/connection/sqlite/GenericSqliteConnection.ts +++ b/app/src/data/connection/sqlite/GenericSqliteConnection.ts @@ -61,7 +61,7 @@ export class GenericSqliteConnection extends SqliteConnection override async executeQueries(...qbs: O): Promise> { const executor = await this.getExecutor(); if (!executor.batch) { - console.warn("Batching is not supported by this database"); + //$console.debug("Batching is not supported by this database"); return super.executeQueries(...qbs); } diff --git a/app/src/data/connection/sqlite/SqliteIntrospector.ts b/app/src/data/connection/sqlite/SqliteIntrospector.ts index 5821bc15..70c3ff61 100644 --- a/app/src/data/connection/sqlite/SqliteIntrospector.ts +++ b/app/src/data/connection/sqlite/SqliteIntrospector.ts @@ -68,32 +68,34 @@ export class SqliteIntrospector extends BaseIntrospector { return tables.map((table) => ({ name: table.name, isView: table.type === "view", - columns: table.columns.map((col) => { - const autoIncrementCol = table.sql - ?.split(/[\(\),]/) - ?.find((it) => it.toLowerCase().includes("autoincrement")) - ?.trimStart() - ?.split(/\s+/)?.[0] - ?.replace(/["`]/g, ""); + columns: + table.columns?.map((col) => { + const autoIncrementCol = table.sql + ?.split(/[\(\),]/) + ?.find((it) => it.toLowerCase().includes("autoincrement")) + ?.trimStart() + ?.split(/\s+/)?.[0] + ?.replace(/["`]/g, ""); - return { - name: col.name, - dataType: col.type, - isNullable: !col.notnull, - isAutoIncrementing: col.name === autoIncrementCol, - hasDefaultValue: col.dflt_value != null, - comment: undefined, - }; - }), - indices: table.indices.map((index) => ({ - name: index.name, - table: table.name, - isUnique: index.sql?.match(/unique/i) != null, - columns: index.columns.map((col) => ({ - name: col.name, - order: col.seqno, - })), - })), + return { + name: col.name, + dataType: col.type, + isNullable: !col.notnull, + isAutoIncrementing: col.name === autoIncrementCol, + hasDefaultValue: col.dflt_value != null, + comment: undefined, + }; + }) ?? [], + indices: + table.indices?.map((index) => ({ + name: index.name, + table: table.name, + isUnique: index.sql?.match(/unique/i) != null, + columns: index.columns.map((col) => ({ + name: col.name, + order: col.seqno, + })), + })) ?? [], })); } } diff --git a/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts b/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts index bd15fbb2..7ae691fa 100644 --- a/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts +++ b/app/src/data/connection/sqlite/libsql/LibsqlConnection.spec.ts @@ -1,12 +1,15 @@ import { connectionTestSuite } from "../../connection-test-suite"; -import { LibsqlConnection } from "./LibsqlConnection"; +import { libsql } from "./LibsqlConnection"; import { bunTestRunner } from "adapter/bun/test"; import { describe } from "bun:test"; import { createClient } from "@libsql/client"; describe("LibsqlConnection", () => { connectionTestSuite(bunTestRunner, { - makeConnection: () => new LibsqlConnection(createClient({ url: ":memory:" })), - rawDialectDetails: ["rowsAffected", "lastInsertRowid"], + makeConnection: () => ({ + connection: libsql(createClient({ url: ":memory:" })), + dispose: async () => {}, + }), + rawDialectDetails: [], }); }); diff --git a/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts index 19181989..c99ad590 100644 --- a/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts @@ -1,9 +1,13 @@ -import type { Client, Config, InStatement } from "@libsql/client"; +import type { Client, Config, ResultSet } from "@libsql/client"; import { createClient } from "libsql-stateless-easy"; -import { LibsqlDialect } from "./LibsqlDialect"; import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; -import { type ConnQuery, type ConnQueryResults, SqliteConnection } from "bknd/data"; +import { + genericSqlite, + type GenericSqliteConnection, +} from "data/connection/sqlite/GenericSqliteConnection"; +import type { QueryResult } from "kysely"; +export type LibsqlConnection = GenericSqliteConnection; export type LibSqlCredentials = Config; function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { @@ -15,39 +19,50 @@ function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { return clientOrCredentials as Client; } -export class LibsqlConnection extends SqliteConnection { - override name = "libsql"; - protected override readonly supported = { - batching: true, - softscans: true, - }; +export function libsql(config: LibSqlCredentials | Client) { + const db = getClient(config); - constructor(clientOrCredentials: Client | LibSqlCredentials) { - const client = getClient(clientOrCredentials); - - super({ - excludeTables: ["libsql_wasm_func_table"], - dialect: LibsqlDialect, - dialectArgs: [{ client }], - additionalPlugins: [new FilterNumericKeysPlugin()], - }); - - this.client = client; - } - - override async executeQueries(...qbs: O): Promise> { - const compiled = this.getCompiled(...qbs); - const stms: InStatement[] = compiled.map((q) => { - return { - sql: q.sql, - args: q.parameters as any[], + return genericSqlite( + "libsql", + db, + (utils) => { + const mapResult = (result: ResultSet): QueryResult => ({ + insertId: result.lastInsertRowid, + numAffectedRows: BigInt(result.rowsAffected), + rows: result.rows, + }); + const execute = async (sql: string, parameters?: any[] | readonly any[]) => { + const result = await db.execute({ sql, args: [...(parameters || [])] }); + return mapResult(result); }; - }); - return this.withTransformedRows(await this.client.batch(stms)) as any; - } -} - -export function libsql(credentials: Client | LibSqlCredentials): LibsqlConnection { - return new LibsqlConnection(credentials); + return { + db, + batch: async (stmts) => { + const results = await db.batch( + stmts.map(({ sql, parameters }) => ({ + sql, + args: parameters as any[], + })), + ); + return results.map(mapResult); + }, + query: utils.buildQueryFn({ + all: async (sql, parameters) => { + return (await execute(sql, parameters)).rows; + }, + run: execute, + }), + close: () => db.close(), + }; + }, + { + supports: { + batching: true, + softscans: true, + }, + additionalPlugins: [new FilterNumericKeysPlugin()], + excludeTables: ["libsql_wasm_func_table"], + }, + ); } diff --git a/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts b/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts deleted file mode 100644 index 1a88c3d9..00000000 --- a/app/src/data/connection/sqlite/libsql/LibsqlDialect.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { Client, Transaction, InValue } from "@libsql/client"; -import { - SqliteAdapter, - SqliteIntrospector, - SqliteQueryCompiler, - type Kysely, - type Dialect, - type DialectAdapter, - type Driver, - type DatabaseIntrospector, - type QueryCompiler, - type TransactionSettings, - type DatabaseConnection, - type QueryResult, - type CompiledQuery, -} from "kysely"; - -export type LibsqlDialectConfig = { - client: Client; -}; - -export class LibsqlDialect implements Dialect { - #config: LibsqlDialectConfig; - - constructor(config: LibsqlDialectConfig) { - this.#config = config; - } - - createAdapter(): DialectAdapter { - return new SqliteAdapter(); - } - - createDriver(): Driver { - let client: Client; - let closeClient: boolean; - if ("client" in this.#config) { - client = this.#config.client; - closeClient = false; - } else { - throw new Error("Please specify either `client` or `url` in the LibsqlDialect config"); - } - - return new LibsqlDriver(client, closeClient); - } - - createIntrospector(db: Kysely): DatabaseIntrospector { - return new SqliteIntrospector(db); - } - - createQueryCompiler(): QueryCompiler { - return new SqliteQueryCompiler(); - } -} - -export class LibsqlDriver implements Driver { - client: Client; - #closeClient: boolean; - - constructor(client: Client, closeClient: boolean) { - this.client = client; - this.#closeClient = closeClient; - } - - async init(): Promise {} - - async acquireConnection(): Promise { - return new LibsqlConnection(this.client); - } - - async beginTransaction( - connection: LibsqlConnection, - _settings: TransactionSettings, - ): Promise { - await connection.beginTransaction(); - } - - async commitTransaction(connection: LibsqlConnection): Promise { - await connection.commitTransaction(); - } - - async rollbackTransaction(connection: LibsqlConnection): Promise { - await connection.rollbackTransaction(); - } - - async releaseConnection(_conn: LibsqlConnection): Promise {} - - async destroy(): Promise { - if (this.#closeClient) { - this.client.close(); - } - } -} - -export class LibsqlConnection implements DatabaseConnection { - client: Client; - #transaction?: Transaction; - - constructor(client: Client) { - this.client = client; - } - - async executeQuery(compiledQuery: CompiledQuery): Promise> { - const target = this.#transaction ?? this.client; - const result = await target.execute({ - sql: compiledQuery.sql, - args: compiledQuery.parameters as Array, - }); - return { - insertId: result.lastInsertRowid, - numAffectedRows: BigInt(result.rowsAffected), - rows: result.rows as Array, - }; - } - - async beginTransaction() { - if (this.#transaction) { - throw new Error("Transaction already in progress"); - } - this.#transaction = await this.client.transaction(); - } - - async commitTransaction() { - if (!this.#transaction) { - throw new Error("No transaction to commit"); - } - await this.#transaction.commit(); - this.#transaction = undefined; - } - - async rollbackTransaction() { - if (!this.#transaction) { - throw new Error("No transaction to rollback"); - } - await this.#transaction.rollback(); - this.#transaction = undefined; - } - - // biome-ignore lint/correctness/useYield: - async *streamQuery( - _compiledQuery: CompiledQuery, - _chunkSize: number, - ): AsyncIterableIterator> { - throw new Error("Libsql Driver does not support streaming yet"); - } -} From 70737c04cd80c24fc167ab8071675f2e762fd249 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 14:02:50 +0200 Subject: [PATCH 27/39] refactor console imports, added config update event --- app/build.ts | 9 +++++- app/src/App.ts | 9 ++++-- .../cloudflare/cloudflare-workers.adapter.ts | 2 +- app/src/adapter/cloudflare/config.ts | 2 +- app/src/adapter/cloudflare/modes/durable.ts | 2 +- app/src/adapter/index.ts | 3 +- app/src/adapter/node/node.adapter.ts | 2 +- app/src/auth/AppAuth.ts | 4 +-- app/src/auth/AppUserPool.ts | 2 +- app/src/auth/authenticate/Authenticator.ts | 3 +- .../strategies/PasswordStrategy.ts | 4 +-- app/src/auth/authorize/Guard.ts | 4 +-- app/src/auth/middlewares.ts | 4 +-- app/src/cli/commands/run/platform.ts | 2 +- app/src/cli/commands/run/run.ts | 4 +-- app/src/cli/commands/user.ts | 2 +- app/src/cli/utils/sys.ts | 2 +- app/src/cli/utils/telemetry.ts | 3 +- app/src/core/events/EventManager.ts | 2 +- app/src/core/index.ts | 1 - app/src/core/{ => utils}/console.ts | 0 app/src/core/utils/file.ts | 2 +- app/src/core/utils/index.ts | 1 + app/src/core/utils/test.ts | 6 ++-- app/src/data/api/DataController.ts | 1 - app/src/data/entities/Entity.ts | 3 +- app/src/data/entities/EntityManager.ts | 3 +- .../data/entities/mutation/MutatorResult.ts | 3 +- app/src/data/entities/query/Repository.ts | 2 +- .../data/entities/query/RepositoryResult.ts | 3 +- app/src/data/events/index.ts | 3 +- app/src/data/fields/DateField.ts | 3 +- app/src/data/schema/SchemaManager.ts | 2 +- app/src/data/server/query.ts | 3 +- app/src/flows/flows/Execution.ts | 2 +- app/src/flows/flows/Flow.ts | 3 +- .../flows/flows/executors/RuntimeExecutor.ts | 2 +- app/src/flows/flows/triggers/EventTrigger.ts | 2 +- app/src/flows/tasks/presets/LogTask.ts | 2 +- app/src/index.ts | 1 + app/src/media/AppMedia.ts | 3 +- app/src/media/storage/Storage.ts | 3 +- app/src/modules/ModuleApi.ts | 3 +- app/src/modules/ModuleManager.ts | 29 +++++++++++++++++-- app/src/modules/server/AdminController.tsx | 3 +- app/src/modules/server/AppServer.ts | 4 +-- app/src/modules/server/SystemController.ts | 3 +- app/vite.dev.ts | 2 +- 48 files changed, 100 insertions(+), 63 deletions(-) rename app/src/core/{ => utils}/console.ts (100%) diff --git a/app/build.ts b/app/build.ts index 49bdf95d..824f0069 100644 --- a/app/build.ts +++ b/app/build.ts @@ -60,7 +60,14 @@ function banner(title: string) { } // collection of always-external packages -const external = ["bun:test", "node:test", "node:assert/strict", "@libsql/client"] as const; +const external = [ + "bun:test", + "node:test", + "node:assert/strict", + "@libsql/client", + "bknd", + /^bknd\/.*/, +] as const; /** * Building backend and general API diff --git a/app/src/App.ts b/app/src/App.ts index 223711ad..f1a495b5 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -1,5 +1,5 @@ import type { CreateUserPayload } from "auth/AppAuth"; -import { $console } from "core"; +import { $console } from "core/utils"; import { Event } from "core/events"; import type { em as prototypeEm } from "data/prototype"; import { Connection } from "data/connection/Connection"; @@ -34,7 +34,10 @@ export type AppPluginConfig = { export type AppPlugin = (app: App) => AppPluginConfig; abstract class AppEvent
extends Event<{ app: App } & A> {} -export class AppConfigUpdatedEvent extends AppEvent { +export class AppConfigUpdatedEvent extends AppEvent<{ + module: string; + config: ModuleConfigs[keyof ModuleConfigs]; +}> { static override slug = "app-config-updated"; } export class AppBuiltEvent extends AppEvent { @@ -265,7 +268,7 @@ export class App( config: CloudflareBkndConfig, diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index 75540407..9e74dd01 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -1,5 +1,6 @@ import { App, type CreateAppConfig } from "bknd"; -import { config as $config, $console } from "bknd/core"; +import { config as $config } from "bknd/core"; +import { $console } from "bknd/utils"; import type { MiddlewareHandler } from "hono"; import type { AdminControllerOptions } from "modules/server/AdminController"; import { Connection } from "bknd/data"; diff --git a/app/src/adapter/node/node.adapter.ts b/app/src/adapter/node/node.adapter.ts index 9d9d5d5a..88b7d62e 100644 --- a/app/src/adapter/node/node.adapter.ts +++ b/app/src/adapter/node/node.adapter.ts @@ -4,7 +4,7 @@ import { serveStatic } from "@hono/node-server/serve-static"; import { registerLocalMediaAdapter } from "adapter/node/storage"; import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; import { config as $config } from "bknd/core"; -import { $console } from "core"; +import { $console } from "core/utils"; import type { App } from "App"; type NodeEnv = NodeJS.ProcessEnv; diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index 973332e1..ac78b9f0 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -1,7 +1,7 @@ import { Authenticator, AuthPermissions, Role, type Strategy } from "auth"; import type { PasswordStrategy } from "auth/authenticate/strategies"; -import { $console, type DB } from "core"; -import { secureRandomString, transformObject } from "core/utils"; +import type { DB } from "core"; +import { $console, secureRandomString, transformObject } from "core/utils"; import type { Entity, EntityManager } from "data"; import { em, entity, enumm, type FieldSchema, text } from "data/prototype"; import { Module } from "modules/Module"; diff --git a/app/src/auth/AppUserPool.ts b/app/src/auth/AppUserPool.ts index 23f24d06..128de6c1 100644 --- a/app/src/auth/AppUserPool.ts +++ b/app/src/auth/AppUserPool.ts @@ -1,6 +1,6 @@ import { AppAuth } from "auth/AppAuth"; import type { CreateUser, SafeUser, User, UserPool } from "auth/authenticate/Authenticator"; -import { $console } from "core"; +import { $console } from "core/utils"; import { pick } from "lodash-es"; import { InvalidConditionsException, diff --git a/app/src/auth/authenticate/Authenticator.ts b/app/src/auth/authenticate/Authenticator.ts index 4357da05..29b65971 100644 --- a/app/src/auth/authenticate/Authenticator.ts +++ b/app/src/auth/authenticate/Authenticator.ts @@ -1,6 +1,7 @@ -import { $console, type DB, Exception } from "core"; +import { type DB, Exception } from "core"; import { addFlashMessage } from "core/server/flash"; import { + $console, type Static, StringEnum, type TObject, diff --git a/app/src/auth/authenticate/strategies/PasswordStrategy.ts b/app/src/auth/authenticate/strategies/PasswordStrategy.ts index 706e14be..6bf059e2 100644 --- a/app/src/auth/authenticate/strategies/PasswordStrategy.ts +++ b/app/src/auth/authenticate/strategies/PasswordStrategy.ts @@ -1,6 +1,6 @@ import { type Authenticator, InvalidCredentialsException, type User } from "auth"; -import { $console, tbValidator as tb } from "core"; -import { hash, parse, type Static, StrictObject, StringEnum } from "core/utils"; +import { tbValidator as tb } from "core"; +import { $console, hash, parse, type Static, StrictObject, StringEnum } from "core/utils"; import { Hono } from "hono"; import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs"; import * as tbbox from "@sinclair/typebox"; diff --git a/app/src/auth/authorize/Guard.ts b/app/src/auth/authorize/Guard.ts index a45c160d..81280db2 100644 --- a/app/src/auth/authorize/Guard.ts +++ b/app/src/auth/authorize/Guard.ts @@ -1,5 +1,5 @@ -import { $console, Exception, Permission } from "core"; -import { objectTransform } from "core/utils"; +import { Exception, Permission } from "core"; +import { $console, objectTransform } from "core/utils"; import type { Context } from "hono"; import type { ServerEnv } from "modules/Controller"; import { Role } from "./Role"; diff --git a/app/src/auth/middlewares.ts b/app/src/auth/middlewares.ts index d6f28c0c..b58b540a 100644 --- a/app/src/auth/middlewares.ts +++ b/app/src/auth/middlewares.ts @@ -1,5 +1,5 @@ -import { $console, type Permission } from "core"; -import { patternMatch } from "core/utils"; +import type { Permission } from "core"; +import { $console, patternMatch } from "core/utils"; import type { Context } from "hono"; import { createMiddleware } from "hono/factory"; import type { ServerEnv } from "modules/Controller"; diff --git a/app/src/cli/commands/run/platform.ts b/app/src/cli/commands/run/platform.ts index c3a41103..bc3379bb 100644 --- a/app/src/cli/commands/run/platform.ts +++ b/app/src/cli/commands/run/platform.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import { $console } from "core"; +import { $console } from "core/utils"; import type { MiddlewareHandler } from "hono"; import open from "open"; import { fileExists, getRelativeDistPath } from "../../utils/sys"; diff --git a/app/src/cli/commands/run/run.ts b/app/src/cli/commands/run/run.ts index 7ed5bed6..0830bc69 100644 --- a/app/src/cli/commands/run/run.ts +++ b/app/src/cli/commands/run/run.ts @@ -3,7 +3,7 @@ import type { App, CreateAppConfig } from "App"; import { StorageLocalAdapter } from "adapter/node/storage"; import type { CliBkndConfig, CliCommand } from "cli/types"; import { Option } from "commander"; -import { colorizeConsole, config } from "core"; +import { config } from "core"; import dotenv from "dotenv"; import { registries } from "modules/registries"; import c from "picocolors"; @@ -17,7 +17,7 @@ import { startServer, } from "./platform"; import { createRuntimeApp, makeConfig } from "adapter"; -import { isBun } from "core/utils"; +import { colorizeConsole, isBun } from "core/utils"; const env_files = [".env", ".dev.vars"]; dotenv.config({ diff --git a/app/src/cli/commands/user.ts b/app/src/cli/commands/user.ts index 0a85d9b2..4f4db7cd 100644 --- a/app/src/cli/commands/user.ts +++ b/app/src/cli/commands/user.ts @@ -9,7 +9,7 @@ import type { PasswordStrategy } from "auth/authenticate/strategies"; import { makeAppFromEnv } from "cli/commands/run"; import type { CliCommand } from "cli/types"; import { Argument } from "commander"; -import { $console } from "core"; +import { $console } from "core/utils"; import c from "picocolors"; import { isBun } from "core/utils"; diff --git a/app/src/cli/utils/sys.ts b/app/src/cli/utils/sys.ts index b4536a5c..56ae32ed 100644 --- a/app/src/cli/utils/sys.ts +++ b/app/src/cli/utils/sys.ts @@ -1,4 +1,4 @@ -import { $console } from "core"; +import { $console } from "core/utils"; import { execSync, exec as nodeExec } from "node:child_process"; import { readFile, writeFile as nodeWriteFile } from "node:fs/promises"; import path from "node:path"; diff --git a/app/src/cli/utils/telemetry.ts b/app/src/cli/utils/telemetry.ts index 9fddb424..6673e0e4 100644 --- a/app/src/cli/utils/telemetry.ts +++ b/app/src/cli/utils/telemetry.ts @@ -1,6 +1,7 @@ import { PostHog } from "posthog-js-lite"; import { getVersion } from "cli/utils/sys"; -import { $console, env, isDebug } from "core"; +import { env, isDebug } from "core"; +import { $console } from "core/utils"; type Properties = { [p: string]: any }; diff --git a/app/src/core/events/EventManager.ts b/app/src/core/events/EventManager.ts index 59efc8f7..78db931f 100644 --- a/app/src/core/events/EventManager.ts +++ b/app/src/core/events/EventManager.ts @@ -1,6 +1,6 @@ import { type Event, type EventClass, InvalidEventReturn } from "./Event"; import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener"; -import { $console } from "core"; +import { $console } from "core/utils"; export type RegisterListenerConfig = | ListenerMode diff --git a/app/src/core/index.ts b/app/src/core/index.ts index a0e96e63..ad4b1a8c 100644 --- a/app/src/core/index.ts +++ b/app/src/core/index.ts @@ -38,7 +38,6 @@ export { } from "./object/schema"; export * from "./drivers"; -export * from "./console"; export * from "./events"; // compatibility diff --git a/app/src/core/console.ts b/app/src/core/utils/console.ts similarity index 100% rename from app/src/core/console.ts rename to app/src/core/utils/console.ts diff --git a/app/src/core/utils/file.ts b/app/src/core/utils/file.ts index 152c71d1..ea5eb2b2 100644 --- a/app/src/core/utils/file.ts +++ b/app/src/core/utils/file.ts @@ -2,7 +2,7 @@ import { extension, guess, isMimeType } from "media/storage/mime-types-tiny"; import { randomString } from "core/utils/strings"; import type { Context } from "hono"; import { invariant } from "core/utils/runtime"; -import { $console } from "../console"; +import { $console } from "./console"; export function getContentName(request: Request): string | undefined; export function getContentName(contentDisposition: string): string | undefined; diff --git a/app/src/core/utils/index.ts b/app/src/core/utils/index.ts index c94c4bb9..19bcef63 100644 --- a/app/src/core/utils/index.ts +++ b/app/src/core/utils/index.ts @@ -1,3 +1,4 @@ +export * from "./console"; export * from "./browser"; export * from "./objects"; export * from "./strings"; diff --git a/app/src/core/utils/test.ts b/app/src/core/utils/test.ts index 91ae2d39..44d38d96 100644 --- a/app/src/core/utils/test.ts +++ b/app/src/core/utils/test.ts @@ -1,4 +1,4 @@ -import { $console } from "core"; +import { $console } from "./console"; type ConsoleSeverity = "log" | "warn" | "error"; const _oldConsoles = { @@ -36,14 +36,14 @@ export function disableConsoleLog(severities: ConsoleSeverity[] = ["log", "warn" severities.forEach((severity) => { console[severity] = () => null; }); - $console.setLevel("critical"); + $console?.setLevel("critical"); } export function enableConsoleLog() { Object.entries(_oldConsoles).forEach(([severity, fn]) => { console[severity as ConsoleSeverity] = fn; }); - $console.resetLevel(); + $console?.resetLevel(); } export function formatMemoryUsage() { diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index f7db2b93..6d6acf4f 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -1,4 +1,3 @@ -import { $console, isDebug } from "core"; import { DataPermissions, type EntityData, diff --git a/app/src/data/entities/Entity.ts b/app/src/data/entities/Entity.ts index 48f23b97..e0eb12cd 100644 --- a/app/src/data/entities/Entity.ts +++ b/app/src/data/entities/Entity.ts @@ -1,5 +1,6 @@ -import { $console, config } from "core"; +import { config } from "core"; import { + $console, type Static, StringEnum, parse, diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 67571708..654c77d6 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -1,4 +1,5 @@ -import { $console, type DB as DefaultDB } from "core"; +import type { DB as DefaultDB } from "core"; +import { $console } from "core/utils"; import { EventManager } from "core/events"; import { sql } from "kysely"; import { Connection } from "../connection/Connection"; diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index 05da0172..a0fe3075 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -1,4 +1,4 @@ -import { $console } from "core/console"; +import { $console } from "core/utils"; import type { Entity, EntityData } from "../Entity"; import type { EntityManager } from "../EntityManager"; import { Result, type ResultJSON, type ResultOptions } from "../Result"; @@ -32,6 +32,7 @@ export class MutatorResult extends Result { onError: (error) => { if (!options?.silent) { $console.error("[ERROR] Mutator:", error.message); + throw error; } }, ...options, diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index bd0e6472..9fdbe996 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -1,5 +1,5 @@ import type { DB as DefaultDB, PrimaryFieldType } from "core"; -import { $console } from "core"; +import { $console } from "core/utils"; import { type EmitsEvents, EventManager } from "core/events"; import { type SelectQueryBuilder, sql } from "kysely"; import { InvalidSearchParamsException } from "../../errors"; diff --git a/app/src/data/entities/query/RepositoryResult.ts b/app/src/data/entities/query/RepositoryResult.ts index fee0dff6..7631f8fc 100644 --- a/app/src/data/entities/query/RepositoryResult.ts +++ b/app/src/data/entities/query/RepositoryResult.ts @@ -1,9 +1,8 @@ -import { $console } from "core/console"; import type { Entity, EntityData } from "../Entity"; import type { EntityManager } from "../EntityManager"; import { Result, type ResultJSON, type ResultOptions } from "../Result"; import type { Compilable, SelectQueryBuilder } from "kysely"; -import { ensureInt } from "core/utils"; +import { $console, ensureInt } from "core/utils"; export type RepositoryResultOptions = ResultOptions & { silent?: boolean; diff --git a/app/src/data/events/index.ts b/app/src/data/events/index.ts index b10f3b6f..246a39d3 100644 --- a/app/src/data/events/index.ts +++ b/app/src/data/events/index.ts @@ -1,4 +1,5 @@ -import { $console, type PrimaryFieldType } from "core"; +import type { PrimaryFieldType } from "core"; +import { $console } from "core/utils"; import { Event, InvalidEventReturn } from "core/events"; import type { Entity, EntityData } from "../entities"; import type { RepoQuery } from "data/server/query"; diff --git a/app/src/data/fields/DateField.ts b/app/src/data/fields/DateField.ts index 0fdf91ea..504273c5 100644 --- a/app/src/data/fields/DateField.ts +++ b/app/src/data/fields/DateField.ts @@ -1,7 +1,6 @@ -import { type Static, StringEnum, dayjs } from "core/utils"; +import { $console, type Static, StringEnum, dayjs } from "core/utils"; import type { EntityManager } from "../entities"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; -import { $console } from "core"; import * as tbbox from "@sinclair/typebox"; import type { TFieldTSType } from "data/entities/EntityTypescript"; const { Type } = tbbox; diff --git a/app/src/data/schema/SchemaManager.ts b/app/src/data/schema/SchemaManager.ts index ab5fbda6..dafd616f 100644 --- a/app/src/data/schema/SchemaManager.ts +++ b/app/src/data/schema/SchemaManager.ts @@ -2,7 +2,7 @@ import type { CompiledQuery, TableMetadata } from "kysely"; import type { IndexMetadata, SchemaResponse } from "../connection/Connection"; import type { Entity, EntityManager } from "../entities"; import { PrimaryField } from "../fields"; -import { $console } from "core"; +import { $console } from "core/utils"; type IntrospectedTable = TableMetadata & { indices: IndexMetadata[]; diff --git a/app/src/data/server/query.ts b/app/src/data/server/query.ts index f0703395..05122969 100644 --- a/app/src/data/server/query.ts +++ b/app/src/data/server/query.ts @@ -1,7 +1,6 @@ import { s } from "core/object/schema"; import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder"; -import { $console } from "core"; -import { isObject } from "core/utils"; +import { isObject, $console } from "core/utils"; import type { CoercionOptions, TAnyOf } from "jsonv-ts"; // ------- diff --git a/app/src/flows/flows/Execution.ts b/app/src/flows/flows/Execution.ts index 61bf05e9..41d2166a 100644 --- a/app/src/flows/flows/Execution.ts +++ b/app/src/flows/flows/Execution.ts @@ -2,7 +2,7 @@ import { Event, EventManager, type ListenerHandler } from "core/events"; import type { EmitsEvents } from "core/events"; import type { Task, TaskResult } from "../tasks/Task"; import type { Flow } from "./Flow"; -import { $console } from "core"; +import { $console } from "core/utils"; export type TaskLog = TaskResult & { task: Task; diff --git a/app/src/flows/flows/Flow.ts b/app/src/flows/flows/Flow.ts index c924756a..cf6a00ba 100644 --- a/app/src/flows/flows/Flow.ts +++ b/app/src/flows/flows/Flow.ts @@ -1,11 +1,10 @@ -import { objectTransform, transformObject } from "core/utils"; +import { $console, transformObject } from "core/utils"; import { type TaskMapType, TriggerMap } from "../index"; import type { Task } from "../tasks/Task"; import { Condition, TaskConnection } from "../tasks/TaskConnection"; import { Execution } from "./Execution"; import { FlowTaskConnector } from "./FlowTaskConnector"; import { Trigger } from "./triggers/Trigger"; -import { $console } from "core"; type Jsoned object }> = ReturnType; diff --git a/app/src/flows/flows/executors/RuntimeExecutor.ts b/app/src/flows/flows/executors/RuntimeExecutor.ts index 46ea1054..55bf8900 100644 --- a/app/src/flows/flows/executors/RuntimeExecutor.ts +++ b/app/src/flows/flows/executors/RuntimeExecutor.ts @@ -1,5 +1,5 @@ import type { Task } from "../../tasks/Task"; -import { $console } from "core"; +import { $console } from "core/utils"; export class RuntimeExecutor { async run( diff --git a/app/src/flows/flows/triggers/EventTrigger.ts b/app/src/flows/flows/triggers/EventTrigger.ts index 3924840a..e79b3a02 100644 --- a/app/src/flows/flows/triggers/EventTrigger.ts +++ b/app/src/flows/flows/triggers/EventTrigger.ts @@ -1,7 +1,7 @@ import type { EventManager } from "core/events"; import type { Flow } from "../Flow"; import { Trigger } from "./Trigger"; -import { $console } from "core"; +import { $console } from "core/utils"; import * as tbbox from "@sinclair/typebox"; const { Type } = tbbox; diff --git a/app/src/flows/tasks/presets/LogTask.ts b/app/src/flows/tasks/presets/LogTask.ts index fda6d125..8023dafb 100644 --- a/app/src/flows/tasks/presets/LogTask.ts +++ b/app/src/flows/tasks/presets/LogTask.ts @@ -1,5 +1,5 @@ import { Task } from "../Task"; -import { $console } from "core"; +import { $console } from "core/utils"; import * as tbbox from "@sinclair/typebox"; const { Type } = tbbox; diff --git a/app/src/index.ts b/app/src/index.ts index f9c1e636..afac83d5 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -16,6 +16,7 @@ export { type ModuleManagerOptions, type ModuleBuildContext, type InitialModuleConfigs, + ModuleManagerEvents, } from "./modules/ModuleManager"; export type { ServerEnv } from "modules/Controller"; diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index 18b536e8..36fce419 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -1,4 +1,5 @@ -import { $console, type AppEntity } from "core"; +import type { AppEntity } from "core"; +import { $console } from "core/utils"; import type { Entity, EntityManager } from "data"; import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; import { Module } from "modules/Module"; diff --git a/app/src/media/storage/Storage.ts b/app/src/media/storage/Storage.ts index ae660701..f8e73cb4 100644 --- a/app/src/media/storage/Storage.ts +++ b/app/src/media/storage/Storage.ts @@ -1,9 +1,8 @@ import { type EmitsEvents, EventManager } from "core/events"; -import { isFile, detectImageDimensions } from "core/utils"; +import { $console, isFile, detectImageDimensions } from "core/utils"; import { isMimeType } from "media/storage/mime-types-tiny"; import * as StorageEvents from "./events"; import type { FileUploadedEventData } from "./events"; -import { $console } from "core"; import type { StorageAdapter } from "./StorageAdapter"; export type FileListObject = { diff --git a/app/src/modules/ModuleApi.ts b/app/src/modules/ModuleApi.ts index ebb64036..f8a295c4 100644 --- a/app/src/modules/ModuleApi.ts +++ b/app/src/modules/ModuleApi.ts @@ -1,4 +1,5 @@ -import { $console, type PrimaryFieldType } from "core"; +import type { PrimaryFieldType } from "core"; +import { $console } from "core/utils"; import { isDebug } from "core/env"; import { encodeSearch } from "core/utils/reqres"; import type { ApiFetcher } from "Api"; diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index 825a2209..c79aeb61 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -1,6 +1,7 @@ import { Guard } from "auth"; -import { $console, BkndError, DebugLogger, env } from "core"; -import { EventManager } from "core/events"; +import { BkndError, DebugLogger, env } from "core"; +import { $console } from "core/utils"; +import { EventManager, Event } from "core/events"; import * as $diff from "core/object/diff"; import { Default, @@ -126,9 +127,24 @@ interface T_INTERNAL_EM { const debug_modules = env("modules_debug"); +abstract class ModuleManagerEvent extends Event<{ ctx: ModuleBuildContext } & A> {} +export class ModuleManagerConfigUpdateEvent< + Module extends keyof ModuleConfigs, +> extends ModuleManagerEvent<{ + module: Module; + config: ModuleConfigs[Module]; +}> { + static override slug = "mm-config-update"; +} +export const ModuleManagerEvents = { + ModuleManagerConfigUpdateEvent, +}; + // @todo: cleanup old diffs on upgrade // @todo: cleanup multiple backups on upgrade export class ModuleManager { + static Events = ModuleManagerEvents; + protected modules: Modules; // internal em for __bknd config table __em!: EntityManager; @@ -151,7 +167,7 @@ export class ModuleManager { ) { this.__em = new EntityManager([__bknd], this.connection); this.modules = {} as Modules; - this.emgr = new EventManager(); + this.emgr = new EventManager({ ...ModuleManagerEvents }); this.logger = new DebugLogger(debug_modules); let initial = {} as Partial; @@ -628,6 +644,13 @@ export class ModuleManager { try { // overwrite listener to run build inside this try/catch module.setListener(async () => { + await this.emgr.emit( + new ModuleManagerConfigUpdateEvent({ + ctx: this.ctx(), + module: name, + config: module.config as any, + }), + ); await this.buildModules(); }); diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index aa109ee4..35ebc819 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -1,7 +1,8 @@ /** @jsxImportSource hono/jsx */ import type { App } from "App"; -import { $console, config, isDebug } from "core"; +import { config, isDebug } from "core"; +import { $console } from "core/utils"; import { addFlashMessage } from "core/server/flash"; import { html } from "hono/html"; import { Fragment } from "hono/jsx"; diff --git a/app/src/modules/server/AppServer.ts b/app/src/modules/server/AppServer.ts index ba1c5662..2c4cb764 100644 --- a/app/src/modules/server/AppServer.ts +++ b/app/src/modules/server/AppServer.ts @@ -1,5 +1,5 @@ -import { Exception, isDebug, $console } from "core"; -import { type Static, StringEnum } from "core/utils"; +import { Exception, isDebug } from "core"; +import { type Static, StringEnum, $console } from "core/utils"; import { cors } from "hono/cors"; import { Module } from "modules/Module"; import * as tbbox from "@sinclair/typebox"; diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index ec078317..9e653158 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -1,9 +1,8 @@ /// import type { App } from "App"; -import { $console, tbValidator as tb } from "core"; import { - StringEnum, + $console, TypeInvalidError, datetimeStringLocal, datetimeStringUTC, diff --git a/app/vite.dev.ts b/app/vite.dev.ts index c1c91bc3..1274d21f 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -7,7 +7,7 @@ import type { Connection } from "./src/data/connection/Connection"; import { __bknd } from "modules/ModuleManager"; import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; import { libsql } from "./src/data/connection/sqlite/libsql/LibsqlConnection"; -import { $console } from "core"; +import { $console } from "core/utils"; import { createClient } from "@libsql/client"; registries.media.register("local", StorageLocalAdapter); From d1378c6c51a4f70a29ba204f1d70bc894e172e30 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 14:07:26 +0200 Subject: [PATCH 28/39] fix cloudflare r2 adapter range requests --- .../cloudflare/cloudflare-workers.adapter.ts | 1 + app/src/adapter/cloudflare/config.ts | 8 +++-- app/src/adapter/cloudflare/index.ts | 2 ++ .../cloudflare/storage/StorageR2Adapter.ts | 31 ++++++++++++------- app/src/ui/routes/media/media.settings.tsx | 3 +- 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts index d1b67124..427f8e4d 100644 --- a/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts +++ b/app/src/adapter/cloudflare/cloudflare-workers.adapter.ts @@ -33,6 +33,7 @@ export type CloudflareBkndConfig = RuntimeBkndConfig & keepAliveSeconds?: number; forceHttps?: boolean; manifest?: string; + registerMedia?: boolean | ((env: Env) => void); }; export type Context = { diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index 7f3eaee1..568b09c9 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -93,8 +93,12 @@ export function makeConfig( config: CloudflareBkndConfig, args?: CfMakeConfigArgs, ) { - if (!media_registered) { - registerMedia(args?.env as any); + if (!media_registered && config.registerMedia !== false) { + if (typeof config.registerMedia === "function") { + config.registerMedia(args?.env as any); + } else { + registerMedia(args?.env as any); + } media_registered = true; } diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 26044ba5..b8b3c4e8 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -13,6 +13,8 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; +export { StorageR2Adapter } from "./storage/StorageR2Adapter"; +export { registries } from "bknd"; // for compatibility with old code export function d1( diff --git a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts index 030855a5..7716f025 100644 --- a/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts +++ b/app/src/adapter/cloudflare/storage/StorageR2Adapter.ts @@ -1,5 +1,6 @@ import { registries } from "bknd"; import { isDebug } from "bknd/core"; +// @ts-ignore import { StringEnum } from "bknd/utils"; import { guessMimeType as guess, StorageAdapter, type FileBody } from "bknd/media"; import { getBindings } from "../bindings"; @@ -63,46 +64,49 @@ export class StorageR2Adapter extends StorageAdapter { async putObject(key: string, body: FileBody) { try { - const res = await this.bucket.put(key, body); + const res = await this.bucket.put(this.getKey(key), body); return res?.etag; } catch (e) { return undefined; } } - async listObjects( - prefix?: string, - ): Promise<{ key: string; last_modified: Date; size: number }[]> { - const list = await this.bucket.list({ limit: 50 }); + async listObjects(prefix = ""): Promise<{ key: string; last_modified: Date; size: number }[]> { + const list = await this.bucket.list({ limit: 50, prefix: this.getKey(prefix) }); return list.objects.map((item) => ({ - key: item.key, + key: item.key.replace(this.getKey(""), ""), size: item.size, last_modified: item.uploaded, })); } private async headObject(key: string): Promise { - return await this.bucket.head(key); + return await this.bucket.head(this.getKey(key)); } async objectExists(key: string): Promise { return (await this.headObject(key)) !== null; } - async getObject(key: string, headers: Headers): Promise { + async getObject(_key: string, headers: Headers): Promise { let object: R2ObjectBody | null; + const key = this.getKey(_key); + const responseHeaders = new Headers({ "Accept-Ranges": "bytes", "Content-Type": guess(key), }); + const range = headers.has("range"); + //console.log("getObject:headers", headersToObject(headers)); - if (headers.has("range")) { + if (range) { const options = isDebug() ? {} // miniflare doesn't support range requests : { range: headers, onlyIf: headers, }; + object = (await this.bucket.get(key, options)) as R2ObjectBody; if (!object) { @@ -130,13 +134,14 @@ export class StorageR2Adapter extends StorageAdapter { responseHeaders.set("Last-Modified", object.uploaded.toUTCString()); return new Response(object.body, { - status: object.range ? 206 : 200, + status: range ? 206 : 200, headers: responseHeaders, }); } private writeHttpMetadata(headers: Headers, object: R2Object | R2ObjectBody): void { let metadata = object.httpMetadata; + if (!metadata || Object.keys(metadata).length === 0) { // guessing is especially required for dev environment (miniflare) metadata = { @@ -163,13 +168,17 @@ export class StorageR2Adapter extends StorageAdapter { } async deleteObject(key: string): Promise { - await this.bucket.delete(key); + await this.bucket.delete(this.getKey(key)); } getObjectUrl(key: string): string { throw new Error("Method getObjectUrl not implemented."); } + protected getKey(key: string) { + return key; + } + toJSON(secrets?: boolean) { return { type: this.getName(), diff --git a/app/src/ui/routes/media/media.settings.tsx b/app/src/ui/routes/media/media.settings.tsx index 1635539a..e8a7cd7e 100644 --- a/app/src/ui/routes/media/media.settings.tsx +++ b/app/src/ui/routes/media/media.settings.tsx @@ -124,7 +124,8 @@ const Icons = { }; const AdapterIcon = ({ type }: { type: string }) => { - const Icon = Icons[type]; + // find icon whose name starts with type + const Icon = Object.entries(Icons).find(([key]) => type.startsWith(key))?.[1]; if (!Icon) return null; return ; }; From 56287eb05e8ffc1bf361ce132cd5b5d7ac05ece1 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 15:46:57 +0200 Subject: [PATCH 29/39] fixing sqlite imports, clean up bun and cf examples --- app/build.ts | 8 +- app/package.json | 2 +- .../bun/connection/BunSqliteConnection.ts | 5 +- app/src/adapter/cloudflare/config.ts | 4 +- .../cloudflare/connection/D1Connection.ts | 5 +- .../cloudflare/connection/DoConnection.ts | 5 +- .../node/connection/NodeSqliteConnection.ts | 2 +- .../commands/create/templates/cloudflare.ts | 116 +++++------------- examples/.gitignore | 3 +- examples/bun/static.ts | 17 --- examples/cloudflare-worker/package.json | 3 +- 11 files changed, 45 insertions(+), 125 deletions(-) delete mode 100644 examples/bun/static.ts diff --git a/app/build.ts b/app/build.ts index 824f0069..3a94a902 100644 --- a/app/build.ts +++ b/app/build.ts @@ -233,7 +233,7 @@ function baseConfig(adapter: string, overrides: Partial = {}): tsu }, external: [ /^cloudflare*/, - /^@?(hono|libsql).*?/, + /^@?(hono).*?/, /^(bknd|react|next|node).*?/, /.*\.(html)$/, ...external, @@ -260,11 +260,7 @@ async function buildAdapters() { ); await tsup.build(baseConfig("astro")); await tsup.build(baseConfig("aws")); - await tsup.build( - baseConfig("cloudflare", { - external: [/^kysely/], - }), - ); + await tsup.build(baseConfig("cloudflare")); await tsup.build({ ...baseConfig("vite"), diff --git a/app/package.json b/app/package.json index 4748fb65..1553c25a 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.3", + "version": "0.15.0-rc.5", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/adapter/bun/connection/BunSqliteConnection.ts b/app/src/adapter/bun/connection/BunSqliteConnection.ts index f4f3f1fd..900b01d0 100644 --- a/app/src/adapter/bun/connection/BunSqliteConnection.ts +++ b/app/src/adapter/bun/connection/BunSqliteConnection.ts @@ -1,8 +1,5 @@ import { Database } from "bun:sqlite"; -import { - genericSqlite, - type GenericSqliteConnection, -} from "data/connection/sqlite/GenericSqliteConnection"; +import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; export type BunSqliteConnection = GenericSqliteConnection; export type BunSqliteConnectionConfig = { diff --git a/app/src/adapter/cloudflare/config.ts b/app/src/adapter/cloudflare/config.ts index 568b09c9..8dbfff6c 100644 --- a/app/src/adapter/cloudflare/config.ts +++ b/app/src/adapter/cloudflare/config.ts @@ -128,14 +128,14 @@ export function makeConfig( // if db is given in bindings, use it if (bindings?.db) { - $console.log("Using database from bindings"); + $console.debug("Using database from bindings"); db = bindings.db; // scan for D1Database in args } else { const binding = getBinding(args.env, "D1Database"); if (binding) { - $console.log(`Using database from env "${binding.key}"`); + $console.debug(`Using database from env "${binding.key}"`); db = binding.value; } } diff --git a/app/src/adapter/cloudflare/connection/D1Connection.ts b/app/src/adapter/cloudflare/connection/D1Connection.ts index b2344e11..3462461c 100644 --- a/app/src/adapter/cloudflare/connection/D1Connection.ts +++ b/app/src/adapter/cloudflare/connection/D1Connection.ts @@ -1,9 +1,6 @@ /// -import { - genericSqlite, - type GenericSqliteConnection, -} from "data/connection/sqlite/GenericSqliteConnection"; +import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; import type { QueryResult } from "kysely"; export type D1SqliteConnection = GenericSqliteConnection; diff --git a/app/src/adapter/cloudflare/connection/DoConnection.ts b/app/src/adapter/cloudflare/connection/DoConnection.ts index c7c8500e..de7d2910 100644 --- a/app/src/adapter/cloudflare/connection/DoConnection.ts +++ b/app/src/adapter/cloudflare/connection/DoConnection.ts @@ -1,9 +1,6 @@ /// -import { - genericSqlite, - type GenericSqliteConnection, -} from "data/connection/sqlite/GenericSqliteConnection"; +import { genericSqlite, type GenericSqliteConnection } from "bknd/data"; import type { QueryResult } from "kysely"; export type D1SqliteConnection = GenericSqliteConnection; diff --git a/app/src/adapter/node/connection/NodeSqliteConnection.ts b/app/src/adapter/node/connection/NodeSqliteConnection.ts index fb298faf..c86d7e8c 100644 --- a/app/src/adapter/node/connection/NodeSqliteConnection.ts +++ b/app/src/adapter/node/connection/NodeSqliteConnection.ts @@ -1,4 +1,4 @@ -import { genericSqlite } from "data/connection/sqlite/GenericSqliteConnection"; +import { genericSqlite } from "bknd/data"; import { DatabaseSync } from "node:sqlite"; export type NodeSqliteConnectionConfig = { diff --git a/app/src/cli/commands/create/templates/cloudflare.ts b/app/src/cli/commands/create/templates/cloudflare.ts index f1f456eb..0bbab033 100644 --- a/app/src/cli/commands/create/templates/cloudflare.ts +++ b/app/src/cli/commands/create/templates/cloudflare.ts @@ -29,30 +29,8 @@ export const cloudflare = { { dir: ctx.dir }, ); - const db = ctx.skip - ? "d1" - : await $p.select({ - message: "What database do you want to use?", - options: [ - { label: "Cloudflare D1", value: "d1" }, - { label: "LibSQL", value: "libsql" }, - ], - }); - if ($p.isCancel(db)) { - process.exit(1); - } - try { - switch (db) { - case "d1": - await createD1(ctx); - break; - case "libsql": - await createLibsql(ctx); - break; - default: - throw new Error("Invalid database"); - } + await createD1(ctx); } catch (e) { const message = (e as any).message || "An error occurred"; $p.log.warn( @@ -60,7 +38,14 @@ export const cloudflare = { ); } - await createR2(ctx); + try { + await createR2(ctx); + } catch (e) { + const message = (e as any).message || "An error occurred"; + $p.log.warn( + "Couldn't add R2 bucket. You can add it manually later. Error: " + c.red(message), + ); + } }, } as const satisfies Template; @@ -89,6 +74,21 @@ async function createD1(ctx: TemplateSetupCtx) { })(), ); + await overrideJson( + WRANGLER_FILE, + (json) => ({ + ...json, + d1_databases: [ + { + binding: "DB", + database_name: name, + database_id: "00000000-0000-0000-0000-000000000000", + }, + ], + }), + { dir: ctx.dir }, + ); + if (!ctx.skip) { exec(`npx wrangler d1 create ${name}`); @@ -98,62 +98,6 @@ async function createD1(ctx: TemplateSetupCtx) { })(), ); } - - await overrideJson( - WRANGLER_FILE, - (json) => ({ - ...json, - d1_databases: [ - { - binding: "DB", - database_name: name, - database_id: uuid(), - }, - ], - }), - { dir: ctx.dir }, - ); -} - -async function createLibsql(ctx: TemplateSetupCtx) { - await overrideJson( - WRANGLER_FILE, - (json) => ({ - ...json, - vars: { - DB_URL: "http://127.0.0.1:8080", - }, - }), - { dir: ctx.dir }, - ); - - await overridePackageJson( - (pkg) => ({ - ...pkg, - scripts: { - ...pkg.scripts, - db: "turso dev", - dev: "npm run db && wrangler dev", - }, - }), - { dir: ctx.dir }, - ); - - await $p.stream.info( - (async function* () { - yield* typewriter("Database set to LibSQL"); - await wait(); - yield* typewriter( - `\nYou can now run ${c.cyan("npm run db")} to start the database and ${c.cyan("npm run dev")} to start the worker.`, - c.dim, - ); - await wait(); - yield* typewriter( - `\nAlso make sure you have Turso's CLI installed. Check their docs on how to install at ${c.cyan("https://docs.turso.tech/cli/introduction")}`, - c.dim, - ); - })(), - ); } async function createR2(ctx: TemplateSetupCtx) { @@ -197,9 +141,11 @@ async function createR2(ctx: TemplateSetupCtx) { process.exit(1); } - if (!ctx.skip) { - exec(`npx wrangler r2 bucket create ${name}`); - } + await $p.stream.info( + (async function* () { + yield* typewriter("Now running wrangler to create a R2 bucket..."); + })(), + ); await overrideJson( WRANGLER_FILE, @@ -214,4 +160,8 @@ async function createR2(ctx: TemplateSetupCtx) { }), { dir: ctx.dir }, ); + + if (!ctx.skip) { + exec(`npx wrangler r2 bucket create ${name}`); + } } diff --git a/examples/.gitignore b/examples/.gitignore index c9181809..f0f60a69 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1 +1,2 @@ -*/package-lock.json \ No newline at end of file +*/package-lock.json +*/bun.lock \ No newline at end of file diff --git a/examples/bun/static.ts b/examples/bun/static.ts deleted file mode 100644 index 559268cb..00000000 --- a/examples/bun/static.ts +++ /dev/null @@ -1,17 +0,0 @@ -// @ts-ignore somehow causes types:build issues on app -import { type BunBkndConfig, serve } from "bknd/adapter/bun"; - -// Actually, all it takes is the following line: -// serve(); - -// this is optional, if omitted, it uses an in-memory database -const config: BunBkndConfig = { - adminOptions: { - assets_path: "https://cdn.bknd.io/0.9.0-rc.1/", - }, - connection: { - url: "file:data.db", - }, -}; - -serve(config); diff --git a/examples/cloudflare-worker/package.json b/examples/cloudflare-worker/package.json index 2b8a6c3b..d2831596 100644 --- a/examples/cloudflare-worker/package.json +++ b/examples/cloudflare-worker/package.json @@ -8,8 +8,7 @@ "typegen": "wrangler types" }, "dependencies": { - "bknd": "file:../../app", - "kysely-d1": "^0.4.0" + "bknd": "file:../../app" }, "devDependencies": { "typescript": "^5.8.3", From e6ee75c712648f124715d049616fcf053de85552 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 16:08:33 +0200 Subject: [PATCH 30/39] update edge sqlite import (libsql) --- app/package.json | 2 +- app/src/adapter/sqlite/edge.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/package.json b/app/package.json index 1553c25a..02a5f06f 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.5", + "version": "0.15.0-rc.7", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/adapter/sqlite/edge.ts b/app/src/adapter/sqlite/edge.ts index f1de953f..f5b584f2 100644 --- a/app/src/adapter/sqlite/edge.ts +++ b/app/src/adapter/sqlite/edge.ts @@ -1,5 +1,4 @@ -import type { Connection } from "bknd/data"; -import { libsql } from "../../data/connection/sqlite/libsql/LibsqlConnection"; +import { type Connection, libsql } from "bknd/data"; export function sqlite(config: { url: string }): Connection { return libsql(config); From 80034b9b0a7227416637be907909a850855c953c Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 16:13:22 +0200 Subject: [PATCH 31/39] fix plugins imports --- app/src/data/index.ts | 7 +++++++ app/src/plugins/dev/sync-types.plugin.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/data/index.ts b/app/src/data/index.ts index 32e914d3..5db48962 100644 --- a/app/src/data/index.ts +++ b/app/src/data/index.ts @@ -36,3 +36,10 @@ export { genericSqliteUtils, type GenericSqliteConnection, } from "./connection/sqlite/GenericSqliteConnection"; + +export { + EntityTypescript, + type EntityTypescriptOptions, + type TEntityTSType, + type TFieldTSType, +} from "./entities/EntityTypescript"; diff --git a/app/src/plugins/dev/sync-types.plugin.ts b/app/src/plugins/dev/sync-types.plugin.ts index 484f8b65..aa6756a2 100644 --- a/app/src/plugins/dev/sync-types.plugin.ts +++ b/app/src/plugins/dev/sync-types.plugin.ts @@ -1,5 +1,5 @@ import { App, type AppPlugin } from "bknd"; -import { EntityTypescript } from "data/entities/EntityTypescript"; +import { EntityTypescript } from "bknd/data"; export type SyncTypesOptions = { enabled?: boolean; From 45138c25f0c822bb666ab700892221289ee45db8 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 16:36:06 +0200 Subject: [PATCH 32/39] refactor auth/media entities to separate files, suppress node:sqlite warning --- app/package.json | 2 +- app/src/auth/AppAuth.ts | 16 +--- app/src/auth/auth-entities.ts | 14 +++ app/src/data/entities/EntityTypescript.ts | 7 +- app/src/data/prototype/index.ts | 104 +++++++++++----------- app/src/index.ts | 11 +++ app/src/media/AppMedia.ts | 25 +----- app/src/media/media-entities.ts | 14 +++ 8 files changed, 104 insertions(+), 89 deletions(-) create mode 100644 app/src/auth/auth-entities.ts create mode 100644 app/src/media/media-entities.ts diff --git a/app/package.json b/app/package.json index 02a5f06f..f0624c21 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.7", + "version": "0.15.0-rc.8", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/auth/AppAuth.ts b/app/src/auth/AppAuth.ts index ac78b9f0..474e86a5 100644 --- a/app/src/auth/AppAuth.ts +++ b/app/src/auth/AppAuth.ts @@ -3,12 +3,13 @@ import type { PasswordStrategy } from "auth/authenticate/strategies"; import type { DB } from "core"; import { $console, secureRandomString, transformObject } from "core/utils"; import type { Entity, EntityManager } from "data"; -import { em, entity, enumm, type FieldSchema, text } from "data/prototype"; +import { em, entity, enumm, type FieldSchema } from "data/prototype"; import { Module } from "modules/Module"; import { AuthController } from "./api/AuthController"; import { type AppAuthSchema, authConfigSchema, STRATEGIES } from "./auth-schema"; import { AppUserPool } from "auth/AppUserPool"; import type { AppEntity } from "core/config"; +import { usersFields } from "./auth-entities"; export type UserFieldSchema = FieldSchema; declare module "core" { @@ -125,18 +126,7 @@ export class AppAuth extends Module { return this.em.entity(entity_name) as any; } - static usersFields = { - email: text().required(), - strategy: text({ - fillable: ["create"], - hidden: ["update", "form"], - }).required(), - strategy_value: text({ - fillable: ["create"], - hidden: ["read", "table", "update", "form"], - }).required(), - role: text(), - }; + static usersFields = usersFields; registerEntities() { const users = this.getUsersEntity(true); diff --git a/app/src/auth/auth-entities.ts b/app/src/auth/auth-entities.ts new file mode 100644 index 00000000..5a9aea4d --- /dev/null +++ b/app/src/auth/auth-entities.ts @@ -0,0 +1,14 @@ +import { text } from "data/prototype"; + +export const usersFields = { + email: text().required(), + strategy: text({ + fillable: ["create"], + hidden: ["update", "form"], + }).required(), + strategy_value: text({ + fillable: ["create"], + hidden: ["read", "table", "update", "form"], + }).required(), + role: text(), +}; diff --git a/app/src/data/entities/EntityTypescript.ts b/app/src/data/entities/EntityTypescript.ts index b0aa89ea..197c22df 100644 --- a/app/src/data/entities/EntityTypescript.ts +++ b/app/src/data/entities/EntityTypescript.ts @@ -1,6 +1,7 @@ import type { Entity, EntityManager, EntityRelation, TEntityType } from "data"; import { autoFormatString } from "core/utils"; -import { AppAuth, AppMedia } from "modules"; +import { usersFields } from "auth/auth-entities"; +import { mediaFields } from "media/media-entities"; export type TEntityTSType = { name: string; @@ -32,8 +33,8 @@ export type EntityTypescriptOptions = { // keep a local copy here until properties have a type const systemEntities = { - users: AppAuth.usersFields, - media: AppMedia.mediaFields, + users: usersFields, + media: mediaFields, }; export class EntityTypescript { diff --git a/app/src/data/prototype/index.ts b/app/src/data/prototype/index.ts index 489607cc..4f25aebf 100644 --- a/app/src/data/prototype/index.ts +++ b/app/src/data/prototype/index.ts @@ -3,16 +3,13 @@ import { EntityManager } from "data/entities/EntityManager"; import type { Generated } from "kysely"; import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; import type { ModuleConfigs } from "modules"; + import { BooleanField, type BooleanFieldConfig, - type Connection, DateField, type DateFieldConfig, - Entity, - type EntityConfig, EntityIndex, - type EntityRelation, EnumField, type EnumFieldConfig, type Field, @@ -20,20 +17,27 @@ import { type JsonFieldConfig, JsonSchemaField, type JsonSchemaFieldConfig, + NumberField, + type NumberFieldConfig, + TextField, + type TextFieldConfig, +} from "data/fields"; + +import { Entity, type EntityConfig, type TEntityType } from "data/entities"; + +import type { Connection } from "data/connection"; + +import { + type EntityRelation, ManyToManyRelation, type ManyToManyRelationConfig, ManyToOneRelation, type ManyToOneRelationConfig, - NumberField, - type NumberFieldConfig, OneToOneRelation, type OneToOneRelationConfig, PolymorphicRelation, type PolymorphicRelationConfig, - type TEntityType, - TextField, - type TextFieldConfig, -} from "../index"; +} from "data/relations"; type Options = { entity: { name: string; fields: Record> }; @@ -61,6 +65,46 @@ const FieldMap = { } as const; type TFieldType = keyof typeof FieldMap; +export class FieldPrototype { + constructor( + public type: TFieldType, + public config: any, + public is_required: boolean, + ) {} + + required() { + this.is_required = true; + return this; + } + + getField(o: Options): Field { + if (!FieldMap[this.type]) { + throw new Error(`Unknown field type: ${this.type}`); + } + try { + return FieldMap[this.type](o) as unknown as Field; + } catch (e) { + throw new Error(`Faild to construct field "${this.type}": ${e}`); + } + } + + make(field_name: string): Field { + if (!FieldMap[this.type]) { + throw new Error(`Unknown field type: ${this.type}`); + } + try { + return FieldMap[this.type]({ + entity: { name: "unknown", fields: {} }, + field_name, + config: this.config, + is_required: this.is_required, + }) as unknown as Field; + } catch (e) { + throw new Error(`Faild to construct field "${this.type}": ${e}`); + } + } +} + export function text( config?: Omit, ): TextField & { required: () => TextField } { @@ -132,46 +176,6 @@ export function make>(name: string, field: Actual throw new Error("Invalid field"); } -export class FieldPrototype { - constructor( - public type: TFieldType, - public config: any, - public is_required: boolean, - ) {} - - required() { - this.is_required = true; - return this; - } - - getField(o: Options): Field { - if (!FieldMap[this.type]) { - throw new Error(`Unknown field type: ${this.type}`); - } - try { - return FieldMap[this.type](o) as unknown as Field; - } catch (e) { - throw new Error(`Faild to construct field "${this.type}": ${e}`); - } - } - - make(field_name: string): Field { - if (!FieldMap[this.type]) { - throw new Error(`Unknown field type: ${this.type}`); - } - try { - return FieldMap[this.type]({ - entity: { name: "unknown", fields: {} }, - field_name, - config: this.config, - is_required: this.is_required, - }) as unknown as Field; - } catch (e) { - throw new Error(`Faild to construct field "${this.type}": ${e}`); - } - } -} - export function entity< EntityName extends string, Fields extends Record>, diff --git a/app/src/index.ts b/app/src/index.ts index afac83d5..ed12dbb9 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -1,3 +1,14 @@ +try { + /** + * Adding this to avoid warnings from node:sqlite being experimental + */ + const { emitWarning } = process; + process.emitWarning = (warning: string, ...args: any[]) => { + if (warning.includes("SQLite is an experimental feature")) return; + return emitWarning(warning, ...args); + }; +} catch (e) {} + export { App, createApp, diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index 36fce419..235a927b 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -3,18 +3,10 @@ import { $console } from "core/utils"; import type { Entity, EntityManager } from "data"; import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; import { Module } from "modules/Module"; -import { - type FieldSchema, - boolean, - datetime, - em, - entity, - json, - number, - text, -} from "../data/prototype"; +import { type FieldSchema, em, entity } from "../data/prototype"; import { MediaController } from "./api/MediaController"; import { buildMediaSchema, type mediaConfigSchema, registry } from "./media-schema"; +import { mediaFields } from "./media-entities"; export type MediaFieldSchema = FieldSchema; declare module "core" { @@ -95,18 +87,7 @@ export class AppMedia extends Module { }; } - static mediaFields = { - path: text().required(), - folder: boolean({ default_value: false, hidden: true, fillable: ["create"] }), - mime_type: text(), - size: number(), - scope: text({ hidden: true, fillable: ["create"] }), - etag: text(), - modified_at: datetime(), - reference: text(), - entity_id: number(), - metadata: json(), - }; + static mediaFields = mediaFields; getMediaEntity(forceCreate?: boolean): Entity<"media", typeof AppMedia.mediaFields> { const entity_name = this.config.entity_name; diff --git a/app/src/media/media-entities.ts b/app/src/media/media-entities.ts new file mode 100644 index 00000000..a074b180 --- /dev/null +++ b/app/src/media/media-entities.ts @@ -0,0 +1,14 @@ +import { boolean, datetime, json, number, text } from "data/prototype"; + +export const mediaFields = { + path: text().required(), + folder: boolean({ default_value: false, hidden: true, fillable: ["create"] }), + mime_type: text(), + size: number(), + scope: text({ hidden: true, fillable: ["create"] }), + etag: text(), + modified_at: datetime(), + reference: text(), + entity_id: number(), + metadata: json(), +}; From 22b54862e88b73de4d670091cf9dc734ff00c528 Mon Sep 17 00:00:00 2001 From: dswbx Date: Wed, 2 Jul 2025 18:14:35 +0200 Subject: [PATCH 33/39] admin: fix theme flash --- app/src/modules/server/AdminController.tsx | 2 +- app/src/ui/Admin.tsx | 2 +- app/src/ui/layouts/AppShell/Header.tsx | 4 +--- bun.lock | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index 35ebc819..227e22b8 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -51,7 +51,7 @@ export class AdminController extends Controller { basepath: this._options.basepath ?? "/", adminBasepath: this._options.adminBasepath ?? "", assetsPath: this._options.assetsPath ?? config.server.assets_path, - theme: this._options.theme ?? "system", + //theme: this._options.theme ?? "system", logo_return_path: this._options.logoReturnPath ?? "/", }; } diff --git a/app/src/ui/Admin.tsx b/app/src/ui/Admin.tsx index 86b4e8ed..df142acd 100644 --- a/app/src/ui/Admin.tsx +++ b/app/src/ui/Admin.tsx @@ -55,7 +55,7 @@ export default function Admin({ const Skeleton = ({ theme }: { theme?: any }) => { const t = useTheme(); - const actualTheme = theme ?? t.theme; + const actualTheme = theme && ["dark", "light"].includes(theme) ? theme : t.theme; return (
diff --git a/app/src/ui/layouts/AppShell/Header.tsx b/app/src/ui/layouts/AppShell/Header.tsx index 0b313c5a..629d5374 100644 --- a/app/src/ui/layouts/AppShell/Header.tsx +++ b/app/src/ui/layouts/AppShell/Header.tsx @@ -185,9 +185,7 @@ function UserMenu() { } } - if (!options.theme) { - items.push(() => ); - } + items.push(() => ); items.push(() => (
{getVersion()} diff --git a/bun.lock b/bun.lock index 434b81f2..008d7b47 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.15.0-rc.3", + "version": "0.15.0-rc.8", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", From 2f684765deed840d4e07ab55f2955a810ecf1f4a Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 3 Jul 2025 14:11:58 +0200 Subject: [PATCH 34/39] libsql: update typings to allow minimal client fns (execute, batch) --- .../data/connection/sqlite/libsql/LibsqlConnection.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts index c99ad590..32de8fc4 100644 --- a/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/libsql/LibsqlConnection.ts @@ -1,4 +1,4 @@ -import type { Client, Config, ResultSet } from "@libsql/client"; +import type { Client, Config, InStatement, ResultSet, TransactionMode } from "@libsql/client"; import { createClient } from "libsql-stateless-easy"; import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin"; import { @@ -10,7 +10,12 @@ import type { QueryResult } from "kysely"; export type LibsqlConnection = GenericSqliteConnection; export type LibSqlCredentials = Config; -function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { +export type LibsqlClientFns = { + execute: (statement: InStatement) => Promise; + batch: (statements: InStatement[], mode?: TransactionMode) => Promise; +}; + +function getClient(clientOrCredentials: Client | LibSqlCredentials | LibsqlClientFns): Client { if (clientOrCredentials && "url" in clientOrCredentials) { const { url, authToken } = clientOrCredentials; return createClient({ url, authToken }); @@ -19,7 +24,7 @@ function getClient(clientOrCredentials: Client | LibSqlCredentials): Client { return clientOrCredentials as Client; } -export function libsql(config: LibSqlCredentials | Client) { +export function libsql(config: LibSqlCredentials | Client | LibsqlClientFns) { const db = getClient(config); return genericSqlite( From a5959acb342f8ee50fb5a837a8f5de3148963d49 Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 3 Jul 2025 15:06:21 +0200 Subject: [PATCH 35/39] fix admin's assetPath for remote static assets --- app/package.json | 2 +- app/src/adapter/sqlite/bun.ts | 2 +- app/src/adapter/sqlite/node.ts | 2 +- app/src/modules/server/AdminController.tsx | 2 +- bun.lock | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/package.json b/app/package.json index f0624c21..fd1460f2 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.15.0-rc.8", + "version": "0.15.0-rc.10", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/adapter/sqlite/bun.ts b/app/src/adapter/sqlite/bun.ts index eaf02c4b..6d549181 100644 --- a/app/src/adapter/sqlite/bun.ts +++ b/app/src/adapter/sqlite/bun.ts @@ -1,6 +1,6 @@ import type { Connection } from "bknd/data"; import { bunSqlite } from "../bun/connection/BunSqliteConnection"; -export function sqlite(config: { url: string }): Connection { +export function sqlite(config?: { url: string }): Connection { return bunSqlite(config); } diff --git a/app/src/adapter/sqlite/node.ts b/app/src/adapter/sqlite/node.ts index 7bc6ebda..f14a8569 100644 --- a/app/src/adapter/sqlite/node.ts +++ b/app/src/adapter/sqlite/node.ts @@ -1,6 +1,6 @@ import type { Connection } from "bknd/data"; import { nodeSqlite } from "../node/connection/NodeSqliteConnection"; -export function sqlite(config: { url: string }): Connection { +export function sqlite(config?: { url: string }): Connection { return nodeSqlite(config); } diff --git a/app/src/modules/server/AdminController.tsx b/app/src/modules/server/AdminController.tsx index 227e22b8..5cb66d6c 100644 --- a/app/src/modules/server/AdminController.tsx +++ b/app/src/modules/server/AdminController.tsx @@ -195,7 +195,7 @@ export class AdminController extends Controller { if (isProd) { let manifest: any; if (this.options.assetsPath.startsWith("http")) { - manifest = await fetch(this.options.assetsPath + "manifest.json", { + manifest = await fetch(this.options.assetsPath + ".vite/manifest.json", { headers: { Accept: "application/json", }, diff --git a/bun.lock b/bun.lock index 008d7b47..3a98944d 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.15.0-rc.8", + "version": "0.15.0-rc.9", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", From 109c72e84f18e43ca3ab2793c7d041ae02ce702c Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 3 Jul 2025 15:31:14 +0200 Subject: [PATCH 36/39] update readme and docs on dbs --- README.md | 8 ++++++++ bun.lock | 2 +- docs/usage/database.mdx | 14 ++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d473e360..41ae4b14 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,18 @@

bknd simplifies app development by providing a fully functional backend for database management, authentication, media and workflows. Being lightweight and built on Web Standards, it can be deployed nearly anywhere, including running inside your framework of choice. No more deploying multiple separate services! +* **Runtimes**: Node.js 22+, Bun 1.0+, Deno, Browser, Cloudflare Workers/Pages, Vercel, Netlify, AWS Lambda, etc. +* **Databases**: + * SQLite: LibSQL, Node SQLite, Bun SQLite, Cloudflare D1, Cloudflare Durable Objects SQLite, SQLocal + * Postgres: Vanilla Postgres, Supabase, Neon, Xata +* **Frameworks**: React, Next.js, React Router, Astro, Vite, Waku +* **Storage**: AWS S3, S3-compatible (Tigris, R2, Minio, etc.), Cloudflare R2 (binding), Cloudinary **For documentation and examples, please visit https://docs.bknd.io.** > [!WARNING] +> This project requires Node.js 22 or higher (because of `node:sqlite`). +> > Please keep in mind that **bknd** is still under active development > and therefore full backward compatibility is not guaranteed before reaching v1.0.0. diff --git a/bun.lock b/bun.lock index 3a98944d..7f982e0a 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "app": { "name": "bknd", - "version": "0.15.0-rc.9", + "version": "0.15.0-rc.10", "bin": "./dist/cli/index.js", "dependencies": { "@cfworker/json-schema": "^4.1.1", diff --git a/docs/usage/database.mdx b/docs/usage/database.mdx index dc520d21..8a43de33 100644 --- a/docs/usage/database.mdx +++ b/docs/usage/database.mdx @@ -107,8 +107,8 @@ import { libsql } from "bknd/data"; export default { connection: libsql({ - url: "libsql://your-database-url.turso.io", - authToken: "your-auth-token", + url: "libsql://.turso.io", + authToken: "", }), } as const satisfies BkndConfig; ``` @@ -120,11 +120,13 @@ import type { BkndConfig } from "bknd"; import { libsql } from "bknd/data"; import { createClient } from "@libsql/client"; +const client = createClient({ + url: "libsql://.turso.io", + authToken: "", +}) + export default { - connection: libsql(createClient({ - url: "libsql://your-database-url.turso.io", - authToken: "your-auth-token", - })), + connection: libsql(client), } as const satisfies BkndConfig; ``` From b35ee36fb1619dceffc23f08a90a0941c0a99f8e Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 5 Jul 2025 09:42:53 +0200 Subject: [PATCH 37/39] admin: fix back behavior to not rely on history object --- app/src/ui/layouts/AppShell/Breadcrumbs2.tsx | 20 +++---------------- app/src/ui/lib/routes.ts | 18 ++++++++++++++++- app/src/ui/routes/data/data.$entity.$id.tsx | 13 +++++------- .../ui/routes/data/data.$entity.create.tsx | 15 +++++--------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/app/src/ui/layouts/AppShell/Breadcrumbs2.tsx b/app/src/ui/layouts/AppShell/Breadcrumbs2.tsx index b495b435..2b7c5621 100644 --- a/app/src/ui/layouts/AppShell/Breadcrumbs2.tsx +++ b/app/src/ui/layouts/AppShell/Breadcrumbs2.tsx @@ -4,6 +4,7 @@ import { Link, useLocation } from "wouter"; import { IconButton } from "../../components/buttons/IconButton"; import { Dropdown } from "../../components/overlay/Dropdown"; import { useEvent } from "../../hooks/use-event"; +import { useNavigate } from "ui/lib/routes"; type Breadcrumb = { label: string | Element; @@ -17,26 +18,11 @@ export type Breadcrumbs2Props = { }; export const Breadcrumbs2 = ({ path: _path, backTo, onBack }: Breadcrumbs2Props) => { - const [_, navigate] = useLocation(); - const location = window.location.pathname; + const [, , _goBack] = useNavigate(); const path = Array.isArray(_path) ? _path : [_path]; - const loc = location.split("/").filter((v) => v !== ""); const hasBack = path.length > 1; - const goBack = onBack - ? onBack - : useEvent(() => { - if (backTo) { - navigate(backTo, { replace: true }); - return; - } else if (_path.length > 0 && _path[0]?.href) { - navigate(_path[0].href, { replace: true }); - return; - } - - const href = loc.slice(0, path.length + 1).join("/"); - navigate(`~/${href}`, { replace: true }); - }); + const goBack = onBack ? onBack : () => _goBack({ fallback: backTo }); const crumbs = useMemo( () => diff --git a/app/src/ui/lib/routes.ts b/app/src/ui/lib/routes.ts index b94e550f..42b896ba 100644 --- a/app/src/ui/lib/routes.ts +++ b/app/src/ui/lib/routes.ts @@ -102,13 +102,29 @@ export function useNavigate() { } const _url = options?.absolute ? `~/${basepath}${url}`.replace(/\/+/g, "/") : url; + const state = { + ...options?.state, + referrer: location, + }; + navigate(options?.query ? withQuery(_url, options?.query) : _url, { replace: options?.replace, - state: options?.state, + state, }); }); }, location, + (opts?: { fallback?: string }) => { + const state = window.history.state; + if (state?.referrer) { + //window.history.replaceState(state, "", state.referrer); + navigate(state.referrer, { replace: true }); + } else if (opts?.fallback) { + navigate(opts.fallback, { replace: true }); + } else { + window.history.back(); + } + }, ] as const; } diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index 978573ab..e531452d 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -31,7 +31,7 @@ function DataEntityUpdateImpl({ params }) { const entityId = params.id as PrimaryFieldType; const [error, setError] = useState(null); - const [navigate] = useNavigate(); + const [navigate, _, _goBack] = useNavigate(); useBrowserTitle(["Data", entity.label, `#${entityId}`]); const targetRelations = relations.listableRelationsOf(entity); @@ -52,9 +52,8 @@ function DataEntityUpdateImpl({ params }) { }, ); - function goBack() { - window.history.go(-1); - } + const backHref = routes.data.entity.list(entity.name); + const goBack = () => _goBack({ fallback: backHref }); async function onSubmitted(changeSet?: EntityData) { //return; @@ -162,10 +161,8 @@ function DataEntityUpdateImpl({ params }) { className="pl-3" > {$q.isLoading ? ( diff --git a/app/src/ui/routes/data/data.$entity.create.tsx b/app/src/ui/routes/data/data.$entity.create.tsx index bb83eaf6..07b2bb58 100644 --- a/app/src/ui/routes/data/data.$entity.create.tsx +++ b/app/src/ui/routes/data/data.$entity.create.tsx @@ -8,13 +8,14 @@ import { useBrowserTitle } from "ui/hooks/use-browser-title"; import { useSearch } from "ui/hooks/use-search"; import * as AppShell from "ui/layouts/AppShell/AppShell"; import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2"; -import { routes } from "ui/lib/routes"; +import { routes, useNavigate } from "ui/lib/routes"; import { EntityForm } from "ui/modules/data/components/EntityForm"; import { useEntityForm } from "ui/modules/data/hooks/useEntityForm"; import { s } from "core/object/schema"; export function DataEntityCreate({ params }) { const { $data } = useBkndData(); + const [navigate, _, _goBack] = useNavigate(); const entity = $data.entity(params.entity as string); if (!entity) { return ; @@ -30,9 +31,8 @@ export function DataEntityCreate({ params }) { // @todo: use entity schema for prefilling const search = useSearch(s.object({}), {}); - function goBack() { - window.history.go(-1); - } + const backHref = routes.data.entity.list(entity.name); + const goBack = () => _goBack({ fallback: backHref }); async function onSubmitted(changeSet?: EntityData) { console.log("create:changeSet", changeSet); @@ -80,12 +80,7 @@ export function DataEntityCreate({ params }) { } > - + {error && ( From e939debba58453066edd4799fb402c9fa352aa04 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 5 Jul 2025 09:43:14 +0200 Subject: [PATCH 38/39] auth: add delete auth after flash in case only one is picked up --- app/src/auth/authenticate/Authenticator.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/auth/authenticate/Authenticator.ts b/app/src/auth/authenticate/Authenticator.ts index 29b65971..d28ec4a2 100644 --- a/app/src/auth/authenticate/Authenticator.ts +++ b/app/src/auth/authenticate/Authenticator.ts @@ -342,9 +342,9 @@ export class Authenticator = Record< await setSignedCookie(c, "auth", token, secret, this.cookieOptions); } - private async deleteAuthCookie(c: Context) { + private deleteAuthCookie(c: Context) { $console.debug("deleting auth cookie"); - await deleteCookie(c, "auth", this.cookieOptions); + deleteCookie(c, "auth", this.cookieOptions); } async logout(c: Context) { @@ -353,9 +353,13 @@ export class Authenticator = Record< const cookie = await this.getAuthCookie(c); if (cookie) { - await this.deleteAuthCookie(c); - await addFlashMessage(c, "Signed out", "info"); + addFlashMessage(c, "Signed out", "info"); } + + // on waku, only one cookie setting is performed + // therefore adding deleting cookie at the end + // as the flash isn't that important + this.deleteAuthCookie(c); } // @todo: move this to a server helper From 5355f2593ecc431f7e0d47bdc91b8df6fb5b3cb2 Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 5 Jul 2025 09:43:22 +0200 Subject: [PATCH 39/39] readme: add filesystem --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41ae4b14..ab86304e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ bknd simplifies app development by providing a fully functional backend for data * SQLite: LibSQL, Node SQLite, Bun SQLite, Cloudflare D1, Cloudflare Durable Objects SQLite, SQLocal * Postgres: Vanilla Postgres, Supabase, Neon, Xata * **Frameworks**: React, Next.js, React Router, Astro, Vite, Waku -* **Storage**: AWS S3, S3-compatible (Tigris, R2, Minio, etc.), Cloudflare R2 (binding), Cloudinary +* **Storage**: AWS S3, S3-compatible (Tigris, R2, Minio, etc.), Cloudflare R2 (binding), Cloudinary, Filesystem **For documentation and examples, please visit https://docs.bknd.io.**