diff --git a/app/src/data/entities/EntityManager.ts b/app/src/data/entities/EntityManager.ts index 544c8ad..36168f8 100644 --- a/app/src/data/entities/EntityManager.ts +++ b/app/src/data/entities/EntityManager.ts @@ -258,6 +258,9 @@ export class EntityManager { // @todo: centralize and add tests hydrate(entity_name: string, _data: EntityData[]) { + if (!Array.isArray(_data) || _data.length === 0) { + return []; + } const entity = this.entity(entity_name); const data: EntityData[] = []; diff --git a/bun.lock b/bun.lock index c7350b2..9e26bd2 100644 --- a/bun.lock +++ b/bun.lock @@ -151,7 +151,6 @@ "bknd": "workspace:*", "kysely-neon": "^1.3.0", "tsup": "^8.4.0", - "typescript": "^5.8.2", }, "optionalDependencies": { "kysely": "^0.27.6", @@ -3832,10 +3831,6 @@ "@bknd/plasmic/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "@bknd/postgres/@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], - - "@bknd/postgres/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - "@bknd/sqlocal/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "@bundled-es-modules/cookie/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], @@ -4684,8 +4679,6 @@ "@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="], - "@bknd/postgres/@types/bun/bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], - "@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], "@cloudflare/vitest-pool-workers/miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], diff --git a/docs/content/docs/(documentation)/usage/database.mdx b/docs/content/docs/(documentation)/usage/database.mdx index febffdd..b09587f 100644 --- a/docs/content/docs/(documentation)/usage/database.mdx +++ b/docs/content/docs/(documentation)/usage/database.mdx @@ -224,7 +224,7 @@ Example using `@neondatabase/serverless`: import { createCustomPostgresConnection } from "@bknd/postgres"; import { NeonDialect } from "kysely-neon"; -const neon = createCustomPostgresConnection(NeonDialect); +const neon = createCustomPostgresConnection("neon", NeonDialect); serve({ connection: neon({ @@ -247,7 +247,7 @@ const xata = new client({ branch: process.env.XATA_BRANCH, }); -const xataConnection = createCustomPostgresConnection(XataDialect, { +const xataConnection = createCustomPostgresConnection("xata", XataDialect, { supports: { batching: false, }, diff --git a/packages/postgres/README.md b/packages/postgres/README.md index b66395b..ed5d1e6 100644 --- a/packages/postgres/README.md +++ b/packages/postgres/README.md @@ -59,7 +59,7 @@ You can create a custom kysely postgres dialect by using the `createCustomPostgr ```ts import { createCustomPostgresConnection } from "@bknd/postgres"; -const connection = createCustomPostgresConnection(MyDialect)({ +const connection = createCustomPostgresConnection("my_postgres_dialect", MyDialect)({ // your custom dialect configuration supports: { batching: true @@ -75,7 +75,7 @@ const connection = createCustomPostgresConnection(MyDialect)({ import { createCustomPostgresConnection } from "@bknd/postgres"; import { NeonDialect } from "kysely-neon"; -const connection = createCustomPostgresConnection(NeonDialect)({ +const connection = createCustomPostgresConnection("neon", NeonDialect)({ connectionString: process.env.NEON, }); ``` @@ -94,7 +94,7 @@ const xata = new client({ branch: process.env.XATA_BRANCH, }); -const connection = createCustomPostgresConnection(XataDialect, { +const connection = createCustomPostgresConnection("xata", XataDialect, { supports: { batching: false, }, diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 6d77c10..e822af4 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -31,8 +31,7 @@ "@xata.io/kysely": "^0.2.1", "bknd": "workspace:*", "kysely-neon": "^1.3.0", - "tsup": "^8.4.0", - "typescript": "^5.8.2" + "tsup": "^8.4.0" }, "tsup": { "entry": ["src/index.ts"], diff --git a/packages/postgres/src/PgPostgresConnection.ts b/packages/postgres/src/PgPostgresConnection.ts index 85a5c84..c96e693 100644 --- a/packages/postgres/src/PgPostgresConnection.ts +++ b/packages/postgres/src/PgPostgresConnection.ts @@ -1,12 +1,13 @@ import { Kysely, PostgresDialect } from "kysely"; import { PostgresIntrospector } from "./PostgresIntrospector"; import { PostgresConnection, plugins } from "./PostgresConnection"; -import { customIntrospector } from "bknd/data"; +import { customIntrospector } from "bknd"; import $pg from "pg"; export type PgPostgresConnectionConfig = $pg.PoolConfig; export class PgPostgresConnection extends PostgresConnection { + override name = "pg"; private pool: $pg.Pool; constructor(config: PgPostgresConnectionConfig) { diff --git a/packages/postgres/src/PostgresConnection.ts b/packages/postgres/src/PostgresConnection.ts index 9ea1d2c..ff67991 100644 --- a/packages/postgres/src/PostgresConnection.ts +++ b/packages/postgres/src/PostgresConnection.ts @@ -1,4 +1,11 @@ -import { Connection, type DbFunctions, type FieldSpec, type SchemaResponse } from "bknd/data"; +import { + Connection, + type DbFunctions, + type FieldSpec, + type SchemaResponse, + type ConnQuery, + type ConnQueryResults, +} from "bknd"; import { ParseJSONResultsPlugin, type ColumnDataType, @@ -13,12 +20,13 @@ export type QB = SelectQueryBuilder; export const plugins = [new ParseJSONResultsPlugin()]; -export abstract class PostgresConnection extends Connection { +export abstract class PostgresConnection extends Connection { protected override readonly supported = { batching: true, + softscans: true, }; - constructor(kysely: Kysely, fn?: Partial, _plugins?: KyselyPlugin[]) { + constructor(kysely: Kysely, fn?: Partial, _plugins?: KyselyPlugin[]) { super( kysely, fn ?? { @@ -73,13 +81,9 @@ export abstract class PostgresConnection extends Connection { ]; } - protected override async batch( - queries: [...Queries], - ): Promise<{ - [K in keyof Queries]: Awaited>; - }> { + override async executeQueries(...qbs: O): Promise> { return this.kysely.transaction().execute(async (trx) => { - return Promise.all(queries.map((q) => trx.executeQuery(q).then((r) => r.rows))); + return Promise.all(qbs.map((q) => trx.executeQuery(q))); }) as any; } } diff --git a/packages/postgres/src/PostgresIntrospector.ts b/packages/postgres/src/PostgresIntrospector.ts index 82b75ba..4b1c928 100644 --- a/packages/postgres/src/PostgresIntrospector.ts +++ b/packages/postgres/src/PostgresIntrospector.ts @@ -1,5 +1,5 @@ import { type SchemaMetadata, sql } from "kysely"; -import { BaseIntrospector } from "bknd/data"; +import { BaseIntrospector } from "bknd"; type PostgresSchemaSpec = { name: string; diff --git a/packages/postgres/src/PostgresJsConnection.ts b/packages/postgres/src/PostgresJsConnection.ts index 1ab1fe4..deff210 100644 --- a/packages/postgres/src/PostgresJsConnection.ts +++ b/packages/postgres/src/PostgresJsConnection.ts @@ -1,13 +1,15 @@ import { Kysely } from "kysely"; import { PostgresIntrospector } from "./PostgresIntrospector"; import { PostgresConnection, plugins } from "./PostgresConnection"; -import { customIntrospector } from "bknd/data"; +import { customIntrospector } from "bknd"; import { PostgresJSDialect } from "kysely-postgres-js"; import $postgresJs, { type Sql, type Options, type PostgresType } from "postgres"; export type PostgresJsConfig = Options>; export class PostgresJsConnection extends PostgresConnection { + override name = "postgres-js"; + private postgres: Sql; constructor(opts: { postgres: Sql }) { diff --git a/packages/postgres/src/custom.ts b/packages/postgres/src/custom.ts index b7369f1..9d626a0 100644 --- a/packages/postgres/src/custom.ts +++ b/packages/postgres/src/custom.ts @@ -1,9 +1,10 @@ -import type { Constructor } from "bknd/core"; -import { customIntrospector, type DbFunctions } from "bknd/data"; +import { customIntrospector, type DbFunctions } from "bknd"; import { Kysely, type Dialect, type KyselyPlugin } from "kysely"; import { plugins, PostgresConnection } from "./PostgresConnection"; import { PostgresIntrospector } from "./PostgresIntrospector"; +export type Constructor = new (...args: any[]) => T; + export type CustomPostgresConnection = { supports?: PostgresConnection["supported"]; fn?: Partial; @@ -15,17 +16,19 @@ export function createCustomPostgresConnection< T extends Constructor, C extends ConstructorParameters[0], >( + name: string, dialect: Constructor, options?: CustomPostgresConnection, -): (config: C) => PostgresConnection { +): (config: C) => PostgresConnection { const supported = { batching: true, ...((options?.supports ?? {}) as any), }; return (config: C) => - new (class extends PostgresConnection { - protected override readonly supported = supported; + new (class extends PostgresConnection { + override name = name; + override readonly supported = supported; constructor(config: C) { super( diff --git a/packages/postgres/test/suite.ts b/packages/postgres/test/suite.ts index 0d4feec..b5c1cf3 100644 --- a/packages/postgres/test/suite.ts +++ b/packages/postgres/test/suite.ts @@ -1,8 +1,11 @@ import { describe, beforeAll, afterAll, expect, it, afterEach } from "bun:test"; import type { PostgresConnection } from "../src"; -import { createApp } from "bknd"; -import * as proto from "bknd/data"; +import { createApp, em, entity, text } from "bknd"; import { disableConsoleLog, enableConsoleLog } from "bknd/utils"; +// @ts-ignore +import { connectionTestSuite } from "$bknd/data/connection/connection-test-suite"; +// @ts-ignore +import { bunTestRunner } from "$bknd/adapter/bun/test"; export type TestSuiteConfig = { createConnection: () => InstanceType; @@ -12,8 +15,9 @@ export type TestSuiteConfig = { export async function defaultCleanDatabase(connection: InstanceType) { const kysely = connection.kysely; - // drop all tables & create new schema + // drop all tables+indexes & create new schema await kysely.schema.dropSchema("public").ifExists().cascade().execute(); + await kysely.schema.dropIndex("public").ifExists().cascade().execute(); await kysely.schema.createSchema("public").execute(); } @@ -32,6 +36,23 @@ export function testSuite(config: TestSuiteConfig) { beforeAll(() => disableConsoleLog(["log", "warn", "error"])); afterAll(() => enableConsoleLog()); + // @todo: postgres seems to add multiple indexes, thus failing the test suite + /* describe("test suite", () => { + connectionTestSuite(bunTestRunner, { + makeConnection: () => { + const connection = config.createConnection(); + return { + connection, + dispose: async () => { + await cleanDatabase(connection, config); + await connection.close(); + }, + }; + }, + rawDialectDetails: [], + }); + }); */ + describe("base", () => { it("should connect to the database", async () => { const connection = config.createConnection(); @@ -73,14 +94,14 @@ export function testSuite(config: TestSuiteConfig) { }); it("should create a basic schema", async () => { - const schema = proto.em( + const schema = em( { - posts: proto.entity("posts", { - title: proto.text().required(), - content: proto.text(), + posts: entity("posts", { + title: text().required(), + content: text(), }), - comments: proto.entity("comments", { - content: proto.text(), + comments: entity("comments", { + content: text(), }), }, (fns, s) => { @@ -153,20 +174,20 @@ export function testSuite(config: TestSuiteConfig) { }); it("should support uuid", async () => { - const schema = proto.em( + const schema = em( { - posts: proto.entity( + posts: entity( "posts", { - title: proto.text().required(), - content: proto.text(), + title: text().required(), + content: text(), }, { primary_format: "uuid", }, ), - comments: proto.entity("comments", { - content: proto.text(), + comments: entity("comments", { + content: text(), }), }, (fns, s) => { @@ -187,8 +208,8 @@ export function testSuite(config: TestSuiteConfig) { // @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 $em = app.em; + const mutator = $em.mutator($em.entity("posts")); const data = await mutator.insertOne({ title: "Hello", content: "World" }); expect(data.data.id).toBeString(); expect(String(data.data.id).length).toBe(36); diff --git a/packages/postgres/tsconfig.json b/packages/postgres/tsconfig.json index d2359e0..5bb10f6 100644 --- a/packages/postgres/tsconfig.json +++ b/packages/postgres/tsconfig.json @@ -1,29 +1,33 @@ { - "compilerOptions": { - "composite": false, - "module": "ESNext", - "moduleResolution": "bundler", - "allowImportingTsExtensions": false, - "target": "ES2022", - "noImplicitAny": false, - "allowJs": true, - "verbatimModuleSyntax": true, - "declaration": true, - "strict": true, - "allowUnusedLabels": false, - "allowUnreachableCode": false, - "exactOptionalPropertyTypes": false, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": false, - "noUncheckedIndexedAccess": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "isolatedModules": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["./src/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "composite": false, + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": false, + "target": "ES2022", + "noImplicitAny": false, + "allowJs": true, + "verbatimModuleSyntax": true, + "declaration": true, + "strict": true, + "allowUnusedLabels": false, + "allowUnreachableCode": false, + "exactOptionalPropertyTypes": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "$bknd/*": ["../../app/src/*"] + } + }, + "include": ["./src/**/*.ts"], + "exclude": ["node_modules"] }