Merge branch 'release/0.15' into feat/jsonv-refactor

# Conflicts:
#	app/build.ts
#	app/package.json
#	app/src/App.ts
#	app/src/adapter/cloudflare/storage/StorageR2Adapter.ts
#	app/src/auth/authenticate/Authenticator.ts
#	app/src/auth/authenticate/strategies/PasswordStrategy.ts
#	app/src/data/entities/Entity.ts
#	app/src/data/fields/DateField.ts
#	app/src/data/server/query.ts
#	app/src/flows/flows/triggers/EventTrigger.ts
#	app/src/flows/tasks/presets/LogTask.ts
#	app/src/media/AppMedia.ts
#	app/src/modules/server/AppServer.ts
#	app/src/modules/server/SystemController.ts
#	app/vite.dev.ts
#	bun.lock
This commit is contained in:
dswbx
2025-07-02 17:18:12 +02:00
84 changed files with 1027 additions and 626 deletions
+9 -6
View File
@@ -60,7 +60,14 @@ function banner(title: string) {
} }
// collection of always-external packages // 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 * Building backend and general API
@@ -253,11 +260,7 @@ async function buildAdapters() {
); );
await tsup.build(baseConfig("astro")); await tsup.build(baseConfig("astro"));
await tsup.build(baseConfig("aws")); await tsup.build(baseConfig("aws"));
await tsup.build( await tsup.build(baseConfig("cloudflare"));
baseConfig("cloudflare", {
external: [/^kysely/],
}),
);
await tsup.build({ await tsup.build({
...baseConfig("vite"), ...baseConfig("vite"),
+8 -9
View File
@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"bin": "./dist/cli/index.js", "bin": "./dist/cli/index.js",
"version": "0.15.0-rc.3", "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.", "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", "homepage": "https://bknd.io",
"repository": { "repository": {
@@ -82,7 +82,6 @@
"@hono/vite-dev-server": "^0.19.1", "@hono/vite-dev-server": "^0.19.1",
"@hookform/resolvers": "^4.1.3", "@hookform/resolvers": "^4.1.3",
"@libsql/client": "^0.15.9", "@libsql/client": "^0.15.9",
"@libsql/kysely-libsql": "^0.4.1",
"@mantine/modals": "^7.17.1", "@mantine/modals": "^7.17.1",
"@mantine/notifications": "^7.17.1", "@mantine/notifications": "^7.17.1",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
@@ -188,6 +187,11 @@
"import": "./dist/media/index.js", "import": "./dist/media/index.js",
"require": "./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": { "./adapter/sqlite": {
"types": "./dist/types/adapter/sqlite/edge.d.ts", "types": "./dist/types/adapter/sqlite/edge.d.ts",
"import": { "import": {
@@ -202,11 +206,6 @@
}, },
"require": "./dist/adapter/sqlite/node.js" "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": { "./adapter/cloudflare": {
"types": "./dist/types/adapter/cloudflare/index.d.ts", "types": "./dist/types/adapter/cloudflare/index.d.ts",
"import": "./dist/adapter/cloudflare/index.js", "import": "./dist/adapter/cloudflare/index.js",
@@ -263,14 +262,14 @@
"cli": ["./dist/types/cli/index.d.ts"], "cli": ["./dist/types/cli/index.d.ts"],
"media": ["./dist/types/media/index.d.ts"], "media": ["./dist/types/media/index.d.ts"],
"plugins": ["./dist/types/plugins/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": ["./dist/types/adapter/index.d.ts"],
"adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"],
"adapter/vite": ["./dist/types/adapter/vite/index.d.ts"], "adapter/vite": ["./dist/types/adapter/vite/index.d.ts"],
"adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"], "adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"],
"adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"],
"adapter/bun": ["./dist/types/adapter/bun/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": { "publishConfig": {
+6 -3
View File
@@ -1,5 +1,5 @@
import type { CreateUserPayload } from "auth/AppAuth"; import type { CreateUserPayload } from "auth/AppAuth";
import { $console } from "core/console"; import { $console } from "core/utils";
import { Event } from "core/events"; import { Event } from "core/events";
import type { em as prototypeEm } from "data/prototype"; import type { em as prototypeEm } from "data/prototype";
import { Connection } from "data/connection/Connection"; import { Connection } from "data/connection/Connection";
@@ -34,7 +34,10 @@ export type AppPluginConfig = {
export type AppPlugin = (app: App) => AppPluginConfig; export type AppPlugin = (app: App) => AppPluginConfig;
abstract class AppEvent<A = {}> extends Event<{ app: App } & A> {} abstract class AppEvent<A = {}> 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"; static override slug = "app-config-updated";
} }
export class AppBuiltEvent extends AppEvent { export class AppBuiltEvent extends AppEvent {
@@ -265,7 +268,7 @@ export class App<C extends Connection = Connection, Options extends AppOptions =
$console.log("App config updated", module); $console.log("App config updated", module);
// @todo: potentially double syncing // @todo: potentially double syncing
await this.build({ sync: true }); await this.build({ sync: true });
await this.emgr.emit(new AppConfigUpdatedEvent({ app: this })); await this.emgr.emit(new AppConfigUpdatedEvent({ app: this, module, config }));
} }
protected async onFirstBoot() { protected async onFirstBoot() {
@@ -6,7 +6,10 @@ import { Database } from "bun:sqlite";
describe("BunSqliteConnection", () => { describe("BunSqliteConnection", () => {
connectionTestSuite(bunTestRunner, { connectionTestSuite(bunTestRunner, {
makeConnection: () => bunSqlite({ database: new Database(":memory:") }), makeConnection: () => ({
connection: bunSqlite({ database: new Database(":memory:") }),
dispose: async () => {},
}),
rawDialectDetails: [], rawDialectDetails: [],
}); });
}); });
@@ -1,8 +1,5 @@
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
genericSqlite,
type GenericSqliteConnection,
} from "data/connection/sqlite/GenericSqliteConnection";
export type BunSqliteConnection = GenericSqliteConnection<Database>; export type BunSqliteConnection = GenericSqliteConnection<Database>;
export type BunSqliteConnectionConfig = { export type BunSqliteConnectionConfig = {
+4 -1
View File
@@ -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 = { export const bunTestRunner = {
describe, describe,
expect, expect,
test, test,
mock, mock,
beforeEach,
afterEach,
afterAll,
}; };
@@ -7,7 +7,7 @@ import { getFresh } from "./modes/fresh";
import { getCached } from "./modes/cached"; import { getCached } from "./modes/cached";
import { getDurable } from "./modes/durable"; import { getDurable } from "./modes/durable";
import type { App } from "bknd"; import type { App } from "bknd";
import { $console } from "core"; import { $console } from "core/utils";
declare global { declare global {
namespace Cloudflare { namespace Cloudflare {
@@ -33,6 +33,7 @@ export type CloudflareBkndConfig<Env = CloudflareEnv> = RuntimeBkndConfig<Env> &
keepAliveSeconds?: number; keepAliveSeconds?: number;
forceHttps?: boolean; forceHttps?: boolean;
manifest?: string; manifest?: string;
registerMedia?: boolean | ((env: Env) => void);
}; };
export type Context<Env = CloudflareEnv> = { export type Context<Env = CloudflareEnv> = {
+18 -10
View File
@@ -2,12 +2,13 @@
import { registerMedia } from "./storage/StorageR2Adapter"; import { registerMedia } from "./storage/StorageR2Adapter";
import { getBinding } from "./bindings"; 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 type { CloudflareBkndConfig, CloudflareEnv } from ".";
import { App } from "bknd"; import { App } from "bknd";
import { makeConfig as makeAdapterConfig } from "bknd/adapter"; import { makeConfig as makeAdapterConfig } from "bknd/adapter";
import type { Context, ExecutionContext } from "hono"; import type { Context, ExecutionContext } from "hono";
import { $console } from "core"; import { $console } from "core/utils";
import { setCookie } from "hono/cookie"; import { setCookie } from "hono/cookie";
import { sqlite } from "bknd/adapter/sqlite"; import { sqlite } from "bknd/adapter/sqlite";
@@ -92,8 +93,12 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>, config: CloudflareBkndConfig<Env>,
args?: CfMakeConfigArgs<Env>, args?: CfMakeConfigArgs<Env>,
) { ) {
if (!media_registered) { if (!media_registered && config.registerMedia !== false) {
registerMedia(args?.env as any); if (typeof config.registerMedia === "function") {
config.registerMedia(args?.env as any);
} else {
registerMedia(args?.env as any);
}
media_registered = true; media_registered = true;
} }
@@ -101,7 +106,7 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
// if connection instance is given, don't do anything // if connection instance is given, don't do anything
// other than checking if D1 session is defined // other than checking if D1 session is defined
if (D1Connection.isConnection(appConfig.connection)) { if (Connection.isConnection(appConfig.connection)) {
if (config.d1?.session) { if (config.d1?.session) {
// we cannot guarantee that db was opened with session // we cannot guarantee that db was opened with session
throw new Error( throw new Error(
@@ -123,14 +128,14 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
// if db is given in bindings, use it // if db is given in bindings, use it
if (bindings?.db) { if (bindings?.db) {
$console.log("Using database from bindings"); $console.debug("Using database from bindings");
db = bindings.db; db = bindings.db;
// scan for D1Database in args // scan for D1Database in args
} else { } else {
const binding = getBinding(args.env, "D1Database"); const binding = getBinding(args.env, "D1Database");
if (binding) { if (binding) {
$console.log(`Using database from env "${binding.key}"`); $console.debug(`Using database from env "${binding.key}"`);
db = binding.value; db = binding.value;
} }
} }
@@ -139,8 +144,11 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
if (db) { if (db) {
if (config.d1?.session) { if (config.d1?.session) {
session = db.withSession(sessionId ?? config.d1?.first); 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 = {
...appConfig.options, ...appConfig.options,
manager: { manager: {
@@ -154,12 +162,12 @@ export function makeConfig<Env extends CloudflareEnv = CloudflareEnv>(
}, },
}; };
} else { } 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"); throw new Error("Couldn't find database connection");
} }
@@ -1,42 +1,75 @@
/// <reference types="@cloudflare/workers-types" /> /// <reference types="@cloudflare/workers-types" />
import { SqliteConnection } from "bknd/data"; import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
import type { ConnQuery, ConnQueryResults } from "data/connection/Connection"; import type { QueryResult } from "kysely";
import { D1Dialect } from "kysely-d1";
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
export type D1ConnectionConfig<DB extends D1Database | D1DatabaseSession = D1Database> = { export type D1ConnectionConfig<DB extends D1Database | D1DatabaseSession = D1Database> = {
binding: DB; binding: DB;
}; };
export class D1Connection< export function d1Sqlite<DB extends D1Database | D1DatabaseSession = D1Database>(
DB extends D1Database | D1DatabaseSession = D1Database, config: D1ConnectionConfig<DB>,
> extends SqliteConnection<DB> { ) {
override name = "sqlite-d1"; const db = config.binding;
protected override readonly supported = { return genericSqlite(
batching: true, "d1-sqlite",
softscans: false, db,
}; (utils) => {
const getStmt = (sql: string, parameters?: any[] | readonly any[]) =>
db.prepare(sql).bind(...(parameters || []));
constructor(private config: D1ConnectionConfig<DB>) { const mapResult = (res: D1Result<any>): QueryResult<any> => {
super({ 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"], excludeTables: ["_cf_KV", "_cf_METADATA"],
dialect: D1Dialect, },
dialectArgs: [{ database: config.binding as D1Database }], );
});
}
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
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;
}
} }
@@ -0,0 +1,33 @@
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";
describe("d1Sqlite", async () => {
connectionTestSuite(viTestRunner, {
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",
],
});
});
@@ -0,0 +1,80 @@
/// <reference types="@cloudflare/workers-types" />
import { genericSqlite, type GenericSqliteConnection } from "bknd/data";
import type { QueryResult } from "kysely";
export type D1SqliteConnection = GenericSqliteConnection<D1Database>;
export type DurableObjecSql = DurableObjectState["storage"]["sql"];
export type D1ConnectionConfig<DB extends DurableObjecSql> =
| DurableObjectState
| {
sql: DB;
};
export function doSqlite<DB extends DurableObjecSql>(config: D1ConnectionConfig<DB>) {
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<Record<string, SqlStorageValue>>,
): QueryResult<any> => {
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"],
},
);
}
@@ -0,0 +1,92 @@
/// <reference types="@cloudflare/workers-types" />
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<any>;
}
>;
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"],
});
});
+9 -4
View File
@@ -1,10 +1,10 @@
import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection"; import { d1Sqlite, type D1ConnectionConfig } from "./connection/D1Connection";
export * from "./cloudflare-workers.adapter"; export * from "./cloudflare-workers.adapter";
export { makeApp, getFresh } from "./modes/fresh"; export { makeApp, getFresh } from "./modes/fresh";
export { getCached } from "./modes/cached"; export { getCached } from "./modes/cached";
export { DurableBkndApp, getDurable } from "./modes/durable"; export { DurableBkndApp, getDurable } from "./modes/durable";
export { D1Connection, type D1ConnectionConfig }; export { d1Sqlite, type D1ConnectionConfig };
export { export {
getBinding, getBinding,
getBindings, getBindings,
@@ -13,7 +13,12 @@ export {
type BindingMap, type BindingMap,
} from "./bindings"; } from "./bindings";
export { constants } from "./config"; export { constants } from "./config";
export { StorageR2Adapter } from "./storage/StorageR2Adapter";
export { registries } from "bknd";
export function d1(config: D1ConnectionConfig) { // for compatibility with old code
return new D1Connection(config); export function d1<DB extends D1Database | D1DatabaseSession = D1Database>(
config: D1ConnectionConfig<DB>,
) {
return d1Sqlite<DB>(config);
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import type { App, CreateAppConfig } from "bknd";
import { createRuntimeApp, makeConfig } from "bknd/adapter"; import { createRuntimeApp, makeConfig } from "bknd/adapter";
import type { CloudflareBkndConfig, Context, CloudflareEnv } from "../index"; import type { CloudflareBkndConfig, Context, CloudflareEnv } from "../index";
import { constants, registerAsyncsExecutionContext } from "../config"; import { constants, registerAsyncsExecutionContext } from "../config";
import { $console } from "core"; import { $console } from "core/utils";
export async function getDurable<Env extends CloudflareEnv = CloudflareEnv>( export async function getDurable<Env extends CloudflareEnv = CloudflareEnv>(
config: CloudflareBkndConfig<Env>, config: CloudflareBkndConfig<Env>,
@@ -61,46 +61,49 @@ export class StorageR2Adapter extends StorageAdapter {
async putObject(key: string, body: FileBody) { async putObject(key: string, body: FileBody) {
try { try {
const res = await this.bucket.put(key, body); const res = await this.bucket.put(this.getKey(key), body);
return res?.etag; return res?.etag;
} catch (e) { } catch (e) {
return undefined; return undefined;
} }
} }
async listObjects( async listObjects(prefix = ""): Promise<{ key: string; last_modified: Date; size: number }[]> {
prefix?: string, const list = await this.bucket.list({ limit: 50, prefix: this.getKey(prefix) });
): Promise<{ key: string; last_modified: Date; size: number }[]> {
const list = await this.bucket.list({ limit: 50 });
return list.objects.map((item) => ({ return list.objects.map((item) => ({
key: item.key, key: item.key.replace(this.getKey(""), ""),
size: item.size, size: item.size,
last_modified: item.uploaded, last_modified: item.uploaded,
})); }));
} }
private async headObject(key: string): Promise<R2Object | null> { private async headObject(key: string): Promise<R2Object | null> {
return await this.bucket.head(key); return await this.bucket.head(this.getKey(key));
} }
async objectExists(key: string): Promise<boolean> { async objectExists(key: string): Promise<boolean> {
return (await this.headObject(key)) !== null; return (await this.headObject(key)) !== null;
} }
async getObject(key: string, headers: Headers): Promise<Response> { async getObject(_key: string, headers: Headers): Promise<Response> {
let object: R2ObjectBody | null; let object: R2ObjectBody | null;
const key = this.getKey(_key);
const responseHeaders = new Headers({ const responseHeaders = new Headers({
"Accept-Ranges": "bytes", "Accept-Ranges": "bytes",
"Content-Type": guess(key), "Content-Type": guess(key),
}); });
const range = headers.has("range");
//console.log("getObject:headers", headersToObject(headers)); //console.log("getObject:headers", headersToObject(headers));
if (headers.has("range")) { if (range) {
const options = isDebug() const options = isDebug()
? {} // miniflare doesn't support range requests ? {} // miniflare doesn't support range requests
: { : {
range: headers, range: headers,
onlyIf: headers, onlyIf: headers,
}; };
object = (await this.bucket.get(key, options)) as R2ObjectBody; object = (await this.bucket.get(key, options)) as R2ObjectBody;
if (!object) { if (!object) {
@@ -128,13 +131,14 @@ export class StorageR2Adapter extends StorageAdapter {
responseHeaders.set("Last-Modified", object.uploaded.toUTCString()); responseHeaders.set("Last-Modified", object.uploaded.toUTCString());
return new Response(object.body, { return new Response(object.body, {
status: object.range ? 206 : 200, status: range ? 206 : 200,
headers: responseHeaders, headers: responseHeaders,
}); });
} }
private writeHttpMetadata(headers: Headers, object: R2Object | R2ObjectBody): void { private writeHttpMetadata(headers: Headers, object: R2Object | R2ObjectBody): void {
let metadata = object.httpMetadata; let metadata = object.httpMetadata;
if (!metadata || Object.keys(metadata).length === 0) { if (!metadata || Object.keys(metadata).length === 0) {
// guessing is especially required for dev environment (miniflare) // guessing is especially required for dev environment (miniflare)
metadata = { metadata = {
@@ -161,13 +165,17 @@ export class StorageR2Adapter extends StorageAdapter {
} }
async deleteObject(key: string): Promise<void> { async deleteObject(key: string): Promise<void> {
await this.bucket.delete(key); await this.bucket.delete(this.getKey(key));
} }
getObjectUrl(key: string): string { getObjectUrl(key: string): string {
throw new Error("Method getObjectUrl not implemented."); throw new Error("Method getObjectUrl not implemented.");
} }
protected getKey(key: string) {
return key;
}
toJSON(secrets?: boolean) { toJSON(secrets?: boolean) {
return { return {
type: this.getName(), type: this.getName(),
+2 -1
View File
@@ -1,5 +1,6 @@
import { App, type CreateAppConfig } from "bknd"; 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 { MiddlewareHandler } from "hono";
import type { AdminControllerOptions } from "modules/server/AdminController"; import type { AdminControllerOptions } from "modules/server/AdminController";
import { Connection } from "bknd/data"; import { Connection } from "bknd/data";
@@ -1,4 +1,4 @@
import { genericSqlite } from "data/connection/sqlite/GenericSqliteConnection"; import { genericSqlite } from "bknd/data";
import { DatabaseSync } from "node:sqlite"; import { DatabaseSync } from "node:sqlite";
export type NodeSqliteConnectionConfig = { export type NodeSqliteConnectionConfig = {
@@ -17,32 +17,41 @@ export function nodeSqlite(config?: NodeSqliteConnectionConfig | { url: string }
db = new DatabaseSync(":memory:"); db = new DatabaseSync(":memory:");
} }
return genericSqlite("node-sqlite", db, (utils) => { return genericSqlite(
const getStmt = (sql: string) => { "node-sqlite",
const stmt = db.prepare(sql); db,
//stmt.setReadBigInts(true); (utils) => {
return stmt; const getStmt = (sql: string) => {
}; const stmt = db.prepare(sql);
//stmt.setReadBigInts(true);
return stmt;
};
return { return {
db, db,
query: utils.buildQueryFn({ query: utils.buildQueryFn({
all: (sql, parameters = []) => getStmt(sql).all(...parameters), all: (sql, parameters = []) => getStmt(sql).all(...parameters),
run: (sql, parameters = []) => { run: (sql, parameters = []) => {
const { changes, lastInsertRowid } = getStmt(sql).run(...parameters); const { changes, lastInsertRowid } = getStmt(sql).run(...parameters);
return { return {
insertId: utils.parseBigInt(lastInsertRowid), insertId: utils.parseBigInt(lastInsertRowid),
numAffectedRows: utils.parseBigInt(changes), 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) { supports: {
throw new Error("Only support select in stream()"); batching: false,
}
return getStmt(sql).iterate(...parameters) as any;
}, },
}; },
}); );
} }
@@ -1,11 +1,15 @@
import { nodeSqlite } from "./NodeSqliteConnection"; import { nodeSqlite } from "./NodeSqliteConnection";
import { DatabaseSync } from "node:sqlite"; import { DatabaseSync } from "node:sqlite";
import { connectionTestSuite } from "data/connection/connection-test-suite"; import { connectionTestSuite } from "data/connection/connection-test-suite";
import { describe, test, expect } from "vitest"; import { describe } from "vitest";
import { viTestRunner } from "../vitest";
describe("NodeSqliteConnection", () => { describe("NodeSqliteConnection", () => {
connectionTestSuite({ describe, test, expect } as any, { connectionTestSuite(viTestRunner, {
makeConnection: () => nodeSqlite({ database: new DatabaseSync(":memory:") }), makeConnection: () => ({
connection: nodeSqlite({ database: new DatabaseSync(":memory:") }),
dispose: async () => {},
}),
rawDialectDetails: [], rawDialectDetails: [],
}); });
}); });
+1 -1
View File
@@ -4,7 +4,7 @@ import { serveStatic } from "@hono/node-server/serve-static";
import { registerLocalMediaAdapter } from "adapter/node/storage"; import { registerLocalMediaAdapter } from "adapter/node/storage";
import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter"; import { type RuntimeBkndConfig, createRuntimeApp, type RuntimeOptions } from "bknd/adapter";
import { config as $config } from "bknd/core"; import { config as $config } from "bknd/core";
import { $console } from "core"; import { $console } from "core/utils";
import type { App } from "App"; import type { App } from "App";
type NodeEnv = NodeJS.ProcessEnv; type NodeEnv = NodeJS.ProcessEnv;
+4 -1
View File
@@ -1,5 +1,5 @@
import nodeAssert from "node:assert/strict"; 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"; import type { Matcher, Test, TestFn, TestRunner } from "core/test";
// Track mock function calls // Track mock function calls
@@ -97,4 +97,7 @@ export const nodeTestRunner: TestRunner = {
reject: (r) => nodeTestMatcher(r, failMsg), reject: (r) => nodeTestMatcher(r, failMsg),
}), }),
}), }),
beforeEach: beforeEach,
afterEach: afterEach,
afterAll: () => {},
}; };
+4 -1
View File
@@ -1,5 +1,5 @@
import type { TestFn, TestRunner, Test } from "core/test"; 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) { function vitestTest(label: string, fn: TestFn, options?: any) {
return test(label, fn as any); return test(label, fn as any);
@@ -47,4 +47,7 @@ export const viTestRunner: TestRunner = {
test: vitestTest, test: vitestTest,
expect: vitestExpect as any, expect: vitestExpect as any,
mock: (fn) => vi.fn(fn), mock: (fn) => vi.fn(fn),
beforeEach: beforeEach,
afterEach: afterEach,
afterAll: afterAll,
}; };
+1 -2
View File
@@ -1,5 +1,4 @@
import type { Connection } from "bknd/data"; import { type Connection, libsql } from "bknd/data";
import { libsql } from "../../data/connection/sqlite/LibsqlConnection";
export function sqlite(config: { url: string }): Connection { export function sqlite(config: { url: string }): Connection {
return libsql(config); return libsql(config);
-3
View File
@@ -1,3 +0,0 @@
import type { Connection } from "bknd/data";
export type SqliteConnection = (config: { url: string }) => Connection;
+5 -15
View File
@@ -1,14 +1,15 @@
import { Authenticator, AuthPermissions, Role, type Strategy } from "auth"; import { Authenticator, AuthPermissions, Role, type Strategy } from "auth";
import type { PasswordStrategy } from "auth/authenticate/strategies"; import type { PasswordStrategy } from "auth/authenticate/strategies";
import { $console, type DB } from "core"; import type { DB } from "core";
import { secureRandomString, transformObject } from "core/utils"; import { $console, secureRandomString, transformObject } from "core/utils";
import type { Entity, EntityManager } from "data"; 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 { Module } from "modules/Module";
import { AuthController } from "./api/AuthController"; import { AuthController } from "./api/AuthController";
import { type AppAuthSchema, authConfigSchema, STRATEGIES } from "./auth-schema"; import { type AppAuthSchema, authConfigSchema, STRATEGIES } from "./auth-schema";
import { AppUserPool } from "auth/AppUserPool"; import { AppUserPool } from "auth/AppUserPool";
import type { AppEntity } from "core/config"; import type { AppEntity } from "core/config";
import { usersFields } from "./auth-entities";
export type UserFieldSchema = FieldSchema<typeof AppAuth.usersFields>; export type UserFieldSchema = FieldSchema<typeof AppAuth.usersFields>;
declare module "core" { declare module "core" {
@@ -125,18 +126,7 @@ export class AppAuth extends Module<AppAuthSchema> {
return this.em.entity(entity_name) as any; return this.em.entity(entity_name) as any;
} }
static usersFields = { static usersFields = 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(),
};
registerEntities() { registerEntities() {
const users = this.getUsersEntity(true); const users = this.getUsersEntity(true);
+1 -1
View File
@@ -1,6 +1,6 @@
import { AppAuth } from "auth/AppAuth"; import { AppAuth } from "auth/AppAuth";
import type { CreateUser, SafeUser, User, UserPool } from "auth/authenticate/Authenticator"; 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 { pick } from "lodash-es";
import { import {
InvalidConditionsException, InvalidConditionsException,
+14
View File
@@ -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(),
};
+2 -2
View File
@@ -1,6 +1,6 @@
import { $console, type DB, Exception } from "core"; import { type DB, Exception } from "core";
import { addFlashMessage } from "core/server/flash"; import { addFlashMessage } from "core/server/flash";
import { runtimeSupports, truncate } from "core/utils"; import { runtimeSupports, truncate, $console } from "core/utils";
import type { Context, Hono } from "hono"; import type { Context, Hono } from "hono";
import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie"; import { deleteCookie, getSignedCookie, setSignedCookie } from "hono/cookie";
import { sign, verify } from "hono/jwt"; import { sign, verify } from "hono/jwt";
@@ -1,6 +1,5 @@
import { type Authenticator, InvalidCredentialsException, type User } from "auth"; import { type Authenticator, InvalidCredentialsException, type User } from "auth";
import { $console } from "core"; import { hash, $console } from "core/utils";
import { hash } from "core/utils";
import { Hono } from "hono"; import { Hono } from "hono";
import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs"; import { compare as bcryptCompare, genSalt as bcryptGenSalt, hash as bcryptHash } from "bcryptjs";
import { Strategy } from "./Strategy"; import { Strategy } from "./Strategy";
+2 -2
View File
@@ -1,5 +1,5 @@
import { $console, Exception, Permission } from "core"; import { Exception, Permission } from "core";
import { objectTransform } from "core/utils"; import { $console, objectTransform } from "core/utils";
import type { Context } from "hono"; import type { Context } from "hono";
import type { ServerEnv } from "modules/Controller"; import type { ServerEnv } from "modules/Controller";
import { Role } from "./Role"; import { Role } from "./Role";
+2 -2
View File
@@ -1,5 +1,5 @@
import { $console, type Permission } from "core"; import type { Permission } from "core";
import { patternMatch } from "core/utils"; import { $console, patternMatch } from "core/utils";
import type { Context } from "hono"; import type { Context } from "hono";
import { createMiddleware } from "hono/factory"; import { createMiddleware } from "hono/factory";
import type { ServerEnv } from "modules/Controller"; import type { ServerEnv } from "modules/Controller";
@@ -29,30 +29,8 @@ export const cloudflare = {
{ dir: ctx.dir }, { 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 { try {
switch (db) { await createD1(ctx);
case "d1":
await createD1(ctx);
break;
case "libsql":
await createLibsql(ctx);
break;
default:
throw new Error("Invalid database");
}
} catch (e) { } catch (e) {
const message = (e as any).message || "An error occurred"; const message = (e as any).message || "An error occurred";
$p.log.warn( $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; } 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) { if (!ctx.skip) {
exec(`npx wrangler d1 create ${name}`); 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) { async function createR2(ctx: TemplateSetupCtx) {
@@ -197,9 +141,11 @@ async function createR2(ctx: TemplateSetupCtx) {
process.exit(1); process.exit(1);
} }
if (!ctx.skip) { await $p.stream.info(
exec(`npx wrangler r2 bucket create ${name}`); (async function* () {
} yield* typewriter("Now running wrangler to create a R2 bucket...");
})(),
);
await overrideJson( await overrideJson(
WRANGLER_FILE, WRANGLER_FILE,
@@ -214,4 +160,8 @@ async function createR2(ctx: TemplateSetupCtx) {
}), }),
{ dir: ctx.dir }, { dir: ctx.dir },
); );
if (!ctx.skip) {
exec(`npx wrangler r2 bucket create ${name}`);
}
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import path from "node:path"; import path from "node:path";
import { $console } from "core"; import { $console } from "core/utils";
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import open from "open"; import open from "open";
import { fileExists, getRelativeDistPath } from "../../utils/sys"; import { fileExists, getRelativeDistPath } from "../../utils/sys";
+2 -2
View File
@@ -3,7 +3,7 @@ import type { App, CreateAppConfig } from "App";
import { StorageLocalAdapter } from "adapter/node/storage"; import { StorageLocalAdapter } from "adapter/node/storage";
import type { CliBkndConfig, CliCommand } from "cli/types"; import type { CliBkndConfig, CliCommand } from "cli/types";
import { Option } from "commander"; import { Option } from "commander";
import { colorizeConsole, config } from "core"; import { config } from "core";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { registries } from "modules/registries"; import { registries } from "modules/registries";
import c from "picocolors"; import c from "picocolors";
@@ -17,7 +17,7 @@ import {
startServer, startServer,
} from "./platform"; } from "./platform";
import { createRuntimeApp, makeConfig } from "adapter"; import { createRuntimeApp, makeConfig } from "adapter";
import { isBun } from "core/utils"; import { colorizeConsole, isBun } from "core/utils";
const env_files = [".env", ".dev.vars"]; const env_files = [".env", ".dev.vars"];
dotenv.config({ dotenv.config({
+1 -1
View File
@@ -9,7 +9,7 @@ import type { PasswordStrategy } from "auth/authenticate/strategies";
import { makeAppFromEnv } from "cli/commands/run"; import { makeAppFromEnv } from "cli/commands/run";
import type { CliCommand } from "cli/types"; import type { CliCommand } from "cli/types";
import { Argument } from "commander"; import { Argument } from "commander";
import { $console } from "core"; import { $console } from "core/utils";
import c from "picocolors"; import c from "picocolors";
import { isBun } from "core/utils"; import { isBun } from "core/utils";
+1 -1
View File
@@ -1,4 +1,4 @@
import { $console } from "core"; import { $console } from "core/utils";
import { execSync, exec as nodeExec } from "node:child_process"; import { execSync, exec as nodeExec } from "node:child_process";
import { readFile, writeFile as nodeWriteFile } from "node:fs/promises"; import { readFile, writeFile as nodeWriteFile } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
+2 -1
View File
@@ -1,6 +1,7 @@
import { PostHog } from "posthog-js-lite"; import { PostHog } from "posthog-js-lite";
import { getVersion } from "cli/utils/sys"; 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 }; type Properties = { [p: string]: any };
+1 -1
View File
@@ -1,6 +1,6 @@
import { type Event, type EventClass, InvalidEventReturn } from "./Event"; import { type Event, type EventClass, InvalidEventReturn } from "./Event";
import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener"; import { EventListener, type ListenerHandler, type ListenerMode } from "./EventListener";
import { $console } from "core"; import { $console } from "core/utils";
export type RegisterListenerConfig = export type RegisterListenerConfig =
| ListenerMode | ListenerMode
-1
View File
@@ -38,7 +38,6 @@ export { getFlashMessage } from "./server/flash";
} from "./object/schema"; */ } from "./object/schema"; */
export * from "./drivers"; export * from "./drivers";
export * from "./console";
export * from "./events"; export * from "./events";
// compatibility // compatibility
+6 -1
View File
@@ -1,3 +1,5 @@
import type { MaybePromise } from "core/types";
export type Matcher<T = unknown> = { export type Matcher<T = unknown> = {
toEqual: (expected: T, failMsg?: string) => void; toEqual: (expected: T, failMsg?: string) => void;
toBe: (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; skipIf: (condition: boolean) => (label: string, fn: TestFn) => void;
} }
export type TestRunner = { export type TestRunner = {
describe: (label: string, asyncFn: () => Promise<void>) => void; describe: (label: string, asyncFn: () => MaybePromise<void>) => void;
test: Test; test: Test;
mock: <T extends (...args: any[]) => any>(fn: T) => T | any; mock: <T extends (...args: any[]) => any>(fn: T) => T | any;
expect: <T = unknown>( expect: <T = unknown>(
@@ -26,6 +28,9 @@ export type TestRunner = {
resolves: Matcher<Awaited<T>>; resolves: Matcher<Awaited<T>>;
rejects: Matcher<Awaited<T>>; rejects: Matcher<Awaited<T>>;
}; };
beforeEach: (fn: () => MaybePromise<void>) => void;
afterEach: (fn: () => MaybePromise<void>) => void;
afterAll: (fn: () => MaybePromise<void>) => void;
}; };
export async function retry<T>( export async function retry<T>(
+1 -1
View File
@@ -2,7 +2,7 @@ import { extension, guess, isMimeType } from "media/storage/mime-types-tiny";
import { randomString } from "core/utils/strings"; import { randomString } from "core/utils/strings";
import type { Context } from "hono"; import type { Context } from "hono";
import { invariant } from "core/utils/runtime"; import { invariant } from "core/utils/runtime";
import { $console } from "../console"; import { $console } from "./console";
export function getContentName(request: Request): string | undefined; export function getContentName(request: Request): string | undefined;
export function getContentName(contentDisposition: string): string | undefined; export function getContentName(contentDisposition: string): string | undefined;
+1
View File
@@ -1,3 +1,4 @@
export * from "./console";
export * from "./browser"; export * from "./browser";
export * from "./objects"; export * from "./objects";
export * from "./strings"; export * from "./strings";
+3 -3
View File
@@ -1,4 +1,4 @@
import { $console } from "core"; import { $console } from "./console";
type ConsoleSeverity = "log" | "warn" | "error"; type ConsoleSeverity = "log" | "warn" | "error";
const _oldConsoles = { const _oldConsoles = {
@@ -36,14 +36,14 @@ export function disableConsoleLog(severities: ConsoleSeverity[] = ["log", "warn"
severities.forEach((severity) => { severities.forEach((severity) => {
console[severity] = () => null; console[severity] = () => null;
}); });
$console.setLevel("critical"); $console?.setLevel("critical");
} }
export function enableConsoleLog() { export function enableConsoleLog() {
Object.entries(_oldConsoles).forEach(([severity, fn]) => { Object.entries(_oldConsoles).forEach(([severity, fn]) => {
console[severity as ConsoleSeverity] = fn; console[severity as ConsoleSeverity] = fn;
}); });
$console.resetLevel(); $console?.resetLevel();
} }
export function formatMemoryUsage() { export function formatMemoryUsage() {
-1
View File
@@ -1,4 +1,3 @@
import { $console, isDebug } from "core";
import { import {
DataPermissions, DataPermissions,
type EntityData, type EntityData,
+223 -60
View File
@@ -1,5 +1,9 @@
import type { TestRunner } from "core/test"; import type { TestRunner } from "core/test";
import { Connection, type FieldSpec } from "./Connection"; 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 various datatypes: string, number, boolean, object, array, null, undefined, date, etc.
// @todo: add toDriver/fromDriver tests on all types and fields // @todo: add toDriver/fromDriver tests on all types and fields
@@ -10,77 +14,92 @@ export function connectionTestSuite(
makeConnection, makeConnection,
rawDialectDetails, rawDialectDetails,
}: { }: {
makeConnection: () => Connection; makeConnection: () => MaybePromise<{
connection: Connection;
dispose: () => MaybePromise<void>;
}>;
rawDialectDetails: string[]; rawDialectDetails: string[];
}, },
) { ) {
const { test, expect, describe } = testRunner; const { test, expect, describe, beforeEach, afterEach, afterAll } = testRunner;
test("pings", async () => { describe("base", () => {
const connection = makeConnection(); let ctx: Awaited<ReturnType<typeof makeConnection>>;
const res = await connection.ping(); beforeEach(async () => {
expect(res).toBe(true); ctx = await makeConnection();
}); });
afterEach(async () => {
await ctx.dispose();
});
test("initializes", async () => { test("pings", async () => {
const connection = makeConnection(); const res = await ctx.connection.ping();
await connection.init(); expect(res).toBe(true);
// @ts-expect-error });
expect(connection.initialized).toBe(true);
expect(connection.client).toBeDefined();
});
test("isConnection", async () => { test("initializes", async () => {
const connection = makeConnection(); await ctx.connection.init();
expect(Connection.isConnection(connection)).toBe(true);
});
test("getFieldSchema", async () => {
const c = makeConnection();
const specToNode = (spec: FieldSpec) => {
// @ts-expect-error // @ts-expect-error
const schema = c.kysely.schema.createTable("test").addColumn(...c.getFieldSchema(spec)); expect(ctx.connection.initialized).toBe(true);
return schema.toOperationNode(); expect(ctx.connection.client).toBeDefined();
}; });
{ test("isConnection", async () => {
// primary expect(Connection.isConnection(ctx.connection)).toBe(true);
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("getFieldSchema", async () => {
// normal const specToNode = (spec: FieldSpec) => {
const node = specToNode({ const schema = ctx.connection.kysely.schema
type: "text", .createTable("test")
name: "text", // @ts-expect-error
}); .addColumn(...ctx.connection.getFieldSchema(spec));
const col = node.columns[0]!; return schema.toOperationNode();
expect(!col.primaryKey).toBe(true); };
expect(!col.notNull).toBe(true);
}
{ {
// nullable (expect to be same as normal) // primary
const node = specToNode({ const node = specToNode({
type: "text", type: "integer",
name: "text", name: "id",
nullable: true, primary: true,
}); });
const col = node.columns[0]!; const col = node.columns[0]!;
expect(!col.primaryKey).toBe(true); expect(col.primaryKey).toBe(true);
expect(!col.notNull).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 () => { describe("schema", async () => {
const connection = makeConnection(); const { connection, dispose } = await makeConnection();
afterAll(async () => {
await dispose();
});
const fields = [ const fields = [
{ {
type: "integer", type: "integer",
@@ -118,14 +137,16 @@ export function connectionTestSuite(
const qb = connection.kysely.selectFrom("test").selectAll(); const qb = connection.kysely.selectFrom("test").selectAll();
const res = await connection.executeQuery(qb); const res = await connection.executeQuery(qb);
expect(res.rows).toEqual([expected]); 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); const res = await connection.executeQueries(qb, qb);
expect(res.length).toBe(2); expect(res.length).toBe(2);
res.map((r) => { res.map((r) => {
expect(r.rows).toEqual([expected]); 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<ReturnType<typeof makeConnection>>;
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);
});
});
} }
@@ -1,4 +1,4 @@
import type { KyselyPlugin } from "kysely"; import type { KyselyPlugin, QueryResult } from "kysely";
import { import {
type IGenericSqlite, type IGenericSqlite,
type OnCreateConnection, type OnCreateConnection,
@@ -8,11 +8,16 @@ import {
GenericSqliteDialect, GenericSqliteDialect,
} from "kysely-generic-sqlite"; } from "kysely-generic-sqlite";
import { SqliteConnection } from "./SqliteConnection"; import { SqliteConnection } from "./SqliteConnection";
import type { Features } from "../Connection"; import type { ConnQuery, ConnQueryResults, Features } from "../Connection";
export type { IGenericSqlite }; export type { IGenericSqlite };
export type TStatement = { sql: string; parameters?: any[] | readonly any[] };
export interface IGenericCustomSqlite<DB = unknown> extends IGenericSqlite<DB> {
batch?: (stmts: TStatement[]) => Promisable<QueryResult<any>[]>;
}
export type GenericSqliteConnectionConfig = { export type GenericSqliteConnectionConfig = {
name: string; name?: string;
additionalPlugins?: KyselyPlugin[]; additionalPlugins?: KyselyPlugin[];
excludeTables?: string[]; excludeTables?: string[];
onCreateConnection?: OnCreateConnection; onCreateConnection?: OnCreateConnection;
@@ -21,10 +26,11 @@ export type GenericSqliteConnectionConfig = {
export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB> { export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB> {
override name = "generic-sqlite"; override name = "generic-sqlite";
#executor: IGenericCustomSqlite<DB> | undefined;
constructor( constructor(
db: DB, public db: DB,
executor: () => Promisable<IGenericSqlite>, private executor: () => Promisable<IGenericCustomSqlite<DB>>,
config?: GenericSqliteConnectionConfig, config?: GenericSqliteConnectionConfig,
) { ) {
super({ super({
@@ -39,18 +45,43 @@ export class GenericSqliteConnection<DB = unknown> extends SqliteConnection<DB>
} }
if (config?.supports) { if (config?.supports) {
for (const [key, value] of Object.entries(config.supports)) { for (const [key, value] of Object.entries(config.supports)) {
if (value) { if (value !== undefined) {
this.supported[key] = value; this.supported[key] = value;
} }
} }
} }
} }
private async getExecutor() {
if (!this.#executor) {
this.#executor = await this.executor();
}
return this.#executor;
}
override async executeQueries<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
const executor = await this.getExecutor();
if (!executor.batch) {
//$console.debug("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<DB>( export function genericSqlite<DB>(
name: string, name: string,
db: DB, db: DB,
executor: (utils: typeof genericSqliteUtils) => Promisable<IGenericSqlite<DB>>, executor: (utils: typeof genericSqliteUtils) => Promisable<IGenericCustomSqlite<DB>>,
config?: GenericSqliteConnectionConfig, config?: GenericSqliteConnectionConfig,
) { ) {
return new GenericSqliteConnection(db, () => executor(genericSqliteUtils), { return new GenericSqliteConnection(db, () => executor(genericSqliteUtils), {
@@ -1,12 +0,0 @@
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(createClient({ url: ":memory:" })),
rawDialectDetails: ["rowsAffected", "lastInsertRowid"],
});
});
@@ -1,62 +0,0 @@
import type { Client, Config, InStatement } from "@libsql/client";
import { createClient } from "libsql-stateless-easy";
import { LibsqlDialect } from "@libsql/kysely-libsql";
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];
};
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<Client> {
override name = "libsql";
protected override readonly supported = {
batching: true,
softscans: true,
};
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<O extends ConnQuery[]>(...qbs: O): Promise<ConnQueryResults<O>> {
const compiled = this.getCompiled(...qbs);
const stms: InStatement[] = compiled.map((q) => {
return {
sql: q.sql,
args: q.parameters as any[],
};
});
return this.withTransformedRows(await this.client.batch(stms)) as any;
}
}
export function libsql(credentials: Client | LibSqlCredentials): LibsqlConnection {
return new LibsqlConnection(credentials);
}
@@ -68,32 +68,34 @@ export class SqliteIntrospector extends BaseIntrospector {
return tables.map((table) => ({ return tables.map((table) => ({
name: table.name, name: table.name,
isView: table.type === "view", isView: table.type === "view",
columns: table.columns.map((col) => { columns:
const autoIncrementCol = table.sql table.columns?.map((col) => {
?.split(/[\(\),]/) const autoIncrementCol = table.sql
?.find((it) => it.toLowerCase().includes("autoincrement")) ?.split(/[\(\),]/)
?.trimStart() ?.find((it) => it.toLowerCase().includes("autoincrement"))
?.split(/\s+/)?.[0] ?.trimStart()
?.replace(/["`]/g, ""); ?.split(/\s+/)?.[0]
?.replace(/["`]/g, "");
return { return {
name: col.name, name: col.name,
dataType: col.type, dataType: col.type,
isNullable: !col.notnull, isNullable: !col.notnull,
isAutoIncrementing: col.name === autoIncrementCol, isAutoIncrementing: col.name === autoIncrementCol,
hasDefaultValue: col.dflt_value != null, hasDefaultValue: col.dflt_value != null,
comment: undefined, comment: undefined,
}; };
}), }) ?? [],
indices: table.indices.map((index) => ({ indices:
name: index.name, table.indices?.map((index) => ({
table: table.name, name: index.name,
isUnique: index.sql?.match(/unique/i) != null, table: table.name,
columns: index.columns.map((col) => ({ isUnique: index.sql?.match(/unique/i) != null,
name: col.name, columns: index.columns.map((col) => ({
order: col.seqno, name: col.name,
})), order: col.seqno,
})), })),
})) ?? [],
})); }));
} }
} }
@@ -0,0 +1,15 @@
import { connectionTestSuite } from "../../connection-test-suite";
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: () => ({
connection: libsql(createClient({ url: ":memory:" })),
dispose: async () => {},
}),
rawDialectDetails: [],
});
});
@@ -0,0 +1,68 @@
import type { Client, Config, ResultSet } from "@libsql/client";
import { createClient } from "libsql-stateless-easy";
import { FilterNumericKeysPlugin } from "data/plugins/FilterNumericKeysPlugin";
import {
genericSqlite,
type GenericSqliteConnection,
} from "data/connection/sqlite/GenericSqliteConnection";
import type { QueryResult } from "kysely";
export type LibsqlConnection = GenericSqliteConnection<Client>;
export type LibSqlCredentials = Config;
function getClient(clientOrCredentials: Client | LibSqlCredentials): Client {
if (clientOrCredentials && "url" in clientOrCredentials) {
const { url, authToken } = clientOrCredentials;
return createClient({ url, authToken });
}
return clientOrCredentials as Client;
}
export function libsql(config: LibSqlCredentials | Client) {
const db = getClient(config);
return genericSqlite(
"libsql",
db,
(utils) => {
const mapResult = (result: ResultSet): QueryResult<any> => ({
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 {
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"],
},
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { $console, config } from "core"; import { config } from "core";
import { snakeToPascalWithSpaces, transformObject } from "core/utils"; import { snakeToPascalWithSpaces, transformObject, $console } from "core/utils";
import { import {
type Field, type Field,
PrimaryField, PrimaryField,
+2 -1
View File
@@ -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 { EventManager } from "core/events";
import { sql } from "kysely"; import { sql } from "kysely";
import { Connection } from "../connection/Connection"; import { Connection } from "../connection/Connection";
+4 -3
View File
@@ -1,6 +1,7 @@
import type { Entity, EntityManager, EntityRelation, TEntityType } from "data"; import type { Entity, EntityManager, EntityRelation, TEntityType } from "data";
import { autoFormatString } from "core/utils"; 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 = { export type TEntityTSType = {
name: string; name: string;
@@ -32,8 +33,8 @@ export type EntityTypescriptOptions = {
// keep a local copy here until properties have a type // keep a local copy here until properties have a type
const systemEntities = { const systemEntities = {
users: AppAuth.usersFields, users: usersFields,
media: AppMedia.mediaFields, media: mediaFields,
}; };
export class EntityTypescript { export class EntityTypescript {
@@ -1,4 +1,4 @@
import { $console } from "core/console"; import { $console } from "core/utils";
import type { Entity, EntityData } from "../Entity"; import type { Entity, EntityData } from "../Entity";
import type { EntityManager } from "../EntityManager"; import type { EntityManager } from "../EntityManager";
import { Result, type ResultJSON, type ResultOptions } from "../Result"; import { Result, type ResultJSON, type ResultOptions } from "../Result";
@@ -32,6 +32,7 @@ export class MutatorResult<T = EntityData[]> extends Result<T> {
onError: (error) => { onError: (error) => {
if (!options?.silent) { if (!options?.silent) {
$console.error("[ERROR] Mutator:", error.message); $console.error("[ERROR] Mutator:", error.message);
throw error;
} }
}, },
...options, ...options,
+2 -2
View File
@@ -1,5 +1,5 @@
import type { DB as DefaultDB, PrimaryFieldType } from "core"; 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 EmitsEvents, EventManager } from "core/events";
import { type SelectQueryBuilder, sql } from "kysely"; import { type SelectQueryBuilder, sql } from "kysely";
import { InvalidSearchParamsException } from "../../errors"; import { InvalidSearchParamsException } from "../../errors";
@@ -57,7 +57,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
} }
} }
getValidOptions(options?: RepoQuery): RepoQuery { getValidOptions(options?: Partial<RepoQuery>): RepoQuery {
const entity = this.entity; const entity = this.entity;
// @todo: if not cloned deep, it will keep references and error if multiple requests come in // @todo: if not cloned deep, it will keep references and error if multiple requests come in
const validated = { const validated = {
@@ -1,9 +1,8 @@
import { $console } from "core/console";
import type { Entity, EntityData } from "../Entity"; import type { Entity, EntityData } from "../Entity";
import type { EntityManager } from "../EntityManager"; import type { EntityManager } from "../EntityManager";
import { Result, type ResultJSON, type ResultOptions } from "../Result"; import { Result, type ResultJSON, type ResultOptions } from "../Result";
import type { Compilable, SelectQueryBuilder } from "kysely"; import type { Compilable, SelectQueryBuilder } from "kysely";
import { ensureInt } from "core/utils"; import { $console, ensureInt } from "core/utils";
export type RepositoryResultOptions = ResultOptions & { export type RepositoryResultOptions = ResultOptions & {
silent?: boolean; silent?: boolean;
+2 -1
View File
@@ -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 { Event, InvalidEventReturn } from "core/events";
import type { Entity, EntityData } from "../entities"; import type { Entity, EntityData } from "../entities";
import type { RepoQuery } from "data/server/query"; import type { RepoQuery } from "data/server/query";
+2 -2
View File
@@ -1,7 +1,7 @@
import { dayjs, omitKeys } from "core/utils"; import { dayjs } from "core/utils";
import type { EntityManager } from "../entities"; import type { EntityManager } from "../entities";
import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field"; import { Field, type TActionContext, type TRenderContext, baseFieldConfigSchema } from "./Field";
import { $console } from "core"; import { $console } from "core/utils";
import type { TFieldTSType } from "data/entities/EntityTypescript"; import type { TFieldTSType } from "data/entities/EntityTypescript";
import { s } from "core/object/schema"; import { s } from "core/object/schema";
+8 -1
View File
@@ -30,9 +30,16 @@ export * as DataPermissions from "./permissions";
export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
export { libsql } from "./connection/sqlite/LibsqlConnection"; export { libsql } from "./connection/sqlite/libsql/LibsqlConnection";
export { export {
genericSqlite, genericSqlite,
genericSqliteUtils, genericSqliteUtils,
type GenericSqliteConnection, type GenericSqliteConnection,
} from "./connection/sqlite/GenericSqliteConnection"; } from "./connection/sqlite/GenericSqliteConnection";
export {
EntityTypescript,
type EntityTypescriptOptions,
type TEntityTSType,
type TFieldTSType,
} from "./entities/EntityTypescript";
+54 -50
View File
@@ -3,16 +3,13 @@ import { EntityManager } from "data/entities/EntityManager";
import type { Generated } from "kysely"; import type { Generated } from "kysely";
import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; import { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField";
import type { ModuleConfigs } from "modules"; import type { ModuleConfigs } from "modules";
import { import {
BooleanField, BooleanField,
type BooleanFieldConfig, type BooleanFieldConfig,
type Connection,
DateField, DateField,
type DateFieldConfig, type DateFieldConfig,
Entity,
type EntityConfig,
EntityIndex, EntityIndex,
type EntityRelation,
EnumField, EnumField,
type EnumFieldConfig, type EnumFieldConfig,
type Field, type Field,
@@ -20,20 +17,27 @@ import {
type JsonFieldConfig, type JsonFieldConfig,
JsonSchemaField, JsonSchemaField,
type JsonSchemaFieldConfig, 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, ManyToManyRelation,
type ManyToManyRelationConfig, type ManyToManyRelationConfig,
ManyToOneRelation, ManyToOneRelation,
type ManyToOneRelationConfig, type ManyToOneRelationConfig,
NumberField,
type NumberFieldConfig,
OneToOneRelation, OneToOneRelation,
type OneToOneRelationConfig, type OneToOneRelationConfig,
PolymorphicRelation, PolymorphicRelation,
type PolymorphicRelationConfig, type PolymorphicRelationConfig,
type TEntityType, } from "data/relations";
TextField,
type TextFieldConfig,
} from "../index";
type Options<Config = any> = { type Options<Config = any> = {
entity: { name: string; fields: Record<string, Field<any, any, any>> }; entity: { name: string; fields: Record<string, Field<any, any, any>> };
@@ -61,6 +65,46 @@ const FieldMap = {
} as const; } as const;
type TFieldType = keyof typeof FieldMap; 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( export function text(
config?: Omit<TextFieldConfig, "required">, config?: Omit<TextFieldConfig, "required">,
): TextField<false> & { required: () => TextField<true> } { ): TextField<false> & { required: () => TextField<true> } {
@@ -132,46 +176,6 @@ export function make<Actual extends Field<any, any>>(name: string, field: Actual
throw new Error("Invalid field"); 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< export function entity<
EntityName extends string, EntityName extends string,
Fields extends Record<string, Field<any, any, any>>, Fields extends Record<string, Field<any, any, any>>,
+1 -1
View File
@@ -2,7 +2,7 @@ import type { CompiledQuery, TableMetadata } from "kysely";
import type { IndexMetadata, SchemaResponse } from "../connection/Connection"; import type { IndexMetadata, SchemaResponse } from "../connection/Connection";
import type { Entity, EntityManager } from "../entities"; import type { Entity, EntityManager } from "../entities";
import { PrimaryField } from "../fields"; import { PrimaryField } from "../fields";
import { $console } from "core"; import { $console } from "core/utils";
type IntrospectedTable = TableMetadata & { type IntrospectedTable = TableMetadata & {
indices: IndexMetadata[]; indices: IndexMetadata[];
+4 -3
View File
@@ -1,7 +1,6 @@
import { s } from "core/object/schema"; import { s } from "core/object/schema";
import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder"; import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder";
import { $console } from "core"; import { isObject, $console } from "core/utils";
import { isObject } from "core/utils";
import type { anyOf, CoercionOptions, Schema } from "jsonv-ts"; import type { anyOf, CoercionOptions, Schema } from "jsonv-ts";
// ------- // -------
@@ -157,7 +156,9 @@ export type RepoQueryIn = {
join?: string[]; join?: string[];
where?: WhereQuery; where?: WhereQuery;
}; };
export type RepoQuery = s.StaticCoerced<typeof repoQuery>; export type RepoQuery = s.StaticCoerced<typeof repoQuery> & {
sort: SortSchema;
};
//export type RepoQuery = s.StaticCoerced<typeof repoQuery>; //export type RepoQuery = s.StaticCoerced<typeof repoQuery>;
// @todo: CURRENT WORKAROUND // @todo: CURRENT WORKAROUND
+1 -1
View File
@@ -2,7 +2,7 @@ import { Event, EventManager, type ListenerHandler } from "core/events";
import type { EmitsEvents } from "core/events"; import type { EmitsEvents } from "core/events";
import type { Task, TaskResult } from "../tasks/Task"; import type { Task, TaskResult } from "../tasks/Task";
import type { Flow } from "./Flow"; import type { Flow } from "./Flow";
import { $console } from "core"; import { $console } from "core/utils";
export type TaskLog = TaskResult & { export type TaskLog = TaskResult & {
task: Task; task: Task;
+1 -2
View File
@@ -1,11 +1,10 @@
import { objectTransform, transformObject } from "core/utils"; import { $console, transformObject } from "core/utils";
import { type TaskMapType, TriggerMap } from "../index"; import { type TaskMapType, TriggerMap } from "../index";
import type { Task } from "../tasks/Task"; import type { Task } from "../tasks/Task";
import { Condition, TaskConnection } from "../tasks/TaskConnection"; import { Condition, TaskConnection } from "../tasks/TaskConnection";
import { Execution } from "./Execution"; import { Execution } from "./Execution";
import { FlowTaskConnector } from "./FlowTaskConnector"; import { FlowTaskConnector } from "./FlowTaskConnector";
import { Trigger } from "./triggers/Trigger"; import { Trigger } from "./triggers/Trigger";
import { $console } from "core";
type Jsoned<T extends { toJSON: () => object }> = ReturnType<T["toJSON"]>; type Jsoned<T extends { toJSON: () => object }> = ReturnType<T["toJSON"]>;
@@ -1,5 +1,5 @@
import type { Task } from "../../tasks/Task"; import type { Task } from "../../tasks/Task";
import { $console } from "core"; import { $console } from "core/utils";
export class RuntimeExecutor { export class RuntimeExecutor {
async run( async run(
+1 -1
View File
@@ -1,7 +1,7 @@
import type { EventManager } from "core/events"; import type { EventManager } from "core/events";
import type { Flow } from "../Flow"; import type { Flow } from "../Flow";
import { Trigger } from "./Trigger"; import { Trigger } from "./Trigger";
import { $console } from "core"; import { $console } from "core/utils";
import { s } from "core/object/schema"; import { s } from "core/object/schema";
export class EventTrigger extends Trigger<typeof EventTrigger.schema> { export class EventTrigger extends Trigger<typeof EventTrigger.schema> {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Task } from "../Task"; import { Task } from "../Task";
import { $console } from "core"; import { $console } from "core/utils";
import { s } from "core/object/schema"; import { s } from "core/object/schema";
export class LogTask extends Task<typeof LogTask.schema> { export class LogTask extends Task<typeof LogTask.schema> {
+12
View File
@@ -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 { export {
App, App,
createApp, createApp,
@@ -16,6 +27,7 @@ export {
type ModuleManagerOptions, type ModuleManagerOptions,
type ModuleBuildContext, type ModuleBuildContext,
type InitialModuleConfigs, type InitialModuleConfigs,
ModuleManagerEvents,
} from "./modules/ModuleManager"; } from "./modules/ModuleManager";
export type { ServerEnv } from "modules/Controller"; export type { ServerEnv } from "modules/Controller";
+6 -24
View File
@@ -1,19 +1,12 @@
import { $console, type AppEntity } from "core"; import type { AppEntity } from "core";
import { $console } from "core/utils";
import type { Entity, EntityManager } from "data"; import type { Entity, EntityManager } from "data";
import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media"; import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media";
import { Module } from "modules/Module"; import { Module } from "modules/Module";
import { import { type FieldSchema, em, entity } from "../data/prototype";
type FieldSchema,
boolean,
datetime,
em,
entity,
json,
number,
text,
} from "../data/prototype";
import { MediaController } from "./api/MediaController"; import { MediaController } from "./api/MediaController";
import { buildMediaSchema, registry, type TAppMediaConfig } from "./media-schema"; import { buildMediaSchema, type mediaConfigSchema, registry, type TAppMediaConfig } from "./media-schema";
import { mediaFields } from "./media-entities";
export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>; export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>;
declare module "core" { declare module "core" {
@@ -95,18 +88,7 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
}; };
} }
static mediaFields = { static mediaFields = 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(),
};
getMediaEntity(forceCreate?: boolean): Entity<"media", typeof AppMedia.mediaFields> { getMediaEntity(forceCreate?: boolean): Entity<"media", typeof AppMedia.mediaFields> {
const entity_name = this.config.entity_name; const entity_name = this.config.entity_name;
+14
View File
@@ -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(),
};
+1 -2
View File
@@ -1,9 +1,8 @@
import { type EmitsEvents, EventManager } from "core/events"; 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 { isMimeType } from "media/storage/mime-types-tiny";
import * as StorageEvents from "./events"; import * as StorageEvents from "./events";
import type { FileUploadedEventData } from "./events"; import type { FileUploadedEventData } from "./events";
import { $console } from "core";
import type { StorageAdapter } from "./StorageAdapter"; import type { StorageAdapter } from "./StorageAdapter";
export type FileListObject = { export type FileListObject = {
+2 -1
View File
@@ -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 { isDebug } from "core/env";
import { encodeSearch } from "core/utils/reqres"; import { encodeSearch } from "core/utils/reqres";
import type { ApiFetcher } from "Api"; import type { ApiFetcher } from "Api";
+26 -3
View File
@@ -1,6 +1,7 @@
import { Guard } from "auth"; import { Guard } from "auth";
import { $console, BkndError, DebugLogger, env } from "core"; import { BkndError, DebugLogger, env } from "core";
import { EventManager } from "core/events"; import { $console } from "core/utils";
import { EventManager, Event } from "core/events";
import * as $diff from "core/object/diff"; import * as $diff from "core/object/diff";
import { objectEach, transformObject } from "core/utils"; import { objectEach, transformObject } from "core/utils";
import type { Connection, Schema } from "data"; import type { Connection, Schema } from "data";
@@ -117,9 +118,24 @@ interface T_INTERNAL_EM {
const debug_modules = env("modules_debug"); const debug_modules = env("modules_debug");
abstract class ModuleManagerEvent<A = {}> 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 old diffs on upgrade
// @todo: cleanup multiple backups on upgrade // @todo: cleanup multiple backups on upgrade
export class ModuleManager { export class ModuleManager {
static Events = ModuleManagerEvents;
protected modules: Modules; protected modules: Modules;
// internal em for __bknd config table // internal em for __bknd config table
__em!: EntityManager<T_INTERNAL_EM>; __em!: EntityManager<T_INTERNAL_EM>;
@@ -142,7 +158,7 @@ export class ModuleManager {
) { ) {
this.__em = new EntityManager([__bknd], this.connection); this.__em = new EntityManager([__bknd], this.connection);
this.modules = {} as Modules; this.modules = {} as Modules;
this.emgr = new EventManager(); this.emgr = new EventManager({ ...ModuleManagerEvents });
this.logger = new DebugLogger(debug_modules); this.logger = new DebugLogger(debug_modules);
let initial = {} as Partial<ModuleConfigs>; let initial = {} as Partial<ModuleConfigs>;
@@ -619,6 +635,13 @@ export class ModuleManager {
try { try {
// overwrite listener to run build inside this try/catch // overwrite listener to run build inside this try/catch
module.setListener(async () => { module.setListener(async () => {
await this.emgr.emit(
new ModuleManagerConfigUpdateEvent({
ctx: this.ctx(),
module: name,
config: module.config as any,
}),
);
await this.buildModules(); await this.buildModules();
}); });
+2 -1
View File
@@ -1,7 +1,8 @@
/** @jsxImportSource hono/jsx */ /** @jsxImportSource hono/jsx */
import type { App } from "App"; 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 { addFlashMessage } from "core/server/flash";
import { html } from "hono/html"; import { html } from "hono/html";
import { Fragment } from "hono/jsx"; import { Fragment } from "hono/jsx";
+2 -1
View File
@@ -1,4 +1,5 @@
import { Exception, isDebug, $console } from "core"; import { Exception, isDebug } from "core";
import { $console } from "core/utils";
import { cors } from "hono/cors"; import { cors } from "hono/cors";
import { Module } from "modules/Module"; import { Module } from "modules/Module";
import { AuthException } from "auth/errors"; import { AuthException } from "auth/errors";
+1 -2
View File
@@ -1,8 +1,7 @@
/// <reference types="@cloudflare/workers-types" /> /// <reference types="@cloudflare/workers-types" />
import type { App } from "App"; import type { App } from "App";
import { $console } from "core"; import { datetimeStringLocal, datetimeStringUTC, getTimezone, getTimezoneOffset, $console } from "core/utils";
import { datetimeStringLocal, datetimeStringUTC, getTimezone, getTimezoneOffset } from "core/utils";
import { getRuntimeKey } from "core/utils"; import { getRuntimeKey } from "core/utils";
import type { Context, Hono } from "hono"; import type { Context, Hono } from "hono";
import { Controller } from "modules/Controller"; import { Controller } from "modules/Controller";
+1 -1
View File
@@ -1,5 +1,5 @@
import { App, type AppPlugin } from "bknd"; import { App, type AppPlugin } from "bknd";
import { EntityTypescript } from "data/entities/EntityTypescript"; import { EntityTypescript } from "bknd/data";
export type SyncTypesOptions = { export type SyncTypesOptions = {
enabled?: boolean; enabled?: boolean;
+2 -1
View File
@@ -124,7 +124,8 @@ const Icons = {
}; };
const AdapterIcon = ({ type }: { type: string }) => { 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; if (!Icon) return null;
return <Icon />; return <Icon />;
}; };
+6 -10
View File
@@ -1,20 +1,16 @@
import { readFile } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import { s } from "./src/core/object/schema";
import { serveStatic } from "@hono/node-server/serve-static"; import { serveStatic } from "@hono/node-server/serve-static";
import { showRoutes } from "hono/dev"; import { showRoutes } from "hono/dev";
//import { registries } from "./src"; import { App, registries } from "./src";
import { App } from "./src/App"; import { StorageLocalAdapter } from "./src/adapter/node";
//import { StorageLocalAdapter } from "./src/adapter/node";
import type { Connection } from "./src/data/connection/Connection"; import type { Connection } from "./src/data/connection/Connection";
import { __bknd } from "./src/modules/ModuleManager"; import { __bknd } from "modules/ModuleManager";
import { nodeSqlite } from "./src/adapter/node/connection/NodeSqliteConnection"; 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 "./src/core/console"; import { $console } from "core/utils";
import { createClient } from "@libsql/client"; import { createClient } from "@libsql/client";
const t = s.string(); registries.media.register("local", StorageLocalAdapter);
//registries.media.register("local", StorageLocalAdapter);
const example = import.meta.env.VITE_EXAMPLE; const example = import.meta.env.VITE_EXAMPLE;
+6 -78
View File
@@ -37,6 +37,7 @@
"json-schema-form-react": "^0.0.2", "json-schema-form-react": "^0.0.2",
"json-schema-library": "10.0.0-rc7", "json-schema-library": "10.0.0-rc7",
"json-schema-to-ts": "^3.1.1", "json-schema-to-ts": "^3.1.1",
"jsonv-ts": "^0.1.0",
"kysely": "^0.27.6", "kysely": "^0.27.6",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"oauth4webapi": "^2.11.1", "oauth4webapi": "^2.11.1",
@@ -54,7 +55,6 @@
"@hono/vite-dev-server": "^0.19.1", "@hono/vite-dev-server": "^0.19.1",
"@hookform/resolvers": "^4.1.3", "@hookform/resolvers": "^4.1.3",
"@libsql/client": "^0.15.9", "@libsql/client": "^0.15.9",
"@libsql/kysely-libsql": "^0.4.1",
"@mantine/modals": "^7.17.1", "@mantine/modals": "^7.17.1",
"@mantine/notifications": "^7.17.1", "@mantine/notifications": "^7.17.1",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
@@ -74,7 +74,6 @@
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"jotai": "^2.12.2", "jotai": "^2.12.2",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"jsonv-ts": "link:jsonv-ts",
"kysely-d1": "^0.3.0", "kysely-d1": "^0.3.0",
"kysely-generic-sqlite": "^1.2.1", "kysely-generic-sqlite": "^1.2.1",
"libsql-stateless-easy": "^1.8.0", "libsql-stateless-easy": "^1.8.0",
@@ -99,7 +98,6 @@
"tsx": "^4.19.3", "tsx": "^4.19.3",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vite": "^6.3.5", "vite": "^6.3.5",
"vite-plugin-circular-dependency": "^0.5.0",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9", "vitest": "^3.0.9",
"wouter": "^3.6.0", "wouter": "^3.6.0",
@@ -755,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/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-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=="], "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.13", "", { "os": "linux", "cpu": "arm" }, "sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA=="],
@@ -999,7 +995,7 @@
"@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="], "@rollup/plugin-replace": ["@rollup/plugin-replace@2.4.2", "", { "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" }, "peerDependencies": { "rollup": "^1.20.0 || ^2.0.0" } }, "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="], "@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.35.0", "", { "os": "android", "cpu": "arm" }, "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.35.0", "", { "os": "android", "cpu": "arm" }, "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ=="],
@@ -2505,7 +2501,7 @@
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
"jsonv-ts": ["jsonv-ts@link:jsonv-ts", {}], "jsonv-ts": ["jsonv-ts@0.1.0", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-wJ+79o49MNie2Xk9w1hPN8ozjqemVWXOfWUTdioLui/SeGDC7C+QKXTDxsmUaIay86lorkjb3CCGo6JDKbyTZQ=="],
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
@@ -3609,8 +3605,6 @@
"vite-node": ["vite-node@3.0.8", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.6.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg=="], "vite-node": ["vite-node@3.0.8", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.6.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg=="],
"vite-plugin-circular-dependency": ["vite-plugin-circular-dependency@0.5.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0", "chalk": "^4.1.2" } }, "sha512-7SQX1IZbf5to/S3A3/syfntRNg20Cth6KgTCwHpNZIcuDCtPclV2Bwvdd9HWG+alKZ04mmdlchNOPQgBl7/vQQ=="],
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], "vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
"vitest": ["vitest@3.0.8", "", { "dependencies": { "@vitest/expect": "3.0.8", "@vitest/mocker": "3.0.8", "@vitest/pretty-format": "^3.0.8", "@vitest/runner": "3.0.8", "@vitest/snapshot": "3.0.8", "@vitest/spy": "3.0.8", "@vitest/utils": "3.0.8", "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.8", "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.8", "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA=="], "vitest": ["vitest@3.0.8", "", { "dependencies": { "@vitest/expect": "3.0.8", "@vitest/mocker": "3.0.8", "@vitest/pretty-format": "^3.0.8", "@vitest/runner": "3.0.8", "@vitest/snapshot": "3.0.8", "@vitest/spy": "3.0.8", "@vitest/utils": "3.0.8", "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.8", "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.8", "@vitest/ui": "3.0.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA=="],
@@ -3881,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/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=="], "@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=="], "@plasmicapp/query/swr": ["swr@1.3.0", "", { "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, "sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw=="],
@@ -3901,21 +3893,13 @@
"@remix-run/web-fetch/mrmime": ["mrmime@1.0.1", "", {}, "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw=="], "@remix-run/web-fetch/mrmime": ["mrmime@1.0.1", "", {}, "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw=="],
"@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-commonjs/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-commonjs/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], "@rollup/plugin-commonjs/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"@rollup/plugin-json/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-node-resolve/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-replace/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], "@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@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=="],
"@sagold/json-query/@sagold/json-pointer": ["@sagold/json-pointer@5.1.2", "", {}, "sha512-+wAhJZBXa6MNxRScg6tkqEbChEHMgVZAhTHVJ60Y7sbtXtu9XA49KfUkdWlS2x78D6H9nryiKePiYozumauPfA=="], "@sagold/json-query/@sagold/json-pointer": ["@sagold/json-pointer@5.1.2", "", {}, "sha512-+wAhJZBXa6MNxRScg6tkqEbChEHMgVZAhTHVJ60Y7sbtXtu9XA49KfUkdWlS2x78D6H9nryiKePiYozumauPfA=="],
@@ -4445,10 +4429,6 @@
"rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], "rollup/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="],
"rollup-plugin-sourcemaps/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"rollup-plugin-typescript2/@rollup/pluginutils": ["@rollup/pluginutils@3.1.0", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"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=="], "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=="],
"rollup-plugin-typescript2/resolve": ["resolve@1.17.0", "", { "dependencies": { "path-parse": "^1.0.6" } }, "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w=="], "rollup-plugin-typescript2/resolve": ["resolve@1.17.0", "", { "dependencies": { "path-parse": "^1.0.6" } }, "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w=="],
@@ -4645,32 +4625,6 @@
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "@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=="],
"@rollup/plugin-babel/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"@rollup/plugin-babel/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@rollup/plugin-commonjs/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"@rollup/plugin-commonjs/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@rollup/plugin-json/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"@rollup/plugin-json/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@rollup/plugin-node-resolve/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"@rollup/plugin-node-resolve/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@rollup/plugin-replace/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@testing-library/dom/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "@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=="], "@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
@@ -4865,14 +4819,6 @@
"rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"rollup-plugin-sourcemaps/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"rollup-plugin-sourcemaps/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"rollup-plugin-typescript2/@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
"rollup-plugin-typescript2/@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"rollup-plugin-typescript2/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], "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=="], "rollup-plugin-typescript2/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
@@ -5029,24 +4975,6 @@
"@cloudflare/vitest-pool-workers/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "@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=="], "@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=="], "@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=="],
+1
View File
@@ -1 +1,2 @@
*/package-lock.json */package-lock.json
*/bun.lock
-17
View File
@@ -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);
+1 -2
View File
@@ -8,8 +8,7 @@
"typegen": "wrangler types" "typegen": "wrangler types"
}, },
"dependencies": { "dependencies": {
"bknd": "file:../../app", "bknd": "file:../../app"
"kysely-d1": "^0.4.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.8.3", "typescript": "^5.8.3",