mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
various fixes: refactored imports, introduced fromDriver/toDriver to improve compat
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"version": "0.14.0",
|
"version": "0.15.0-rc.0",
|
||||||
"description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.",
|
"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": {
|
||||||
@@ -201,11 +201,11 @@
|
|||||||
},
|
},
|
||||||
"require": "./dist/adapter/sqlite/node.js"
|
"require": "./dist/adapter/sqlite/node.js"
|
||||||
},
|
},
|
||||||
"./plugins": {
|
"./plugins": {
|
||||||
"types": "./dist/types/plugins/index.d.ts",
|
"types": "./dist/types/plugins/index.d.ts",
|
||||||
"import": "./dist/plugins/index.js",
|
"import": "./dist/plugins/index.js",
|
||||||
"require": "./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",
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ export async function createAdapterApp<Config extends BkndConfig = BkndConfig, A
|
|||||||
connection = config.connection;
|
connection = config.connection;
|
||||||
} else {
|
} else {
|
||||||
const sqlite = (await import("bknd/adapter/sqlite")).sqlite;
|
const sqlite = (await import("bknd/adapter/sqlite")).sqlite;
|
||||||
connection = sqlite(config.connection ?? { url: ":memory:" });
|
const conf = config.connection ?? { url: ":memory:" };
|
||||||
$console.info(`Using ${connection.name} connection`);
|
connection = sqlite(conf);
|
||||||
|
$console.info(`Using ${connection.name} connection`, conf.url);
|
||||||
}
|
}
|
||||||
appConfig.connection = connection;
|
appConfig.connection = connection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ type MakeAppConfig = {
|
|||||||
async function makeApp(config: MakeAppConfig) {
|
async function makeApp(config: MakeAppConfig) {
|
||||||
return await createRuntimeApp({
|
return await createRuntimeApp({
|
||||||
serveStatic: await serveStatic(config.server?.platform ?? "node"),
|
serveStatic: await serveStatic(config.server?.platform ?? "node"),
|
||||||
|
...config,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ export async function makeAppFromEnv(options: Partial<RunOptions> = {}) {
|
|||||||
let app: App | undefined = undefined;
|
let app: App | undefined = undefined;
|
||||||
// first start from arguments if given
|
// first start from arguments if given
|
||||||
if (options.dbUrl) {
|
if (options.dbUrl) {
|
||||||
console.info("Using connection from", c.cyan("--db-url"));
|
console.info("Using connection from", c.cyan("--db-url"), c.cyan(options.dbUrl));
|
||||||
const connection = options.dbUrl ? { url: options.dbUrl } : undefined;
|
const connection = options.dbUrl ? { url: options.dbUrl } : undefined;
|
||||||
app = await makeApp({ connection, server: { platform: options.server } });
|
app = await makeApp({ connection, server: { platform: options.server } });
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector";
|
import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector";
|
||||||
import type { Constructor, DB } from "core";
|
import type { Constructor, DB } from "core";
|
||||||
import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner";
|
import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner";
|
||||||
|
import type { Field } from "data/fields/Field";
|
||||||
|
|
||||||
export type QB = SelectQueryBuilder<any, any, any>;
|
export type QB = SelectQueryBuilder<any, any, any>;
|
||||||
|
|
||||||
@@ -200,6 +201,14 @@ export abstract class Connection<Client = unknown> {
|
|||||||
|
|
||||||
abstract getFieldSchema(spec: FieldSpec, strict?: boolean): SchemaResponse;
|
abstract getFieldSchema(spec: FieldSpec, strict?: boolean): SchemaResponse;
|
||||||
|
|
||||||
|
toDriver(value: unknown, field: Field): unknown {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fromDriver(value: any, field: Field): unknown {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
// no-op by default
|
// no-op by default
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { TestRunner } from "core/test";
|
import type { TestRunner } from "core/test";
|
||||||
import { Connection, type FieldSpec } from "./Connection";
|
import { Connection, type FieldSpec } from "./Connection";
|
||||||
|
|
||||||
|
// @todo: add various datatypes: string, number, boolean, object, array, null, undefined, date, etc.
|
||||||
|
|
||||||
export function connectionTestSuite(
|
export function connectionTestSuite(
|
||||||
testRunner: TestRunner,
|
testRunner: TestRunner,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,6 +57,6 @@ export class LibsqlConnection extends SqliteConnection<Client> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function libsql(credentials: LibSqlCredentials): LibsqlConnection {
|
export function libsql(credentials: Client | LibSqlCredentials): LibsqlConnection {
|
||||||
return new LibsqlConnection(credentials);
|
return new LibsqlConnection(credentials);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } fro
|
|||||||
import type { Constructor } from "core";
|
import type { Constructor } from "core";
|
||||||
import { customIntrospector } from "../Connection";
|
import { customIntrospector } from "../Connection";
|
||||||
import { SqliteIntrospector } from "./SqliteIntrospector";
|
import { SqliteIntrospector } from "./SqliteIntrospector";
|
||||||
|
import type { Field } from "data/fields/Field";
|
||||||
|
|
||||||
|
// @todo: add pragmas
|
||||||
export type SqliteConnectionConfig<
|
export type SqliteConnectionConfig<
|
||||||
CustomDialect extends Constructor<Dialect> = Constructor<Dialect>,
|
CustomDialect extends Constructor<Dialect> = Constructor<Dialect>,
|
||||||
> = {
|
> = {
|
||||||
@@ -80,4 +82,24 @@ export abstract class SqliteConnection<Client = unknown> extends Connection<Clie
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override toDriver(value: unknown, field: Field): unknown {
|
||||||
|
if (field.type === "boolean") {
|
||||||
|
return value ? 1 : 0;
|
||||||
|
}
|
||||||
|
if (typeof value === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fromDriver(value: any, field: Field): unknown {
|
||||||
|
if (field.type === "boolean" && typeof value === "number") {
|
||||||
|
return value === 1;
|
||||||
|
}
|
||||||
|
if (value === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { type Static, StringEnum, StringRecord, objectTransform } from "core/utils";
|
import { type Static, StringEnum, StringRecord, objectTransform } from "core/utils";
|
||||||
import * as tb from "@sinclair/typebox";
|
import * as tb from "@sinclair/typebox";
|
||||||
import {
|
|
||||||
FieldClassMap,
|
|
||||||
RelationClassMap,
|
|
||||||
RelationFieldClassMap,
|
|
||||||
entityConfigSchema,
|
|
||||||
entityTypes,
|
|
||||||
} from "data";
|
|
||||||
import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
|
import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
|
||||||
|
import { FieldClassMap } from "data/fields";
|
||||||
|
import { RelationClassMap, RelationFieldClassMap } from "data/relations";
|
||||||
|
import { entityConfigSchema, entityTypes } from "data/entities";
|
||||||
import { primaryFieldTypes } from "./fields";
|
import { primaryFieldTypes } from "./fields";
|
||||||
|
|
||||||
export const FIELDS = {
|
export const FIELDS = {
|
||||||
|
|||||||
@@ -278,6 +278,10 @@ export class EntityManager<TBD extends object = DefaultDB> {
|
|||||||
row[key] = field.getDefault();
|
row[key] = field.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transform from driver
|
||||||
|
value = this.connection.fromDriver(value, field);
|
||||||
|
|
||||||
|
// transform from field
|
||||||
row[key] = field.transformRetrieve(value as any);
|
row[key] = field.transformRetrieve(value as any);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new TransformRetrieveFailedException(
|
throw new TransformRetrieveFailedException(
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { $console, type DB as DefaultDB, type PrimaryFieldType } from "core";
|
import type { DB as DefaultDB, PrimaryFieldType } from "core";
|
||||||
import { type EmitsEvents, EventManager } from "core/events";
|
import { type EmitsEvents, EventManager } from "core/events";
|
||||||
import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely";
|
import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely";
|
||||||
import { type TActionContext, WhereBuilder } from "../..";
|
import type { TActionContext } from "../..";
|
||||||
|
import { WhereBuilder } from "../query/WhereBuilder";
|
||||||
import type { Entity, EntityData, EntityManager } from "../../entities";
|
import type { Entity, EntityData, EntityManager } from "../../entities";
|
||||||
import { InvalidSearchParamsException } from "../../errors";
|
import { InvalidSearchParamsException } from "../../errors";
|
||||||
import { MutatorEvents } from "../../events";
|
import { MutatorEvents } from "../../events";
|
||||||
import { RelationMutator } from "../../relations";
|
import { RelationMutator } from "../../relations";
|
||||||
import type { RepoQuery } from "../../server/query";
|
import type { RepoQuery } from "../../server/query";
|
||||||
import { MutatorResult, type MutatorResultOptions } from "./MutatorResult";
|
import { MutatorResult, type MutatorResultOptions } from "./MutatorResult";
|
||||||
|
import { transformObject } from "core/utils";
|
||||||
|
|
||||||
type MutatorQB =
|
type MutatorQB =
|
||||||
| InsertQueryBuilder<any, any, any>
|
| InsertQueryBuilder<any, any, any>
|
||||||
@@ -86,7 +88,11 @@ export class Mutator<
|
|||||||
throw new Error(`Field "${key}" is not fillable on entity "${entity.name}"`);
|
throw new Error(`Field "${key}" is not fillable on entity "${entity.name}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transform from field
|
||||||
validatedData[key] = await field.transformPersist(data[key], this.em, context);
|
validatedData[key] = await field.transformPersist(data[key], this.em, context);
|
||||||
|
|
||||||
|
// transform to driver
|
||||||
|
validatedData[key] = this.em.connection.toDriver(validatedData[key], field);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(validatedData).length === 0) {
|
if (Object.keys(validatedData).length === 0) {
|
||||||
@@ -283,6 +289,10 @@ export class Mutator<
|
|||||||
): Promise<MutatorResult<Output[]>> {
|
): Promise<MutatorResult<Output[]>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
const validatedData = await this.getValidatedData(data, "update");
|
const validatedData = await this.getValidatedData(data, "update");
|
||||||
|
console.log("updateWhere", {
|
||||||
|
entity,
|
||||||
|
validatedData,
|
||||||
|
});
|
||||||
|
|
||||||
// @todo: add a way to delete all by adding force?
|
// @todo: add a way to delete all by adding force?
|
||||||
if (!where || typeof where !== "object" || Object.keys(where).length === 0) {
|
if (!where || typeof where !== "object" || Object.keys(where).length === 0) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Result, type ResultJSON, type ResultOptions } from "../Result";
|
|||||||
|
|
||||||
export type MutatorResultOptions = ResultOptions & {
|
export type MutatorResultOptions = ResultOptions & {
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
|
logParams?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MutatorResultJSON<T = EntityData[]> = ResultJSON<T>;
|
export type MutatorResultJSON<T = EntityData[]> = ResultJSON<T>;
|
||||||
@@ -19,7 +20,10 @@ export class MutatorResult<T = EntityData[]> extends Result<T> {
|
|||||||
hydrator: (rows) => em.hydrate(entity.name, rows as any),
|
hydrator: (rows) => em.hydrate(entity.name, rows as any),
|
||||||
beforeExecute: (compiled) => {
|
beforeExecute: (compiled) => {
|
||||||
if (!options?.silent) {
|
if (!options?.silent) {
|
||||||
$console.debug(`[Mutation]\n${compiled.sql}\n`);
|
$console.debug(
|
||||||
|
`[Mutation]\n${compiled.sql}\n`,
|
||||||
|
options?.logParams ? compiled.parameters : undefined,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|||||||
@@ -246,8 +246,10 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
qb = WhereBuilder.addClause(qb, options.where);
|
qb = WhereBuilder.addClause(qb, options.where);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignore.includes("limit")) qb = qb.limit(options.limit ?? defaults.limit);
|
if (!ignore.includes("limit")) {
|
||||||
if (!ignore.includes("offset")) qb = qb.offset(options.offset ?? defaults.offset);
|
qb = qb.limit(options.limit ?? defaults.limit);
|
||||||
|
if (!ignore.includes("offset")) qb = qb.offset(options.offset ?? defaults.offset);
|
||||||
|
}
|
||||||
|
|
||||||
// sorting
|
// sorting
|
||||||
if (!ignore.includes("sort")) {
|
if (!ignore.includes("sort")) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { s } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
import { WhereBuilder, type WhereQuery } from "data";
|
import { WhereBuilder, type WhereQuery } from "data/entities/query/WhereBuilder";
|
||||||
import { $console } from "core";
|
import { $console } from "core";
|
||||||
import { isObject } from "core/utils";
|
import { isObject } from "core/utils";
|
||||||
import type { CoercionOptions, TAnyOf } from "jsonv-ts";
|
import type { CoercionOptions, TAnyOf } from "jsonv-ts";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Static } from "core/utils";
|
import type { Static } from "core/utils";
|
||||||
import { Field, baseFieldConfigSchema } from "data";
|
import { Field, baseFieldConfigSchema } from "data/fields";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|
||||||
|
|||||||
@@ -8,25 +8,39 @@ 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/LibsqlConnection";
|
||||||
import { $console } from "core";
|
import { $console } from "core";
|
||||||
|
import { createClient } from "@libsql/client";
|
||||||
|
|
||||||
registries.media.register("local", StorageLocalAdapter);
|
registries.media.register("local", StorageLocalAdapter);
|
||||||
|
|
||||||
const example = import.meta.env.VITE_EXAMPLE;
|
const example = import.meta.env.VITE_EXAMPLE;
|
||||||
const dbUrl = example ? `file:.configs/${example}.db` : import.meta.env.VITE_DB_URL;
|
|
||||||
|
|
||||||
let connection: Connection;
|
let connection: Connection;
|
||||||
if (dbUrl) {
|
|
||||||
connection = nodeSqlite({ url: dbUrl });
|
if (import.meta.env.VITE_DB_LIBSQL_URL) {
|
||||||
$console.debug("Using node-sqlite connection", dbUrl);
|
connection = libsql(
|
||||||
} else if (import.meta.env.VITE_DB_LIBSQL_URL) {
|
createClient({
|
||||||
connection = libsql({
|
url: import.meta.env.VITE_DB_LIBSQL_URL!,
|
||||||
url: import.meta.env.VITE_DB_LIBSQL_URL!,
|
authToken: import.meta.env.VITE_DB_LIBSQL_TOKEN!,
|
||||||
authToken: import.meta.env.VITE_DB_LIBSQL_TOKEN!,
|
}),
|
||||||
});
|
);
|
||||||
$console.debug("Using libsql connection", import.meta.env.VITE_DB_URL);
|
$console.debug("Using libsql connection", import.meta.env.VITE_DB_URL);
|
||||||
} else {
|
} else {
|
||||||
connection = nodeSqlite();
|
const dbUrl = example ? `file:.configs/${example}.db` : import.meta.env.VITE_DB_URL;
|
||||||
$console.debug("No connection provided, using in-memory database");
|
if (dbUrl) {
|
||||||
|
connection = nodeSqlite({ url: dbUrl });
|
||||||
|
$console.debug("Using node-sqlite connection", dbUrl);
|
||||||
|
} else if (import.meta.env.VITE_DB_LIBSQL_URL) {
|
||||||
|
connection = libsql(
|
||||||
|
createClient({
|
||||||
|
url: import.meta.env.VITE_DB_LIBSQL_URL!,
|
||||||
|
authToken: import.meta.env.VITE_DB_LIBSQL_TOKEN!,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
$console.debug("Using libsql connection", import.meta.env.VITE_DB_URL);
|
||||||
|
} else {
|
||||||
|
connection = nodeSqlite();
|
||||||
|
$console.debug("No connection provided, using in-memory database");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if (example) {
|
/* if (example) {
|
||||||
|
|||||||
Reference in New Issue
Block a user