@livestore/common 0.0.0-snapshot-2b8a9de3ec1a701aca891ebc2c98eb328274ae9e → 0.0.0-snapshot-2c861249e50661661613204300b1fc0d902c2e46
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/__tests__/fixture.d.ts +83 -221
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +33 -11
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +36 -22
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +20 -8
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +1 -0
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +13 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +6 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +46 -46
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +12 -13
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +34 -12
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +284 -226
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/apply-event.d.ts +16 -0
- package/dist/leader-thread/apply-event.d.ts.map +1 -0
- package/dist/leader-thread/apply-event.js +122 -0
- package/dist/leader-thread/apply-event.js.map +1 -0
- package/dist/leader-thread/eventlog.d.ts +27 -0
- package/dist/leader-thread/eventlog.d.ts.map +1 -0
- package/dist/leader-thread/eventlog.js +123 -0
- package/dist/leader-thread/eventlog.js.map +1 -0
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +22 -23
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +16 -4
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +36 -41
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +1 -1
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -1
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +7 -7
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +40 -25
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/materializer-helper.d.ts +23 -0
- package/dist/materializer-helper.d.ts.map +1 -0
- package/dist/materializer-helper.js +70 -0
- package/dist/materializer-helper.js.map +1 -0
- package/dist/query-builder/api.d.ts +55 -50
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/api.js +3 -5
- package/dist/query-builder/api.js.map +1 -1
- package/dist/query-builder/astToSql.d.ts.map +1 -1
- package/dist/query-builder/astToSql.js +59 -37
- package/dist/query-builder/astToSql.js.map +1 -1
- package/dist/query-builder/impl.d.ts +2 -3
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/query-builder/impl.js +47 -43
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.d.ts +86 -1
- package/dist/query-builder/impl.test.d.ts.map +1 -1
- package/dist/query-builder/impl.test.js +223 -36
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/rehydrate-from-eventlog.d.ts +15 -0
- package/dist/rehydrate-from-eventlog.d.ts.map +1 -0
- package/dist/{rehydrate-from-mutationlog.js → rehydrate-from-eventlog.js} +27 -28
- package/dist/rehydrate-from-eventlog.js.map +1 -0
- package/dist/schema/EventDef.d.ts +136 -0
- package/dist/schema/EventDef.d.ts.map +1 -0
- package/dist/schema/EventDef.js +58 -0
- package/dist/schema/EventDef.js.map +1 -0
- package/dist/schema/EventId.d.ts +10 -1
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +24 -3
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +255 -0
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent.js +118 -0
- package/dist/schema/LiveStoreEvent.js.map +1 -0
- package/dist/schema/client-document-def.d.ts +223 -0
- package/dist/schema/client-document-def.d.ts.map +1 -0
- package/dist/schema/client-document-def.js +164 -0
- package/dist/schema/client-document-def.js.map +1 -0
- package/dist/schema/client-document-def.test.d.ts +2 -0
- package/dist/schema/client-document-def.test.d.ts.map +1 -0
- package/dist/schema/client-document-def.test.js +161 -0
- package/dist/schema/client-document-def.test.js.map +1 -0
- package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/events.d.ts +2 -0
- package/dist/schema/events.d.ts.map +1 -0
- package/dist/schema/events.js +2 -0
- package/dist/schema/events.js.map +1 -0
- package/dist/schema/mod.d.ts +4 -3
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +4 -3
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +26 -22
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +45 -43
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/sqlite-state.d.ts +12 -0
- package/dist/schema/sqlite-state.d.ts.map +1 -0
- package/dist/schema/sqlite-state.js +36 -0
- package/dist/schema/sqlite-state.js.map +1 -0
- package/dist/schema/system-tables.d.ts +121 -85
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +68 -43
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema/table-def.d.ts +26 -96
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +14 -64
- package/dist/schema/table-def.js.map +1 -1
- package/dist/schema/view.d.ts +3 -0
- package/dist/schema/view.d.ts.map +1 -0
- package/dist/schema/view.js +3 -0
- package/dist/schema/view.js.map +1 -0
- package/dist/schema-management/common.d.ts +4 -4
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +6 -6
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/schema-management/validate-mutation-defs.d.ts +3 -3
- package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
- package/dist/schema-management/validate-mutation-defs.js +17 -17
- package/dist/schema-management/validate-mutation-defs.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +16 -8
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +50 -43
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/facts.d.ts +19 -19
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +2 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +3 -3
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +1 -1
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/history-dag.js +1 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +7 -7
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +5 -5
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +38 -33
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +71 -71
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +25 -25
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
- package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +60 -25
- package/dist/sync/next/test/event-fixtures.js.map +1 -0
- package/dist/sync/next/test/mod.d.ts +1 -1
- package/dist/sync/next/test/mod.d.ts.map +1 -1
- package/dist/sync/next/test/mod.js +1 -1
- package/dist/sync/next/test/mod.js.map +1 -1
- package/dist/sync/sync.d.ts +8 -7
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +69 -93
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +143 -146
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +208 -289
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixture.ts +36 -15
- package/src/adapter-types.ts +34 -23
- package/src/debug-info.ts +1 -0
- package/src/devtools/devtools-messages-common.ts +9 -0
- package/src/devtools/devtools-messages-leader.ts +14 -15
- package/src/index.ts +2 -5
- package/src/leader-thread/LeaderSyncProcessor.ts +485 -389
- package/src/leader-thread/apply-event.ts +197 -0
- package/src/leader-thread/eventlog.ts +199 -0
- package/src/leader-thread/leader-worker-devtools.ts +23 -25
- package/src/leader-thread/make-leader-thread-layer.ts +68 -61
- package/src/leader-thread/mod.ts +1 -1
- package/src/leader-thread/recreate-db.ts +7 -8
- package/src/leader-thread/types.ts +39 -29
- package/src/materializer-helper.ts +110 -0
- package/src/query-builder/api.ts +76 -102
- package/src/query-builder/astToSql.ts +68 -39
- package/src/query-builder/impl.test.ts +239 -42
- package/src/query-builder/impl.ts +66 -54
- package/src/{rehydrate-from-mutationlog.ts → rehydrate-from-eventlog.ts} +37 -40
- package/src/schema/EventDef.ts +216 -0
- package/src/schema/EventId.ts +30 -4
- package/src/schema/LiveStoreEvent.ts +239 -0
- package/src/schema/client-document-def.test.ts +188 -0
- package/src/schema/client-document-def.ts +436 -0
- package/src/schema/db-schema/dsl/mod.ts +0 -1
- package/src/schema/events.ts +1 -0
- package/src/schema/mod.ts +4 -3
- package/src/schema/schema.ts +78 -68
- package/src/schema/sqlite-state.ts +62 -0
- package/src/schema/system-tables.ts +54 -46
- package/src/schema/table-def.ts +51 -209
- package/src/schema/view.ts +2 -0
- package/src/schema-management/common.ts +4 -4
- package/src/schema-management/migrations.ts +8 -9
- package/src/schema-management/validate-mutation-defs.ts +22 -24
- package/src/sync/ClientSessionSyncProcessor.ts +66 -53
- package/src/sync/next/facts.ts +31 -32
- package/src/sync/next/history-dag-common.ts +4 -4
- package/src/sync/next/history-dag.ts +1 -1
- package/src/sync/next/rebase-events.ts +13 -13
- package/src/sync/next/test/compact-events.calculator.test.ts +45 -45
- package/src/sync/next/test/compact-events.test.ts +73 -73
- package/src/sync/next/test/event-fixtures.ts +219 -0
- package/src/sync/next/test/mod.ts +1 -1
- package/src/sync/sync.ts +9 -12
- package/src/sync/syncstate.test.ts +236 -323
- package/src/sync/syncstate.ts +218 -203
- package/src/sync/validate-push-payload.ts +2 -2
- package/src/version.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/derived-mutations.d.ts +0 -109
- package/dist/derived-mutations.d.ts.map +0 -1
- package/dist/derived-mutations.js +0 -54
- package/dist/derived-mutations.js.map +0 -1
- package/dist/derived-mutations.test.d.ts +0 -2
- package/dist/derived-mutations.test.d.ts.map +0 -1
- package/dist/derived-mutations.test.js +0 -93
- package/dist/derived-mutations.test.js.map +0 -1
- package/dist/init-singleton-tables.d.ts +0 -4
- package/dist/init-singleton-tables.d.ts.map +0 -1
- package/dist/init-singleton-tables.js +0 -16
- package/dist/init-singleton-tables.js.map +0 -1
- package/dist/leader-thread/apply-mutation.d.ts +0 -11
- package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
- package/dist/leader-thread/apply-mutation.js +0 -115
- package/dist/leader-thread/apply-mutation.js.map +0 -1
- package/dist/leader-thread/mutationlog.d.ts +0 -11
- package/dist/leader-thread/mutationlog.d.ts.map +0 -1
- package/dist/leader-thread/mutationlog.js +0 -31
- package/dist/leader-thread/mutationlog.js.map +0 -1
- package/dist/leader-thread/pull-queue-set.d.ts +0 -7
- package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
- package/dist/leader-thread/pull-queue-set.js +0 -48
- package/dist/leader-thread/pull-queue-set.js.map +0 -1
- package/dist/mutation.d.ts +0 -20
- package/dist/mutation.d.ts.map +0 -1
- package/dist/mutation.js +0 -68
- package/dist/mutation.js.map +0 -1
- package/dist/query-info.d.ts +0 -41
- package/dist/query-info.d.ts.map +0 -1
- package/dist/query-info.js +0 -7
- package/dist/query-info.js.map +0 -1
- package/dist/rehydrate-from-mutationlog.d.ts +0 -14
- package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
- package/dist/rehydrate-from-mutationlog.js.map +0 -1
- package/dist/schema/MutationEvent.d.ts +0 -202
- package/dist/schema/MutationEvent.d.ts.map +0 -1
- package/dist/schema/MutationEvent.js +0 -105
- package/dist/schema/MutationEvent.js.map +0 -1
- package/dist/schema/mutations.d.ts +0 -115
- package/dist/schema/mutations.d.ts.map +0 -1
- package/dist/schema/mutations.js +0 -42
- package/dist/schema/mutations.js.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
- package/src/derived-mutations.test.ts +0 -101
- package/src/derived-mutations.ts +0 -170
- package/src/init-singleton-tables.ts +0 -24
- package/src/leader-thread/apply-mutation.ts +0 -187
- package/src/leader-thread/mutationlog.ts +0 -49
- package/src/leader-thread/pull-queue-set.ts +0 -67
- package/src/mutation.ts +0 -108
- package/src/query-info.ts +0 -83
- package/src/schema/MutationEvent.ts +0 -224
- package/src/schema/mutations.ts +0 -193
- package/src/sync/next/test/mutation-fixtures.ts +0 -228
|
@@ -1,40 +1,78 @@
|
|
|
1
1
|
import { Schema } from '@livestore/utils/effect'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { State } from '../schema/mod.js'
|
|
5
5
|
import { getResultSchema } from './impl.js'
|
|
6
6
|
|
|
7
|
-
const todos =
|
|
8
|
-
'todos',
|
|
9
|
-
{
|
|
10
|
-
id:
|
|
11
|
-
text:
|
|
12
|
-
completed:
|
|
13
|
-
status:
|
|
14
|
-
deletedAt:
|
|
7
|
+
const todos = State.SQLite.table({
|
|
8
|
+
name: 'todos',
|
|
9
|
+
columns: {
|
|
10
|
+
id: State.SQLite.text({ primaryKey: true }),
|
|
11
|
+
text: State.SQLite.text({ default: '', nullable: false }),
|
|
12
|
+
completed: State.SQLite.boolean({ default: false, nullable: false }),
|
|
13
|
+
status: State.SQLite.text({ schema: Schema.Literal('active', 'completed') }),
|
|
14
|
+
deletedAt: State.SQLite.datetime({ nullable: true }),
|
|
15
15
|
// TODO consider leaning more into Effect schema
|
|
16
|
-
// other: Schema.Number.pipe(
|
|
16
|
+
// other: Schema.Number.pipe(State.SQLite.asInteger),
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const todosWithIntId = State.SQLite.table({
|
|
21
|
+
name: 'todos_with_int_id',
|
|
22
|
+
columns: {
|
|
23
|
+
id: State.SQLite.integer({ primaryKey: true }),
|
|
24
|
+
text: State.SQLite.text({ default: '', nullable: false }),
|
|
25
|
+
status: State.SQLite.text({ schema: Schema.Literal('active', 'completed') }),
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const comments = State.SQLite.table({
|
|
30
|
+
name: 'comments',
|
|
31
|
+
columns: {
|
|
32
|
+
id: State.SQLite.text({ primaryKey: true }),
|
|
33
|
+
text: State.SQLite.text({ default: '', nullable: false }),
|
|
34
|
+
todoId: State.SQLite.text({}),
|
|
27
35
|
},
|
|
28
|
-
|
|
29
|
-
)
|
|
36
|
+
})
|
|
30
37
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
const UiState = State.SQLite.clientDocument({
|
|
39
|
+
name: 'UiState',
|
|
40
|
+
schema: Schema.Struct({
|
|
41
|
+
filter: Schema.Literal('all', 'active', 'completed'),
|
|
42
|
+
}),
|
|
43
|
+
default: { value: { filter: 'all' } },
|
|
35
44
|
})
|
|
36
45
|
|
|
37
|
-
const
|
|
46
|
+
const UiStateWithDefaultId = State.SQLite.clientDocument({
|
|
47
|
+
name: 'UiState',
|
|
48
|
+
schema: Schema.Struct({
|
|
49
|
+
filter: Schema.Literal('all', 'active', 'completed'),
|
|
50
|
+
}),
|
|
51
|
+
default: {
|
|
52
|
+
id: 'static',
|
|
53
|
+
value: { filter: 'all' },
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const issue = State.SQLite.table({
|
|
58
|
+
name: 'issue',
|
|
59
|
+
columns: {
|
|
60
|
+
id: State.SQLite.integer({ primaryKey: true }),
|
|
61
|
+
title: State.SQLite.text({ default: '' }),
|
|
62
|
+
creator: State.SQLite.text({ default: '' }),
|
|
63
|
+
priority: State.SQLite.integer({ schema: Schema.Literal(0, 1, 2, 3, 4), default: 0 }),
|
|
64
|
+
created: State.SQLite.integer({ schema: Schema.DateFromNumber }),
|
|
65
|
+
deleted: State.SQLite.integer({ nullable: true, schema: Schema.DateFromNumber }),
|
|
66
|
+
modified: State.SQLite.integer({ schema: Schema.DateFromNumber }),
|
|
67
|
+
kanbanorder: State.SQLite.text({ nullable: false, default: '' }),
|
|
68
|
+
},
|
|
69
|
+
indexes: [
|
|
70
|
+
{ name: 'issue_kanbanorder', columns: ['kanbanorder'] },
|
|
71
|
+
{ name: 'issue_created', columns: ['created'] },
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const db = { todos, todosWithIntId, comments, issue, UiState, UiStateWithDefaultId }
|
|
38
76
|
|
|
39
77
|
describe('query builder', () => {
|
|
40
78
|
describe('result schema', () => {
|
|
@@ -52,6 +90,13 @@ describe('query builder', () => {
|
|
|
52
90
|
}
|
|
53
91
|
`)
|
|
54
92
|
|
|
93
|
+
expect(db.todos.select('id').asSql()).toMatchInlineSnapshot(`
|
|
94
|
+
{
|
|
95
|
+
"bindValues": [],
|
|
96
|
+
"query": "SELECT id FROM 'todos'",
|
|
97
|
+
}
|
|
98
|
+
`)
|
|
99
|
+
|
|
55
100
|
expect(db.todos.select('id', 'text').asSql()).toMatchInlineSnapshot(`
|
|
56
101
|
{
|
|
57
102
|
"bindValues": [],
|
|
@@ -147,6 +192,42 @@ describe('query builder', () => {
|
|
|
147
192
|
`)
|
|
148
193
|
})
|
|
149
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(`
|
|
198
|
+
{
|
|
199
|
+
"bindValues": [
|
|
200
|
+
1,
|
|
201
|
+
5,
|
|
202
|
+
10,
|
|
203
|
+
],
|
|
204
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ? LIMIT ?",
|
|
205
|
+
}
|
|
206
|
+
`)
|
|
207
|
+
|
|
208
|
+
// Test with only offset
|
|
209
|
+
expect(db.todos.select('id', 'text').where('completed', true).offset(5).asSql()).toMatchInlineSnapshot(`
|
|
210
|
+
{
|
|
211
|
+
"bindValues": [
|
|
212
|
+
1,
|
|
213
|
+
5,
|
|
214
|
+
],
|
|
215
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? OFFSET ?",
|
|
216
|
+
}
|
|
217
|
+
`)
|
|
218
|
+
|
|
219
|
+
// Test with only limit
|
|
220
|
+
expect(db.todos.select('id', 'text').where('completed', true).limit(10).asSql()).toMatchInlineSnapshot(`
|
|
221
|
+
{
|
|
222
|
+
"bindValues": [
|
|
223
|
+
1,
|
|
224
|
+
10,
|
|
225
|
+
],
|
|
226
|
+
"query": "SELECT id, text FROM 'todos' WHERE completed = ? LIMIT ?",
|
|
227
|
+
}
|
|
228
|
+
`)
|
|
229
|
+
})
|
|
230
|
+
|
|
150
231
|
it('should handle COUNT queries', () => {
|
|
151
232
|
expect(db.todos.count().asSql()).toMatchInlineSnapshot(`
|
|
152
233
|
{
|
|
@@ -203,40 +284,92 @@ describe('query builder', () => {
|
|
|
203
284
|
})
|
|
204
285
|
})
|
|
205
286
|
|
|
206
|
-
describe('
|
|
207
|
-
|
|
208
|
-
|
|
287
|
+
// describe('getOrCreate queries', () => {
|
|
288
|
+
// it('should handle getOrCreate queries', () => {
|
|
289
|
+
// expect(db.UiState.getOrCreate('sessionid-1').asSql()).toMatchInlineSnapshot(`
|
|
290
|
+
// {
|
|
291
|
+
// "bindValues": [
|
|
292
|
+
// "sessionid-1",
|
|
293
|
+
// ],
|
|
294
|
+
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
295
|
+
// }
|
|
296
|
+
// `)
|
|
297
|
+
// })
|
|
298
|
+
|
|
299
|
+
// it('should handle getOrCreate queries with default id', () => {
|
|
300
|
+
// expect(db.UiStateWithDefaultId.getOrCreate().asSql()).toMatchInlineSnapshot(`
|
|
301
|
+
// {
|
|
302
|
+
// "bindValues": [],
|
|
303
|
+
// "query": "SELECT * FROM 'UiState' WHERE id = ?",
|
|
304
|
+
// }
|
|
305
|
+
// `)
|
|
306
|
+
// })
|
|
307
|
+
// // it('should handle row queries with numbers', () => {
|
|
308
|
+
// // expect(db.todosWithIntId.getOrCreate(123, { insertValues: { status: 'active' } }).asSql()).toMatchInlineSnapshot(`
|
|
309
|
+
// // {
|
|
310
|
+
// // "bindValues": [
|
|
311
|
+
// // 123,
|
|
312
|
+
// // ],
|
|
313
|
+
// // "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
|
|
314
|
+
// // }
|
|
315
|
+
// // `)
|
|
316
|
+
// // })
|
|
317
|
+
// })
|
|
318
|
+
|
|
319
|
+
describe('write operations', () => {
|
|
320
|
+
it('should handle INSERT queries', () => {
|
|
321
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).asSql()).toMatchInlineSnapshot(`
|
|
209
322
|
{
|
|
210
323
|
"bindValues": [
|
|
211
324
|
"123",
|
|
325
|
+
"Buy milk",
|
|
326
|
+
"active",
|
|
212
327
|
],
|
|
213
|
-
"query": "
|
|
328
|
+
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
214
329
|
}
|
|
215
330
|
`)
|
|
216
331
|
})
|
|
217
332
|
|
|
218
|
-
it('should handle
|
|
219
|
-
expect(db.
|
|
333
|
+
it('should handle INSERT queries with undefined values', () => {
|
|
334
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active', completed: undefined }).asSql())
|
|
335
|
+
.toMatchInlineSnapshot(`
|
|
220
336
|
{
|
|
221
337
|
"bindValues": [
|
|
222
|
-
123,
|
|
338
|
+
"123",
|
|
339
|
+
"Buy milk",
|
|
340
|
+
"active",
|
|
223
341
|
],
|
|
224
|
-
"query": "
|
|
342
|
+
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
225
343
|
}
|
|
226
344
|
`)
|
|
227
345
|
})
|
|
228
|
-
})
|
|
229
346
|
|
|
230
|
-
|
|
231
|
-
it('should handle INSERT queries', () => {
|
|
232
|
-
expect(
|
|
347
|
+
// Test helped to catch a bindValues ordering bug
|
|
348
|
+
it('should handle INSERT queries (issue)', () => {
|
|
349
|
+
expect(
|
|
350
|
+
db.issue
|
|
351
|
+
.insert({
|
|
352
|
+
id: 1,
|
|
353
|
+
title: 'Revert the user profile page',
|
|
354
|
+
priority: 2,
|
|
355
|
+
created: new Date('2024-08-01T17:15:20.507Z'),
|
|
356
|
+
modified: new Date('2024-12-29T17:15:20.507Z'),
|
|
357
|
+
kanbanorder: 'a2',
|
|
358
|
+
creator: 'John Doe',
|
|
359
|
+
})
|
|
360
|
+
.asSql(),
|
|
361
|
+
).toMatchInlineSnapshot(`
|
|
233
362
|
{
|
|
234
363
|
"bindValues": [
|
|
235
|
-
|
|
236
|
-
"
|
|
237
|
-
|
|
364
|
+
1,
|
|
365
|
+
"Revert the user profile page",
|
|
366
|
+
2,
|
|
367
|
+
1722532520507,
|
|
368
|
+
1735492520507,
|
|
369
|
+
"a2",
|
|
370
|
+
"John Doe",
|
|
238
371
|
],
|
|
239
|
-
"query": "INSERT INTO '
|
|
372
|
+
"query": "INSERT INTO 'issue' (id, title, priority, created, modified, kanbanorder, creator) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
240
373
|
}
|
|
241
374
|
`)
|
|
242
375
|
})
|
|
@@ -251,6 +384,40 @@ describe('query builder', () => {
|
|
|
251
384
|
"query": "UPDATE 'todos' SET status = ? WHERE id = ?",
|
|
252
385
|
}
|
|
253
386
|
`)
|
|
387
|
+
|
|
388
|
+
// empty update set
|
|
389
|
+
expect(db.todos.update({}).where({ id: '123' }).asSql()).toMatchInlineSnapshot(`
|
|
390
|
+
{
|
|
391
|
+
"bindValues": [],
|
|
392
|
+
"query": "SELECT 1",
|
|
393
|
+
}
|
|
394
|
+
`)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should handle UPDATE queries with undefined values', () => {
|
|
398
|
+
expect(db.todos.update({ status: undefined, text: 'some text' }).where({ id: '123' }).asSql())
|
|
399
|
+
.toMatchInlineSnapshot(`
|
|
400
|
+
{
|
|
401
|
+
"bindValues": [
|
|
402
|
+
"some text",
|
|
403
|
+
"123",
|
|
404
|
+
],
|
|
405
|
+
"query": "UPDATE 'todos' SET text = ? WHERE id = ?",
|
|
406
|
+
}
|
|
407
|
+
`)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('should handle UPDATE queries with undefined values (issue)', () => {
|
|
411
|
+
expect(db.issue.update({ priority: 2, creator: 'John Doe' }).where({ id: 1 }).asSql()).toMatchInlineSnapshot(`
|
|
412
|
+
{
|
|
413
|
+
"bindValues": [
|
|
414
|
+
2,
|
|
415
|
+
"John Doe",
|
|
416
|
+
1,
|
|
417
|
+
],
|
|
418
|
+
"query": "UPDATE 'issue' SET priority = ?, creator = ? WHERE id = ?",
|
|
419
|
+
}
|
|
420
|
+
`)
|
|
254
421
|
})
|
|
255
422
|
|
|
256
423
|
it('should handle DELETE queries', () => {
|
|
@@ -294,6 +461,36 @@ describe('query builder', () => {
|
|
|
294
461
|
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET text = ?, status = ?",
|
|
295
462
|
}
|
|
296
463
|
`)
|
|
464
|
+
|
|
465
|
+
expect(db.todos.insert({ id: '123', text: 'Buy milk', status: 'active' }).onConflict('id', 'replace').asSql())
|
|
466
|
+
.toMatchInlineSnapshot(`
|
|
467
|
+
{
|
|
468
|
+
"bindValues": [
|
|
469
|
+
"123",
|
|
470
|
+
"Buy milk",
|
|
471
|
+
"active",
|
|
472
|
+
],
|
|
473
|
+
"query": "INSERT OR REPLACE INTO 'todos' (id, text, status) VALUES (?, ?, ?)",
|
|
474
|
+
}
|
|
475
|
+
`)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it('should handle ON CONFLICT with multiple columns', () => {
|
|
479
|
+
expect(
|
|
480
|
+
db.todos
|
|
481
|
+
.insert({ id: '123', text: 'Buy milk', status: 'active' })
|
|
482
|
+
.onConflict(['id', 'status'], 'ignore')
|
|
483
|
+
.asSql(),
|
|
484
|
+
).toMatchInlineSnapshot(`
|
|
485
|
+
{
|
|
486
|
+
"bindValues": [
|
|
487
|
+
"123",
|
|
488
|
+
"Buy milk",
|
|
489
|
+
"active",
|
|
490
|
+
],
|
|
491
|
+
"query": "INSERT INTO 'todos' (id, text, status) VALUES (?, ?, ?) ON CONFLICT (id, status) DO NOTHING",
|
|
492
|
+
}
|
|
493
|
+
`)
|
|
297
494
|
})
|
|
298
495
|
|
|
299
496
|
it('should handle RETURNING clause', () => {
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { casesHandled } from '@livestore/utils'
|
|
1
|
+
import { casesHandled, shouldNeverHappen } from '@livestore/utils'
|
|
2
2
|
import { Match, Option, Predicate, Schema } from '@livestore/utils/effect'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
5
|
-
import type { DbSchema } from '../schema/mod.js'
|
|
4
|
+
import type { State } from '../schema/mod.js'
|
|
6
5
|
import type { QueryBuilder, QueryBuilderAst } from './api.js'
|
|
7
|
-
import { QueryBuilderAstSymbol,
|
|
6
|
+
import { QueryBuilderAstSymbol, QueryBuilderTypeId } from './api.js'
|
|
8
7
|
import { astToSql } from './astToSql.js'
|
|
9
8
|
|
|
10
|
-
export const makeQueryBuilder = <TResult, TTableDef extends
|
|
9
|
+
export const makeQueryBuilder = <TResult, TTableDef extends State.SQLite.TableDefBase>(
|
|
11
10
|
tableDef: TTableDef,
|
|
12
11
|
ast: QueryBuilderAst = emptyAst(tableDef),
|
|
13
|
-
): QueryBuilder<TResult, TTableDef, never
|
|
12
|
+
): QueryBuilder<TResult, TTableDef, never> => {
|
|
14
13
|
const api = {
|
|
15
14
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
16
15
|
select() {
|
|
@@ -19,11 +18,12 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
|
|
|
19
18
|
// eslint-disable-next-line prefer-rest-params
|
|
20
19
|
const params = [...arguments]
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// Pluck if there's only one column selected
|
|
22
|
+
if (params.length === 1) {
|
|
23
|
+
const [col] = params as any as [string]
|
|
24
24
|
return makeQueryBuilder(tableDef, {
|
|
25
25
|
...ast,
|
|
26
|
-
resultSchemaSingle:
|
|
26
|
+
resultSchemaSingle: ast.resultSchemaSingle.pipe(Schema.pluck(col)),
|
|
27
27
|
select: { columns: [col] },
|
|
28
28
|
})
|
|
29
29
|
}
|
|
@@ -148,51 +148,60 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
|
|
|
148
148
|
pickFirst: options?.fallback ? { fallback: options.fallback } : { fallback: () => undefined },
|
|
149
149
|
})
|
|
150
150
|
},
|
|
151
|
-
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
151
|
+
// // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
152
|
+
// getOrCreate() {
|
|
153
|
+
// if (tableDef.options.isClientDocumentTable === false) {
|
|
154
|
+
// return invalidQueryBuilder(`getOrCreate() is not allowed when table is not a client document table`)
|
|
155
|
+
// }
|
|
156
|
+
|
|
157
|
+
// // eslint-disable-next-line prefer-rest-params
|
|
158
|
+
// const params = [...arguments]
|
|
159
|
+
|
|
160
|
+
// let id: string | number
|
|
161
|
+
|
|
162
|
+
// // TODO refactor to handle default id
|
|
163
|
+
// id = params[0] as string | number
|
|
164
|
+
// if (id === undefined) {
|
|
165
|
+
// invalidQueryBuilder(`Id missing for row query on non-singleton table ${tableDef.sqliteDef.name}`)
|
|
166
|
+
// }
|
|
167
|
+
|
|
168
|
+
// // TODO validate all required columns are present and values are matching the schema
|
|
169
|
+
// const insertValues: Record<string, unknown> = params[1]?.insertValues ?? {}
|
|
170
|
+
|
|
171
|
+
// return makeQueryBuilder(tableDef, {
|
|
172
|
+
// _tag: 'RowQuery',
|
|
173
|
+
// id,
|
|
174
|
+
// tableDef,
|
|
175
|
+
// insertValues,
|
|
176
|
+
// }) as any
|
|
177
|
+
// },
|
|
177
178
|
insert: (values) => {
|
|
179
|
+
const filteredValues = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined))
|
|
180
|
+
|
|
178
181
|
return makeQueryBuilder(tableDef, {
|
|
179
182
|
_tag: 'InsertQuery',
|
|
180
183
|
tableDef,
|
|
181
|
-
values:
|
|
184
|
+
values: filteredValues,
|
|
182
185
|
onConflict: undefined,
|
|
183
186
|
returning: undefined,
|
|
184
187
|
resultSchema: Schema.Void,
|
|
185
188
|
}) as any
|
|
186
189
|
},
|
|
187
|
-
onConflict: (
|
|
190
|
+
onConflict: (
|
|
191
|
+
targetOrTargets: string | string[],
|
|
192
|
+
action: 'ignore' | 'replace' | 'update',
|
|
193
|
+
updateValues?: Record<string, unknown>,
|
|
194
|
+
) => {
|
|
195
|
+
const targets = Array.isArray(targetOrTargets) ? targetOrTargets : [targetOrTargets]
|
|
196
|
+
|
|
188
197
|
assertInsertQueryBuilderAst(ast)
|
|
189
198
|
|
|
190
199
|
const onConflict = Match.value(action).pipe(
|
|
191
|
-
Match.when('ignore', () => ({
|
|
192
|
-
Match.when('replace', () => ({
|
|
200
|
+
Match.when('ignore', () => ({ targets, action: { _tag: 'ignore' } }) satisfies QueryBuilderAst.OnConflict),
|
|
201
|
+
Match.when('replace', () => ({ targets, action: { _tag: 'replace' } }) satisfies QueryBuilderAst.OnConflict),
|
|
193
202
|
Match.when(
|
|
194
203
|
'update',
|
|
195
|
-
() => ({
|
|
204
|
+
() => ({ targets, action: { _tag: 'update', update: updateValues! } }) satisfies QueryBuilderAst.OnConflict,
|
|
196
205
|
),
|
|
197
206
|
Match.exhaustive,
|
|
198
207
|
)
|
|
@@ -209,15 +218,17 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
|
|
|
209
218
|
return makeQueryBuilder(tableDef, {
|
|
210
219
|
...ast,
|
|
211
220
|
returning: columns,
|
|
212
|
-
resultSchema: tableDef.
|
|
221
|
+
resultSchema: tableDef.rowSchema.pipe(Schema.pick(...columns), Schema.Array),
|
|
213
222
|
}) as any
|
|
214
223
|
},
|
|
215
224
|
|
|
216
225
|
update: (values) => {
|
|
226
|
+
const filteredValues = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined))
|
|
227
|
+
|
|
217
228
|
return makeQueryBuilder(tableDef, {
|
|
218
229
|
_tag: 'UpdateQuery',
|
|
219
230
|
tableDef,
|
|
220
|
-
values:
|
|
231
|
+
values: filteredValues,
|
|
221
232
|
where: [],
|
|
222
233
|
returning: undefined,
|
|
223
234
|
resultSchema: Schema.Void,
|
|
@@ -233,11 +244,12 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
|
|
|
233
244
|
resultSchema: Schema.Void,
|
|
234
245
|
}) as any
|
|
235
246
|
},
|
|
236
|
-
} satisfies QueryBuilder.ApiFull<TResult, TTableDef, never
|
|
247
|
+
} satisfies QueryBuilder.ApiFull<TResult, TTableDef, never>
|
|
237
248
|
|
|
238
249
|
return {
|
|
239
|
-
[
|
|
250
|
+
[QueryBuilderTypeId]: QueryBuilderTypeId,
|
|
240
251
|
[QueryBuilderAstSymbol]: ast,
|
|
252
|
+
['ResultType']: 'only-for-type-inference' as TResult,
|
|
241
253
|
asSql: () => astToSql(ast),
|
|
242
254
|
toString: () => {
|
|
243
255
|
try {
|
|
@@ -251,7 +263,7 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
|
|
|
251
263
|
} satisfies QueryBuilder<TResult, TTableDef>
|
|
252
264
|
}
|
|
253
265
|
|
|
254
|
-
const emptyAst = (tableDef:
|
|
266
|
+
const emptyAst = (tableDef: State.SQLite.TableDefBase): QueryBuilderAst.SelectQuery => ({
|
|
255
267
|
_tag: 'SelectQuery',
|
|
256
268
|
columns: [],
|
|
257
269
|
pickFirst: false,
|
|
@@ -261,35 +273,35 @@ const emptyAst = (tableDef: DbSchema.TableDefBase): QueryBuilderAst.SelectQuery
|
|
|
261
273
|
limit: Option.none(),
|
|
262
274
|
tableDef,
|
|
263
275
|
where: [],
|
|
264
|
-
resultSchemaSingle: tableDef.
|
|
276
|
+
resultSchemaSingle: tableDef.rowSchema,
|
|
265
277
|
})
|
|
266
278
|
|
|
267
279
|
// Helper functions
|
|
268
280
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
269
281
|
function assertSelectQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.SelectQuery {
|
|
270
282
|
if (ast._tag !== 'SelectQuery') {
|
|
271
|
-
|
|
283
|
+
return shouldNeverHappen('Expected SelectQuery but got ' + ast._tag)
|
|
272
284
|
}
|
|
273
285
|
}
|
|
274
286
|
|
|
275
287
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
276
288
|
function assertInsertQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.InsertQuery {
|
|
277
289
|
if (ast._tag !== 'InsertQuery') {
|
|
278
|
-
|
|
290
|
+
return shouldNeverHappen('Expected InsertQuery but got ' + ast._tag)
|
|
279
291
|
}
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
|
|
283
295
|
function assertWriteQueryBuilderAst(ast: QueryBuilderAst): asserts ast is QueryBuilderAst.WriteQuery {
|
|
284
296
|
if (ast._tag !== 'InsertQuery' && ast._tag !== 'UpdateQuery' && ast._tag !== 'DeleteQuery') {
|
|
285
|
-
|
|
297
|
+
return shouldNeverHappen('Expected WriteQuery but got ' + ast._tag)
|
|
286
298
|
}
|
|
287
299
|
}
|
|
288
300
|
|
|
289
301
|
const isRowQuery = (ast: QueryBuilderAst): ast is QueryBuilderAst.RowQuery => ast._tag === 'RowQuery'
|
|
290
302
|
|
|
291
303
|
export const invalidQueryBuilder = (msg?: string) => {
|
|
292
|
-
|
|
304
|
+
return shouldNeverHappen('Invalid query builder' + (msg ? `: ${msg}` : ''))
|
|
293
305
|
}
|
|
294
306
|
|
|
295
307
|
export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<any> => {
|
|
@@ -312,17 +324,17 @@ export const getResultSchema = (qb: QueryBuilder<any, any, any>): Schema.Schema<
|
|
|
312
324
|
// For write operations with RETURNING clause, we need to return the appropriate schema
|
|
313
325
|
if (queryAst.returning && queryAst.returning.length > 0) {
|
|
314
326
|
// Create a schema for the returned columns
|
|
315
|
-
return queryAst.tableDef.
|
|
327
|
+
return queryAst.tableDef.rowSchema.pipe(Schema.pick(...queryAst.returning), Schema.Array)
|
|
316
328
|
}
|
|
317
329
|
|
|
318
330
|
// For write operations without RETURNING, the result is the number of affected rows
|
|
319
331
|
return Schema.Number
|
|
320
332
|
}
|
|
321
333
|
default: {
|
|
322
|
-
if (queryAst.tableDef.options.
|
|
323
|
-
return queryAst.tableDef.
|
|
334
|
+
if (queryAst.tableDef.options.isClientDocumentTable) {
|
|
335
|
+
return queryAst.tableDef.rowSchema.pipe(Schema.pluck('value'), Schema.Array, Schema.headOrElse())
|
|
324
336
|
} else {
|
|
325
|
-
return queryAst.tableDef.
|
|
337
|
+
return queryAst.tableDef.rowSchema.pipe(Schema.Array, Schema.headOrElse())
|
|
326
338
|
}
|
|
327
339
|
}
|
|
328
340
|
}
|