@fragno-dev/db 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +137 -13
- package/.turbo/turbo-test.log +36 -0
- package/CHANGELOG.md +7 -0
- package/dist/adapters/adapters.d.ts +18 -0
- package/dist/adapters/adapters.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +21 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.js +62 -0
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts +17 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-query.js +139 -0
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +9 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +300 -0
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +82 -0
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js +125 -0
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -0
- package/dist/adapters/drizzle/generate.js +273 -0
- package/dist/adapters/drizzle/generate.js.map +1 -0
- package/dist/adapters/drizzle/join-column-utils.js +28 -0
- package/dist/adapters/drizzle/join-column-utils.js.map +1 -0
- package/dist/adapters/drizzle/shared.js +11 -0
- package/dist/adapters/drizzle/shared.js.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +23 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.js +119 -0
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-builder.js +306 -0
- package/dist/adapters/kysely/kysely-query-builder.js.map +1 -0
- package/dist/adapters/kysely/kysely-query-compiler.js +67 -0
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-query.js +158 -0
- package/dist/adapters/kysely/kysely-query.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +139 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -0
- package/dist/adapters/kysely/kysely-uow-executor.js +89 -0
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -0
- package/dist/adapters/kysely/migration/execute.js +176 -0
- package/dist/adapters/kysely/migration/execute.js.map +1 -0
- package/dist/fragment.d.ts +54 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/fragment.js +92 -0
- package/dist/fragment.js.map +1 -0
- package/dist/id.d.ts +2 -0
- package/dist/migration-engine/auto-from-schema.js +116 -0
- package/dist/migration-engine/auto-from-schema.js.map +1 -0
- package/dist/migration-engine/create.d.ts +41 -0
- package/dist/migration-engine/create.d.ts.map +1 -0
- package/dist/migration-engine/create.js +58 -0
- package/dist/migration-engine/create.js.map +1 -0
- package/dist/migration-engine/shared.d.ts +90 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js +8 -0
- package/dist/migration-engine/shared.js.map +1 -0
- package/dist/mod.d.ts +55 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +111 -2
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js +108 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column-builder.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js +55 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/column.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js +18 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/entity.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js +183 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/common.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js +58 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/columns/enum.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js +68 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/foreign-keys.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js +56 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/unique-constraint.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js +65 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/pg-core/utils/array.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js +81 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/conditions.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js +13 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/expressions/select.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js +10 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/functions/aggregate.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js +372 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/sql/sql.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js +23 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/subquery.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js +62 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/table.utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing-utils.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js +8 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/tracing.js.map +1 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js +6 -0
- package/dist/node_modules/.bun/drizzle-orm@0.44.6_4fae081eecb963e2/node_modules/drizzle-orm/view-common.js.map +1 -0
- package/dist/query/condition-builder.d.ts +41 -0
- package/dist/query/condition-builder.d.ts.map +1 -0
- package/dist/query/condition-builder.js +93 -0
- package/dist/query/condition-builder.js.map +1 -0
- package/dist/query/cursor.d.ts +88 -0
- package/dist/query/cursor.d.ts.map +1 -0
- package/dist/query/cursor.js +103 -0
- package/dist/query/cursor.js.map +1 -0
- package/dist/query/orm/orm.d.ts +18 -0
- package/dist/query/orm/orm.d.ts.map +1 -0
- package/dist/query/orm/orm.js +48 -0
- package/dist/query/orm/orm.js.map +1 -0
- package/dist/query/query.d.ts +79 -0
- package/dist/query/query.d.ts.map +1 -0
- package/dist/query/query.js +1 -0
- package/dist/query/result-transform.js +155 -0
- package/dist/query/result-transform.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +435 -0
- package/dist/query/unit-of-work.d.ts.map +1 -0
- package/dist/query/unit-of-work.js +549 -0
- package/dist/query/unit-of-work.js.map +1 -0
- package/dist/schema/create.d.ts +273 -116
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +410 -222
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/serialize.js +101 -0
- package/dist/schema/serialize.js.map +1 -0
- package/dist/schema-generator/schema-generator.d.ts +15 -0
- package/dist/schema-generator/schema-generator.d.ts.map +1 -0
- package/dist/shared/providers.d.ts +6 -0
- package/dist/shared/providers.d.ts.map +1 -0
- package/dist/util/import-generator.js +26 -0
- package/dist/util/import-generator.js.map +1 -0
- package/dist/util/parse.js +15 -0
- package/dist/util/parse.js.map +1 -0
- package/dist/util/types.d.ts +8 -0
- package/dist/util/types.d.ts.map +1 -0
- package/package.json +63 -2
- package/src/adapters/adapters.ts +22 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +433 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +122 -0
- package/src/adapters/drizzle/drizzle-adapter.ts +118 -0
- package/src/adapters/drizzle/drizzle-query.ts +234 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +1084 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +546 -0
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +165 -0
- package/src/adapters/drizzle/drizzle-uow-executor.ts +213 -0
- package/src/adapters/drizzle/generate.test.ts +643 -0
- package/src/adapters/drizzle/generate.ts +481 -0
- package/src/adapters/drizzle/join-column-utils.test.ts +79 -0
- package/src/adapters/drizzle/join-column-utils.ts +39 -0
- package/src/adapters/drizzle/migrate-drizzle.test.ts +226 -0
- package/src/adapters/drizzle/shared.ts +22 -0
- package/src/adapters/drizzle/test-utils.ts +56 -0
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +789 -0
- package/src/adapters/kysely/kysely-adapter.ts +196 -0
- package/src/adapters/kysely/kysely-query-builder.test.ts +1344 -0
- package/src/adapters/kysely/kysely-query-builder.ts +611 -0
- package/src/adapters/kysely/kysely-query-compiler.ts +124 -0
- package/src/adapters/kysely/kysely-query.ts +254 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +916 -0
- package/src/adapters/kysely/kysely-uow-compiler.ts +271 -0
- package/src/adapters/kysely/kysely-uow-executor.ts +149 -0
- package/src/adapters/kysely/kysely-uow-joins.test.ts +811 -0
- package/src/adapters/kysely/migration/execute-mysql.test.ts +1173 -0
- package/src/adapters/kysely/migration/execute-postgres.test.ts +2657 -0
- package/src/adapters/kysely/migration/execute.ts +382 -0
- package/src/adapters/kysely/migration/kysely-migrator.test.ts +197 -0
- package/src/fragment.test.ts +287 -0
- package/src/fragment.ts +198 -0
- package/src/migration-engine/auto-from-schema.test.ts +118 -58
- package/src/migration-engine/auto-from-schema.ts +103 -32
- package/src/migration-engine/create.test.ts +34 -46
- package/src/migration-engine/create.ts +41 -26
- package/src/migration-engine/shared.ts +26 -6
- package/src/mod.ts +197 -1
- package/src/query/condition-builder.test.ts +379 -0
- package/src/query/condition-builder.ts +294 -0
- package/src/query/cursor.test.ts +296 -0
- package/src/query/cursor.ts +147 -0
- package/src/query/orm/orm.ts +92 -0
- package/src/query/query-type.test.ts +429 -0
- package/src/query/query.ts +200 -0
- package/src/query/result-transform.test.ts +795 -0
- package/src/query/result-transform.ts +247 -0
- package/src/query/unit-of-work-types.test.ts +192 -0
- package/src/query/unit-of-work.test.ts +947 -0
- package/src/query/unit-of-work.ts +1199 -0
- package/src/schema/create.test.ts +653 -110
- package/src/schema/create.ts +708 -337
- package/src/schema/serialize.test.ts +559 -0
- package/src/schema/serialize.ts +359 -0
- package/src/schema-generator/schema-generator.ts +12 -0
- package/src/shared/config.ts +0 -8
- package/src/util/import-generator.ts +28 -0
- package/src/util/parse.ts +16 -0
- package/src/util/types.ts +4 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +11 -1
- package/vitest.config.ts +3 -0
- /package/dist/{cuid.js → id.js} +0 -0
- /package/src/{cuid.ts → id.ts} +0 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { drizzle } from "drizzle-orm/pglite";
|
|
2
|
+
import { DrizzleAdapter } from "./drizzle-adapter";
|
|
3
|
+
import { beforeAll, describe, expect, expectTypeOf, it } from "vitest";
|
|
4
|
+
import { column, idColumn, referenceColumn, schema } from "../../schema/create";
|
|
5
|
+
import type { DBType } from "./shared";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { encodeCursor } from "../../query/cursor";
|
|
8
|
+
import type { DrizzleCompiledQuery } from "./drizzle-uow-compiler";
|
|
9
|
+
import { writeAndLoadSchema } from "./test-utils";
|
|
10
|
+
|
|
11
|
+
// Import drizzle-kit for migrations
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const { generateDrizzleJson, generateMigration } =
|
|
14
|
+
require("drizzle-kit/api") as typeof import("drizzle-kit/api");
|
|
15
|
+
|
|
16
|
+
describe("DrizzleAdapter PGLite", () => {
|
|
17
|
+
const testSchema = schema((s) => {
|
|
18
|
+
return s
|
|
19
|
+
.addTable("users", (t) => {
|
|
20
|
+
return t
|
|
21
|
+
.addColumn("id", idColumn())
|
|
22
|
+
.addColumn("name", column("string"))
|
|
23
|
+
.addColumn("age", column("integer").nullable())
|
|
24
|
+
.createIndex("name_idx", ["name"]);
|
|
25
|
+
})
|
|
26
|
+
.addTable("emails", (t) => {
|
|
27
|
+
return t
|
|
28
|
+
.addColumn("id", idColumn())
|
|
29
|
+
.addColumn("user_id", referenceColumn())
|
|
30
|
+
.addColumn("email", column("string"))
|
|
31
|
+
.addColumn("is_primary", column("bool").defaultTo(false))
|
|
32
|
+
.createIndex("unique_email", ["email"], { unique: true })
|
|
33
|
+
.createIndex("user_emails", ["user_id"]);
|
|
34
|
+
})
|
|
35
|
+
.addTable("posts", (t) => {
|
|
36
|
+
return t
|
|
37
|
+
.addColumn("id", idColumn())
|
|
38
|
+
.addColumn("user_id", referenceColumn())
|
|
39
|
+
.addColumn("title", column("string"))
|
|
40
|
+
.addColumn("content", column("string"))
|
|
41
|
+
.createIndex("posts_user_idx", ["user_id"]);
|
|
42
|
+
})
|
|
43
|
+
.addTable("comments", (t) => {
|
|
44
|
+
return t
|
|
45
|
+
.addColumn("id", idColumn())
|
|
46
|
+
.addColumn("post_id", referenceColumn())
|
|
47
|
+
.addColumn("user_id", referenceColumn())
|
|
48
|
+
.addColumn("text", column("string"))
|
|
49
|
+
.createIndex("comments_post_idx", ["post_id"])
|
|
50
|
+
.createIndex("comments_user_idx", ["user_id"]);
|
|
51
|
+
})
|
|
52
|
+
.addReference("user", {
|
|
53
|
+
type: "one",
|
|
54
|
+
from: { table: "emails", column: "user_id" },
|
|
55
|
+
to: { table: "users", column: "id" },
|
|
56
|
+
})
|
|
57
|
+
.addReference("author", {
|
|
58
|
+
type: "one",
|
|
59
|
+
from: { table: "posts", column: "user_id" },
|
|
60
|
+
to: { table: "users", column: "id" },
|
|
61
|
+
})
|
|
62
|
+
.addReference("post", {
|
|
63
|
+
type: "one",
|
|
64
|
+
from: { table: "comments", column: "post_id" },
|
|
65
|
+
to: { table: "posts", column: "id" },
|
|
66
|
+
})
|
|
67
|
+
.addReference("commenter", {
|
|
68
|
+
type: "one",
|
|
69
|
+
from: { table: "comments", column: "user_id" },
|
|
70
|
+
to: { table: "users", column: "id" },
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let adapter: DrizzleAdapter;
|
|
75
|
+
let db: DBType;
|
|
76
|
+
|
|
77
|
+
beforeAll(async () => {
|
|
78
|
+
// Write schema to file and dynamically import it
|
|
79
|
+
const { schemaModule, cleanup } = await writeAndLoadSchema(
|
|
80
|
+
"drizzle-adapter-pglite",
|
|
81
|
+
testSchema,
|
|
82
|
+
"postgresql",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Create Drizzle instance with PGLite (in-memory Postgres)
|
|
86
|
+
db = drizzle({
|
|
87
|
+
schema: schemaModule,
|
|
88
|
+
}) as unknown as DBType;
|
|
89
|
+
|
|
90
|
+
// Generate and run migrations
|
|
91
|
+
const migrationStatements = await generateMigration(
|
|
92
|
+
generateDrizzleJson({}), // Empty schema (starting state)
|
|
93
|
+
generateDrizzleJson(schemaModule), // Target schema
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Execute migration SQL
|
|
97
|
+
for (const statement of migrationStatements) {
|
|
98
|
+
await db.execute(statement);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
adapter = new DrizzleAdapter({
|
|
102
|
+
db,
|
|
103
|
+
provider: "postgresql",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return async () => {
|
|
107
|
+
await cleanup();
|
|
108
|
+
};
|
|
109
|
+
}, 12000);
|
|
110
|
+
|
|
111
|
+
it("should execute Unit of Work with version checking", async () => {
|
|
112
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
113
|
+
|
|
114
|
+
// Create initial user using UOW
|
|
115
|
+
const createUow = queryEngine.createUnitOfWork("create-user").create("users", {
|
|
116
|
+
name: "Alice",
|
|
117
|
+
age: 25,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expectTypeOf(createUow.find).parameter(0).toEqualTypeOf<keyof typeof testSchema.tables>();
|
|
121
|
+
|
|
122
|
+
const { success: createSuccess } = await createUow.executeMutations();
|
|
123
|
+
expect(createSuccess).toBe(true);
|
|
124
|
+
|
|
125
|
+
// Fetch the created user to get its ID
|
|
126
|
+
const [[initialUser]] = await queryEngine
|
|
127
|
+
.createUnitOfWork("get-created-user")
|
|
128
|
+
.find("users")
|
|
129
|
+
.executeRetrieve();
|
|
130
|
+
|
|
131
|
+
expect(initialUser).toBeDefined();
|
|
132
|
+
expect(initialUser.name).toBe("Alice");
|
|
133
|
+
expect(initialUser.id.version).toBe(0);
|
|
134
|
+
|
|
135
|
+
const initialUserId = initialUser.id;
|
|
136
|
+
|
|
137
|
+
// Build a UOW to update the user with optimistic locking
|
|
138
|
+
const uow = queryEngine
|
|
139
|
+
.createUnitOfWork("update-user-age")
|
|
140
|
+
// Retrieval phase: find the user
|
|
141
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)));
|
|
142
|
+
|
|
143
|
+
// Execute retrieval and transition to mutation phase
|
|
144
|
+
const [users] = await uow.executeRetrieve();
|
|
145
|
+
|
|
146
|
+
// Mutation phase: update with version check
|
|
147
|
+
uow.update("users", initialUserId, (b) => b.set({ age: 26 }).check());
|
|
148
|
+
|
|
149
|
+
// Execute mutations
|
|
150
|
+
const { success } = await uow.executeMutations();
|
|
151
|
+
|
|
152
|
+
// Should succeed
|
|
153
|
+
expect(success).toBe(true);
|
|
154
|
+
expect(users).toHaveLength(1);
|
|
155
|
+
|
|
156
|
+
// Verify the user was updated
|
|
157
|
+
const [[updatedUser]] = await queryEngine
|
|
158
|
+
.createUnitOfWork("get-updated-user")
|
|
159
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)))
|
|
160
|
+
.executeRetrieve();
|
|
161
|
+
|
|
162
|
+
expect(updatedUser).toMatchObject({
|
|
163
|
+
id: expect.objectContaining({
|
|
164
|
+
externalId: initialUserId.externalId,
|
|
165
|
+
version: 1, // Version incremented
|
|
166
|
+
}),
|
|
167
|
+
name: "Alice",
|
|
168
|
+
age: 26,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Try to update again with stale version (should fail)
|
|
172
|
+
const uow2 = queryEngine.createUnitOfWork("update-user-stale");
|
|
173
|
+
|
|
174
|
+
// Use the old version (0) which is now stale
|
|
175
|
+
uow2.update("users", initialUserId, (b) => b.set({ age: 27 }).check());
|
|
176
|
+
|
|
177
|
+
const { success: success2 } = await uow2.executeMutations();
|
|
178
|
+
|
|
179
|
+
// Should fail due to version conflict
|
|
180
|
+
expect(success2).toBe(false);
|
|
181
|
+
|
|
182
|
+
// Verify the user was NOT updated
|
|
183
|
+
const [[unchangedUser]] = await queryEngine
|
|
184
|
+
.createUnitOfWork("verify-unchanged")
|
|
185
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", initialUserId)))
|
|
186
|
+
.executeRetrieve();
|
|
187
|
+
|
|
188
|
+
expect(unchangedUser).toMatchObject({
|
|
189
|
+
id: expect.objectContaining({
|
|
190
|
+
version: 1, // Still version 1
|
|
191
|
+
}),
|
|
192
|
+
age: 26, // Still 26, not 27
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should support count operations", async () => {
|
|
197
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
198
|
+
|
|
199
|
+
// Create some users
|
|
200
|
+
await queryEngine
|
|
201
|
+
.createUnitOfWork("create-users")
|
|
202
|
+
.create("users", { name: "User1", age: 20 })
|
|
203
|
+
.create("users", { name: "User2", age: 30 })
|
|
204
|
+
.create("users", { name: "User3", age: 40 })
|
|
205
|
+
.executeMutations();
|
|
206
|
+
|
|
207
|
+
// Count all users
|
|
208
|
+
const [totalCount] = await queryEngine
|
|
209
|
+
.createUnitOfWork("count-all")
|
|
210
|
+
.find("users", (b) => b.whereIndex("primary").selectCount())
|
|
211
|
+
.executeRetrieve();
|
|
212
|
+
|
|
213
|
+
// Tests are not isolated, so we can't use expect(totalCount).toBe(3)
|
|
214
|
+
expect(totalCount).toBeGreaterThanOrEqual(3);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("should support cursor-based pagination", async () => {
|
|
218
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
219
|
+
|
|
220
|
+
const createUow = queryEngine
|
|
221
|
+
.createUnitOfWork("create-users")
|
|
222
|
+
.create("users", { name: "Page User A", age: 20 })
|
|
223
|
+
.create("users", { name: "Page User B", age: 30 })
|
|
224
|
+
.create("users", { name: "Page User C", age: 40 })
|
|
225
|
+
.create("users", { name: "Page User D", age: 50 })
|
|
226
|
+
.create("users", { name: "Page User E", age: 60 });
|
|
227
|
+
|
|
228
|
+
await createUow.executeMutations();
|
|
229
|
+
|
|
230
|
+
// Fetch first page ordered by name
|
|
231
|
+
const [firstPage] = await queryEngine
|
|
232
|
+
.createUnitOfWork("first-page")
|
|
233
|
+
.find("users", (b) => b.whereIndex("name_idx").orderByIndex("name_idx", "asc").pageSize(2))
|
|
234
|
+
.executeRetrieve();
|
|
235
|
+
|
|
236
|
+
// Verify first page contains the first 2 users alphabetically
|
|
237
|
+
expect(firstPage.map((u) => u.name)).toEqual(["Alice", "Page User A"]);
|
|
238
|
+
|
|
239
|
+
// Create cursor from last item of first page
|
|
240
|
+
const lastItem = firstPage[firstPage.length - 1]!;
|
|
241
|
+
const cursor = encodeCursor({
|
|
242
|
+
indexValues: { name: lastItem.name },
|
|
243
|
+
direction: "forward",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Fetch next page using cursor
|
|
247
|
+
const [secondPage] = await queryEngine
|
|
248
|
+
.createUnitOfWork("second-page")
|
|
249
|
+
.find("users", (b) =>
|
|
250
|
+
b.whereIndex("name_idx").orderByIndex("name_idx", "asc").after(cursor).pageSize(2),
|
|
251
|
+
)
|
|
252
|
+
.executeRetrieve();
|
|
253
|
+
|
|
254
|
+
// Verify page 2 continues alphabetically
|
|
255
|
+
expect(secondPage.map((u) => u.name)).toEqual(["Page User B", "Page User C"]);
|
|
256
|
+
|
|
257
|
+
// Ensure no overlap between pages
|
|
258
|
+
const firstPageNames = new Set(firstPage.map((u) => u.name));
|
|
259
|
+
for (const user of secondPage) {
|
|
260
|
+
expect(firstPageNames.has(user.name)).toBe(false);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should support joins", async () => {
|
|
265
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
266
|
+
const queries: DrizzleCompiledQuery[] = [];
|
|
267
|
+
|
|
268
|
+
const createUow = queryEngine
|
|
269
|
+
.createUnitOfWork("create-users")
|
|
270
|
+
.create("users", { name: "Email User", age: 20 });
|
|
271
|
+
|
|
272
|
+
await createUow.executeMutations();
|
|
273
|
+
|
|
274
|
+
// Get an existing user to create an email for
|
|
275
|
+
const [[existingUser]] = await queryEngine
|
|
276
|
+
.createUnitOfWork("get-existing-user")
|
|
277
|
+
.find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Email User")))
|
|
278
|
+
.executeRetrieve();
|
|
279
|
+
|
|
280
|
+
// Create an email for testing joins
|
|
281
|
+
const createEmailUow = queryEngine.createUnitOfWork("create-test-email").create("emails", {
|
|
282
|
+
user_id: existingUser.id,
|
|
283
|
+
email: "test@example.com",
|
|
284
|
+
is_primary: true,
|
|
285
|
+
});
|
|
286
|
+
await createEmailUow.executeMutations();
|
|
287
|
+
|
|
288
|
+
// Test join query
|
|
289
|
+
const uow = queryEngine
|
|
290
|
+
.createUnitOfWork("test-joins", { onQuery: (query) => queries.push(query) })
|
|
291
|
+
.find("emails", (b) =>
|
|
292
|
+
b
|
|
293
|
+
.whereIndex("user_emails")
|
|
294
|
+
.join((jb) => jb.user((builder) => builder.select(["name", "id", "age"]))),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const [[email]] = await uow.executeRetrieve();
|
|
298
|
+
|
|
299
|
+
const [query] = queries;
|
|
300
|
+
expect(query.sql).toMatchInlineSnapshot(
|
|
301
|
+
`"select "emails"."id", "emails"."user_id", "emails"."email", "emails"."is_primary", "emails"."_internalId", "emails"."_version", "emails_user"."data" as "user" from "emails" "emails" left join lateral (select json_build_array("emails_user"."name", "emails_user"."id", "emails_user"."age", "emails_user"."_internalId", "emails_user"."_version") as "data" from (select * from "users" "emails_user" where "emails_user"."_internalId" = "emails"."user_id" limit $1) "emails_user") "emails_user" on true"`,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(email).toMatchObject({
|
|
305
|
+
id: expect.objectContaining({
|
|
306
|
+
externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
|
|
307
|
+
internalId: expect.any(Number),
|
|
308
|
+
}),
|
|
309
|
+
user_id: expect.objectContaining({
|
|
310
|
+
internalId: expect.any(Number),
|
|
311
|
+
}),
|
|
312
|
+
email: "test@example.com",
|
|
313
|
+
is_primary: true,
|
|
314
|
+
user: {
|
|
315
|
+
id: expect.objectContaining({
|
|
316
|
+
externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
|
|
317
|
+
internalId: expect.any(Number),
|
|
318
|
+
}),
|
|
319
|
+
name: existingUser.name,
|
|
320
|
+
age: existingUser.age,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should support complex nested joins (comments -> post -> author)", async () => {
|
|
326
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
327
|
+
const queries: DrizzleCompiledQuery[] = [];
|
|
328
|
+
|
|
329
|
+
// Create a user (author)
|
|
330
|
+
const createAuthorUow = queryEngine
|
|
331
|
+
.createUnitOfWork("create-author")
|
|
332
|
+
.create("users", { name: "Blog Author", age: 30 });
|
|
333
|
+
await createAuthorUow.executeMutations();
|
|
334
|
+
|
|
335
|
+
// Get the author
|
|
336
|
+
const [[author]] = await queryEngine
|
|
337
|
+
.createUnitOfWork("get-author")
|
|
338
|
+
.find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Blog Author")))
|
|
339
|
+
.executeRetrieve();
|
|
340
|
+
|
|
341
|
+
// Create a post by the author
|
|
342
|
+
const createPostUow = queryEngine.createUnitOfWork("create-post").create("posts", {
|
|
343
|
+
user_id: author.id,
|
|
344
|
+
title: "My First Post",
|
|
345
|
+
content: "This is the content of my first post",
|
|
346
|
+
});
|
|
347
|
+
await createPostUow.executeMutations();
|
|
348
|
+
|
|
349
|
+
// Get the post
|
|
350
|
+
const [[post]] = await queryEngine.createUnitOfWork("get-post").find("posts").executeRetrieve();
|
|
351
|
+
|
|
352
|
+
// Create a commenter
|
|
353
|
+
const createCommenterUow = queryEngine
|
|
354
|
+
.createUnitOfWork("create-commenter")
|
|
355
|
+
.create("users", { name: "Commenter User", age: 25 });
|
|
356
|
+
await createCommenterUow.executeMutations();
|
|
357
|
+
|
|
358
|
+
// Get the commenter
|
|
359
|
+
const [[commenter]] = await queryEngine
|
|
360
|
+
.createUnitOfWork("get-commenter")
|
|
361
|
+
.find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Commenter User")))
|
|
362
|
+
.executeRetrieve();
|
|
363
|
+
|
|
364
|
+
// Create a comment on the post
|
|
365
|
+
const createCommentUow = queryEngine.createUnitOfWork("create-comment").create("comments", {
|
|
366
|
+
post_id: post.id,
|
|
367
|
+
user_id: commenter.id,
|
|
368
|
+
text: "Great post!",
|
|
369
|
+
});
|
|
370
|
+
await createCommentUow.executeMutations();
|
|
371
|
+
|
|
372
|
+
// Now perform a complex nested join: comments -> post -> author, and comments -> commenter
|
|
373
|
+
const uow = queryEngine
|
|
374
|
+
.createUnitOfWork("test-complex-joins", { onQuery: (query) => queries.push(query) })
|
|
375
|
+
.find("comments", (b) =>
|
|
376
|
+
b.whereIndex("primary").join((jb) =>
|
|
377
|
+
jb
|
|
378
|
+
.post((postBuilder) =>
|
|
379
|
+
postBuilder
|
|
380
|
+
.select(["id", "title", "content"])
|
|
381
|
+
.orderByIndex("primary", "desc")
|
|
382
|
+
.pageSize(1)
|
|
383
|
+
.join((jb2) =>
|
|
384
|
+
// Nested join to the post's author
|
|
385
|
+
jb2.author((authorBuilder) =>
|
|
386
|
+
authorBuilder.select(["id", "name", "age"]).orderByIndex("name_idx", "asc"),
|
|
387
|
+
),
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
.commenter((commenterBuilder) => commenterBuilder.select(["id", "name"])),
|
|
391
|
+
),
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const [[comment]] = await uow.executeRetrieve();
|
|
395
|
+
|
|
396
|
+
// Verify the result structure with nested joins
|
|
397
|
+
expect(comment).toMatchObject({
|
|
398
|
+
id: expect.objectContaining({
|
|
399
|
+
externalId: expect.stringMatching(/^[a-z0-9]{20,}$/),
|
|
400
|
+
internalId: expect.any(Number),
|
|
401
|
+
}),
|
|
402
|
+
text: "Great post!",
|
|
403
|
+
// Post join (first level)
|
|
404
|
+
post: {
|
|
405
|
+
id: expect.objectContaining({
|
|
406
|
+
externalId: post.id.externalId,
|
|
407
|
+
}),
|
|
408
|
+
title: "My First Post",
|
|
409
|
+
content: "This is the content of my first post",
|
|
410
|
+
// Nested author join (second level) - now decoded!
|
|
411
|
+
author: {
|
|
412
|
+
id: expect.objectContaining({
|
|
413
|
+
externalId: author.id.externalId,
|
|
414
|
+
}),
|
|
415
|
+
name: "Blog Author",
|
|
416
|
+
age: 30,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
// Commenter join (first level)
|
|
420
|
+
commenter: {
|
|
421
|
+
id: expect.objectContaining({
|
|
422
|
+
externalId: commenter.id.externalId,
|
|
423
|
+
}),
|
|
424
|
+
name: "Commenter User",
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const [query] = queries;
|
|
429
|
+
expect(query.sql).toMatchInlineSnapshot(
|
|
430
|
+
`"select "comments"."id", "comments"."post_id", "comments"."user_id", "comments"."text", "comments"."_internalId", "comments"."_version", "comments_post"."data" as "post", "comments_commenter"."data" as "commenter" from "comments" "comments" left join lateral (select json_build_array("comments_post"."id", "comments_post"."title", "comments_post"."content", "comments_post"."_internalId", "comments_post"."_version", "comments_post_author"."data") as "data" from (select * from "posts" "comments_post" where "comments_post"."_internalId" = "comments"."post_id" order by "comments_post"."id" desc limit $1) "comments_post" left join lateral (select json_build_array("comments_post_author"."id", "comments_post_author"."name", "comments_post_author"."age", "comments_post_author"."_internalId", "comments_post_author"."_version") as "data" from (select * from "users" "comments_post_author" where "comments_post_author"."_internalId" = "comments_post"."user_id" order by "comments_post_author"."name" asc limit $2) "comments_post_author") "comments_post_author" on true) "comments_post" on true left join lateral (select json_build_array("comments_commenter"."id", "comments_commenter"."name", "comments_commenter"."_internalId", "comments_commenter"."_version") as "data" from (select * from "users" "comments_commenter" where "comments_commenter"."_internalId" = "comments"."user_id" limit $3) "comments_commenter") "comments_commenter" on true"`,
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { column, idColumn, schema } from "../../schema/create";
|
|
3
|
+
import { DrizzleAdapter } from "./drizzle-adapter";
|
|
4
|
+
|
|
5
|
+
describe("DrizzleAdapter", () => {
|
|
6
|
+
const testSchema = schema((s) => {
|
|
7
|
+
return s.addTable("users", (t) => {
|
|
8
|
+
return t.addColumn("id", idColumn()).addColumn("name", column("string"));
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should generate schema with settings table for postgresql", () => {
|
|
13
|
+
const adapter = new DrizzleAdapter({
|
|
14
|
+
db: {},
|
|
15
|
+
provider: "postgresql",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const generator = adapter.createSchemaGenerator(testSchema, "test");
|
|
19
|
+
const result = generator.generateSchema({ path: "schema.ts" });
|
|
20
|
+
|
|
21
|
+
expect(result.path).toBe("schema.ts");
|
|
22
|
+
expect(result.schema).toMatchInlineSnapshot(`
|
|
23
|
+
"import { pgTable, varchar, text, bigserial, integer, uniqueIndex } from "drizzle-orm/pg-core"
|
|
24
|
+
import { createId } from "@fragno-dev/db/id"
|
|
25
|
+
|
|
26
|
+
export const users = pgTable("users", {
|
|
27
|
+
id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
|
|
28
|
+
name: text("name").notNull(),
|
|
29
|
+
_internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
|
|
30
|
+
_version: integer("_version").notNull().default(0)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export const fragno_db_settings = pgTable("fragno_db_settings", {
|
|
34
|
+
id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
|
|
35
|
+
key: text("key").notNull(),
|
|
36
|
+
value: text("value").notNull().default("1"),
|
|
37
|
+
_internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
|
|
38
|
+
_version: integer("_version").notNull().default(0)
|
|
39
|
+
}, (table) => [
|
|
40
|
+
uniqueIndex("unique_key").on(table.key)
|
|
41
|
+
])"
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should generate schema with settings table for sqlite", () => {
|
|
46
|
+
const adapter = new DrizzleAdapter({
|
|
47
|
+
db: {},
|
|
48
|
+
provider: "sqlite",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const generator = adapter.createSchemaGenerator(testSchema, "test");
|
|
52
|
+
const result = generator.generateSchema({ path: "schema.ts" });
|
|
53
|
+
|
|
54
|
+
expect(result.path).toBe("schema.ts");
|
|
55
|
+
expect(result.schema).toMatchInlineSnapshot(`
|
|
56
|
+
"import { sqliteTable, text, integer, uniqueIndex } from "drizzle-orm/sqlite-core"
|
|
57
|
+
import { createId } from "@fragno-dev/db/id"
|
|
58
|
+
|
|
59
|
+
export const users = sqliteTable("users", {
|
|
60
|
+
id: text("id").notNull().$defaultFn(() => createId()),
|
|
61
|
+
name: text("name").notNull(),
|
|
62
|
+
_internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
|
|
63
|
+
_version: integer("_version").notNull().default(0)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export const fragno_db_settings = sqliteTable("fragno_db_settings", {
|
|
67
|
+
id: text("id").notNull().$defaultFn(() => createId()),
|
|
68
|
+
key: text("key").notNull(),
|
|
69
|
+
value: text("value").notNull().default("1"),
|
|
70
|
+
_internalId: integer("_internalId").primaryKey().autoincrement().notNull(),
|
|
71
|
+
_version: integer("_version").notNull().default(0)
|
|
72
|
+
}, (table) => [
|
|
73
|
+
uniqueIndex("unique_key").on(table.key)
|
|
74
|
+
])"
|
|
75
|
+
`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should use default path if not provided", () => {
|
|
79
|
+
const adapter = new DrizzleAdapter({
|
|
80
|
+
db: {},
|
|
81
|
+
provider: "postgresql",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const generator = adapter.createSchemaGenerator(testSchema, "myapp");
|
|
85
|
+
const result = generator.generateSchema();
|
|
86
|
+
|
|
87
|
+
expect(result.path).toBe("drizzle-schema-myapp.ts");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should preserve original schema tables", () => {
|
|
91
|
+
const adapter = new DrizzleAdapter({
|
|
92
|
+
db: {},
|
|
93
|
+
provider: "postgresql",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const generator = adapter.createSchemaGenerator(testSchema, "test");
|
|
97
|
+
const result = generator.generateSchema();
|
|
98
|
+
|
|
99
|
+
// Original table should still be there
|
|
100
|
+
expect(result.schema).toMatchInlineSnapshot(`
|
|
101
|
+
"import { pgTable, varchar, text, bigserial, integer, uniqueIndex } from "drizzle-orm/pg-core"
|
|
102
|
+
import { createId } from "@fragno-dev/db/id"
|
|
103
|
+
|
|
104
|
+
export const users = pgTable("users", {
|
|
105
|
+
id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
|
|
106
|
+
name: text("name").notNull(),
|
|
107
|
+
_internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
|
|
108
|
+
_version: integer("_version").notNull().default(0)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
export const fragno_db_settings = pgTable("fragno_db_settings", {
|
|
112
|
+
id: varchar("id", { length: 30 }).notNull().$defaultFn(() => createId()),
|
|
113
|
+
key: text("key").notNull(),
|
|
114
|
+
value: text("value").notNull().default("1"),
|
|
115
|
+
_internalId: bigserial("_internalId", { mode: "number" }).primaryKey().notNull(),
|
|
116
|
+
_version: integer("_version").notNull().default(0)
|
|
117
|
+
}, (table) => [
|
|
118
|
+
uniqueIndex("unique_key").on(table.key)
|
|
119
|
+
])"
|
|
120
|
+
`);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from "../adapters";
|
|
2
|
+
import {
|
|
3
|
+
column,
|
|
4
|
+
idColumn,
|
|
5
|
+
schema,
|
|
6
|
+
SchemaBuilder,
|
|
7
|
+
type AnySchema,
|
|
8
|
+
type FragnoId,
|
|
9
|
+
} from "../../schema/create";
|
|
10
|
+
import type { AbstractQuery } from "../../query/query";
|
|
11
|
+
import type { SchemaGenerator } from "../../schema-generator/schema-generator";
|
|
12
|
+
import { generateSchema } from "./generate";
|
|
13
|
+
import { fromDrizzle, type DrizzleUOWConfig } from "./drizzle-query";
|
|
14
|
+
import { createId } from "../../id";
|
|
15
|
+
|
|
16
|
+
const SETTINGS_TABLE_NAME = "fragno_db_settings" as const;
|
|
17
|
+
|
|
18
|
+
export interface DrizzleConfig {
|
|
19
|
+
db: unknown;
|
|
20
|
+
provider: "sqlite" | "mysql" | "postgresql";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class DrizzleAdapter implements DatabaseAdapter<DrizzleUOWConfig> {
|
|
24
|
+
#drizzleConfig: DrizzleConfig;
|
|
25
|
+
|
|
26
|
+
constructor(config: DrizzleConfig) {
|
|
27
|
+
this.#drizzleConfig = config;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#createFullSchema<T extends AnySchema>(schema: T) {
|
|
31
|
+
return new SchemaBuilder()
|
|
32
|
+
.mergeWithExistingSchema(schema)
|
|
33
|
+
.mergeWithExistingSchema(createSettingsSchema(schema.version))
|
|
34
|
+
.build();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getSchemaVersion(namespace: string): Promise<string | undefined> {
|
|
38
|
+
const queryEngine = this.createQueryEngine(createSettingsSchema(0), namespace);
|
|
39
|
+
const manager = createSettingsManager(queryEngine, namespace);
|
|
40
|
+
const randomId = createId();
|
|
41
|
+
|
|
42
|
+
const result = await manager.createKeyWithDefault(randomId);
|
|
43
|
+
if (result) {
|
|
44
|
+
await manager.delete(result.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result?.value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
createQueryEngine<TSchema extends AnySchema>(
|
|
51
|
+
schema: TSchema,
|
|
52
|
+
_namespace: string,
|
|
53
|
+
): AbstractQuery<TSchema, DrizzleUOWConfig> {
|
|
54
|
+
return fromDrizzle(schema, this.#drizzleConfig);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
createSchemaGenerator(schema: AnySchema, namespace: string): SchemaGenerator {
|
|
58
|
+
return {
|
|
59
|
+
generateSchema: (options) => {
|
|
60
|
+
const path = options?.path ?? `drizzle-schema-${namespace}.ts`;
|
|
61
|
+
|
|
62
|
+
const schemaWithSettingsTable = this.#createFullSchema(schema);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
schema: generateSchema(schemaWithSettingsTable, this.#drizzleConfig.provider),
|
|
66
|
+
path,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createSettingsSchema(version: number) {
|
|
74
|
+
return schema((s) => {
|
|
75
|
+
return s.addTable(SETTINGS_TABLE_NAME, (t) => {
|
|
76
|
+
return t
|
|
77
|
+
.addColumn("id", idColumn())
|
|
78
|
+
.addColumn("key", column("string"))
|
|
79
|
+
.addColumn("value", column("string").defaultTo(String(version)))
|
|
80
|
+
.createIndex("unique_key", ["key"], { unique: true });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createSettingsManager(
|
|
86
|
+
queryEngine: AbstractQuery<ReturnType<typeof createSettingsSchema>, DrizzleUOWConfig>,
|
|
87
|
+
namespace: string,
|
|
88
|
+
) {
|
|
89
|
+
return {
|
|
90
|
+
async createKeyWithDefault(key: string) {
|
|
91
|
+
const writeUow = queryEngine
|
|
92
|
+
.createUnitOfWork("createKeyWithDefault")
|
|
93
|
+
.create(SETTINGS_TABLE_NAME, {
|
|
94
|
+
key: `${namespace}.${key}`,
|
|
95
|
+
});
|
|
96
|
+
const { success } = await writeUow.executeMutations();
|
|
97
|
+
if (!success) {
|
|
98
|
+
throw new Error("Failed to create key with default");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return this.get(key);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async get(key: string): Promise<{ id: FragnoId; key: string; value: string } | undefined> {
|
|
105
|
+
const uow = queryEngine
|
|
106
|
+
.createUnitOfWork()
|
|
107
|
+
.find(SETTINGS_TABLE_NAME, (b) =>
|
|
108
|
+
b.whereIndex("unique_key", (eb) => eb("key", "=", `${namespace}.${key}`)),
|
|
109
|
+
);
|
|
110
|
+
const [[result]] = await uow.executeRetrieve();
|
|
111
|
+
return result; // FIXME: result should be maybe undefined
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async delete(id: FragnoId) {
|
|
115
|
+
await queryEngine.delete(SETTINGS_TABLE_NAME, id);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|