@effect-app/infra 4.0.0-beta.315 → 4.0.0-beta.317

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 (46) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/Store/Cosmos/query.d.ts +2 -1
  3. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  4. package/dist/Store/Cosmos/query.js +60 -2
  5. package/dist/Store/Cosmos.d.ts.map +1 -1
  6. package/dist/Store/Cosmos.js +17 -12
  7. package/dist/Store/Disk.d.ts.map +1 -1
  8. package/dist/Store/Disk.js +12 -7
  9. package/dist/Store/Memory.d.ts +2 -1
  10. package/dist/Store/Memory.d.ts.map +1 -1
  11. package/dist/Store/Memory.js +28 -15
  12. package/dist/Store/SQL/Pg.d.ts.map +1 -1
  13. package/dist/Store/SQL/Pg.js +15 -11
  14. package/dist/Store/SQL/query.d.ts +5 -1
  15. package/dist/Store/SQL/query.d.ts.map +1 -1
  16. package/dist/Store/SQL/query.js +131 -2
  17. package/dist/Store/SQL.d.ts +1 -1
  18. package/dist/Store/SQL.d.ts.map +1 -1
  19. package/dist/Store/SQL.js +24 -18
  20. package/dist/Store/codeFilter.d.ts.map +1 -1
  21. package/dist/Store/codeFilter.js +67 -20
  22. package/dist/Store/jsonDocument.d.ts +13 -0
  23. package/dist/Store/jsonDocument.d.ts.map +1 -0
  24. package/dist/Store/jsonDocument.js +31 -0
  25. package/dist/Store/utils.d.ts +16 -0
  26. package/dist/Store/utils.d.ts.map +1 -1
  27. package/dist/Store/utils.js +245 -6
  28. package/dist/logger.d.ts +5 -5
  29. package/examples/query.ts +4 -4
  30. package/package.json +3 -3
  31. package/src/Store/Cosmos/query.ts +65 -9
  32. package/src/Store/Cosmos.ts +17 -11
  33. package/src/Store/Disk.ts +28 -7
  34. package/src/Store/Memory.ts +40 -17
  35. package/src/Store/SQL/Pg.ts +23 -10
  36. package/src/Store/SQL/query.ts +152 -7
  37. package/src/Store/SQL.ts +37 -17
  38. package/src/Store/codeFilter.ts +71 -23
  39. package/src/Store/jsonDocument.ts +43 -0
  40. package/src/Store/utils.ts +275 -5
  41. package/test/cosmos-query.test.ts +100 -5
  42. package/test/dist/json-lower.test.d.ts.map +1 -0
  43. package/test/dist/query.test.d.ts.map +1 -1
  44. package/test/json-lower.test.ts +115 -0
  45. package/test/query.test.ts +261 -12
  46. package/test/sql-store.test.ts +233 -39
@@ -24,11 +24,157 @@ describe("SQL query builder (SQLite dialect)", () => {
24
24
  expect(result.params).toContain("John")
25
25
  })
26
26
 
27
+ it("where eq Date binds ISO string", () => {
28
+ const result = buildWhereSQLQuery(
29
+ sqliteDialect,
30
+ "id",
31
+ [{ t: "where", path: "n", op: "eq", value: new Date("2024-01-01T00:00:00.000Z") }],
32
+ "users",
33
+ {}
34
+ )
35
+ expect(result.params).toContain("2024-01-01T00:00:00.000Z")
36
+ })
37
+
38
+ it("where eq app native Encoded binds via JsonLower", () => {
39
+ class Day {
40
+ readonly ymd: string
41
+ constructor(ymd: string) {
42
+ this.ymd = ymd
43
+ }
44
+ }
45
+ const day = new Day("2024-06-01")
46
+ const json = {
47
+ toJson: (v: unknown) => v instanceof Day ? v.ymd : v,
48
+ jsonifyFilter: (filter: readonly { t: string; path?: string; op?: string; value?: unknown }[]) =>
49
+ filter.map((r) => r.t === "where" ? { ...r, value: r.value instanceof Day ? r.value.ymd : r.value } : r)
50
+ }
51
+ const result = buildWhereSQLQuery(
52
+ sqliteDialect,
53
+ "id",
54
+ [{ t: "where", path: "day", op: "eq", value: day }],
55
+ "users",
56
+ {},
57
+ undefined,
58
+ undefined,
59
+ undefined,
60
+ undefined,
61
+ undefined,
62
+ json as never
63
+ )
64
+ expect(result.params).toContain("2024-06-01")
65
+ })
66
+
67
+ it("where in Set binds array values", () => {
68
+ const result = buildWhereSQLQuery(
69
+ sqliteDialect,
70
+ "id",
71
+ [{ t: "where", path: "tags", op: "in", value: new Set(["a", "b"]) }],
72
+ "users",
73
+ {}
74
+ )
75
+ expect(result.params).toEqual(expect.arrayContaining(["a", "b"]))
76
+ })
77
+
78
+ it("where includes Date binds ISO string", () => {
79
+ const result = buildWhereSQLQuery(
80
+ sqliteDialect,
81
+ "id",
82
+ [{ t: "where", path: "dates", op: "includes", value: new Date("2024-01-01T00:00:00.000Z") }],
83
+ "users",
84
+ {}
85
+ )
86
+ expect(result.params).toContain("2024-01-01T00:00:00.000Z")
87
+ })
88
+
89
+ it("where hasKey / hasValue / hasKeyValue on Map JSON tuples", () => {
90
+ const key = buildWhereSQLQuery(
91
+ sqliteDialect,
92
+ "id",
93
+ [{ t: "where", path: "meta", op: "hasKey", value: "n" }],
94
+ "users",
95
+ {}
96
+ )
97
+ expect(key.sql).toContain("json_extract(value, '$[0]')")
98
+ expect(key.params).toContain("n")
99
+
100
+ const value = buildWhereSQLQuery(
101
+ sqliteDialect,
102
+ "id",
103
+ [{ t: "where", path: "meta", op: "hasValue", value: 1 }],
104
+ "users",
105
+ {}
106
+ )
107
+ expect(value.sql).toContain("json_extract(value, '$[1]')")
108
+ expect(value.params).toContain(1)
109
+
110
+ const pair = buildWhereSQLQuery(
111
+ sqliteDialect,
112
+ "id",
113
+ [{ t: "where", path: "meta", op: "hasKeyValue", value: ["n", 1] }],
114
+ "users",
115
+ {}
116
+ )
117
+ expect(pair.sql).toContain("json_each")
118
+ expect(pair.params).toContain(JSON.stringify(["n", 1]))
119
+
120
+ const anyKeys = buildWhereSQLQuery(
121
+ sqliteDialect,
122
+ "id",
123
+ [{ t: "where", path: "meta", op: "hasKey-any", value: ["n", "x"] }],
124
+ "users",
125
+ {}
126
+ )
127
+ expect(anyKeys.sql).toContain(" OR ")
128
+ expect(anyKeys.params).toEqual(expect.arrayContaining(["n", "x"]))
129
+ })
130
+
131
+ it("pg where hasKey / hasKeyValue uses jsonb tuple elements", () => {
132
+ const key = buildWhereSQLQuery(
133
+ pgDialect,
134
+ "id",
135
+ [{ t: "where", path: "meta", op: "hasKey", value: "n" }],
136
+ "users",
137
+ {}
138
+ )
139
+ expect(key.sql).toContain("jsonb_array_elements")
140
+ expect(key.sql).toContain("e->0")
141
+ expect(key.params).toContain(JSON.stringify("n"))
142
+
143
+ const pair = buildWhereSQLQuery(
144
+ pgDialect,
145
+ "id",
146
+ [{ t: "where", path: "meta", op: "hasKeyValue", value: ["n", 1] }],
147
+ "users",
148
+ {}
149
+ )
150
+ expect(pair.sql).toContain("@>")
151
+ expect(pair.sql).toContain("jsonb_build_array")
152
+ expect(pair.params).toContain(JSON.stringify(["n", 1]))
153
+ })
154
+
155
+ it("where includes-any Date[] binds ISO strings", () => {
156
+ const result = buildWhereSQLQuery(
157
+ sqliteDialect,
158
+ "id",
159
+ [{
160
+ t: "where",
161
+ path: "dates",
162
+ op: "includes-any",
163
+ value: [new Date("2024-01-01T00:00:00.000Z"), new Date("2024-06-01T00:00:00.000Z")]
164
+ }],
165
+ "users",
166
+ {}
167
+ )
168
+ expect(result.params).toEqual(
169
+ expect.arrayContaining(["2024-01-01T00:00:00.000Z", "2024-06-01T00:00:00.000Z"])
170
+ )
171
+ })
172
+
27
173
  it("where eq number", () => {
28
174
  const result = buildWhereSQLQuery(
29
175
  sqliteDialect,
30
176
  "id",
31
- [{ t: "where", path: "age", op: "eq", value: 25 as any }],
177
+ [{ t: "where", path: "age", op: "eq", value: 25 }],
32
178
  "users",
33
179
  {}
34
180
  )
@@ -40,7 +186,7 @@ describe("SQL query builder (SQLite dialect)", () => {
40
186
  const result = buildWhereSQLQuery(
41
187
  sqliteDialect,
42
188
  "id",
43
- [{ t: "where", path: "age", op: "gt", value: 18 as any }],
189
+ [{ t: "where", path: "age", op: "gt", value: 18 }],
44
190
  "users",
45
191
  {}
46
192
  )
@@ -70,7 +216,7 @@ describe("SQL query builder (SQLite dialect)", () => {
70
216
  "id",
71
217
  [
72
218
  { t: "where", path: "name", op: "eq", value: "Alice" },
73
- { t: "and", path: "age", op: "gt", value: 18 as any }
219
+ { t: "and", path: "age", op: "gt", value: 18 }
74
220
  ],
75
221
  "users",
76
222
  {}
@@ -83,7 +229,7 @@ describe("SQL query builder (SQLite dialect)", () => {
83
229
  const result = buildWhereSQLQuery(
84
230
  sqliteDialect,
85
231
  "id",
86
- [{ t: "where", path: "id", op: "in", value: ["a", "b", "c"] as any }],
232
+ [{ t: "where", path: "id", op: "in", value: ["a", "b", "c"] }],
87
233
  "users",
88
234
  {}
89
235
  )
@@ -167,7 +313,7 @@ describe("SQL query builder (SQLite dialect)", () => {
167
313
  const result = buildWhereSQLQuery(
168
314
  sqliteDialect,
169
315
  "id",
170
- [{ t: "where", path: "tags", op: "includes-any", value: ["admin", "user"] as any }],
316
+ [{ t: "where", path: "tags", op: "includes-any", value: ["admin", "user"] }],
171
317
  "users",
172
318
  {}
173
319
  )
@@ -495,7 +641,7 @@ describe("SQL query builder (PostgreSQL dialect)", () => {
495
641
  const result = buildWhereSQLQuery(
496
642
  pgDialect,
497
643
  "id",
498
- [{ t: "where", path: "status", op: "in", value: ["active", "pending"] as any }],
644
+ [{ t: "where", path: "status", op: "in", value: ["active", "pending"] }],
499
645
  "users",
500
646
  {}
501
647
  )
@@ -787,7 +933,7 @@ describe("SQL Store (SQLite integration)", () => {
787
933
  )
788
934
  const r1 = query(db, q1.sql, q1.params)
789
935
  expect(r1.length).toBe(1)
790
- expect((r1[0] as any).id).toBe("1")
936
+ expect(r1[0].id).toBe("1")
791
937
 
792
938
  const q2 = buildWhereSQLQuery(
793
939
  sqliteDialect,
@@ -798,13 +944,13 @@ describe("SQL Store (SQLite integration)", () => {
798
944
  )
799
945
  const r2 = query(db, q2.sql, q2.params)
800
946
  expect(r2.length).toBe(1)
801
- expect((r2[0] as any).id).toBe("2")
947
+ expect(r2[0].id).toBe("2")
802
948
 
803
949
  // Both queryable by id column
804
950
  const q3 = buildWhereSQLQuery(
805
951
  sqliteDialect,
806
952
  "id",
807
- [{ t: "where", path: "id", op: "in", value: ["1", "2"] as any }],
953
+ [{ t: "where", path: "id", op: "in", value: ["1", "2"] }],
808
954
  "test_compat",
809
955
  {}
810
956
  )
@@ -830,7 +976,7 @@ describe("SQL Store (SQLite integration)", () => {
830
976
  const q1 = buildWhereSQLQuery(
831
977
  sqliteDialect,
832
978
  "id",
833
- [{ t: "where", path: "age", op: "gt", value: 28 as any }],
979
+ [{ t: "where", path: "age", op: "gt", value: 28 }],
834
980
  "test_noid",
835
981
  {}
836
982
  )
@@ -846,8 +992,8 @@ describe("SQL Store (SQLite integration)", () => {
846
992
  )
847
993
  const r2 = query(db, q2.sql, q2.params)
848
994
  expect(r2.length).toBe(1)
849
- expect((r2[0] as any).id).toBe("2")
850
- expect((JSON.parse((r2[0] as any).data) as any).name).toBe("Bob")
995
+ expect(r2[0].id).toBe("2")
996
+ expect((JSON.parse(r2[0].data) as any).name).toBe("Bob")
851
997
 
852
998
  // Order + limit still works
853
999
  const q3 = buildWhereSQLQuery(
@@ -863,7 +1009,7 @@ describe("SQL Store (SQLite integration)", () => {
863
1009
  )
864
1010
  const r3 = query(db, q3.sql, q3.params)
865
1011
  expect(r3.length).toBe(2)
866
- expect((JSON.parse((r3[0] as any).data) as any).name).toBe("Bob") // youngest first
1012
+ expect((JSON.parse(r3[0].data) as any).name).toBe("Bob") // youngest first
867
1013
  }))
868
1014
 
869
1015
  it("query builder generates valid SQL for SQLite", () =>
@@ -896,13 +1042,13 @@ describe("SQL Store (SQLite integration)", () => {
896
1042
  {}
897
1043
  )
898
1044
  expect(query(db, q1.sql, q1.params).length).toBe(1)
899
- expect((JSON.parse((query(db, q1.sql, q1.params)[0] as any).data) as any).name).toBe("Alice")
1045
+ expect((JSON.parse(query(db, q1.sql, q1.params)[0].data) as any).name).toBe("Alice")
900
1046
 
901
1047
  // Test gt
902
1048
  const q2 = buildWhereSQLQuery(
903
1049
  sqliteDialect,
904
1050
  "id",
905
- [{ t: "where", path: "age", op: "gt", value: 28 as any }],
1051
+ [{ t: "where", path: "age", op: "gt", value: 28 }],
906
1052
  "test_people",
907
1053
  {}
908
1054
  )
@@ -927,20 +1073,20 @@ describe("SQL Store (SQLite integration)", () => {
927
1073
  "id",
928
1074
  [
929
1075
  { t: "where", path: "name", op: "eq", value: "Alice" },
930
- { t: "and", path: "age", op: "gt", value: 25 as any }
1076
+ { t: "and", path: "age", op: "gt", value: 25 }
931
1077
  ],
932
1078
  "test_people",
933
1079
  {}
934
1080
  )
935
1081
  const r4 = query(db, q4.sql, q4.params)
936
1082
  expect(r4.length).toBe(1)
937
- expect((JSON.parse((r4[0] as any).data) as any).name).toBe("Alice")
1083
+ expect((JSON.parse(r4[0].data) as any).name).toBe("Alice")
938
1084
 
939
1085
  // Test IN
940
1086
  const q5 = buildWhereSQLQuery(
941
1087
  sqliteDialect,
942
1088
  "id",
943
- [{ t: "where", path: "id", op: "in", value: ["1", "3"] as any }],
1089
+ [{ t: "where", path: "id", op: "in", value: ["1", "3"] }],
944
1090
  "test_people",
945
1091
  {}
946
1092
  )
@@ -966,7 +1112,7 @@ describe("SQL Store (SQLite integration)", () => {
966
1112
  )
967
1113
  const r7 = query(db, q7.sql, q7.params)
968
1114
  expect(r7.length).toBe(1)
969
- expect((JSON.parse((r7[0] as any).data) as any).name).toBe("Alice")
1115
+ expect((JSON.parse(r7[0].data) as any).name).toBe("Alice")
970
1116
 
971
1117
  // Test includes (array)
972
1118
  const q8 = buildWhereSQLQuery(
@@ -987,7 +1133,7 @@ describe("SQL Store (SQLite integration)", () => {
987
1133
  {
988
1134
  t: "or-scope",
989
1135
  result: [
990
- { t: "where", path: "age", op: "gt", value: 30 as any },
1136
+ { t: "where", path: "age", op: "gt", value: 30 },
991
1137
  { t: "and", path: "name", op: "contains", value: "ar" }
992
1138
  ],
993
1139
  relation: "some"
@@ -1012,7 +1158,7 @@ describe("SQL Store (SQLite integration)", () => {
1012
1158
  )
1013
1159
  const r10 = query(db, q10.sql, q10.params)
1014
1160
  expect(r10.length).toBe(2)
1015
- expect((JSON.parse((r10[0] as any).data) as any).name).toBe("Charlie") // oldest first
1161
+ expect((JSON.parse(r10[0].data) as any).name).toBe("Charlie") // oldest first
1016
1162
  }))
1017
1163
 
1018
1164
  it("computed relation-every / distinct-count / sum / collect run on SQLite", () =>
@@ -1154,7 +1300,7 @@ describe("SQL Store (SQLite integration)", () => {
1154
1300
  const results = query(db, nsSql, params)
1155
1301
  // Should only get Alice and Bob (primary namespace), not Charlie (other namespace)
1156
1302
  expect(results.length).toBe(2)
1157
- const names = results.map((r) => (JSON.parse((r as any).data) as any).name).sort()
1303
+ const names = results.map((r) => (JSON.parse(r.data) as any).name).sort()
1158
1304
  expect(names).toEqual(["Alice", "Bob"])
1159
1305
  }))
1160
1306
 
@@ -1285,7 +1431,7 @@ describe("boolean WHERE clauses — query builder", () => {
1285
1431
  const result = buildWhereSQLQuery(
1286
1432
  sqliteDialect,
1287
1433
  "id",
1288
- [{ t: "where", path: "flag", op: "eq", value: true as any }],
1434
+ [{ t: "where", path: "flag", op: "eq", value: true }],
1289
1435
  "t",
1290
1436
  {}
1291
1437
  )
@@ -1297,7 +1443,7 @@ describe("boolean WHERE clauses — query builder", () => {
1297
1443
  const result = buildWhereSQLQuery(
1298
1444
  sqliteDialect,
1299
1445
  "id",
1300
- [{ t: "where", path: "flag", op: "eq", value: false as any }],
1446
+ [{ t: "where", path: "flag", op: "eq", value: false }],
1301
1447
  "t",
1302
1448
  {}
1303
1449
  )
@@ -1308,7 +1454,7 @@ describe("boolean WHERE clauses — query builder", () => {
1308
1454
  const r1 = buildWhereSQLQuery(
1309
1455
  sqliteDialect,
1310
1456
  "id",
1311
- [{ t: "where", path: "flag", op: "neq", value: true as any }],
1457
+ [{ t: "where", path: "flag", op: "neq", value: true }],
1312
1458
  "t",
1313
1459
  {}
1314
1460
  )
@@ -1317,7 +1463,7 @@ describe("boolean WHERE clauses — query builder", () => {
1317
1463
  const r2 = buildWhereSQLQuery(
1318
1464
  sqliteDialect,
1319
1465
  "id",
1320
- [{ t: "where", path: "flag", op: "in", value: [true, false] as any }],
1466
+ [{ t: "where", path: "flag", op: "in", value: [true, false] }],
1321
1467
  "t",
1322
1468
  {}
1323
1469
  )
@@ -1328,7 +1474,7 @@ describe("boolean WHERE clauses — query builder", () => {
1328
1474
  const result = buildWhereSQLQuery(
1329
1475
  pgDialect,
1330
1476
  "id",
1331
- [{ t: "where", path: "flag", op: "eq", value: true as any }],
1477
+ [{ t: "where", path: "flag", op: "eq", value: true }],
1332
1478
  "t",
1333
1479
  {}
1334
1480
  )
@@ -1340,7 +1486,7 @@ describe("boolean WHERE clauses — query builder", () => {
1340
1486
  const result = buildWhereSQLQuery(
1341
1487
  pgDialect,
1342
1488
  "id",
1343
- [{ t: "where", path: "flag", op: "eq", value: false as any }],
1489
+ [{ t: "where", path: "flag", op: "eq", value: false }],
1344
1490
  "t",
1345
1491
  {}
1346
1492
  )
@@ -1351,7 +1497,7 @@ describe("boolean WHERE clauses — query builder", () => {
1351
1497
  const result = buildWhereSQLQuery(
1352
1498
  pgDialect,
1353
1499
  "id",
1354
- [{ t: "where", path: "flag", op: "in", value: [true, false] as any }],
1500
+ [{ t: "where", path: "flag", op: "in", value: [true, false] }],
1355
1501
  "t",
1356
1502
  {}
1357
1503
  )
@@ -1373,7 +1519,7 @@ describe("boolean WHERE clauses — query builder", () => {
1373
1519
  const result = buildWhereSQLQuery(
1374
1520
  pgDialect,
1375
1521
  "id",
1376
- [{ t: "where", path: "age", op: "gt", value: 18 as any }],
1522
+ [{ t: "where", path: "age", op: "gt", value: 18 }],
1377
1523
  "t",
1378
1524
  {}
1379
1525
  )
@@ -1404,13 +1550,13 @@ describe("boolean WHERE clauses — SQLite integration (end-to-end)", () => {
1404
1550
  const q = buildWhereSQLQuery(
1405
1551
  sqliteDialect,
1406
1552
  "id",
1407
- [{ t: "where", path: "flag", op: "eq", value: true as any }],
1553
+ [{ t: "where", path: "flag", op: "eq", value: true }],
1408
1554
  "t",
1409
1555
  {}
1410
1556
  )
1411
1557
  const rows = query(db, q.sql, q.params)
1412
1558
  expect(rows.length).toBe(1)
1413
- expect((JSON.parse((rows[0] as any).data) as any).name).toBe("Alice")
1559
+ expect((JSON.parse(rows[0].data) as any).name).toBe("Alice")
1414
1560
  }))
1415
1561
 
1416
1562
  it("where flag = false matches only false rows", () =>
@@ -1426,13 +1572,13 @@ describe("boolean WHERE clauses — SQLite integration (end-to-end)", () => {
1426
1572
  const q = buildWhereSQLQuery(
1427
1573
  sqliteDialect,
1428
1574
  "id",
1429
- [{ t: "where", path: "flag", op: "eq", value: false as any }],
1575
+ [{ t: "where", path: "flag", op: "eq", value: false }],
1430
1576
  "t",
1431
1577
  {}
1432
1578
  )
1433
1579
  const rows = query(db, q.sql, q.params)
1434
1580
  expect(rows.length).toBe(1)
1435
- expect((JSON.parse((rows[0] as any).data) as any).name).toBe("Bob")
1581
+ expect((JSON.parse(rows[0].data) as any).name).toBe("Bob")
1436
1582
  }))
1437
1583
 
1438
1584
  it("where nested boolean path works", () =>
@@ -1448,13 +1594,51 @@ describe("boolean WHERE clauses — SQLite integration (end-to-end)", () => {
1448
1594
  const q = buildWhereSQLQuery(
1449
1595
  sqliteDialect,
1450
1596
  "id",
1451
- [{ t: "where", path: "meta.active", op: "eq", value: true as any }],
1597
+ [{ t: "where", path: "meta.active", op: "eq", value: true }],
1452
1598
  "t",
1453
1599
  {}
1454
1600
  )
1455
1601
  const rows = query(db, q.sql, q.params)
1456
1602
  expect(rows.length).toBe(1)
1457
- expect((JSON.parse((rows[0] as any).data) as any).name).toBe("Alice")
1603
+ expect((JSON.parse(rows[0].data) as any).name).toBe("Alice")
1604
+ }))
1605
+
1606
+ it("where hasKey / hasValue / hasKeyValue match Map tuple JSON", () =>
1607
+ withDb((db) => {
1608
+ db.exec(`CREATE TABLE "t" (id TEXT PRIMARY KEY, _etag TEXT, data JSON NOT NULL)`)
1609
+ db
1610
+ .prepare(`INSERT INTO "t" (id, _etag, data) VALUES (?, ?, ?)`)
1611
+ .run("1", "e", JSON.stringify({ meta: [["n", 1], ["x", 2]] }))
1612
+ db
1613
+ .prepare(`INSERT INTO "t" (id, _etag, data) VALUES (?, ?, ?)`)
1614
+ .run("2", "e", JSON.stringify({ meta: [["n", 9]] }))
1615
+
1616
+ const byKey = buildWhereSQLQuery(
1617
+ sqliteDialect,
1618
+ "id",
1619
+ [{ t: "where", path: "meta", op: "hasKey", value: "x" }],
1620
+ "t",
1621
+ {}
1622
+ )
1623
+ expect(query(db, byKey.sql, byKey.params).map((r) => r.id)).toEqual(["1"])
1624
+
1625
+ const byValue = buildWhereSQLQuery(
1626
+ sqliteDialect,
1627
+ "id",
1628
+ [{ t: "where", path: "meta", op: "hasValue", value: 9 }],
1629
+ "t",
1630
+ {}
1631
+ )
1632
+ expect(query(db, byValue.sql, byValue.params).map((r) => r.id)).toEqual(["2"])
1633
+
1634
+ const byPair = buildWhereSQLQuery(
1635
+ sqliteDialect,
1636
+ "id",
1637
+ [{ t: "where", path: "meta", op: "hasKeyValue", value: ["n", 1] }],
1638
+ "t",
1639
+ {}
1640
+ )
1641
+ expect(query(db, byPair.sql, byPair.params).map((r) => r.id)).toEqual(["1"])
1458
1642
  }))
1459
1643
 
1460
1644
  it("where neq boolean works", () =>
@@ -1470,13 +1654,13 @@ describe("boolean WHERE clauses — SQLite integration (end-to-end)", () => {
1470
1654
  const q = buildWhereSQLQuery(
1471
1655
  sqliteDialect,
1472
1656
  "id",
1473
- [{ t: "where", path: "flag", op: "neq", value: true as any }],
1657
+ [{ t: "where", path: "flag", op: "neq", value: true }],
1474
1658
  "t",
1475
1659
  {}
1476
1660
  )
1477
1661
  const rows = query(db, q.sql, q.params)
1478
1662
  expect(rows.length).toBe(1)
1479
- expect((JSON.parse((rows[0] as any).data) as any).name).toBe("Bob")
1663
+ expect((JSON.parse(rows[0].data) as any).name).toBe("Bob")
1480
1664
  }))
1481
1665
  })
1482
1666
 
@@ -1573,7 +1757,7 @@ describe("toRow strips _etag and id from data", () => {
1573
1757
  const toRow = <IdKey extends PropertyKey>(e: any, idKey: IdKey) => {
1574
1758
  const newE = makeETag(e)
1575
1759
  const id = newE[idKey] as string
1576
- const { _etag, [idKey]: _id, ...rest } = newE as any
1760
+ const { _etag, [idKey]: _id, ...rest } = newE
1577
1761
  const data = JSON.stringify(rest)
1578
1762
  return { id, _etag: newE._etag!, data, item: newE }
1579
1763
  }
@@ -1708,4 +1892,14 @@ describe("parseRow reconstructs full object from row", () => {
1708
1892
  expect(reconstructed.tags).toEqual(["admin"])
1709
1893
  expect(reconstructed._etag).toBe(newE._etag)
1710
1894
  })
1895
+
1896
+ it("lowers Date defaultValues to ISO before merge", () => {
1897
+ const result: any = parseRow(
1898
+ { id: "1", _etag: "e1", data: JSON.stringify({ name: "Alice" }) },
1899
+ "id",
1900
+ { at: new Date("2024-06-01T00:00:00.000Z") }
1901
+ )
1902
+ expect(result.at).toBe("2024-06-01T00:00:00.000Z")
1903
+ expect(result.name).toBe("Alice")
1904
+ })
1711
1905
  })