@fragno-dev/db 0.1.10 → 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.
Files changed (61) hide show
  1. package/.turbo/turbo-build.log +40 -37
  2. package/CHANGELOG.md +19 -0
  3. package/dist/adapters/drizzle/drizzle-query.d.ts +1 -0
  4. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  5. package/dist/adapters/drizzle/drizzle-query.js +41 -38
  6. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  7. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +2 -0
  8. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
  9. package/dist/adapters/drizzle/drizzle-uow-compiler.js +13 -1
  10. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  11. package/dist/adapters/drizzle/shared.d.ts +1 -0
  12. package/dist/adapters/kysely/kysely-adapter.d.ts +3 -2
  13. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  14. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  15. package/dist/adapters/kysely/kysely-query-builder.js +23 -12
  16. package/dist/adapters/kysely/kysely-query-builder.js.map +1 -1
  17. package/dist/adapters/kysely/kysely-query.d.ts +22 -0
  18. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -0
  19. package/dist/adapters/kysely/kysely-query.js +72 -50
  20. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  21. package/dist/adapters/kysely/kysely-uow-executor.js +2 -2
  22. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  23. package/dist/migration-engine/generation-engine.d.ts +1 -1
  24. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  25. package/dist/migration-engine/generation-engine.js.map +1 -1
  26. package/dist/mod.d.ts +5 -5
  27. package/dist/mod.d.ts.map +1 -1
  28. package/dist/mod.js.map +1 -1
  29. package/dist/query/query.d.ts +24 -8
  30. package/dist/query/query.d.ts.map +1 -1
  31. package/dist/query/result-transform.js +17 -5
  32. package/dist/query/result-transform.js.map +1 -1
  33. package/dist/query/unit-of-work.d.ts +5 -4
  34. package/dist/query/unit-of-work.d.ts.map +1 -1
  35. package/dist/query/unit-of-work.js +2 -3
  36. package/dist/query/unit-of-work.js.map +1 -1
  37. package/dist/schema/serialize.js +2 -0
  38. package/dist/schema/serialize.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +170 -50
  41. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +89 -35
  42. package/src/adapters/drizzle/drizzle-query.test.ts +56 -6
  43. package/src/adapters/drizzle/drizzle-query.ts +68 -63
  44. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +63 -3
  45. package/src/adapters/drizzle/drizzle-uow-compiler.ts +27 -2
  46. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +88 -0
  47. package/src/adapters/kysely/kysely-adapter.ts +6 -3
  48. package/src/adapters/kysely/kysely-query-builder.ts +35 -11
  49. package/src/adapters/kysely/kysely-query.test.ts +498 -0
  50. package/src/adapters/kysely/kysely-query.ts +137 -82
  51. package/src/adapters/kysely/kysely-uow-compiler.test.ts +66 -0
  52. package/src/adapters/kysely/kysely-uow-executor.ts +5 -9
  53. package/src/migration-engine/generation-engine.ts +2 -1
  54. package/src/mod.ts +6 -6
  55. package/src/query/query-type.test.ts +34 -14
  56. package/src/query/query.ts +77 -36
  57. package/src/query/result-transform.test.ts +5 -5
  58. package/src/query/result-transform.ts +29 -11
  59. package/src/query/unit-of-work.ts +8 -11
  60. package/src/schema/serialize.test.ts +223 -0
  61. package/src/schema/serialize.ts +16 -0
@@ -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
 
@@ -269,9 +272,8 @@ describe("DrizzleAdapter PGLite", () => {
269
272
  const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
270
273
  const queries: DrizzleCompiledQuery[] = [];
271
274
 
272
- const createUow = queryEngine
273
- .createUnitOfWork("create-users")
274
- .create("users", { name: "Email User", age: 20 });
275
+ const createUow = queryEngine.createUnitOfWork("create-users");
276
+ createUow.create("users", { name: "Email User", age: 20 });
275
277
 
276
278
  await createUow.executeMutations();
277
279
 
@@ -282,7 +284,8 @@ describe("DrizzleAdapter PGLite", () => {
282
284
  .executeRetrieve();
283
285
 
284
286
  // Create an email for testing joins
285
- const createEmailUow = queryEngine.createUnitOfWork("create-test-email").create("emails", {
287
+ const createEmailUow = queryEngine.createUnitOfWork("create-test-email");
288
+ createEmailUow.create("emails", {
286
289
  user_id: existingUser.id,
287
290
  email: "test@example.com",
288
291
  is_primary: true,
@@ -330,9 +333,8 @@ describe("DrizzleAdapter PGLite", () => {
330
333
  const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
331
334
 
332
335
  // 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 });
336
+ const createUserUow = queryEngine.createUnitOfWork("create-user-for-external-id");
337
+ createUserUow.create("users", { name: "External ID Test User", age: 35 });
336
338
  await createUserUow.executeMutations();
337
339
 
338
340
  // Get the user
@@ -344,13 +346,12 @@ describe("DrizzleAdapter PGLite", () => {
344
346
  .executeRetrieve();
345
347
 
346
348
  // 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
- });
349
+ const createEmailUow = queryEngine.createUnitOfWork("create-email-with-external-id");
350
+ createEmailUow.create("emails", {
351
+ user_id: user.id.externalId,
352
+ email: "external-id-test@example.com",
353
+ is_primary: false,
354
+ });
354
355
 
355
356
  const { success } = await createEmailUow.executeMutations();
356
357
  expect(success).toBe(true);
@@ -385,9 +386,8 @@ describe("DrizzleAdapter PGLite", () => {
385
386
  const queries: DrizzleCompiledQuery[] = [];
386
387
 
387
388
  // Create a user (author)
388
- const createAuthorUow = queryEngine
389
- .createUnitOfWork("create-author")
390
- .create("users", { name: "Blog Author", age: 30 });
389
+ const createAuthorUow = queryEngine.createUnitOfWork("create-author");
390
+ createAuthorUow.create("users", { name: "Blog Author", age: 30 });
391
391
  await createAuthorUow.executeMutations();
392
392
 
393
393
  // Get the author
@@ -397,7 +397,8 @@ describe("DrizzleAdapter PGLite", () => {
397
397
  .executeRetrieve();
398
398
 
399
399
  // Create a post by the author
400
- const createPostUow = queryEngine.createUnitOfWork("create-post").create("posts", {
400
+ const createPostUow = queryEngine.createUnitOfWork("create-post");
401
+ createPostUow.create("posts", {
401
402
  user_id: author.id,
402
403
  title: "My First Post",
403
404
  content: "This is the content of my first post",
@@ -408,9 +409,8 @@ describe("DrizzleAdapter PGLite", () => {
408
409
  const [[post]] = await queryEngine.createUnitOfWork("get-post").find("posts").executeRetrieve();
409
410
 
410
411
  // Create a commenter
411
- const createCommenterUow = queryEngine
412
- .createUnitOfWork("create-commenter")
413
- .create("users", { name: "Commenter User", age: 25 });
412
+ const createCommenterUow = queryEngine.createUnitOfWork("create-commenter");
413
+ createCommenterUow.create("users", { name: "Commenter User", age: 25 });
414
414
  await createCommenterUow.executeMutations();
415
415
 
416
416
  // Get the commenter
@@ -420,7 +420,8 @@ describe("DrizzleAdapter PGLite", () => {
420
420
  .executeRetrieve();
421
421
 
422
422
  // Create a comment on the post
423
- const createCommentUow = queryEngine.createUnitOfWork("create-comment").create("comments", {
423
+ const createCommentUow = queryEngine.createUnitOfWork("create-comment");
424
+ createCommentUow.create("comments", {
424
425
  post_id: post.id,
425
426
  user_id: commenter.id,
426
427
  text: "Great post!",
@@ -564,24 +565,22 @@ describe("DrizzleAdapter PGLite", () => {
564
565
  const queryEngine = adapter.createQueryEngine(authSchema, "test-namespace");
565
566
 
566
567
  // Create a user
567
- await queryEngine
568
- .createUnitOfWork("create-user")
569
- .create("user", {
570
- email: "test@example.com",
571
- passwordHash: "hash",
572
- })
573
- .executeMutations();
568
+ const createUserUow = queryEngine.createUnitOfWork("create-user");
569
+ createUserUow.create("user", {
570
+ email: "test@example.com",
571
+ passwordHash: "hash",
572
+ });
573
+ await createUserUow.executeMutations();
574
574
 
575
575
  // Create a session
576
576
  const [[user]] = await queryEngine.createUnitOfWork("get-user").find("user").executeRetrieve();
577
577
 
578
- await queryEngine
579
- .createUnitOfWork("create-session")
580
- .create("session", {
581
- userId: user.id,
582
- expiresAt: new Date("2025-12-31"),
583
- })
584
- .executeMutations();
578
+ const createSessionUow = queryEngine.createUnitOfWork("create-session");
579
+ createSessionUow.create("session", {
580
+ userId: user.id,
581
+ expiresAt: new Date("2025-12-31"),
582
+ });
583
+ await createSessionUow.executeMutations();
585
584
 
586
585
  // Get session
587
586
  const [[session]] = await queryEngine
@@ -609,4 +608,125 @@ describe("DrizzleAdapter PGLite", () => {
609
608
 
610
609
  await cleanup();
611
610
  });
611
+
612
+ it("should handle timestamps and timezones correctly", async () => {
613
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
614
+
615
+ // Create a user
616
+ const createUserUow = queryEngine.createUnitOfWork("create-user-for-timestamp");
617
+ createUserUow.create("users", { name: "Timestamp User", age: 28 });
618
+ await createUserUow.executeMutations();
619
+
620
+ const [[user]] = await queryEngine
621
+ .createUnitOfWork("get-user-for-timestamp")
622
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Timestamp User")))
623
+ .executeRetrieve();
624
+
625
+ // Create a post with database-generated timestamp (defaultTo(b => b.now()))
626
+ const createPostUow = queryEngine.createUnitOfWork("create-post-with-timestamp");
627
+ createPostUow.create("posts", {
628
+ user_id: user.id,
629
+ title: "Timestamp Test Post",
630
+ content: "Testing timestamp handling",
631
+ });
632
+ await createPostUow.executeMutations();
633
+
634
+ const createdPostIds = createPostUow.getCreatedIds();
635
+ expect(createdPostIds).toHaveLength(1);
636
+ const postId = createdPostIds[0];
637
+
638
+ // Retrieve the specific post we just created by its ID
639
+ const [[post]] = await queryEngine
640
+ .createUnitOfWork("get-post-with-timestamp")
641
+ .find("posts", (b) => b.whereIndex("primary", (eb) => eb("id", "=", postId)))
642
+ .executeRetrieve();
643
+
644
+ // Verify created_at is a Date
645
+ expect(post.created_at).toBeInstanceOf(Date);
646
+
647
+ // Verify the timestamp is a valid date (not too far in the past or future)
648
+ const now = Date.now();
649
+ const createdTime = post.created_at.getTime();
650
+ expect(createdTime).toBeGreaterThan(now - 24 * 60 * 60 * 1000); // Within last 24 hours
651
+ expect(createdTime).toBeLessThan(now + 24 * 60 * 60 * 1000); // Not more than 24 hours in future
652
+
653
+ // Verify we can compare timestamps
654
+ expect(post.created_at.getTime()).toBeGreaterThan(0);
655
+
656
+ // Test that the Date object has the correct methods
657
+ expect(typeof post.created_at.toISOString).toBe("function");
658
+ expect(typeof post.created_at.getTime).toBe("function");
659
+ expect(typeof post.created_at.getTimezoneOffset).toBe("function");
660
+
661
+ // Verify the date can be serialized and deserialized
662
+ const isoString = post.created_at.toISOString();
663
+ expect(typeof isoString).toBe("string");
664
+ expect(new Date(isoString).getTime()).toBe(post.created_at.getTime());
665
+ });
666
+
667
+ it("should create user and post in same transaction using returned ID", async () => {
668
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
669
+ const queries: DrizzleCompiledQuery[] = [];
670
+
671
+ // Create UOW and create both user and post in same transaction
672
+ const uow = queryEngine.createUnitOfWork("create-user-and-post", {
673
+ onQuery: (query) => queries.push(query),
674
+ });
675
+
676
+ // Create user and capture the returned ID
677
+ const userId = uow.create("users", {
678
+ name: "UOW Test User",
679
+ age: 35,
680
+ });
681
+
682
+ // Use the returned FragnoId directly to create a post in the same transaction
683
+ // The compiler will extract externalId and generate a subquery to lookup the internal ID
684
+ const postId = uow.create("posts", {
685
+ user_id: userId,
686
+ title: "UOW Test Post",
687
+ content: "This post was created in the same transaction as the user",
688
+ });
689
+
690
+ // Execute all mutations in a single transaction
691
+ const { success } = await uow.executeMutations();
692
+ expect(success).toBe(true);
693
+
694
+ // Verify both records were created
695
+ const userIdStr = userId.toString();
696
+ const postIdStr = postId.toString();
697
+
698
+ const [[user]] = await queryEngine
699
+ .createUnitOfWork("verify-user")
700
+ .find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userIdStr)))
701
+ .executeRetrieve();
702
+
703
+ expect(user.name).toBe("UOW Test User");
704
+ expect(user.age).toBe(35);
705
+
706
+ const [[post]] = await queryEngine
707
+ .createUnitOfWork("verify-post")
708
+ .find("posts", (b) => b.whereIndex("primary", (eb) => eb("id", "=", postIdStr)))
709
+ .executeRetrieve();
710
+
711
+ expect(post.title).toBe("UOW Test Post");
712
+ expect(post.content).toBe("This post was created in the same transaction as the user");
713
+
714
+ // Verify the foreign key relationship is correct
715
+ expect(post.user_id.internalId).toBe(user.id.internalId);
716
+
717
+ const [insertUserQuery, insertPostQuery] = queries;
718
+ expect(insertUserQuery.sql).toMatchInlineSnapshot(
719
+ `"insert into "users_namespace" ("id", "name", "age", "_internalId", "_version") values ($1, $2, $3, default, default)"`,
720
+ );
721
+ expect(insertUserQuery.params).toEqual([userId.externalId, "UOW Test User", 35]);
722
+ expect(insertPostQuery.sql).toMatchInlineSnapshot(
723
+ `"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)"`,
724
+ );
725
+ expect(insertPostQuery.params).toEqual([
726
+ postId.externalId,
727
+ userId.externalId,
728
+ "UOW Test Post",
729
+ "This post was created in the same transaction as the user",
730
+ ]);
731
+ });
612
732
  });
@@ -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
 
@@ -302,9 +299,8 @@ describe("DrizzleAdapter SQLite", () => {
302
299
  it("should support joins", async () => {
303
300
  const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
304
301
 
305
- const createUow = queryEngine
306
- .createUnitOfWork("create-users")
307
- .create("users", { name: "Email User", age: 20 });
302
+ const createUow = queryEngine.createUnitOfWork("create-users");
303
+ createUow.create("users", { name: "Email User", age: 20 });
308
304
 
309
305
  const { success } = await createUow.executeMutations();
310
306
  expect(success).toBe(true);
@@ -321,7 +317,8 @@ describe("DrizzleAdapter SQLite", () => {
321
317
  expect(createdUser.name).toBe("Email User");
322
318
 
323
319
  // Create an email for testing joins
324
- const createEmailUow = queryEngine.createUnitOfWork("create-test-email").create("emails", {
320
+ const createEmailUow = queryEngine.createUnitOfWork("create-test-email");
321
+ createEmailUow.create("emails", {
325
322
  user_id: createdUser.id,
326
323
  email: "test@example.com",
327
324
  is_primary: true,
@@ -364,9 +361,8 @@ describe("DrizzleAdapter SQLite", () => {
364
361
  const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
365
362
 
366
363
  // Create a user (author)
367
- const createAuthorUow = queryEngine
368
- .createUnitOfWork("create-author")
369
- .create("users", { name: "Blog Author", age: 30 });
364
+ const createAuthorUow = queryEngine.createUnitOfWork("create-author");
365
+ createAuthorUow.create("users", { name: "Blog Author", age: 30 });
370
366
  await createAuthorUow.executeMutations();
371
367
 
372
368
  // Fetch the created author to get the proper ID
@@ -376,7 +372,8 @@ describe("DrizzleAdapter SQLite", () => {
376
372
  .executeRetrieve();
377
373
 
378
374
  // Create a post by the author
379
- const createPostUow = queryEngine.createUnitOfWork("create-post").create("posts", {
375
+ const createPostUow = queryEngine.createUnitOfWork("create-post");
376
+ createPostUow.create("posts", {
380
377
  user_id: author.id,
381
378
  title: "My First Post",
382
379
  content: "This is the content of my first post",
@@ -390,9 +387,8 @@ describe("DrizzleAdapter SQLite", () => {
390
387
  .executeRetrieve();
391
388
 
392
389
  // Create a commenter
393
- const createCommenterUow = queryEngine
394
- .createUnitOfWork("create-commenter")
395
- .create("users", { name: "Commenter User", age: 25 });
390
+ const createCommenterUow = queryEngine.createUnitOfWork("create-commenter");
391
+ createCommenterUow.create("users", { name: "Commenter User", age: 25 });
396
392
  await createCommenterUow.executeMutations();
397
393
 
398
394
  // Fetch the created commenter to get the proper ID
@@ -402,7 +398,8 @@ describe("DrizzleAdapter SQLite", () => {
402
398
  .executeRetrieve();
403
399
 
404
400
  // Create a comment on the post
405
- const createCommentUow = queryEngine.createUnitOfWork("create-comment").create("comments", {
401
+ const createCommentUow = queryEngine.createUnitOfWork("create-comment");
402
+ createCommentUow.create("comments", {
406
403
  post_id: post.id,
407
404
  user_id: commenter.id,
408
405
  text: "Great post!",
@@ -580,4 +577,61 @@ describe("DrizzleAdapter SQLite", () => {
580
577
  age: 60,
581
578
  });
582
579
  });
580
+
581
+ it("should handle timestamps and timezones correctly", async () => {
582
+ const queryEngine = adapter.createQueryEngine(testSchema, "namespace");
583
+
584
+ // Create a user
585
+ const createUserUow = queryEngine.createUnitOfWork("create-user-for-timestamp");
586
+ createUserUow.create("users", { name: "Timestamp User", age: 28 });
587
+ await createUserUow.executeMutations();
588
+
589
+ const [[user]] = await queryEngine
590
+ .createUnitOfWork("get-user-for-timestamp")
591
+ .find("users", (b) => b.whereIndex("name_idx", (eb) => eb("name", "=", "Timestamp User")))
592
+ .executeRetrieve();
593
+
594
+ // Create a post (note: SQLite schema doesn't have created_at column)
595
+ const createPostUow = queryEngine.createUnitOfWork("create-post-for-timestamp");
596
+ createPostUow.create("posts", {
597
+ user_id: user.id,
598
+ title: "Timestamp Test Post",
599
+ content: "Testing timestamp handling",
600
+ });
601
+ await createPostUow.executeMutations();
602
+
603
+ // Retrieve the post
604
+ const [[post]] = await queryEngine
605
+ .createUnitOfWork("get-post-for-timestamp")
606
+ .find("posts", (b) => b.whereIndex("posts_user_idx", (eb) => eb("user_id", "=", user.id)))
607
+ .executeRetrieve();
608
+
609
+ expect(post).toBeDefined();
610
+ expect(post.title).toBe("Timestamp Test Post");
611
+
612
+ // Test general Date handling (SQLite stores timestamps as integers)
613
+ const now = new Date();
614
+ expect(now).toBeInstanceOf(Date);
615
+ expect(typeof now.getTime).toBe("function");
616
+ expect(typeof now.toISOString).toBe("function");
617
+
618
+ // Verify date serialization/deserialization works
619
+ const isoString = now.toISOString();
620
+ expect(typeof isoString).toBe("string");
621
+ expect(new Date(isoString).getTime()).toBe(now.getTime());
622
+
623
+ // Test timezone preservation
624
+ const specificDate = new Date("2024-06-15T14:30:00Z");
625
+ expect(specificDate.toISOString()).toBe("2024-06-15T14:30:00.000Z");
626
+
627
+ // Verify SQLite numeric timestamp conversion
628
+ const timestamp = Date.now();
629
+ const dateFromTimestamp = new Date(timestamp);
630
+ expect(dateFromTimestamp.getTime()).toBe(timestamp);
631
+
632
+ // Verify that dates from different timezones are handled correctly
633
+ const localDate = new Date("2024-06-15T14:30:00");
634
+ expect(localDate).toBeInstanceOf(Date);
635
+ expect(typeof localDate.getTimezoneOffset()).toBe("number");
636
+ });
583
637
  });
@@ -1,6 +1,6 @@
1
1
  import { drizzle } from "drizzle-orm/pglite";
2
- import { beforeAll, beforeEach, describe, expect, it } from "vitest";
3
- import { column, idColumn, referenceColumn, schema } from "../../schema/create";
2
+ import { beforeAll, beforeEach, describe, expect, expectTypeOf, it } from "vitest";
3
+ import { column, FragnoId, idColumn, referenceColumn, schema } from "../../schema/create";
4
4
  import type { DBType } from "./shared";
5
5
  import { writeAndLoadSchema } from "./test-utils";
6
6
  import { fromDrizzle } from "./drizzle-query";
@@ -134,10 +134,14 @@ describe("drizzle-query", () => {
134
134
  it("should find with select subset of columns", async () => {
135
135
  const someExternalId = "some-external-id";
136
136
 
137
- await orm.findFirst("user", (b) =>
137
+ const res = await orm.findFirst("user", (b) =>
138
138
  b.whereIndex("primary", (eb) => eb("id", "=", someExternalId)).select(["id", "email"]),
139
139
  );
140
140
 
141
+ if (res) {
142
+ expectTypeOf(res.email).toEqualTypeOf<string>();
143
+ }
144
+
141
145
  const [query] = queries;
142
146
  expect(query.sql).toMatchInlineSnapshot(
143
147
  `"select "id", "email", "_internalId", "_version" from "user" "user" where "user"."id" = $1 limit $2"`,
@@ -178,7 +182,7 @@ describe("drizzle-query", () => {
178
182
 
179
183
  const [query] = queries;
180
184
  expect(query.sql).toMatchInlineSnapshot(
181
- `"select "id", "userId", "expiresAt", "createdAt", "_internalId", "_version" from "session" "session" where "session"."userId" = $1"`,
185
+ `"select "id", "userId", "expiresAt", "createdAt", "_internalId", "_version" from "session" "session" where "session"."userId" = (select "_internalId" from "user" where "id" = $1 limit 1)"`,
182
186
  );
183
187
  expect(query.params).toEqual([userId]);
184
188
  });
@@ -194,7 +198,7 @@ describe("drizzle-query", () => {
194
198
  });
195
199
 
196
200
  it("should find with select subset", async () => {
197
- await orm.find("user", (b) => b.whereIndex("primary").select(["id", "email"]));
201
+ const _res = await orm.find("user", (b) => b.whereIndex("primary").select(["id", "email"]));
198
202
 
199
203
  const [query] = queries;
200
204
  expect(query.sql).toMatchInlineSnapshot(
@@ -331,6 +335,33 @@ describe("drizzle-query", () => {
331
335
  );
332
336
  expect(query.params).toEqual([newExpiresAt.toISOString(), sessionId]);
333
337
  });
338
+
339
+ it("should update with version check using FragnoId", async () => {
340
+ const userId = FragnoId.fromExternal("user-123", 5);
341
+
342
+ await orm.update("user", userId, (b) =>
343
+ b
344
+ .set({
345
+ email: "checked@example.com",
346
+ })
347
+ .check(),
348
+ );
349
+
350
+ // Verify the SQL query includes version check in WHERE clause
351
+ const [query] = queries;
352
+ expect(query.sql).toMatchInlineSnapshot(
353
+ `"update "user" set "email" = $1, "_version" = COALESCE(_version, 0) + 1 where ("user"."id" = $2 and "user"."_version" = $3)"`,
354
+ );
355
+ expect(query.params).toEqual(["checked@example.com", "user-123", 5]);
356
+ });
357
+
358
+ it("should throw when trying to check() with string ID", async () => {
359
+ await expect(
360
+ orm.update("user", "user-123", (b) => b.set({ email: "test@example.com" }).check()),
361
+ ).rejects.toThrow(
362
+ 'Cannot use check() with a string ID on table "user". Version checking requires a FragnoId with version information.',
363
+ );
364
+ });
334
365
  });
335
366
 
336
367
  describe("updateMany", () => {
@@ -378,6 +409,25 @@ describe("drizzle-query", () => {
378
409
  expect(query.sql).toMatchInlineSnapshot(`"delete from "session" where "session"."id" = $1"`);
379
410
  expect(query.params).toEqual([sessionId]);
380
411
  });
412
+
413
+ it("should delete with version check using FragnoId", async () => {
414
+ const userId = FragnoId.fromExternal("user-789", 3);
415
+
416
+ await orm.delete("user", userId, (b) => b.check());
417
+
418
+ // Verify the SQL query includes version check in WHERE clause
419
+ const [query] = queries;
420
+ expect(query.sql).toMatchInlineSnapshot(
421
+ `"delete from "user" where ("user"."id" = $1 and "user"."_version" = $2)"`,
422
+ );
423
+ expect(query.params).toEqual(["user-789", 3]);
424
+ });
425
+
426
+ it("should throw when trying to check() with string ID on delete", async () => {
427
+ await expect(orm.delete("user", "user-123", (b) => b.check())).rejects.toThrow(
428
+ 'Cannot use check() with a string ID on table "user". Version checking requires a FragnoId with version information.',
429
+ );
430
+ });
381
431
  });
382
432
 
383
433
  describe("deleteMany", () => {
@@ -394,7 +444,7 @@ describe("drizzle-query", () => {
394
444
  // Verify the find query that's executed first
395
445
  const findQuery = queries[0];
396
446
  expect(findQuery.sql).toMatchInlineSnapshot(
397
- `"select "id", "userId", "expiresAt", "createdAt", "_internalId", "_version" from "session" "session" where "session"."userId" = $1"`,
447
+ `"select "id", "userId", "expiresAt", "createdAt", "_internalId", "_version" from "session" "session" where "session"."userId" = (select "_internalId" from "user" where "id" = $1 limit 1)"`,
398
448
  );
399
449
  expect(findQuery.params).toEqual([userId]);
400
450