@fragno-dev/db 0.1.11 → 0.1.12
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 +30 -28
- package/CHANGELOG.md +13 -0
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +38 -34
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +3 -2
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts +22 -0
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-query.js +72 -50
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +2 -2
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +1 -1
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +5 -5
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/query/query.d.ts +24 -8
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/result-transform.js +17 -5
- package/dist/query/result-transform.js.map +1 -1
- package/dist/query/unit-of-work.d.ts +5 -4
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +2 -3
- package/dist/query/unit-of-work.js.map +1 -1
- package/dist/schema/serialize.js +2 -0
- package/dist/schema/serialize.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +170 -50
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +89 -35
- package/src/adapters/drizzle/drizzle-query.test.ts +54 -4
- package/src/adapters/drizzle/drizzle-query.ts +65 -60
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +63 -3
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +88 -0
- package/src/adapters/kysely/kysely-adapter.ts +6 -3
- package/src/adapters/kysely/kysely-query.test.ts +498 -0
- package/src/adapters/kysely/kysely-query.ts +137 -82
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +66 -0
- package/src/adapters/kysely/kysely-uow-executor.ts +5 -9
- package/src/migration-engine/generation-engine.ts +2 -1
- package/src/mod.ts +6 -6
- package/src/query/query-type.test.ts +34 -14
- package/src/query/query.ts +77 -36
- package/src/query/result-transform.test.ts +5 -5
- package/src/query/result-transform.ts +29 -11
- package/src/query/unit-of-work.ts +8 -11
- package/src/schema/serialize.test.ts +223 -0
- package/src/schema/serialize.ts +16 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { AbstractQuery } from "../../query/query";
|
|
2
|
-
import type { AnySchema } from "../../schema/create";
|
|
3
|
-
import type { CompiledMutation, UOWExecutor } from "../../query/unit-of-work";
|
|
2
|
+
import type { AnySchema, AnyTable } from "../../schema/create";
|
|
3
|
+
import type { CompiledMutation, UOWExecutor, ValidIndexName } from "../../query/unit-of-work";
|
|
4
4
|
import { createDrizzleUOWCompiler, type DrizzleCompiledQuery } from "./drizzle-uow-compiler";
|
|
5
5
|
import { executeDrizzleRetrievalPhase, executeDrizzleMutationPhase } from "./drizzle-uow-executor";
|
|
6
6
|
import { UnitOfWork } from "../../query/unit-of-work";
|
|
7
7
|
import { parseDrizzle, type DrizzleResult, type TableNameMapper, type DBType } from "./shared";
|
|
8
8
|
import { createDrizzleUOWDecoder } from "./drizzle-uow-decoder";
|
|
9
9
|
import type { ConnectionPool } from "../../shared/connection-pool";
|
|
10
|
+
import type { TableToUpdateValues } from "../../query/query";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Configuration options for creating a Drizzle Unit of Work
|
|
@@ -24,6 +25,37 @@ export interface DrizzleUOWConfig {
|
|
|
24
25
|
dryRun?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Special builder for updateMany operations that captures configuration
|
|
30
|
+
*/
|
|
31
|
+
class UpdateManySpecialBuilder<TTable extends AnyTable> {
|
|
32
|
+
#indexName?: string;
|
|
33
|
+
#condition?: unknown;
|
|
34
|
+
#setValues?: TableToUpdateValues<TTable>;
|
|
35
|
+
|
|
36
|
+
whereIndex<TIndexName extends ValidIndexName<TTable>>(
|
|
37
|
+
indexName: TIndexName,
|
|
38
|
+
condition?: unknown,
|
|
39
|
+
): this {
|
|
40
|
+
this.#indexName = indexName as string;
|
|
41
|
+
this.#condition = condition;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
set(values: TableToUpdateValues<TTable>): this {
|
|
46
|
+
this.#setValues = values;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getConfig() {
|
|
51
|
+
return {
|
|
52
|
+
indexName: this.#indexName,
|
|
53
|
+
condition: this.#condition,
|
|
54
|
+
setValues: this.#setValues,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
27
59
|
/**
|
|
28
60
|
* Creates a Drizzle-based query engine for the given schema.
|
|
29
61
|
*
|
|
@@ -114,7 +146,9 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
114
146
|
|
|
115
147
|
return {
|
|
116
148
|
async find(tableName, builderFn) {
|
|
117
|
-
|
|
149
|
+
// Safe: builderFn returns a FindBuilder (or void), which matches UnitOfWork signature
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
+
const uow = createUOW({ config: uowConfig }).find(tableName, builderFn as any);
|
|
118
152
|
const [result] = await uow.executeRetrieve();
|
|
119
153
|
return result;
|
|
120
154
|
},
|
|
@@ -122,7 +156,10 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
122
156
|
async findFirst(tableName, builderFn) {
|
|
123
157
|
const uow = createUOW({ config: uowConfig });
|
|
124
158
|
if (builderFn) {
|
|
125
|
-
uow.find(tableName, (b) =>
|
|
159
|
+
uow.find(tableName, (b) => {
|
|
160
|
+
builderFn(b);
|
|
161
|
+
return b.pageSize(1);
|
|
162
|
+
});
|
|
126
163
|
} else {
|
|
127
164
|
uow.find(tableName, (b) => b.whereIndex("primary").pageSize(1));
|
|
128
165
|
}
|
|
@@ -133,7 +170,7 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
133
170
|
|
|
134
171
|
async create(tableName, values) {
|
|
135
172
|
const uow = createUOW({ config: uowConfig });
|
|
136
|
-
uow.create(tableName
|
|
173
|
+
uow.create(tableName, values);
|
|
137
174
|
const { success } = await uow.executeMutations();
|
|
138
175
|
if (!success) {
|
|
139
176
|
throw new Error("Failed to create record");
|
|
@@ -150,7 +187,7 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
150
187
|
async createMany(tableName, valuesArray) {
|
|
151
188
|
const uow = createUOW({ config: uowConfig });
|
|
152
189
|
for (const values of valuesArray) {
|
|
153
|
-
uow.create(tableName
|
|
190
|
+
uow.create(tableName, values);
|
|
154
191
|
}
|
|
155
192
|
const { success } = await uow.executeMutations();
|
|
156
193
|
if (!success) {
|
|
@@ -162,7 +199,7 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
162
199
|
|
|
163
200
|
async update(tableName, id, builderFn) {
|
|
164
201
|
const uow = createUOW({ config: uowConfig });
|
|
165
|
-
uow.update(tableName
|
|
202
|
+
uow.update(tableName, id, builderFn);
|
|
166
203
|
const { success } = await uow.executeMutations();
|
|
167
204
|
if (!success) {
|
|
168
205
|
throw new Error("Failed to update record (version conflict or record not found)");
|
|
@@ -170,25 +207,17 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
170
207
|
},
|
|
171
208
|
|
|
172
209
|
async updateMany(tableName, builderFn) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const specialBuilder = {
|
|
179
|
-
whereIndex(indexName: string, condition?: unknown) {
|
|
180
|
-
whereConfig = { indexName, condition };
|
|
181
|
-
return this;
|
|
182
|
-
},
|
|
183
|
-
set(values: unknown) {
|
|
184
|
-
setValues = values;
|
|
185
|
-
return this;
|
|
186
|
-
},
|
|
187
|
-
};
|
|
210
|
+
const table = schema.tables[tableName];
|
|
211
|
+
if (!table) {
|
|
212
|
+
throw new Error(`Table ${tableName} not found in schema`);
|
|
213
|
+
}
|
|
188
214
|
|
|
215
|
+
const specialBuilder = new UpdateManySpecialBuilder<typeof table>();
|
|
189
216
|
builderFn(specialBuilder);
|
|
190
217
|
|
|
191
|
-
|
|
218
|
+
const { indexName, condition, setValues } = specialBuilder.getConfig();
|
|
219
|
+
|
|
220
|
+
if (!indexName) {
|
|
192
221
|
throw new Error("whereIndex() must be called in updateMany");
|
|
193
222
|
}
|
|
194
223
|
if (!setValues) {
|
|
@@ -197,24 +226,22 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
197
226
|
|
|
198
227
|
const findUow = createUOW({ config: uowConfig });
|
|
199
228
|
findUow.find(tableName, (b) => {
|
|
200
|
-
if (
|
|
201
|
-
|
|
229
|
+
if (condition) {
|
|
230
|
+
// Safe: condition is captured from whereIndex call with proper typing
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
|
+
return b.whereIndex(indexName as ValidIndexName<typeof table>, condition as any);
|
|
202
233
|
}
|
|
203
|
-
return b.whereIndex(
|
|
234
|
+
return b.whereIndex(indexName as ValidIndexName<typeof table>);
|
|
204
235
|
});
|
|
205
|
-
const
|
|
206
|
-
const records = (findResults as unknown as [unknown])[0];
|
|
236
|
+
const [records]: unknown[][] = await findUow.executeRetrieve();
|
|
207
237
|
|
|
208
|
-
// @ts-expect-error - Type narrowing doesn't work through unknown cast
|
|
209
238
|
if (!records || records.length === 0) {
|
|
210
239
|
return;
|
|
211
240
|
}
|
|
212
241
|
|
|
213
242
|
const updateUow = createUOW({ config: uowConfig });
|
|
214
|
-
for (const record of records as
|
|
215
|
-
updateUow.update(tableName
|
|
216
|
-
b.set(setValues as never),
|
|
217
|
-
);
|
|
243
|
+
for (const record of records as Array<{ id: unknown }>) {
|
|
244
|
+
updateUow.update(tableName, record.id as string, (b) => b.set(setValues));
|
|
218
245
|
}
|
|
219
246
|
const { success } = await updateUow.executeMutations();
|
|
220
247
|
if (!success) {
|
|
@@ -224,7 +251,7 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
224
251
|
|
|
225
252
|
async delete(tableName, id, builderFn) {
|
|
226
253
|
const uow = createUOW({ config: uowConfig });
|
|
227
|
-
uow.delete(tableName
|
|
254
|
+
uow.delete(tableName, id, builderFn);
|
|
228
255
|
const { success } = await uow.executeMutations();
|
|
229
256
|
if (!success) {
|
|
230
257
|
throw new Error("Failed to delete record (version conflict or record not found)");
|
|
@@ -232,39 +259,17 @@ export function fromDrizzle<T extends AnySchema>(
|
|
|
232
259
|
},
|
|
233
260
|
|
|
234
261
|
async deleteMany(tableName, builderFn) {
|
|
235
|
-
let whereConfig: { indexName?: string; condition?: unknown } = {};
|
|
236
|
-
|
|
237
|
-
const specialBuilder = {
|
|
238
|
-
whereIndex(indexName: string, condition?: unknown) {
|
|
239
|
-
whereConfig = { indexName, condition };
|
|
240
|
-
return this;
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
builderFn(specialBuilder as never);
|
|
245
|
-
|
|
246
|
-
if (!whereConfig.indexName) {
|
|
247
|
-
throw new Error("whereIndex() must be called in deleteMany");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
262
|
const findUow = createUOW({ config: uowConfig });
|
|
251
|
-
findUow.find(tableName
|
|
252
|
-
|
|
253
|
-
return b.whereIndex(whereConfig.indexName as never, whereConfig.condition as never);
|
|
254
|
-
}
|
|
255
|
-
return b.whereIndex(whereConfig.indexName as never);
|
|
256
|
-
});
|
|
257
|
-
const findResults2 = await findUow.executeRetrieve();
|
|
258
|
-
const records = (findResults2 as unknown as [unknown])[0];
|
|
263
|
+
findUow.find(tableName, builderFn);
|
|
264
|
+
const [records]: unknown[][] = await findUow.executeRetrieve();
|
|
259
265
|
|
|
260
|
-
// @ts-expect-error - Type narrowing doesn't work through unknown cast
|
|
261
266
|
if (!records || records.length === 0) {
|
|
262
267
|
return;
|
|
263
268
|
}
|
|
264
269
|
|
|
265
270
|
const deleteUow = createUOW({ config: uowConfig });
|
|
266
|
-
for (const record of records as
|
|
267
|
-
deleteUow.delete(tableName
|
|
271
|
+
for (const record of records as Array<{ id: unknown }>) {
|
|
272
|
+
deleteUow.delete(tableName, record.id as string);
|
|
268
273
|
}
|
|
269
274
|
const { success } = await deleteUow.executeMutations();
|
|
270
275
|
if (!success) {
|
|
@@ -457,10 +457,9 @@ describe("drizzle-uow-compiler", () => {
|
|
|
457
457
|
const [batch] = compiled.mutationBatch;
|
|
458
458
|
assert(batch);
|
|
459
459
|
expect(batch.expectedAffectedRows).toBeNull();
|
|
460
|
-
// FragnoId should
|
|
461
|
-
// But since we don't have the internal ID populated in the test, it should serialize
|
|
460
|
+
// FragnoId should generate a subquery to lookup the internal ID from external ID
|
|
462
461
|
expect(batch.query.sql).toMatchInlineSnapshot(
|
|
463
|
-
`"insert into "posts" ("id", "title", "content", "userId", "viewCount", "_internalId", "_version") values ($1, $2, $3, $4, default, default, default)"`,
|
|
462
|
+
`"insert into "posts" ("id", "title", "content", "userId", "viewCount", "_internalId", "_version") values ($1, $2, $3, (select "_internalId" from "users" where "id" = $4 limit 1), default, default, default)"`,
|
|
464
463
|
);
|
|
465
464
|
});
|
|
466
465
|
|
|
@@ -1303,5 +1302,66 @@ describe("drizzle-uow-compiler", () => {
|
|
|
1303
1302
|
);
|
|
1304
1303
|
expect(compiled.retrievalBatch[0].params).toEqual([1, sessionId]);
|
|
1305
1304
|
});
|
|
1305
|
+
|
|
1306
|
+
it("should support creating and using ID in same UOW", () => {
|
|
1307
|
+
const uow = createAuthUOW("create-user-and-session");
|
|
1308
|
+
|
|
1309
|
+
// Create user and capture the returned ID
|
|
1310
|
+
const userId = uow.create("user", {
|
|
1311
|
+
email: "test@example.com",
|
|
1312
|
+
passwordHash: "hashed_password",
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
// Use the returned FragnoId directly to create a session
|
|
1316
|
+
// The compiler should extract externalId and generate a subquery
|
|
1317
|
+
uow.create("session", {
|
|
1318
|
+
userId: userId,
|
|
1319
|
+
expiresAt: new Date("2025-12-31"),
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
const compiler = createDrizzleUOWCompiler(authSchema, authPool, "postgresql");
|
|
1323
|
+
const compiled = uow.compile(compiler);
|
|
1324
|
+
|
|
1325
|
+
// Should have no retrieval operations
|
|
1326
|
+
expect(compiled.retrievalBatch).toHaveLength(0);
|
|
1327
|
+
|
|
1328
|
+
// Should have 2 mutation operations (create user, create session)
|
|
1329
|
+
expect(compiled.mutationBatch).toHaveLength(2);
|
|
1330
|
+
|
|
1331
|
+
const [userCreate, sessionCreate] = compiled.mutationBatch;
|
|
1332
|
+
assert(userCreate);
|
|
1333
|
+
assert(sessionCreate);
|
|
1334
|
+
|
|
1335
|
+
// Verify user create SQL
|
|
1336
|
+
expect(userCreate.query.sql).toMatchInlineSnapshot(
|
|
1337
|
+
`"insert into "user" ("id", "email", "passwordHash", "createdAt", "_internalId", "_version") values ($1, $2, $3, $4, default, default)"`,
|
|
1338
|
+
);
|
|
1339
|
+
expect(userCreate.query.params).toMatchObject([
|
|
1340
|
+
userId.externalId, // The generated ID
|
|
1341
|
+
"test@example.com",
|
|
1342
|
+
"hashed_password",
|
|
1343
|
+
expect.any(String), // timestamp
|
|
1344
|
+
]);
|
|
1345
|
+
expect(userCreate.expectedAffectedRows).toBeNull();
|
|
1346
|
+
|
|
1347
|
+
// Verify session create SQL - FragnoId generates subquery to lookup internal ID
|
|
1348
|
+
expect(sessionCreate.query.sql).toMatchInlineSnapshot(
|
|
1349
|
+
`"insert into "session" ("id", "userId", "expiresAt", "createdAt", "_internalId", "_version") values ($1, (select "_internalId" from "user" where "id" = $2 limit 1), $3, $4, default, default)"`,
|
|
1350
|
+
);
|
|
1351
|
+
expect(sessionCreate.query.params).toMatchObject([
|
|
1352
|
+
expect.any(String), // generated session ID
|
|
1353
|
+
userId.externalId, // FragnoId's externalId is used in the subquery
|
|
1354
|
+
expect.any(String), // expiresAt timestamp
|
|
1355
|
+
expect.any(String), // createdAt timestamp
|
|
1356
|
+
]);
|
|
1357
|
+
expect(sessionCreate.expectedAffectedRows).toBeNull();
|
|
1358
|
+
|
|
1359
|
+
// Verify the returned FragnoId has the expected structure
|
|
1360
|
+
expect(userId).toMatchObject({
|
|
1361
|
+
externalId: expect.any(String),
|
|
1362
|
+
version: 0,
|
|
1363
|
+
internalId: undefined,
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1306
1366
|
});
|
|
1307
1367
|
});
|
|
@@ -784,4 +784,92 @@ describe("KyselyAdapter PGLite", () => {
|
|
|
784
784
|
age: 60,
|
|
785
785
|
});
|
|
786
786
|
});
|
|
787
|
+
|
|
788
|
+
it("should handle timestamps and timezones correctly", async () => {
|
|
789
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
790
|
+
|
|
791
|
+
// Create a user
|
|
792
|
+
const userId = await queryEngine.create("users", {
|
|
793
|
+
name: "Timestamp Test User",
|
|
794
|
+
age: 28,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Create a post
|
|
798
|
+
const postId = await queryEngine.create("posts", {
|
|
799
|
+
user_id: userId,
|
|
800
|
+
title: "Timestamp Test Post",
|
|
801
|
+
content: "Testing timestamp handling",
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// Retrieve the post
|
|
805
|
+
const post = await queryEngine.findFirst("posts", (b) =>
|
|
806
|
+
b.whereIndex("primary", (eb) => eb("id", "=", postId)),
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
expect(post).toBeDefined();
|
|
810
|
+
|
|
811
|
+
// Test with a table that doesn't have timestamps
|
|
812
|
+
// Verify that Date handling works in general by checking basic Date operations
|
|
813
|
+
const now = new Date();
|
|
814
|
+
expect(now).toBeInstanceOf(Date);
|
|
815
|
+
expect(typeof now.getTime).toBe("function");
|
|
816
|
+
expect(typeof now.toISOString).toBe("function");
|
|
817
|
+
|
|
818
|
+
// Verify date serialization/deserialization works
|
|
819
|
+
const isoString = now.toISOString();
|
|
820
|
+
expect(typeof isoString).toBe("string");
|
|
821
|
+
expect(new Date(isoString).getTime()).toBe(now.getTime());
|
|
822
|
+
|
|
823
|
+
// Test timezone preservation
|
|
824
|
+
const specificDate = new Date("2024-06-15T14:30:00Z");
|
|
825
|
+
expect(specificDate.toISOString()).toBe("2024-06-15T14:30:00.000Z");
|
|
826
|
+
|
|
827
|
+
// Verify that dates from different timezones are handled correctly
|
|
828
|
+
const localDate = new Date("2024-06-15T14:30:00");
|
|
829
|
+
expect(localDate).toBeInstanceOf(Date);
|
|
830
|
+
expect(typeof localDate.getTimezoneOffset()).toBe("number");
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it("should create user and post in same transaction using returned ID", async () => {
|
|
834
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "test");
|
|
835
|
+
|
|
836
|
+
// Create UOW and create both user and post in same transaction
|
|
837
|
+
const uow = queryEngine.createUnitOfWork("create-user-and-post");
|
|
838
|
+
|
|
839
|
+
// Create user and capture the returned ID
|
|
840
|
+
const userId = uow.create("users", {
|
|
841
|
+
name: "UOW Test User",
|
|
842
|
+
age: 35,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// Use the returned FragnoId directly to create a post in the same transaction
|
|
846
|
+
// The compiler will extract externalId and generate a subquery to lookup the internal ID
|
|
847
|
+
const postId = uow.create("posts", {
|
|
848
|
+
user_id: userId,
|
|
849
|
+
title: "UOW Test Post",
|
|
850
|
+
content: "This post was created in the same transaction as the user",
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Execute all mutations in a single transaction
|
|
854
|
+
const { success } = await uow.executeMutations();
|
|
855
|
+
expect(success).toBe(true);
|
|
856
|
+
|
|
857
|
+
// Verify both records were created
|
|
858
|
+
const user = await queryEngine.findFirst("users", (b) =>
|
|
859
|
+
b.whereIndex("primary", (eb) => eb("id", "=", userId)),
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
expect(user?.name).toBe("UOW Test User");
|
|
863
|
+
expect(user?.age).toBe(35);
|
|
864
|
+
|
|
865
|
+
const post = await queryEngine.findFirst("posts", (b) =>
|
|
866
|
+
b.whereIndex("primary", (eb) => eb("id", "=", postId.externalId)),
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
expect(post?.title).toBe("UOW Test Post");
|
|
870
|
+
expect(post?.content).toBe("This post was created in the same transaction as the user");
|
|
871
|
+
|
|
872
|
+
// Verify the foreign key relationship is correct
|
|
873
|
+
expect(post?.user_id.internalId).toBe(user?.id.internalId);
|
|
874
|
+
});
|
|
787
875
|
});
|
|
@@ -10,7 +10,7 @@ import type { AnySchema } from "../../schema/create";
|
|
|
10
10
|
import type { CustomOperation, MigrationOperation } from "../../migration-engine/shared";
|
|
11
11
|
import { execute, preprocessOperations } from "./migration/execute";
|
|
12
12
|
import type { AbstractQuery } from "../../query/query";
|
|
13
|
-
import { fromKysely } from "./kysely-query";
|
|
13
|
+
import { fromKysely, type KyselyUOWConfig } from "./kysely-query";
|
|
14
14
|
import { createTableNameMapper } from "./kysely-shared";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { SETTINGS_TABLE_NAME } from "../../shared/settings-schema";
|
|
@@ -25,7 +25,7 @@ export interface KyselyConfig {
|
|
|
25
25
|
provider: SQLProvider;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export class KyselyAdapter implements DatabaseAdapter {
|
|
28
|
+
export class KyselyAdapter implements DatabaseAdapter<KyselyUOWConfig> {
|
|
29
29
|
#connectionPool: ConnectionPool<KyselyAny>;
|
|
30
30
|
#provider: SQLProvider;
|
|
31
31
|
|
|
@@ -46,7 +46,10 @@ export class KyselyAdapter implements DatabaseAdapter {
|
|
|
46
46
|
await this.#connectionPool.close();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
createQueryEngine<T extends AnySchema>(
|
|
49
|
+
createQueryEngine<T extends AnySchema>(
|
|
50
|
+
schema: T,
|
|
51
|
+
namespace: string,
|
|
52
|
+
): AbstractQuery<T, KyselyUOWConfig> {
|
|
50
53
|
// Only create mapper if namespace is non-empty
|
|
51
54
|
const mapper = namespace ? createTableNameMapper(namespace) : undefined;
|
|
52
55
|
return fromKysely(schema, this.#connectionPool, this.#provider, mapper);
|