@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.
Files changed (287) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixture.d.ts +83 -221
  3. package/dist/__tests__/fixture.d.ts.map +1 -1
  4. package/dist/__tests__/fixture.js +33 -11
  5. package/dist/__tests__/fixture.js.map +1 -1
  6. package/dist/adapter-types.d.ts +36 -22
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +20 -8
  9. package/dist/adapter-types.js.map +1 -1
  10. package/dist/debug-info.d.ts.map +1 -1
  11. package/dist/debug-info.js +1 -0
  12. package/dist/debug-info.js.map +1 -1
  13. package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
  14. package/dist/devtools/devtools-messages-common.d.ts +13 -6
  15. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  16. package/dist/devtools/devtools-messages-common.js +6 -0
  17. package/dist/devtools/devtools-messages-common.js.map +1 -1
  18. package/dist/devtools/devtools-messages-leader.d.ts +46 -46
  19. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  20. package/dist/devtools/devtools-messages-leader.js +12 -13
  21. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  22. package/dist/index.d.ts +2 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -5
  25. package/dist/index.js.map +1 -1
  26. package/dist/leader-thread/LeaderSyncProcessor.d.ts +34 -12
  27. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  28. package/dist/leader-thread/LeaderSyncProcessor.js +284 -226
  29. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  30. package/dist/leader-thread/apply-event.d.ts +16 -0
  31. package/dist/leader-thread/apply-event.d.ts.map +1 -0
  32. package/dist/leader-thread/apply-event.js +122 -0
  33. package/dist/leader-thread/apply-event.js.map +1 -0
  34. package/dist/leader-thread/eventlog.d.ts +27 -0
  35. package/dist/leader-thread/eventlog.d.ts.map +1 -0
  36. package/dist/leader-thread/eventlog.js +123 -0
  37. package/dist/leader-thread/eventlog.js.map +1 -0
  38. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  39. package/dist/leader-thread/leader-worker-devtools.js +22 -23
  40. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  41. package/dist/leader-thread/make-leader-thread-layer.d.ts +16 -4
  42. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  43. package/dist/leader-thread/make-leader-thread-layer.js +36 -41
  44. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  45. package/dist/leader-thread/mod.d.ts +1 -1
  46. package/dist/leader-thread/mod.d.ts.map +1 -1
  47. package/dist/leader-thread/mod.js +1 -1
  48. package/dist/leader-thread/mod.js.map +1 -1
  49. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  50. package/dist/leader-thread/recreate-db.js +7 -7
  51. package/dist/leader-thread/recreate-db.js.map +1 -1
  52. package/dist/leader-thread/types.d.ts +40 -25
  53. package/dist/leader-thread/types.d.ts.map +1 -1
  54. package/dist/leader-thread/types.js.map +1 -1
  55. package/dist/materializer-helper.d.ts +23 -0
  56. package/dist/materializer-helper.d.ts.map +1 -0
  57. package/dist/materializer-helper.js +70 -0
  58. package/dist/materializer-helper.js.map +1 -0
  59. package/dist/query-builder/api.d.ts +55 -50
  60. package/dist/query-builder/api.d.ts.map +1 -1
  61. package/dist/query-builder/api.js +3 -5
  62. package/dist/query-builder/api.js.map +1 -1
  63. package/dist/query-builder/astToSql.d.ts.map +1 -1
  64. package/dist/query-builder/astToSql.js +59 -37
  65. package/dist/query-builder/astToSql.js.map +1 -1
  66. package/dist/query-builder/impl.d.ts +2 -3
  67. package/dist/query-builder/impl.d.ts.map +1 -1
  68. package/dist/query-builder/impl.js +47 -43
  69. package/dist/query-builder/impl.js.map +1 -1
  70. package/dist/query-builder/impl.test.d.ts +86 -1
  71. package/dist/query-builder/impl.test.d.ts.map +1 -1
  72. package/dist/query-builder/impl.test.js +223 -36
  73. package/dist/query-builder/impl.test.js.map +1 -1
  74. package/dist/rehydrate-from-eventlog.d.ts +15 -0
  75. package/dist/rehydrate-from-eventlog.d.ts.map +1 -0
  76. package/dist/{rehydrate-from-mutationlog.js → rehydrate-from-eventlog.js} +27 -28
  77. package/dist/rehydrate-from-eventlog.js.map +1 -0
  78. package/dist/schema/EventDef.d.ts +136 -0
  79. package/dist/schema/EventDef.d.ts.map +1 -0
  80. package/dist/schema/EventDef.js +58 -0
  81. package/dist/schema/EventDef.js.map +1 -0
  82. package/dist/schema/EventId.d.ts +10 -1
  83. package/dist/schema/EventId.d.ts.map +1 -1
  84. package/dist/schema/EventId.js +24 -3
  85. package/dist/schema/EventId.js.map +1 -1
  86. package/dist/schema/LiveStoreEvent.d.ts +255 -0
  87. package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
  88. package/dist/schema/LiveStoreEvent.js +118 -0
  89. package/dist/schema/LiveStoreEvent.js.map +1 -0
  90. package/dist/schema/client-document-def.d.ts +223 -0
  91. package/dist/schema/client-document-def.d.ts.map +1 -0
  92. package/dist/schema/client-document-def.js +164 -0
  93. package/dist/schema/client-document-def.js.map +1 -0
  94. package/dist/schema/client-document-def.test.d.ts +2 -0
  95. package/dist/schema/client-document-def.test.d.ts.map +1 -0
  96. package/dist/schema/client-document-def.test.js +161 -0
  97. package/dist/schema/client-document-def.test.js.map +1 -0
  98. package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
  99. package/dist/schema/events.d.ts +2 -0
  100. package/dist/schema/events.d.ts.map +1 -0
  101. package/dist/schema/events.js +2 -0
  102. package/dist/schema/events.js.map +1 -0
  103. package/dist/schema/mod.d.ts +4 -3
  104. package/dist/schema/mod.d.ts.map +1 -1
  105. package/dist/schema/mod.js +4 -3
  106. package/dist/schema/mod.js.map +1 -1
  107. package/dist/schema/schema.d.ts +26 -22
  108. package/dist/schema/schema.d.ts.map +1 -1
  109. package/dist/schema/schema.js +45 -43
  110. package/dist/schema/schema.js.map +1 -1
  111. package/dist/schema/sqlite-state.d.ts +12 -0
  112. package/dist/schema/sqlite-state.d.ts.map +1 -0
  113. package/dist/schema/sqlite-state.js +36 -0
  114. package/dist/schema/sqlite-state.js.map +1 -0
  115. package/dist/schema/system-tables.d.ts +121 -85
  116. package/dist/schema/system-tables.d.ts.map +1 -1
  117. package/dist/schema/system-tables.js +68 -43
  118. package/dist/schema/system-tables.js.map +1 -1
  119. package/dist/schema/table-def.d.ts +26 -96
  120. package/dist/schema/table-def.d.ts.map +1 -1
  121. package/dist/schema/table-def.js +14 -64
  122. package/dist/schema/table-def.js.map +1 -1
  123. package/dist/schema/view.d.ts +3 -0
  124. package/dist/schema/view.d.ts.map +1 -0
  125. package/dist/schema/view.js +3 -0
  126. package/dist/schema/view.js.map +1 -0
  127. package/dist/schema-management/common.d.ts +4 -4
  128. package/dist/schema-management/common.d.ts.map +1 -1
  129. package/dist/schema-management/migrations.d.ts.map +1 -1
  130. package/dist/schema-management/migrations.js +6 -6
  131. package/dist/schema-management/migrations.js.map +1 -1
  132. package/dist/schema-management/validate-mutation-defs.d.ts +3 -3
  133. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
  134. package/dist/schema-management/validate-mutation-defs.js +17 -17
  135. package/dist/schema-management/validate-mutation-defs.js.map +1 -1
  136. package/dist/sync/ClientSessionSyncProcessor.d.ts +16 -8
  137. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  138. package/dist/sync/ClientSessionSyncProcessor.js +50 -43
  139. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  140. package/dist/sync/next/facts.d.ts +19 -19
  141. package/dist/sync/next/facts.d.ts.map +1 -1
  142. package/dist/sync/next/facts.js +2 -2
  143. package/dist/sync/next/facts.js.map +1 -1
  144. package/dist/sync/next/history-dag-common.d.ts +3 -3
  145. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  146. package/dist/sync/next/history-dag-common.js +1 -1
  147. package/dist/sync/next/history-dag-common.js.map +1 -1
  148. package/dist/sync/next/history-dag.js +1 -1
  149. package/dist/sync/next/history-dag.js.map +1 -1
  150. package/dist/sync/next/rebase-events.d.ts +7 -7
  151. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  152. package/dist/sync/next/rebase-events.js +5 -5
  153. package/dist/sync/next/rebase-events.js.map +1 -1
  154. package/dist/sync/next/test/compact-events.calculator.test.js +38 -33
  155. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
  156. package/dist/sync/next/test/compact-events.test.js +71 -71
  157. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  158. package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +25 -25
  159. package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
  160. package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +60 -25
  161. package/dist/sync/next/test/event-fixtures.js.map +1 -0
  162. package/dist/sync/next/test/mod.d.ts +1 -1
  163. package/dist/sync/next/test/mod.d.ts.map +1 -1
  164. package/dist/sync/next/test/mod.js +1 -1
  165. package/dist/sync/next/test/mod.js.map +1 -1
  166. package/dist/sync/sync.d.ts +8 -7
  167. package/dist/sync/sync.d.ts.map +1 -1
  168. package/dist/sync/sync.js.map +1 -1
  169. package/dist/sync/syncstate.d.ts +69 -93
  170. package/dist/sync/syncstate.d.ts.map +1 -1
  171. package/dist/sync/syncstate.js +143 -146
  172. package/dist/sync/syncstate.js.map +1 -1
  173. package/dist/sync/syncstate.test.js +208 -289
  174. package/dist/sync/syncstate.test.js.map +1 -1
  175. package/dist/sync/validate-push-payload.d.ts +2 -2
  176. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  177. package/dist/sync/validate-push-payload.js.map +1 -1
  178. package/dist/version.d.ts +1 -1
  179. package/dist/version.js +1 -1
  180. package/package.json +2 -2
  181. package/src/__tests__/fixture.ts +36 -15
  182. package/src/adapter-types.ts +34 -23
  183. package/src/debug-info.ts +1 -0
  184. package/src/devtools/devtools-messages-common.ts +9 -0
  185. package/src/devtools/devtools-messages-leader.ts +14 -15
  186. package/src/index.ts +2 -5
  187. package/src/leader-thread/LeaderSyncProcessor.ts +485 -389
  188. package/src/leader-thread/apply-event.ts +197 -0
  189. package/src/leader-thread/eventlog.ts +199 -0
  190. package/src/leader-thread/leader-worker-devtools.ts +23 -25
  191. package/src/leader-thread/make-leader-thread-layer.ts +68 -61
  192. package/src/leader-thread/mod.ts +1 -1
  193. package/src/leader-thread/recreate-db.ts +7 -8
  194. package/src/leader-thread/types.ts +39 -29
  195. package/src/materializer-helper.ts +110 -0
  196. package/src/query-builder/api.ts +76 -102
  197. package/src/query-builder/astToSql.ts +68 -39
  198. package/src/query-builder/impl.test.ts +239 -42
  199. package/src/query-builder/impl.ts +66 -54
  200. package/src/{rehydrate-from-mutationlog.ts → rehydrate-from-eventlog.ts} +37 -40
  201. package/src/schema/EventDef.ts +216 -0
  202. package/src/schema/EventId.ts +30 -4
  203. package/src/schema/LiveStoreEvent.ts +239 -0
  204. package/src/schema/client-document-def.test.ts +188 -0
  205. package/src/schema/client-document-def.ts +436 -0
  206. package/src/schema/db-schema/dsl/mod.ts +0 -1
  207. package/src/schema/events.ts +1 -0
  208. package/src/schema/mod.ts +4 -3
  209. package/src/schema/schema.ts +78 -68
  210. package/src/schema/sqlite-state.ts +62 -0
  211. package/src/schema/system-tables.ts +54 -46
  212. package/src/schema/table-def.ts +51 -209
  213. package/src/schema/view.ts +2 -0
  214. package/src/schema-management/common.ts +4 -4
  215. package/src/schema-management/migrations.ts +8 -9
  216. package/src/schema-management/validate-mutation-defs.ts +22 -24
  217. package/src/sync/ClientSessionSyncProcessor.ts +66 -53
  218. package/src/sync/next/facts.ts +31 -32
  219. package/src/sync/next/history-dag-common.ts +4 -4
  220. package/src/sync/next/history-dag.ts +1 -1
  221. package/src/sync/next/rebase-events.ts +13 -13
  222. package/src/sync/next/test/compact-events.calculator.test.ts +45 -45
  223. package/src/sync/next/test/compact-events.test.ts +73 -73
  224. package/src/sync/next/test/event-fixtures.ts +219 -0
  225. package/src/sync/next/test/mod.ts +1 -1
  226. package/src/sync/sync.ts +9 -12
  227. package/src/sync/syncstate.test.ts +236 -323
  228. package/src/sync/syncstate.ts +218 -203
  229. package/src/sync/validate-push-payload.ts +2 -2
  230. package/src/version.ts +1 -1
  231. package/tsconfig.json +1 -0
  232. package/dist/derived-mutations.d.ts +0 -109
  233. package/dist/derived-mutations.d.ts.map +0 -1
  234. package/dist/derived-mutations.js +0 -54
  235. package/dist/derived-mutations.js.map +0 -1
  236. package/dist/derived-mutations.test.d.ts +0 -2
  237. package/dist/derived-mutations.test.d.ts.map +0 -1
  238. package/dist/derived-mutations.test.js +0 -93
  239. package/dist/derived-mutations.test.js.map +0 -1
  240. package/dist/init-singleton-tables.d.ts +0 -4
  241. package/dist/init-singleton-tables.d.ts.map +0 -1
  242. package/dist/init-singleton-tables.js +0 -16
  243. package/dist/init-singleton-tables.js.map +0 -1
  244. package/dist/leader-thread/apply-mutation.d.ts +0 -11
  245. package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
  246. package/dist/leader-thread/apply-mutation.js +0 -115
  247. package/dist/leader-thread/apply-mutation.js.map +0 -1
  248. package/dist/leader-thread/mutationlog.d.ts +0 -11
  249. package/dist/leader-thread/mutationlog.d.ts.map +0 -1
  250. package/dist/leader-thread/mutationlog.js +0 -31
  251. package/dist/leader-thread/mutationlog.js.map +0 -1
  252. package/dist/leader-thread/pull-queue-set.d.ts +0 -7
  253. package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
  254. package/dist/leader-thread/pull-queue-set.js +0 -48
  255. package/dist/leader-thread/pull-queue-set.js.map +0 -1
  256. package/dist/mutation.d.ts +0 -20
  257. package/dist/mutation.d.ts.map +0 -1
  258. package/dist/mutation.js +0 -68
  259. package/dist/mutation.js.map +0 -1
  260. package/dist/query-info.d.ts +0 -41
  261. package/dist/query-info.d.ts.map +0 -1
  262. package/dist/query-info.js +0 -7
  263. package/dist/query-info.js.map +0 -1
  264. package/dist/rehydrate-from-mutationlog.d.ts +0 -14
  265. package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
  266. package/dist/rehydrate-from-mutationlog.js.map +0 -1
  267. package/dist/schema/MutationEvent.d.ts +0 -202
  268. package/dist/schema/MutationEvent.d.ts.map +0 -1
  269. package/dist/schema/MutationEvent.js +0 -105
  270. package/dist/schema/MutationEvent.js.map +0 -1
  271. package/dist/schema/mutations.d.ts +0 -115
  272. package/dist/schema/mutations.d.ts.map +0 -1
  273. package/dist/schema/mutations.js +0 -42
  274. package/dist/schema/mutations.js.map +0 -1
  275. package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
  276. package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
  277. package/src/derived-mutations.test.ts +0 -101
  278. package/src/derived-mutations.ts +0 -170
  279. package/src/init-singleton-tables.ts +0 -24
  280. package/src/leader-thread/apply-mutation.ts +0 -187
  281. package/src/leader-thread/mutationlog.ts +0 -49
  282. package/src/leader-thread/pull-queue-set.ts +0 -67
  283. package/src/mutation.ts +0 -108
  284. package/src/query-info.ts +0 -83
  285. package/src/schema/MutationEvent.ts +0 -224
  286. package/src/schema/mutations.ts +0 -193
  287. package/src/sync/next/test/mutation-fixtures.ts +0 -228
@@ -0,0 +1,188 @@
1
+ import { Schema } from '@livestore/utils/effect'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ import { tables } from '../__tests__/fixture.js'
5
+ import { clientDocument, ClientDocumentTableDefSymbol } from './client-document-def.js'
6
+ import type * as LiveStoreEvent from './LiveStoreEvent.js'
7
+
8
+ const materializerContext = {
9
+ currentFacts: new Map(),
10
+ clientOnly: false,
11
+ }
12
+
13
+ describe('client document table', () => {
14
+ test('set event', () => {
15
+ expect(patchId(tables.UiState.set({ showSidebar: false }, 'session-1'))).toMatchInlineSnapshot(`
16
+ {
17
+ "args": {
18
+ "id": "session-1",
19
+ "value": {
20
+ "showSidebar": false,
21
+ },
22
+ },
23
+ "id": "00000000-0000-0000-0000-000000000000",
24
+ "name": "UiStateSet",
25
+ }
26
+ `)
27
+
28
+ expect(patchId(tables.appConfig.set({ fontSize: 12, theme: 'dark' }))).toMatchInlineSnapshot(`
29
+ {
30
+ "args": {
31
+ "id": "static",
32
+ "value": {
33
+ "fontSize": 12,
34
+ "theme": "dark",
35
+ },
36
+ },
37
+ "id": "00000000-0000-0000-0000-000000000000",
38
+ "name": "AppConfigSet",
39
+ }
40
+ `)
41
+ })
42
+
43
+ describe('materializer', () => {
44
+ const forSchema = <T>(schema: Schema.Schema<T, any>, value: T, id?: string, options?: { partialSet?: boolean }) => {
45
+ const Doc = clientDocument({
46
+ name: 'test',
47
+ schema,
48
+ default: { value },
49
+ ...options,
50
+ })
51
+
52
+ const materializer = Doc[ClientDocumentTableDefSymbol].derived.setMaterializer
53
+
54
+ return materializer(Doc.set(value, id as any).args, materializerContext)
55
+ }
56
+
57
+ test('string value', () => {
58
+ expect(forSchema(Schema.String, 'hello', 'id1')).toMatchInlineSnapshot(`
59
+ {
60
+ "bindValues": [
61
+ "id1",
62
+ ""hello"",
63
+ ""hello"",
64
+ ],
65
+ "sql": "INSERT INTO 'test' (id, value) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET value = ?",
66
+ "writeTables": Set {
67
+ "test",
68
+ },
69
+ }
70
+ `)
71
+ })
72
+
73
+ test('struct value (partial set=true)', () => {
74
+ expect(forSchema(Schema.Struct({ a: Schema.String }), { a: 'hello' }, 'id1', { partialSet: true }))
75
+ .toMatchInlineSnapshot(`
76
+ {
77
+ "bindValues": [
78
+ "id1",
79
+ "{"a":"hello"}",
80
+ "$.a",
81
+ ""hello"",
82
+ ],
83
+ "sql": "
84
+ INSERT INTO 'test' (id, value)
85
+ VALUES (?, ?)
86
+ ON CONFLICT (id) DO UPDATE SET
87
+ value = json_set(value, ?, json(?))
88
+ ",
89
+ "writeTables": Set {
90
+ "test",
91
+ },
92
+ }
93
+ `)
94
+ })
95
+
96
+ test('struct value (partial set=false)', () => {
97
+ expect(forSchema(Schema.Struct({ a: Schema.String }), { a: 'hello' }, 'id1', { partialSet: false }))
98
+ .toMatchInlineSnapshot(`
99
+ {
100
+ "bindValues": [
101
+ "id1",
102
+ "{"a":"hello"}",
103
+ "{"a":"hello"}",
104
+ ],
105
+ "sql": "INSERT INTO 'test' (id, value) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET value = ?",
106
+ "writeTables": Set {
107
+ "test",
108
+ },
109
+ }
110
+ `)
111
+ })
112
+
113
+ test('struct value (partial set=true) advanced', () => {
114
+ expect(
115
+ forSchema(
116
+ Schema.Struct({ a: Schema.String, b: Schema.String, c: Schema.Number }),
117
+ { a: 'hello', c: 123 } as any,
118
+ 'id1',
119
+ { partialSet: true },
120
+ ),
121
+ ).toMatchInlineSnapshot(`
122
+ {
123
+ "bindValues": [
124
+ "id1",
125
+ "{"a":"hello","c":123}",
126
+ "$.a",
127
+ ""hello"",
128
+ "$.c",
129
+ "123",
130
+ ],
131
+ "sql": "
132
+ INSERT INTO 'test' (id, value)
133
+ VALUES (?, ?)
134
+ ON CONFLICT (id) DO UPDATE SET
135
+ value = json_set(json_set(value, ?, json(?)), ?, json(?))
136
+ ",
137
+ "writeTables": Set {
138
+ "test",
139
+ },
140
+ }
141
+ `)
142
+ })
143
+
144
+ test('struct union value', () => {
145
+ expect(
146
+ forSchema(
147
+ Schema.Union(Schema.Struct({ a: Schema.String }), Schema.Struct({ b: Schema.String })),
148
+ { a: 'hello' },
149
+ 'id1',
150
+ ),
151
+ ).toMatchInlineSnapshot(`
152
+ {
153
+ "bindValues": [
154
+ "id1",
155
+ "{"a":"hello"}",
156
+ "{"a":"hello"}",
157
+ ],
158
+ "sql": "INSERT INTO 'test' (id, value) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET value = ?",
159
+ "writeTables": Set {
160
+ "test",
161
+ },
162
+ }
163
+ `)
164
+ })
165
+
166
+ test('array value', () => {
167
+ expect(forSchema(Schema.Array(Schema.String), ['hello', 'world'], 'id1')).toMatchInlineSnapshot(`
168
+ {
169
+ "bindValues": [
170
+ "id1",
171
+ "["hello","world"]",
172
+ "["hello","world"]",
173
+ ],
174
+ "sql": "INSERT INTO 'test' (id, value) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET value = ?",
175
+ "writeTables": Set {
176
+ "test",
177
+ },
178
+ }
179
+ `)
180
+ })
181
+ })
182
+ })
183
+
184
+ const patchId = (muationEvent: LiveStoreEvent.PartialAnyDecoded) => {
185
+ // TODO use new id paradigm
186
+ const id = `00000000-0000-0000-0000-000000000000`
187
+ return { ...muationEvent, id }
188
+ }
@@ -0,0 +1,436 @@
1
+ import { shouldNeverHappen } from '@livestore/utils'
2
+ import type { Option, Types } from '@livestore/utils/effect'
3
+ import { Schema, SchemaAST } from '@livestore/utils/effect'
4
+
5
+ import { SessionIdSymbol } from '../adapter-types.js'
6
+ import type { QueryBuilder, QueryBuilderAst } from '../query-builder/mod.js'
7
+ import { QueryBuilderAstSymbol, QueryBuilderTypeId } from '../query-builder/mod.js'
8
+ import { sql } from '../util.js'
9
+ import { SqliteDsl } from './db-schema/mod.js'
10
+ import type { EventDef, Materializer } from './EventDef.js'
11
+ import { defineEvent, defineMaterializer } from './EventDef.js'
12
+ import type { TableDef, TableDefBase } from './table-def.js'
13
+ import { table } from './table-def.js'
14
+
15
+ /**
16
+ * Special:
17
+ * - Synced across client sessions (e.g. tabs) but not across different clients
18
+ * - Derived setters
19
+ * - Emits client-only events
20
+ * - Has implicit setter-reducers
21
+ * - Similar to `React.useState` (except it's persisted)
22
+ *
23
+ * Careful:
24
+ * - When changing the table definitions in a non-backwards compatible way, the state might be lost without
25
+ * explicit reducers to handle the old auto-generated events
26
+ */
27
+ export const clientDocument = <
28
+ TName extends string,
29
+ TType,
30
+ TEncoded,
31
+ const TOptions extends ClientDocumentTableOptions.Input<TType>,
32
+ >({
33
+ name,
34
+ schema: valueSchema,
35
+ ...inputOptions
36
+ }: {
37
+ name: TName
38
+ schema: Schema.Schema<TType, TEncoded>
39
+ } & TOptions): ClientDocumentTableDef<
40
+ TName,
41
+ TType,
42
+ TEncoded,
43
+ Types.Simplify<ClientDocumentTableOptions.WithDefaults<TOptions>>
44
+ > => {
45
+ const options = {
46
+ partialSet: inputOptions.partialSet ?? true,
47
+ default: {
48
+ id: inputOptions.default.id,
49
+ value: inputOptions.default.value,
50
+ },
51
+ } satisfies ClientDocumentTableOptions<TType>
52
+
53
+ const columns = {
54
+ id: SqliteDsl.text({ primaryKey: true }),
55
+ value: SqliteDsl.json({ schema: valueSchema }),
56
+ }
57
+
58
+ const tableDef = table({ name, columns })
59
+
60
+ // @ts-expect-error TODO properly type this
61
+ tableDef.options.isClientDocumentTable = true
62
+
63
+ const { eventDef: derivedSetEventDef, materializer: derivedSetMaterializer } = deriveEventAndMaterializer({
64
+ name,
65
+ valueSchema,
66
+ defaultValue: options.default.value,
67
+ partialSet: options.partialSet,
68
+ })
69
+
70
+ const setEventDef = (...args: any[]) => {
71
+ const [value, id = options.default.id] = args
72
+ return derivedSetEventDef({ id, value })
73
+ }
74
+
75
+ Object.defineProperty(setEventDef, 'name', { value: `${name}Set` })
76
+ Object.defineProperty(setEventDef, 'schema', {
77
+ value: Schema.Struct({
78
+ id: Schema.String,
79
+ value: options.partialSet ? Schema.partial(valueSchema) : valueSchema,
80
+ }),
81
+ })
82
+ Object.defineProperty(setEventDef, 'options', { value: { derived: true, clientOnly: true, facts: undefined } })
83
+
84
+ const clientDocumentTableDefTrait: ClientDocumentTableDef.Trait<
85
+ TName,
86
+ TType,
87
+ TEncoded,
88
+ ClientDocumentTableOptions<TType>
89
+ > = {
90
+ get: makeGetQueryBuilder(() => clientDocumentTableDef) as any,
91
+ set: setEventDef as any,
92
+ Value: 'only-for-type-inference' as any,
93
+ default: options.default,
94
+ valueSchema,
95
+ [ClientDocumentTableDefSymbol]: {
96
+ derived: {
97
+ setEventDef: derivedSetEventDef as any,
98
+ setMaterializer: derivedSetMaterializer as any,
99
+ },
100
+ options,
101
+ },
102
+ }
103
+
104
+ const clientDocumentTableDef = {
105
+ ...tableDef,
106
+ ...clientDocumentTableDefTrait,
107
+ } as any
108
+
109
+ return clientDocumentTableDef
110
+ }
111
+
112
+ const mergeDefaultValues = <T>(schemaDefaultValues: T, explicitDefaultValues: T): T => {
113
+ if (
114
+ typeof schemaDefaultValues !== 'object' ||
115
+ typeof explicitDefaultValues !== 'object' ||
116
+ schemaDefaultValues === null ||
117
+ explicitDefaultValues === null
118
+ ) {
119
+ return explicitDefaultValues
120
+ }
121
+
122
+ return Object.keys(schemaDefaultValues as any).reduce((acc, key) => {
123
+ acc[key] = (explicitDefaultValues as any)[key] ?? (schemaDefaultValues as any)[key]
124
+ return acc
125
+ }, {} as any)
126
+ }
127
+
128
+ export const deriveEventAndMaterializer = ({
129
+ name,
130
+ valueSchema,
131
+ defaultValue,
132
+ partialSet,
133
+ }: {
134
+ name: string
135
+ valueSchema: Schema.Schema<any, any>
136
+ defaultValue: any
137
+ partialSet: boolean
138
+ }) => {
139
+ const derivedSetEventDef = defineEvent({
140
+ name: `${name}Set`,
141
+ schema: Schema.Struct({
142
+ id: Schema.Union(Schema.String, Schema.UniqueSymbolFromSelf(SessionIdSymbol)),
143
+ value: partialSet ? Schema.partial(valueSchema) : valueSchema,
144
+ }).annotations({ title: `${name}Set:Args` }),
145
+ clientOnly: true,
146
+ derived: true,
147
+ })
148
+
149
+ const derivedSetMaterializer = defineMaterializer(derivedSetEventDef, ({ id, value }) => {
150
+ if (id === SessionIdSymbol) {
151
+ return shouldNeverHappen(`SessionIdSymbol needs to be replaced before materializing the set event`)
152
+ }
153
+
154
+ // Override the full value if it's not an object or no partial set is allowed
155
+ const schemaProps = SchemaAST.getPropertySignatures(valueSchema.ast)
156
+ if (schemaProps.length === 0 || partialSet === false) {
157
+ const valueColJsonSchema = Schema.parseJson(valueSchema)
158
+ const encodedInsertValue = Schema.encodeSyncDebug(valueColJsonSchema)(value ?? defaultValue)
159
+ const encodedUpdateValue = Schema.encodeSyncDebug(valueColJsonSchema)(value)
160
+
161
+ return {
162
+ sql: `INSERT INTO '${name}' (id, value) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET value = ?`,
163
+ bindValues: [id, encodedInsertValue, encodedUpdateValue],
164
+ writeTables: new Set([name]),
165
+ }
166
+ } else {
167
+ const valueColJsonSchema = Schema.parseJson(Schema.partial(valueSchema))
168
+
169
+ const encodedInsertValue = Schema.encodeSyncDebug(valueColJsonSchema)(mergeDefaultValues(defaultValue, value))
170
+
171
+ let jsonSetSql = 'value'
172
+ const setBindValues: unknown[] = []
173
+
174
+ const keys = Object.keys(value)
175
+ const partialUpdateSchema = valueSchema.pipe(Schema.pick(...keys))
176
+ const encodedPartialUpdate = Schema.encodeSyncDebug(partialUpdateSchema)(value)
177
+
178
+ for (const key in encodedPartialUpdate) {
179
+ const encodedValueForKey = encodedPartialUpdate[key]
180
+ jsonSetSql = `json_set(${jsonSetSql}, ?, json(?))`
181
+ setBindValues.push(`$.${key}`, JSON.stringify(encodedValueForKey))
182
+ }
183
+
184
+ const sqlQuery = `
185
+ INSERT INTO '${name}' (id, value)
186
+ VALUES (?, ?)
187
+ ON CONFLICT (id) DO UPDATE SET
188
+ value = ${jsonSetSql}
189
+ `
190
+
191
+ return {
192
+ sql: sqlQuery,
193
+ bindValues: [id, encodedInsertValue, ...setBindValues],
194
+ writeTables: new Set([name]),
195
+ }
196
+ }
197
+ })
198
+
199
+ return { eventDef: derivedSetEventDef, materializer: derivedSetMaterializer }
200
+ }
201
+
202
+ export const tableIsClientDocumentTable = <TTableDef extends TableDefBase>(
203
+ tableDef: TTableDef,
204
+ ): tableDef is TTableDef & {
205
+ options: { isClientDocumentTable: true }
206
+ } & ClientDocumentTableDef.Trait<TTableDef['sqliteDef']['name'], any, any, any> =>
207
+ tableDef.options.isClientDocumentTable === true
208
+
209
+ const makeGetQueryBuilder = <TTableDef extends ClientDocumentTableDef<any, any, any, any>>(
210
+ getTableDef: () => TTableDef,
211
+ ): ClientDocumentTableDef.MakeGetQueryBuilder<any, any, any> => {
212
+ return ((...args: any[]) => {
213
+ const tableDef = getTableDef()
214
+
215
+ const [id = tableDef[ClientDocumentTableDefSymbol].options.default.id, options = {}] = args
216
+
217
+ const explicitDefaultValues = options.default ?? tableDef[ClientDocumentTableDefSymbol].options.default.value
218
+
219
+ const ast: QueryBuilderAst.RowQuery = {
220
+ _tag: 'RowQuery',
221
+ tableDef,
222
+ id,
223
+ explicitDefaultValues,
224
+ }
225
+
226
+ const query = sql`SELECT * FROM '${tableDef.sqliteDef.name}' WHERE id = ?`
227
+
228
+ return {
229
+ [QueryBuilderTypeId]: QueryBuilderTypeId,
230
+ [QueryBuilderAstSymbol]: ast,
231
+ ResultType: 'only-for-type-inference' as any,
232
+ asSql: () => ({ query, bindValues: [id] }),
233
+ toString: () => query.toString(),
234
+ ...({} as any), // Needed for type cast
235
+ }
236
+ }) as any
237
+ }
238
+
239
+ export type ClientDocumentTableOptions<TType> = {
240
+ partialSet: boolean
241
+ default: {
242
+ id: SessionIdSymbol | string | undefined
243
+ value: TType
244
+ }
245
+ }
246
+
247
+ export namespace ClientDocumentTableOptions {
248
+ export type Input<TType> = {
249
+ /**
250
+ * Whether to allow for partial set operations. Only applies if the schema is a struct.
251
+ *
252
+ * @default true
253
+ */
254
+ partialSet?: boolean
255
+ default: {
256
+ id?: SessionIdSymbol | string | undefined
257
+ value: TType
258
+ }
259
+ }
260
+
261
+ export type WithDefaults<TInput extends Input<any>> = {
262
+ partialSet: TInput['partialSet'] extends false ? false : true
263
+ default: {
264
+ id: TInput['default']['id'] extends string | SessionIdSymbol ? TInput['default']['id'] : undefined
265
+ value: TInput['default']['value']
266
+ }
267
+ }
268
+ }
269
+
270
+ export type ClientDocumentTableDef<
271
+ TName extends string,
272
+ TType,
273
+ TEncoded,
274
+ TOptions extends ClientDocumentTableOptions<TType>,
275
+ > = TableDef<
276
+ ClientDocumentTableDef.SqliteDef<TName, TType>,
277
+ {
278
+ isClientDocumentTable: true
279
+ }
280
+ > &
281
+ ClientDocumentTableDef.Trait<TName, TType, TEncoded, TOptions>
282
+
283
+ export namespace ClientDocumentTableDef {
284
+ export type Any = ClientDocumentTableDef<any, any, any, any>
285
+
286
+ export type SqliteDef<TName extends string, TType> = SqliteDsl.TableDefinition<
287
+ TName,
288
+ {
289
+ id: SqliteDsl.ColumnDefinition<string, string> & { default: Option.Some<string> }
290
+ value: SqliteDsl.ColumnDefinition<string, TType> & { default: Option.Some<TType> }
291
+ }
292
+ >
293
+
294
+ export type TableDefBase_<TName extends string, TType> = TableDefBase<
295
+ SqliteDef<TName, TType>,
296
+ {
297
+ isClientDocumentTable: true
298
+ }
299
+ >
300
+
301
+ export interface Trait<TName extends string, TType, TEncoded, TOptions extends ClientDocumentTableOptions<TType>> {
302
+ /**
303
+ * Get the current value of the client document table.
304
+ *
305
+ * @example
306
+ * ```ts
307
+ * const someDocumentTable = State.SQLite.clientDocument({
308
+ * name: 'SomeDocumentTable',
309
+ * schema: Schema.Struct({
310
+ * someField: Schema.String,
311
+ * }),
312
+ * default: { value: { someField: 'some-value' } },
313
+ * })
314
+ *
315
+ * const value$ = queryDb(someDocumentTable.get('some-id'))
316
+ *
317
+ * // When you've set a default id, you can omit the id argument
318
+ *
319
+ * const uiState = State.SQLite.clientDocument({
320
+ * name: 'UiState',
321
+ * schema: Schema.Struct({
322
+ * someField: Schema.String,
323
+ * }),
324
+ * default: { id: SessionIdSymbol, value: { someField: 'some-value' } },
325
+ * })
326
+ *
327
+ * const value$ = queryDb(uiState.get())
328
+ * ```
329
+ */
330
+ readonly get: MakeGetQueryBuilder<TName, TType, TOptions>
331
+ /**
332
+ * Derived event definition for setting the value of the client document table.
333
+ * If the document doesn't exist yet, the first .set event will create it.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * const someDocumentTable = State.SQLite.clientDocument({
338
+ * name: 'SomeDocumentTable',
339
+ * schema: Schema.Struct({
340
+ * someField: Schema.String,
341
+ * someOtherField: Schema.String,
342
+ * }),
343
+ * default: { value: { someField: 'some-default-value', someOtherField: 'some-other-default-value' } },
344
+ * })
345
+ *
346
+ * const setEventDef = store.commit(someDocumentTable.set({ someField: 'explicit-value' }, 'some-id'))
347
+ * // Will commit an event with the following payload:
348
+ * // { id: 'some-id', value: { someField: 'explicit-value', someOtherField: 'some-other-default-value' } }
349
+ * ```
350
+ *
351
+ * Similar to `.get`, you can omit the id argument if you've set a default id.
352
+ *
353
+ * @example
354
+ * ```ts
355
+ * const uiState = State.SQLite.clientDocument({
356
+ * name: 'UiState',
357
+ * schema: Schema.Struct({ someField: Schema.String }),
358
+ * default: { id: SessionIdSymbol, value: { someField: 'some-default-value' } },
359
+ * })
360
+ *
361
+ * const setEventDef = store.commit(uiState.set({ someField: 'explicit-value' }))
362
+ * // Will commit an event with the following payload:
363
+ * // { id: '...', value: { someField: 'explicit-value' } }
364
+ * // ^^^
365
+ * // Automatically replaced with the client session id
366
+ * ```
367
+ */
368
+ readonly set: SetEventDefLike<TName, TType, TOptions>
369
+ readonly Value: TType
370
+ readonly valueSchema: Schema.Schema<TType, TEncoded>
371
+ readonly default: TOptions['default']
372
+ readonly [ClientDocumentTableDefSymbol]: {
373
+ readonly options: TOptions
374
+ readonly derived: {
375
+ readonly setEventDef: SetEventDef<TName, TType, TOptions>
376
+ readonly setMaterializer: Materializer<SetEventDef<TName, TType, TOptions>>
377
+ }
378
+ }
379
+ }
380
+
381
+ export type GetOptions<TTableDef extends TraitAny> =
382
+ TTableDef extends ClientDocumentTableDef.Trait<any, any, any, infer TOptions> ? TOptions : never
383
+
384
+ export type TraitAny = Trait<any, any, any, any>
385
+
386
+ export type DefaultIdType<TTableDef extends TraitAny> =
387
+ TTableDef extends ClientDocumentTableDef.Trait<any, any, any, infer TOptions>
388
+ ? TOptions['default']['id'] extends SessionIdSymbol | string
389
+ ? TOptions['default']['id']
390
+ : never
391
+ : never
392
+
393
+ export type SetEventDefLike<TName extends string, TType, TOptions extends ClientDocumentTableOptions<TType>> =
394
+ // Helper to create partial event
395
+ (TOptions['default']['id'] extends undefined
396
+ ? (
397
+ args: TOptions['partialSet'] extends false ? TType : Partial<TType>,
398
+ id: string | SessionIdSymbol,
399
+ ) => { name: `${TName}Set`; args: { id: string; value: TType } }
400
+ : (
401
+ args: TOptions['partialSet'] extends false ? TType : Partial<TType>,
402
+ id?: string | SessionIdSymbol,
403
+ ) => { name: `${TName}Set`; args: { id: string; value: TType } }) & {
404
+ readonly name: `${TName}Set`
405
+ readonly schema: Schema.Schema<any>
406
+ readonly Event: {
407
+ readonly name: `${TName}Set`
408
+ readonly args: { id: string; value: TType }
409
+ }
410
+ readonly options: { derived: true; clientOnly: true; facts: undefined }
411
+ }
412
+
413
+ export type SetEventDef<TName extends string, TType, TOptions extends ClientDocumentTableOptions<TType>> = EventDef<
414
+ TName,
415
+ TOptions['partialSet'] extends false ? { id: string; value: TType } : { id: string; value: Partial<TType> },
416
+ any,
417
+ true
418
+ >
419
+
420
+ export type MakeGetQueryBuilder<
421
+ TName extends string,
422
+ TType,
423
+ TOptions extends ClientDocumentTableOptions<TType>,
424
+ > = TOptions extends ClientDocumentTableOptions<TType> & { default: { id: string | SessionIdSymbol } }
425
+ ? (
426
+ id?: TOptions['default']['id'] | SessionIdSymbol,
427
+ options?: { default: Partial<TType> },
428
+ ) => QueryBuilder<TType, ClientDocumentTableDef.TableDefBase_<TName, TType>, QueryBuilder.ApiFeature>
429
+ : (
430
+ id: string | SessionIdSymbol,
431
+ options?: { default: Partial<TType> },
432
+ ) => QueryBuilder<TType, ClientDocumentTableDef.TableDefBase_<TName, TType>, QueryBuilder.ApiFeature>
433
+ }
434
+
435
+ export const ClientDocumentTableDefSymbol = Symbol('ClientDocumentTableDef')
436
+ export type ClientDocumentTableDefSymbol = typeof ClientDocumentTableDefSymbol
@@ -122,7 +122,6 @@ export type IsSingleColumn<TColumns extends Columns | ColumnDefinition<any, any>
122
122
  *
123
123
  * Hopefully this can be removed in the future
124
124
  */
125
-
126
125
  export type ConstraintColumns = Record<string, ColumnDefinition<any, any>> & { __constrained?: never }
127
126
 
128
127
  export type Index = {
@@ -0,0 +1 @@
1
+ export { defineEvent, synced, clientOnly } from './EventDef.js'
package/src/schema/mod.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export * from './system-tables.js'
2
- export * as DbSchema from './table-def.js'
3
2
  export { SqliteAst, SqliteDsl } from './db-schema/mod.js'
4
- export * from './mutations.js'
3
+ export * from './EventDef.js'
5
4
  export * from './schema-helpers.js'
6
5
  export * from './schema.js'
7
- export * as MutationEvent from './MutationEvent.js'
6
+ export * as State from './view.js'
7
+ export * as LiveStoreEvent from './LiveStoreEvent.js'
8
8
  export * as EventId from './EventId.js'
9
+ export * as Events from './events.js'