@livestore/common 0.0.0-snapshot-8115ad48d5a57244358c943ecc92bb0a30274b87 → 0.0.0-snapshot-fe350f42b94ebf67d6bfd18480008befb405cf64
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/dist/.tsbuildinfo +1 -1
- package/dist/adapter-types.d.ts +2 -8
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +6 -6
- package/dist/devtools/devtools-messages-leader.d.ts +24 -24
- package/dist/leader-thread/apply-event.d.ts.map +1 -1
- package/dist/leader-thread/apply-event.js +2 -21
- package/dist/leader-thread/apply-event.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +0 -1
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/query-builder/api.d.ts +3 -3
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/query-builder/impl.js +11 -5
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.d.ts.map +1 -1
- package/dist/query-builder/impl.test.js +166 -120
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/rehydrate-from-eventlog.d.ts +2 -3
- package/dist/rehydrate-from-eventlog.d.ts.map +1 -1
- package/dist/rehydrate-from-eventlog.js +1 -3
- package/dist/rehydrate-from-eventlog.js.map +1 -1
- package/dist/schema/schema.d.ts +1 -1
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +4 -2
- package/dist/schema/table-def.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/adapter-types.ts +3 -9
- package/src/leader-thread/apply-event.ts +3 -27
- package/src/leader-thread/recreate-db.ts +0 -1
- package/src/query-builder/api.ts +3 -3
- package/src/query-builder/impl.test.ts +178 -131
- package/src/query-builder/impl.ts +11 -6
- package/src/rehydrate-from-eventlog.ts +1 -5
- package/src/schema/schema.ts +1 -1
- package/src/schema/table-def.ts +4 -2
- package/src/version.ts +1 -1
|
@@ -2,6 +2,7 @@ import { Schema } from '@livestore/utils/effect'
|
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import { State } from '../schema/mod.js'
|
|
5
|
+
import type { QueryBuilder } from './api.js'
|
|
5
6
|
import { getResultSchema } from './impl.js'
|
|
6
7
|
|
|
7
8
|
const todos = State.SQLite.table({
|
|
@@ -74,211 +75,241 @@ export const issue = State.SQLite.table({
|
|
|
74
75
|
|
|
75
76
|
const db = { todos, todosWithIntId, comments, issue, UiState, UiStateWithDefaultId }
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
78
|
+
const dump = (qb: QueryBuilder<any, any, any>) => ({
|
|
79
|
+
bindValues: qb.asSql().bindValues,
|
|
80
|
+
query: qb.asSql().query,
|
|
81
|
+
schema: getResultSchema(qb).toString(),
|
|
82
|
+
})
|
|
83
83
|
|
|
84
|
+
describe('query builder', () => {
|
|
84
85
|
describe('basic queries', () => {
|
|
85
86
|
it('should handle simple SELECT queries', () => {
|
|
86
|
-
expect(db.todos
|
|
87
|
+
expect(dump(db.todos)).toMatchInlineSnapshot(`
|
|
87
88
|
{
|
|
88
89
|
"bindValues": [],
|
|
89
90
|
"query": "SELECT * FROM 'todos'",
|
|
91
|
+
"schema": "ReadonlyArray<todos>",
|
|
90
92
|
}
|
|
91
93
|
`)
|
|
92
94
|
|
|
93
|
-
expect(db.todos.select('id')
|
|
95
|
+
expect(dump(db.todos.select('id'))).toMatchInlineSnapshot(`
|
|
94
96
|
{
|
|
95
97
|
"bindValues": [],
|
|
96
98
|
"query": "SELECT id FROM 'todos'",
|
|
99
|
+
"schema": "ReadonlyArray<({ readonly id: string } <-> string)>",
|
|
97
100
|
}
|
|
98
101
|
`)
|
|
99
102
|
|
|
100
|
-
expect(db.todos.select('id', 'text')
|
|
103
|
+
expect(dump(db.todos.select('id', 'text'))).toMatchInlineSnapshot(`
|
|
101
104
|
{
|
|
102
105
|
"bindValues": [],
|
|
103
106
|
"query": "SELECT id, text FROM 'todos'",
|
|
107
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
104
108
|
}
|
|
105
109
|
`)
|
|
106
110
|
})
|
|
107
111
|
|
|
108
|
-
it('should handle
|
|
109
|
-
expect(db.todos.select('id', 'text').
|
|
112
|
+
it('should handle .first()', () => {
|
|
113
|
+
expect(dump(db.todos.select('id', 'text').first())).toMatchInlineSnapshot(`
|
|
110
114
|
{
|
|
111
115
|
"bindValues": [
|
|
112
116
|
1,
|
|
113
117
|
],
|
|
114
|
-
"query": "SELECT id, text FROM 'todos'
|
|
118
|
+
"query": "SELECT id, text FROM 'todos' LIMIT ?",
|
|
119
|
+
"schema": "(ReadonlyArray<{ readonly id: string; readonly text: string }> <-> { readonly id: string; readonly text: string })",
|
|
115
120
|
}
|
|
116
121
|
`)
|
|
117
|
-
|
|
122
|
+
|
|
123
|
+
expect(dump(db.todos.select('id', 'text').first({ fallback: () => undefined }))).toMatchInlineSnapshot(`
|
|
118
124
|
{
|
|
119
125
|
"bindValues": [
|
|
120
126
|
1,
|
|
121
127
|
],
|
|
122
|
-
"query": "SELECT id, text FROM 'todos'
|
|
128
|
+
"query": "SELECT id, text FROM 'todos' LIMIT ?",
|
|
129
|
+
"schema": "(ReadonlyArray<{ readonly id: string; readonly text: string }> | readonly [undefined] <-> { readonly id: string; readonly text: string } | undefined)",
|
|
123
130
|
}
|
|
124
131
|
`)
|
|
125
|
-
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should handle WHERE clauses', () => {
|
|
135
|
+
expect(dump(db.todos.select('id', 'text').where('completed', true))).toMatchInlineSnapshot(`
|
|
126
136
|
{
|
|
127
137
|
"bindValues": [
|
|
128
138
|
1,
|
|
129
139
|
],
|
|
130
140
|
"query": "SELECT id, text FROM 'todos' WHERE completed = ?",
|
|
141
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
131
142
|
}
|
|
132
143
|
`)
|
|
133
|
-
expect(db.todos.select('id', 'text').where(
|
|
134
|
-
{
|
|
135
|
-
"bindValues": [],
|
|
136
|
-
"query": "SELECT id, text FROM 'todos'",
|
|
137
|
-
}
|
|
138
|
-
`)
|
|
139
|
-
expect(
|
|
140
|
-
db.todos
|
|
141
|
-
.select('id', 'text')
|
|
142
|
-
.where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } })
|
|
143
|
-
.asSql(),
|
|
144
|
-
).toMatchInlineSnapshot(`
|
|
144
|
+
expect(dump(db.todos.select('id', 'text').where('completed', '!=', true))).toMatchInlineSnapshot(`
|
|
145
145
|
{
|
|
146
146
|
"bindValues": [
|
|
147
|
-
|
|
147
|
+
1,
|
|
148
148
|
],
|
|
149
|
-
"query": "SELECT id, text FROM 'todos' WHERE
|
|
149
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed != ?",
|
|
150
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
150
151
|
}
|
|
151
152
|
`)
|
|
152
|
-
expect(
|
|
153
|
-
db.todos
|
|
154
|
-
.select('id', 'text')
|
|
155
|
-
.where({ status: { op: 'IN', value: ['active'] } })
|
|
156
|
-
.asSql(),
|
|
157
|
-
).toMatchInlineSnapshot(`
|
|
153
|
+
expect(dump(db.todos.select('id', 'text').where({ completed: true }))).toMatchInlineSnapshot(`
|
|
158
154
|
{
|
|
159
155
|
"bindValues": [
|
|
160
|
-
|
|
156
|
+
1,
|
|
161
157
|
],
|
|
162
|
-
"query": "SELECT id, text FROM 'todos' WHERE
|
|
158
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ?",
|
|
159
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
163
160
|
}
|
|
164
161
|
`)
|
|
165
|
-
expect(
|
|
166
|
-
db.todos
|
|
167
|
-
.select('id', 'text')
|
|
168
|
-
.where({ status: { op: 'NOT IN', value: ['active', 'completed'] } })
|
|
169
|
-
.asSql(),
|
|
170
|
-
).toMatchInlineSnapshot(`
|
|
162
|
+
expect(dump(db.todos.select('id', 'text').where({ completed: undefined }))).toMatchInlineSnapshot(`
|
|
171
163
|
{
|
|
172
|
-
"bindValues": [
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
],
|
|
176
|
-
"query": "SELECT id, text FROM 'todos' WHERE status NOT IN (?, ?)",
|
|
164
|
+
"bindValues": [],
|
|
165
|
+
"query": "SELECT id, text FROM 'todos'",
|
|
166
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
177
167
|
}
|
|
178
168
|
`)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
it('should handle OFFSET and LIMIT clauses', () => {
|
|
182
|
-
expect(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10).asSql())
|
|
169
|
+
expect(dump(db.todos.select('id', 'text').where({ deletedAt: { op: '<=', value: new Date('2024-01-01') } })))
|
|
183
170
|
.toMatchInlineSnapshot(`
|
|
184
171
|
{
|
|
185
172
|
"bindValues": [
|
|
186
|
-
|
|
187
|
-
10,
|
|
188
|
-
10,
|
|
173
|
+
"2024-01-01T00:00:00.000Z",
|
|
189
174
|
],
|
|
190
|
-
"query": "SELECT id, text FROM 'todos' WHERE
|
|
175
|
+
"query": "SELECT id, text FROM 'todos' WHERE deletedAt <= ?",
|
|
176
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
191
177
|
}
|
|
192
178
|
`)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
it('should handle OFFSET and LIMIT clauses correctly', () => {
|
|
196
|
-
// Test with both offset and limit
|
|
197
|
-
expect(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10).asSql()).toMatchInlineSnapshot(`
|
|
179
|
+
expect(dump(db.todos.select('id', 'text').where({ status: { op: 'IN', value: ['active'] } })))
|
|
180
|
+
.toMatchInlineSnapshot(`
|
|
198
181
|
{
|
|
199
182
|
"bindValues": [
|
|
200
|
-
|
|
201
|
-
5,
|
|
202
|
-
10,
|
|
183
|
+
"active",
|
|
203
184
|
],
|
|
204
|
-
"query": "SELECT id, text FROM 'todos' WHERE
|
|
185
|
+
"query": "SELECT id, text FROM 'todos' WHERE status IN (?)",
|
|
186
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
205
187
|
}
|
|
206
188
|
`)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
expect(db.todos.select('id', 'text').where('completed', true).offset(5).asSql()).toMatchInlineSnapshot(`
|
|
189
|
+
expect(dump(db.todos.select('id', 'text').where({ status: { op: 'NOT IN', value: ['active', 'completed'] } })))
|
|
190
|
+
.toMatchInlineSnapshot(`
|
|
210
191
|
{
|
|
211
192
|
"bindValues": [
|
|
212
|
-
|
|
213
|
-
|
|
193
|
+
"active",
|
|
194
|
+
"completed",
|
|
214
195
|
],
|
|
215
|
-
"query": "SELECT id, text FROM 'todos' WHERE
|
|
196
|
+
"query": "SELECT id, text FROM 'todos' WHERE status NOT IN (?, ?)",
|
|
197
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
216
198
|
}
|
|
217
199
|
`)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should handle OFFSET and LIMIT clauses', () => {
|
|
203
|
+
expect(dump(db.todos.select('id', 'text').where('completed', true).offset(10).limit(10))).toMatchInlineSnapshot(`
|
|
204
|
+
{
|
|
205
|
+
"bindValues": [
|
|
206
|
+
1,
|
|
207
|
+
10,
|
|
208
|
+
10,
|
|
209
|
+
],
|
|
210
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ? LIMIT ?",
|
|
211
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
212
|
+
}
|
|
213
|
+
`)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should handle OFFSET and LIMIT clauses correctly', () => {
|
|
217
|
+
// Test with both offset and limit
|
|
218
|
+
expect(dump(db.todos.select('id', 'text').where('completed', true).offset(5).limit(10))).toMatchInlineSnapshot(`
|
|
219
|
+
{
|
|
220
|
+
"bindValues": [
|
|
221
|
+
1,
|
|
222
|
+
5,
|
|
223
|
+
10,
|
|
224
|
+
],
|
|
225
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ? LIMIT ?",
|
|
226
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
227
|
+
}
|
|
228
|
+
`)
|
|
229
|
+
|
|
230
|
+
// Test with only offset
|
|
231
|
+
expect(dump(db.todos.select('id', 'text').where('completed', true).offset(5))).toMatchInlineSnapshot(`
|
|
232
|
+
{
|
|
233
|
+
"bindValues": [
|
|
234
|
+
1,
|
|
235
|
+
5,
|
|
236
|
+
],
|
|
237
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ?",
|
|
238
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
239
|
+
}
|
|
240
|
+
`)
|
|
218
241
|
|
|
219
242
|
// Test with only limit
|
|
220
|
-
expect(db.todos.select('id', 'text').where('completed', true).limit(10)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
243
|
+
expect(dump(db.todos.select('id', 'text').where('completed', true).limit(10))).toMatchInlineSnapshot(`
|
|
244
|
+
{
|
|
245
|
+
"bindValues": [
|
|
246
|
+
1,
|
|
247
|
+
10,
|
|
248
|
+
],
|
|
249
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? LIMIT ?",
|
|
250
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
251
|
+
}
|
|
252
|
+
`)
|
|
229
253
|
})
|
|
230
254
|
|
|
231
255
|
it('should handle COUNT queries', () => {
|
|
232
|
-
expect(db.todos.count()
|
|
256
|
+
expect(dump(db.todos.count())).toMatchInlineSnapshot(`
|
|
233
257
|
{
|
|
234
258
|
"bindValues": [],
|
|
235
259
|
"query": "SELECT COUNT(*) as count FROM 'todos'",
|
|
260
|
+
"schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
|
|
236
261
|
}
|
|
237
262
|
`)
|
|
238
|
-
expect(db.todos.count().where('completed', true)
|
|
263
|
+
expect(dump(db.todos.count().where('completed', true))).toMatchInlineSnapshot(`
|
|
239
264
|
{
|
|
240
265
|
"bindValues": [
|
|
241
266
|
1,
|
|
242
267
|
],
|
|
243
268
|
"query": "SELECT COUNT(*) as count FROM 'todos' WHERE completed = ?",
|
|
269
|
+
"schema": "(ReadonlyArray<({ readonly count: number } <-> number)> <-> number)",
|
|
244
270
|
}
|
|
245
271
|
`)
|
|
246
272
|
})
|
|
247
273
|
|
|
248
274
|
it('should handle NULL comparisons', () => {
|
|
249
|
-
expect(db.todos.select('id', 'text').where('deletedAt', '=', null)
|
|
275
|
+
expect(dump(db.todos.select('id', 'text').where('deletedAt', '=', null))).toMatchInlineSnapshot(`
|
|
250
276
|
{
|
|
251
277
|
"bindValues": [],
|
|
252
278
|
"query": "SELECT id, text FROM 'todos' WHERE deletedAt IS NULL",
|
|
279
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
253
280
|
}
|
|
254
281
|
`)
|
|
255
|
-
expect(db.todos.select('id', 'text').where('deletedAt', '!=', null)
|
|
282
|
+
expect(dump(db.todos.select('id', 'text').where('deletedAt', '!=', null))).toMatchInlineSnapshot(`
|
|
256
283
|
{
|
|
257
284
|
"bindValues": [],
|
|
258
285
|
"query": "SELECT id, text FROM 'todos' WHERE deletedAt IS NOT NULL",
|
|
286
|
+
"schema": "ReadonlyArray<{ readonly id: string; readonly text: string }>",
|
|
259
287
|
}
|
|
260
288
|
`)
|
|
261
289
|
})
|
|
262
290
|
|
|
263
291
|
it('should handle orderBy', () => {
|
|
264
|
-
expect(db.todos.orderBy('completed', 'desc')
|
|
292
|
+
expect(dump(db.todos.orderBy('completed', 'desc'))).toMatchInlineSnapshot(`
|
|
265
293
|
{
|
|
266
294
|
"bindValues": [],
|
|
267
295
|
"query": "SELECT * FROM 'todos' ORDER BY completed desc",
|
|
296
|
+
"schema": "ReadonlyArray<todos>",
|
|
268
297
|
}
|
|
269
298
|
`)
|
|
270
299
|
|
|
271
|
-
expect(db.todos.orderBy([{ col: 'completed', direction: 'desc' }])
|
|
300
|
+
expect(dump(db.todos.orderBy([{ col: 'completed', direction: 'desc' }]))).toMatchInlineSnapshot(`
|
|
272
301
|
{
|
|
273
302
|
"bindValues": [],
|
|
274
303
|
"query": "SELECT * FROM 'todos' ORDER BY completed desc",
|
|
304
|
+
"schema": "ReadonlyArray<todos>",
|
|
275
305
|
}
|
|
276
306
|
`)
|
|
277
307
|
|
|
278
|
-
expect(db.todos.orderBy([])
|
|
308
|
+
expect(dump(db.todos.orderBy([]))).toMatchInlineSnapshot(`
|
|
279
309
|
{
|
|
280
310
|
"bindValues": [],
|
|
281
311
|
"query": "SELECT * FROM 'todos'",
|
|
312
|
+
"schema": "ReadonlyArray<todos>",
|
|
282
313
|
}
|
|
283
314
|
`)
|
|
284
315
|
})
|
|
@@ -286,31 +317,34 @@ describe('query builder', () => {
|
|
|
286
317
|
|
|
287
318
|
// describe('getOrCreate queries', () => {
|
|
288
319
|
// it('should handle getOrCreate queries', () => {
|
|
289
|
-
// expect(db.UiState.getOrCreate('sessionid-1')
|
|
320
|
+
// expect(dump(db.UiState.getOrCreate('sessionid-1'))).toMatchInlineSnapshot(`
|
|
290
321
|
// {
|
|
291
322
|
// "bindValues": [
|
|
292
323
|
// "sessionid-1",
|
|
293
324
|
// ],
|
|
294
325
|
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
326
|
+
// "schema": "...", // TODO determine schema
|
|
295
327
|
// }
|
|
296
328
|
// `)
|
|
297
329
|
// })
|
|
298
330
|
|
|
299
331
|
// it('should handle getOrCreate queries with default id', () => {
|
|
300
|
-
// expect(db.UiStateWithDefaultId.getOrCreate()
|
|
332
|
+
// expect(dump(db.UiStateWithDefaultId.getOrCreate())).toMatchInlineSnapshot(`
|
|
301
333
|
// {
|
|
302
334
|
// "bindValues": [],
|
|
303
335
|
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
336
|
+
// "schema": "...", // TODO determine schema
|
|
304
337
|
// }
|
|
305
338
|
// `)
|
|
306
339
|
// })
|
|
307
340
|
// // it('should handle row queries with numbers', () => {
|
|
308
|
-
// // expect(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } })
|
|
341
|
+
// // expect(dump(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } }))).toMatchInlineSnapshot(`
|
|
309
342
|
// // {
|
|
310
343
|
// // "bindValues": [
|
|
311
344
|
// // 123,
|
|
312
345
|
// // ],
|
|
313
346
|
// // "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
|
|
347
|
+
// // "schema": "...", // TODO determine schema
|
|
314
348
|
// // }
|
|
315
349
|
// // `)
|
|
316
350
|
// // })
|
|
@@ -318,7 +352,7 @@ describe('query builder', () => {
|
|
|
318
352
|
|
|
319
353
|
describe('write operations', () => {
|
|
320
354
|
it('should handle INSERT queries', () => {
|
|
321
|
-
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
355
|
+
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }))).toMatchInlineSnapshot(`
|
|
322
356
|
{
|
|
323
357
|
"bindValues": [
|
|
324
358
|
"123",
|
|
@@ -326,12 +360,13 @@ describe('query builder', () => {
|
|
|
326
360
|
"active",
|
|
327
361
|
],
|
|
328
362
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
363
|
+
"schema": "number",
|
|
329
364
|
}
|
|
330
365
|
`)
|
|
331
366
|
})
|
|
332
367
|
|
|
333
368
|
it('should handle INSERT queries with undefined values', () => {
|
|
334
|
-
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active', completed: undefined })
|
|
369
|
+
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active', completed: undefined })))
|
|
335
370
|
.toMatchInlineSnapshot(`
|
|
336
371
|
{
|
|
337
372
|
"bindValues": [
|
|
@@ -340,6 +375,7 @@ describe('query builder', () => {
|
|
|
340
375
|
"active",
|
|
341
376
|
],
|
|
342
377
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
378
|
+
"schema": "number",
|
|
343
379
|
}
|
|
344
380
|
`)
|
|
345
381
|
})
|
|
@@ -347,8 +383,8 @@ describe('query builder', () => {
|
|
|
347
383
|
// Test helped to catch a bindValues ordering bug
|
|
348
384
|
it('should handle INSERT queries (issue)', () => {
|
|
349
385
|
expect(
|
|
350
|
-
|
|
351
|
-
.insert({
|
|
386
|
+
dump(
|
|
387
|
+
db.issue.insert({
|
|
352
388
|
id: 1,
|
|
353
389
|
title: 'Revert the user profile page',
|
|
354
390
|
priority: 2,
|
|
@@ -356,8 +392,8 @@ describe('query builder', () => {
|
|
|
356
392
|
modified: new Date('2024-12-29T17:15:20.507Z'),
|
|
357
393
|
kanbanorder: 'a2',
|
|
358
394
|
creator: 'John Doe',
|
|
359
|
-
})
|
|
360
|
-
|
|
395
|
+
}),
|
|
396
|
+
),
|
|
361
397
|
).toMatchInlineSnapshot(`
|
|
362
398
|
{
|
|
363
399
|
"bindValues": [
|
|
@@ -370,32 +406,35 @@ describe('query builder', () => {
|
|
|
370
406
|
"John Doe",
|
|
371
407
|
],
|
|
372
408
|
"query": "INSERT INTO 'issue' (id, title, priority, created, modified, kanbanorder, creator) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
409
|
+
"schema": "number",
|
|
373
410
|
}
|
|
374
411
|
`)
|
|
375
412
|
})
|
|
376
413
|
|
|
377
414
|
it('should handle UPDATE queries', () => {
|
|
378
|
-
expect(db.todos.update({ status: 'completed' }).where({ id: '123' })
|
|
415
|
+
expect(dump(db.todos.update({ status: 'completed' }).where({ id: '123' }))).toMatchInlineSnapshot(`
|
|
379
416
|
{
|
|
380
417
|
"bindValues": [
|
|
381
418
|
"completed",
|
|
382
419
|
"123",
|
|
383
420
|
],
|
|
384
421
|
"query": "UPDATE 'todos' SET status = ? WHERE id = ?",
|
|
422
|
+
"schema": "number",
|
|
385
423
|
}
|
|
386
424
|
`)
|
|
387
425
|
|
|
388
426
|
// empty update set
|
|
389
|
-
expect(db.todos.update({}).where({ id: '123' })
|
|
427
|
+
expect(dump(db.todos.update({}).where({ id: '123' }))).toMatchInlineSnapshot(`
|
|
390
428
|
{
|
|
391
429
|
"bindValues": [],
|
|
392
430
|
"query": "SELECT 1",
|
|
431
|
+
"schema": "number",
|
|
393
432
|
}
|
|
394
433
|
`)
|
|
395
434
|
})
|
|
396
435
|
|
|
397
436
|
it('should handle UPDATE queries with undefined values', () => {
|
|
398
|
-
expect(db.todos.update({ status: undefined, text: 'some text' }).where({ id: '123' })
|
|
437
|
+
expect(dump(db.todos.update({ status: undefined, text: 'some text' }).where({ id: '123' })))
|
|
399
438
|
.toMatchInlineSnapshot(`
|
|
400
439
|
{
|
|
401
440
|
"bindValues": [
|
|
@@ -403,12 +442,13 @@ describe('query builder', () => {
|
|
|
403
442
|
"123",
|
|
404
443
|
],
|
|
405
444
|
"query": "UPDATE 'todos' SET text = ? WHERE id = ?",
|
|
445
|
+
"schema": "number",
|
|
406
446
|
}
|
|
407
447
|
`)
|
|
408
448
|
})
|
|
409
449
|
|
|
410
450
|
it('should handle UPDATE queries with undefined values (issue)', () => {
|
|
411
|
-
expect(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 })
|
|
451
|
+
expect(dump(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }))).toMatchInlineSnapshot(`
|
|
412
452
|
{
|
|
413
453
|
"bindValues": [
|
|
414
454
|
2,
|
|
@@ -416,23 +456,25 @@ describe('query builder', () => {
|
|
|
416
456
|
1,
|
|
417
457
|
],
|
|
418
458
|
"query": "UPDATE 'issue' SET priority = ?, creator = ? WHERE id = ?",
|
|
459
|
+
"schema": "number",
|
|
419
460
|
}
|
|
420
461
|
`)
|
|
421
462
|
})
|
|
422
463
|
|
|
423
464
|
it('should handle DELETE queries', () => {
|
|
424
|
-
expect(db.todos.delete().where({ status: 'completed' })
|
|
465
|
+
expect(dump(db.todos.delete().where({ status: 'completed' }))).toMatchInlineSnapshot(`
|
|
425
466
|
{
|
|
426
467
|
"bindValues": [
|
|
427
468
|
"completed",
|
|
428
469
|
],
|
|
429
470
|
"query": "DELETE FROM 'todos' WHERE status = ?",
|
|
471
|
+
"schema": "number",
|
|
430
472
|
}
|
|
431
473
|
`)
|
|
432
474
|
})
|
|
433
475
|
|
|
434
476
|
it('should handle INSERT with ON CONFLICT', () => {
|
|
435
|
-
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore')
|
|
477
|
+
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'ignore')))
|
|
436
478
|
.toMatchInlineSnapshot(`
|
|
437
479
|
{
|
|
438
480
|
"bindValues": [
|
|
@@ -441,14 +483,16 @@ describe('query builder', () => {
|
|
|
441
483
|
"active",
|
|
442
484
|
],
|
|
443
485
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING",
|
|
486
|
+
"schema": "number",
|
|
444
487
|
}
|
|
445
488
|
`)
|
|
446
489
|
|
|
447
490
|
expect(
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
491
|
+
dump(
|
|
492
|
+
db.todos
|
|
493
|
+
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
494
|
+
.onConflict('id', 'update', { text: 'Buy soy milk', status: 'active' }),
|
|
495
|
+
),
|
|
452
496
|
).toMatchInlineSnapshot(`
|
|
453
497
|
{
|
|
454
498
|
"bindValues": [
|
|
@@ -459,10 +503,11 @@ describe('query builder', () => {
|
|
|
459
503
|
"active",
|
|
460
504
|
],
|
|
461
505
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET text = ?, status = ?",
|
|
506
|
+
"schema": "number",
|
|
462
507
|
}
|
|
463
508
|
`)
|
|
464
509
|
|
|
465
|
-
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace')
|
|
510
|
+
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace')))
|
|
466
511
|
.toMatchInlineSnapshot(`
|
|
467
512
|
{
|
|
468
513
|
"bindValues": [
|
|
@@ -471,16 +516,14 @@ describe('query builder', () => {
|
|
|
471
516
|
"active",
|
|
472
517
|
],
|
|
473
518
|
"query": "INSERT OR REPLACE INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
519
|
+
"schema": "number",
|
|
474
520
|
}
|
|
475
521
|
`)
|
|
476
522
|
})
|
|
477
523
|
|
|
478
524
|
it('should handle ON CONFLICT with multiple columns', () => {
|
|
479
525
|
expect(
|
|
480
|
-
db.todos
|
|
481
|
-
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
482
|
-
.onConflict(['id', 'status'], 'ignore')
|
|
483
|
-
.asSql(),
|
|
526
|
+
dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict(['id', 'status'], 'ignore')),
|
|
484
527
|
).toMatchInlineSnapshot(`
|
|
485
528
|
{
|
|
486
529
|
"bindValues": [
|
|
@@ -489,40 +532,44 @@ describe('query builder', () => {
|
|
|
489
532
|
"active",
|
|
490
533
|
],
|
|
491
534
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id, status) DO NOTHING",
|
|
535
|
+
"schema": "number",
|
|
492
536
|
}
|
|
493
537
|
`)
|
|
494
538
|
})
|
|
495
539
|
|
|
496
540
|
it('should handle RETURNING clause', () => {
|
|
497
|
-
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')
|
|
541
|
+
expect(dump(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).returning('id')))
|
|
498
542
|
.toMatchInlineSnapshot(`
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
543
|
+
{
|
|
544
|
+
"bindValues": [
|
|
545
|
+
"123",
|
|
546
|
+
"Buy milk",
|
|
547
|
+
"active",
|
|
548
|
+
],
|
|
549
|
+
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) RETURNING id",
|
|
550
|
+
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
551
|
+
}
|
|
552
|
+
`)
|
|
508
553
|
|
|
509
|
-
expect(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id')
|
|
554
|
+
expect(dump(db.todos.update({ status: 'completed' }).where({ id: '123' }).returning('id')))
|
|
510
555
|
.toMatchInlineSnapshot(`
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
556
|
+
{
|
|
557
|
+
"bindValues": [
|
|
558
|
+
"completed",
|
|
559
|
+
"123",
|
|
560
|
+
],
|
|
561
|
+
"query": "UPDATE 'todos' SET status = ? WHERE id = ? RETURNING id",
|
|
562
|
+
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
563
|
+
}
|
|
564
|
+
`)
|
|
519
565
|
|
|
520
|
-
expect(db.todos.delete().where({ status: 'completed' }).returning('id')
|
|
566
|
+
expect(dump(db.todos.delete().where({ status: 'completed' }).returning('id'))).toMatchInlineSnapshot(`
|
|
521
567
|
{
|
|
522
568
|
"bindValues": [
|
|
523
569
|
"completed",
|
|
524
570
|
],
|
|
525
571
|
"query": "DELETE FROM 'todos' WHERE status = ? RETURNING id",
|
|
572
|
+
"schema": "ReadonlyArray<{ readonly id: string }>",
|
|
526
573
|
}
|
|
527
574
|
`)
|
|
528
575
|
})
|
|
@@ -144,8 +144,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends State.SQLite.TableDe
|
|
|
144
144
|
return makeQueryBuilder(tableDef, {
|
|
145
145
|
...ast,
|
|
146
146
|
limit: Option.some(1),
|
|
147
|
-
|
|
148
|
-
pickFirst: options?.fallback ? { fallback: options.fallback } : { fallback: () => undefined },
|
|
147
|
+
pickFirst: options?.fallback ? { fallback: options.fallback } : 'no-fallback',
|
|
149
148
|
})
|
|
150
149
|
},
|
|
151
150
|
// // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
@@ -309,11 +308,17 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
|
|
|
309
308
|
switch (queryAst._tag) {
|
|
310
309
|
case 'SelectQuery': {
|
|
311
310
|
const arraySchema = Schema.Array(queryAst.resultSchemaSingle)
|
|
312
|
-
if (queryAst.pickFirst
|
|
313
|
-
return arraySchema
|
|
311
|
+
if (queryAst.pickFirst === false) {
|
|
312
|
+
return arraySchema
|
|
313
|
+
} else if (queryAst.pickFirst === 'no-fallback') {
|
|
314
|
+
// Will throw if the array is empty
|
|
315
|
+
return arraySchema.pipe(Schema.headOrElse())
|
|
316
|
+
} else {
|
|
317
|
+
const fallbackValue = queryAst.pickFirst.fallback()
|
|
318
|
+
return Schema.Union(arraySchema, Schema.Tuple(Schema.Literal(fallbackValue))).pipe(
|
|
319
|
+
Schema.headOrElse(() => fallbackValue),
|
|
320
|
+
)
|
|
314
321
|
}
|
|
315
|
-
|
|
316
|
-
return arraySchema
|
|
317
322
|
}
|
|
318
323
|
case 'CountQuery': {
|
|
319
324
|
return Schema.Struct({ count: Schema.Number }).pipe(Schema.pluck('count'), Schema.Array, Schema.headOrElse())
|