@livestore/common 0.0.58-dev.6 → 0.0.58-dev.7

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 (113) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +29 -3
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js +5 -0
  5. package/dist/adapter-types.js.map +1 -1
  6. package/dist/debug-info.d.ts +8 -8
  7. package/dist/derived-mutations.d.ts +4 -4
  8. package/dist/derived-mutations.d.ts.map +1 -1
  9. package/dist/derived-mutations.test.js +1 -0
  10. package/dist/derived-mutations.test.js.map +1 -1
  11. package/dist/devtools/devtools-bridge.d.ts +1 -1
  12. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  13. package/dist/devtools/devtools-messages.d.ts +99 -21
  14. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  15. package/dist/devtools/devtools-messages.js +13 -4
  16. package/dist/devtools/devtools-messages.js.map +1 -1
  17. package/dist/devtools/index.d.ts +1 -0
  18. package/dist/devtools/index.d.ts.map +1 -1
  19. package/dist/devtools/index.js +2 -0
  20. package/dist/devtools/index.js.map +1 -1
  21. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  22. package/dist/rehydrate-from-mutationlog.js +11 -5
  23. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  24. package/dist/schema/mutations.d.ts +137 -18
  25. package/dist/schema/mutations.d.ts.map +1 -1
  26. package/dist/schema/mutations.js +41 -16
  27. package/dist/schema/mutations.js.map +1 -1
  28. package/dist/schema/system-tables.d.ts +118 -5
  29. package/dist/schema/system-tables.d.ts.map +1 -1
  30. package/dist/schema/system-tables.js +22 -8
  31. package/dist/schema/system-tables.js.map +1 -1
  32. package/dist/schema-management/migrations.js +1 -1
  33. package/dist/schema-management/migrations.js.map +1 -1
  34. package/dist/sync/next/compact-events.d.ts +15 -0
  35. package/dist/sync/next/compact-events.d.ts.map +1 -0
  36. package/dist/sync/next/compact-events.js +176 -0
  37. package/dist/sync/next/compact-events.js.map +1 -0
  38. package/dist/sync/next/facts.d.ts +37 -0
  39. package/dist/sync/next/facts.d.ts.map +1 -0
  40. package/dist/sync/next/facts.js +155 -0
  41. package/dist/sync/next/facts.js.map +1 -0
  42. package/dist/sync/next/graphology.d.ts +8 -0
  43. package/dist/sync/next/graphology.d.ts.map +1 -0
  44. package/dist/sync/next/graphology.js +36 -0
  45. package/dist/sync/next/graphology.js.map +1 -0
  46. package/dist/sync/next/graphology_.d.ts +3 -0
  47. package/dist/sync/next/graphology_.d.ts.map +1 -0
  48. package/dist/sync/next/graphology_.js +3 -0
  49. package/dist/sync/next/graphology_.js.map +1 -0
  50. package/dist/sync/next/history-dag.d.ts +30 -0
  51. package/dist/sync/next/history-dag.d.ts.map +1 -0
  52. package/dist/sync/next/history-dag.js +69 -0
  53. package/dist/sync/next/history-dag.js.map +1 -0
  54. package/dist/sync/next/mod.d.ts +5 -0
  55. package/dist/sync/next/mod.d.ts.map +1 -0
  56. package/dist/sync/next/mod.js +5 -0
  57. package/dist/sync/next/mod.js.map +1 -0
  58. package/dist/sync/next/mutation-fixtures.d.ts +73 -0
  59. package/dist/sync/next/mutation-fixtures.d.ts.map +1 -0
  60. package/dist/sync/next/mutation-fixtures.js +160 -0
  61. package/dist/sync/next/mutation-fixtures.js.map +1 -0
  62. package/dist/sync/next/rebase-events.d.ts +27 -0
  63. package/dist/sync/next/rebase-events.d.ts.map +1 -0
  64. package/dist/sync/next/rebase-events.js +41 -0
  65. package/dist/sync/next/rebase-events.js.map +1 -0
  66. package/dist/sync/next/test/compact-events.calculator.test.d.ts +2 -0
  67. package/dist/sync/next/test/compact-events.calculator.test.d.ts.map +1 -0
  68. package/dist/sync/next/test/compact-events.calculator.test.js +101 -0
  69. package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -0
  70. package/dist/sync/next/test/compact-events.test.d.ts +2 -0
  71. package/dist/sync/next/test/compact-events.test.d.ts.map +1 -0
  72. package/dist/sync/next/test/compact-events.test.js +201 -0
  73. package/dist/sync/next/test/compact-events.test.js.map +1 -0
  74. package/dist/sync/next/test/mod.d.ts +2 -0
  75. package/dist/sync/next/test/mod.d.ts.map +1 -0
  76. package/dist/sync/next/test/mod.js +2 -0
  77. package/dist/sync/next/test/mod.js.map +1 -0
  78. package/dist/sync/next/test/mutation-fixtures.d.ts +73 -0
  79. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -0
  80. package/dist/sync/next/test/mutation-fixtures.js +161 -0
  81. package/dist/sync/next/test/mutation-fixtures.js.map +1 -0
  82. package/dist/sync/sync.d.ts +5 -4
  83. package/dist/sync/sync.d.ts.map +1 -1
  84. package/dist/sync/sync.js.map +1 -1
  85. package/package.json +21 -4
  86. package/src/adapter-types.ts +27 -3
  87. package/src/derived-mutations.test.ts +2 -1
  88. package/src/derived-mutations.ts +5 -5
  89. package/src/devtools/devtools-bridge.ts +1 -1
  90. package/src/devtools/devtools-messages.ts +12 -2
  91. package/src/devtools/index.ts +2 -0
  92. package/src/rehydrate-from-mutationlog.ts +16 -7
  93. package/src/schema/mutations.ts +171 -30
  94. package/src/schema/system-tables.ts +30 -8
  95. package/src/schema-management/migrations.ts +1 -1
  96. package/src/sync/next/ambient.d.ts +3 -0
  97. package/src/sync/next/compact-events.ts +219 -0
  98. package/src/sync/next/facts.ts +228 -0
  99. package/src/sync/next/graphology.ts +49 -0
  100. package/src/sync/next/graphology_.ts +2 -0
  101. package/src/sync/next/history-dag.ts +109 -0
  102. package/src/sync/next/mod.ts +4 -0
  103. package/src/sync/next/rebase-events.ts +97 -0
  104. package/src/sync/next/test/compact-events.calculator.test.ts +121 -0
  105. package/src/sync/next/test/compact-events.test.ts +232 -0
  106. package/src/sync/next/test/mod.ts +1 -0
  107. package/src/sync/next/test/mutation-fixtures.ts +230 -0
  108. package/src/sync/sync.ts +10 -4
  109. package/dist/global-types.d.ts +0 -7
  110. package/dist/global-types.d.ts.map +0 -1
  111. package/dist/global-types.js +0 -2
  112. package/dist/global-types.js.map +0 -1
  113. package/src/global-types.d.ts +0 -5
@@ -9,6 +9,7 @@ export interface PreparedStatement {
9
9
  execute(bindValues: PreparedBindValues | undefined, options?: { onRowsChanged?: (rowsChanged: number) => void }): void
10
10
  select<T>(bindValues: PreparedBindValues | undefined): ReadonlyArray<T>
11
11
  finalize(): void
12
+ sql: string
12
13
  }
13
14
 
14
15
  export type StoreAdapter = {
@@ -28,6 +29,7 @@ export type SynchronousDatabase = {
28
29
  ): void
29
30
  select<T>(queryStr: string, bindValues?: PreparedBindValues | undefined): ReadonlyArray<T>
30
31
  export(): Uint8Array
32
+ close(): void
31
33
  }
32
34
 
33
35
  export type ResetMode = 'all-data' | 'only-app-db'
@@ -64,9 +66,16 @@ export type Coordinator = {
64
66
  }
65
67
  // TODO is exposing the lock status really needed (or only relevant for web adapter?)
66
68
  lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
67
- syncMutations: Stream.Stream<MutationEvent.AnyEncoded, UnexpectedError>
69
+ syncMutations: Stream.Stream<MutationEvent.Any, UnexpectedError>
68
70
  execute(queryStr: string, bindValues: PreparedBindValues | undefined): Effect.Effect<void, UnexpectedError>
69
- mutate(mutationEventEncoded: MutationEvent.Any, options: { persisted: boolean }): Effect.Effect<void, UnexpectedError>
71
+ mutate(
72
+ mutationEventEncoded: MutationEvent.AnyEncoded,
73
+ options: { persisted: boolean },
74
+ ): Effect.Effect<void, UnexpectedError>
75
+ /** Can be called synchronously */
76
+ getNextMutationEventId: (opts: { localOnly: boolean }) => Effect.Effect<EventId, UnexpectedError>
77
+ /** Used to initially get the current mutation event id to use as `parentId` for the next mutation event */
78
+ getCurrentMutationEventId: Effect.Effect<EventId, UnexpectedError>
70
79
  export: Effect.Effect<Uint8Array | undefined, UnexpectedError>
71
80
  getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
72
81
  networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
@@ -74,10 +83,25 @@ export type Coordinator = {
74
83
 
75
84
  export type LockStatus = 'has-lock' | 'no-lock'
76
85
 
86
+ /**
87
+ * LiveStore event id value consisting of a globally unique event sequence number
88
+ * and a local sequence number.
89
+ *
90
+ * The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
91
+ */
92
+ export type EventId = { global: number; local: number }
93
+
94
+ export const EventId = Schema.Struct({
95
+ global: Schema.Number,
96
+ local: Schema.Number,
97
+ }).annotations({ title: 'LiveStore.EventId' })
98
+
99
+ export const ROOT_ID = { global: -1, local: 0 } satisfies EventId
100
+
77
101
  export type BootDb = {
78
102
  _tag: 'BootDb'
79
103
  execute(queryStr: string, bindValues?: PreparedBindValues): void
80
- mutate: <const TMutationArg extends ReadonlyArray<MutationEvent.Any>>(...list: TMutationArg) => void
104
+ mutate: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialAny>>(...list: TMutationArg) => void
81
105
  select<T>(queryStr: string, bindValues?: PreparedBindValues): ReadonlyArray<T>
82
106
  txn(callback: () => void): void
83
107
  }
@@ -94,7 +94,8 @@ describe('derived mutations', () => {
94
94
  })
95
95
  })
96
96
 
97
- const patchId = (muationEvent: MutationEvent.Any) => {
97
+ const patchId = (muationEvent: MutationEvent.PartialAny) => {
98
+ // TODO use new id paradigm
98
99
  const id = `00000000-0000-0000-0000-000000000000`
99
100
  return { ...muationEvent, id }
100
101
  }
@@ -140,8 +140,8 @@ export namespace DerivedMutationHelperFns {
140
140
  > = SqliteDsl.AnyIfConstained<
141
141
  TColumns,
142
142
  UseShortcut<TOptions> extends true
143
- ? (values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>) => MutationEvent.Any
144
- : (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.Any
143
+ ? (values?: GetValForKey<SqliteDsl.FromColumns.InsertRowDecoded<TColumns>, 'value'>) => MutationEvent.PartialAny
144
+ : (values: SqliteDsl.FromColumns.InsertRowDecoded<TColumns>) => MutationEvent.PartialAny
145
145
  >
146
146
 
147
147
  export type UpdateMutationFn<
@@ -150,17 +150,17 @@ export namespace DerivedMutationHelperFns {
150
150
  > = SqliteDsl.AnyIfConstained<
151
151
  TColumns,
152
152
  UseShortcut<TOptions> extends true
153
- ? (values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>) => MutationEvent.Any
153
+ ? (values: Partial<GetValForKey<SqliteDsl.FromColumns.RowDecoded<TColumns>, 'value'>>) => MutationEvent.PartialAny
154
154
  : (args: {
155
155
  where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
156
156
  values: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>>
157
- }) => MutationEvent.Any
157
+ }) => MutationEvent.PartialAny
158
158
  >
159
159
 
160
160
  export type DeleteMutationFn<
161
161
  TColumns extends SqliteDsl.ConstraintColumns,
162
162
  _TOptions extends DbSchema.TableOptions,
163
- > = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.Any
163
+ > = (args: { where: Partial<SqliteDsl.FromColumns.RowDecoded<TColumns>> }) => MutationEvent.PartialAny
164
164
 
165
165
  type UseShortcut<TOptions extends DbSchema.TableOptions> = TOptions['isSingleColumn'] extends true
166
166
  ? TOptions['isSingleton'] extends true
@@ -9,5 +9,5 @@ export type PrepareDevtoolsBridge = {
9
9
  appHostId: string
10
10
  copyToClipboard: (text: string) => Effect.Effect<void>
11
11
  sendEscapeKey?: Effect.Effect<void>
12
- isLeaderTab: boolean
12
+ isLeader: boolean
13
13
  }
@@ -74,7 +74,7 @@ export class MutationBroadcast extends LSDMessage('LSD.MutationBroadcast', {
74
74
  }) {}
75
75
 
76
76
  export class RunMutationReq extends LSDReqResMessage('LSD.RunMutationReq', {
77
- mutationEventEncoded: mutationEventSchemaEncodedAny,
77
+ mutationEventEncoded: mutationEventSchemaEncodedAny.pipe(Schema.omit('id', 'parentId')),
78
78
  persisted: Schema.Boolean,
79
79
  }) {}
80
80
 
@@ -158,12 +158,19 @@ export class SyncingInfoRes extends LSDReqResMessage('LSD.SyncingInfoRes', {
158
158
  syncingInfo: SyncingInfo,
159
159
  }) {}
160
160
 
161
+ export class SyncHistorySubscribe extends LSDReqResMessage('LSD.SyncHistorySubscribe', {}) {}
162
+ export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.SyncHistoryUnsubscribe', {}) {}
163
+ export class SyncHistoryRes extends LSDReqResMessage('LSD.SyncHistoryRes', {
164
+ mutationEventEncoded: mutationEventSchemaEncodedAny,
165
+ metadata: Schema.Option(Schema.JsonValue),
166
+ }) {}
167
+
161
168
  export class DevtoolsReady extends LSDMessage('LSD.DevtoolsReady', {}) {}
162
169
 
163
170
  export class DevtoolsConnected extends LSDChannelMessage('LSD.DevtoolsConnected', {}) {}
164
171
 
165
172
  export class AppHostReady extends LSDChannelMessage('LSD.AppHostReady', {
166
- isLeaderTab: Schema.Boolean,
173
+ isLeader: Schema.Boolean,
167
174
  }) {}
168
175
 
169
176
  export class Disconnect extends LSDChannelMessage('LSD.Disconnect', {}) {}
@@ -186,6 +193,8 @@ export const MessageToAppHostCoordinator = Schema.Union(
186
193
  RunMutationReq,
187
194
  Ping,
188
195
  DatabaseFileInfoReq,
196
+ SyncHistorySubscribe,
197
+ SyncHistoryUnsubscribe,
189
198
  SyncingInfoReq,
190
199
  ).annotations({ identifier: 'LSD.MessageToAppHostCoordinator' })
191
200
 
@@ -219,6 +228,7 @@ export const MessageFromAppHostCoordinator = Schema.Union(
219
228
  RunMutationRes,
220
229
  Pong,
221
230
  DatabaseFileInfoRes,
231
+ SyncHistoryRes,
222
232
  SyncingInfoRes,
223
233
  ).annotations({ identifier: 'LSD.MessageFromAppHostCoordinator' })
224
234
 
@@ -8,6 +8,7 @@ export * from './devtools-bridge.js'
8
8
  export namespace WebBridge {
9
9
  export class AppHostReady extends Schema.TaggedStruct('LSD.WebBridge.AppHostReady', {
10
10
  appHostId: Schema.String,
11
+ // storeId: Schema.String,
11
12
  isLeader: Schema.Boolean,
12
13
  }) {}
13
14
 
@@ -24,6 +25,7 @@ export namespace WebBridge {
24
25
  */
25
26
  webBridgeId: Schema.String,
26
27
  isLeader: Schema.Boolean,
28
+ storeId: Schema.String,
27
29
  }) {}
28
30
 
29
31
  export class AppHostWillDisconnect extends Schema.TaggedStruct('LSD.WebBridge.AppHostWillDisconnect', {
@@ -3,7 +3,7 @@ import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
3
3
 
4
4
  import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
5
5
  import { getExecArgsFromMutation } from './mutation.js'
6
- import type { LiveStoreSchema, MutationDef, MutationLogMetaRow } from './schema/index.js'
6
+ import type { LiveStoreSchema, MutationDef, MutationEvent, MutationLogMetaRow } from './schema/index.js'
7
7
  import { MUTATION_LOG_META_TABLE } from './schema/index.js'
8
8
  import type { PreparedBindValues } from './util.js'
9
9
  import { sql } from './util.js'
@@ -54,10 +54,11 @@ This likely means the schema has changed in an incompatible way.
54
54
  )
55
55
 
56
56
  const mutationEventDecoded = {
57
- id: row.id,
57
+ id: { global: row.idGlobal, local: row.idLocal },
58
+ parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
58
59
  mutation: row.mutation,
59
60
  args: argsDecoded,
60
- }
61
+ } satisfies MutationEvent.Any
61
62
 
62
63
  const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
63
64
 
@@ -84,8 +85,8 @@ This likely means the schema has changed in an incompatible way.
84
85
 
85
86
  const stmt = logDb.prepare(sql`\
86
87
  SELECT * FROM ${MUTATION_LOG_META_TABLE}
87
- WHERE id > COALESCE($id, '')
88
- ORDER BY id ASC
88
+ WHERE idGlobal > COALESCE($idGlobal, '') AND idLocal > COALESCE($idLocal, '')
89
+ ORDER BY idGlobal ASC, idLocal ASC
89
90
  LIMIT ${CHUNK_SIZE}
90
91
  `)
91
92
 
@@ -97,9 +98,17 @@ LIMIT ${CHUNK_SIZE}
97
98
  // End stream if no more rows
98
99
  if (Chunk.isChunk(item) && item.length === 0) return Option.none()
99
100
 
100
- const lastId = Chunk.isChunk(item) ? Chunk.last(item).pipe(Option.getOrUndefined)?.id : undefined
101
+ const lastId = Chunk.isChunk(item)
102
+ ? Chunk.last(item).pipe(
103
+ Option.map((_) => ({ global: _.idGlobal, local: _.idLocal })),
104
+ Option.getOrUndefined,
105
+ )
106
+ : undefined
101
107
  const nextItem = Chunk.fromIterable(
102
- stmt.select<MutationLogMetaRow>({ $id: lastId } as any as PreparedBindValues),
108
+ stmt.select<MutationLogMetaRow>({
109
+ $idGlobal: lastId?.global,
110
+ $idLocal: lastId?.local,
111
+ } as any as PreparedBindValues),
103
112
  )
104
113
  const prevItem = Chunk.isChunk(item) ? item : Chunk.empty()
105
114
  return Option.some([prevItem, nextItem])
@@ -1,7 +1,7 @@
1
1
  import { memoizeByRef } from '@livestore/utils'
2
- import { cuid } from '@livestore/utils/cuid'
3
2
  import { Schema } from '@livestore/utils/effect'
4
3
 
4
+ import { EventId } from '../adapter-types.js'
5
5
  import type { BindValues } from '../sql-queries/sql-queries.js'
6
6
  import type { LiveStoreSchema } from './index.js'
7
7
 
@@ -35,42 +35,131 @@ export type SingleOrReadonlyArray<T> = T | ReadonlyArray<T>
35
35
  export type MutationDef<TName extends string, TFrom, TTo> = {
36
36
  name: TName
37
37
  schema: Schema.Schema<TTo, TFrom>
38
- sql: MutationDefSqlResult<TTo>
38
+ sql: MutationDefSqlResult<NoInfer<TTo>>
39
39
  options: {
40
+ /** Warning: This feature is not fully implemented yet */
41
+ historyId: string
40
42
  /**
41
43
  * When set to true, the mutation won't be synced over the network
42
44
  */
43
45
  localOnly: boolean
46
+ /** Warning: This feature is not fully implemented yet */
47
+ facts: FactsCallback<TTo> | undefined
44
48
  }
45
49
 
46
- /** Helper function to construct mutation event */
47
- (args: TTo): { mutation: TName; args: TTo; id: string }
50
+ /** Helper function to construct a partial mutation event */
51
+ (
52
+ args: TTo,
53
+ options?: {
54
+ id?: number
55
+ },
56
+ ): {
57
+ mutation: TName
58
+ args: TTo
59
+ // TODO remove/clean up after sync-next is fully implemented
60
+ id?: EventId
61
+ }
62
+ }
63
+
64
+ export type FactsCallback<TTo> = (
65
+ args: TTo,
66
+ currentFacts: MutationEventFacts,
67
+ ) => {
68
+ modify: {
69
+ set: Iterable<MutationEventFactInput>
70
+ unset: Iterable<MutationEventFactInput>
71
+ }
72
+ require: Iterable<MutationEventFactInput>
48
73
  }
49
74
 
50
75
  export namespace MutationDef {
51
76
  export type Any = MutationDef<string, any, any>
52
77
  }
53
78
 
79
+ export type MutationEventKey = string
80
+ export type MutationEventFact = string
81
+ export type MutationEventFacts = ReadonlyMap<string, any>
82
+
83
+ export type MutationEventFactsGroup = {
84
+ modifySet: MutationEventFacts
85
+ modifyUnset: MutationEventFacts
86
+
87
+ /**
88
+ * Events on independent "dependency" branches are commutative which can facilitate more prioritized syncing
89
+ */
90
+ depRequire: MutationEventFacts
91
+ depRead: MutationEventFacts
92
+ }
93
+
94
+ export type MutationEventFactsSnapshot = Map<string, any>
95
+
96
+ export type MutationEventFactInput = string | readonly [string, any]
97
+
98
+ export const defineFacts = <
99
+ TRecord extends Record<string, MutationEventFactInput | ((...args: any[]) => MutationEventFactInput)>,
100
+ >(
101
+ record: TRecord,
102
+ ): TRecord => record
103
+
104
+ export type DefineMutationOptions<TTo> = {
105
+ historyId?: string
106
+ /** Warning: This feature is not fully implemented yet */
107
+ facts?: (
108
+ args: TTo,
109
+ currentFacts: MutationEventFacts,
110
+ ) => {
111
+ modify?: {
112
+ set?: Iterable<MutationEventFactInput>
113
+ unset?: Iterable<MutationEventFactInput>
114
+ }
115
+ /**
116
+ * Two purposes: constrain history and constrain compaction
117
+ */
118
+ require?: Iterable<MutationEventFactInput>
119
+ }
120
+ /**
121
+ * When set to true, the mutation won't be synced over the network
122
+ */
123
+ localOnly?: boolean
124
+ }
125
+
54
126
  // TODO possibly also allow for mutation event subsumption behaviour
55
127
  export const defineMutation = <TName extends string, TFrom, TTo>(
56
128
  name: TName,
57
129
  schema: Schema.Schema<TTo, TFrom>,
58
- sql: MutationDefSqlResult<TTo>,
59
- options?: {
60
- /**
61
- * When set to true, the mutation won't be synced over the network
62
- */
63
- localOnly?: boolean
64
- },
130
+ sql: MutationDefSqlResult<NoInfer<TTo>>,
131
+ options?: DefineMutationOptions<TTo>,
65
132
  ): MutationDef<TName, TFrom, TTo> => {
66
- const makeEvent = (args: TTo) => ({ mutation: name, args, id: cuid() })
133
+ const makePartialEvent = (
134
+ args: TTo,
135
+ options?: {
136
+ id?: EventId
137
+ },
138
+ ) => ({ mutation: name, args, ...options })
67
139
 
68
- Object.defineProperty(makeEvent, 'name', { value: name })
69
- Object.defineProperty(makeEvent, 'schema', { value: schema })
70
- Object.defineProperty(makeEvent, 'sql', { value: sql })
71
- Object.defineProperty(makeEvent, 'options', { value: { localOnly: options?.localOnly ?? false } })
140
+ Object.defineProperty(makePartialEvent, 'name', { value: name })
141
+ Object.defineProperty(makePartialEvent, 'schema', { value: schema })
142
+ Object.defineProperty(makePartialEvent, 'sql', { value: sql })
143
+ Object.defineProperty(makePartialEvent, 'options', {
144
+ value: {
145
+ historyId: options?.historyId ?? 'main',
146
+ localOnly: options?.localOnly ?? false,
147
+ facts: options?.facts
148
+ ? (args, currentFacts) => {
149
+ const res = options.facts!(args, currentFacts)
150
+ return {
151
+ modify: {
152
+ set: res.modify?.set ? new Set(res.modify.set) : new Set(),
153
+ unset: res.modify?.unset ? new Set(res.modify.unset) : new Set(),
154
+ },
155
+ require: res.require ? new Set(res.require) : new Set(),
156
+ }
157
+ }
158
+ : undefined,
159
+ } satisfies MutationDef.Any['options'],
160
+ })
72
161
 
73
- return makeEvent as MutationDef<TName, TFrom, TTo>
162
+ return makePartialEvent as MutationDef<TName, TFrom, TTo>
74
163
  }
75
164
 
76
165
  export const makeMutationDefRecord = <TInputRecord extends Record<string, MutationDef.Any>>(
@@ -102,22 +191,41 @@ export const rawSqlMutation = defineMutation(
102
191
  export type RawSqlMutation = typeof rawSqlMutation
103
192
  export type RawSqlMutationEvent = ReturnType<typeof rawSqlMutation>
104
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
+
105
204
  export type MutationEvent<TMutationsDef extends MutationDef.Any> = {
106
205
  mutation: TMutationsDef['name']
107
206
  args: Schema.Schema.Type<TMutationsDef['schema']>
108
- id: string
207
+ id: EventId
208
+ parentId: EventId
109
209
  }
110
210
 
111
211
  export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
112
212
  mutation: TMutationsDef['name']
113
213
  args: Schema.Schema.Encoded<TMutationsDef['schema']>
114
- id: string
214
+ id: EventId
215
+ parentId: EventId
115
216
  }
116
217
 
117
218
  export namespace MutationEvent {
118
219
  export type Any = MutationEvent<MutationDef.Any>
119
220
  export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
120
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
+
121
229
  export type ForSchema<TSchema extends LiveStoreSchema> = {
122
230
  [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
123
231
  }[keyof TSchema['_MutationDefMapType']]
@@ -128,14 +236,31 @@ export type MutationEventSchema<TMutationsDefRecord extends MutationDefRecord> =
128
236
  [K in keyof TMutationsDefRecord]: {
129
237
  mutation: K
130
238
  args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
131
- id: string
239
+ id: EventId
240
+ parentId: EventId
241
+ }
242
+ }[keyof TMutationsDefRecord],
243
+ {
244
+ [K in keyof TMutationsDefRecord]: {
245
+ mutation: K
246
+ args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
247
+ id: EventId
248
+ parentId: EventId
249
+ }
250
+ }[keyof TMutationsDefRecord]
251
+ >
252
+
253
+ export type MutationEventPartialSchema<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
254
+ {
255
+ [K in keyof TMutationsDefRecord]: {
256
+ mutation: K
257
+ args: Schema.Schema.Type<TMutationsDefRecord[K]['schema']>
132
258
  }
133
259
  }[keyof TMutationsDefRecord],
134
260
  {
135
261
  [K in keyof TMutationsDefRecord]: {
136
262
  mutation: K
137
263
  args: Schema.Schema.Encoded<TMutationsDefRecord[K]['schema']>
138
- id: string
139
264
  }
140
265
  }[keyof TMutationsDefRecord]
141
266
  >
@@ -148,21 +273,37 @@ export const makeMutationEventSchema = <TSchema extends LiveStoreSchema>(
148
273
  Schema.Struct({
149
274
  mutation: Schema.Literal(def.name),
150
275
  args: def.schema,
151
- id: Schema.String,
276
+ id: EventId,
277
+ parentId: EventId,
152
278
  }),
153
279
  ),
154
280
  ).annotations({ title: 'MutationEventSchema' }) as any
155
281
 
282
+ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
283
+ schema: TSchema,
284
+ ): MutationEventPartialSchema<TSchema['_MutationDefMapType']> =>
285
+ Schema.Union(
286
+ ...[...schema.mutations.values()].map((def) =>
287
+ Schema.Struct({
288
+ mutation: Schema.Literal(def.name),
289
+ args: def.schema,
290
+ }),
291
+ ),
292
+ ).annotations({ title: 'MutationEventSchemaPartial' }) as any
293
+
156
294
  export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
157
295
 
158
- export const mutationEventSchemaDecodedAny = Schema.Struct({
296
+ export const mutationEventSchemaAny = Schema.Struct({
159
297
  mutation: Schema.String,
160
298
  args: Schema.Any,
161
- id: Schema.String,
162
- }).annotations({ title: 'MutationEventSchema.DecodedAny' })
299
+ id: EventId,
300
+ parentId: EventId,
301
+ }).annotations({ title: 'MutationEventSchema.Any' })
163
302
 
164
- export const mutationEventSchemaEncodedAny = Schema.Struct({
165
- mutation: Schema.String,
166
- args: Schema.Any,
167
- id: Schema.String,
168
- }).annotations({ title: 'MutationEventSchema.EncodedAny' })
303
+ export const mutationEventSchemaDecodedAny = Schema.typeSchema(mutationEventSchemaAny).annotations({
304
+ title: 'MutationEventSchema.DecodedAny',
305
+ })
306
+
307
+ export const mutationEventSchemaEncodedAny = Schema.encodedSchema(mutationEventSchemaAny).annotations({
308
+ title: 'MutationEventSchema.EncodedAny',
309
+ })
@@ -36,7 +36,25 @@ export const schemaMutationsMetaTable = table(
36
36
 
37
37
  export type SchemaMutationsMetaRow = FromTable.RowDecoded<typeof schemaMutationsMetaTable>
38
38
 
39
- export const systemTables = [schemaMetaTable, schemaMutationsMetaTable]
39
+ /**
40
+ * Table which stores SQLite changeset blobs which is used for rolling back
41
+ * read-model state during rebasing.
42
+ */
43
+ export const SESSION_CHANGESET_META_TABLE = '__livestore_session_changeset'
44
+
45
+ export const sessionChangesetMetaTable = table(
46
+ SESSION_CHANGESET_META_TABLE,
47
+ {
48
+ idGlobal: SqliteDsl.integer({ primaryKey: true }),
49
+ idLocal: SqliteDsl.integer({ primaryKey: true }),
50
+ changeset: SqliteDsl.blob({}),
51
+ },
52
+ { disableAutomaticIdColumn: true },
53
+ )
54
+
55
+ export type SessionChangesetMetaRow = FromTable.RowDecoded<typeof sessionChangesetMetaTable>
56
+
57
+ export const systemTables = [schemaMetaTable, schemaMutationsMetaTable, sessionChangesetMetaTable]
40
58
 
41
59
  /// Mutation log DB
42
60
 
@@ -48,17 +66,21 @@ export const MUTATION_LOG_META_TABLE = 'mutation_log'
48
66
  export const mutationLogMetaTable = table(
49
67
  MUTATION_LOG_META_TABLE,
50
68
  {
51
- // TODO add parent ids (see https://vlcn.io/blog/crdt-substrate)
52
- id: SqliteDsl.text({ primaryKey: true }),
53
- mutation: SqliteDsl.text({ nullable: false }),
54
- argsJson: SqliteDsl.text({ nullable: false, schema: Schema.parseJson(Schema.Any) }),
55
- schemaHash: SqliteDsl.integer({ nullable: false }),
69
+ idGlobal: SqliteDsl.integer({ primaryKey: true }),
70
+ idLocal: SqliteDsl.integer({ primaryKey: true }),
71
+ parentIdGlobal: SqliteDsl.integer({}),
72
+ parentIdLocal: SqliteDsl.integer({}),
73
+ mutation: SqliteDsl.text({}),
74
+ argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
75
+ schemaHash: SqliteDsl.integer({}),
76
+ /** Local only, used for ordered queries to avoid recursive id traversal */
77
+ orderKey: SqliteDsl.integer({}),
56
78
  /** ISO date format */
57
- createdAt: SqliteDsl.text({ nullable: false }),
79
+ createdAt: SqliteDsl.text({}),
58
80
  syncStatus: SqliteDsl.text({ schema: SyncStatus }),
59
81
  syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
60
82
  },
61
- { disableAutomaticIdColumn: true },
83
+ { disableAutomaticIdColumn: true, indexes: [{ columns: ['orderKey'], name: 'mutation_log_order_key_idx' }] },
62
84
  )
63
85
 
64
86
  export type MutationLogMetaRow = FromTable.RowDecoded<typeof mutationLogMetaTable>
@@ -155,7 +155,7 @@ export const migrateTable = ({
155
155
 
156
156
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
157
157
  const uniqueStr = index.unique ? 'UNIQUE' : ''
158
- return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`
158
+ return sql`create ${uniqueStr} index if not exists ${index.name} on ${tableName} (${index.columns.join(', ')})`
159
159
  }
160
160
 
161
161
  export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
@@ -0,0 +1,3 @@
1
+ declare module 'graphology' {
2
+ export * from 'graphology-types'
3
+ }