@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.
- package/CHANGELOG.md +30 -0
- package/dist/Store/Cosmos/query.d.ts +2 -1
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +60 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +17 -12
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +12 -7
- package/dist/Store/Memory.d.ts +2 -1
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +28 -15
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +15 -11
- package/dist/Store/SQL/query.d.ts +5 -1
- package/dist/Store/SQL/query.d.ts.map +1 -1
- package/dist/Store/SQL/query.js +131 -2
- package/dist/Store/SQL.d.ts +1 -1
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +24 -18
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +67 -20
- package/dist/Store/jsonDocument.d.ts +13 -0
- package/dist/Store/jsonDocument.d.ts.map +1 -0
- package/dist/Store/jsonDocument.js +31 -0
- package/dist/Store/utils.d.ts +16 -0
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +245 -6
- package/dist/logger.d.ts +5 -5
- package/examples/query.ts +4 -4
- package/package.json +3 -3
- package/src/Store/Cosmos/query.ts +65 -9
- package/src/Store/Cosmos.ts +17 -11
- package/src/Store/Disk.ts +28 -7
- package/src/Store/Memory.ts +40 -17
- package/src/Store/SQL/Pg.ts +23 -10
- package/src/Store/SQL/query.ts +152 -7
- package/src/Store/SQL.ts +37 -17
- package/src/Store/codeFilter.ts +71 -23
- package/src/Store/jsonDocument.ts +43 -0
- package/src/Store/utils.ts +275 -5
- package/test/cosmos-query.test.ts +100 -5
- package/test/dist/json-lower.test.d.ts.map +1 -0
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/json-lower.test.ts +115 -0
- package/test/query.test.ts +261 -12
- package/test/sql-store.test.ts +233 -39
package/test/sql-store.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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"]
|
|
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"]
|
|
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"]
|
|
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(
|
|
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(
|
|
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"]
|
|
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
|
|
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(
|
|
850
|
-
expect((JSON.parse(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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"]
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
})
|