@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.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +41 -39
  2. package/CHANGELOG.md +19 -0
  3. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  4. package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
  5. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-query.js +42 -34
  7. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
  9. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
  11. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  12. package/dist/adapters/drizzle/generate.js +1 -1
  13. package/dist/adapters/kysely/kysely-adapter.d.ts +4 -3
  14. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  15. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  16. package/dist/adapters/kysely/kysely-query.d.ts +22 -0
  17. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -0
  18. package/dist/adapters/kysely/kysely-query.js +101 -51
  19. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  20. package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
  21. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  22. package/dist/adapters/kysely/kysely-uow-executor.js +2 -2
  23. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  24. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  25. package/dist/migration-engine/generation-engine.d.ts +1 -1
  26. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  27. package/dist/migration-engine/generation-engine.js.map +1 -1
  28. package/dist/mod.d.ts +7 -6
  29. package/dist/mod.d.ts.map +1 -1
  30. package/dist/mod.js +2 -1
  31. package/dist/mod.js.map +1 -1
  32. package/dist/query/cursor.d.ts +67 -32
  33. package/dist/query/cursor.d.ts.map +1 -1
  34. package/dist/query/cursor.js +84 -31
  35. package/dist/query/cursor.js.map +1 -1
  36. package/dist/query/query.d.ts +29 -8
  37. package/dist/query/query.d.ts.map +1 -1
  38. package/dist/query/result-transform.js +17 -5
  39. package/dist/query/result-transform.js.map +1 -1
  40. package/dist/query/unit-of-work.d.ts +19 -8
  41. package/dist/query/unit-of-work.d.ts.map +1 -1
  42. package/dist/query/unit-of-work.js +54 -12
  43. package/dist/query/unit-of-work.js.map +1 -1
  44. package/dist/schema/serialize.js +2 -0
  45. package/dist/schema/serialize.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +242 -55
  48. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +95 -39
  49. package/src/adapters/drizzle/drizzle-query.test.ts +54 -4
  50. package/src/adapters/drizzle/drizzle-query.ts +74 -60
  51. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +82 -6
  52. package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
  53. package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
  54. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +190 -4
  55. package/src/adapters/kysely/kysely-adapter.ts +6 -3
  56. package/src/adapters/kysely/kysely-query.test.ts +498 -0
  57. package/src/adapters/kysely/kysely-query.ts +187 -83
  58. package/src/adapters/kysely/kysely-uow-compiler.test.ts +85 -3
  59. package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
  60. package/src/adapters/kysely/kysely-uow-executor.ts +5 -9
  61. package/src/migration-engine/generation-engine.ts +2 -1
  62. package/src/mod.ts +12 -7
  63. package/src/query/cursor.test.ts +113 -68
  64. package/src/query/cursor.ts +127 -36
  65. package/src/query/query-type.test.ts +34 -14
  66. package/src/query/query.ts +94 -34
  67. package/src/query/result-transform.test.ts +5 -5
  68. package/src/query/result-transform.ts +29 -11
  69. package/src/query/unit-of-work.ts +141 -26
  70. package/src/schema/serialize.test.ts +223 -0
  71. 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 { encodeCursor } from "../../query/cursor";
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").create("users", {
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
- await queryEngine
205
- .createUnitOfWork("create-users")
206
- .create("users", { name: "User1", age: 20 })
207
- .create("users", { name: "User2", age: 30 })
208
- .create("users", { name: "User3", age: 40 })
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
- .createUnitOfWork("create-users")
226
- .create("users", { name: "Page User A", age: 20 })
227
- .create("users", { name: "Page User B", age: 30 })
228
- .create("users", { name: "Page User C", age: 40 })
229
- .create("users", { name: "Page User D", age: 50 })
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 = encodeCursor({
248
+ const cursor = new Cursor({
249
+ indexName: "name_idx",
250
+ orderDirection: "asc",
251
+ pageSize: 2,
246
252
  indexValues: { name: lastItem.name },
247
- direction: "forward",
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
- .createUnitOfWork("create-users")
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").create("emails", {
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
- .createUnitOfWork("create-user-for-external-id")
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
- .createUnitOfWork("create-email-with-external-id")
349
- .create("emails", {
350
- user_id: user.id.externalId,
351
- email: "external-id-test@example.com",
352
- is_primary: false,
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
- .createUnitOfWork("create-author")
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").create("posts", {
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
- .createUnitOfWork("create-commenter")
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").create("comments", {
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
- await queryEngine
568
- .createUnitOfWork("create-user")
569
- .create("user", {
570
- email: "test@example.com",
571
- passwordHash: "hash",
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
- await queryEngine
579
- .createUnitOfWork("create-session")
580
- .create("session", {
581
- userId: user.id,
582
- expiresAt: new Date("2025-12-31"),
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 { encodeCursor } from "../../query/cursor";
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
- .createUnitOfWork("create-users")
123
- .create("users", {
124
- name: "Alice",
125
- age: 25,
126
- })
127
- .create("users", {
128
- name: "Bob",
129
- age: 30,
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
- await queryEngine
236
- .createUnitOfWork("create-users")
237
- .create("users", { name: "User1", age: 20 })
238
- .create("users", { name: "User2", age: 30 })
239
- .create("users", { name: "User3", age: 40 })
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
- .createUnitOfWork("create-users")
258
- .create("users", { name: "Page User A", age: 20 })
259
- .create("users", { name: "Page User B", age: 30 })
260
- .create("users", { name: "Page User C", age: 40 })
261
- .create("users", { name: "Page User D", age: 50 })
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 = encodeCursor({
275
+ const cursor = new Cursor({
276
+ indexName: "name_idx",
277
+ orderDirection: "asc",
278
+ pageSize: 2,
279
279
  indexValues: { name: lastItem.name },
280
- direction: "forward",
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
- .createUnitOfWork("create-users")
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").create("emails", {
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
- .createUnitOfWork("create-author")
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").create("posts", {
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
- .createUnitOfWork("create-commenter")
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").create("comments", {
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
  });