diff --git a/app/package.json b/app/package.json index 0fc8819..6832025 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 d7eb5a0..2f3ccfa 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 0000000..762e0ea --- /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 0000000..e3637f9 --- /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 60ad9df..fb298fa 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 97b09ec..f1de953 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 2a0a5e5..0000000 --- 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 5bd4543..03709cc 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 d6f14d1..bd15fbb 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 cf68962..1918198 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 0000000..1a88c3d --- /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 f41bd8b..bd0e647 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 9fb5a35..32e914d 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 b4ebc9e..f070339 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 9181dc9..c1c91bc 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 7a37352..434b81f 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=="],