@livestore/common 0.3.0-dev.10 → 0.3.0-dev.2

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 (146) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +23 -26
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js.map +1 -1
  5. package/dist/derived-mutations.d.ts +4 -4
  6. package/dist/derived-mutations.d.ts.map +1 -1
  7. package/dist/derived-mutations.test.js.map +1 -1
  8. package/dist/devtools/devtools-bridge.d.ts +1 -2
  9. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  10. package/dist/devtools/devtools-messages.d.ts +110 -98
  11. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  12. package/dist/devtools/devtools-messages.js +6 -9
  13. package/dist/devtools/devtools-messages.js.map +1 -1
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/leader-thread/apply-mutation.d.ts +2 -5
  17. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  18. package/dist/leader-thread/apply-mutation.js +26 -38
  19. package/dist/leader-thread/apply-mutation.js.map +1 -1
  20. package/dist/leader-thread/leader-sync-processor.d.ts +2 -2
  21. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
  22. package/dist/leader-thread/leader-sync-processor.js +12 -20
  23. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  24. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  25. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  26. package/dist/leader-thread/leader-worker-devtools.js +66 -22
  27. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  28. package/dist/leader-thread/make-leader-thread-layer.d.ts +7 -8
  29. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  30. package/dist/leader-thread/make-leader-thread-layer.js +5 -11
  31. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  32. package/dist/leader-thread/mutationlog.d.ts +17 -4
  33. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  34. package/dist/leader-thread/mutationlog.js +1 -2
  35. package/dist/leader-thread/mutationlog.js.map +1 -1
  36. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  37. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  38. package/dist/leader-thread/recreate-db.js +3 -9
  39. package/dist/leader-thread/recreate-db.js.map +1 -1
  40. package/dist/leader-thread/types.d.ts +9 -17
  41. package/dist/leader-thread/types.d.ts.map +1 -1
  42. package/dist/leader-thread/types.js.map +1 -1
  43. package/dist/mutation.d.ts +2 -9
  44. package/dist/mutation.d.ts.map +1 -1
  45. package/dist/mutation.js +5 -5
  46. package/dist/mutation.js.map +1 -1
  47. package/dist/query-builder/impl.d.ts +1 -1
  48. package/dist/rehydrate-from-mutationlog.d.ts +2 -2
  49. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  50. package/dist/rehydrate-from-mutationlog.js +19 -13
  51. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  52. package/dist/schema/EventId.d.ts +14 -16
  53. package/dist/schema/EventId.d.ts.map +1 -1
  54. package/dist/schema/EventId.js +7 -15
  55. package/dist/schema/EventId.js.map +1 -1
  56. package/dist/schema/MutationEvent.d.ts +80 -49
  57. package/dist/schema/MutationEvent.d.ts.map +1 -1
  58. package/dist/schema/MutationEvent.js +15 -32
  59. package/dist/schema/MutationEvent.js.map +1 -1
  60. package/dist/schema/system-tables.d.ts +26 -26
  61. package/dist/schema/system-tables.d.ts.map +1 -1
  62. package/dist/schema/system-tables.js +11 -19
  63. package/dist/schema/system-tables.js.map +1 -1
  64. package/dist/schema-management/migrations.js +6 -6
  65. package/dist/schema-management/migrations.js.map +1 -1
  66. package/dist/sync/client-session-sync-processor.d.ts +4 -4
  67. package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
  68. package/dist/sync/index.d.ts +1 -1
  69. package/dist/sync/index.d.ts.map +1 -1
  70. package/dist/sync/index.js +1 -1
  71. package/dist/sync/index.js.map +1 -1
  72. package/dist/sync/next/history-dag-common.d.ts +4 -1
  73. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  74. package/dist/sync/next/history-dag-common.js +1 -1
  75. package/dist/sync/next/history-dag-common.js.map +1 -1
  76. package/dist/sync/next/rebase-events.d.ts +3 -3
  77. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  78. package/dist/sync/next/rebase-events.js +2 -3
  79. package/dist/sync/next/rebase-events.js.map +1 -1
  80. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  81. package/dist/sync/next/test/mutation-fixtures.js +9 -3
  82. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  83. package/dist/sync/sync.d.ts +11 -21
  84. package/dist/sync/sync.d.ts.map +1 -1
  85. package/dist/sync/sync.js.map +1 -1
  86. package/dist/sync/syncstate.d.ts +23 -45
  87. package/dist/sync/syncstate.d.ts.map +1 -1
  88. package/dist/sync/syncstate.js +12 -56
  89. package/dist/sync/syncstate.js.map +1 -1
  90. package/dist/sync/syncstate.test.js +69 -125
  91. package/dist/sync/syncstate.test.js.map +1 -1
  92. package/dist/sync/validate-push-payload.d.ts +2 -2
  93. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  94. package/dist/sync/validate-push-payload.js +2 -2
  95. package/dist/sync/validate-push-payload.js.map +1 -1
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.d.ts.map +1 -1
  98. package/dist/version.js +1 -1
  99. package/dist/version.js.map +1 -1
  100. package/package.json +5 -6
  101. package/src/adapter-types.ts +24 -22
  102. package/src/derived-mutations.test.ts +1 -1
  103. package/src/derived-mutations.ts +5 -9
  104. package/src/devtools/devtools-bridge.ts +1 -2
  105. package/src/devtools/devtools-messages.ts +6 -9
  106. package/src/index.ts +6 -0
  107. package/src/leader-thread/apply-mutation.ts +31 -49
  108. package/src/leader-thread/{LeaderSyncProcessor.ts → leader-sync-processor.ts} +230 -235
  109. package/src/leader-thread/leader-worker-devtools.ts +109 -30
  110. package/src/leader-thread/make-leader-thread-layer.ts +13 -24
  111. package/src/leader-thread/mutationlog.ts +5 -9
  112. package/src/leader-thread/recreate-db.ts +5 -9
  113. package/src/leader-thread/types.ts +11 -18
  114. package/src/mutation.ts +7 -17
  115. package/src/rehydrate-from-mutationlog.ts +23 -15
  116. package/src/schema/EventId.ts +9 -23
  117. package/src/schema/MutationEvent.ts +24 -46
  118. package/src/schema/system-tables.ts +11 -19
  119. package/src/schema-management/migrations.ts +6 -6
  120. package/src/sync/{ClientSessionSyncProcessor.ts → client-session-sync-processor.ts} +9 -11
  121. package/src/sync/index.ts +1 -1
  122. package/src/sync/next/history-dag-common.ts +1 -1
  123. package/src/sync/next/rebase-events.ts +7 -7
  124. package/src/sync/next/test/mutation-fixtures.ts +10 -3
  125. package/src/sync/sync.ts +6 -19
  126. package/src/sync/syncstate.test.ts +67 -127
  127. package/src/sync/syncstate.ts +19 -21
  128. package/src/sync/validate-push-payload.ts +4 -7
  129. package/src/version.ts +1 -1
  130. package/dist/leader-thread/LeaderSyncProcessor.d.ts +0 -37
  131. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +0 -1
  132. package/dist/leader-thread/LeaderSyncProcessor.js +0 -417
  133. package/dist/leader-thread/LeaderSyncProcessor.js.map +0 -1
  134. package/dist/schema/EventId.test.d.ts +0 -2
  135. package/dist/schema/EventId.test.d.ts.map +0 -1
  136. package/dist/schema/EventId.test.js +0 -11
  137. package/dist/schema/EventId.test.js.map +0 -1
  138. package/dist/schema/MutationEvent.test.d.ts +0 -2
  139. package/dist/schema/MutationEvent.test.d.ts.map +0 -1
  140. package/dist/schema/MutationEvent.test.js +0 -2
  141. package/dist/schema/MutationEvent.test.js.map +0 -1
  142. package/dist/sync/ClientSessionSyncProcessor.d.ts +0 -45
  143. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +0 -1
  144. package/dist/sync/ClientSessionSyncProcessor.js +0 -134
  145. package/dist/sync/ClientSessionSyncProcessor.js.map +0 -1
  146. package/src/schema/EventId.test.ts +0 -12
@@ -1,11 +1,18 @@
1
- import { Effect, FiberMap, Option, Stream, SubscriptionRef } from '@livestore/utils/effect'
1
+ import { Effect, FiberMap, Option, PubSub, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
2
2
 
3
3
  import { Devtools, IntentionalShutdownCause, liveStoreVersion, UnexpectedError } from '../index.js'
4
4
  import { MUTATION_LOG_META_TABLE, SCHEMA_META_TABLE, SCHEMA_MUTATIONS_META_TABLE } from '../schema/mod.js'
5
+ import type { ShutdownChannel } from './shutdown-channel.js'
5
6
  import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
6
7
  import { LeaderThreadCtx } from './types.js'
7
8
 
8
- type SendMessageToDevtools = (message: Devtools.MessageFromAppLeader) => Effect.Effect<void>
9
+ type SendMessageToDevtools = (
10
+ message: Devtools.MessageFromAppLeader,
11
+ options?: {
12
+ /** Send message even if not connected (e.g. for initial broadcast messages) */
13
+ force: boolean
14
+ },
15
+ ) => Effect.Effect<void>
9
16
 
10
17
  // TODO bind scope to the webchannel lifetime
11
18
  export const bootDevtools = (options: DevtoolsOptions) =>
@@ -14,24 +21,44 @@ export const bootDevtools = (options: DevtoolsOptions) =>
14
21
  return
15
22
  }
16
23
 
17
- const { connectedClientSessionPullQueues, syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
24
+ const { persistenceInfo, shutdownChannel, devtoolsWebChannel } = yield* options.makeContext
18
25
 
19
- yield* listenToDevtools({
20
- incomingMessages: Stream.fromQueue(extraIncomingMessagesQueue),
21
- sendMessage: () => Effect.void,
22
- }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
26
+ const isConnected = yield* SubscriptionRef.make(true)
27
+
28
+ const incomingMessagesPubSub = yield* PubSub.unbounded<Devtools.MessageToAppLeader>().pipe(
29
+ Effect.acquireRelease(PubSub.shutdown),
30
+ )
23
31
 
24
- const { persistenceInfo, devtoolsWebChannel } = yield* options.makeContext
32
+ const incomingMessages = Stream.fromPubSub(incomingMessagesPubSub)
25
33
 
26
- const sendMessage: SendMessageToDevtools = (message) =>
27
- devtoolsWebChannel
28
- .send(message)
29
- .pipe(
30
- Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
31
- Effect.interruptible,
32
- Effect.ignoreLogged,
33
- )
34
+ const outgoingMessagesQueue = yield* Queue.unbounded<Devtools.MessageFromAppLeader>().pipe(
35
+ Effect.acquireRelease(Queue.shutdown),
36
+ )
34
37
 
38
+ const devtoolsCoordinatorChannel = devtoolsWebChannel
39
+ // coordinatorMessagePortOrChannel instanceof MessagePort
40
+ // ? yield* WebChannel.messagePortChannel({
41
+ // port: coordinatorMessagePortOrChannel,
42
+ // schema: { send: Devtools.MessageFromAppLeader, listen: Devtools.MessageToAppLeader },
43
+ // })
44
+ // : coordinatorMessagePortOrChannel
45
+
46
+ const sendMessage: SendMessageToDevtools = (message, options) =>
47
+ Effect.gen(function* () {
48
+ if (options?.force === true || (yield* isConnected)) {
49
+ yield* devtoolsCoordinatorChannel.send(message)
50
+ } else {
51
+ yield* Queue.offer(outgoingMessagesQueue, message)
52
+ }
53
+ }).pipe(
54
+ Effect.withSpan('@livestore/common:leader-thread:devtools:sendToDevtools'),
55
+ Effect.interruptible,
56
+ Effect.ignoreLogged,
57
+ )
58
+
59
+ // broadcastCallbacks.add((message) => sendMessage(message))
60
+
61
+ const { connectedClientSessionPullQueues, syncProcessor } = yield* LeaderThreadCtx
35
62
  const { localHead } = yield* syncProcessor.syncState
36
63
 
37
64
  // TODO close queue when devtools disconnects
@@ -42,8 +69,13 @@ export const bootDevtools = (options: DevtoolsOptions) =>
42
69
  Effect.gen(function* () {
43
70
  if (msg.payload._tag === 'upstream-advance') {
44
71
  for (const mutationEventEncoded of msg.payload.newEvents) {
45
- // TODO refactor with push semantics
46
- yield* sendMessage(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
72
+ yield* sendMessage(
73
+ Devtools.MutationBroadcast.make({
74
+ mutationEventEncoded,
75
+
76
+ liveStoreVersion,
77
+ }),
78
+ )
47
79
  }
48
80
  } else {
49
81
  yield* Effect.logWarning('TODO implement rebases in devtools')
@@ -54,25 +86,68 @@ export const bootDevtools = (options: DevtoolsOptions) =>
54
86
  Effect.forkScoped,
55
87
  )
56
88
 
89
+ yield* devtoolsCoordinatorChannel.listen.pipe(
90
+ Stream.flatten(),
91
+ // Stream.tapLogWithLabel('@livestore/common:leader-thread:devtools:onPortMessage'),
92
+ Stream.tap((msg) =>
93
+ Effect.gen(function* () {
94
+ // yield* Effect.logDebug(`[@livestore/common:leader-thread:devtools] message from port: ${msg._tag}`, msg)
95
+ // if (msg._tag === 'LSD.MessagePortForStoreRes') {
96
+ // yield* Deferred.succeed(storeMessagePortDeferred, msg.port)
97
+ // } else {
98
+ yield* PubSub.publish(incomingMessagesPubSub, msg)
99
+ // }
100
+ }),
101
+ ),
102
+ Stream.runDrain,
103
+ Effect.withSpan(`@livestore/common:leader-thread:devtools:onPortMessage`),
104
+ Effect.ignoreLogged,
105
+ Effect.forkScoped,
106
+ )
107
+
108
+ // yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), { force: true })
109
+
110
+ // yield* sendMessage(Devtools.MessagePortForStoreReq.make({ appHostId, liveStoreVersion, requestId: nanoid() }), {
111
+ // force: true,
112
+ // })
113
+
57
114
  yield* listenToDevtools({
58
- incomingMessages: devtoolsWebChannel.listen.pipe(Stream.flatten(), Stream.orDie),
115
+ incomingMessages,
59
116
  sendMessage,
117
+ // isConnected,
118
+ // disconnect,
119
+ // storeId,
120
+ // appHostId,
121
+ // isLeader,
60
122
  persistenceInfo,
61
- }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
123
+ shutdownChannel,
124
+ })
62
125
  }).pipe(Effect.withSpan('@livestore/common:leader-thread:devtools:boot'))
63
126
 
64
127
  const listenToDevtools = ({
65
128
  incomingMessages,
66
129
  sendMessage,
130
+ // isConnected,
131
+ // disconnect,
132
+ // appHostId,
133
+ // storeId,
134
+ // isLeader,
67
135
  persistenceInfo,
136
+ shutdownChannel,
68
137
  }: {
69
138
  incomingMessages: Stream.Stream<Devtools.MessageToAppLeader>
70
139
  sendMessage: SendMessageToDevtools
71
- persistenceInfo?: PersistenceInfoPair
140
+ // isConnected: SubscriptionRef.SubscriptionRef<boolean>
141
+ // disconnect: Effect.Effect<void>
142
+ // appHostId: string
143
+ // storeId: string
144
+ // isLeader: boolean
145
+ persistenceInfo: PersistenceInfoPair
146
+ shutdownChannel: ShutdownChannel
72
147
  }) =>
73
148
  Effect.gen(function* () {
74
- const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, shutdownChannel, syncProcessor } =
75
- yield* LeaderThreadCtx
149
+ const innerWorkerCtx = yield* LeaderThreadCtx
150
+ const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, syncProcessor } = innerWorkerCtx
76
151
 
77
152
  type RequestId = string
78
153
  const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
@@ -83,6 +158,15 @@ const listenToDevtools = ({
83
158
  // yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
84
159
 
85
160
  if (decodedEvent._tag === 'LSD.Disconnect') {
161
+ // yield* SubscriptionRef.set(isConnected, false)
162
+
163
+ // yield* disconnect
164
+
165
+ // TODO is there a better place for this?
166
+ // yield* sendMessage(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }), {
167
+ // force: true,
168
+ // })
169
+
86
170
  return
87
171
  }
88
172
 
@@ -142,7 +226,7 @@ const listenToDevtools = ({
142
226
 
143
227
  yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
144
228
 
145
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
229
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
146
230
 
147
231
  return
148
232
  }
@@ -159,16 +243,11 @@ const listenToDevtools = ({
159
243
 
160
244
  yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
161
245
 
162
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
246
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
163
247
 
164
248
  return
165
249
  }
166
250
  case 'LSD.Leader.DatabaseFileInfoReq': {
167
- if (persistenceInfo === undefined) {
168
- console.log('[@livestore/common:leader-thread:devtools] persistenceInfo is required for this request')
169
- return
170
- }
171
-
172
251
  const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
173
252
  const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
174
253
  const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
@@ -1,43 +1,41 @@
1
- import type { HttpClient, Scope } from '@livestore/utils/effect'
2
- import { Deferred, Effect, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
1
+ import type { HttpClient, Scope, WebChannel } from '@livestore/utils/effect'
2
+ import { Deferred, Effect, FiberSet, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
3
3
 
4
4
  import type { BootStatus, MakeSynchronousDatabase, SqliteError, SynchronousDatabase } from '../adapter-types.js'
5
5
  import { UnexpectedError } from '../adapter-types.js'
6
- import type * as Devtools from '../devtools/index.js'
7
6
  import type { LiveStoreSchema } from '../schema/mod.js'
8
7
  import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
9
8
  import { migrateTable } from '../schema-management/migrations.js'
10
- import type { InvalidPullError, IsOfflineError, SyncOptions } from '../sync/sync.js'
9
+ import type { InvalidPullError, IsOfflineError, SyncBackend } from '../sync/sync.js'
11
10
  import { sql } from '../util.js'
12
11
  import { execSql } from './connection.js'
12
+ import { makeLeaderSyncProcessor } from './leader-sync-processor.js'
13
13
  import { bootDevtools } from './leader-worker-devtools.js'
14
- import { makeLeaderSyncProcessor } from './LeaderSyncProcessor.js'
15
14
  import { makePullQueueSet } from './pull-queue-set.js'
16
15
  import { recreateDb } from './recreate-db.js'
17
- import type { ShutdownChannel } from './shutdown-channel.js'
18
16
  import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
19
17
  import { LeaderThreadCtx } from './types.js'
20
18
 
21
19
  export const makeLeaderThreadLayer = ({
22
20
  schema,
23
21
  storeId,
24
- clientId,
22
+ originId,
25
23
  makeSyncDb,
26
- syncOptions,
24
+ makeSyncBackend,
27
25
  db,
28
26
  dbLog,
29
27
  devtoolsOptions,
30
- shutdownChannel,
28
+ initialSyncOptions = { _tag: 'Skip' },
31
29
  }: {
32
30
  storeId: string
33
- clientId: string
31
+ originId: string
34
32
  schema: LiveStoreSchema
35
33
  makeSyncDb: MakeSynchronousDatabase
36
- syncOptions: SyncOptions | undefined
34
+ makeSyncBackend: Effect.Effect<SyncBackend, UnexpectedError, Scope.Scope> | undefined
37
35
  db: SynchronousDatabase
38
36
  dbLog: SynchronousDatabase
39
37
  devtoolsOptions: DevtoolsOptions
40
- shutdownChannel: ShutdownChannel
38
+ initialSyncOptions: InitialSyncOptions | undefined
41
39
  }): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
42
40
  Effect.gen(function* () {
43
41
  const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
@@ -46,34 +44,25 @@ export const makeLeaderThreadLayer = ({
46
44
  // Either happens on initial boot or if schema changes
47
45
  const dbMissing = db.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
48
46
 
49
- const syncBackend = syncOptions === undefined ? undefined : yield* syncOptions.makeBackend({ storeId, clientId })
47
+ const syncBackend = makeSyncBackend === undefined ? undefined : yield* makeSyncBackend
50
48
 
51
- const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
52
- initialSyncOptions: syncOptions?.initialSyncOptions ?? { _tag: 'Skip' },
53
- bootStatusQueue,
54
- })
49
+ const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({ initialSyncOptions, bootStatusQueue })
55
50
 
56
51
  const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
57
52
 
58
- const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
59
- Effect.acquireRelease(Queue.shutdown),
60
- )
61
-
62
53
  const ctx = {
63
54
  schema,
64
55
  bootStatusQueue,
65
56
  storeId,
66
- clientId,
57
+ originId,
67
58
  db,
68
59
  dbLog,
69
60
  makeSyncDb,
70
61
  mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
71
62
  shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
72
- shutdownChannel,
73
63
  syncBackend,
74
64
  syncProcessor,
75
65
  connectedClientSessionPullQueues: yield* makePullQueueSet,
76
- extraIncomingMessagesQueue,
77
66
  } satisfies typeof LeaderThreadCtx.Service
78
67
 
79
68
  // @ts-expect-error For debugging purposes
@@ -2,14 +2,11 @@ import { Effect, Schema } from '@livestore/utils/effect'
2
2
 
3
3
  import type { SynchronousDatabase } from '../adapter-types.js'
4
4
  import * as EventId from '../schema/EventId.js'
5
- import type * as MutationEvent from '../schema/MutationEvent.js'
6
5
  import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
7
6
  import { prepareBindValues, sql } from '../util.js'
8
7
  import { LeaderThreadCtx } from './types.js'
9
8
 
10
- export const getMutationEventsSince = (
11
- since: EventId.EventId,
12
- ): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
9
+ export const getMutationEventsSince = (since: EventId.EventId) =>
13
10
  Effect.gen(function* () {
14
11
  const { dbLog } = yield* LeaderThreadCtx
15
12
 
@@ -29,17 +26,16 @@ export const getMutationEventsSince = (
29
26
  .filter((_) => EventId.compare(_.id, since) > 0)
30
27
  })
31
28
 
32
- export const getLocalHeadFromDb = (dbLog: SynchronousDatabase): EventId.EventId => {
33
- const res = dbLog.select<{ idGlobal: EventId.GlobalEventId; idLocal: EventId.LocalEventId }>(
29
+ export const getLocalHeadFromDb = (dbLog: SynchronousDatabase) => {
30
+ const res = dbLog.select<{ idGlobal: number; idLocal: number }>(
34
31
  sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
35
32
  )[0]
36
33
 
37
34
  return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
38
35
  }
39
36
 
40
- export const getBackendHeadFromDb = (dbLog: SynchronousDatabase): EventId.GlobalEventId =>
41
- dbLog.select<{ head: EventId.GlobalEventId }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ??
42
- EventId.ROOT.global
37
+ export const getBackendHeadFromDb = (dbLog: SynchronousDatabase) =>
38
+ dbLog.select<{ head: number }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ?? EventId.ROOT.global
43
39
 
44
40
  // TODO use prepared statements
45
41
  export const updateBackendHead = (dbLog: SynchronousDatabase, head: EventId.EventId) =>
@@ -24,9 +24,7 @@ export const recreateDb: Effect.Effect<
24
24
 
25
25
  // NOTE to speed up the operations below, we're creating a temporary in-memory database
26
26
  // and later we'll overwrite the persisted database with the new data
27
- // TODO bring back this optimization
28
- // const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
29
- const tmpSyncDb = db
27
+ const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
30
28
  yield* configureConnection(tmpSyncDb, { fkEnabled: true })
31
29
 
32
30
  const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
@@ -93,19 +91,17 @@ export const recreateDb: Effect.Effect<
93
91
  }
94
92
  }
95
93
 
96
- // TODO bring back
97
94
  // Import the temporary in-memory database into the persistent database
98
- // yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
99
- // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
100
- // )
95
+ yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
96
+ Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
97
+ )
101
98
 
102
99
  // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
103
100
  // We've disabled this for now as it made the code too complex, as we often run syncing right after
104
101
  // so the snapshot is no longer up to date
105
102
  // const snapshotFromTmpDb = tmpSyncDb.export()
106
103
 
107
- // TODO bring back
108
- // tmpSyncDb.close()
104
+ tmpSyncDb.close()
109
105
  }).pipe(
110
106
  Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
111
107
  Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
@@ -6,7 +6,6 @@ import type {
6
6
  Option,
7
7
  Queue,
8
8
  Scope,
9
- Subscribable,
10
9
  SubscriptionRef,
11
10
  WebChannel,
12
11
  } from '@livestore/utils/effect'
@@ -23,7 +22,7 @@ import type {
23
22
  UnexpectedError,
24
23
  } from '../index.js'
25
24
  import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
26
- import type * as SyncState from '../sync/syncstate.js'
25
+ import type { PayloadUpstream, SyncState } from '../sync/syncstate.js'
27
26
  import type { ShutdownChannel } from './shutdown-channel.js'
28
27
 
29
28
  export type ShutdownState = 'running' | 'shutting-down'
@@ -68,6 +67,7 @@ export type DevtoolsOptions =
68
67
  makeContext: Effect.Effect<
69
68
  {
70
69
  devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
70
+ shutdownChannel: ShutdownChannel
71
71
  persistenceInfo: PersistenceInfoPair
72
72
  },
73
73
  UnexpectedError,
@@ -80,21 +80,18 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
80
80
  {
81
81
  schema: LiveStoreSchema
82
82
  storeId: string
83
- clientId: string
83
+ originId: string
84
84
  makeSyncDb: MakeSynchronousDatabase
85
85
  db: LeaderDatabase
86
86
  dbLog: LeaderDatabase
87
87
  bootStatusQueue: Queue.Queue<BootStatus>
88
88
  // TODO we should find a more elegant way to handle cases which need this ref for their implementation
89
89
  shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
90
- shutdownChannel: ShutdownChannel
91
90
  mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
92
91
  // devtools: DevtoolsContext
93
92
  syncBackend: SyncBackend | undefined
94
- syncProcessor: LeaderSyncProcessor
93
+ syncProcessor: SyncProcessor
95
94
  connectedClientSessionPullQueues: PullQueueSet
96
- /** e.g. used for `store.__dev` APIs */
97
- extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
98
95
  }
99
96
  >() {}
100
97
 
@@ -104,28 +101,24 @@ export type InitialBlockingSyncContext = {
104
101
  }
105
102
 
106
103
  export type PullQueueItem = {
107
- payload: SyncState.PayloadUpstream
104
+ // mutationEvents: ReadonlyArray<MutationEvent.AnyEncoded>
105
+ // backendHead: number
106
+ payload: PayloadUpstream
107
+ // TODO move `remaining` into `PayloadUpstream`
108
108
  remaining: number
109
109
  }
110
110
 
111
- export interface LeaderSyncProcessor {
111
+ export interface SyncProcessor {
112
112
  push: (
113
113
  /** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
114
114
  batch: ReadonlyArray<MutationEvent.EncodedWithMeta>,
115
- options?: {
116
- /**
117
- * If true, the effect will only finish when the local push has been processed (i.e. succeeded or was rejected).
118
- * @default false
119
- */
120
- waitForProcessing?: boolean
121
- },
122
- ) => Effect.Effect<void, InvalidPushError>
115
+ ) => Effect.Effect<void, UnexpectedError | InvalidPushError, HttpClient.HttpClient | LeaderThreadCtx>
123
116
 
124
117
  pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
125
118
  boot: (args: {
126
119
  dbReady: Deferred.Deferred<void>
127
120
  }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
128
- syncState: Subscribable.Subscribable<SyncState.SyncState>
121
+ syncState: Effect.Effect<SyncState, UnexpectedError>
129
122
  }
130
123
 
131
124
  export interface PullQueueSet {
package/src/mutation.ts CHANGED
@@ -8,19 +8,10 @@ import { prepareBindValues } from './util.js'
8
8
 
9
9
  export const getExecArgsFromMutation = ({
10
10
  mutationDef,
11
- mutationEvent,
11
+ mutationEventDecoded,
12
12
  }: {
13
13
  mutationDef: MutationDef.Any
14
- /** Both encoded and decoded mutation events are supported to reduce the number of times we need to decode/encode */
15
- mutationEvent:
16
- | {
17
- decoded: MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded
18
- encoded: undefined
19
- }
20
- | {
21
- decoded: undefined
22
- encoded: MutationEvent.AnyEncoded | MutationEvent.PartialAnyEncoded
23
- }
14
+ mutationEventDecoded: MutationEvent.Any | MutationEvent.PartialAny
24
15
  }): ReadonlyArray<{
25
16
  statementSql: string
26
17
  bindValues: PreparedBindValues
@@ -32,9 +23,7 @@ export const getExecArgsFromMutation = ({
32
23
 
33
24
  switch (typeof mutationDef.sql) {
34
25
  case 'function': {
35
- const mutationArgsDecoded =
36
- mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
37
- const res = mutationDef.sql(mutationArgsDecoded)
26
+ const res = mutationDef.sql(mutationEventDecoded.args)
38
27
  statementRes = Array.isArray(res) ? res : [res]
39
28
  break
40
29
  }
@@ -51,9 +40,10 @@ export const getExecArgsFromMutation = ({
51
40
  return statementRes.map((statementRes) => {
52
41
  const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
53
42
 
54
- const mutationArgsEncoded =
55
- mutationEvent.encoded?.args ?? Schema.encodeUnknownSync(mutationDef.schema)(mutationEvent.decoded!.args)
56
- const bindValues = typeof statementRes === 'string' ? mutationArgsEncoded : statementRes.bindValues
43
+ const bindValues =
44
+ typeof statementRes === 'string'
45
+ ? Schema.encodeUnknownSync(mutationDef.schema)(mutationEventDecoded.args)
46
+ : statementRes.bindValues
57
47
 
58
48
  const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
59
49
 
@@ -1,8 +1,8 @@
1
- import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
1
+ import { isDevEnv, memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
2
  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
- import { makeApplyMutation } from './leader-thread/apply-mutation.js'
5
+ import { getExecArgsFromMutation } from './mutation.js'
6
6
  import type { LiveStoreSchema, MutationDef, MutationEvent, MutationLogMetaRow } from './schema/mod.js'
7
7
  import { EventId, MUTATION_LOG_META_TABLE } from './schema/mod.js'
8
8
  import type { PreparedBindValues } from './util.js'
@@ -10,8 +10,7 @@ import { sql } from './util.js'
10
10
 
11
11
  export const rehydrateFromMutationLog = ({
12
12
  logDb,
13
- // TODO re-use this db when bringing back the boot in-memory db implementation
14
- // db,
13
+ db,
15
14
  schema,
16
15
  migrationOptions,
17
16
  onProgress,
@@ -29,8 +28,6 @@ export const rehydrateFromMutationLog = ({
29
28
 
30
29
  const hashMutation = memoizeByRef((mutation: MutationDef.Any) => Schema.hash(mutation.schema))
31
30
 
32
- const applyMutation = yield* makeApplyMutation
33
-
34
31
  const processMutation = (row: MutationLogMetaRow) =>
35
32
  Effect.gen(function* () {
36
33
  const mutationDef = schema.mutations.get(row.mutation) ?? shouldNeverHappen(`Unknown mutation ${row.mutation}`)
@@ -43,10 +40,7 @@ export const rehydrateFromMutationLog = ({
43
40
  )
44
41
  }
45
42
 
46
- const args = JSON.parse(row.argsJson)
47
-
48
- // Checking whether the schema has changed in an incompatible way
49
- yield* Schema.decodeUnknown(mutationDef.schema)(args).pipe(
43
+ const argsDecoded = yield* Schema.decodeUnknown(Schema.parseJson(mutationDef.schema))(row.argsJson).pipe(
50
44
  Effect.mapError((cause) =>
51
45
  UnexpectedError.make({
52
46
  cause,
@@ -59,14 +53,28 @@ This likely means the schema has changed in an incompatible way.
59
53
  ),
60
54
  )
61
55
 
62
- const mutationEventEncoded = {
56
+ const mutationEventDecoded = {
63
57
  id: { global: row.idGlobal, local: row.idLocal },
64
58
  parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
65
59
  mutation: row.mutation,
66
- args,
67
- } satisfies MutationEvent.AnyEncoded
68
-
69
- yield* applyMutation(mutationEventEncoded, { skipMutationLog: true })
60
+ args: argsDecoded,
61
+ } satisfies MutationEvent.Any
62
+
63
+ const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
64
+
65
+ const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
66
+ onRowsChanged: (rowsChanged: number) => {
67
+ if (rowsChanged === 0 && migrationOptions.logging?.excludeAffectedRows?.(statementSql) !== true) {
68
+ console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
69
+ }
70
+ },
71
+ })
72
+
73
+ for (const { statementSql, bindValues } of execArgsArr) {
74
+ // TODO cache prepared statements for mutations
75
+ db.execute(statementSql, bindValues, isDevEnv() ? makeExecuteOptions(statementSql, bindValues) : undefined)
76
+ // console.log(`Re-executed mutation ${mutationSql}`, bindValues)
77
+ }
70
78
  }).pipe(Effect.withSpan(`@livestore/common:rehydrateFromMutationLog:processMutation`))
71
79
 
72
80
  const CHUNK_SIZE = 100
@@ -1,14 +1,4 @@
1
- import { Brand, Schema } from '@livestore/utils/effect'
2
-
3
- export type LocalEventId = Brand.Branded<number, 'LocalEventId'>
4
- export const localEventId = Brand.nominal<LocalEventId>()
5
- export const LocalEventId = Schema.fromBrand(localEventId)(Schema.Int)
6
-
7
- export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
8
- export const globalEventId = Brand.nominal<GlobalEventId>()
9
- export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
10
-
11
- export const localDefault = 0 as any as LocalEventId
1
+ import { Schema } from '@livestore/utils/effect'
12
2
 
13
3
  /**
14
4
  * LiveStore event id value consisting of a globally unique event sequence number
@@ -16,11 +6,11 @@ export const localDefault = 0 as any as LocalEventId
16
6
  *
17
7
  * The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
18
8
  */
19
- export type EventId = { global: GlobalEventId; local: LocalEventId }
9
+ export type EventId = { global: number; local: number }
20
10
 
21
11
  export const EventId = Schema.Struct({
22
- global: GlobalEventId,
23
- local: LocalEventId,
12
+ global: Schema.Number,
13
+ local: Schema.Number,
24
14
  }).annotations({ title: 'LiveStore.EventId' })
25
15
 
26
16
  /**
@@ -37,24 +27,20 @@ export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.lo
37
27
 
38
28
  export type EventIdPair = { id: EventId; parentId: EventId }
39
29
 
40
- export const ROOT = { global: -1 as any as GlobalEventId, local: localDefault } satisfies EventId
30
+ export const ROOT = { global: -1, local: 0 } satisfies EventId
41
31
 
42
32
  export const isGreaterThan = (a: EventId, b: EventId) => {
43
33
  return a.global > b.global || (a.global === b.global && a.local > b.local)
44
34
  }
45
35
 
46
- export const make = (id: EventId | typeof EventId.Encoded): EventId => {
47
- return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
48
- }
49
-
50
- export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
36
+ export const nextPair = (id: EventId, isLocal: boolean) => {
51
37
  if (isLocal) {
52
- return { id: { global: id.global, local: (id.local + 1) as any as LocalEventId }, parentId: id }
38
+ return { id: { global: id.global, local: id.local + 1 }, parentId: id }
53
39
  }
54
40
 
55
41
  return {
56
- id: { global: (id.global + 1) as any as GlobalEventId, local: localDefault },
42
+ id: { global: id.global + 1, local: 0 },
57
43
  // NOTE we always point to `local: 0` for non-localOnly mutations
58
- parentId: { global: id.global, local: localDefault },
44
+ parentId: { global: id.global, local: 0 },
59
45
  }
60
46
  }