@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,947 @@
|
|
|
1
|
+
import { describe, it, expect, assert, expectTypeOf } from "vitest";
|
|
2
|
+
import { column, schema, idColumn } from "../schema/create";
|
|
3
|
+
import {
|
|
4
|
+
UnitOfWork,
|
|
5
|
+
type UOWCompiler,
|
|
6
|
+
type UOWDecoder,
|
|
7
|
+
createUnitOfWork,
|
|
8
|
+
type InferIdColumnName,
|
|
9
|
+
type IndexColumns,
|
|
10
|
+
} from "./unit-of-work";
|
|
11
|
+
import { createIndexedBuilder } from "./condition-builder";
|
|
12
|
+
import type { AnySchema } from "../schema/create";
|
|
13
|
+
import type { AbstractQuery } from "./query";
|
|
14
|
+
|
|
15
|
+
// Mock compiler and executor for testing
|
|
16
|
+
function createMockCompiler<TSchema extends AnySchema = AnySchema>(): UOWCompiler<
|
|
17
|
+
TSchema,
|
|
18
|
+
unknown
|
|
19
|
+
> {
|
|
20
|
+
return {
|
|
21
|
+
compileRetrievalOperation: () => null,
|
|
22
|
+
compileMutationOperation: () => null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createMockExecutor() {
|
|
27
|
+
return {
|
|
28
|
+
executeRetrievalPhase: async () => [],
|
|
29
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createMockDecoder<TSchema extends AnySchema = AnySchema>(): UOWDecoder<TSchema> {
|
|
34
|
+
return (rawResults, operations) => {
|
|
35
|
+
if (rawResults.length !== operations.length) {
|
|
36
|
+
throw new Error("rawResults and operations must have the same length");
|
|
37
|
+
}
|
|
38
|
+
return rawResults;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("FindBuilder", () => {
|
|
43
|
+
it("should support primary index", () => {
|
|
44
|
+
const testSchema = schema((s) =>
|
|
45
|
+
s.addTable("users", (t) =>
|
|
46
|
+
t
|
|
47
|
+
.addColumn("id", idColumn())
|
|
48
|
+
.addColumn("email", "string")
|
|
49
|
+
.addColumn("name", "string")
|
|
50
|
+
.addColumn("age", "integer")
|
|
51
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
52
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const uow = new UnitOfWork(
|
|
57
|
+
testSchema,
|
|
58
|
+
createMockCompiler(),
|
|
59
|
+
createMockExecutor(),
|
|
60
|
+
createMockDecoder(),
|
|
61
|
+
);
|
|
62
|
+
uow.find("users", (b) => b.whereIndex("primary"));
|
|
63
|
+
|
|
64
|
+
const ops = uow.getRetrievalOperations();
|
|
65
|
+
expect(ops).toHaveLength(1);
|
|
66
|
+
expect(ops[0].indexName).toBe("_primary");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should support custom indexes", () => {
|
|
70
|
+
const testSchema = schema((s) =>
|
|
71
|
+
s.addTable("users", (t) =>
|
|
72
|
+
t
|
|
73
|
+
.addColumn("id", idColumn())
|
|
74
|
+
.addColumn("email", "string")
|
|
75
|
+
.addColumn("name", "string")
|
|
76
|
+
.createIndex("idx_email", ["email"], { unique: true }),
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const uow = new UnitOfWork(
|
|
81
|
+
testSchema,
|
|
82
|
+
createMockCompiler(),
|
|
83
|
+
createMockExecutor(),
|
|
84
|
+
createMockDecoder(),
|
|
85
|
+
);
|
|
86
|
+
uow.find("users", (b) =>
|
|
87
|
+
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const ops = uow.getRetrievalOperations();
|
|
91
|
+
expect(ops).toHaveLength(1);
|
|
92
|
+
expect(ops[0].indexName).toBe("idx_email");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should support cursor-based pagination", () => {
|
|
96
|
+
const testSchema = schema((s) =>
|
|
97
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const uow = new UnitOfWork(
|
|
101
|
+
testSchema,
|
|
102
|
+
createMockCompiler(),
|
|
103
|
+
createMockExecutor(),
|
|
104
|
+
createMockDecoder(),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
|
|
108
|
+
uow.find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
|
|
109
|
+
|
|
110
|
+
const ops = uow.getRetrievalOperations();
|
|
111
|
+
expect(ops).toHaveLength(1);
|
|
112
|
+
const op = ops[0];
|
|
113
|
+
assert(op.type === "find");
|
|
114
|
+
expect(op.options.after).toBe(cursor);
|
|
115
|
+
expect(op.options.pageSize).toBe(10);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should support backward cursor pagination", () => {
|
|
119
|
+
const testSchema = schema((s) =>
|
|
120
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const uow = new UnitOfWork(
|
|
124
|
+
testSchema,
|
|
125
|
+
createMockCompiler(),
|
|
126
|
+
createMockExecutor(),
|
|
127
|
+
createMockDecoder(),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
|
|
131
|
+
uow.find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
|
|
132
|
+
|
|
133
|
+
const ops = uow.getRetrievalOperations();
|
|
134
|
+
expect(ops).toHaveLength(1);
|
|
135
|
+
const op = ops[0];
|
|
136
|
+
assert(op.type === "find");
|
|
137
|
+
expect(op.options.before).toBe(cursor);
|
|
138
|
+
expect(op.options.pageSize).toBe(5);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should throw if index doesn't exist", () => {
|
|
142
|
+
const testSchema = schema((s) =>
|
|
143
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const uow = new UnitOfWork(
|
|
147
|
+
testSchema,
|
|
148
|
+
createMockCompiler(),
|
|
149
|
+
createMockExecutor(),
|
|
150
|
+
createMockDecoder(),
|
|
151
|
+
);
|
|
152
|
+
expect(() => {
|
|
153
|
+
uow.find("users", (b) => b.whereIndex("nonexistent" as "primary"));
|
|
154
|
+
}).toThrow('Index "nonexistent" not found on table "users"');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should throw if finalized without index", () => {
|
|
158
|
+
const testSchema = schema((s) =>
|
|
159
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const uow = new UnitOfWork(
|
|
163
|
+
testSchema,
|
|
164
|
+
createMockCompiler(),
|
|
165
|
+
createMockExecutor(),
|
|
166
|
+
createMockDecoder(),
|
|
167
|
+
);
|
|
168
|
+
expect(() => {
|
|
169
|
+
uow.find("users", (b) => b);
|
|
170
|
+
}).toThrow(
|
|
171
|
+
'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should support count operations", () => {
|
|
176
|
+
const testSchema = schema((s) =>
|
|
177
|
+
s.addTable("users", (t) =>
|
|
178
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("age", "integer"),
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const uow = new UnitOfWork(
|
|
183
|
+
testSchema,
|
|
184
|
+
createMockCompiler(),
|
|
185
|
+
createMockExecutor(),
|
|
186
|
+
createMockDecoder(),
|
|
187
|
+
);
|
|
188
|
+
uow.find("users", (b) => b.whereIndex("primary").selectCount());
|
|
189
|
+
|
|
190
|
+
const ops = uow.getRetrievalOperations();
|
|
191
|
+
expect(ops).toHaveLength(1);
|
|
192
|
+
expect(ops[0]?.type).toBe("count");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should throw when using both select and selectCount", () => {
|
|
196
|
+
const testSchema = schema((s) =>
|
|
197
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const uow = new UnitOfWork(
|
|
201
|
+
testSchema,
|
|
202
|
+
createMockCompiler(),
|
|
203
|
+
createMockExecutor(),
|
|
204
|
+
createMockDecoder(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// select() then selectCount()
|
|
208
|
+
expect(() => {
|
|
209
|
+
uow.find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
|
|
210
|
+
}).toThrow(/cannot call selectCount/i);
|
|
211
|
+
|
|
212
|
+
// selectCount() then select()
|
|
213
|
+
const uow2 = new UnitOfWork(
|
|
214
|
+
testSchema,
|
|
215
|
+
createMockCompiler(),
|
|
216
|
+
createMockExecutor(),
|
|
217
|
+
createMockDecoder(),
|
|
218
|
+
);
|
|
219
|
+
expect(() => {
|
|
220
|
+
uow2.find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
|
|
221
|
+
}).toThrow(/cannot call select/i);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should support orderByIndex", () => {
|
|
225
|
+
const testSchema = schema((s) =>
|
|
226
|
+
s.addTable("users", (t) =>
|
|
227
|
+
t
|
|
228
|
+
.addColumn("id", idColumn())
|
|
229
|
+
.addColumn("name", "string")
|
|
230
|
+
.addColumn("createdAt", "integer")
|
|
231
|
+
.createIndex("idx_created", ["createdAt"]),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const uow = new UnitOfWork(
|
|
236
|
+
testSchema,
|
|
237
|
+
createMockCompiler(),
|
|
238
|
+
createMockExecutor(),
|
|
239
|
+
createMockDecoder(),
|
|
240
|
+
);
|
|
241
|
+
uow.find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
|
|
242
|
+
|
|
243
|
+
const ops = uow.getRetrievalOperations();
|
|
244
|
+
expect(ops).toHaveLength(1);
|
|
245
|
+
const op = ops[0];
|
|
246
|
+
if (op?.type === "find") {
|
|
247
|
+
expect(op.options.orderByIndex).toEqual({
|
|
248
|
+
indexName: "idx_created",
|
|
249
|
+
direction: "desc",
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
throw new Error("Expected find operation");
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should support join operations", () => {
|
|
257
|
+
const testSchema = schema((s) =>
|
|
258
|
+
s
|
|
259
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
260
|
+
.addTable("posts", (t) =>
|
|
261
|
+
t
|
|
262
|
+
.addColumn("id", idColumn())
|
|
263
|
+
.addColumn("userId", column("string"))
|
|
264
|
+
.addColumn("title", "string")
|
|
265
|
+
.createIndex("idx_user", ["userId"]),
|
|
266
|
+
)
|
|
267
|
+
.addReference("user", {
|
|
268
|
+
type: "one",
|
|
269
|
+
from: { table: "posts", column: "userId" },
|
|
270
|
+
to: { table: "users", column: "id" },
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const uow = new UnitOfWork(
|
|
275
|
+
testSchema,
|
|
276
|
+
createMockCompiler(),
|
|
277
|
+
createMockExecutor(),
|
|
278
|
+
createMockDecoder(),
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
uow.find("posts", (b) =>
|
|
282
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const ops = uow.getRetrievalOperations();
|
|
286
|
+
expect(ops).toHaveLength(1);
|
|
287
|
+
const op = ops[0];
|
|
288
|
+
assert(op.type === "find");
|
|
289
|
+
expect(op.options.joins).toBeDefined();
|
|
290
|
+
expect(op.options.joins).toHaveLength(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should support join operations without builder function", () => {
|
|
294
|
+
const testSchema = schema((s) =>
|
|
295
|
+
s
|
|
296
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
297
|
+
.addTable("posts", (t) =>
|
|
298
|
+
t
|
|
299
|
+
.addColumn("id", idColumn())
|
|
300
|
+
.addColumn("userId", column("string"))
|
|
301
|
+
.addColumn("title", "string")
|
|
302
|
+
.createIndex("idx_user", ["userId"]),
|
|
303
|
+
)
|
|
304
|
+
.addReference("user", {
|
|
305
|
+
type: "one",
|
|
306
|
+
from: { table: "posts", column: "userId" },
|
|
307
|
+
to: { table: "users", column: "id" },
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const uow = new UnitOfWork(
|
|
312
|
+
testSchema,
|
|
313
|
+
createMockCompiler(),
|
|
314
|
+
createMockExecutor(),
|
|
315
|
+
createMockDecoder(),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Join without builder function should use default options
|
|
319
|
+
uow.find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
|
|
320
|
+
|
|
321
|
+
const ops = uow.getRetrievalOperations();
|
|
322
|
+
expect(ops).toHaveLength(1);
|
|
323
|
+
const op = ops[0];
|
|
324
|
+
assert(op.type === "find");
|
|
325
|
+
expect(op.options.joins).toBeDefined();
|
|
326
|
+
expect(op.options.joins).toHaveLength(1);
|
|
327
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
328
|
+
assert(joinOptions !== false);
|
|
329
|
+
expect(joinOptions.select).toBe(true); // Should default to selecting all columns
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("should support join with whereIndex", () => {
|
|
333
|
+
const testSchema = schema((s) =>
|
|
334
|
+
s
|
|
335
|
+
.addTable("users", (t) =>
|
|
336
|
+
t
|
|
337
|
+
.addColumn("id", idColumn())
|
|
338
|
+
.addColumn("name", "string")
|
|
339
|
+
.createIndex("idx_name", ["name"]),
|
|
340
|
+
)
|
|
341
|
+
.addTable("posts", (t) =>
|
|
342
|
+
t
|
|
343
|
+
.addColumn("id", idColumn())
|
|
344
|
+
.addColumn("userId", column("string"))
|
|
345
|
+
.addColumn("title", "string")
|
|
346
|
+
.createIndex("idx_user", ["userId"]),
|
|
347
|
+
)
|
|
348
|
+
.addReference("user", {
|
|
349
|
+
type: "one",
|
|
350
|
+
from: { table: "posts", column: "userId" },
|
|
351
|
+
to: { table: "users", column: "id" },
|
|
352
|
+
}),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
const uow = new UnitOfWork(
|
|
356
|
+
testSchema,
|
|
357
|
+
createMockCompiler(),
|
|
358
|
+
createMockExecutor(),
|
|
359
|
+
createMockDecoder(),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
uow.find("posts", (b) =>
|
|
363
|
+
b
|
|
364
|
+
.whereIndex("primary")
|
|
365
|
+
.join((jb) =>
|
|
366
|
+
jb["user"]((builder) =>
|
|
367
|
+
builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
|
|
368
|
+
),
|
|
369
|
+
),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const ops = uow.getRetrievalOperations();
|
|
373
|
+
expect(ops).toHaveLength(1);
|
|
374
|
+
const op = ops[0];
|
|
375
|
+
assert(op.type === "find");
|
|
376
|
+
expect(op.options.joins).toBeDefined();
|
|
377
|
+
expect(op.options.joins).toHaveLength(1);
|
|
378
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
379
|
+
assert(joinOptions !== false);
|
|
380
|
+
expect(joinOptions.where).toBeDefined();
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should support join with orderByIndex", () => {
|
|
384
|
+
const testSchema = schema((s) =>
|
|
385
|
+
s
|
|
386
|
+
.addTable("users", (t) =>
|
|
387
|
+
t
|
|
388
|
+
.addColumn("id", idColumn())
|
|
389
|
+
.addColumn("name", "string")
|
|
390
|
+
.addColumn("createdAt", "integer")
|
|
391
|
+
.createIndex("idx_created", ["createdAt"]),
|
|
392
|
+
)
|
|
393
|
+
.addTable("posts", (t) =>
|
|
394
|
+
t
|
|
395
|
+
.addColumn("id", idColumn())
|
|
396
|
+
.addColumn("userId", column("string"))
|
|
397
|
+
.addColumn("title", "string")
|
|
398
|
+
.createIndex("idx_user", ["userId"]),
|
|
399
|
+
)
|
|
400
|
+
.addReference("user", {
|
|
401
|
+
type: "one",
|
|
402
|
+
from: { table: "posts", column: "userId" },
|
|
403
|
+
to: { table: "users", column: "id" },
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const uow = new UnitOfWork(
|
|
408
|
+
testSchema,
|
|
409
|
+
createMockCompiler(),
|
|
410
|
+
createMockExecutor(),
|
|
411
|
+
createMockDecoder(),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
uow.find("posts", (b) =>
|
|
415
|
+
b
|
|
416
|
+
.whereIndex("primary")
|
|
417
|
+
.join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const ops = uow.getRetrievalOperations();
|
|
421
|
+
expect(ops).toHaveLength(1);
|
|
422
|
+
const op = ops[0];
|
|
423
|
+
assert(op.type === "find");
|
|
424
|
+
expect(op.options.joins).toBeDefined();
|
|
425
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
426
|
+
assert(joinOptions !== false);
|
|
427
|
+
expect(joinOptions.orderBy).toBeDefined();
|
|
428
|
+
expect(joinOptions.orderBy).toHaveLength(1);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("should support join with pageSize", () => {
|
|
432
|
+
const testSchema = schema((s) =>
|
|
433
|
+
s
|
|
434
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
435
|
+
.addTable("posts", (t) =>
|
|
436
|
+
t
|
|
437
|
+
.addColumn("id", idColumn())
|
|
438
|
+
.addColumn("userId", column("string"))
|
|
439
|
+
.addColumn("title", "string")
|
|
440
|
+
.createIndex("idx_user", ["userId"]),
|
|
441
|
+
)
|
|
442
|
+
.addReference("user", {
|
|
443
|
+
type: "one",
|
|
444
|
+
from: { table: "posts", column: "userId" },
|
|
445
|
+
to: { table: "users", column: "id" },
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const uow = new UnitOfWork(
|
|
450
|
+
testSchema,
|
|
451
|
+
createMockCompiler(),
|
|
452
|
+
createMockExecutor(),
|
|
453
|
+
createMockDecoder(),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
uow.find("posts", (b) =>
|
|
457
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const ops = uow.getRetrievalOperations();
|
|
461
|
+
expect(ops).toHaveLength(1);
|
|
462
|
+
const op = ops[0];
|
|
463
|
+
assert(op.type === "find");
|
|
464
|
+
const joinOptions = op.options.joins![0]!.options;
|
|
465
|
+
assert(joinOptions !== false);
|
|
466
|
+
expect(joinOptions.limit).toBe(5);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("should support nested joins", () => {
|
|
470
|
+
const testSchema = schema((s) =>
|
|
471
|
+
s
|
|
472
|
+
.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string"))
|
|
473
|
+
.addTable("posts", (t) =>
|
|
474
|
+
t
|
|
475
|
+
.addColumn("id", idColumn())
|
|
476
|
+
.addColumn("userId", column("string"))
|
|
477
|
+
.addColumn("authorId", column("string"))
|
|
478
|
+
.addColumn("title", "string")
|
|
479
|
+
.createIndex("idx_user", ["userId"])
|
|
480
|
+
.createIndex("idx_author", ["authorId"]),
|
|
481
|
+
)
|
|
482
|
+
.addTable("comments", (t) =>
|
|
483
|
+
t
|
|
484
|
+
.addColumn("id", idColumn())
|
|
485
|
+
.addColumn("postId", column("string"))
|
|
486
|
+
.addColumn("text", "string")
|
|
487
|
+
.createIndex("idx_post", ["postId"]),
|
|
488
|
+
)
|
|
489
|
+
.addReference("user", {
|
|
490
|
+
type: "one",
|
|
491
|
+
from: { table: "posts", column: "userId" },
|
|
492
|
+
to: { table: "users", column: "id" },
|
|
493
|
+
})
|
|
494
|
+
.addReference("post", {
|
|
495
|
+
type: "one",
|
|
496
|
+
from: { table: "comments", column: "postId" },
|
|
497
|
+
to: { table: "posts", column: "id" },
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const uow = new UnitOfWork(
|
|
502
|
+
testSchema,
|
|
503
|
+
createMockCompiler(),
|
|
504
|
+
createMockExecutor(),
|
|
505
|
+
createMockDecoder(),
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
uow.find("comments", (b) =>
|
|
509
|
+
b
|
|
510
|
+
.whereIndex("primary")
|
|
511
|
+
.join((jb) =>
|
|
512
|
+
jb["post"]((postBuilder) =>
|
|
513
|
+
postBuilder
|
|
514
|
+
.select(["title"])
|
|
515
|
+
.join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
|
|
516
|
+
),
|
|
517
|
+
),
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
const ops = uow.getRetrievalOperations();
|
|
521
|
+
expect(ops).toHaveLength(1);
|
|
522
|
+
const op = ops[0];
|
|
523
|
+
assert(op.type === "find");
|
|
524
|
+
expect(op.options.joins).toBeDefined();
|
|
525
|
+
expect(op.options.joins).toHaveLength(1);
|
|
526
|
+
|
|
527
|
+
const postJoin = op.options.joins![0]!;
|
|
528
|
+
assert(postJoin.options !== false);
|
|
529
|
+
expect(postJoin.options.join).toBeDefined();
|
|
530
|
+
expect(postJoin.options.join).toHaveLength(1);
|
|
531
|
+
|
|
532
|
+
const userJoin = postJoin.options.join![0]!;
|
|
533
|
+
assert(userJoin.options !== false);
|
|
534
|
+
expect(userJoin.relation.name).toBe("user");
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
describe("IndexedConditionBuilder", () => {
|
|
539
|
+
const testSchema = schema((s) =>
|
|
540
|
+
s.addTable("users", (t) =>
|
|
541
|
+
t
|
|
542
|
+
.addColumn("id", idColumn())
|
|
543
|
+
.addColumn("email", column("string"))
|
|
544
|
+
.addColumn("name", column("string"))
|
|
545
|
+
.addColumn("age", column("integer").nullable())
|
|
546
|
+
.addColumn("bio", column("string").nullable()) // Not indexed
|
|
547
|
+
.createIndex("_primary", ["id"], { unique: true })
|
|
548
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
549
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
550
|
+
),
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
const usersTable = testSchema.tables.users;
|
|
554
|
+
|
|
555
|
+
it("should enforce indexed columns at runtime", () => {
|
|
556
|
+
// Collect all indexed column names from all indexes
|
|
557
|
+
const indexedColumns = new Set<string>();
|
|
558
|
+
for (const index of Object.values(usersTable.indexes)) {
|
|
559
|
+
for (const col of index.columns) {
|
|
560
|
+
indexedColumns.add(col.ormName);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
565
|
+
|
|
566
|
+
// Should work with indexed columns
|
|
567
|
+
expect(() => builder("id", "=", "123")).not.toThrow();
|
|
568
|
+
expect(() => builder("email", "=", "test@example.com")).not.toThrow();
|
|
569
|
+
expect(() => builder("name", "=", "Alice")).not.toThrow();
|
|
570
|
+
expect(() => builder("age", ">", 18)).not.toThrow();
|
|
571
|
+
|
|
572
|
+
// Should throw when using non-indexed column
|
|
573
|
+
expect(() => builder("bio" as "email", "=", "Some bio")).toThrow('Column "bio" is not indexed');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("should work with complex conditions", () => {
|
|
577
|
+
const indexedColumns = new Set(["id", "email", "name", "age"]);
|
|
578
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
579
|
+
|
|
580
|
+
// Complex AND condition with indexed columns
|
|
581
|
+
const condition = builder.and(
|
|
582
|
+
builder("name", "=", "Alice"),
|
|
583
|
+
builder("age", ">", 18),
|
|
584
|
+
builder("email", "contains", "example"),
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
expect(condition).toEqual({
|
|
588
|
+
type: "and",
|
|
589
|
+
items: [
|
|
590
|
+
{
|
|
591
|
+
type: "compare",
|
|
592
|
+
a: usersTable.columns.name,
|
|
593
|
+
operator: "=",
|
|
594
|
+
b: "Alice",
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
type: "compare",
|
|
598
|
+
a: usersTable.columns.age,
|
|
599
|
+
operator: ">",
|
|
600
|
+
b: 18,
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
type: "compare",
|
|
604
|
+
a: usersTable.columns.email,
|
|
605
|
+
operator: "contains",
|
|
606
|
+
b: "example",
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it("should provide helpful error message listing available columns", () => {
|
|
613
|
+
const indexedColumns = new Set(["id", "email"]);
|
|
614
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
615
|
+
|
|
616
|
+
expect(() => builder("name" as "email", "=", "Alice")).toThrow(
|
|
617
|
+
"Only indexed columns can be used in Unit of Work queries. Available indexed columns: id, email",
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("should work with all builder helper methods", () => {
|
|
622
|
+
const indexedColumns = new Set(["id", "email", "age"]);
|
|
623
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
624
|
+
|
|
625
|
+
// isNull
|
|
626
|
+
expect(() => builder.isNull("age")).not.toThrow();
|
|
627
|
+
expect(() => builder.isNull("bio" as "age")).toThrow('Column "bio" is not indexed');
|
|
628
|
+
|
|
629
|
+
// isNotNull
|
|
630
|
+
expect(() => builder.isNotNull("email")).not.toThrow();
|
|
631
|
+
expect(() => builder.isNotNull("bio" as "email")).toThrow('Column "bio" is not indexed');
|
|
632
|
+
|
|
633
|
+
// not
|
|
634
|
+
const notCondition = builder.not(builder("id", "=", "123"));
|
|
635
|
+
expect(notCondition).toEqual({
|
|
636
|
+
type: "not",
|
|
637
|
+
item: {
|
|
638
|
+
type: "compare",
|
|
639
|
+
a: usersTable.columns.id,
|
|
640
|
+
operator: "=",
|
|
641
|
+
b: "123",
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// or
|
|
646
|
+
const orCondition = builder.or(builder("email", "contains", "gmail"), builder("age", ">", 30));
|
|
647
|
+
expect(orCondition).toEqual({
|
|
648
|
+
type: "or",
|
|
649
|
+
items: [
|
|
650
|
+
{
|
|
651
|
+
type: "compare",
|
|
652
|
+
a: usersTable.columns.email,
|
|
653
|
+
operator: "contains",
|
|
654
|
+
b: "gmail",
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
type: "compare",
|
|
658
|
+
a: usersTable.columns.age,
|
|
659
|
+
operator: ">",
|
|
660
|
+
b: 30,
|
|
661
|
+
},
|
|
662
|
+
],
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it("should enforce index restrictions in nested conditions", () => {
|
|
667
|
+
const indexedColumns = new Set(["id", "email"]);
|
|
668
|
+
const builder = createIndexedBuilder(usersTable.columns, indexedColumns);
|
|
669
|
+
|
|
670
|
+
// This should throw because "name" is not indexed, even though it's nested
|
|
671
|
+
expect(() => {
|
|
672
|
+
builder.and(
|
|
673
|
+
builder("email", "=", "test@example.com"),
|
|
674
|
+
builder("name" as "email", "=", "Alice"),
|
|
675
|
+
);
|
|
676
|
+
}).toThrow('Column "name" is not indexed');
|
|
677
|
+
|
|
678
|
+
// This should throw because "bio" is not indexed, even in OR
|
|
679
|
+
expect(() => {
|
|
680
|
+
builder.or(builder("id", "=", "123"), builder("bio" as "id", "=", "Some bio"));
|
|
681
|
+
}).toThrow('Column "bio" is not indexed');
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
describe("type safety", () => {
|
|
685
|
+
it("should restrict to only indexed columns at type level", () => {
|
|
686
|
+
// This schema has "bio" column that is NOT indexed
|
|
687
|
+
const typeTestSchema = schema((s) =>
|
|
688
|
+
s.addTable("users", (t) =>
|
|
689
|
+
t
|
|
690
|
+
.addColumn("id", idColumn())
|
|
691
|
+
.addColumn("email", column("string"))
|
|
692
|
+
.addColumn("name", column("string"))
|
|
693
|
+
.addColumn("age", column("integer").nullable())
|
|
694
|
+
.addColumn("bio", column("string").nullable()) // Not indexed!
|
|
695
|
+
.createIndex("idx_email", ["email"], { unique: true })
|
|
696
|
+
.createIndex("idx_name_age", ["name", "age"]),
|
|
697
|
+
),
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
type _IdColumnName = InferIdColumnName<typeof typeTestSchema.tables.users>;
|
|
701
|
+
expectTypeOf<_IdColumnName>().toEqualTypeOf<"id">();
|
|
702
|
+
type _IndexColumnNames = IndexColumns<
|
|
703
|
+
typeof typeTestSchema.tables.users.indexes.idx_name_age
|
|
704
|
+
>;
|
|
705
|
+
expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
|
|
706
|
+
|
|
707
|
+
const uow = createUnitOfWork(
|
|
708
|
+
typeTestSchema,
|
|
709
|
+
createMockCompiler<typeof typeTestSchema>(),
|
|
710
|
+
createMockExecutor(),
|
|
711
|
+
createMockDecoder<typeof typeTestSchema>(),
|
|
712
|
+
);
|
|
713
|
+
expectTypeOf(uow.schema).toEqualTypeOf(typeTestSchema);
|
|
714
|
+
expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
|
|
715
|
+
type _Query = AbstractQuery<typeof typeTestSchema>;
|
|
716
|
+
expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
|
|
717
|
+
|
|
718
|
+
expectTypeOf(uow.find).parameter(0).toEqualTypeOf<"users">();
|
|
719
|
+
|
|
720
|
+
uow.find("users", (b) =>
|
|
721
|
+
b.whereIndex("primary", (eb) => {
|
|
722
|
+
type _EbFirstParameter = Parameters<typeof eb>[0];
|
|
723
|
+
expectTypeOf<_EbFirstParameter>().toEqualTypeOf<"id">();
|
|
724
|
+
return eb("id", "=", "123");
|
|
725
|
+
}),
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
uow.find("users", (b) =>
|
|
729
|
+
b.whereIndex("idx_email", (eb) => {
|
|
730
|
+
expectTypeOf(eb).parameter(0).toEqualTypeOf<"email">();
|
|
731
|
+
return eb("email", "=", "123");
|
|
732
|
+
}),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
uow.find("users", (b) =>
|
|
736
|
+
b.whereIndex("idx_name_age", (eb) => {
|
|
737
|
+
expectTypeOf(eb).parameter(0).toEqualTypeOf<"name" | "age">();
|
|
738
|
+
return eb("name", "=", "123");
|
|
739
|
+
}),
|
|
740
|
+
);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
describe("UpdateBuilder with string ID", () => {
|
|
746
|
+
const testSchema = schema((s) =>
|
|
747
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
it("should allow update with string ID", async () => {
|
|
751
|
+
const uow = new UnitOfWork(
|
|
752
|
+
testSchema,
|
|
753
|
+
createMockCompiler(),
|
|
754
|
+
createMockExecutor(),
|
|
755
|
+
createMockDecoder(),
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
// Should work with string ID
|
|
759
|
+
uow.update("users", "user-123", (b) => b.set({ name: "New Name" }));
|
|
760
|
+
|
|
761
|
+
const ops = uow.getMutationOperations();
|
|
762
|
+
expect(ops).toHaveLength(1);
|
|
763
|
+
expect(ops).toMatchObject([
|
|
764
|
+
{
|
|
765
|
+
type: "update",
|
|
766
|
+
id: "user-123",
|
|
767
|
+
checkVersion: false,
|
|
768
|
+
},
|
|
769
|
+
]);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it("should throw when using check() with string ID", async () => {
|
|
773
|
+
const uow = new UnitOfWork(
|
|
774
|
+
testSchema,
|
|
775
|
+
createMockCompiler(),
|
|
776
|
+
createMockExecutor(),
|
|
777
|
+
createMockDecoder(),
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
// Should throw because check() is not allowed with string ID
|
|
781
|
+
expect(() => {
|
|
782
|
+
uow.update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
|
|
783
|
+
}).toThrow(
|
|
784
|
+
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
785
|
+
);
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
describe("DeleteBuilder with string ID", () => {
|
|
790
|
+
const testSchema = schema((s) =>
|
|
791
|
+
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
it("should allow delete with string ID", async () => {
|
|
795
|
+
const uow = new UnitOfWork(
|
|
796
|
+
testSchema,
|
|
797
|
+
createMockCompiler(),
|
|
798
|
+
createMockExecutor(),
|
|
799
|
+
createMockDecoder(),
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// Should work with string ID
|
|
803
|
+
uow.delete("users", "user-123");
|
|
804
|
+
|
|
805
|
+
const ops = uow.getMutationOperations();
|
|
806
|
+
expect(ops).toMatchObject([
|
|
807
|
+
{
|
|
808
|
+
type: "delete",
|
|
809
|
+
id: "user-123",
|
|
810
|
+
checkVersion: false,
|
|
811
|
+
},
|
|
812
|
+
]);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it("should throw when using check() with string ID", async () => {
|
|
816
|
+
const uow = new UnitOfWork(
|
|
817
|
+
testSchema,
|
|
818
|
+
createMockCompiler(),
|
|
819
|
+
createMockExecutor(),
|
|
820
|
+
createMockDecoder(),
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
// Should throw because check() is not allowed with string ID
|
|
824
|
+
expect(() => {
|
|
825
|
+
uow.delete("users", "user-123", (b) => b.check());
|
|
826
|
+
}).toThrow(
|
|
827
|
+
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
828
|
+
);
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
describe("getCreatedIds", () => {
|
|
833
|
+
const testSchema = schema((s) =>
|
|
834
|
+
s.addTable("users", (t) =>
|
|
835
|
+
t.addColumn("id", idColumn()).addColumn("email", "string").addColumn("name", "string"),
|
|
836
|
+
),
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
it("should return created IDs after executeMutations with internal IDs", async () => {
|
|
840
|
+
const executor = {
|
|
841
|
+
executeRetrievalPhase: async () => [],
|
|
842
|
+
executeMutationPhase: async () => ({
|
|
843
|
+
success: true,
|
|
844
|
+
createdInternalIds: [1n, 2n],
|
|
845
|
+
}),
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
|
|
849
|
+
|
|
850
|
+
uow.create("users", { email: "user1@example.com", name: "User 1" });
|
|
851
|
+
uow.create("users", { email: "user2@example.com", name: "User 2" });
|
|
852
|
+
|
|
853
|
+
await uow.executeMutations();
|
|
854
|
+
const createdIds = uow.getCreatedIds();
|
|
855
|
+
|
|
856
|
+
expect(createdIds).toHaveLength(2);
|
|
857
|
+
expect(createdIds[0].externalId).toBeDefined();
|
|
858
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
859
|
+
expect(createdIds[0].version).toBe(0);
|
|
860
|
+
expect(createdIds[1].externalId).toBeDefined();
|
|
861
|
+
expect(createdIds[1].internalId).toBe(2n);
|
|
862
|
+
expect(createdIds[1].version).toBe(0);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it("should return created IDs without internal IDs when not supported", async () => {
|
|
866
|
+
const executor = {
|
|
867
|
+
executeRetrievalPhase: async () => [],
|
|
868
|
+
executeMutationPhase: async () => ({
|
|
869
|
+
success: true,
|
|
870
|
+
createdInternalIds: [null, null],
|
|
871
|
+
}),
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
|
|
875
|
+
|
|
876
|
+
uow.create("users", { email: "user1@example.com", name: "User 1" });
|
|
877
|
+
uow.create("users", { email: "user2@example.com", name: "User 2" });
|
|
878
|
+
|
|
879
|
+
await uow.executeMutations();
|
|
880
|
+
const createdIds = uow.getCreatedIds();
|
|
881
|
+
|
|
882
|
+
expect(createdIds).toHaveLength(2);
|
|
883
|
+
expect(createdIds[0].externalId).toBeDefined();
|
|
884
|
+
expect(createdIds[0].internalId).toBeUndefined();
|
|
885
|
+
expect(createdIds[1].externalId).toBeDefined();
|
|
886
|
+
expect(createdIds[1].internalId).toBeUndefined();
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
it("should preserve user-provided external IDs", async () => {
|
|
890
|
+
const executor = {
|
|
891
|
+
executeRetrievalPhase: async () => [],
|
|
892
|
+
executeMutationPhase: async () => ({
|
|
893
|
+
success: true,
|
|
894
|
+
createdInternalIds: [1n],
|
|
895
|
+
}),
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
|
|
899
|
+
|
|
900
|
+
uow.create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
|
|
901
|
+
|
|
902
|
+
await uow.executeMutations();
|
|
903
|
+
const createdIds = uow.getCreatedIds();
|
|
904
|
+
|
|
905
|
+
expect(createdIds).toHaveLength(1);
|
|
906
|
+
expect(createdIds[0].externalId).toBe("my-custom-id");
|
|
907
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it("should only return IDs for create operations, not updates or deletes", async () => {
|
|
911
|
+
const executor = {
|
|
912
|
+
executeRetrievalPhase: async () => [],
|
|
913
|
+
executeMutationPhase: async () => ({
|
|
914
|
+
success: true,
|
|
915
|
+
createdInternalIds: [1n],
|
|
916
|
+
}),
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
const uow = new UnitOfWork(testSchema, createMockCompiler(), executor, createMockDecoder());
|
|
920
|
+
|
|
921
|
+
uow.create("users", { email: "user@example.com", name: "User" });
|
|
922
|
+
uow.update("users", "existing-id", (b) => b.set({ name: "Updated" }));
|
|
923
|
+
uow.delete("users", "other-id");
|
|
924
|
+
|
|
925
|
+
await uow.executeMutations();
|
|
926
|
+
const createdIds = uow.getCreatedIds();
|
|
927
|
+
|
|
928
|
+
// Only one create operation, so only one ID returned
|
|
929
|
+
expect(createdIds).toHaveLength(1);
|
|
930
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
it("should throw when called before executeMutations", () => {
|
|
934
|
+
const uow = new UnitOfWork(
|
|
935
|
+
testSchema,
|
|
936
|
+
createMockCompiler(),
|
|
937
|
+
createMockExecutor(),
|
|
938
|
+
createMockDecoder(),
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
uow.create("users", { email: "user@example.com", name: "User" });
|
|
942
|
+
|
|
943
|
+
expect(() => uow.getCreatedIds()).toThrow(
|
|
944
|
+
"getCreatedIds() can only be called after executeMutations()",
|
|
945
|
+
);
|
|
946
|
+
});
|
|
947
|
+
});
|