@livestore/common 0.2.0 → 0.3.0-dev.1

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 (243) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/fixture.d.ts +163 -1
  3. package/dist/__tests__/fixture.d.ts.map +1 -1
  4. package/dist/__tests__/fixture.js +3 -1
  5. package/dist/__tests__/fixture.js.map +1 -1
  6. package/dist/adapter-types.d.ts +53 -38
  7. package/dist/adapter-types.d.ts.map +1 -1
  8. package/dist/adapter-types.js +5 -7
  9. package/dist/adapter-types.js.map +1 -1
  10. package/dist/bounded-collections.d.ts +2 -2
  11. package/dist/bounded-collections.d.ts.map +1 -1
  12. package/dist/debug-info.d.ts +13 -13
  13. package/dist/derived-mutations.d.ts +1 -1
  14. package/dist/derived-mutations.d.ts.map +1 -1
  15. package/dist/devtools/devtools-bridge.d.ts +2 -2
  16. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  17. package/dist/devtools/devtools-messages.d.ts +84 -196
  18. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  19. package/dist/devtools/devtools-messages.js +55 -61
  20. package/dist/devtools/devtools-messages.js.map +1 -1
  21. package/dist/devtools/index.d.ts.map +1 -1
  22. package/dist/devtools/index.js +1 -2
  23. package/dist/devtools/index.js.map +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/init-singleton-tables.d.ts +1 -1
  29. package/dist/init-singleton-tables.d.ts.map +1 -1
  30. package/dist/leader-thread/apply-mutation.d.ts +8 -0
  31. package/dist/leader-thread/apply-mutation.d.ts.map +1 -0
  32. package/dist/leader-thread/apply-mutation.js +95 -0
  33. package/dist/leader-thread/apply-mutation.js.map +1 -0
  34. package/dist/leader-thread/connection.d.ts +11 -0
  35. package/dist/leader-thread/connection.d.ts.map +1 -0
  36. package/dist/leader-thread/connection.js +44 -0
  37. package/dist/leader-thread/connection.js.map +1 -0
  38. package/dist/leader-thread/leader-sync-processor.d.ts +47 -0
  39. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -0
  40. package/dist/leader-thread/leader-sync-processor.js +422 -0
  41. package/dist/leader-thread/leader-sync-processor.js.map +1 -0
  42. package/dist/leader-thread/leader-worker-devtools.d.ts +6 -0
  43. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -0
  44. package/dist/leader-thread/leader-worker-devtools.js +216 -0
  45. package/dist/leader-thread/leader-worker-devtools.js.map +1 -0
  46. package/dist/leader-thread/make-leader-thread-layer.d.ts +20 -0
  47. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -0
  48. package/dist/leader-thread/make-leader-thread-layer.js +106 -0
  49. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -0
  50. package/dist/leader-thread/mod.d.ts +7 -0
  51. package/dist/leader-thread/mod.d.ts.map +1 -0
  52. package/dist/leader-thread/mod.js +7 -0
  53. package/dist/leader-thread/mod.js.map +1 -0
  54. package/dist/leader-thread/mutationlog.d.ts +23 -0
  55. package/dist/leader-thread/mutationlog.d.ts.map +1 -0
  56. package/dist/leader-thread/mutationlog.js +27 -0
  57. package/dist/leader-thread/mutationlog.js.map +1 -0
  58. package/dist/leader-thread/pull-queue-set.d.ts +7 -0
  59. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -0
  60. package/dist/leader-thread/pull-queue-set.js +39 -0
  61. package/dist/leader-thread/pull-queue-set.js.map +1 -0
  62. package/dist/leader-thread/recreate-db.d.ts +7 -0
  63. package/dist/leader-thread/recreate-db.d.ts.map +1 -0
  64. package/dist/leader-thread/recreate-db.js +69 -0
  65. package/dist/leader-thread/recreate-db.js.map +1 -0
  66. package/dist/leader-thread/shutdown-channel.d.ts +15 -0
  67. package/dist/leader-thread/shutdown-channel.d.ts.map +1 -0
  68. package/dist/leader-thread/shutdown-channel.js +7 -0
  69. package/dist/leader-thread/shutdown-channel.js.map +1 -0
  70. package/dist/leader-thread/types.d.ts +87 -0
  71. package/dist/leader-thread/types.d.ts.map +1 -0
  72. package/dist/leader-thread/types.js +11 -0
  73. package/dist/leader-thread/types.js.map +1 -0
  74. package/dist/mutation.d.ts +3 -4
  75. package/dist/mutation.d.ts.map +1 -1
  76. package/dist/mutation.js +0 -14
  77. package/dist/mutation.js.map +1 -1
  78. package/dist/otel.d.ts +7 -0
  79. package/dist/otel.d.ts.map +1 -0
  80. package/dist/otel.js +11 -0
  81. package/dist/otel.js.map +1 -0
  82. package/dist/query-builder/api.d.ts +2 -2
  83. package/dist/query-builder/api.d.ts.map +1 -1
  84. package/dist/query-builder/api.js.map +1 -1
  85. package/dist/query-builder/impl.d.ts +1 -1
  86. package/dist/query-builder/impl.d.ts.map +1 -1
  87. package/dist/query-builder/impl.js +21 -4
  88. package/dist/query-builder/impl.js.map +1 -1
  89. package/dist/query-builder/impl.test.js +24 -1
  90. package/dist/query-builder/impl.test.js.map +1 -1
  91. package/dist/query-info.d.ts +1 -1
  92. package/dist/query-info.d.ts.map +1 -1
  93. package/dist/rehydrate-from-mutationlog.d.ts +1 -1
  94. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  95. package/dist/rehydrate-from-mutationlog.js +6 -6
  96. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  97. package/dist/schema/EventId.d.ts +37 -0
  98. package/dist/schema/EventId.d.ts.map +1 -0
  99. package/dist/schema/EventId.js +30 -0
  100. package/dist/schema/EventId.js.map +1 -0
  101. package/dist/schema/MutationEvent.d.ts +191 -0
  102. package/dist/schema/MutationEvent.d.ts.map +1 -0
  103. package/dist/schema/MutationEvent.js +56 -0
  104. package/dist/schema/MutationEvent.js.map +1 -0
  105. package/dist/schema/mod.d.ts +8 -0
  106. package/dist/schema/mod.d.ts.map +1 -0
  107. package/dist/schema/mod.js +8 -0
  108. package/dist/schema/mod.js.map +1 -0
  109. package/dist/schema/mutations.d.ts +3 -123
  110. package/dist/schema/mutations.d.ts.map +1 -1
  111. package/dist/schema/mutations.js +0 -26
  112. package/dist/schema/mutations.js.map +1 -1
  113. package/dist/schema/{index.d.ts → schema.d.ts} +1 -5
  114. package/dist/schema/schema.d.ts.map +1 -0
  115. package/dist/schema/{index.js → schema.js} +1 -5
  116. package/dist/schema/schema.js.map +1 -0
  117. package/dist/schema/system-tables.d.ts +55 -29
  118. package/dist/schema/system-tables.d.ts.map +1 -1
  119. package/dist/schema/system-tables.js +10 -5
  120. package/dist/schema/system-tables.js.map +1 -1
  121. package/dist/schema-management/migrations.d.ts +1 -1
  122. package/dist/schema-management/migrations.d.ts.map +1 -1
  123. package/dist/schema-management/migrations.js +6 -1
  124. package/dist/schema-management/migrations.js.map +1 -1
  125. package/dist/schema-management/validate-mutation-defs.d.ts +1 -1
  126. package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
  127. package/dist/sync/client-session-sync-processor.d.ts +45 -0
  128. package/dist/sync/client-session-sync-processor.d.ts.map +1 -0
  129. package/dist/sync/client-session-sync-processor.js +131 -0
  130. package/dist/sync/client-session-sync-processor.js.map +1 -0
  131. package/dist/sync/index.d.ts +2 -0
  132. package/dist/sync/index.d.ts.map +1 -1
  133. package/dist/sync/index.js +2 -0
  134. package/dist/sync/index.js.map +1 -1
  135. package/dist/sync/next/compact-events.d.ts +1 -1
  136. package/dist/sync/next/compact-events.d.ts.map +1 -1
  137. package/dist/sync/next/compact-events.js +2 -1
  138. package/dist/sync/next/compact-events.js.map +1 -1
  139. package/dist/sync/next/facts.d.ts +5 -5
  140. package/dist/sync/next/facts.d.ts.map +1 -1
  141. package/dist/sync/next/facts.js +1 -1
  142. package/dist/sync/next/facts.js.map +1 -1
  143. package/dist/sync/next/history-dag-common.d.ts +30 -0
  144. package/dist/sync/next/history-dag-common.d.ts.map +1 -0
  145. package/dist/sync/next/history-dag-common.js +20 -0
  146. package/dist/sync/next/history-dag-common.js.map +1 -0
  147. package/dist/sync/next/history-dag.d.ts +4 -27
  148. package/dist/sync/next/history-dag.d.ts.map +1 -1
  149. package/dist/sync/next/history-dag.js +1 -19
  150. package/dist/sync/next/history-dag.js.map +1 -1
  151. package/dist/sync/next/mod.d.ts +1 -0
  152. package/dist/sync/next/mod.d.ts.map +1 -1
  153. package/dist/sync/next/mod.js +1 -0
  154. package/dist/sync/next/mod.js.map +1 -1
  155. package/dist/sync/next/rebase-events.d.ts +3 -2
  156. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  157. package/dist/sync/next/rebase-events.js.map +1 -1
  158. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -1
  159. package/dist/sync/next/test/compact-events.test.js +2 -1
  160. package/dist/sync/next/test/compact-events.test.js.map +1 -1
  161. package/dist/sync/next/test/mutation-fixtures.d.ts +1 -1
  162. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  163. package/dist/sync/next/test/mutation-fixtures.js +4 -3
  164. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  165. package/dist/sync/sync.d.ts +33 -12
  166. package/dist/sync/sync.d.ts.map +1 -1
  167. package/dist/sync/sync.js +10 -1
  168. package/dist/sync/sync.js.map +1 -1
  169. package/dist/sync/syncstate.d.ts +123 -0
  170. package/dist/sync/syncstate.d.ts.map +1 -0
  171. package/dist/sync/syncstate.js +248 -0
  172. package/dist/sync/syncstate.js.map +1 -0
  173. package/dist/sync/syncstate.test.d.ts +2 -0
  174. package/dist/sync/syncstate.test.d.ts.map +1 -0
  175. package/dist/sync/syncstate.test.js +399 -0
  176. package/dist/sync/syncstate.test.js.map +1 -0
  177. package/dist/sync/validate-push-payload.d.ts +5 -0
  178. package/dist/sync/validate-push-payload.d.ts.map +1 -0
  179. package/dist/sync/validate-push-payload.js +15 -0
  180. package/dist/sync/validate-push-payload.js.map +1 -0
  181. package/dist/util.d.ts +2 -2
  182. package/dist/util.d.ts.map +1 -1
  183. package/dist/version.d.ts +2 -2
  184. package/dist/version.d.ts.map +1 -1
  185. package/dist/version.js +2 -2
  186. package/package.json +13 -6
  187. package/src/__tests__/fixture.ts +5 -1
  188. package/src/adapter-types.ts +60 -34
  189. package/src/derived-mutations.test.ts +1 -1
  190. package/src/derived-mutations.ts +1 -1
  191. package/src/devtools/devtools-bridge.ts +2 -2
  192. package/src/devtools/devtools-messages.ts +70 -74
  193. package/src/devtools/index.ts +1 -2
  194. package/src/index.ts +2 -1
  195. package/src/init-singleton-tables.ts +1 -1
  196. package/src/leader-thread/apply-mutation.ts +143 -0
  197. package/src/leader-thread/connection.ts +67 -0
  198. package/src/leader-thread/leader-sync-processor.ts +666 -0
  199. package/src/leader-thread/leader-worker-devtools.ts +358 -0
  200. package/src/leader-thread/make-leader-thread-layer.ts +192 -0
  201. package/src/leader-thread/mod.ts +6 -0
  202. package/src/leader-thread/mutationlog.ts +42 -0
  203. package/src/leader-thread/pull-queue-set.ts +58 -0
  204. package/src/leader-thread/recreate-db.ts +109 -0
  205. package/src/leader-thread/shutdown-channel.ts +13 -0
  206. package/src/leader-thread/types.ts +129 -0
  207. package/src/mutation.ts +3 -21
  208. package/src/otel.ts +20 -0
  209. package/src/query-builder/api.ts +3 -2
  210. package/src/query-builder/impl.test.ts +28 -1
  211. package/src/query-builder/impl.ts +21 -5
  212. package/src/query-info.ts +1 -1
  213. package/src/rehydrate-from-mutationlog.ts +7 -11
  214. package/src/schema/EventId.ts +46 -0
  215. package/src/schema/MutationEvent.ts +161 -0
  216. package/src/schema/mod.ts +7 -0
  217. package/src/schema/mutations.ts +5 -126
  218. package/src/schema/{index.ts → schema.ts} +0 -5
  219. package/src/schema/system-tables.ts +18 -5
  220. package/src/schema-management/migrations.ts +9 -2
  221. package/src/schema-management/validate-mutation-defs.ts +1 -1
  222. package/src/sync/client-session-sync-processor.ts +207 -0
  223. package/src/sync/index.ts +2 -0
  224. package/src/sync/next/compact-events.ts +3 -2
  225. package/src/sync/next/facts.ts +11 -5
  226. package/src/sync/next/history-dag-common.ts +44 -0
  227. package/src/sync/next/history-dag.ts +3 -45
  228. package/src/sync/next/mod.ts +1 -0
  229. package/src/sync/next/rebase-events.ts +6 -5
  230. package/src/sync/next/test/compact-events.test.ts +3 -2
  231. package/src/sync/next/test/mutation-fixtures.ts +7 -6
  232. package/src/sync/sync.ts +32 -12
  233. package/src/sync/syncstate.test.ts +464 -0
  234. package/src/sync/syncstate.ts +385 -0
  235. package/src/sync/validate-push-payload.ts +18 -0
  236. package/src/version.ts +2 -2
  237. package/dist/schema/index.d.ts.map +0 -1
  238. package/dist/schema/index.js.map +0 -1
  239. package/dist/sync/next-mutation-event-id-pair.d.ts +0 -14
  240. package/dist/sync/next-mutation-event-id-pair.d.ts.map +0 -1
  241. package/dist/sync/next-mutation-event-id-pair.js +0 -13
  242. package/dist/sync/next-mutation-event-id-pair.js.map +0 -1
  243. package/src/sync/next-mutation-event-id-pair.ts +0 -20
@@ -0,0 +1,161 @@
1
+ import { memoizeByRef } from '@livestore/utils'
2
+ import type { Deferred } from '@livestore/utils/effect'
3
+ import { Schema } from '@livestore/utils/effect'
4
+
5
+ import * as EventId from './EventId.js'
6
+ import type { MutationDef, MutationDefRecord } from './mutations.js'
7
+ import type { LiveStoreSchema } from './schema.js'
8
+
9
+ export type MutationEventPartial<TMutationsDef extends MutationDef.Any> = {
10
+ mutation: TMutationsDef['name']
11
+ args: Schema.Schema.Type<TMutationsDef['schema']>
12
+ }
13
+
14
+ export type MutationEventPartialEncoded<TMutationsDef extends MutationDef.Any> = {
15
+ mutation: TMutationsDef['name']
16
+ args: Schema.Schema.Encoded<TMutationsDef['schema']>
17
+ }
18
+
19
+ export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
20
+ mutation: TMutationsDef['name']
21
+ args: Schema.Schema.Type<TMutationsDef['schema']>
22
+ id: EventId.EventId
23
+ parentId: EventId.EventId
24
+ }
25
+
26
+ export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
27
+ mutation: TMutationsDef['name']
28
+ args: Schema.Schema.Encoded<TMutationsDef['schema']>
29
+ id: EventId.EventId
30
+ parentId: EventId.EventId
31
+ }
32
+
33
+ export type Any = MutationEvent<MutationDef.Any>
34
+ export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
35
+
36
+ export type PartialAny = MutationEventPartial<MutationDef.Any>
37
+ export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
38
+
39
+ export type PartialForSchema<TSchema extends LiveStoreSchema> = {
40
+ [K in keyof TSchema['_MutationDefMapType']]: MutationEventPartial<TSchema['_MutationDefMapType'][K]>
41
+ }[keyof TSchema['_MutationDefMapType']]
42
+
43
+ export type ForSchema<TSchema extends LiveStoreSchema> = {
44
+ [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
45
+ }[keyof TSchema['_MutationDefMapType']]
46
+
47
+ export const isPartialMutationEvent = (mutationEvent: Any | PartialAny): mutationEvent is PartialAny =>
48
+ 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
49
+
50
+ export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
51
+ {
52
+ [K in keyof TMutationsDefRecord]: {
53
+ mutation: K
54
+ args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
55
+ id: EventId.EventId
56
+ parentId: EventId.EventId
57
+ }
58
+ }[keyof TMutationsDefRecord],
59
+ {
60
+ [K in keyof TMutationsDefRecord]: {
61
+ mutation: K
62
+ args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
63
+ id: EventId.EventId
64
+ parentId: EventId.EventId
65
+ }
66
+ }[keyof TMutationsDefRecord]
67
+ >
68
+
69
+ export type MutationEventPartialSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
70
+ {
71
+ [K in keyof TMutationsDefRecord]: {
72
+ mutation: K
73
+ args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
74
+ }
75
+ }[keyof TMutationsDefRecord],
76
+ {
77
+ [K in keyof TMutationsDefRecord]: {
78
+ mutation: K
79
+ args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
80
+ }
81
+ }[keyof TMutationsDefRecord]
82
+ >
83
+
84
+ export const makeMutationEventSchema = <TSchema extends LiveStoreSchema>(
85
+ schema: TSchema,
86
+ ): ForMutationDefRecord<TSchema['_MutationDefMapType']> =>
87
+ Schema.Union(
88
+ ...[...schema.mutations.values()].map((def) =>
89
+ Schema.Struct({
90
+ mutation: Schema.Literal(def.name),
91
+ args: def.schema,
92
+ id: EventId.EventId,
93
+ parentId: EventId.EventId,
94
+ }),
95
+ ),
96
+ ).annotations({ title: 'MutationEvent' }) as any
97
+
98
+ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
99
+ schema: TSchema,
100
+ ): MutationEventPartialSchema<TSchema['_MutationDefMapType']> =>
101
+ Schema.Union(
102
+ ...[...schema.mutations.values()].map((def) =>
103
+ Schema.Struct({
104
+ mutation: Schema.Literal(def.name),
105
+ args: def.schema,
106
+ }),
107
+ ),
108
+ ).annotations({ title: 'MutationEventSchemaPartial' }) as any
109
+
110
+ export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
111
+
112
+ export const Any = Schema.Struct({
113
+ mutation: Schema.String,
114
+ args: Schema.Any,
115
+ id: EventId.EventId,
116
+ parentId: EventId.EventId,
117
+ }).annotations({ title: 'MutationEvent.Any' })
118
+
119
+ export const DecodedAny = Schema.typeSchema(Any).annotations({
120
+ title: 'MutationEvent.DecodedAny',
121
+ })
122
+
123
+ export const EncodedAny = Schema.encodedSchema(Any).annotations({
124
+ title: 'MutationEvent.EncodedAny',
125
+ })
126
+
127
+ /** Equivalent to EncodedAny but with a meta field and some convenience methods */
128
+ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEvent.EncodedWithMeta')({
129
+ mutation: Schema.String,
130
+ args: Schema.Any,
131
+ id: EventId.EventId,
132
+ parentId: EventId.EventId,
133
+ meta: Schema.optionalWith(
134
+ Schema.Any as Schema.Schema<{ deferred?: Deferred.Deferred<void>; sessionChangeset?: Uint8Array }>,
135
+ { default: () => ({}) },
136
+ ),
137
+ }) {
138
+ toJSON = (): any => {
139
+ // Only used for logging/debugging
140
+ // - More readable way to print the id + parentId
141
+ // - not including `meta`
142
+ return {
143
+ id: `(${this.id.global},${this.id.local}) → (${this.parentId.global},${this.parentId.local})`,
144
+ mutation: this.mutation,
145
+ args: this.args,
146
+ }
147
+ }
148
+
149
+ rebase = (parentId: EventId.EventId, isLocal: boolean) =>
150
+ new EncodedWithMeta({
151
+ ...this,
152
+ ...EventId.nextPair(this.id, isLocal),
153
+ })
154
+ }
155
+
156
+ export const isEqualEncoded = (a: AnyEncoded, b: AnyEncoded) =>
157
+ a.id.global === b.id.global &&
158
+ a.id.local === b.id.local &&
159
+ a.mutation === b.mutation &&
160
+ // TODO use schema equality here
161
+ JSON.stringify(a.args) === JSON.stringify(b.args)
@@ -0,0 +1,7 @@
1
+ export * from './system-tables.js'
2
+ export * as DbSchema from './table-def.js'
3
+ export * from './mutations.js'
4
+ export * from './schema-helpers.js'
5
+ export * from './schema.js'
6
+ export * as MutationEvent from './MutationEvent.js'
7
+ export * as EventId from './EventId.js'
@@ -1,9 +1,7 @@
1
- import { memoizeByRef } from '@livestore/utils'
2
1
  import { Schema } from '@livestore/utils/effect'
3
2
 
4
- import { EventId } from '../adapter-types.js'
5
3
  import type { BindValues } from '../sql-queries/sql-queries.js'
6
- import type { LiveStoreSchema } from './index.js'
4
+ import type * as EventId from './EventId.js'
7
5
 
8
6
  export type MutationDefMap = Map<string | 'livestore.RawSql', MutationDef.Any>
9
7
  export type MutationDefRecord = {
@@ -57,7 +55,7 @@ export type MutationDef<TName extends string, TFrom, TTo> = {
57
55
  mutation: TName
58
56
  args: TTo
59
57
  // TODO remove/clean up after sync-next is fully implemented
60
- id?: EventId
58
+ id?: EventId.EventId
61
59
  }
62
60
  }
63
61
 
@@ -102,6 +100,8 @@ export const defineFacts = <
102
100
  ): TRecord => record
103
101
 
104
102
  export type DefineMutationOptions<TTo> = {
103
+ // TODO actually implement this
104
+ onError?: (error: any) => void
105
105
  historyId?: string
106
106
  /** Warning: This feature is not fully implemented yet */
107
107
  facts?: (
@@ -133,7 +133,7 @@ export const defineMutation = <TName extends string, TFrom, TTo>(
133
133
  const makePartialEvent = (
134
134
  args: TTo,
135
135
  options?: {
136
- id?: EventId
136
+ id?: EventId.EventId
137
137
  },
138
138
  ) => ({ mutation: name, args, ...options })
139
139
 
@@ -190,124 +190,3 @@ export const rawSqlMutation = defineMutation(
190
190
 
191
191
  export type RawSqlMutation = typeof rawSqlMutation
192
192
  export type RawSqlMutationEvent = ReturnType<typeof rawSqlMutation>
193
-
194
- export type MutationEventPartial<TMutationsDef extends MutationDef.Any> = {
195
- mutation: TMutationsDef['name']
196
- args: Schema.Schema.Type<TMutationsDef['schema']>
197
- }
198
-
199
- export type MutationEventPartialEncoded<TMutationsDef extends MutationDef.Any> = {
200
- mutation: TMutationsDef['name']
201
- args: Schema.Schema.Encoded<TMutationsDef['schema']>
202
- }
203
-
204
- export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
205
- mutation: TMutationsDef['name']
206
- args: Schema.Schema.Type<TMutationsDef['schema']>
207
- id: EventId
208
- parentId: EventId
209
- }
210
-
211
- export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
212
- mutation: TMutationsDef['name']
213
- args: Schema.Schema.Encoded<TMutationsDef['schema']>
214
- id: EventId
215
- parentId: EventId
216
- }
217
-
218
- export namespace MutationEvent {
219
- export type Any = MutationEvent<MutationDef.Any>
220
- export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
221
-
222
- export type PartialAny = MutationEventPartial<MutationDef.Any>
223
- export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
224
-
225
- export type PartialForSchema<TSchema extends LiveStoreSchema> = {
226
- [K in keyof TSchema['_MutationDefMapType']]: MutationEventPartial<TSchema['_MutationDefMapType'][K]>
227
- }[keyof TSchema['_MutationDefMapType']]
228
-
229
- export type ForSchema<TSchema extends LiveStoreSchema> = {
230
- [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
231
- }[keyof TSchema['_MutationDefMapType']]
232
- }
233
-
234
- export const isPartialMutationEvent = (
235
- mutationEvent: MutationEvent.Any | MutationEvent.PartialAny,
236
- ): mutationEvent is MutationEvent.PartialAny => 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
237
-
238
- export type MutationEventSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
239
- {
240
- [K in keyof TMutationsDefRecord]: {
241
- mutation: K
242
- args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
243
- id: EventId
244
- parentId: EventId
245
- }
246
- }[keyof TMutationsDefRecord],
247
- {
248
- [K in keyof TMutationsDefRecord]: {
249
- mutation: K
250
- args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
251
- id: EventId
252
- parentId: EventId
253
- }
254
- }[keyof TMutationsDefRecord]
255
- >
256
-
257
- export type MutationEventPartialSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
258
- {
259
- [K in keyof TMutationsDefRecord]: {
260
- mutation: K
261
- args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
262
- }
263
- }[keyof TMutationsDefRecord],
264
- {
265
- [K in keyof TMutationsDefRecord]: {
266
- mutation: K
267
- args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
268
- }
269
- }[keyof TMutationsDefRecord]
270
- >
271
-
272
- export const makeMutationEventSchema = <TSchema extends LiveStoreSchema>(
273
- schema: TSchema,
274
- ): MutationEventSchema<TSchema['_MutationDefMapType']> =>
275
- Schema.Union(
276
- ...[...schema.mutations.values()].map((def) =>
277
- Schema.Struct({
278
- mutation: Schema.Literal(def.name),
279
- args: def.schema,
280
- id: EventId,
281
- parentId: EventId,
282
- }),
283
- ),
284
- ).annotations({ title: 'MutationEventSchema' }) as any
285
-
286
- export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
287
- schema: TSchema,
288
- ): MutationEventPartialSchema<TSchema['_MutationDefMapType']> =>
289
- Schema.Union(
290
- ...[...schema.mutations.values()].map((def) =>
291
- Schema.Struct({
292
- mutation: Schema.Literal(def.name),
293
- args: def.schema,
294
- }),
295
- ),
296
- ).annotations({ title: 'MutationEventSchemaPartial' }) as any
297
-
298
- export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
299
-
300
- export const mutationEventSchemaAny = Schema.Struct({
301
- mutation: Schema.String,
302
- args: Schema.Any,
303
- id: EventId,
304
- parentId: EventId,
305
- }).annotations({ title: 'MutationEventSchema.Any' })
306
-
307
- export const mutationEventSchemaDecodedAny = Schema.typeSchema(mutationEventSchemaAny).annotations({
308
- title: 'MutationEventSchema.DecodedAny',
309
- })
310
-
311
- export const mutationEventSchemaEncodedAny = Schema.encodedSchema(mutationEventSchemaAny).annotations({
312
- title: 'MutationEventSchema.EncodedAny',
313
- })
@@ -15,11 +15,6 @@ import { systemTables } from './system-tables.js'
15
15
  import type { TableDef, TableDefBase } from './table-def.js'
16
16
  import { tableHasDerivedMutations } from './table-def.js'
17
17
 
18
- export * from './system-tables.js'
19
- export * as DbSchema from './table-def.js'
20
- export * from './mutations.js'
21
- export * from './schema-helpers.js'
22
-
23
18
  export const LiveStoreSchemaSymbol = Symbol.for('livestore.LiveStoreSchema')
24
19
  export type LiveStoreSchemaSymbol = typeof LiveStoreSchemaSymbol
25
20
 
@@ -45,9 +45,13 @@ export const SESSION_CHANGESET_META_TABLE = '__livestore_session_changeset'
45
45
  export const sessionChangesetMetaTable = table(
46
46
  SESSION_CHANGESET_META_TABLE,
47
47
  {
48
- idGlobal: SqliteDsl.integer({ primaryKey: true }),
49
- idLocal: SqliteDsl.integer({ primaryKey: true }),
48
+ // TODO bring back primary key
49
+ idGlobal: SqliteDsl.integer({}),
50
+ idLocal: SqliteDsl.integer({}),
51
+ // idGlobal: SqliteDsl.integer({ primaryKey: true }),
52
+ // idLocal: SqliteDsl.integer({ primaryKey: true }),
50
53
  changeset: SqliteDsl.blob({}),
54
+ debug: SqliteDsl.json({ nullable: true }),
51
55
  },
52
56
  { disableAutomaticIdColumn: true },
53
57
  )
@@ -73,12 +77,21 @@ export const mutationLogMetaTable = table(
73
77
  mutation: SqliteDsl.text({}),
74
78
  argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
75
79
  schemaHash: SqliteDsl.integer({}),
76
- /** ISO date format */
77
- createdAt: SqliteDsl.text({}),
78
- syncStatus: SqliteDsl.text({ schema: SyncStatus }),
79
80
  syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
80
81
  },
81
82
  { disableAutomaticIdColumn: true, indexes: [] },
82
83
  )
83
84
 
84
85
  export type MutationLogMetaRow = FromTable.RowDecoded<typeof mutationLogMetaTable>
86
+
87
+ export const SYNC_STATUS_TABLE = '__livestore_sync_status'
88
+
89
+ export const syncStatusTable = table(
90
+ SYNC_STATUS_TABLE,
91
+ {
92
+ head: SqliteDsl.integer({ primaryKey: true }),
93
+ },
94
+ { disableAutomaticIdColumn: true },
95
+ )
96
+
97
+ export type SyncStatusRow = FromTable.RowDecoded<typeof syncStatusTable>
@@ -3,7 +3,7 @@ import { memoizeByStringifyArgs } from '@livestore/utils'
3
3
  import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
4
4
 
5
5
  import type { SynchronousDatabase } from '../adapter-types.js'
6
- import type { LiveStoreSchema } from '../schema/index.js'
6
+ import type { LiveStoreSchema } from '../schema/mod.js'
7
7
  import type { SchemaMetaRow, SchemaMutationsMetaRow } from '../schema/system-tables.js'
8
8
  import {
9
9
  SCHEMA_META_TABLE,
@@ -151,7 +151,14 @@ export const migrateTable = ({
151
151
  { tableName, schemaHash, updatedAt },
152
152
  )
153
153
  }
154
- }).pipe(Effect.withSpan('@livestore/common:migrateTable', { attributes: { tableName: tableAst.name } }))
154
+ }).pipe(
155
+ Effect.withSpan('@livestore/common:migrateTable', {
156
+ attributes: {
157
+ 'span.label': tableAst.name,
158
+ tableName: tableAst.name,
159
+ },
160
+ }),
161
+ )
155
162
 
156
163
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
157
164
  const uniqueStr = index.unique ? 'UNIQUE' : ''
@@ -1,7 +1,7 @@
1
1
  import { Effect, Schema } from '@livestore/utils/effect'
2
2
 
3
3
  import { UnexpectedError } from '../adapter-types.js'
4
- import type { LiveStoreSchema } from '../schema/index.js'
4
+ import type { LiveStoreSchema } from '../schema/mod.js'
5
5
  import type { MutationDef } from '../schema/mutations.js'
6
6
  import type { MutationDefInfo, SchemaManager } from './common.js'
7
7
 
@@ -0,0 +1,207 @@
1
+ import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
2
+ import type { Scope } from '@livestore/utils/effect'
3
+ import { Effect, Schema, Stream } from '@livestore/utils/effect'
4
+ import * as otel from '@opentelemetry/api'
5
+
6
+ import type { Coordinator, UnexpectedError } from '../adapter-types.js'
7
+ import * as EventId from '../schema/EventId.js'
8
+ import { type LiveStoreSchema } from '../schema/mod.js'
9
+ import * as MutationEvent from '../schema/MutationEvent.js'
10
+ import type { SyncState } from './syncstate.js'
11
+ import { updateSyncState } from './syncstate.js'
12
+
13
+ /**
14
+ * Rebase behaviour:
15
+ * - We continously pull mutations from the leader and apply them to the local store.
16
+ * - If there was a race condition (i.e. the leader and client session have both advacned),
17
+ * we'll need to rebase the local pending mutations on top of the leader's head.
18
+ * - The goal is to never block the UI, so we'll interrupt rebasing if a new mutations is pushed by the client session.
19
+ * - We also want to avoid "backwards-jumping" in the UI, so we'll transactionally apply a read model changes during a rebase.
20
+ * - We might need to make the rebase behaviour configurable e.g. to let users manually trigger a rebase
21
+ */
22
+ export const makeClientSessionSyncProcessor = ({
23
+ schema,
24
+ initialLeaderHead,
25
+ pushToLeader,
26
+ pullFromLeader,
27
+ applyMutation,
28
+ rollback,
29
+ refreshTables,
30
+ span,
31
+ }: {
32
+ schema: LiveStoreSchema
33
+ initialLeaderHead: EventId.EventId
34
+ pushToLeader: (batch: ReadonlyArray<MutationEvent.AnyEncoded>) => void
35
+ pullFromLeader: Coordinator['mutations']['pull']
36
+ applyMutation: (
37
+ mutationEventDecoded: MutationEvent.PartialAny,
38
+ options: { otelContext: otel.Context; withChangeset: boolean },
39
+ ) => {
40
+ writeTables: Set<string>
41
+ sessionChangeset: Uint8Array | undefined
42
+ }
43
+ rollback: (changeset: Uint8Array) => void
44
+ refreshTables: (tables: Set<string>) => void
45
+ // rebaseBehaviour: 'auto-rebase' | 'manual-rebase'
46
+ span: otel.Span
47
+ }): ClientSessionSyncProcessor => {
48
+ const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
49
+
50
+ const syncStateRef = {
51
+ current: {
52
+ localHead: initialLeaderHead,
53
+ upstreamHead: initialLeaderHead,
54
+ pending: [],
55
+ // TODO init rollbackTail from leader to be ready for backend rebasing
56
+ rollbackTail: [],
57
+ } as SyncState,
58
+ }
59
+
60
+ const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
61
+ const mutationDef = schema.mutations.get(mutationEventEncoded.mutation)!
62
+ return mutationDef.options.localOnly
63
+ }
64
+
65
+ const push: ClientSessionSyncProcessor['push'] = (batch, { otelContext }) => {
66
+ // TODO validate batch
67
+
68
+ let baseEventId = syncStateRef.current.localHead
69
+ const encodedMutationEvents = batch.map((mutationEvent) => {
70
+ const mutationDef = schema.mutations.get(mutationEvent.mutation)!
71
+ const nextIdPair = EventId.nextPair(baseEventId, mutationDef.options.localOnly)
72
+ baseEventId = nextIdPair.id
73
+ return new MutationEvent.EncodedWithMeta(
74
+ Schema.encodeUnknownSync(mutationEventSchema)({ ...mutationEvent, ...nextIdPair }),
75
+ )
76
+ })
77
+
78
+ const updateResult = updateSyncState({
79
+ syncState: syncStateRef.current,
80
+ payload: { _tag: 'local-push', newEvents: encodedMutationEvents },
81
+ isLocalEvent,
82
+ isEqualEvent: MutationEvent.isEqualEncoded,
83
+ })
84
+
85
+ span.addEvent('local-push', {
86
+ batchSize: encodedMutationEvents.length,
87
+ updateResult: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
88
+ })
89
+
90
+ if (updateResult._tag !== 'advance') {
91
+ return shouldNeverHappen(`Expected advance, got ${updateResult._tag}`)
92
+ }
93
+
94
+ syncStateRef.current = updateResult.newSyncState
95
+
96
+ const writeTables = new Set<string>()
97
+ for (const mutationEvent of updateResult.newEvents) {
98
+ // TODO avoid encoding and decoding here again
99
+ const decodedMutationEvent = Schema.decodeSync(mutationEventSchema)(mutationEvent)
100
+ const res = applyMutation(decodedMutationEvent, { otelContext, withChangeset: true })
101
+ for (const table of res.writeTables) {
102
+ writeTables.add(table)
103
+ }
104
+ mutationEvent.meta.sessionChangeset = res.sessionChangeset
105
+ }
106
+
107
+ pushToLeader(encodedMutationEvents)
108
+
109
+ return { writeTables }
110
+ }
111
+
112
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
113
+
114
+ const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
115
+ yield* pullFromLeader.pipe(
116
+ Stream.tap(({ payload, remaining }) =>
117
+ Effect.gen(function* () {
118
+ // console.log('pulled payload from leader', { payload, remaining })
119
+
120
+ const updateResult = updateSyncState({
121
+ syncState: syncStateRef.current,
122
+ payload,
123
+ isLocalEvent,
124
+ isEqualEvent: MutationEvent.isEqualEncoded,
125
+ })
126
+
127
+ if (updateResult._tag === 'reject') {
128
+ debugger
129
+ throw new Error('TODO: implement reject in client-session-sync-queue for pull')
130
+ }
131
+
132
+ syncStateRef.current = updateResult.newSyncState
133
+
134
+ if (updateResult._tag === 'rebase') {
135
+ span.addEvent('pull:rebase', {
136
+ payloadTag: payload._tag,
137
+ payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
138
+ newEventsCount: updateResult.newEvents.length,
139
+ rollbackCount: updateResult.eventsToRollback.length,
140
+ res: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
141
+ remaining,
142
+ })
143
+ if (LS_DEV) {
144
+ console.debug(
145
+ 'pull:rebase: rollback',
146
+ updateResult.eventsToRollback.length,
147
+ ...updateResult.eventsToRollback.map((_) => _.toJSON()),
148
+ )
149
+ }
150
+
151
+ for (let i = updateResult.eventsToRollback.length - 1; i >= 0; i--) {
152
+ const event = updateResult.eventsToRollback[i]!
153
+ if (event.meta.sessionChangeset) {
154
+ rollback(event.meta.sessionChangeset)
155
+ event.meta.sessionChangeset = undefined
156
+ }
157
+ }
158
+ } else {
159
+ span.addEvent('pull:advance', {
160
+ payloadTag: payload._tag,
161
+ payload: TRACE_VERBOSE ? JSON.stringify(payload) : undefined,
162
+ newEventsCount: updateResult.newEvents.length,
163
+ res: TRACE_VERBOSE ? JSON.stringify(updateResult) : undefined,
164
+ remaining,
165
+ })
166
+ }
167
+
168
+ if (updateResult.newEvents.length === 0) return
169
+
170
+ const writeTables = new Set<string>()
171
+ for (const mutationEvent of updateResult.newEvents) {
172
+ const decodedMutationEvent = Schema.decodeSync(mutationEventSchema)(mutationEvent)
173
+ const res = applyMutation(decodedMutationEvent, { otelContext, withChangeset: true })
174
+ for (const table of res.writeTables) {
175
+ writeTables.add(table)
176
+ }
177
+
178
+ mutationEvent.meta.sessionChangeset = res.sessionChangeset
179
+ }
180
+
181
+ refreshTables(writeTables)
182
+ }),
183
+ ),
184
+ Stream.runDrain,
185
+ Effect.tapCauseLogPretty,
186
+ Effect.forkScoped,
187
+ )
188
+ })
189
+
190
+ return {
191
+ push,
192
+ boot,
193
+ syncStateRef,
194
+ } satisfies ClientSessionSyncProcessor
195
+ }
196
+
197
+ export interface ClientSessionSyncProcessor {
198
+ push: (
199
+ batch: ReadonlyArray<MutationEvent.PartialAny>,
200
+ options: { otelContext: otel.Context },
201
+ ) => {
202
+ writeTables: Set<string>
203
+ }
204
+ boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
205
+
206
+ syncStateRef: { current: SyncState }
207
+ }
package/src/sync/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export * from './sync.js'
2
+ export * from './validate-push-payload.js'
3
+ export * from './client-session-sync-processor.js'
@@ -1,7 +1,8 @@
1
1
  import { replacesFacts } from './facts.js'
2
2
  import { graphologyDag } from './graphology_.js'
3
- import type { HistoryDag } from './history-dag.js'
4
- import { emptyHistoryDag, eventIdToString } from './history-dag.js'
3
+ import { eventIdToString } from './history-dag.js'
4
+ import type { HistoryDag } from './history-dag-common.js'
5
+ import { emptyHistoryDag } from './history-dag-common.js'
5
6
 
6
7
  /**
7
8
  * Idea:
@@ -1,6 +1,6 @@
1
1
  import { notYetImplemented } from '@livestore/utils'
2
2
 
3
- import type { EventId } from '../../adapter-types.js'
3
+ import type * as EventId from '../../schema/EventId.js'
4
4
  import type {
5
5
  FactsCallback,
6
6
  MutationEventFactInput,
@@ -9,9 +9,12 @@ import type {
9
9
  MutationEventFactsSnapshot,
10
10
  } from '../../schema/mutations.js'
11
11
  import { graphologyDag } from './graphology_.js'
12
- import { EMPTY_FACT_VALUE, type HistoryDag, type HistoryDagNode } from './history-dag.js'
12
+ import { EMPTY_FACT_VALUE, type HistoryDag, type HistoryDagNode } from './history-dag-common.js'
13
13
 
14
- export const factsSnapshotForEvents = (events: HistoryDagNode[], endEventId: EventId): MutationEventFactsSnapshot => {
14
+ export const factsSnapshotForEvents = (
15
+ events: HistoryDagNode[],
16
+ endEventId: EventId.EventId,
17
+ ): MutationEventFactsSnapshot => {
15
18
  const facts = new Map<string, any>()
16
19
 
17
20
  for (const event of events) {
@@ -25,7 +28,10 @@ export const factsSnapshotForEvents = (events: HistoryDagNode[], endEventId: Eve
25
28
  return facts
26
29
  }
27
30
 
28
- export const factsSnapshotForDag = (dag: HistoryDag, endEventId: EventId | undefined): MutationEventFactsSnapshot => {
31
+ export const factsSnapshotForDag = (
32
+ dag: HistoryDag,
33
+ endEventId: EventId.EventId | undefined,
34
+ ): MutationEventFactsSnapshot => {
29
35
  const facts = new Map<string, any>()
30
36
 
31
37
  const orderedEventIdStrs = graphologyDag.topologicalSort(dag)
@@ -221,7 +227,7 @@ export const getFactsGroupForMutationArgs = ({
221
227
  return facts
222
228
  }
223
229
 
224
- export const compareEventIds = (a: EventId, b: EventId) => {
230
+ export const compareEventIds = (a: EventId.EventId, b: EventId.EventId) => {
225
231
  if (a.global !== b.global) {
226
232
  return a.global - b.global
227
233
  }