@fragno-dev/db 0.1.13 → 0.1.15
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 +179 -132
- package/CHANGELOG.md +30 -0
- package/dist/adapters/adapters.d.ts +27 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +5 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +7 -5
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +76 -44
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.d.ts +4 -1
- package/dist/adapters/drizzle/generate.d.ts.map +1 -1
- package/dist/adapters/drizzle/generate.js +11 -18
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/drizzle/shared.d.ts +14 -1
- package/dist/adapters/drizzle/shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +5 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +14 -3
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts +1 -0
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +28 -19
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-shared.js +16 -1
- package/dist/adapters/kysely/kysely-shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
- package/dist/adapters/kysely/migration/execute-base.js +1 -1
- package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
- package/dist/db-fragment-definition-builder.d.ts +152 -0
- package/dist/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/db-fragment-definition-builder.js +137 -0
- package/dist/db-fragment-definition-builder.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +19 -0
- package/dist/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.js +39 -0
- package/dist/fragments/internal-fragment.js.map +1 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +35 -15
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +8 -18
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +7 -34
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/packages/fragno/dist/api/bind-services.js +20 -0
- package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
- package/dist/packages/fragno/dist/api/error.js +48 -0
- package/dist/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
- package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
- package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/route.js +17 -0
- package/dist/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/packages/fragno/dist/internal/symbols.js +10 -0
- package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
- package/dist/query/cursor.d.ts +10 -2
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +11 -4
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/execute-unit-of-work.d.ts +123 -0
- package/dist/query/execute-unit-of-work.d.ts.map +1 -0
- package/dist/query/execute-unit-of-work.js +184 -0
- package/dist/query/execute-unit-of-work.js.map +1 -0
- package/dist/query/query.d.ts +3 -3
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/result-transform.js +4 -2
- package/dist/query/result-transform.js.map +1 -1
- package/dist/query/retry-policy.d.ts +88 -0
- package/dist/query/retry-policy.d.ts.map +1 -0
- package/dist/query/retry-policy.js +61 -0
- package/dist/query/retry-policy.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +171 -32
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +530 -133
- package/dist/query/unit-of-work.js.map +1 -1
- package/dist/schema/serialize.js +12 -7
- package/dist/schema/serialize.js.map +1 -1
- package/dist/with-database.d.ts +28 -0
- package/dist/with-database.d.ts.map +1 -0
- package/dist/with-database.js +34 -0
- package/dist/with-database.js.map +1 -0
- package/package.json +10 -3
- package/src/adapters/adapters.ts +30 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
- package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
- package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
- package/src/adapters/drizzle/drizzle-query.ts +25 -15
- package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
- package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
- package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
- package/src/adapters/drizzle/generate.test.ts +102 -269
- package/src/adapters/drizzle/generate.ts +12 -30
- package/src/adapters/drizzle/test-utils.ts +36 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
- package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
- package/src/adapters/kysely/kysely-adapter.ts +25 -2
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
- package/src/adapters/kysely/kysely-query.ts +57 -37
- package/src/adapters/kysely/kysely-shared.ts +34 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
- package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
- package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
- package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
- package/src/adapters/kysely/migration/execute-base.ts +1 -1
- package/src/db-fragment-definition-builder.test.ts +887 -0
- package/src/db-fragment-definition-builder.ts +506 -0
- package/src/db-fragment-instantiator.test.ts +467 -0
- package/src/db-fragment-integration.test.ts +408 -0
- package/src/fragments/internal-fragment.test.ts +160 -0
- package/src/fragments/internal-fragment.ts +85 -0
- package/src/migration-engine/generation-engine.test.ts +58 -15
- package/src/migration-engine/generation-engine.ts +78 -25
- package/src/mod.ts +35 -43
- package/src/query/cursor.test.ts +119 -0
- package/src/query/cursor.ts +17 -4
- package/src/query/execute-unit-of-work.test.ts +1310 -0
- package/src/query/execute-unit-of-work.ts +463 -0
- package/src/query/query.ts +4 -4
- package/src/query/result-transform.test.ts +129 -0
- package/src/query/result-transform.ts +4 -1
- package/src/query/retry-policy.test.ts +217 -0
- package/src/query/retry-policy.ts +141 -0
- package/src/query/unit-of-work-coordinator.test.ts +833 -0
- package/src/query/unit-of-work-types.test.ts +15 -2
- package/src/query/unit-of-work.test.ts +878 -200
- package/src/query/unit-of-work.ts +963 -321
- package/src/schema/serialize.ts +22 -11
- package/src/with-database.ts +140 -0
- package/tsdown.config.ts +1 -0
- package/dist/fragment.d.ts +0 -54
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -92
- package/dist/fragment.js.map +0 -1
- package/dist/shared/settings-schema.js +0 -36
- package/dist/shared/settings-schema.js.map +0 -1
- package/src/fragment.test.ts +0 -341
- package/src/fragment.ts +0 -198
- package/src/shared/settings-schema.ts +0 -61
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, assert, expectTypeOf } from "vitest";
|
|
2
|
-
import { column, schema, idColumn } from "../schema/create";
|
|
2
|
+
import { column, schema, idColumn, FragnoId } from "../schema/create";
|
|
3
3
|
import {
|
|
4
|
-
UnitOfWork,
|
|
5
4
|
type UOWCompiler,
|
|
6
5
|
type UOWDecoder,
|
|
7
6
|
createUnitOfWork,
|
|
@@ -9,14 +8,10 @@ import {
|
|
|
9
8
|
type IndexColumns,
|
|
10
9
|
} from "./unit-of-work";
|
|
11
10
|
import { createIndexedBuilder } from "./condition-builder";
|
|
12
|
-
import type { AnySchema } from "../schema/create";
|
|
13
11
|
import type { AbstractQuery } from "./query";
|
|
14
12
|
|
|
15
13
|
// Mock compiler and executor for testing
|
|
16
|
-
function createMockCompiler
|
|
17
|
-
TSchema,
|
|
18
|
-
unknown
|
|
19
|
-
> {
|
|
14
|
+
function createMockCompiler(): UOWCompiler<unknown> {
|
|
20
15
|
return {
|
|
21
16
|
compileRetrievalOperation: () => null,
|
|
22
17
|
compileMutationOperation: () => null,
|
|
@@ -30,7 +25,7 @@ function createMockExecutor() {
|
|
|
30
25
|
};
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
function createMockDecoder
|
|
28
|
+
function createMockDecoder(): UOWDecoder {
|
|
34
29
|
return (rawResults, operations) => {
|
|
35
30
|
if (rawResults.length !== operations.length) {
|
|
36
31
|
throw new Error("rawResults and operations must have the same length");
|
|
@@ -53,13 +48,8 @@ describe("FindBuilder", () => {
|
|
|
53
48
|
),
|
|
54
49
|
);
|
|
55
50
|
|
|
56
|
-
const uow =
|
|
57
|
-
|
|
58
|
-
createMockCompiler(),
|
|
59
|
-
createMockExecutor(),
|
|
60
|
-
createMockDecoder(),
|
|
61
|
-
);
|
|
62
|
-
uow.find("users", (b) => b.whereIndex("primary"));
|
|
51
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
52
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
63
53
|
|
|
64
54
|
const ops = uow.getRetrievalOperations();
|
|
65
55
|
expect(ops).toHaveLength(1);
|
|
@@ -77,15 +67,12 @@ describe("FindBuilder", () => {
|
|
|
77
67
|
),
|
|
78
68
|
);
|
|
79
69
|
|
|
80
|
-
const uow =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
uow.find("users", (b) =>
|
|
87
|
-
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
88
|
-
);
|
|
70
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
71
|
+
uow
|
|
72
|
+
.forSchema(testSchema)
|
|
73
|
+
.find("users", (b) =>
|
|
74
|
+
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
75
|
+
);
|
|
89
76
|
|
|
90
77
|
const ops = uow.getRetrievalOperations();
|
|
91
78
|
expect(ops).toHaveLength(1);
|
|
@@ -97,15 +84,12 @@ describe("FindBuilder", () => {
|
|
|
97
84
|
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
98
85
|
);
|
|
99
86
|
|
|
100
|
-
const uow =
|
|
101
|
-
testSchema,
|
|
102
|
-
createMockCompiler(),
|
|
103
|
-
createMockExecutor(),
|
|
104
|
-
createMockDecoder(),
|
|
105
|
-
);
|
|
87
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
106
88
|
|
|
107
89
|
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXIxMjMifSwiZGlyZWN0aW9uIjoiZm9yd2FyZCJ9";
|
|
108
|
-
uow
|
|
90
|
+
uow
|
|
91
|
+
.forSchema(testSchema)
|
|
92
|
+
.find("users", (b) => b.whereIndex("primary").after(cursor).pageSize(10));
|
|
109
93
|
|
|
110
94
|
const ops = uow.getRetrievalOperations();
|
|
111
95
|
expect(ops).toHaveLength(1);
|
|
@@ -120,15 +104,12 @@ describe("FindBuilder", () => {
|
|
|
120
104
|
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
121
105
|
);
|
|
122
106
|
|
|
123
|
-
const uow =
|
|
124
|
-
testSchema,
|
|
125
|
-
createMockCompiler(),
|
|
126
|
-
createMockExecutor(),
|
|
127
|
-
createMockDecoder(),
|
|
128
|
-
);
|
|
107
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
129
108
|
|
|
130
109
|
const cursor = "eyJpbmRleFZhbHVlcyI6eyJpZCI6InVzZXI0NTYifSwiZGlyZWN0aW9uIjoiYmFja3dhcmQifQ==";
|
|
131
|
-
uow
|
|
110
|
+
uow
|
|
111
|
+
.forSchema(testSchema)
|
|
112
|
+
.find("users", (b) => b.whereIndex("primary").before(cursor).pageSize(5));
|
|
132
113
|
|
|
133
114
|
const ops = uow.getRetrievalOperations();
|
|
134
115
|
expect(ops).toHaveLength(1);
|
|
@@ -143,14 +124,9 @@ describe("FindBuilder", () => {
|
|
|
143
124
|
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
144
125
|
);
|
|
145
126
|
|
|
146
|
-
const uow =
|
|
147
|
-
testSchema,
|
|
148
|
-
createMockCompiler(),
|
|
149
|
-
createMockExecutor(),
|
|
150
|
-
createMockDecoder(),
|
|
151
|
-
);
|
|
127
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
152
128
|
expect(() => {
|
|
153
|
-
uow.find("users", (b) => b.whereIndex("nonexistent" as "primary"));
|
|
129
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("nonexistent" as "primary"));
|
|
154
130
|
}).toThrow('Index "nonexistent" not found on table "users"');
|
|
155
131
|
});
|
|
156
132
|
|
|
@@ -159,14 +135,9 @@ describe("FindBuilder", () => {
|
|
|
159
135
|
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
160
136
|
);
|
|
161
137
|
|
|
162
|
-
const uow =
|
|
163
|
-
testSchema,
|
|
164
|
-
createMockCompiler(),
|
|
165
|
-
createMockExecutor(),
|
|
166
|
-
createMockDecoder(),
|
|
167
|
-
);
|
|
138
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
168
139
|
expect(() => {
|
|
169
|
-
uow.find("users", (b) => b);
|
|
140
|
+
uow.forSchema(testSchema).find("users", (b) => b);
|
|
170
141
|
}).toThrow(
|
|
171
142
|
'Must specify an index using .whereIndex() before finalizing find operation on table "users"',
|
|
172
143
|
);
|
|
@@ -179,13 +150,8 @@ describe("FindBuilder", () => {
|
|
|
179
150
|
),
|
|
180
151
|
);
|
|
181
152
|
|
|
182
|
-
const uow =
|
|
183
|
-
|
|
184
|
-
createMockCompiler(),
|
|
185
|
-
createMockExecutor(),
|
|
186
|
-
createMockDecoder(),
|
|
187
|
-
);
|
|
188
|
-
uow.find("users", (b) => b.whereIndex("primary").selectCount());
|
|
153
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
154
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary").selectCount());
|
|
189
155
|
|
|
190
156
|
const ops = uow.getRetrievalOperations();
|
|
191
157
|
expect(ops).toHaveLength(1);
|
|
@@ -197,27 +163,21 @@ describe("FindBuilder", () => {
|
|
|
197
163
|
s.addTable("users", (t) => t.addColumn("id", idColumn()).addColumn("name", "string")),
|
|
198
164
|
);
|
|
199
165
|
|
|
200
|
-
const uow =
|
|
201
|
-
testSchema,
|
|
202
|
-
createMockCompiler(),
|
|
203
|
-
createMockExecutor(),
|
|
204
|
-
createMockDecoder(),
|
|
205
|
-
);
|
|
166
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
206
167
|
|
|
207
168
|
// select() then selectCount()
|
|
208
169
|
expect(() => {
|
|
209
|
-
uow
|
|
170
|
+
uow
|
|
171
|
+
.forSchema(testSchema)
|
|
172
|
+
.find("users", (b) => b.whereIndex("primary").select(["name"]).selectCount());
|
|
210
173
|
}).toThrow(/cannot call selectCount/i);
|
|
211
174
|
|
|
212
175
|
// selectCount() then select()
|
|
213
|
-
const uow2 =
|
|
214
|
-
testSchema,
|
|
215
|
-
createMockCompiler(),
|
|
216
|
-
createMockExecutor(),
|
|
217
|
-
createMockDecoder(),
|
|
218
|
-
);
|
|
176
|
+
const uow2 = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
219
177
|
expect(() => {
|
|
220
|
-
uow2
|
|
178
|
+
uow2
|
|
179
|
+
.forSchema(testSchema)
|
|
180
|
+
.find("users", (b) => b.whereIndex("primary").selectCount().select(["name"]));
|
|
221
181
|
}).toThrow(/cannot call select/i);
|
|
222
182
|
});
|
|
223
183
|
|
|
@@ -232,13 +192,10 @@ describe("FindBuilder", () => {
|
|
|
232
192
|
),
|
|
233
193
|
);
|
|
234
194
|
|
|
235
|
-
const uow =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
createMockDecoder(),
|
|
240
|
-
);
|
|
241
|
-
uow.find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
|
|
195
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
196
|
+
uow
|
|
197
|
+
.forSchema(testSchema)
|
|
198
|
+
.find("users", (b) => b.whereIndex("primary").orderByIndex("idx_created", "desc"));
|
|
242
199
|
|
|
243
200
|
const ops = uow.getRetrievalOperations();
|
|
244
201
|
expect(ops).toHaveLength(1);
|
|
@@ -271,16 +228,13 @@ describe("FindBuilder", () => {
|
|
|
271
228
|
}),
|
|
272
229
|
);
|
|
273
230
|
|
|
274
|
-
const uow =
|
|
275
|
-
testSchema,
|
|
276
|
-
createMockCompiler(),
|
|
277
|
-
createMockExecutor(),
|
|
278
|
-
createMockDecoder(),
|
|
279
|
-
);
|
|
231
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
280
232
|
|
|
281
|
-
uow
|
|
282
|
-
|
|
283
|
-
|
|
233
|
+
uow
|
|
234
|
+
.forSchema(testSchema)
|
|
235
|
+
.find("posts", (b) =>
|
|
236
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.select(["name"]))),
|
|
237
|
+
);
|
|
284
238
|
|
|
285
239
|
const ops = uow.getRetrievalOperations();
|
|
286
240
|
expect(ops).toHaveLength(1);
|
|
@@ -308,15 +262,10 @@ describe("FindBuilder", () => {
|
|
|
308
262
|
}),
|
|
309
263
|
);
|
|
310
264
|
|
|
311
|
-
const uow =
|
|
312
|
-
testSchema,
|
|
313
|
-
createMockCompiler(),
|
|
314
|
-
createMockExecutor(),
|
|
315
|
-
createMockDecoder(),
|
|
316
|
-
);
|
|
265
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
317
266
|
|
|
318
267
|
// Join without builder function should use default options
|
|
319
|
-
uow.find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
|
|
268
|
+
uow.forSchema(testSchema).find("posts", (b) => b.whereIndex("primary").join((jb) => jb.user()));
|
|
320
269
|
|
|
321
270
|
const ops = uow.getRetrievalOperations();
|
|
322
271
|
expect(ops).toHaveLength(1);
|
|
@@ -352,22 +301,19 @@ describe("FindBuilder", () => {
|
|
|
352
301
|
}),
|
|
353
302
|
);
|
|
354
303
|
|
|
355
|
-
const uow =
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
jb["user"]((builder) =>
|
|
367
|
-
builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
|
|
304
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
305
|
+
|
|
306
|
+
uow
|
|
307
|
+
.forSchema(testSchema)
|
|
308
|
+
.find("posts", (b) =>
|
|
309
|
+
b
|
|
310
|
+
.whereIndex("primary")
|
|
311
|
+
.join((jb) =>
|
|
312
|
+
jb["user"]((builder) =>
|
|
313
|
+
builder.whereIndex("idx_name", (eb) => eb("name", "=", "Alice")).select(["name"]),
|
|
314
|
+
),
|
|
368
315
|
),
|
|
369
|
-
|
|
370
|
-
);
|
|
316
|
+
);
|
|
371
317
|
|
|
372
318
|
const ops = uow.getRetrievalOperations();
|
|
373
319
|
expect(ops).toHaveLength(1);
|
|
@@ -404,18 +350,15 @@ describe("FindBuilder", () => {
|
|
|
404
350
|
}),
|
|
405
351
|
);
|
|
406
352
|
|
|
407
|
-
const uow =
|
|
408
|
-
testSchema,
|
|
409
|
-
createMockCompiler(),
|
|
410
|
-
createMockExecutor(),
|
|
411
|
-
createMockDecoder(),
|
|
412
|
-
);
|
|
353
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
413
354
|
|
|
414
|
-
uow
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
355
|
+
uow
|
|
356
|
+
.forSchema(testSchema)
|
|
357
|
+
.find("posts", (b) =>
|
|
358
|
+
b
|
|
359
|
+
.whereIndex("primary")
|
|
360
|
+
.join((jb) => jb["user"]((builder) => builder.orderByIndex("idx_created", "desc"))),
|
|
361
|
+
);
|
|
419
362
|
|
|
420
363
|
const ops = uow.getRetrievalOperations();
|
|
421
364
|
expect(ops).toHaveLength(1);
|
|
@@ -446,16 +389,13 @@ describe("FindBuilder", () => {
|
|
|
446
389
|
}),
|
|
447
390
|
);
|
|
448
391
|
|
|
449
|
-
const uow =
|
|
450
|
-
testSchema,
|
|
451
|
-
createMockCompiler(),
|
|
452
|
-
createMockExecutor(),
|
|
453
|
-
createMockDecoder(),
|
|
454
|
-
);
|
|
392
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
455
393
|
|
|
456
|
-
uow
|
|
457
|
-
|
|
458
|
-
|
|
394
|
+
uow
|
|
395
|
+
.forSchema(testSchema)
|
|
396
|
+
.find("posts", (b) =>
|
|
397
|
+
b.whereIndex("primary").join((jb) => jb["user"]((builder) => builder.pageSize(5))),
|
|
398
|
+
);
|
|
459
399
|
|
|
460
400
|
const ops = uow.getRetrievalOperations();
|
|
461
401
|
expect(ops).toHaveLength(1);
|
|
@@ -498,24 +438,21 @@ describe("FindBuilder", () => {
|
|
|
498
438
|
}),
|
|
499
439
|
);
|
|
500
440
|
|
|
501
|
-
const uow =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.select(["title"])
|
|
515
|
-
.join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
|
|
441
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
442
|
+
|
|
443
|
+
uow
|
|
444
|
+
.forSchema(testSchema)
|
|
445
|
+
.find("comments", (b) =>
|
|
446
|
+
b
|
|
447
|
+
.whereIndex("primary")
|
|
448
|
+
.join((jb) =>
|
|
449
|
+
jb["post"]((postBuilder) =>
|
|
450
|
+
postBuilder
|
|
451
|
+
.select(["title"])
|
|
452
|
+
.join((jb2) => jb2["user"]((userBuilder) => userBuilder.select(["name"]))),
|
|
453
|
+
),
|
|
516
454
|
),
|
|
517
|
-
|
|
518
|
-
);
|
|
455
|
+
);
|
|
519
456
|
|
|
520
457
|
const ops = uow.getRetrievalOperations();
|
|
521
458
|
expect(ops).toHaveLength(1);
|
|
@@ -704,18 +641,17 @@ describe("IndexedConditionBuilder", () => {
|
|
|
704
641
|
>;
|
|
705
642
|
expectTypeOf<_IndexColumnNames>().toEqualTypeOf<"name" | "age">();
|
|
706
643
|
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
createMockCompiler<typeof typeTestSchema>(),
|
|
644
|
+
const baseUow = createUnitOfWork(
|
|
645
|
+
createMockCompiler(),
|
|
710
646
|
createMockExecutor(),
|
|
711
|
-
createMockDecoder
|
|
647
|
+
createMockDecoder(),
|
|
712
648
|
);
|
|
713
|
-
|
|
649
|
+
const uow = baseUow.forSchema(typeTestSchema);
|
|
714
650
|
expectTypeOf<keyof typeof typeTestSchema.tables>().toEqualTypeOf<"users">();
|
|
715
651
|
type _Query = AbstractQuery<typeof typeTestSchema>;
|
|
716
652
|
expectTypeOf<Parameters<_Query["create"]>[0]>().toEqualTypeOf<"users">();
|
|
717
653
|
|
|
718
|
-
expectTypeOf
|
|
654
|
+
expectTypeOf<Parameters<typeof uow.find>[0]>().toEqualTypeOf<"users">();
|
|
719
655
|
|
|
720
656
|
uow.find("users", (b) =>
|
|
721
657
|
b.whereIndex("primary", (eb) => {
|
|
@@ -748,15 +684,10 @@ describe("UpdateBuilder with string ID", () => {
|
|
|
748
684
|
);
|
|
749
685
|
|
|
750
686
|
it("should allow update with string ID", async () => {
|
|
751
|
-
const uow =
|
|
752
|
-
testSchema,
|
|
753
|
-
createMockCompiler(),
|
|
754
|
-
createMockExecutor(),
|
|
755
|
-
createMockDecoder(),
|
|
756
|
-
);
|
|
687
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
757
688
|
|
|
758
689
|
// Should work with string ID
|
|
759
|
-
uow.update("users", "user-123", (b) => b.set({ name: "New Name" }));
|
|
690
|
+
uow.forSchema(testSchema).update("users", "user-123", (b) => b.set({ name: "New Name" }));
|
|
760
691
|
|
|
761
692
|
const ops = uow.getMutationOperations();
|
|
762
693
|
expect(ops).toHaveLength(1);
|
|
@@ -770,16 +701,13 @@ describe("UpdateBuilder with string ID", () => {
|
|
|
770
701
|
});
|
|
771
702
|
|
|
772
703
|
it("should throw when using check() with string ID", async () => {
|
|
773
|
-
const uow =
|
|
774
|
-
testSchema,
|
|
775
|
-
createMockCompiler(),
|
|
776
|
-
createMockExecutor(),
|
|
777
|
-
createMockDecoder(),
|
|
778
|
-
);
|
|
704
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
779
705
|
|
|
780
706
|
// Should throw because check() is not allowed with string ID
|
|
781
707
|
expect(() => {
|
|
782
|
-
uow
|
|
708
|
+
uow
|
|
709
|
+
.forSchema(testSchema)
|
|
710
|
+
.update("users", "user-123", (b) => b.set({ name: "New Name" }).check());
|
|
783
711
|
}).toThrow(
|
|
784
712
|
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
785
713
|
);
|
|
@@ -792,15 +720,10 @@ describe("DeleteBuilder with string ID", () => {
|
|
|
792
720
|
);
|
|
793
721
|
|
|
794
722
|
it("should allow delete with string ID", async () => {
|
|
795
|
-
const uow =
|
|
796
|
-
testSchema,
|
|
797
|
-
createMockCompiler(),
|
|
798
|
-
createMockExecutor(),
|
|
799
|
-
createMockDecoder(),
|
|
800
|
-
);
|
|
723
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
801
724
|
|
|
802
725
|
// Should work with string ID
|
|
803
|
-
uow.delete("users", "user-123");
|
|
726
|
+
uow.forSchema(testSchema).delete("users", "user-123");
|
|
804
727
|
|
|
805
728
|
const ops = uow.getMutationOperations();
|
|
806
729
|
expect(ops).toMatchObject([
|
|
@@ -813,16 +736,11 @@ describe("DeleteBuilder with string ID", () => {
|
|
|
813
736
|
});
|
|
814
737
|
|
|
815
738
|
it("should throw when using check() with string ID", async () => {
|
|
816
|
-
const uow =
|
|
817
|
-
testSchema,
|
|
818
|
-
createMockCompiler(),
|
|
819
|
-
createMockExecutor(),
|
|
820
|
-
createMockDecoder(),
|
|
821
|
-
);
|
|
739
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
822
740
|
|
|
823
741
|
// Should throw because check() is not allowed with string ID
|
|
824
742
|
expect(() => {
|
|
825
|
-
uow.delete("users", "user-123", (b) => b.check());
|
|
743
|
+
uow.forSchema(testSchema).delete("users", "user-123", (b) => b.check());
|
|
826
744
|
}).toThrow(
|
|
827
745
|
'Cannot use check() with a string ID on table "users". Version checking requires a FragnoId with version information.',
|
|
828
746
|
);
|
|
@@ -845,10 +763,10 @@ describe("getCreatedIds", () => {
|
|
|
845
763
|
}),
|
|
846
764
|
};
|
|
847
765
|
|
|
848
|
-
const uow =
|
|
766
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
849
767
|
|
|
850
|
-
uow.create("users", { email: "user1@example.com", name: "User 1" });
|
|
851
|
-
uow.create("users", { email: "user2@example.com", name: "User 2" });
|
|
768
|
+
uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
|
|
769
|
+
uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
|
|
852
770
|
|
|
853
771
|
await uow.executeMutations();
|
|
854
772
|
const createdIds = uow.getCreatedIds();
|
|
@@ -871,10 +789,10 @@ describe("getCreatedIds", () => {
|
|
|
871
789
|
}),
|
|
872
790
|
};
|
|
873
791
|
|
|
874
|
-
const uow =
|
|
792
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
875
793
|
|
|
876
|
-
uow.create("users", { email: "user1@example.com", name: "User 1" });
|
|
877
|
-
uow.create("users", { email: "user2@example.com", name: "User 2" });
|
|
794
|
+
uow.forSchema(testSchema).create("users", { email: "user1@example.com", name: "User 1" });
|
|
795
|
+
uow.forSchema(testSchema).create("users", { email: "user2@example.com", name: "User 2" });
|
|
878
796
|
|
|
879
797
|
await uow.executeMutations();
|
|
880
798
|
const createdIds = uow.getCreatedIds();
|
|
@@ -895,9 +813,11 @@ describe("getCreatedIds", () => {
|
|
|
895
813
|
}),
|
|
896
814
|
};
|
|
897
815
|
|
|
898
|
-
const uow =
|
|
816
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
899
817
|
|
|
900
|
-
uow
|
|
818
|
+
uow
|
|
819
|
+
.forSchema(testSchema)
|
|
820
|
+
.create("users", { id: "my-custom-id", email: "user@example.com", name: "User" });
|
|
901
821
|
|
|
902
822
|
await uow.executeMutations();
|
|
903
823
|
const createdIds = uow.getCreatedIds();
|
|
@@ -916,11 +836,11 @@ describe("getCreatedIds", () => {
|
|
|
916
836
|
}),
|
|
917
837
|
};
|
|
918
838
|
|
|
919
|
-
const uow =
|
|
839
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
920
840
|
|
|
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");
|
|
841
|
+
uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
|
|
842
|
+
uow.forSchema(testSchema).update("users", "existing-id", (b) => b.set({ name: "Updated" }));
|
|
843
|
+
uow.forSchema(testSchema).delete("users", "other-id");
|
|
924
844
|
|
|
925
845
|
await uow.executeMutations();
|
|
926
846
|
const createdIds = uow.getCreatedIds();
|
|
@@ -931,17 +851,775 @@ describe("getCreatedIds", () => {
|
|
|
931
851
|
});
|
|
932
852
|
|
|
933
853
|
it("should throw when called before executeMutations", () => {
|
|
934
|
-
const uow =
|
|
935
|
-
|
|
854
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
855
|
+
|
|
856
|
+
uow.forSchema(testSchema).create("users", { email: "user@example.com", name: "User" });
|
|
857
|
+
|
|
858
|
+
expect(() => uow.getCreatedIds()).toThrow(
|
|
859
|
+
"getCreatedIds() can only be called after executeMutations()",
|
|
860
|
+
);
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
describe("Phase promises with multiple views", () => {
|
|
865
|
+
it("should return only operations added to the current view when using retrievalPhase promise", async () => {
|
|
866
|
+
// Create two separate schemas
|
|
867
|
+
const schema1 = schema((s) =>
|
|
868
|
+
s.addTable("users", (t) =>
|
|
869
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
870
|
+
),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const schema2 = schema((s) =>
|
|
874
|
+
s.addTable("posts", (t) =>
|
|
875
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
876
|
+
),
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
// Create a schema namespace map
|
|
880
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
881
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
882
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
883
|
+
|
|
884
|
+
// Mock executor that returns distinct results
|
|
885
|
+
const executor = {
|
|
886
|
+
executeRetrievalPhase: async () => {
|
|
887
|
+
return [
|
|
888
|
+
[{ id: "user1", name: "Alice", email: "alice@example.com" }],
|
|
889
|
+
[{ id: "user2", name: "Bob", email: "bob@example.com" }],
|
|
890
|
+
[{ id: "post1", title: "Post 1", content: "Content 1" }],
|
|
891
|
+
];
|
|
892
|
+
},
|
|
893
|
+
executeMutationPhase: async () => ({
|
|
894
|
+
success: true,
|
|
895
|
+
createdInternalIds: [],
|
|
896
|
+
}),
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// Create parent UOW
|
|
900
|
+
const parentUow = createUnitOfWork(
|
|
936
901
|
createMockCompiler(),
|
|
937
|
-
|
|
902
|
+
executor,
|
|
938
903
|
createMockDecoder(),
|
|
904
|
+
schemaNamespaceMap,
|
|
905
|
+
"test-uow",
|
|
939
906
|
);
|
|
940
907
|
|
|
941
|
-
|
|
908
|
+
// Add a find operation via a schema1 view (but don't keep a reference to this view)
|
|
909
|
+
parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
|
|
942
910
|
|
|
943
|
-
|
|
944
|
-
|
|
911
|
+
// Create a view for schema1 and add another find operation
|
|
912
|
+
const view1 = parentUow.forSchema(schema1).find("users", (b) => b.whereIndex("primary"));
|
|
913
|
+
|
|
914
|
+
// Create a view for schema2 and add a find operation
|
|
915
|
+
const view2 = parentUow.forSchema(schema2).find("posts", (b) => b.whereIndex("primary"));
|
|
916
|
+
|
|
917
|
+
// Execute retrieval phase on parent
|
|
918
|
+
const parentResults = await parentUow.executeRetrieve();
|
|
919
|
+
|
|
920
|
+
// Parent should have all 3 results
|
|
921
|
+
expect(parentResults).toHaveLength(3);
|
|
922
|
+
|
|
923
|
+
// View1's retrievalPhase promise should only contain results from operations added through view1
|
|
924
|
+
// (which is index 1 in the parent's operations)
|
|
925
|
+
const view1Results = await view1.retrievalPhase;
|
|
926
|
+
expect(view1Results).toHaveLength(1);
|
|
927
|
+
expect(view1Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
|
|
928
|
+
|
|
929
|
+
// View2's retrievalPhase promise should only contain results from operations added through view2
|
|
930
|
+
// (which is index 2 in the parent's operations)
|
|
931
|
+
const view2Results = await view2.retrievalPhase;
|
|
932
|
+
expect(view2Results).toHaveLength(1);
|
|
933
|
+
expect(view2Results[0]).toEqual([{ id: "post1", title: "Post 1", content: "Content 1" }]);
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
it("should isolate operations when getUnitOfWork is called multiple times with same schema", async () => {
|
|
937
|
+
const testSchema = schema((s) =>
|
|
938
|
+
s.addTable("users", (t) =>
|
|
939
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
940
|
+
),
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
const executor = {
|
|
944
|
+
executeRetrievalPhase: async () => {
|
|
945
|
+
return [
|
|
946
|
+
[{ id: "user1", name: "Alice", email: "alice@example.com" }],
|
|
947
|
+
[{ id: "user2", name: "Bob", email: "bob@example.com" }],
|
|
948
|
+
];
|
|
949
|
+
},
|
|
950
|
+
executeMutationPhase: async () => ({
|
|
951
|
+
success: true,
|
|
952
|
+
createdInternalIds: [],
|
|
953
|
+
}),
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
const parentUow = createUnitOfWork(
|
|
957
|
+
createMockCompiler(),
|
|
958
|
+
executor,
|
|
959
|
+
createMockDecoder(),
|
|
960
|
+
undefined,
|
|
961
|
+
"test-uow",
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Simulate what happens in db-fragment-definition-builder when getUnitOfWork(schema) is called twice
|
|
965
|
+
const view1 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
966
|
+
|
|
967
|
+
const view2 = parentUow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
968
|
+
|
|
969
|
+
// Execute retrieval
|
|
970
|
+
await parentUow.executeRetrieve();
|
|
971
|
+
|
|
972
|
+
// Each view should only see its own operation's results
|
|
973
|
+
const view1Results = await view1.retrievalPhase;
|
|
974
|
+
expect(view1Results).toHaveLength(1);
|
|
975
|
+
expect(view1Results[0]).toEqual([{ id: "user1", name: "Alice", email: "alice@example.com" }]);
|
|
976
|
+
|
|
977
|
+
const view2Results = await view2.retrievalPhase;
|
|
978
|
+
expect(view2Results).toHaveLength(1);
|
|
979
|
+
expect(view2Results[0]).toEqual([{ id: "user2", name: "Bob", email: "bob@example.com" }]);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it("should show that getCreatedIds returns ALL created IDs regardless of which view created them", async () => {
|
|
983
|
+
const schema1 = schema((s) =>
|
|
984
|
+
s.addTable("users", (t) =>
|
|
985
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
986
|
+
),
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
const schema2 = schema((s) =>
|
|
990
|
+
s.addTable("posts", (t) =>
|
|
991
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
992
|
+
),
|
|
945
993
|
);
|
|
994
|
+
|
|
995
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
996
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
997
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
998
|
+
|
|
999
|
+
const executor = {
|
|
1000
|
+
executeRetrievalPhase: async () => [],
|
|
1001
|
+
executeMutationPhase: async () => ({
|
|
1002
|
+
success: true,
|
|
1003
|
+
createdInternalIds: [1n, 2n],
|
|
1004
|
+
}),
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
const parentUow = createUnitOfWork(
|
|
1008
|
+
createMockCompiler(),
|
|
1009
|
+
executor,
|
|
1010
|
+
createMockDecoder(),
|
|
1011
|
+
schemaNamespaceMap,
|
|
1012
|
+
"test-uow",
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
// View1 creates one user
|
|
1016
|
+
const view1 = parentUow.forSchema(schema1);
|
|
1017
|
+
view1.create("users", { name: "Alice", email: "alice@example.com" });
|
|
1018
|
+
|
|
1019
|
+
// View2 creates one post
|
|
1020
|
+
const view2 = parentUow.forSchema(schema2);
|
|
1021
|
+
view2.create("posts", { title: "Post 1", content: "Content 1" });
|
|
1022
|
+
|
|
1023
|
+
// Execute mutations
|
|
1024
|
+
await parentUow.executeMutations();
|
|
1025
|
+
|
|
1026
|
+
// Both views see ALL created IDs (not filtered by view)
|
|
1027
|
+
const view1Ids = view1.getCreatedIds();
|
|
1028
|
+
const view2Ids = view2.getCreatedIds();
|
|
1029
|
+
|
|
1030
|
+
expect(view1Ids).toHaveLength(2); // Sees both IDs, not just the one it created
|
|
1031
|
+
expect(view2Ids).toHaveLength(2); // Sees both IDs, not just the one it created
|
|
1032
|
+
|
|
1033
|
+
// They're the same array
|
|
1034
|
+
expect(view1Ids).toEqual(view2Ids);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("should generate unique IDs when multiple views create items", async () => {
|
|
1038
|
+
const schema1 = schema((s) =>
|
|
1039
|
+
s.addTable("users", (t) =>
|
|
1040
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1041
|
+
),
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
const schema2 = schema((s) =>
|
|
1045
|
+
s.addTable("posts", (t) =>
|
|
1046
|
+
t.addColumn("id", idColumn()).addColumn("title", "string").addColumn("content", "string"),
|
|
1047
|
+
),
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
const schemaNamespaceMap = new WeakMap<typeof schema1 | typeof schema2, string>();
|
|
1051
|
+
schemaNamespaceMap.set(schema1, "namespace1");
|
|
1052
|
+
schemaNamespaceMap.set(schema2, "namespace2");
|
|
1053
|
+
|
|
1054
|
+
const executor = {
|
|
1055
|
+
executeRetrievalPhase: async () => [],
|
|
1056
|
+
executeMutationPhase: async () => ({
|
|
1057
|
+
success: true,
|
|
1058
|
+
createdInternalIds: [1n, 2n],
|
|
1059
|
+
}),
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const parentUow = createUnitOfWork(
|
|
1063
|
+
createMockCompiler(),
|
|
1064
|
+
executor,
|
|
1065
|
+
createMockDecoder(),
|
|
1066
|
+
schemaNamespaceMap,
|
|
1067
|
+
"test-uow",
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
// View1 creates a user
|
|
1071
|
+
const view1 = parentUow.forSchema(schema1);
|
|
1072
|
+
const userId = view1.create("users", { name: "Alice", email: "alice@example.com" });
|
|
1073
|
+
|
|
1074
|
+
// View2 creates a post
|
|
1075
|
+
const view2 = parentUow.forSchema(schema2);
|
|
1076
|
+
const postId = view2.create("posts", { title: "Post 1", content: "Content 1" });
|
|
1077
|
+
|
|
1078
|
+
// IDs should be unique before execution
|
|
1079
|
+
expect(userId.externalId).not.toBe(postId.externalId);
|
|
1080
|
+
expect(userId.externalId).toBeTruthy();
|
|
1081
|
+
expect(postId.externalId).toBeTruthy();
|
|
1082
|
+
expect(userId.internalId).toBeUndefined();
|
|
1083
|
+
expect(postId.internalId).toBeUndefined();
|
|
1084
|
+
|
|
1085
|
+
// Execute mutations
|
|
1086
|
+
await parentUow.executeMutations();
|
|
1087
|
+
|
|
1088
|
+
// Get the created IDs after execution
|
|
1089
|
+
const createdIds = parentUow.getCreatedIds();
|
|
1090
|
+
expect(createdIds).toHaveLength(2);
|
|
1091
|
+
|
|
1092
|
+
// Both should have external IDs set (from create operation)
|
|
1093
|
+
expect(createdIds[0].externalId).toBe(userId.externalId);
|
|
1094
|
+
expect(createdIds[1].externalId).toBe(postId.externalId);
|
|
1095
|
+
|
|
1096
|
+
// Both should now have internal IDs (from database)
|
|
1097
|
+
expect(createdIds[0].internalId).toBe(1n);
|
|
1098
|
+
expect(createdIds[1].internalId).toBe(2n);
|
|
1099
|
+
|
|
1100
|
+
// IDs should still be unique
|
|
1101
|
+
expect(createdIds[0].externalId).not.toBe(createdIds[1].externalId);
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
describe("Error Handling", () => {
|
|
1106
|
+
const testSchema = schema((s) =>
|
|
1107
|
+
s.addTable("users", (t) =>
|
|
1108
|
+
t.addColumn("id", idColumn()).addColumn("name", "string").addColumn("email", "string"),
|
|
1109
|
+
),
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1112
|
+
it("should throw error from executeRetrieve() when retrieval fails", async () => {
|
|
1113
|
+
const executor = {
|
|
1114
|
+
executeRetrievalPhase: async () => {
|
|
1115
|
+
throw new Error("Database connection failed");
|
|
1116
|
+
},
|
|
1117
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1121
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1122
|
+
|
|
1123
|
+
await expect(uow.executeRetrieve()).rejects.toThrow("Database connection failed");
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
it("should throw error from executeMutations() when mutation fails", async () => {
|
|
1127
|
+
const executor = {
|
|
1128
|
+
executeRetrievalPhase: async () => [],
|
|
1129
|
+
executeMutationPhase: async () => {
|
|
1130
|
+
throw new Error("Write conflict");
|
|
1131
|
+
},
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1135
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1136
|
+
|
|
1137
|
+
await expect(uow.executeMutations()).rejects.toThrow("Write conflict");
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
it("should reject retrievalPhase promise when executeRetrieve() fails", async () => {
|
|
1141
|
+
const executor = {
|
|
1142
|
+
executeRetrievalPhase: async () => {
|
|
1143
|
+
throw new Error("Query timeout");
|
|
1144
|
+
},
|
|
1145
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1149
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1150
|
+
|
|
1151
|
+
// Start executing (this will fail)
|
|
1152
|
+
const executePromise = uow.executeRetrieve().catch((e) => {
|
|
1153
|
+
return (e as Error).message;
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
uow.retrievalPhase
|
|
1157
|
+
.then(() => {
|
|
1158
|
+
throw new Error("Should not be called");
|
|
1159
|
+
})
|
|
1160
|
+
.catch(() => {
|
|
1161
|
+
throw new Error("Should not be called");
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// uow.retrievalPhase should not be settled yet
|
|
1165
|
+
|
|
1166
|
+
// Wait for execute to complete
|
|
1167
|
+
expect(await executePromise).toBe("Query timeout");
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
it("should reject mutationPhase promise when executeMutations() fails", async () => {
|
|
1171
|
+
const executor = {
|
|
1172
|
+
executeRetrievalPhase: async () => [],
|
|
1173
|
+
executeMutationPhase: async () => {
|
|
1174
|
+
throw new Error("Constraint violation");
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1179
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1180
|
+
|
|
1181
|
+
// Start executing (this will fail)
|
|
1182
|
+
const executePromise = uow.executeMutations().catch((e) => {
|
|
1183
|
+
return (e as Error).message;
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
uow.mutationPhase
|
|
1187
|
+
.then(() => {
|
|
1188
|
+
throw new Error("Should not be called");
|
|
1189
|
+
})
|
|
1190
|
+
.catch(() => {
|
|
1191
|
+
throw new Error("Should not be called");
|
|
1192
|
+
});
|
|
1193
|
+
// Wait for execute to complete
|
|
1194
|
+
expect(await executePromise).toBe("Constraint violation");
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it("should not cause unhandled promise rejection when executeRetrieve() fails and coordination promise is not awaited", async () => {
|
|
1198
|
+
const executor = {
|
|
1199
|
+
executeRetrievalPhase: async () => {
|
|
1200
|
+
throw new Error("Table does not exist");
|
|
1201
|
+
},
|
|
1202
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1206
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1207
|
+
|
|
1208
|
+
// Access the retrievalPhase promise but don't await it
|
|
1209
|
+
// This simulates the internal coordination promise that might not be awaited
|
|
1210
|
+
const _retrievalPhase = uow.retrievalPhase;
|
|
1211
|
+
|
|
1212
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1213
|
+
|
|
1214
|
+
// Execute and catch the error from executeRetrieve()
|
|
1215
|
+
try {
|
|
1216
|
+
await uow.executeRetrieve();
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
// Error is caught, this is expected
|
|
1219
|
+
expect(error).toBeInstanceOf(Error);
|
|
1220
|
+
expect((error as Error).message).toBe("Table does not exist");
|
|
1221
|
+
errorResolver.resolve();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
await errorResolver.promise;
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
it("should not cause unhandled promise rejection when executeMutations() fails and coordination promise is not awaited", async () => {
|
|
1228
|
+
const executor = {
|
|
1229
|
+
executeRetrievalPhase: async () => [],
|
|
1230
|
+
executeMutationPhase: async () => {
|
|
1231
|
+
throw new Error("Deadlock detected");
|
|
1232
|
+
},
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1236
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1237
|
+
|
|
1238
|
+
// Access the mutationPhase promise but don't await it
|
|
1239
|
+
// This simulates the internal coordination promise that might not be awaited
|
|
1240
|
+
const _mutationPhase = uow.mutationPhase;
|
|
1241
|
+
|
|
1242
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1243
|
+
|
|
1244
|
+
// Execute and catch the error from executeMutations()
|
|
1245
|
+
try {
|
|
1246
|
+
await uow.executeMutations();
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
// Error is caught, this is expected
|
|
1249
|
+
expect(error).toBeInstanceOf(Error);
|
|
1250
|
+
expect((error as Error).message).toBe("Deadlock detected");
|
|
1251
|
+
errorResolver.resolve();
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
await errorResolver.promise;
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
it("should handle error in executeRetrieve() when coordination promise is never accessed", async () => {
|
|
1258
|
+
const executor = {
|
|
1259
|
+
executeRetrievalPhase: async () => {
|
|
1260
|
+
throw new Error("Connection lost");
|
|
1261
|
+
},
|
|
1262
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1266
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1267
|
+
|
|
1268
|
+
// Don't access retrievalPhase at all - this is the most common case
|
|
1269
|
+
// The internal coordination promise should not cause unhandled rejection
|
|
1270
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1271
|
+
|
|
1272
|
+
// Execute and catch the error
|
|
1273
|
+
try {
|
|
1274
|
+
await uow.executeRetrieve();
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
expect(error).toBeInstanceOf(Error);
|
|
1277
|
+
expect((error as Error).message).toBe("Connection lost");
|
|
1278
|
+
errorResolver.resolve();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
await errorResolver.promise;
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
it("should handle error in executeMutations() when coordination promise is never accessed", async () => {
|
|
1285
|
+
const executor = {
|
|
1286
|
+
executeRetrievalPhase: async () => [],
|
|
1287
|
+
executeMutationPhase: async () => {
|
|
1288
|
+
throw new Error("Transaction aborted");
|
|
1289
|
+
},
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1293
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1294
|
+
|
|
1295
|
+
// Don't access mutationPhase at all - this is the most common case
|
|
1296
|
+
// The internal coordination promise should not cause unhandled rejection
|
|
1297
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1298
|
+
// Execute and catch the error
|
|
1299
|
+
try {
|
|
1300
|
+
await uow.executeMutations();
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
expect(error).toBeInstanceOf(Error);
|
|
1303
|
+
expect((error as Error).message).toBe("Transaction aborted");
|
|
1304
|
+
errorResolver.resolve();
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
await errorResolver.promise;
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it("should handle reset() after retrieval error", async () => {
|
|
1311
|
+
const executor = {
|
|
1312
|
+
executeRetrievalPhase: async () => {
|
|
1313
|
+
throw new Error("First attempt failed");
|
|
1314
|
+
},
|
|
1315
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1319
|
+
uow.forSchema(testSchema).find("users", (b) => b.whereIndex("primary"));
|
|
1320
|
+
|
|
1321
|
+
// First attempt fails
|
|
1322
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1323
|
+
try {
|
|
1324
|
+
await uow.executeRetrieve();
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
expect((error as Error).message).toBe("First attempt failed");
|
|
1327
|
+
errorResolver.resolve();
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Reset the UOW
|
|
1331
|
+
uow.reset();
|
|
1332
|
+
|
|
1333
|
+
// The UOW should be in a clean state
|
|
1334
|
+
expect(uow.state).toBe("building-retrieval");
|
|
1335
|
+
expect(uow.getRetrievalOperations()).toHaveLength(0);
|
|
1336
|
+
|
|
1337
|
+
await errorResolver.promise;
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
it("should handle reset() after mutation error", async () => {
|
|
1341
|
+
const executor = {
|
|
1342
|
+
executeRetrievalPhase: async () => [],
|
|
1343
|
+
executeMutationPhase: async () => {
|
|
1344
|
+
throw new Error("First mutation failed");
|
|
1345
|
+
},
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1349
|
+
uow.forSchema(testSchema).create("users", { name: "Alice", email: "alice@example.com" });
|
|
1350
|
+
|
|
1351
|
+
// First attempt fails
|
|
1352
|
+
const errorResolver = Promise.withResolvers<void>();
|
|
1353
|
+
try {
|
|
1354
|
+
await uow.executeMutations();
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
expect((error as Error).message).toBe("First mutation failed");
|
|
1357
|
+
errorResolver.resolve();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// Reset the UOW
|
|
1361
|
+
uow.reset();
|
|
1362
|
+
|
|
1363
|
+
// The UOW should be in a clean state
|
|
1364
|
+
expect(uow.state).toBe("building-retrieval");
|
|
1365
|
+
expect(uow.getMutationOperations()).toHaveLength(0);
|
|
1366
|
+
|
|
1367
|
+
await errorResolver.promise;
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
it("should support standalone check() operation", () => {
|
|
1371
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
1372
|
+
const typedUow = uow.forSchema(testSchema);
|
|
1373
|
+
|
|
1374
|
+
// Create a FragnoId for testing
|
|
1375
|
+
const userId = FragnoId.fromExternal("user-123", 5);
|
|
1376
|
+
|
|
1377
|
+
typedUow.check("users", userId);
|
|
1378
|
+
|
|
1379
|
+
const mutationOps = uow.getMutationOperations();
|
|
1380
|
+
expect(mutationOps).toHaveLength(1);
|
|
1381
|
+
|
|
1382
|
+
const checkOp = mutationOps[0];
|
|
1383
|
+
assert(checkOp);
|
|
1384
|
+
assert(checkOp.type === "check");
|
|
1385
|
+
expect(checkOp.table).toBe("users");
|
|
1386
|
+
expect(checkOp.id).toBe(userId);
|
|
1387
|
+
});
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
describe("findFirst convenience method", () => {
|
|
1391
|
+
const testSchema = schema((s) =>
|
|
1392
|
+
s
|
|
1393
|
+
.addTable("users", (t) =>
|
|
1394
|
+
t
|
|
1395
|
+
.addColumn("id", idColumn())
|
|
1396
|
+
.addColumn("name", "string")
|
|
1397
|
+
.addColumn("email", "string")
|
|
1398
|
+
.createIndex("idx_email", ["email"])
|
|
1399
|
+
.createIndex("idx_name", ["name"]),
|
|
1400
|
+
)
|
|
1401
|
+
.addTable("posts", (t) =>
|
|
1402
|
+
t
|
|
1403
|
+
.addColumn("id", idColumn())
|
|
1404
|
+
.addColumn("userId", "string")
|
|
1405
|
+
.addColumn("title", "string")
|
|
1406
|
+
.createIndex("idx_user", ["userId"]),
|
|
1407
|
+
),
|
|
1408
|
+
);
|
|
1409
|
+
|
|
1410
|
+
it("should return a single result instead of an array", async () => {
|
|
1411
|
+
const executor = {
|
|
1412
|
+
executeRetrievalPhase: async () => {
|
|
1413
|
+
return [[{ id: "mock-id", name: "Mock User", email: "mock@example.com" }]];
|
|
1414
|
+
},
|
|
1415
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1419
|
+
|
|
1420
|
+
// Use findFirst instead of find
|
|
1421
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1422
|
+
|
|
1423
|
+
// Execute retrieval
|
|
1424
|
+
await uow.executeRetrieve();
|
|
1425
|
+
const results = await typedUow.retrievalPhase;
|
|
1426
|
+
|
|
1427
|
+
// Result should be a single object, not an array
|
|
1428
|
+
const [user] = results;
|
|
1429
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "mock@example.com" });
|
|
1430
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it("should return null when no results are found", async () => {
|
|
1434
|
+
// Create executor that returns empty results
|
|
1435
|
+
const emptyExecutor = {
|
|
1436
|
+
executeRetrievalPhase: async () => {
|
|
1437
|
+
return [[]]; // Empty array for no results
|
|
1438
|
+
},
|
|
1439
|
+
executeMutationPhase: async () => {
|
|
1440
|
+
return { success: true, createdInternalIds: [] };
|
|
1441
|
+
},
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
const uow = createUnitOfWork(createMockCompiler(), emptyExecutor, createMockDecoder());
|
|
1445
|
+
|
|
1446
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1447
|
+
|
|
1448
|
+
await uow.executeRetrieve();
|
|
1449
|
+
const results = await typedUow.retrievalPhase;
|
|
1450
|
+
const [user] = results;
|
|
1451
|
+
|
|
1452
|
+
// Should be null when no results
|
|
1453
|
+
expect(user).toBeNull();
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
it("should automatically set pageSize to 1", () => {
|
|
1457
|
+
const uow = createUnitOfWork(createMockCompiler(), createMockExecutor(), createMockDecoder());
|
|
1458
|
+
|
|
1459
|
+
uow.forSchema(testSchema).findFirst("users", (b) => b.whereIndex("primary"));
|
|
1460
|
+
|
|
1461
|
+
// Check that pageSize was set to 1 in the operation
|
|
1462
|
+
const ops = uow.getRetrievalOperations();
|
|
1463
|
+
expect(ops).toHaveLength(1);
|
|
1464
|
+
expect(ops[0]?.type).toBe("find");
|
|
1465
|
+
if (ops[0]?.type === "find") {
|
|
1466
|
+
expect(ops[0].options.pageSize).toBe(1);
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
it("should work with custom select clause", async () => {
|
|
1471
|
+
const executor = {
|
|
1472
|
+
executeRetrievalPhase: async () => {
|
|
1473
|
+
return [[{ id: "mock-id", name: "Mock User" }]];
|
|
1474
|
+
},
|
|
1475
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1476
|
+
};
|
|
1477
|
+
|
|
1478
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1479
|
+
|
|
1480
|
+
const typedUow = uow
|
|
1481
|
+
.forSchema(testSchema)
|
|
1482
|
+
.findFirst("users", (b) => b.whereIndex("primary").select(["id", "name"] as const));
|
|
1483
|
+
|
|
1484
|
+
await uow.executeRetrieve();
|
|
1485
|
+
const results = await typedUow.retrievalPhase;
|
|
1486
|
+
const [user] = results;
|
|
1487
|
+
|
|
1488
|
+
expect(user).toBeDefined();
|
|
1489
|
+
expect(user).not.toBeNull();
|
|
1490
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User" });
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
it("should work with where conditions", async () => {
|
|
1494
|
+
const executor = {
|
|
1495
|
+
executeRetrievalPhase: async () => {
|
|
1496
|
+
return [[{ id: "mock-id", name: "Mock User", email: "test@example.com" }]];
|
|
1497
|
+
},
|
|
1498
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1502
|
+
|
|
1503
|
+
const typedUow = uow
|
|
1504
|
+
.forSchema(testSchema)
|
|
1505
|
+
.findFirst("users", (b) =>
|
|
1506
|
+
b.whereIndex("idx_email", (eb) => eb("email", "=", "test@example.com")),
|
|
1507
|
+
);
|
|
1508
|
+
|
|
1509
|
+
await uow.executeRetrieve();
|
|
1510
|
+
const results = await typedUow.retrievalPhase;
|
|
1511
|
+
const [user] = results;
|
|
1512
|
+
|
|
1513
|
+
expect(user).toEqual({ id: "mock-id", name: "Mock User", email: "test@example.com" });
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
it("should handle multiple findFirst operations in the same UOW", async () => {
|
|
1517
|
+
const executor = {
|
|
1518
|
+
executeRetrievalPhase: async () => {
|
|
1519
|
+
return [
|
|
1520
|
+
[{ id: "user-1", name: "User 1", email: "user1@example.com" }],
|
|
1521
|
+
[{ id: "post-1", userId: "user-1", title: "Post 1" }],
|
|
1522
|
+
];
|
|
1523
|
+
},
|
|
1524
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1528
|
+
|
|
1529
|
+
const typedUow = uow
|
|
1530
|
+
.forSchema(testSchema)
|
|
1531
|
+
.findFirst("users", (b) => b.whereIndex("primary"))
|
|
1532
|
+
.findFirst("posts", (b) => b.whereIndex("primary"));
|
|
1533
|
+
|
|
1534
|
+
await uow.executeRetrieve();
|
|
1535
|
+
const results = await typedUow.retrievalPhase;
|
|
1536
|
+
const [user, post] = results;
|
|
1537
|
+
|
|
1538
|
+
// Both should be single objects, not arrays
|
|
1539
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1540
|
+
expect(post).toEqual({ id: "post-1", userId: "user-1", title: "Post 1" });
|
|
1541
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1542
|
+
expect(Array.isArray(post)).toBe(false);
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
it("should handle mix of find and findFirst operations", async () => {
|
|
1546
|
+
const executor = {
|
|
1547
|
+
executeRetrievalPhase: async () => {
|
|
1548
|
+
return [
|
|
1549
|
+
[{ id: "user-1", name: "User 1", email: "user1@example.com" }],
|
|
1550
|
+
[
|
|
1551
|
+
{ id: "post-1", userId: "user-1", title: "Post 1" },
|
|
1552
|
+
{ id: "post-2", userId: "user-1", title: "Post 2" },
|
|
1553
|
+
],
|
|
1554
|
+
];
|
|
1555
|
+
},
|
|
1556
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1560
|
+
|
|
1561
|
+
const typedUow = uow
|
|
1562
|
+
.forSchema(testSchema)
|
|
1563
|
+
.findFirst("users", (b) => b.whereIndex("primary")) // Single result
|
|
1564
|
+
.find("posts", (b) => b.whereIndex("primary")); // Array of results
|
|
1565
|
+
|
|
1566
|
+
await uow.executeRetrieve();
|
|
1567
|
+
const results = await typedUow.retrievalPhase;
|
|
1568
|
+
const [user, posts] = results;
|
|
1569
|
+
|
|
1570
|
+
// User should be a single object
|
|
1571
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1572
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1573
|
+
|
|
1574
|
+
// Posts should be an array
|
|
1575
|
+
expect(Array.isArray(posts)).toBe(true);
|
|
1576
|
+
expect(posts).toHaveLength(2);
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
it("should work with orderByIndex", async () => {
|
|
1580
|
+
const executor = {
|
|
1581
|
+
executeRetrievalPhase: async () => {
|
|
1582
|
+
return [[{ id: "user-1", name: "Alice", email: "alice@example.com" }]];
|
|
1583
|
+
},
|
|
1584
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1588
|
+
|
|
1589
|
+
const typedUow = uow
|
|
1590
|
+
.forSchema(testSchema)
|
|
1591
|
+
.findFirst("users", (b) => b.whereIndex("idx_name").orderByIndex("idx_name", "asc"));
|
|
1592
|
+
|
|
1593
|
+
await uow.executeRetrieve();
|
|
1594
|
+
const results = await typedUow.retrievalPhase;
|
|
1595
|
+
const [user] = results;
|
|
1596
|
+
|
|
1597
|
+
expect(user).toEqual({ id: "user-1", name: "Alice", email: "alice@example.com" });
|
|
1598
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
it("should work without explicit builder function", async () => {
|
|
1602
|
+
const executor = {
|
|
1603
|
+
executeRetrievalPhase: async () => {
|
|
1604
|
+
return [[{ id: "user-1", name: "User 1", email: "user1@example.com" }]];
|
|
1605
|
+
},
|
|
1606
|
+
executeMutationPhase: async () => ({ success: true, createdInternalIds: [] }),
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const uow = createUnitOfWork(createMockCompiler(), executor, createMockDecoder());
|
|
1610
|
+
|
|
1611
|
+
// findFirst without builder function should use primary index by default
|
|
1612
|
+
const typedUow = uow.forSchema(testSchema).findFirst("users");
|
|
1613
|
+
|
|
1614
|
+
await uow.executeRetrieve();
|
|
1615
|
+
const results = await typedUow.retrievalPhase;
|
|
1616
|
+
const [user] = results;
|
|
1617
|
+
|
|
1618
|
+
expect(user).toEqual({ id: "user-1", name: "User 1", email: "user1@example.com" });
|
|
1619
|
+
expect(Array.isArray(user)).toBe(false);
|
|
1620
|
+
|
|
1621
|
+
// Verify the operation used the primary index
|
|
1622
|
+
const ops = uow.getRetrievalOperations();
|
|
1623
|
+
expect(ops[0]?.indexName).toBe("_primary");
|
|
946
1624
|
});
|
|
947
1625
|
});
|