diff --git a/app/package.json b/app/package.json index 18b9e5e..2d85879 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.14.0", + "version": "0.15.0-rc.0", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, React Router, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { @@ -201,11 +201,11 @@ }, "require": "./dist/adapter/sqlite/node.js" }, - "./plugins": { - "types": "./dist/types/plugins/index.d.ts", - "import": "./dist/plugins/index.js", - "require": "./dist/plugins/index.js" - }, + "./plugins": { + "types": "./dist/types/plugins/index.d.ts", + "import": "./dist/plugins/index.js", + "require": "./dist/plugins/index.js" + }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", diff --git a/app/src/adapter/index.ts b/app/src/adapter/index.ts index abd2f8e..7554040 100644 --- a/app/src/adapter/index.ts +++ b/app/src/adapter/index.ts @@ -69,8 +69,9 @@ export async function createAdapterApp = {}) { let app: App | undefined = undefined; // first start from arguments if given if (options.dbUrl) { - console.info("Using connection from", c.cyan("--db-url")); + console.info("Using connection from", c.cyan("--db-url"), c.cyan(options.dbUrl)); const connection = options.dbUrl ? { url: options.dbUrl } : undefined; app = await makeApp({ connection, server: { platform: options.server } }); diff --git a/app/src/data/connection/Connection.ts b/app/src/data/connection/Connection.ts index fab8e36..cd807b7 100644 --- a/app/src/data/connection/Connection.ts +++ b/app/src/data/connection/Connection.ts @@ -20,6 +20,7 @@ import { import type { BaseIntrospector, BaseIntrospectorConfig } from "./BaseIntrospector"; import type { Constructor, DB } from "core"; import { KyselyPluginRunner } from "data/plugins/KyselyPluginRunner"; +import type { Field } from "data/fields/Field"; export type QB = SelectQueryBuilder; @@ -200,6 +201,14 @@ export abstract class Connection { abstract getFieldSchema(spec: FieldSpec, strict?: boolean): SchemaResponse; + toDriver(value: unknown, field: Field): unknown { + return value; + } + + fromDriver(value: any, field: Field): unknown { + return value; + } + async close(): Promise { // no-op by default } diff --git a/app/src/data/connection/connection-test-suite.ts b/app/src/data/connection/connection-test-suite.ts index 5305337..6d6f84d 100644 --- a/app/src/data/connection/connection-test-suite.ts +++ b/app/src/data/connection/connection-test-suite.ts @@ -1,6 +1,8 @@ import type { TestRunner } from "core/test"; import { Connection, type FieldSpec } from "./Connection"; +// @todo: add various datatypes: string, number, boolean, object, array, null, undefined, date, etc. + export function connectionTestSuite( testRunner: TestRunner, { diff --git a/app/src/data/connection/sqlite/LibsqlConnection.ts b/app/src/data/connection/sqlite/LibsqlConnection.ts index e6833eb..cf68962 100644 --- a/app/src/data/connection/sqlite/LibsqlConnection.ts +++ b/app/src/data/connection/sqlite/LibsqlConnection.ts @@ -57,6 +57,6 @@ export class LibsqlConnection extends SqliteConnection { } } -export function libsql(credentials: LibSqlCredentials): LibsqlConnection { +export function libsql(credentials: Client | LibSqlCredentials): LibsqlConnection { return new LibsqlConnection(credentials); } diff --git a/app/src/data/connection/sqlite/SqliteConnection.ts b/app/src/data/connection/sqlite/SqliteConnection.ts index 705046d..d435c6c 100644 --- a/app/src/data/connection/sqlite/SqliteConnection.ts +++ b/app/src/data/connection/sqlite/SqliteConnection.ts @@ -11,7 +11,9 @@ import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } fro import type { Constructor } from "core"; import { customIntrospector } from "../Connection"; import { SqliteIntrospector } from "./SqliteIntrospector"; +import type { Field } from "data/fields/Field"; +// @todo: add pragmas export type SqliteConnectionConfig< CustomDialect extends Constructor = Constructor, > = { @@ -80,4 +82,24 @@ export abstract class SqliteConnection extends Connection { row[key] = field.getDefault(); } + // transform from driver + value = this.connection.fromDriver(value, field); + + // transform from field row[key] = field.transformRetrieve(value as any); } catch (e: any) { throw new TransformRetrieveFailedException( diff --git a/app/src/data/entities/mutation/Mutator.ts b/app/src/data/entities/mutation/Mutator.ts index bf8cebe..a270c7a 100644 --- a/app/src/data/entities/mutation/Mutator.ts +++ b/app/src/data/entities/mutation/Mutator.ts @@ -1,13 +1,15 @@ -import { $console, type DB as DefaultDB, type PrimaryFieldType } from "core"; +import type { DB as DefaultDB, PrimaryFieldType } from "core"; import { type EmitsEvents, EventManager } from "core/events"; import type { DeleteQueryBuilder, InsertQueryBuilder, UpdateQueryBuilder } from "kysely"; -import { type TActionContext, WhereBuilder } from "../.."; +import type { TActionContext } from "../.."; +import { WhereBuilder } from "../query/WhereBuilder"; import type { Entity, EntityData, EntityManager } from "../../entities"; import { InvalidSearchParamsException } from "../../errors"; import { MutatorEvents } from "../../events"; import { RelationMutator } from "../../relations"; import type { RepoQuery } from "../../server/query"; import { MutatorResult, type MutatorResultOptions } from "./MutatorResult"; +import { transformObject } from "core/utils"; type MutatorQB = | InsertQueryBuilder @@ -86,7 +88,11 @@ export class Mutator< throw new Error(`Field "${key}" is not fillable on entity "${entity.name}"`); } + // transform from field validatedData[key] = await field.transformPersist(data[key], this.em, context); + + // transform to driver + validatedData[key] = this.em.connection.toDriver(validatedData[key], field); } if (Object.keys(validatedData).length === 0) { @@ -283,6 +289,10 @@ export class Mutator< ): Promise> { const entity = this.entity; const validatedData = await this.getValidatedData(data, "update"); + console.log("updateWhere", { + entity, + validatedData, + }); // @todo: add a way to delete all by adding force? if (!where || typeof where !== "object" || Object.keys(where).length === 0) { diff --git a/app/src/data/entities/mutation/MutatorResult.ts b/app/src/data/entities/mutation/MutatorResult.ts index 25ae7f6..b5ad083 100644 --- a/app/src/data/entities/mutation/MutatorResult.ts +++ b/app/src/data/entities/mutation/MutatorResult.ts @@ -5,6 +5,7 @@ import { Result, type ResultJSON, type ResultOptions } from "../Result"; export type MutatorResultOptions = ResultOptions & { silent?: boolean; + logParams?: boolean; }; export type MutatorResultJSON = ResultJSON; @@ -19,7 +20,10 @@ export class MutatorResult extends Result { hydrator: (rows) => em.hydrate(entity.name, rows as any), beforeExecute: (compiled) => { if (!options?.silent) { - $console.debug(`[Mutation]\n${compiled.sql}\n`); + $console.debug( + `[Mutation]\n${compiled.sql}\n`, + options?.logParams ? compiled.parameters : undefined, + ); } }, onError: (error) => { diff --git a/app/src/data/entities/query/Repository.ts b/app/src/data/entities/query/Repository.ts index ce39c6a..f41bd8b 100644 --- a/app/src/data/entities/query/Repository.ts +++ b/app/src/data/entities/query/Repository.ts @@ -246,8 +246,10 @@ export class Repository