@fragno-dev/db 0.1.11 → 0.1.13
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 +41 -39
- package/CHANGELOG.md +19 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +42 -34
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/generate.js +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +4 -3
- 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 +101 -51
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
- package/dist/adapters/kysely/kysely-uow-compiler.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/adapters/kysely/migration/execute-base.js +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 +7 -6
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +2 -1
- package/dist/mod.js.map +1 -1
- package/dist/query/cursor.d.ts +67 -32
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +84 -31
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/query.d.ts +29 -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 +19 -8
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +54 -12
- 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 +3 -3
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +242 -55
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +95 -39
- package/src/adapters/drizzle/drizzle-query.test.ts +54 -4
- package/src/adapters/drizzle/drizzle-query.ts +74 -60
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +82 -6
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +190 -4
- 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 +187 -83
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +85 -3
- package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
- package/src/adapters/kysely/kysely-uow-executor.ts +5 -9
- package/src/migration-engine/generation-engine.ts +2 -1
- package/src/mod.ts +12 -7
- package/src/query/cursor.test.ts +113 -68
- package/src/query/cursor.ts +127 -36
- package/src/query/query-type.test.ts +34 -14
- package/src/query/query.ts +94 -34
- package/src/query/result-transform.test.ts +5 -5
- package/src/query/result-transform.ts +29 -11
- package/src/query/unit-of-work.ts +141 -26
- package/src/schema/serialize.test.ts +223 -0
- package/src/schema/serialize.ts +16 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/pglite";
|
|
2
2
|
import { DrizzleAdapter } from "./drizzle-adapter";
|
|
3
|
-
import { beforeAll, describe, expect, expectTypeOf, it } from "vitest";
|
|
3
|
+
import { assert, beforeAll, describe, expect, expectTypeOf, it } from "vitest";
|
|
4
4
|
import { column, idColumn, referenceColumn, schema } from "../../schema/create";
|
|
5
5
|
import type { DBType } from "./shared";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
-
import {
|
|
7
|
+
import { Cursor } from "../../query/cursor";
|
|
8
8
|
import type { DrizzleCompiledQuery } from "./drizzle-uow-compiler";
|
|
9
9
|
import { writeAndLoadSchema } from "./test-utils";
|
|
10
10
|
|
|
@@ -38,6 +38,10 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
38
38
|
.addColumn("user_id", referenceColumn())
|
|
39
39
|
.addColumn("title", column("string"))
|
|
40
40
|
.addColumn("content", column("string"))
|
|
41
|
+
.addColumn(
|
|
42
|
+
"created_at",
|
|
43
|
+
column("timestamp").defaultTo((b) => b.now()),
|
|
44
|
+
)
|
|
41
45
|
.createIndex("posts_user_idx", ["user_id"]);
|
|
42
46
|
})
|
|
43
47
|
.addTable("comments", (t) => {
|
|
@@ -116,7 +120,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
116
120
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
117
121
|
|
|
118
122
|
// Create initial user using UOW
|
|
119
|
-
const createUow = queryEngine.createUnitOfWork("create-user")
|
|
123
|
+
const createUow = queryEngine.createUnitOfWork("create-user");
|
|
124
|
+
createUow.create("users", {
|
|
120
125
|
name: "Alice",
|
|
121
126
|
age: 25,
|
|
122
127
|
});
|
|
@@ -201,12 +206,11 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
201
206
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
202
207
|
|
|
203
208
|
// Create some users
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
.executeMutations();
|
|
209
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
210
|
+
createUow.create("users", { name: "User1", age: 20 });
|
|
211
|
+
createUow.create("users", { name: "User2", age: 30 });
|
|
212
|
+
createUow.create("users", { name: "User3", age: 40 });
|
|
213
|
+
await createUow.executeMutations();
|
|
210
214
|
|
|
211
215
|
// Count all users
|
|
212
216
|
const [totalCount] = await queryEngine
|
|
@@ -221,13 +225,12 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
221
225
|
it("should support cursor-based pagination", async () => {
|
|
222
226
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
223
227
|
|
|
224
|
-
const createUow = queryEngine
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
.create("users", { name: "Page User E", age: 60 });
|
|
228
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
229
|
+
createUow.create("users", { name: "Page User A", age: 20 });
|
|
230
|
+
createUow.create("users", { name: "Page User B", age: 30 });
|
|
231
|
+
createUow.create("users", { name: "Page User C", age: 40 });
|
|
232
|
+
createUow.create("users", { name: "Page User D", age: 50 });
|
|
233
|
+
createUow.create("users", { name: "Page User E", age: 60 });
|
|
231
234
|
|
|
232
235
|
await createUow.executeMutations();
|
|
233
236
|
|
|
@@ -242,10 +245,12 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
242
245
|
|
|
243
246
|
// Create cursor from last item of first page
|
|
244
247
|
const lastItem = firstPage[firstPage.length - 1]!;
|
|
245
|
-
const cursor =
|
|
248
|
+
const cursor = new Cursor({
|
|
249
|
+
indexName: "name_idx",
|
|
250
|
+
orderDirection: "asc",
|
|
251
|
+
pageSize: 2,
|
|
246
252
|
indexValues: { name: lastItem.name },
|
|
247
|
-
|
|
248
|
-
});
|
|
253
|
+
}).encode();
|
|
249
254
|
|
|
250
255
|
// Fetch next page using cursor
|
|
251
256
|
const [secondPage] = await queryEngine
|
|
@@ -269,9 +274,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
269
274
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
270
275
|
const queries: DrizzleCompiledQuery[] = [];
|
|
271
276
|
|
|
272
|
-
const createUow = queryEngine
|
|
273
|
-
|
|
274
|
-
.create("users", { name: "Email User", age: 20 });
|
|
277
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
278
|
+
createUow.create("users", { name: "Email User", age: 20 });
|
|
275
279
|
|
|
276
280
|
await createUow.executeMutations();
|
|
277
281
|
|
|
@@ -282,7 +286,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
282
286
|
.executeRetrieve();
|
|
283
287
|
|
|
284
288
|
// Create an email for testing joins
|
|
285
|
-
const createEmailUow = queryEngine.createUnitOfWork("create-test-email")
|
|
289
|
+
const createEmailUow = queryEngine.createUnitOfWork("create-test-email");
|
|
290
|
+
createEmailUow.create("emails", {
|
|
286
291
|
user_id: existingUser.id,
|
|
287
292
|
email: "test@example.com",
|
|
288
293
|
is_primary: true,
|
|
@@ -330,9 +335,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
330
335
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
331
336
|
|
|
332
337
|
// Create a user first
|
|
333
|
-
const createUserUow = queryEngine
|
|
334
|
-
|
|
335
|
-
.create("users", { name: "External ID Test User", age: 35 });
|
|
338
|
+
const createUserUow = queryEngine.createUnitOfWork("create-user-for-external-id");
|
|
339
|
+
createUserUow.create("users", { name: "External ID Test User", age: 35 });
|
|
336
340
|
await createUserUow.executeMutations();
|
|
337
341
|
|
|
338
342
|
// Get the user
|
|
@@ -344,13 +348,12 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
344
348
|
.executeRetrieve();
|
|
345
349
|
|
|
346
350
|
// Create an email using just the external id string (not the full id object)
|
|
347
|
-
const createEmailUow = queryEngine
|
|
348
|
-
|
|
349
|
-
.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
});
|
|
351
|
+
const createEmailUow = queryEngine.createUnitOfWork("create-email-with-external-id");
|
|
352
|
+
createEmailUow.create("emails", {
|
|
353
|
+
user_id: user.id.externalId,
|
|
354
|
+
email: "external-id-test@example.com",
|
|
355
|
+
is_primary: false,
|
|
356
|
+
});
|
|
354
357
|
|
|
355
358
|
const { success } = await createEmailUow.executeMutations();
|
|
356
359
|
expect(success).toBe(true);
|
|
@@ -385,9 +388,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
385
388
|
const queries: DrizzleCompiledQuery[] = [];
|
|
386
389
|
|
|
387
390
|
// Create a user (author)
|
|
388
|
-
const createAuthorUow = queryEngine
|
|
389
|
-
|
|
390
|
-
.create("users", { name: "Blog Author", age: 30 });
|
|
391
|
+
const createAuthorUow = queryEngine.createUnitOfWork("create-author");
|
|
392
|
+
createAuthorUow.create("users", { name: "Blog Author", age: 30 });
|
|
391
393
|
await createAuthorUow.executeMutations();
|
|
392
394
|
|
|
393
395
|
// Get the author
|
|
@@ -397,7 +399,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
397
399
|
.executeRetrieve();
|
|
398
400
|
|
|
399
401
|
// Create a post by the author
|
|
400
|
-
const createPostUow = queryEngine.createUnitOfWork("create-post")
|
|
402
|
+
const createPostUow = queryEngine.createUnitOfWork("create-post");
|
|
403
|
+
createPostUow.create("posts", {
|
|
401
404
|
user_id: author.id,
|
|
402
405
|
title: "My First Post",
|
|
403
406
|
content: "This is the content of my first post",
|
|
@@ -408,9 +411,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
408
411
|
const [[post]] = await queryEngine.createUnitOfWork("get-post").find("posts").executeRetrieve();
|
|
409
412
|
|
|
410
413
|
// Create a commenter
|
|
411
|
-
const createCommenterUow = queryEngine
|
|
412
|
-
|
|
413
|
-
.create("users", { name: "Commenter User", age: 25 });
|
|
414
|
+
const createCommenterUow = queryEngine.createUnitOfWork("create-commenter");
|
|
415
|
+
createCommenterUow.create("users", { name: "Commenter User", age: 25 });
|
|
414
416
|
await createCommenterUow.executeMutations();
|
|
415
417
|
|
|
416
418
|
// Get the commenter
|
|
@@ -420,7 +422,8 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
420
422
|
.executeRetrieve();
|
|
421
423
|
|
|
422
424
|
// Create a comment on the post
|
|
423
|
-
const createCommentUow = queryEngine.createUnitOfWork("create-comment")
|
|
425
|
+
const createCommentUow = queryEngine.createUnitOfWork("create-comment");
|
|
426
|
+
createCommentUow.create("comments", {
|
|
424
427
|
post_id: post.id,
|
|
425
428
|
user_id: commenter.id,
|
|
426
429
|
text: "Great post!",
|
|
@@ -564,24 +567,22 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
564
567
|
const queryEngine = adapter.createQueryEngine(authSchema, "test-namespace");
|
|
565
568
|
|
|
566
569
|
// Create a user
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
.executeMutations();
|
|
570
|
+
const createUserUow = queryEngine.createUnitOfWork("create-user");
|
|
571
|
+
createUserUow.create("user", {
|
|
572
|
+
email: "test@example.com",
|
|
573
|
+
passwordHash: "hash",
|
|
574
|
+
});
|
|
575
|
+
await createUserUow.executeMutations();
|
|
574
576
|
|
|
575
577
|
// Create a session
|
|
576
578
|
const [[user]] = await queryEngine.createUnitOfWork("get-user").find("user").executeRetrieve();
|
|
577
579
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
.executeMutations();
|
|
580
|
+
const createSessionUow = queryEngine.createUnitOfWork("create-session");
|
|
581
|
+
createSessionUow.create("session", {
|
|
582
|
+
userId: user.id,
|
|
583
|
+
expiresAt: new Date("2025-12-31"),
|
|
584
|
+
});
|
|
585
|
+
await createSessionUow.executeMutations();
|
|
585
586
|
|
|
586
587
|
// Get session
|
|
587
588
|
const [[session]] = await queryEngine
|
|
@@ -609,4 +610,190 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
609
610
|
|
|
610
611
|
await cleanup();
|
|
611
612
|
});
|
|
613
|
+
|
|
614
|
+
it("should handle timestamps and timezones correctly", async () => {
|
|
615
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
616
|
+
|
|
617
|
+
// Create a user
|
|
618
|
+
const createUserUow = queryEngine.createUnitOfWork("create-user-for-timestamp");
|
|
619
|
+
createUserUow.create("users", { name: "Timestamp User", age: 28 });
|
|
620
|
+
await createUserUow.executeMutations();
|
|
621
|
+
|
|
622
|
+
const [[user]] = await queryEngine
|
|
623
|
+
.createUnitOfWork("get-user-for-timestamp")
|
|
624
|
+
.find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Timestamp User")))
|
|
625
|
+
.executeRetrieve();
|
|
626
|
+
|
|
627
|
+
// Create a post with database-generated timestamp (defaultTo(b => b.now()))
|
|
628
|
+
const createPostUow = queryEngine.createUnitOfWork("create-post-with-timestamp");
|
|
629
|
+
createPostUow.create("posts", {
|
|
630
|
+
user_id: user.id,
|
|
631
|
+
title: "Timestamp Test Post",
|
|
632
|
+
content: "Testing timestamp handling",
|
|
633
|
+
});
|
|
634
|
+
await createPostUow.executeMutations();
|
|
635
|
+
|
|
636
|
+
const createdPostIds = createPostUow.getCreatedIds();
|
|
637
|
+
expect(createdPostIds).toHaveLength(1);
|
|
638
|
+
const postId = createdPostIds[0];
|
|
639
|
+
|
|
640
|
+
// Retrieve the specific post we just created by its ID
|
|
641
|
+
const [[post]] = await queryEngine
|
|
642
|
+
.createUnitOfWork("get-post-with-timestamp")
|
|
643
|
+
.find("posts", (b) => b.whereIndex("primary", (eb) => eb("id", "=", postId)))
|
|
644
|
+
.executeRetrieve();
|
|
645
|
+
|
|
646
|
+
// Verify created_at is a Date
|
|
647
|
+
expect(post.created_at).toBeInstanceOf(Date);
|
|
648
|
+
|
|
649
|
+
// Verify the timestamp is a valid date (not too far in the past or future)
|
|
650
|
+
const now = Date.now();
|
|
651
|
+
const createdTime = post.created_at.getTime();
|
|
652
|
+
expect(createdTime).toBeGreaterThan(now - 24 * 60 * 60 * 1000); // Within last 24 hours
|
|
653
|
+
expect(createdTime).toBeLessThan(now + 24 * 60 * 60 * 1000); // Not more than 24 hours in future
|
|
654
|
+
|
|
655
|
+
// Verify we can compare timestamps
|
|
656
|
+
expect(post.created_at.getTime()).toBeGreaterThan(0);
|
|
657
|
+
|
|
658
|
+
// Test that the Date object has the correct methods
|
|
659
|
+
expect(typeof post.created_at.toISOString).toBe("function");
|
|
660
|
+
expect(typeof post.created_at.getTime).toBe("function");
|
|
661
|
+
expect(typeof post.created_at.getTimezoneOffset).toBe("function");
|
|
662
|
+
|
|
663
|
+
// Verify the date can be serialized and deserialized
|
|
664
|
+
const isoString = post.created_at.toISOString();
|
|
665
|
+
expect(typeof isoString).toBe("string");
|
|
666
|
+
expect(new Date(isoString).getTime()).toBe(post.created_at.getTime());
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it("should create user and post in same transaction using returned ID", async () => {
|
|
670
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
671
|
+
const queries: DrizzleCompiledQuery[] = [];
|
|
672
|
+
|
|
673
|
+
// Create UOW and create both user and post in same transaction
|
|
674
|
+
const uow = queryEngine.createUnitOfWork("create-user-and-post", {
|
|
675
|
+
onQuery: (query) => queries.push(query),
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Create user and capture the returned ID
|
|
679
|
+
const userId = uow.create("users", {
|
|
680
|
+
name: "UOW Test User",
|
|
681
|
+
age: 35,
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Use the returned FragnoId directly to create a post in the same transaction
|
|
685
|
+
// The compiler will extract externalId and generate a subquery to lookup the internal ID
|
|
686
|
+
const postId = uow.create("posts", {
|
|
687
|
+
user_id: userId,
|
|
688
|
+
title: "UOW Test Post",
|
|
689
|
+
content: "This post was created in the same transaction as the user",
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// Execute all mutations in a single transaction
|
|
693
|
+
const { success } = await uow.executeMutations();
|
|
694
|
+
expect(success).toBe(true);
|
|
695
|
+
|
|
696
|
+
// Verify both records were created
|
|
697
|
+
const userIdStr = userId.toString();
|
|
698
|
+
const postIdStr = postId.toString();
|
|
699
|
+
|
|
700
|
+
const [[user]] = await queryEngine
|
|
701
|
+
.createUnitOfWork("verify-user")
|
|
702
|
+
.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userIdStr)))
|
|
703
|
+
.executeRetrieve();
|
|
704
|
+
|
|
705
|
+
expect(user.name).toBe("UOW Test User");
|
|
706
|
+
expect(user.age).toBe(35);
|
|
707
|
+
|
|
708
|
+
const [[post]] = await queryEngine
|
|
709
|
+
.createUnitOfWork("verify-post")
|
|
710
|
+
.find("posts", (b) => b.whereIndex("primary", (eb) => eb("id", "=", postIdStr)))
|
|
711
|
+
.executeRetrieve();
|
|
712
|
+
|
|
713
|
+
expect(post.title).toBe("UOW Test Post");
|
|
714
|
+
expect(post.content).toBe("This post was created in the same transaction as the user");
|
|
715
|
+
|
|
716
|
+
// Verify the foreign key relationship is correct
|
|
717
|
+
expect(post.user_id.internalId).toBe(user.id.internalId);
|
|
718
|
+
|
|
719
|
+
const [insertUserQuery, insertPostQuery] = queries;
|
|
720
|
+
expect(insertUserQuery.sql).toMatchInlineSnapshot(
|
|
721
|
+
`"insert into "users_namespace" ("id", "name", "age", "_internalId", "_version") values ($1, $2, $3, default, default)"`,
|
|
722
|
+
);
|
|
723
|
+
expect(insertUserQuery.params).toEqual([userId.externalId, "UOW Test User", 35]);
|
|
724
|
+
expect(insertPostQuery.sql).toMatchInlineSnapshot(
|
|
725
|
+
`"insert into "posts_namespace" ("id", "user_id", "title", "content", "created_at", "_internalId", "_version") values ($1, (select "_internalId" from "users_namespace" where "id" = $2 limit 1), $3, $4, default, default, default)"`,
|
|
726
|
+
);
|
|
727
|
+
expect(insertPostQuery.params).toEqual([
|
|
728
|
+
postId.externalId,
|
|
729
|
+
userId.externalId,
|
|
730
|
+
"UOW Test Post",
|
|
731
|
+
"This post was created in the same transaction as the user",
|
|
732
|
+
]);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it("should support cursor-based pagination with findWithCursor()", async () => {
|
|
736
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
737
|
+
|
|
738
|
+
// Create multiple users for pagination testing
|
|
739
|
+
for (let i = 1; i <= 25; i++) {
|
|
740
|
+
await queryEngine.create("users", {
|
|
741
|
+
name: `Cursor User ${i.toString().padStart(2, "0")}`,
|
|
742
|
+
age: 20 + i,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Fetch first page with cursor
|
|
747
|
+
const firstPage = await queryEngine.findWithCursor("users", (b) =>
|
|
748
|
+
b.whereIndex("name_idx").orderByIndex("name_idx", "asc").pageSize(10),
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
// Check structure
|
|
752
|
+
expect(firstPage).toHaveProperty("items");
|
|
753
|
+
expect(firstPage).toHaveProperty("cursor");
|
|
754
|
+
expect(Array.isArray(firstPage.items)).toBe(true);
|
|
755
|
+
expect(firstPage.items.length).toBeGreaterThan(0);
|
|
756
|
+
expect(firstPage.items.length).toBeLessThanOrEqual(10);
|
|
757
|
+
|
|
758
|
+
assert(firstPage.cursor instanceof Cursor);
|
|
759
|
+
expect(firstPage.items).toHaveLength(10);
|
|
760
|
+
expect(firstPage.cursor).toBeInstanceOf(Cursor);
|
|
761
|
+
|
|
762
|
+
// Fetch second page using cursor
|
|
763
|
+
const secondPage = await queryEngine.findWithCursor("users", (b) =>
|
|
764
|
+
b
|
|
765
|
+
.whereIndex("name_idx")
|
|
766
|
+
.after(firstPage.cursor!)
|
|
767
|
+
.orderByIndex("name_idx", "asc")
|
|
768
|
+
.pageSize(10),
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
expect(secondPage.items.length).toBeGreaterThan(0);
|
|
772
|
+
expect(secondPage.items.length).toBeLessThanOrEqual(10);
|
|
773
|
+
|
|
774
|
+
// Verify no overlap - first item of second page should come after last item of first page
|
|
775
|
+
const firstPageLastName = firstPage.items[firstPage.items.length - 1].name;
|
|
776
|
+
const secondPageFirstName = secondPage.items[0].name;
|
|
777
|
+
expect(secondPageFirstName > firstPageLastName).toBe(true);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it("should support findWithCursor() in Unit of Work", async () => {
|
|
781
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
782
|
+
|
|
783
|
+
// Use findWithCursor in UOW
|
|
784
|
+
const uow = queryEngine
|
|
785
|
+
.createUnitOfWork("cursor-test")
|
|
786
|
+
.findWithCursor("users", (b) =>
|
|
787
|
+
b.whereIndex("name_idx").orderByIndex("name_idx", "asc").pageSize(5),
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
const [result] = await uow.executeRetrieve();
|
|
791
|
+
|
|
792
|
+
// Verify result structure
|
|
793
|
+
expect(result).toHaveProperty("items");
|
|
794
|
+
expect(result).toHaveProperty("cursor");
|
|
795
|
+
expect(Array.isArray(result.items)).toBe(true);
|
|
796
|
+
expect(result.items).toHaveLength(5);
|
|
797
|
+
expect(result.cursor).toBeInstanceOf(Cursor);
|
|
798
|
+
});
|
|
612
799
|
});
|
|
@@ -6,7 +6,7 @@ import { column, idColumn, referenceColumn, schema } from "../../schema/create";
|
|
|
6
6
|
import type { DBType } from "./shared";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { writeAndLoadSchema } from "./test-utils";
|
|
9
|
-
import {
|
|
9
|
+
import { Cursor } from "../../query/cursor";
|
|
10
10
|
|
|
11
11
|
// Import drizzle-kit for migrations
|
|
12
12
|
const require = createRequire(import.meta.url);
|
|
@@ -118,16 +118,15 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
118
118
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
119
119
|
|
|
120
120
|
// Create two users at once using UOW
|
|
121
|
-
const createUow = queryEngine
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
121
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
122
|
+
createUow.create("users", {
|
|
123
|
+
name: "Alice",
|
|
124
|
+
age: 25,
|
|
125
|
+
});
|
|
126
|
+
createUow.create("users", {
|
|
127
|
+
name: "Bob",
|
|
128
|
+
age: 30,
|
|
129
|
+
});
|
|
131
130
|
|
|
132
131
|
expectTypeOf(createUow.find).parameter(0).toEqualTypeOf<keyof typeof testSchema.tables>();
|
|
133
132
|
|
|
@@ -232,12 +231,11 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
232
231
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
233
232
|
|
|
234
233
|
// Create some users
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
.executeMutations();
|
|
234
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
235
|
+
createUow.create("users", { name: "User1", age: 20 });
|
|
236
|
+
createUow.create("users", { name: "User2", age: 30 });
|
|
237
|
+
createUow.create("users", { name: "User3", age: 40 });
|
|
238
|
+
await createUow.executeMutations();
|
|
241
239
|
|
|
242
240
|
// Count all users
|
|
243
241
|
const [totalCount] = await queryEngine
|
|
@@ -253,13 +251,12 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
253
251
|
it("should support cursor-based pagination", async () => {
|
|
254
252
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
255
253
|
|
|
256
|
-
const createUow = queryEngine
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.create("users", { name: "Page User E", age: 60 });
|
|
254
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
255
|
+
createUow.create("users", { name: "Page User A", age: 20 });
|
|
256
|
+
createUow.create("users", { name: "Page User B", age: 30 });
|
|
257
|
+
createUow.create("users", { name: "Page User C", age: 40 });
|
|
258
|
+
createUow.create("users", { name: "Page User D", age: 50 });
|
|
259
|
+
createUow.create("users", { name: "Page User E", age: 60 });
|
|
263
260
|
|
|
264
261
|
await createUow.executeMutations();
|
|
265
262
|
|
|
@@ -275,10 +272,12 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
275
272
|
|
|
276
273
|
// Create cursor from last item of first page
|
|
277
274
|
const lastItem = firstPage[firstPage.length - 1]!;
|
|
278
|
-
const cursor =
|
|
275
|
+
const cursor = new Cursor({
|
|
276
|
+
indexName: "name_idx",
|
|
277
|
+
orderDirection: "asc",
|
|
278
|
+
pageSize: 2,
|
|
279
279
|
indexValues: { name: lastItem.name },
|
|
280
|
-
|
|
281
|
-
});
|
|
280
|
+
}).encode();
|
|
282
281
|
|
|
283
282
|
// Fetch next page using cursor
|
|
284
283
|
const [secondPage] = await queryEngine
|
|
@@ -302,9 +301,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
302
301
|
it("should support joins", async () => {
|
|
303
302
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
304
303
|
|
|
305
|
-
const createUow = queryEngine
|
|
306
|
-
|
|
307
|
-
.create("users", { name: "Email User", age: 20 });
|
|
304
|
+
const createUow = queryEngine.createUnitOfWork("create-users");
|
|
305
|
+
createUow.create("users", { name: "Email User", age: 20 });
|
|
308
306
|
|
|
309
307
|
const { success } = await createUow.executeMutations();
|
|
310
308
|
expect(success).toBe(true);
|
|
@@ -321,7 +319,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
321
319
|
expect(createdUser.name).toBe("Email User");
|
|
322
320
|
|
|
323
321
|
// Create an email for testing joins
|
|
324
|
-
const createEmailUow = queryEngine.createUnitOfWork("create-test-email")
|
|
322
|
+
const createEmailUow = queryEngine.createUnitOfWork("create-test-email");
|
|
323
|
+
createEmailUow.create("emails", {
|
|
325
324
|
user_id: createdUser.id,
|
|
326
325
|
email: "test@example.com",
|
|
327
326
|
is_primary: true,
|
|
@@ -364,9 +363,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
364
363
|
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
365
364
|
|
|
366
365
|
// Create a user (author)
|
|
367
|
-
const createAuthorUow = queryEngine
|
|
368
|
-
|
|
369
|
-
.create("users", { name: "Blog Author", age: 30 });
|
|
366
|
+
const createAuthorUow = queryEngine.createUnitOfWork("create-author");
|
|
367
|
+
createAuthorUow.create("users", { name: "Blog Author", age: 30 });
|
|
370
368
|
await createAuthorUow.executeMutations();
|
|
371
369
|
|
|
372
370
|
// Fetch the created author to get the proper ID
|
|
@@ -376,7 +374,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
376
374
|
.executeRetrieve();
|
|
377
375
|
|
|
378
376
|
// Create a post by the author
|
|
379
|
-
const createPostUow = queryEngine.createUnitOfWork("create-post")
|
|
377
|
+
const createPostUow = queryEngine.createUnitOfWork("create-post");
|
|
378
|
+
createPostUow.create("posts", {
|
|
380
379
|
user_id: author.id,
|
|
381
380
|
title: "My First Post",
|
|
382
381
|
content: "This is the content of my first post",
|
|
@@ -390,9 +389,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
390
389
|
.executeRetrieve();
|
|
391
390
|
|
|
392
391
|
// Create a commenter
|
|
393
|
-
const createCommenterUow = queryEngine
|
|
394
|
-
|
|
395
|
-
.create("users", { name: "Commenter User", age: 25 });
|
|
392
|
+
const createCommenterUow = queryEngine.createUnitOfWork("create-commenter");
|
|
393
|
+
createCommenterUow.create("users", { name: "Commenter User", age: 25 });
|
|
396
394
|
await createCommenterUow.executeMutations();
|
|
397
395
|
|
|
398
396
|
// Fetch the created commenter to get the proper ID
|
|
@@ -402,7 +400,8 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
402
400
|
.executeRetrieve();
|
|
403
401
|
|
|
404
402
|
// Create a comment on the post
|
|
405
|
-
const createCommentUow = queryEngine.createUnitOfWork("create-comment")
|
|
403
|
+
const createCommentUow = queryEngine.createUnitOfWork("create-comment");
|
|
404
|
+
createCommentUow.create("comments", {
|
|
406
405
|
post_id: post.id,
|
|
407
406
|
user_id: commenter.id,
|
|
408
407
|
text: "Great post!",
|
|
@@ -580,4 +579,61 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
580
579
|
age: 60,
|
|
581
580
|
});
|
|
582
581
|
});
|
|
582
|
+
|
|
583
|
+
it("should handle timestamps and timezones correctly", async () => {
|
|
584
|
+
const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
|
|
585
|
+
|
|
586
|
+
// Create a user
|
|
587
|
+
const createUserUow = queryEngine.createUnitOfWork("create-user-for-timestamp");
|
|
588
|
+
createUserUow.create("users", { name: "Timestamp User", age: 28 });
|
|
589
|
+
await createUserUow.executeMutations();
|
|
590
|
+
|
|
591
|
+
const [[user]] = await queryEngine
|
|
592
|
+
.createUnitOfWork("get-user-for-timestamp")
|
|
593
|
+
.find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Timestamp User")))
|
|
594
|
+
.executeRetrieve();
|
|
595
|
+
|
|
596
|
+
// Create a post (note: SQLite schema doesn't have created_at column)
|
|
597
|
+
const createPostUow = queryEngine.createUnitOfWork("create-post-for-timestamp");
|
|
598
|
+
createPostUow.create("posts", {
|
|
599
|
+
user_id: user.id,
|
|
600
|
+
title: "Timestamp Test Post",
|
|
601
|
+
content: "Testing timestamp handling",
|
|
602
|
+
});
|
|
603
|
+
await createPostUow.executeMutations();
|
|
604
|
+
|
|
605
|
+
// Retrieve the post
|
|
606
|
+
const [[post]] = await queryEngine
|
|
607
|
+
.createUnitOfWork("get-post-for-timestamp")
|
|
608
|
+
.find("posts", (b) => b.whereIndex("posts_user_idx", (eb) => eb("user_id", "=", user.id)))
|
|
609
|
+
.executeRetrieve();
|
|
610
|
+
|
|
611
|
+
expect(post).toBeDefined();
|
|
612
|
+
expect(post.title).toBe("Timestamp Test Post");
|
|
613
|
+
|
|
614
|
+
// Test general Date handling (SQLite stores timestamps as integers)
|
|
615
|
+
const now = new Date();
|
|
616
|
+
expect(now).toBeInstanceOf(Date);
|
|
617
|
+
expect(typeof now.getTime).toBe("function");
|
|
618
|
+
expect(typeof now.toISOString).toBe("function");
|
|
619
|
+
|
|
620
|
+
// Verify date serialization/deserialization works
|
|
621
|
+
const isoString = now.toISOString();
|
|
622
|
+
expect(typeof isoString).toBe("string");
|
|
623
|
+
expect(new Date(isoString).getTime()).toBe(now.getTime());
|
|
624
|
+
|
|
625
|
+
// Test timezone preservation
|
|
626
|
+
const specificDate = new Date("2024-06-15T14:30:00Z");
|
|
627
|
+
expect(specificDate.toISOString()).toBe("2024-06-15T14:30:00.000Z");
|
|
628
|
+
|
|
629
|
+
// Verify SQLite numeric timestamp conversion
|
|
630
|
+
const timestamp = Date.now();
|
|
631
|
+
const dateFromTimestamp = new Date(timestamp);
|
|
632
|
+
expect(dateFromTimestamp.getTime()).toBe(timestamp);
|
|
633
|
+
|
|
634
|
+
// Verify that dates from different timezones are handled correctly
|
|
635
|
+
const localDate = new Date("2024-06-15T14:30:00");
|
|
636
|
+
expect(localDate).toBeInstanceOf(Date);
|
|
637
|
+
expect(typeof localDate.getTimezoneOffset()).toBe("number");
|
|
638
|
+
});
|
|
583
639
|
});
|