@livestore/common 0.3.0-dev.4 → 0.3.0-dev.5

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 (96) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +26 -23
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js.map +1 -1
  5. package/dist/devtools/devtools-bridge.d.ts +2 -1
  6. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  7. package/dist/devtools/devtools-messages.d.ts +90 -102
  8. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  9. package/dist/devtools/devtools-messages.js +9 -6
  10. package/dist/devtools/devtools-messages.js.map +1 -1
  11. package/dist/leader-thread/apply-mutation.js.map +1 -1
  12. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
  13. package/dist/leader-thread/leader-sync-processor.js +10 -7
  14. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  15. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  16. package/dist/leader-thread/leader-worker-devtools.js +22 -66
  17. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  18. package/dist/leader-thread/make-leader-thread-layer.d.ts +4 -2
  19. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  20. package/dist/leader-thread/make-leader-thread-layer.js +5 -2
  21. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  22. package/dist/leader-thread/mutationlog.d.ts +4 -17
  23. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  24. package/dist/leader-thread/mutationlog.js +2 -1
  25. package/dist/leader-thread/mutationlog.js.map +1 -1
  26. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  27. package/dist/leader-thread/types.d.ts +7 -5
  28. package/dist/leader-thread/types.d.ts.map +1 -1
  29. package/dist/leader-thread/types.js.map +1 -1
  30. package/dist/mutation.d.ts +1 -1
  31. package/dist/mutation.d.ts.map +1 -1
  32. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  33. package/dist/schema/EventId.d.ts +16 -14
  34. package/dist/schema/EventId.d.ts.map +1 -1
  35. package/dist/schema/EventId.js +15 -7
  36. package/dist/schema/EventId.js.map +1 -1
  37. package/dist/schema/MutationEvent.d.ts +51 -76
  38. package/dist/schema/MutationEvent.d.ts.map +1 -1
  39. package/dist/schema/MutationEvent.js +29 -13
  40. package/dist/schema/MutationEvent.js.map +1 -1
  41. package/dist/schema/system-tables.d.ts +17 -17
  42. package/dist/schema/system-tables.d.ts.map +1 -1
  43. package/dist/schema/system-tables.js +14 -7
  44. package/dist/schema/system-tables.js.map +1 -1
  45. package/dist/schema-management/migrations.js +6 -6
  46. package/dist/schema-management/migrations.js.map +1 -1
  47. package/dist/sync/client-session-sync-processor.d.ts +2 -2
  48. package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
  49. package/dist/sync/next/history-dag-common.d.ts +1 -4
  50. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  51. package/dist/sync/next/history-dag-common.js +1 -1
  52. package/dist/sync/next/history-dag-common.js.map +1 -1
  53. package/dist/sync/next/rebase-events.d.ts +1 -1
  54. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  55. package/dist/sync/next/rebase-events.js +3 -2
  56. package/dist/sync/next/rebase-events.js.map +1 -1
  57. package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
  58. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  59. package/dist/sync/next/test/mutation-fixtures.js +3 -9
  60. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  61. package/dist/sync/sync.d.ts +6 -6
  62. package/dist/sync/sync.d.ts.map +1 -1
  63. package/dist/sync/sync.js.map +1 -1
  64. package/dist/sync/syncstate.d.ts +10 -10
  65. package/dist/sync/syncstate.test.js +2 -6
  66. package/dist/sync/syncstate.test.js.map +1 -1
  67. package/dist/sync/validate-push-payload.d.ts +2 -2
  68. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  69. package/dist/sync/validate-push-payload.js +2 -2
  70. package/dist/sync/validate-push-payload.js.map +1 -1
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/package.json +3 -3
  74. package/src/adapter-types.ts +22 -24
  75. package/src/devtools/devtools-bridge.ts +2 -1
  76. package/src/devtools/devtools-messages.ts +9 -6
  77. package/src/leader-thread/apply-mutation.ts +1 -1
  78. package/src/leader-thread/leader-sync-processor.ts +14 -10
  79. package/src/leader-thread/leader-worker-devtools.ts +30 -109
  80. package/src/leader-thread/make-leader-thread-layer.ts +15 -5
  81. package/src/leader-thread/mutationlog.ts +9 -5
  82. package/src/leader-thread/types.ts +7 -8
  83. package/src/mutation.ts +1 -1
  84. package/src/rehydrate-from-mutationlog.ts +1 -1
  85. package/src/schema/EventId.ts +23 -9
  86. package/src/schema/MutationEvent.ts +37 -18
  87. package/src/schema/system-tables.ts +14 -7
  88. package/src/schema-management/migrations.ts +6 -6
  89. package/src/sync/client-session-sync-processor.ts +2 -2
  90. package/src/sync/next/history-dag-common.ts +1 -1
  91. package/src/sync/next/rebase-events.ts +5 -5
  92. package/src/sync/next/test/mutation-fixtures.ts +3 -10
  93. package/src/sync/sync.ts +4 -2
  94. package/src/sync/syncstate.test.ts +4 -4
  95. package/src/sync/validate-push-payload.ts +7 -4
  96. package/src/version.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Effect } from '@livestore/utils/effect';
2
- import type { MutationEvent } from '../schema/mod.js';
2
+ import type { EventId, MutationEvent } from '../schema/mod.js';
3
3
  import { InvalidPushError } from './sync.js';
4
- export declare const validatePushPayload: (batch: ReadonlyArray<MutationEvent.AnyEncoded>, currentEventId: number) => Effect.Effect<undefined, InvalidPushError, never>;
4
+ export declare const validatePushPayload: (batch: ReadonlyArray<MutationEvent.AnyEncodedGlobal>, currentEventId: EventId.GlobalEventId) => Effect.Effect<undefined, InvalidPushError, never>;
5
5
  //# sourceMappingURL=validate-push-payload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-push-payload.d.ts","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C,eAAO,MAAM,mBAAmB,UAAW,aAAa,CAAC,aAAa,CAAC,UAAU,CAAC,kBAAkB,MAAM,sDAWtG,CAAA"}
1
+ {"version":3,"file":"validate-push-payload.d.ts","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C,eAAO,MAAM,mBAAmB,UACvB,aAAa,CAAC,aAAa,CAAC,gBAAgB,CAAC,kBACpC,OAAO,CAAC,aAAa,sDAYnC,CAAA"}
@@ -2,12 +2,12 @@ import { Effect } from '@livestore/utils/effect';
2
2
  import { InvalidPushError } from './sync.js';
3
3
  // TODO proper batch validation
4
4
  export const validatePushPayload = (batch, currentEventId) => Effect.gen(function* () {
5
- if (batch[0].id.global <= currentEventId) {
5
+ if (batch[0].id <= currentEventId) {
6
6
  return yield* InvalidPushError.make({
7
7
  reason: {
8
8
  _tag: 'ServerAhead',
9
9
  minimumExpectedId: currentEventId + 1,
10
- providedId: batch[0].id.global,
10
+ providedId: batch[0].id,
11
11
  },
12
12
  });
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-push-payload.js","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,+BAA+B;AAC/B,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAA8C,EAAE,cAAsB,EAAE,EAAE,CAC5G,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,MAAM,IAAI,cAAc,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAClC,MAAM,EAAE;gBACN,IAAI,EAAE,aAAa;gBACnB,iBAAiB,EAAE,cAAc,GAAG,CAAC;gBACrC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,MAAM;aAChC;SACF,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"validate-push-payload.js","sourceRoot":"","sources":["../../src/sync/validate-push-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,+BAA+B;AAC/B,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,KAAoD,EACpD,cAAqC,EACrC,EAAE,CACF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,IAAI,cAAc,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAClC,MAAM,EAAE;gBACN,IAAI,EAAE,aAAa;gBACnB,iBAAiB,EAAE,cAAc,GAAG,CAAC;gBACrC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE;aACzB;SACF,CAAC,CAAA;IACJ,CAAC;AACH,CAAC,CAAC,CAAA"}
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const liveStoreVersion: "0.3.0-dev.3";
1
+ export declare const liveStoreVersion: "0.3.0-dev.4";
2
2
  /**
3
3
  * This version number is incremented whenever the internal storage format changes in a breaking way.
4
4
  * Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // TODO bring back when Expo and Playwright supports `with` imports
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
- export const liveStoreVersion = '0.3.0-dev.3';
4
+ export const liveStoreVersion = '0.3.0-dev.4';
5
5
  /**
6
6
  * This version number is incremented whenever the internal storage format changes in a breaking way.
7
7
  * Whenever this version changes, LiveStore will start with fresh database files. Old database files are not deleted.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livestore/common",
3
- "version": "0.3.0-dev.4",
3
+ "version": "0.3.0-dev.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -53,8 +53,8 @@
53
53
  "graphology": "0.26.0-alpha1",
54
54
  "graphology-dag": "0.4.1",
55
55
  "graphology-types": "0.24.7",
56
- "@livestore/utils": "0.3.0-dev.4",
57
- "@livestore/db-schema": "0.3.0-dev.4"
56
+ "@livestore/utils": "0.3.0-dev.5",
57
+ "@livestore/db-schema": "0.3.0-dev.5"
58
58
  },
59
59
  "devDependencies": {
60
60
  "vitest": "^2.1.4"
@@ -29,8 +29,28 @@ export type SynchronousDatabaseChangeset = {
29
29
  export type ClientSession = {
30
30
  /** SQLite database with synchronous API running in the same thread (usually in-memory) */
31
31
  syncDb: SynchronousDatabase
32
- /** The coordinator is responsible for persisting the database, syncing etc */
33
- coordinator: Coordinator
32
+ devtools: { enabled: boolean }
33
+ clientId: string
34
+ sessionId: string
35
+ /** Status info whether current session is leader or not */
36
+ lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
37
+ shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
38
+ /** A proxy API to communicate with the leader thread */
39
+ leaderThread: ClientSessionLeaderThreadProxy
40
+ }
41
+
42
+ export type ClientSessionLeaderThreadProxy = {
43
+ mutations: {
44
+ pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
45
+ push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
46
+ initialMutationEventId: EventId
47
+ }
48
+ export: Effect.Effect<Uint8Array, UnexpectedError>
49
+ getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
50
+ getSyncState: Effect.Effect<SyncState, UnexpectedError>
51
+ networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
52
+ /** For debugging purposes it can be useful to manually trigger devtools messages (e.g. to reset the database) */
53
+ sendDevtoolsMessage: (message: Devtools.MessageToAppLeader) => Effect.Effect<void, UnexpectedError>
34
54
  }
35
55
 
36
56
  export type SynchronousDatabase<TReq = any, TMetadata extends TReq = TReq> = {
@@ -103,28 +123,6 @@ export const BootStatus = Schema.Union(
103
123
 
104
124
  export type BootStatus = typeof BootStatus.Type
105
125
 
106
- // TODO refactor `Coordinator` to embrace more of the "leader semantics"
107
- export type Coordinator = {
108
- devtools: {
109
- enabled: boolean
110
- // TODO incorporate sessionId and rethink appHostId
111
- appHostId: string
112
- }
113
- sessionId: string
114
- // TODO is exposing the lock status really needed (or only relevant for web adapter?)
115
- lockStatus: SubscriptionRef.SubscriptionRef<LockStatus>
116
- mutations: {
117
- pull: Stream.Stream<{ payload: PayloadUpstream; remaining: number }, UnexpectedError>
118
- push(batch: ReadonlyArray<MutationEvent.AnyEncoded>): Effect.Effect<void, UnexpectedError | InvalidPushError>
119
- initialMutationEventId: EventId
120
- }
121
- export: Effect.Effect<Uint8Array, UnexpectedError>
122
- getMutationLogData: Effect.Effect<Uint8Array, UnexpectedError>
123
- getLeaderSyncState: Effect.Effect<SyncState, UnexpectedError>
124
- networkStatus: SubscriptionRef.SubscriptionRef<NetworkStatus>
125
- shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => Effect.Effect<void>
126
- }
127
-
128
126
  /**
129
127
  * Can be used in queries to refer to the current session id.
130
128
  * Will be replaced with the actual session id at runtime
@@ -6,7 +6,8 @@ export type PrepareDevtoolsBridge = {
6
6
  /** Messages coming from the app host (usually responses to requests) */
7
7
  responsePubSub: PubSub.PubSub<Devtools.MessageFromAppLeader | Devtools.MessageFromAppClientSession>
8
8
  sendToAppHost: (msg: Devtools.MessageToAppLeader | Devtools.MessageToAppClientSession) => Effect.Effect<void>
9
- appHostId: string
9
+ clientId: string
10
+ sessionId: string
10
11
  copyToClipboard: (text: string) => Effect.Effect<void>
11
12
  sendEscapeKey?: Effect.Effect<void>
12
13
  isLeader: boolean
@@ -7,7 +7,8 @@ import { PreparedBindValues } from '../util.js'
7
7
  import { liveStoreVersion as pkgVersion } from '../version.js'
8
8
 
9
9
  const requestId = Schema.String
10
- const appHostId = Schema.String
10
+ const clientId = Schema.String
11
+ const sessionId = Schema.String
11
12
  const liveStoreVersion = Schema.Literal(pkgVersion)
12
13
 
13
14
  const LSDMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
@@ -23,13 +24,15 @@ const LSDChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fiel
23
24
 
24
25
  const LSDStoreChannelMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
25
26
  LSDMessage(tag, {
26
- appHostId,
27
+ clientId,
28
+ sessionId,
27
29
  ...fields,
28
30
  })
29
31
 
30
32
  const LSDStoreReqResMessage = <Tag extends string, Fields extends Schema.Struct.Fields>(tag: Tag, fields: Fields) =>
31
33
  LSDMessage(tag, {
32
- appHostId,
34
+ clientId,
35
+ sessionId,
33
36
  requestId,
34
37
  ...fields,
35
38
  })
@@ -82,12 +85,12 @@ export class DebugInfoRerunQueryRes extends LSDStoreReqResMessage('LSD.DebugInfo
82
85
 
83
86
  // TODO refactor this to use push/pull semantics
84
87
  export class MutationBroadcast extends LSDMessage('LSD.Leader.MutationBroadcast', {
85
- mutationEventEncoded: MutationEvent.EncodedAny,
88
+ mutationEventEncoded: MutationEvent.AnyEncoded,
86
89
  }) {}
87
90
 
88
91
  // TODO refactor this to use push/pull semantics
89
92
  export class RunMutationReq extends LSDReqResMessage('LSD.Leader.RunMutationReq', {
90
- mutationEventEncoded: MutationEvent.EncodedAny.pipe(Schema.omit('id', 'parentId')),
93
+ mutationEventEncoded: MutationEvent.AnyEncoded.pipe(Schema.omit('id', 'parentId')),
91
94
  }) {}
92
95
 
93
96
  export class RunMutationRes extends LSDReqResMessage('LSD.Leader.RunMutationRes', {}) {}
@@ -167,7 +170,7 @@ export class SyncingInfoRes extends LSDReqResMessage('LSD.Leader.SyncingInfoRes'
167
170
  export class SyncHistorySubscribe extends LSDReqResMessage('LSD.Leader.SyncHistorySubscribe', {}) {}
168
171
  export class SyncHistoryUnsubscribe extends LSDReqResMessage('LSD.Leader.SyncHistoryUnsubscribe', {}) {}
169
172
  export class SyncHistoryRes extends LSDReqResMessage('LSD.Leader.SyncHistoryRes', {
170
- mutationEventEncoded: MutationEvent.EncodedAny,
173
+ mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
171
174
  metadata: Schema.Option(Schema.JsonValue),
172
175
  }) {}
173
176
 
@@ -132,7 +132,7 @@ const makeShouldExcludeMutationFromLog = memoizeByRef((schema: LiveStoreSchema)
132
132
  ? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
133
133
  : new Set(['livestore.RawSql'])
134
134
 
135
- return (mutationName: string, mutationEventDecoded: MutationEvent.Any): boolean => {
135
+ return (mutationName: string, mutationEventDecoded: MutationEvent.AnyDecoded): boolean => {
136
136
  if (mutationLogExclude.has(mutationName)) return true
137
137
 
138
138
  const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
@@ -9,6 +9,7 @@ import {
9
9
  FiberHandle,
10
10
  Option,
11
11
  OtelTracer,
12
+ ReadonlyArray,
12
13
  Ref,
13
14
  Schema,
14
15
  Stream,
@@ -102,7 +103,7 @@ export const makeLeaderSyncProcessor = ({
102
103
  initialBlockingSyncContext: InitialBlockingSyncContext
103
104
  }): Effect.Effect<SyncProcessor, UnexpectedError, Scope.Scope> =>
104
105
  Effect.gen(function* () {
105
- const syncBackendQueue = yield* BucketQueue.make<MutationEvent.AnyEncoded>()
106
+ const syncBackendQueue = yield* BucketQueue.make<MutationEvent.EncodedWithMeta>()
106
107
 
107
108
  const stateRef = yield* Ref.make<ProcessorState>({ _tag: 'init' })
108
109
 
@@ -257,13 +258,16 @@ export const makeLeaderSyncProcessor = ({
257
258
  )
258
259
  }
259
260
 
260
- const pendingMutationEvents = yield* getMutationEventsSince({ global: initialBackendHead, local: 0 })
261
+ const pendingMutationEvents = yield* getMutationEventsSince({
262
+ global: initialBackendHead,
263
+ local: EventId.localDefault,
264
+ }).pipe(Effect.map(ReadonlyArray.map((_) => new MutationEvent.EncodedWithMeta(_))))
261
265
 
262
266
  const initialSyncState = {
263
- pending: pendingMutationEvents.map((_) => new MutationEvent.EncodedWithMeta(_)),
267
+ pending: pendingMutationEvents,
264
268
  // On the leader we don't need a rollback tail beyond `pending` items
265
269
  rollbackTail: [],
266
- upstreamHead: { global: initialBackendHead, local: 0 },
270
+ upstreamHead: { global: initialBackendHead, local: EventId.localDefault },
267
271
  localHead: initialLocalHead,
268
272
  } as SyncState.SyncState
269
273
 
@@ -410,7 +414,7 @@ const backgroundBackendPulling = ({
410
414
  initialBlockingSyncContext,
411
415
  }: {
412
416
  dbReady: Deferred.Deferred<void>
413
- initialBackendHead: number
417
+ initialBackendHead: EventId.GlobalEventId
414
418
  isLocalEvent: (mutationEventEncoded: MutationEvent.EncodedWithMeta) => boolean
415
419
  restartBackendPushing: (
416
420
  filteredRebasedPending: ReadonlyArray<MutationEvent.EncodedWithMeta>,
@@ -538,7 +542,7 @@ const backgroundBackendPulling = ({
538
542
  yield* SubscriptionRef.waitUntil(syncBackend.isConnected, (isConnected) => isConnected === true)
539
543
 
540
544
  yield* onNewPullChunk(
541
- batch.map((_) => new MutationEvent.EncodedWithMeta(_.mutationEventEncoded)),
545
+ batch.map((_) => MutationEvent.EncodedWithMeta.fromGlobal(_.mutationEventEncoded)),
542
546
  remaining,
543
547
  )
544
548
 
@@ -588,7 +592,7 @@ const rollback = ({
588
592
  }),
589
593
  )
590
594
 
591
- const getCursorInfo = (remoteHead: number) =>
595
+ const getCursorInfo = (remoteHead: EventId.GlobalEventId) =>
592
596
  Effect.gen(function* () {
593
597
  const { dbLog } = yield* LeaderThreadCtx
594
598
 
@@ -605,7 +609,7 @@ const getCursorInfo = (remoteHead: number) =>
605
609
  ).pipe(Effect.andThen(Schema.decode(MutationlogQuerySchema)), Effect.map(Option.flatten), Effect.orDie)
606
610
 
607
611
  return Option.some({
608
- cursor: { global: remoteHead, local: 0 },
612
+ cursor: { global: remoteHead, local: EventId.localDefault },
609
613
  metadata: syncMetadataOption,
610
614
  }) satisfies InitialSyncInfo
611
615
  }).pipe(Effect.withSpan('@livestore/common:leader-thread:syncing:getCursorInfo', { attributes: { remoteHead } }))
@@ -616,7 +620,7 @@ const backgroundBackendPushing = ({
616
620
  span,
617
621
  }: {
618
622
  dbReady: Deferred.Deferred<void>
619
- syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.AnyEncoded>
623
+ syncBackendQueue: BucketQueue.BucketQueue<MutationEvent.EncodedWithMeta>
620
624
  span: otel.Span | undefined
621
625
  }) =>
622
626
  Effect.gen(function* () {
@@ -639,7 +643,7 @@ const backgroundBackendPushing = ({
639
643
  })
640
644
 
641
645
  // TODO handle push errors (should only happen during concurrent pull+push)
642
- const pushResult = yield* syncBackend.push(queueItems).pipe(Effect.either)
646
+ const pushResult = yield* syncBackend.push(queueItems.map((_) => _.toGlobal())).pipe(Effect.either)
643
647
 
644
648
  if (pushResult._tag === 'Left') {
645
649
  span?.addEvent('backend-push-error', { error: pushResult.left.toString() })
@@ -1,18 +1,11 @@
1
- import { Effect, FiberMap, Option, PubSub, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
1
+ import { Effect, FiberMap, Option, 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'
6
5
  import type { DevtoolsOptions, PersistenceInfoPair } from './types.js'
7
6
  import { LeaderThreadCtx } from './types.js'
8
7
 
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>
8
+ type SendMessageToDevtools = (message: Devtools.MessageFromAppLeader) => Effect.Effect<void>
16
9
 
17
10
  // TODO bind scope to the webchannel lifetime
18
11
  export const bootDevtools = (options: DevtoolsOptions) =>
@@ -21,44 +14,24 @@ export const bootDevtools = (options: DevtoolsOptions) =>
21
14
  return
22
15
  }
23
16
 
24
- const { persistenceInfo, shutdownChannel, devtoolsWebChannel } = yield* options.makeContext
17
+ const { connectedClientSessionPullQueues, syncProcessor, extraIncomingMessagesQueue } = yield* LeaderThreadCtx
25
18
 
26
- const isConnected = yield* SubscriptionRef.make(true)
27
-
28
- const incomingMessagesPubSub = yield* PubSub.unbounded<Devtools.MessageToAppLeader>().pipe(
29
- Effect.acquireRelease(PubSub.shutdown),
30
- )
19
+ yield* listenToDevtools({
20
+ incomingMessages: Stream.fromQueue(extraIncomingMessagesQueue),
21
+ sendMessage: () => Effect.void,
22
+ }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
31
23
 
32
- const incomingMessages = Stream.fromPubSub(incomingMessagesPubSub)
24
+ const { persistenceInfo, devtoolsWebChannel } = yield* options.makeContext
33
25
 
34
- const outgoingMessagesQueue = yield* Queue.unbounded<Devtools.MessageFromAppLeader>().pipe(
35
- Effect.acquireRelease(Queue.shutdown),
36
- )
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
+ )
37
34
 
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
62
35
  const { localHead } = yield* syncProcessor.syncState
63
36
 
64
37
  // TODO close queue when devtools disconnects
@@ -69,13 +42,8 @@ export const bootDevtools = (options: DevtoolsOptions) =>
69
42
  Effect.gen(function* () {
70
43
  if (msg.payload._tag === 'upstream-advance') {
71
44
  for (const mutationEventEncoded of msg.payload.newEvents) {
72
- yield* sendMessage(
73
- Devtools.MutationBroadcast.make({
74
- mutationEventEncoded,
75
-
76
- liveStoreVersion,
77
- }),
78
- )
45
+ // TODO refactor with push semantics
46
+ yield* sendMessage(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
79
47
  }
80
48
  } else {
81
49
  yield* Effect.logWarning('TODO implement rebases in devtools')
@@ -86,68 +54,25 @@ export const bootDevtools = (options: DevtoolsOptions) =>
86
54
  Effect.forkScoped,
87
55
  )
88
56
 
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
-
114
57
  yield* listenToDevtools({
115
- incomingMessages,
58
+ incomingMessages: devtoolsWebChannel.listen.pipe(Stream.flatten(), Stream.orDie),
116
59
  sendMessage,
117
- // isConnected,
118
- // disconnect,
119
- // storeId,
120
- // appHostId,
121
- // isLeader,
122
60
  persistenceInfo,
123
- shutdownChannel,
124
- })
61
+ }).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
125
62
  }).pipe(Effect.withSpan('@livestore/common:leader-thread:devtools:boot'))
126
63
 
127
64
  const listenToDevtools = ({
128
65
  incomingMessages,
129
66
  sendMessage,
130
- // isConnected,
131
- // disconnect,
132
- // appHostId,
133
- // storeId,
134
- // isLeader,
135
67
  persistenceInfo,
136
- shutdownChannel,
137
68
  }: {
138
69
  incomingMessages: Stream.Stream<Devtools.MessageToAppLeader>
139
70
  sendMessage: SendMessageToDevtools
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
71
+ persistenceInfo?: PersistenceInfoPair
147
72
  }) =>
148
73
  Effect.gen(function* () {
149
- const innerWorkerCtx = yield* LeaderThreadCtx
150
- const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, syncProcessor } = innerWorkerCtx
74
+ const { syncBackend, makeSyncDb, db, dbLog, shutdownStateSubRef, shutdownChannel, syncProcessor } =
75
+ yield* LeaderThreadCtx
151
76
 
152
77
  type RequestId = string
153
78
  const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
@@ -158,15 +83,6 @@ const listenToDevtools = ({
158
83
  // yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
159
84
 
160
85
  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
-
170
86
  return
171
87
  }
172
88
 
@@ -226,7 +142,7 @@ const listenToDevtools = ({
226
142
 
227
143
  yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
228
144
 
229
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
145
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
230
146
 
231
147
  return
232
148
  }
@@ -243,11 +159,16 @@ const listenToDevtools = ({
243
159
 
244
160
  yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
245
161
 
246
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
162
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
247
163
 
248
164
  return
249
165
  }
250
166
  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
+
251
172
  const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
252
173
  const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
253
174
  const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
@@ -1,8 +1,9 @@
1
- import type { HttpClient, Scope, WebChannel } from '@livestore/utils/effect'
2
- import { Deferred, Effect, FiberSet, Layer, Queue, SubscriptionRef } from '@livestore/utils/effect'
1
+ import type { HttpClient, Scope } from '@livestore/utils/effect'
2
+ import { Deferred, Effect, 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'
6
7
  import type { LiveStoreSchema } from '../schema/mod.js'
7
8
  import { EventId, MutationEvent, mutationLogMetaTable, SYNC_STATUS_TABLE, syncStatusTable } from '../schema/mod.js'
8
9
  import { migrateTable } from '../schema-management/migrations.js'
@@ -13,22 +14,24 @@ import { makeLeaderSyncProcessor } from './leader-sync-processor.js'
13
14
  import { bootDevtools } from './leader-worker-devtools.js'
14
15
  import { makePullQueueSet } from './pull-queue-set.js'
15
16
  import { recreateDb } from './recreate-db.js'
17
+ import type { ShutdownChannel } from './shutdown-channel.js'
16
18
  import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
17
19
  import { LeaderThreadCtx } from './types.js'
18
20
 
19
21
  export const makeLeaderThreadLayer = ({
20
22
  schema,
21
23
  storeId,
22
- originId,
24
+ clientId,
23
25
  makeSyncDb,
24
26
  makeSyncBackend,
25
27
  db,
26
28
  dbLog,
27
29
  devtoolsOptions,
28
30
  initialSyncOptions = { _tag: 'Skip' },
31
+ shutdownChannel,
29
32
  }: {
30
33
  storeId: string
31
- originId: string
34
+ clientId: string
32
35
  schema: LiveStoreSchema
33
36
  makeSyncDb: MakeSynchronousDatabase
34
37
  makeSyncBackend: Effect.Effect<SyncBackend, UnexpectedError, Scope.Scope> | undefined
@@ -36,6 +39,7 @@ export const makeLeaderThreadLayer = ({
36
39
  dbLog: SynchronousDatabase
37
40
  devtoolsOptions: DevtoolsOptions
38
41
  initialSyncOptions: InitialSyncOptions | undefined
42
+ shutdownChannel: ShutdownChannel
39
43
  }): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
40
44
  Effect.gen(function* () {
41
45
  const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
@@ -50,19 +54,25 @@ export const makeLeaderThreadLayer = ({
50
54
 
51
55
  const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
52
56
 
57
+ const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
58
+ Effect.acquireRelease(Queue.shutdown),
59
+ )
60
+
53
61
  const ctx = {
54
62
  schema,
55
63
  bootStatusQueue,
56
64
  storeId,
57
- originId,
65
+ clientId,
58
66
  db,
59
67
  dbLog,
60
68
  makeSyncDb,
61
69
  mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
62
70
  shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
71
+ shutdownChannel,
63
72
  syncBackend,
64
73
  syncProcessor,
65
74
  connectedClientSessionPullQueues: yield* makePullQueueSet,
75
+ extraIncomingMessagesQueue,
66
76
  } satisfies typeof LeaderThreadCtx.Service
67
77
 
68
78
  // @ts-expect-error For debugging purposes
@@ -2,11 +2,14 @@ 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'
5
6
  import { MUTATION_LOG_META_TABLE, mutationLogMetaTable, SYNC_STATUS_TABLE } from '../schema/system-tables.js'
6
7
  import { prepareBindValues, sql } from '../util.js'
7
8
  import { LeaderThreadCtx } from './types.js'
8
9
 
9
- export const getMutationEventsSince = (since: EventId.EventId) =>
10
+ export const getMutationEventsSince = (
11
+ since: EventId.EventId,
12
+ ): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
10
13
  Effect.gen(function* () {
11
14
  const { dbLog } = yield* LeaderThreadCtx
12
15
 
@@ -26,16 +29,17 @@ export const getMutationEventsSince = (since: EventId.EventId) =>
26
29
  .filter((_) => EventId.compare(_.id, since) > 0)
27
30
  })
28
31
 
29
- export const getLocalHeadFromDb = (dbLog: SynchronousDatabase) => {
30
- const res = dbLog.select<{ idGlobal: number; idLocal: number }>(
32
+ export const getLocalHeadFromDb = (dbLog: SynchronousDatabase): EventId.EventId => {
33
+ const res = dbLog.select<{ idGlobal: EventId.GlobalEventId; idLocal: EventId.LocalEventId }>(
31
34
  sql`select idGlobal, idLocal from ${MUTATION_LOG_META_TABLE} order by idGlobal DESC, idLocal DESC limit 1`,
32
35
  )[0]
33
36
 
34
37
  return res ? { global: res.idGlobal, local: res.idLocal } : EventId.ROOT
35
38
  }
36
39
 
37
- export const getBackendHeadFromDb = (dbLog: SynchronousDatabase) =>
38
- dbLog.select<{ head: number }>(sql`select head from ${SYNC_STATUS_TABLE}`)[0]?.head ?? EventId.ROOT.global
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
39
43
 
40
44
  // TODO use prepared statements
41
45
  export const updateBackendHead = (dbLog: SynchronousDatabase, head: EventId.EventId) =>