@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,197 @@
1
+ import { LS_DEV, memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
+ import { Effect, ReadonlyArray, Schema } from '@livestore/utils/effect'
3
+
4
+ import type { SqliteDb } from '../adapter-types.js'
5
+ import { getExecArgsFromEvent } from '../materializer-helper.js'
6
+ import type { LiveStoreEvent, LiveStoreSchema, SessionChangesetMetaRow } from '../schema/mod.js'
7
+ import {
8
+ EventId,
9
+ EVENTLOG_META_TABLE,
10
+ getEventDef,
11
+ SESSION_CHANGESET_META_TABLE,
12
+ sessionChangesetMetaTable,
13
+ } from '../schema/mod.js'
14
+ import { insertRow } from '../sql-queries/index.js'
15
+ import { sql } from '../util.js'
16
+ import { execSql, execSqlPrepared } from './connection.js'
17
+ import * as Eventlog from './eventlog.js'
18
+ import type { ApplyEvent } from './types.js'
19
+
20
+ export const makeApplyEvent = ({
21
+ schema,
22
+ dbReadModel: db,
23
+ dbEventlog,
24
+ }: {
25
+ schema: LiveStoreSchema
26
+ dbReadModel: SqliteDb
27
+ dbEventlog: SqliteDb
28
+ }): Effect.Effect<ApplyEvent, never> =>
29
+ Effect.gen(function* () {
30
+ const shouldExcludeEventFromLog = makeShouldExcludeEventFromLog(schema)
31
+
32
+ const eventDefSchemaHashMap = new Map(
33
+ // TODO Running `Schema.hash` can be a bottleneck for larger schemas. There is an opportunity to run this
34
+ // at build time and lookup the pre-computed hash at runtime.
35
+ // Also see https://github.com/Effect-TS/effect/issues/2719
36
+ [...schema.eventsDefsMap.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const),
37
+ )
38
+
39
+ return (eventEncoded, options) =>
40
+ Effect.gen(function* () {
41
+ const skipEventlog = options?.skipEventlog ?? false
42
+
43
+ const eventName = eventEncoded.name
44
+ const eventDef = getEventDef(schema, eventName)
45
+
46
+ const execArgsArr = getExecArgsFromEvent({
47
+ eventDef,
48
+ event: { decoded: undefined, encoded: eventEncoded },
49
+ })
50
+
51
+ // NOTE we might want to bring this back if we want to debug no-op events
52
+ // const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
53
+ // onRowsChanged: (rowsChanged: number) => {
54
+ // if (rowsChanged === 0) {
55
+ // console.warn(`Event "${eventDef.name}" did not affect any rows:`, statementSql, bindValues)
56
+ // }
57
+ // },
58
+ // })
59
+
60
+ // console.group('[@livestore/common:leader-thread:applyEvent]', { eventName })
61
+
62
+ const session = db.session()
63
+
64
+ for (const { statementSql, bindValues } of execArgsArr) {
65
+ // console.debug(eventName, statementSql, bindValues)
66
+ // TODO use cached prepared statements instead of exec
67
+ yield* execSqlPrepared(db, statementSql, bindValues)
68
+ }
69
+
70
+ const changeset = session.changeset()
71
+ session.finish()
72
+
73
+ // TODO use prepared statements
74
+ yield* execSql(
75
+ db,
76
+ ...insertRow({
77
+ tableName: SESSION_CHANGESET_META_TABLE,
78
+ columns: sessionChangesetMetaTable.sqliteDef.columns,
79
+ values: {
80
+ idGlobal: eventEncoded.id.global,
81
+ idClient: eventEncoded.id.client,
82
+ // NOTE the changeset will be empty (i.e. null) for no-op events
83
+ changeset: changeset ?? null,
84
+ debug: LS_DEV ? execArgsArr : null,
85
+ },
86
+ }),
87
+ )
88
+
89
+ // console.groupEnd()
90
+
91
+ // write to eventlog
92
+ const excludeFromEventlog = shouldExcludeEventFromLog(eventName, eventEncoded)
93
+ if (skipEventlog === false && excludeFromEventlog === false) {
94
+ const eventName = eventEncoded.name
95
+ const eventDefSchemaHash =
96
+ eventDefSchemaHashMap.get(eventName) ?? shouldNeverHappen(`Unknown event definition: ${eventName}`)
97
+
98
+ yield* Eventlog.insertIntoEventlog(
99
+ eventEncoded,
100
+ dbEventlog,
101
+ eventDefSchemaHash,
102
+ eventEncoded.clientId,
103
+ eventEncoded.sessionId,
104
+ )
105
+ } else {
106
+ // console.debug('[@livestore/common:leader-thread] skipping eventlog write', mutation, statementSql, bindValues)
107
+ }
108
+
109
+ return {
110
+ sessionChangeset: changeset
111
+ ? {
112
+ _tag: 'sessionChangeset' as const,
113
+ data: changeset,
114
+ debug: LS_DEV ? execArgsArr : null,
115
+ }
116
+ : { _tag: 'no-op' as const },
117
+ }
118
+ }).pipe(
119
+ Effect.withSpan(`@livestore/common:leader-thread:applyEvent`, {
120
+ attributes: {
121
+ eventName: eventEncoded.name,
122
+ mutationId: eventEncoded.id,
123
+ 'span.label': `${EventId.toString(eventEncoded.id)} ${eventEncoded.name}`,
124
+ },
125
+ }),
126
+ // Effect.logDuration('@livestore/common:leader-thread:applyEvent'),
127
+ )
128
+ })
129
+
130
+ export const rollback = ({
131
+ db,
132
+ dbEventlog,
133
+ eventIdsToRollback,
134
+ }: {
135
+ db: SqliteDb
136
+ dbEventlog: SqliteDb
137
+ eventIdsToRollback: EventId.EventId[]
138
+ }) =>
139
+ Effect.gen(function* () {
140
+ const rollbackEvents = db
141
+ .select<SessionChangesetMetaRow>(
142
+ sql`SELECT * FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idClient) IN (${eventIdsToRollback.map((id) => `(${id.global}, ${id.client})`).join(', ')})`,
143
+ )
144
+ .map((_) => ({ id: { global: _.idGlobal, client: _.idClient }, changeset: _.changeset, debug: _.debug }))
145
+ .toSorted((a, b) => EventId.compare(a.id, b.id))
146
+
147
+ // Apply changesets in reverse order
148
+ for (let i = rollbackEvents.length - 1; i >= 0; i--) {
149
+ const { changeset } = rollbackEvents[i]!
150
+ if (changeset !== null) {
151
+ db.makeChangeset(changeset).invert().apply()
152
+ }
153
+ }
154
+
155
+ const eventIdPairChunks = ReadonlyArray.chunksOf(100)(
156
+ eventIdsToRollback.map((id) => `(${id.global}, ${id.client})`),
157
+ )
158
+
159
+ // Delete the changeset rows
160
+ for (const eventIdPairChunk of eventIdPairChunks) {
161
+ db.execute(
162
+ sql`DELETE FROM ${SESSION_CHANGESET_META_TABLE} WHERE (idGlobal, idClient) IN (${eventIdPairChunk.join(', ')})`,
163
+ )
164
+ }
165
+
166
+ // Delete the eventlog rows
167
+ for (const eventIdPairChunk of eventIdPairChunks) {
168
+ dbEventlog.execute(
169
+ sql`DELETE FROM ${EVENTLOG_META_TABLE} WHERE (idGlobal, idClient) IN (${eventIdPairChunk.join(', ')})`,
170
+ )
171
+ }
172
+ }).pipe(
173
+ Effect.withSpan('@livestore/common:LeaderSyncProcessor:rollback', {
174
+ attributes: { count: eventIdsToRollback.length },
175
+ }),
176
+ )
177
+
178
+ // TODO let's consider removing this "should exclude" mechanism in favour of log compaction etc
179
+ const makeShouldExcludeEventFromLog = memoizeByRef((schema: LiveStoreSchema) => {
180
+ const migrationOptions = schema.migrationOptions
181
+ const eventlogExclude =
182
+ migrationOptions.strategy === 'from-eventlog'
183
+ ? (migrationOptions.excludeEvents ?? new Set(['livestore.RawSql']))
184
+ : new Set(['livestore.RawSql'])
185
+
186
+ return (eventName: string, eventEncoded: LiveStoreEvent.AnyEncoded): boolean => {
187
+ if (eventlogExclude.has(eventName)) return true
188
+
189
+ const eventDef = getEventDef(schema, eventName)
190
+ const execArgsArr = getExecArgsFromEvent({
191
+ eventDef,
192
+ event: { decoded: undefined, encoded: eventEncoded },
193
+ })
194
+
195
+ return execArgsArr.some((_) => _.statementSql.includes('__livestore'))
196
+ }
197
+ })
@@ -0,0 +1,199 @@
1
+ import { LS_DEV, shouldNeverHappen } from '@livestore/utils'
2
+ import { Effect, Option, Schema } from '@livestore/utils/effect'
3
+
4
+ import type { SqliteDb } from '../adapter-types.js'
5
+ import * as EventId from '../schema/EventId.js'
6
+ import * as LiveStoreEvent from '../schema/LiveStoreEvent.js'
7
+ import {
8
+ EVENTLOG_META_TABLE,
9
+ eventlogMetaTable,
10
+ sessionChangesetMetaTable,
11
+ SYNC_STATUS_TABLE,
12
+ syncStatusTable,
13
+ } from '../schema/system-tables.js'
14
+ import { migrateTable } from '../schema-management/migrations.js'
15
+ import { insertRow, updateRows } from '../sql-queries/sql-queries.js'
16
+ import type { PreparedBindValues } from '../util.js'
17
+ import { prepareBindValues, sql } from '../util.js'
18
+ import { execSql } from './connection.js'
19
+ import type { InitialSyncInfo } from './types.js'
20
+ import { LeaderThreadCtx } from './types.js'
21
+
22
+ export const initEventlogDb = (dbEventlog: SqliteDb) =>
23
+ Effect.gen(function* () {
24
+ yield* migrateTable({
25
+ db: dbEventlog,
26
+ behaviour: 'create-if-not-exists',
27
+ tableAst: eventlogMetaTable.sqliteDef.ast,
28
+ skipMetaTable: true,
29
+ })
30
+
31
+ yield* migrateTable({
32
+ db: dbEventlog,
33
+ behaviour: 'create-if-not-exists',
34
+ tableAst: syncStatusTable.sqliteDef.ast,
35
+ skipMetaTable: true,
36
+ })
37
+
38
+ // Create sync status row if it doesn't exist
39
+ yield* execSql(
40
+ dbEventlog,
41
+ sql`INSERT INTO ${SYNC_STATUS_TABLE} (head)
42
+ SELECT ${EventId.ROOT.global}
43
+ WHERE NOT EXISTS (SELECT 1 FROM ${SYNC_STATUS_TABLE})`,
44
+ {},
45
+ )
46
+ })
47
+
48
+ /** Exclusive of the "since event" */
49
+ export const getEventsSince = (
50
+ since: EventId.EventId,
51
+ ): Effect.Effect<ReadonlyArray<LiveStoreEvent.EncodedWithMeta>, never, LeaderThreadCtx> =>
52
+ Effect.gen(function* () {
53
+ const { dbEventlog, dbReadModel } = yield* LeaderThreadCtx
54
+
55
+ const query = eventlogMetaTable.where('idGlobal', '>=', since.global).asSql()
56
+ const pendingEventsRaw = dbEventlog.select(query.query, prepareBindValues(query.bindValues, query.query))
57
+ const pendingEvents = Schema.decodeUnknownSync(eventlogMetaTable.rowSchema.pipe(Schema.Array))(pendingEventsRaw)
58
+
59
+ const sessionChangesetRows = sessionChangesetMetaTable.where('idGlobal', '>=', since.global).asSql()
60
+ const sessionChangesetRowsRaw = dbReadModel.select(
61
+ sessionChangesetRows.query,
62
+ prepareBindValues(sessionChangesetRows.bindValues, sessionChangesetRows.query),
63
+ )
64
+ const sessionChangesetRowsDecoded = Schema.decodeUnknownSync(
65
+ sessionChangesetMetaTable.rowSchema.pipe(Schema.Array),
66
+ )(sessionChangesetRowsRaw)
67
+
68
+ return pendingEvents
69
+ .map((eventlogEvent) => {
70
+ const sessionChangeset = sessionChangesetRowsDecoded.find(
71
+ (readModelEvent) =>
72
+ readModelEvent.idGlobal === eventlogEvent.idGlobal && readModelEvent.idClient === eventlogEvent.idClient,
73
+ )
74
+ return LiveStoreEvent.EncodedWithMeta.make({
75
+ name: eventlogEvent.name,
76
+ args: eventlogEvent.argsJson,
77
+ id: { global: eventlogEvent.idGlobal, client: eventlogEvent.idClient },
78
+ parentId: { global: eventlogEvent.parentIdGlobal, client: eventlogEvent.parentIdClient },
79
+ clientId: eventlogEvent.clientId,
80
+ sessionId: eventlogEvent.sessionId,
81
+ meta: {
82
+ sessionChangeset:
83
+ sessionChangeset && sessionChangeset.changeset !== null
84
+ ? {
85
+ _tag: 'sessionChangeset' as const,
86
+ data: sessionChangeset.changeset,
87
+ debug: sessionChangeset.debug,
88
+ }
89
+ : { _tag: 'unset' as const },
90
+ syncMetadata: eventlogEvent.syncMetadataJson,
91
+ },
92
+ })
93
+ })
94
+ .filter((_) => EventId.compare(_.id, since) > 0)
95
+ .sort((a, b) => EventId.compare(a.id, b.id))
96
+ })
97
+
98
+ export const getClientHeadFromDb = (dbEventlog: SqliteDb): EventId.EventId => {
99
+ const res = dbEventlog.select<{ idGlobal: EventId.GlobalEventId; idClient: EventId.ClientEventId }>(
100
+ sql`select idGlobal, idClient from ${EVENTLOG_META_TABLE} order by idGlobal DESC, idClient DESC limit 1`,
101
+ )[0]
102
+
103
+ return res ? { global: res.idGlobal, client: res.idClient } : EventId.ROOT
104
+ }
105
+
106
+ export const getBackendHeadFromDb = (dbEventlog: SqliteDb): EventId.GlobalEventId =>
107
+ dbEventlog.select<{ head: EventId.GlobalEventId }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ??
108
+ EventId.ROOT.global
109
+
110
+ // TODO use prepared statements
111
+ export const updateBackendHead = (dbEventlog: SqliteDb, head: EventId.EventId) =>
112
+ dbEventlog.execute(sql`UPDATE ${SYNC_STATUS_TABLE} SET head = ${head.global}`)
113
+
114
+ export const insertIntoEventlog = (
115
+ eventEncoded: LiveStoreEvent.EncodedWithMeta,
116
+ dbEventlog: SqliteDb,
117
+ eventDefSchemaHash: number,
118
+ clientId: string,
119
+ sessionId: string,
120
+ ) =>
121
+ Effect.gen(function* () {
122
+ // Check history consistency during LS_DEV
123
+ if (LS_DEV && eventEncoded.parentId.global !== EventId.ROOT.global) {
124
+ const parentEventExists =
125
+ dbEventlog.select<{ count: number }>(
126
+ `SELECT COUNT(*) as count FROM ${EVENTLOG_META_TABLE} WHERE idGlobal = ? AND idClient = ?`,
127
+ [eventEncoded.parentId.global, eventEncoded.parentId.client] as any as PreparedBindValues,
128
+ )[0]!.count === 1
129
+
130
+ if (parentEventExists === false) {
131
+ shouldNeverHappen(
132
+ `Parent mutation ${eventEncoded.parentId.global},${eventEncoded.parentId.client} does not exist`,
133
+ )
134
+ }
135
+ }
136
+
137
+ // TODO use prepared statements
138
+ yield* execSql(
139
+ dbEventlog,
140
+ ...insertRow({
141
+ tableName: EVENTLOG_META_TABLE,
142
+ columns: eventlogMetaTable.sqliteDef.columns,
143
+ values: {
144
+ idGlobal: eventEncoded.id.global,
145
+ idClient: eventEncoded.id.client,
146
+ parentIdGlobal: eventEncoded.parentId.global,
147
+ parentIdClient: eventEncoded.parentId.client,
148
+ name: eventEncoded.name,
149
+ argsJson: eventEncoded.args ?? {},
150
+ clientId,
151
+ sessionId,
152
+ schemaHash: eventDefSchemaHash,
153
+ syncMetadataJson: eventEncoded.meta.syncMetadata,
154
+ },
155
+ }),
156
+ )
157
+ })
158
+
159
+ export const updateSyncMetadata = (items: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>) =>
160
+ Effect.gen(function* () {
161
+ const { dbEventlog } = yield* LeaderThreadCtx
162
+
163
+ // TODO try to do this in a single query
164
+ for (let i = 0; i < items.length; i++) {
165
+ const event = items[i]!
166
+
167
+ yield* execSql(
168
+ dbEventlog,
169
+ ...updateRows({
170
+ tableName: EVENTLOG_META_TABLE,
171
+ columns: eventlogMetaTable.sqliteDef.columns,
172
+ where: { idGlobal: event.id.global, idClient: event.id.client },
173
+ updateValues: { syncMetadataJson: event.meta.syncMetadata },
174
+ }),
175
+ )
176
+ }
177
+ })
178
+
179
+ export const getSyncBackendCursorInfo = (remoteHead: EventId.GlobalEventId) =>
180
+ Effect.gen(function* () {
181
+ const { dbEventlog } = yield* LeaderThreadCtx
182
+
183
+ if (remoteHead === EventId.ROOT.global) return Option.none()
184
+
185
+ const EventlogQuerySchema = Schema.Struct({
186
+ syncMetadataJson: Schema.parseJson(Schema.Option(Schema.JsonValue)),
187
+ }).pipe(Schema.pluck('syncMetadataJson'), Schema.Array, Schema.head)
188
+
189
+ const syncMetadataOption = yield* Effect.sync(() =>
190
+ dbEventlog.select<{ syncMetadataJson: string }>(
191
+ sql`SELECT syncMetadataJson FROM ${EVENTLOG_META_TABLE} WHERE idGlobal = ${remoteHead} ORDER BY idClient ASC LIMIT 1`,
192
+ ),
193
+ ).pipe(Effect.andThen(Schema.decode(EventlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie)
194
+
195
+ return Option.some({
196
+ cursor: { global: remoteHead, client: EventId.clientDefault },
197
+ metadata: syncMetadataOption,
198
+ }) satisfies InitialSyncInfo
199
+ }).pipe(Effect.withSpan('@livestore/common:eventlog:getSyncBackendCursorInfo', { attributes: { remoteHead } }))
@@ -2,7 +2,7 @@ import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/ut
2
2
  import { nanoid } from '@livestore/utils/nanoid'
3
3
 
4
4
  import { Devtools, IntentionalShutdownCause, liveStoreVersion, UnexpectedError } from '../index.js'
5
- import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '../schema/mod.js'
5
+ import { EVENTLOG_META_TABLE, SCHEMA_EVENT_DEFS_META_TABLE, SCHEMA_META_TABLE } from '../schema/mod.js'
6
6
  import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
7
7
  import { LeaderThreadCtx } from './types.js'
8
8
 
@@ -15,7 +15,7 @@ export const bootDevtools = (options: DevtoolsOptions) =>
15
15
  return
16
16
  }
17
17
 
18
- const { connectedClientSessionPullQueues, syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
18
+ const { syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
19
19
 
20
20
  yield* listenToDevtools({
21
21
  incomingMessages: Stream.fromQueue(extraIncomingMessagesQueue),
@@ -33,13 +33,11 @@ export const bootDevtools = (options: DevtoolsOptions) =>
33
33
  Effect.ignoreLogged,
34
34
  )
35
35
 
36
- const { localHead } = yield* syncProcessor.syncState
36
+ const syncState = yield* syncProcessor.syncState
37
+ const mergeCounter = syncProcessor.getMergeCounter()
37
38
 
38
- // TODO close queue when devtools disconnects
39
- const pullQueue = yield* connectedClientSessionPullQueues.makeQueue(localHead)
40
-
41
- yield* Stream.fromQueue(pullQueue).pipe(
42
- Stream.tap((msg) => sendMessage(Devtools.Leader.SyncPull.make({ payload: msg.payload, liveStoreVersion }))),
39
+ yield* syncProcessor.pull({ cursor: { mergeCounter, eventId: syncState.localHead } }).pipe(
40
+ Stream.tap(({ payload }) => sendMessage(Devtools.Leader.SyncPull.make({ payload, liveStoreVersion }))),
43
41
  Stream.runDrain,
44
42
  Effect.forkScoped,
45
43
  )
@@ -65,7 +63,7 @@ const listenToDevtools = ({
65
63
  syncBackend,
66
64
  makeSqliteDb,
67
65
  dbReadModel,
68
- dbMutationLog,
66
+ dbEventlog,
69
67
  shutdownStateSubRef,
70
68
  shutdownChannel,
71
69
  syncProcessor,
@@ -145,20 +143,20 @@ const listenToDevtools = ({
145
143
  }
146
144
 
147
145
  try {
148
- if (tableNames.has(MUTATION_LOG_META_TABLE)) {
149
- // Is mutation log
146
+ if (tableNames.has(EVENTLOG_META_TABLE)) {
147
+ // Is eventlog
150
148
  yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
151
149
 
152
- dbMutationLog.import(data)
150
+ dbEventlog.import(data)
153
151
 
154
152
  dbReadModel.destroy()
155
- } else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
153
+ } else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_EVENT_DEFS_META_TABLE)) {
156
154
  // Is read model
157
155
  yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
158
156
 
159
157
  dbReadModel.import(data)
160
158
 
161
- dbMutationLog.destroy()
159
+ dbEventlog.destroy()
162
160
  } else {
163
161
  yield* sendMessage(
164
162
  Devtools.Leader.LoadDatabaseFile.Error.make({
@@ -192,7 +190,7 @@ const listenToDevtools = ({
192
190
  dbReadModel.destroy()
193
191
 
194
192
  if (mode === 'all-data') {
195
- dbMutationLog.destroy()
193
+ dbEventlog.destroy()
196
194
  }
197
195
 
198
196
  yield* sendMessage(Devtools.Leader.ResetAllData.Success.make({ ...reqPayload }))
@@ -209,33 +207,33 @@ const listenToDevtools = ({
209
207
 
210
208
  const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
211
209
  const dbFileSize = dbReadModel.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
212
- const mutationLogFileSize = dbMutationLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
210
+ const eventlogFileSize = dbEventlog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
213
211
 
214
212
  yield* sendMessage(
215
213
  Devtools.Leader.DatabaseFileInfoRes.make({
216
214
  readModel: { fileSize: dbFileSize, persistenceInfo: persistenceInfo.readModel },
217
- mutationLog: { fileSize: mutationLogFileSize, persistenceInfo: persistenceInfo.mutationLog },
215
+ eventlog: { fileSize: eventlogFileSize, persistenceInfo: persistenceInfo.eventlog },
218
216
  ...reqPayload,
219
217
  }),
220
218
  )
221
219
 
222
220
  return
223
221
  }
224
- case 'LSD.Leader.MutationLogReq': {
225
- const mutationLog = dbMutationLog.export()
222
+ case 'LSD.Leader.EventlogReq': {
223
+ const eventlog = dbEventlog.export()
226
224
 
227
- yield* sendMessage(Devtools.Leader.MutationLogRes.make({ mutationLog, ...reqPayload }))
225
+ yield* sendMessage(Devtools.Leader.EventlogRes.make({ eventlog, ...reqPayload }))
228
226
 
229
227
  return
230
228
  }
231
- case 'LSD.Leader.RunMutationReq': {
229
+ case 'LSD.Leader.CommitEventReq': {
232
230
  yield* syncProcessor.pushPartial({
233
- mutationEvent: decodedEvent.mutationEventEncoded,
231
+ event: decodedEvent.eventEncoded,
234
232
  clientId: `devtools-${clientId}`,
235
233
  sessionId: `devtools-${clientId}`,
236
234
  })
237
235
 
238
- yield* sendMessage(Devtools.Leader.RunMutationRes.make({ ...reqPayload }))
236
+ yield* sendMessage(Devtools.Leader.CommitEventRes.make({ ...reqPayload }))
239
237
 
240
238
  return
241
239
  }
@@ -247,10 +245,10 @@ const listenToDevtools = ({
247
245
  yield* syncBackend.pull(Option.none()).pipe(
248
246
  Stream.map((_) => _.batch),
249
247
  Stream.flattenIterables,
250
- Stream.tap(({ mutationEventEncoded, metadata }) =>
248
+ Stream.tap(({ eventEncoded, metadata }) =>
251
249
  sendMessage(
252
250
  Devtools.Leader.SyncHistoryRes.make({
253
- mutationEventEncoded,
251
+ eventEncoded,
254
252
  metadata,
255
253
  subscriptionId,
256
254
  ...reqPayload,