@livestore/common 0.3.0-dev.11 → 0.3.0-dev.12

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 (88) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +41 -18
  3. package/dist/adapter-types.d.ts.map +1 -1
  4. package/dist/adapter-types.js +12 -0
  5. package/dist/adapter-types.js.map +1 -1
  6. package/dist/devtools/devtools-bridge.d.ts +10 -7
  7. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  8. package/dist/devtools/devtools-messages-client-session.d.ts +101 -28
  9. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -1
  10. package/dist/devtools/devtools-messages-client-session.js +19 -3
  11. package/dist/devtools/devtools-messages-client-session.js.map +1 -1
  12. package/dist/devtools/devtools-messages-common.d.ts +24 -32
  13. package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
  14. package/dist/devtools/devtools-messages-common.js +19 -10
  15. package/dist/devtools/devtools-messages-common.js.map +1 -1
  16. package/dist/devtools/devtools-messages-leader.d.ts +210 -34
  17. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
  18. package/dist/devtools/devtools-messages-leader.js +53 -6
  19. package/dist/devtools/devtools-messages-leader.js.map +1 -1
  20. package/dist/devtools/devtools-messages.d.ts +2 -2
  21. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  22. package/dist/devtools/devtools-messages.js +2 -2
  23. package/dist/devtools/devtools-messages.js.map +1 -1
  24. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  25. package/dist/leader-thread/LeaderSyncProcessor.js +26 -13
  26. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  27. package/dist/leader-thread/connection.d.ts +31 -3
  28. package/dist/leader-thread/connection.d.ts.map +1 -1
  29. package/dist/leader-thread/connection.js +18 -3
  30. package/dist/leader-thread/connection.js.map +1 -1
  31. package/dist/leader-thread/leader-worker-devtools.js +51 -20
  32. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  33. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  34. package/dist/leader-thread/make-leader-thread-layer.js +17 -5
  35. package/dist/leader-thread/make-leader-thread-layer.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 +4 -2
  38. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  39. package/dist/leader-thread/recreate-db.js +13 -8
  40. package/dist/leader-thread/recreate-db.js.map +1 -1
  41. package/dist/leader-thread/types.d.ts +15 -12
  42. package/dist/leader-thread/types.d.ts.map +1 -1
  43. package/dist/leader-thread/types.js +0 -2
  44. package/dist/leader-thread/types.js.map +1 -1
  45. package/dist/query-builder/api.d.ts +2 -2
  46. package/dist/query-builder/api.d.ts.map +1 -1
  47. package/dist/query-builder/impl.js.map +1 -1
  48. package/dist/query-builder/impl.test.js +16 -1
  49. package/dist/query-builder/impl.test.js.map +1 -1
  50. package/dist/query-info.d.ts +3 -3
  51. package/dist/query-info.d.ts.map +1 -1
  52. package/dist/schema/EventId.d.ts +1 -0
  53. package/dist/schema/EventId.d.ts.map +1 -1
  54. package/dist/schema/EventId.js +3 -0
  55. package/dist/schema/EventId.js.map +1 -1
  56. package/dist/schema/mutations.d.ts +1 -1
  57. package/dist/schema/system-tables.d.ts +1 -1
  58. package/dist/schema-management/migrations.d.ts +2 -2
  59. package/dist/schema-management/migrations.d.ts.map +1 -1
  60. package/dist/schema-management/migrations.js +6 -1
  61. package/dist/schema-management/migrations.js.map +1 -1
  62. package/dist/sync/ClientSessionSyncProcessor.d.ts +3 -5
  63. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  64. package/dist/sync/ClientSessionSyncProcessor.js +20 -9
  65. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +3 -3
  69. package/src/adapter-types.ts +35 -17
  70. package/src/devtools/devtools-bridge.ts +10 -7
  71. package/src/devtools/devtools-messages-client-session.ts +26 -10
  72. package/src/devtools/devtools-messages-common.ts +37 -8
  73. package/src/devtools/devtools-messages-leader.ts +73 -12
  74. package/src/devtools/devtools-messages.ts +2 -2
  75. package/src/leader-thread/LeaderSyncProcessor.ts +31 -16
  76. package/src/leader-thread/connection.ts +48 -3
  77. package/src/leader-thread/leader-worker-devtools.ts +80 -23
  78. package/src/leader-thread/make-leader-thread-layer.ts +20 -8
  79. package/src/leader-thread/recreate-db.ts +19 -10
  80. package/src/leader-thread/types.ts +16 -13
  81. package/src/query-builder/api.ts +3 -3
  82. package/src/query-builder/impl.test.ts +22 -1
  83. package/src/query-builder/impl.ts +2 -2
  84. package/src/query-info.ts +3 -3
  85. package/src/schema/EventId.ts +4 -0
  86. package/src/schema-management/migrations.ts +9 -5
  87. package/src/sync/ClientSessionSyncProcessor.ts +22 -11
  88. package/src/version.ts +1 -1
@@ -2,19 +2,20 @@ import { casesHandled } from '@livestore/utils'
2
2
  import type { HttpClient } from '@livestore/utils/effect'
3
3
  import { Effect, Queue } from '@livestore/utils/effect'
4
4
 
5
- import type { InvalidPullError, IsOfflineError, MigrationHooks, SqliteError } from '../index.js'
5
+ import type { InvalidPullError, IsOfflineError, MigrationHooks, MigrationsReport, SqliteError } from '../index.js'
6
6
  import { initializeSingletonTables, migrateDb, rehydrateFromMutationLog, UnexpectedError } from '../index.js'
7
7
  import { configureConnection } from './connection.js'
8
8
  import { LeaderThreadCtx } from './types.js'
9
9
 
10
10
  export const recreateDb: Effect.Effect<
11
- void,
11
+ { migrationsReport: MigrationsReport },
12
12
  UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
13
13
  LeaderThreadCtx | HttpClient.HttpClient
14
14
  > = Effect.gen(function* () {
15
15
  const { dbReadModel, dbMutationLog, schema, bootStatusQueue } = yield* LeaderThreadCtx
16
16
 
17
17
  const migrationOptions = schema.migrationOptions
18
+ let migrationsReport: MigrationsReport
18
19
 
19
20
  yield* Effect.addFinalizer(
20
21
  Effect.fn('recreateDb:finalizer')(function* (ex) {
@@ -27,13 +28,13 @@ export const recreateDb: Effect.Effect<
27
28
  // TODO bring back this optimization
28
29
  // const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
29
30
  const tmpDb = dbReadModel
30
- yield* configureConnection(tmpDb, { fkEnabled: true })
31
+ yield* configureConnection(tmpDb, { foreignKeys: true })
31
32
 
32
33
  const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
33
34
  Effect.gen(function* () {
34
35
  yield* Effect.tryAll(() => hooks?.init?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
35
36
 
36
- yield* migrateDb({
37
+ const migrationsReport = yield* migrateDb({
37
38
  db: tmpDb,
38
39
  schema,
39
40
  onProgress: ({ done, total }) =>
@@ -44,16 +45,18 @@ export const recreateDb: Effect.Effect<
44
45
 
45
46
  yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
46
47
 
47
- return tmpDb
48
+ return { migrationsReport, tmpDb }
48
49
  })
49
50
 
50
51
  switch (migrationOptions.strategy) {
51
52
  case 'from-mutation-log': {
52
53
  const hooks = migrationOptions.hooks
53
- const tmpDb = yield* initDb(hooks)
54
+ const initResult = yield* initDb(hooks)
55
+
56
+ migrationsReport = initResult.migrationsReport
54
57
 
55
58
  yield* rehydrateFromMutationLog({
56
- db: tmpDb,
59
+ db: initResult.tmpDb,
57
60
  logDb: dbMutationLog,
58
61
  schema,
59
62
  migrationOptions,
@@ -61,23 +64,27 @@ export const recreateDb: Effect.Effect<
61
64
  Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
62
65
  })
63
66
 
64
- yield* Effect.tryAll(() => hooks?.post?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
67
+ yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
65
68
 
66
69
  break
67
70
  }
68
71
  case 'hard-reset': {
69
72
  const hooks = migrationOptions.hooks
70
- const tmpInMemoryDb = yield* initDb(hooks)
73
+ const initResult = yield* initDb(hooks)
74
+
75
+ migrationsReport = initResult.migrationsReport
71
76
 
72
77
  // The database is migrated but empty now, so nothing else to do
73
78
 
74
- yield* Effect.tryAll(() => hooks?.post?.(tmpInMemoryDb)).pipe(UnexpectedError.mapToUnexpectedError)
79
+ yield* Effect.tryAll(() => hooks?.post?.(initResult.tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
75
80
 
76
81
  break
77
82
  }
78
83
  case 'manual': {
79
84
  const oldDbData = dbReadModel.export()
80
85
 
86
+ migrationsReport = { migrations: [] }
87
+
81
88
  const newDbData = yield* Effect.tryAll(() => migrationOptions.migrate(oldDbData)).pipe(
82
89
  UnexpectedError.mapToUnexpectedError,
83
90
  )
@@ -106,6 +113,8 @@ export const recreateDb: Effect.Effect<
106
113
 
107
114
  // TODO bring back
108
115
  // tmpDb.close()
116
+
117
+ return { migrationsReport }
109
118
  }).pipe(
110
119
  Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
111
120
  Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  Deferred,
3
3
  Effect,
4
- Fiber,
5
4
  HttpClient,
6
5
  Option,
7
6
  Queue,
@@ -17,6 +16,7 @@ import type {
17
16
  Devtools,
18
17
  InvalidPushError,
19
18
  MakeSqliteDb,
19
+ MigrationsReport,
20
20
  PersistenceInfo,
21
21
  SqliteDb,
22
22
  SyncBackend,
@@ -28,13 +28,6 @@ import type { ShutdownChannel } from './shutdown-channel.js'
28
28
 
29
29
  export type ShutdownState = 'running' | 'shutting-down'
30
30
 
31
- export class OuterWorkerCtx extends Context.Tag('OuterWorkerCtx')<
32
- OuterWorkerCtx,
33
- {
34
- innerFiber: Fiber.RuntimeFiber<any, any>
35
- }
36
- >() {}
37
-
38
31
  export const InitialSyncOptionsSkip = Schema.TaggedStruct('Skip', {})
39
32
  export type InitialSyncOptionsSkip = typeof InitialSyncOptionsSkip.Type
40
33
 
@@ -67,7 +60,7 @@ export type DevtoolsOptions =
67
60
  enabled: true
68
61
  makeBootContext: Effect.Effect<
69
62
  {
70
- devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
63
+ devtoolsWebChannel: WebChannel.WebChannel<Devtools.Leader.MessageToApp, Devtools.Leader.MessageFromApp>
71
64
  persistenceInfo: PersistenceInfoPair
72
65
  },
73
66
  UnexpectedError,
@@ -78,8 +71,10 @@ export type DevtoolsOptions =
78
71
  export type DevtoolsContext =
79
72
  | {
80
73
  enabled: true
81
- syncBackendPullLatch: Effect.Latch
82
- syncBackendPushLatch: Effect.Latch
74
+ // syncBackendPullLatch: Effect.Latch
75
+ // syncBackendPushLatch: Effect.Latch
76
+ syncBackendLatch: Effect.Latch
77
+ syncBackendLatchState: SubscriptionRef.SubscriptionRef<{ latchClosed: boolean }>
83
78
  }
84
79
  | {
85
80
  enabled: false
@@ -103,12 +98,16 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
103
98
  syncBackend: SyncBackend | undefined
104
99
  syncProcessor: LeaderSyncProcessor
105
100
  connectedClientSessionPullQueues: PullQueueSet
101
+ initialState: {
102
+ leaderHead: EventId.EventId
103
+ migrationsReport: MigrationsReport
104
+ }
106
105
  /**
107
106
  * e.g. used for `store._dev` APIs
108
107
  *
109
108
  * This is currently separated from `.devtools` as it also needs to work when devtools are disabled
110
109
  */
111
- extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
110
+ extraIncomingMessagesQueue: Queue.Queue<Devtools.Leader.MessageToApp>
112
111
  }
113
112
  >() {}
114
113
 
@@ -138,7 +137,11 @@ export interface LeaderSyncProcessor {
138
137
  pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
139
138
  boot: (args: {
140
139
  dbReady: Deferred.Deferred<void>
141
- }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
140
+ }) => Effect.Effect<
141
+ { initialLeaderHead: EventId.EventId },
142
+ UnexpectedError,
143
+ LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
144
+ >
142
145
  syncState: Subscribable.Subscribable<SyncState.SyncState>
143
146
  }
144
147
 
@@ -35,7 +35,7 @@ export namespace QueryBuilderAst {
35
35
  export type RowQuery = {
36
36
  readonly _tag: 'RowQuery'
37
37
  readonly tableDef: DbSchema.TableDefBase
38
- readonly id: string | SessionIdSymbol
38
+ readonly id: string | SessionIdSymbol | number
39
39
  readonly insertValues: Record<string, unknown>
40
40
  }
41
41
 
@@ -252,10 +252,10 @@ export namespace QueryBuilder {
252
252
  ? (_: 'Error: Need to enable deriveMutations to use row()') => any
253
253
  : TTableDef['options']['requiredInsertColumnNames'] extends never
254
254
  ? (
255
- id: string | SessionIdSymbol,
255
+ id: string | SessionIdSymbol | number,
256
256
  ) => QueryBuilder<RowQuery.Result<TTableDef>, TTableDef, QueryBuilder.ApiFeature, QueryInfo.Row>
257
257
  : <TOptions extends RowQuery.RequiredColumnsOptions<TTableDef>>(
258
- id: string | SessionIdSymbol,
258
+ id: string | SessionIdSymbol | number,
259
259
  opts: TOptions,
260
260
  ) => QueryBuilder<RowQuery.Result<TTableDef>, TTableDef, QueryBuilder.ApiFeature, QueryInfo.Row>
261
261
  }
@@ -18,13 +18,23 @@ const todos = DbSchema.table(
18
18
  { deriveMutations: true },
19
19
  )
20
20
 
21
+ const todosWithIntId = DbSchema.table(
22
+ 'todos_with_int_id',
23
+ {
24
+ id: DbSchema.integer({ primaryKey: true }),
25
+ text: DbSchema.text({ default: '', nullable: false }),
26
+ status: DbSchema.text({ schema: Schema.Literal('active', 'completed') }),
27
+ },
28
+ { deriveMutations: true },
29
+ )
30
+
21
31
  const comments = DbSchema.table('comments', {
22
32
  id: DbSchema.text({ primaryKey: true }),
23
33
  text: DbSchema.text({ default: '', nullable: false }),
24
34
  todoId: DbSchema.text({}),
25
35
  })
26
36
 
27
- const db = { todos: todos.query, comments: comments.query }
37
+ const db = { todos: todos.query, todosWithIntId: todosWithIntId.query, comments: comments.query }
28
38
 
29
39
  describe('query builder', () => {
30
40
  describe('result schema', () => {
@@ -204,6 +214,17 @@ describe('query builder', () => {
204
214
  }
205
215
  `)
206
216
  })
217
+
218
+ it('should handle row queries with numbers', () => {
219
+ expect(db.todosWithIntId.row(123, { insertValues: { status: 'active' } }).asSql()).toMatchInlineSnapshot(`
220
+ {
221
+ "bindValues": [
222
+ 123,
223
+ ],
224
+ "query": "SELECT * FROM 'todos_with_int_id' WHERE id = ?",
225
+ }
226
+ `)
227
+ })
207
228
  })
208
229
  })
209
230
 
@@ -128,12 +128,12 @@ export const makeQueryBuilder = <TResult, TTableDef extends DbSchema.TableDefBas
128
128
  // eslint-disable-next-line prefer-rest-params
129
129
  const params = [...arguments]
130
130
 
131
- let id: string
131
+ let id: string | number
132
132
 
133
133
  if (tableDef.options.isSingleton) {
134
134
  id = tableDef.sqliteDef.columns.id!.default.pipe(Option.getOrThrow)
135
135
  } else {
136
- id = params[0] as string
136
+ id = params[0] as string | number
137
137
  if (id === undefined) {
138
138
  invalidQueryBuilder(`Id missing for row query on non-singleton table ${tableDef.sqliteDef.name}`)
139
139
  }
package/src/query-info.ts CHANGED
@@ -24,20 +24,20 @@ export namespace QueryInfo {
24
24
  export type Row = {
25
25
  _tag: 'Row'
26
26
  table: DbSchema.TableDefBase
27
- id: string | SessionIdSymbol
27
+ id: string | SessionIdSymbol | number
28
28
  }
29
29
 
30
30
  export type Col = {
31
31
  _tag: 'Col'
32
32
  table: DbSchema.TableDefBase
33
- id: string | SessionIdSymbol
33
+ id: string | SessionIdSymbol | number
34
34
  column: string
35
35
  }
36
36
 
37
37
  export type ColJsonValue = {
38
38
  _tag: 'ColJsonValue'
39
39
  table: DbSchema.TableDefBase
40
- id: string | SessionIdSymbol
40
+ id: string | SessionIdSymbol | number
41
41
  column: string
42
42
  /**
43
43
  * example: `$.tabs[3].items[2]` (`$` referring to the column value)
@@ -43,6 +43,10 @@ export const isGreaterThan = (a: EventId, b: EventId) => {
43
43
  return a.global > b.global || (a.global === b.global && a.local > b.local)
44
44
  }
45
45
 
46
+ export const isGreaterThanOrEqual = (a: EventId, b: EventId) => {
47
+ return a.global > b.global || (a.global === b.global && a.local >= b.local)
48
+ }
49
+
46
50
  export const make = (id: EventId | typeof EventId.Encoded): EventId => {
47
51
  return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
48
52
  }
@@ -2,7 +2,7 @@ import { SqliteAst, SqliteDsl } from '@livestore/db-schema'
2
2
  import { memoizeByStringifyArgs } from '@livestore/utils'
3
3
  import { Effect, Schema as EffectSchema } from '@livestore/utils/effect'
4
4
 
5
- import type { SqliteDb } from '../adapter-types.js'
5
+ import type { MigrationsReport, MigrationsReportEntry, SqliteDb, UnexpectedError } from '../adapter-types.js'
6
6
  import type { LiveStoreSchema } from '../schema/mod.js'
7
7
  import type { SchemaMetaRow, SchemaMutationsMetaRow } from '../schema/system-tables.js'
8
8
  import {
@@ -54,7 +54,7 @@ export const migrateDb = ({
54
54
  db: SqliteDb
55
55
  schema: LiveStoreSchema
56
56
  onProgress?: (opts: { done: number; total: number }) => Effect.Effect<void>
57
- }) =>
57
+ }): Effect.Effect<MigrationsReport, UnexpectedError> =>
58
58
  Effect.gen(function* () {
59
59
  yield* migrateTable({
60
60
  db,
@@ -81,6 +81,7 @@ export const migrateDb = ({
81
81
 
82
82
  const tablesToMigrate = new Set<{ tableAst: SqliteAst.Table; schemaHash: number }>()
83
83
 
84
+ const migrationsReportEntries: MigrationsReportEntry[] = []
84
85
  for (const tableDef of tableDefs) {
85
86
  const tableAst = tableDef.sqliteDef.ast
86
87
  const tableName = tableAst.name
@@ -90,9 +91,10 @@ export const migrateDb = ({
90
91
  if (schemaHash !== dbSchemaHash) {
91
92
  tablesToMigrate.add({ tableAst, schemaHash })
92
93
 
93
- console.log(
94
- `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
95
- )
94
+ migrationsReportEntries.push({
95
+ tableName,
96
+ hashes: { expected: schemaHash, actual: dbSchemaHash },
97
+ })
96
98
  }
97
99
  }
98
100
 
@@ -107,6 +109,8 @@ export const migrateDb = ({
107
109
  yield* onProgress({ done: processedTables, total: tablesCount })
108
110
  }
109
111
  }
112
+
113
+ return { migrations: migrationsReportEntries }
110
114
  })
111
115
 
112
116
  export const migrateTable = ({
@@ -1,13 +1,13 @@
1
1
  import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
2
2
  import type { Runtime, Scope } from '@livestore/utils/effect'
3
- import { Effect, Schema, Stream } from '@livestore/utils/effect'
3
+ import { Effect, Queue, Schema, Stream, Subscribable } from '@livestore/utils/effect'
4
4
  import * as otel from '@opentelemetry/api'
5
5
 
6
6
  import type { ClientSession, UnexpectedError } from '../adapter-types.js'
7
7
  import * as EventId from '../schema/EventId.js'
8
8
  import { type LiveStoreSchema } from '../schema/mod.js'
9
9
  import * as MutationEvent from '../schema/MutationEvent.js'
10
- import { SyncState, updateSyncState } from './syncstate.js'
10
+ import * as SyncState from './syncstate.js'
11
11
 
12
12
  /**
13
13
  * Rebase behaviour:
@@ -44,15 +44,17 @@ export const makeClientSessionSyncProcessor = ({
44
44
  const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
45
45
 
46
46
  const syncStateRef = {
47
- current: new SyncState({
48
- localHead: clientSession.leaderThread.mutations.initialMutationEventId,
49
- upstreamHead: clientSession.leaderThread.mutations.initialMutationEventId,
47
+ current: new SyncState.SyncState({
48
+ localHead: clientSession.leaderThread.initialState.leaderHead,
49
+ upstreamHead: clientSession.leaderThread.initialState.leaderHead,
50
50
  pending: [],
51
51
  // TODO init rollbackTail from leader to be ready for backend rebasing
52
52
  rollbackTail: [],
53
53
  }),
54
54
  }
55
55
 
56
+ const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
57
+
56
58
  const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
57
59
  const mutationDef = schema.mutations.get(mutationEventEncoded.mutation)!
58
60
  return mutationDef.options.localOnly
@@ -71,7 +73,7 @@ export const makeClientSessionSyncProcessor = ({
71
73
  )
72
74
  })
73
75
 
74
- const updateResult = updateSyncState({
76
+ const updateResult = SyncState.updateSyncState({
75
77
  syncState: syncStateRef.current,
76
78
  payload: { _tag: 'local-push', newEvents: encodedMutationEvents },
77
79
  isLocalEvent,
@@ -88,6 +90,7 @@ export const makeClientSessionSyncProcessor = ({
88
90
  }
89
91
 
90
92
  syncStateRef.current = updateResult.newSyncState
93
+ syncStateUpdateQueue.offer(updateResult.newSyncState).pipe(Effect.runSync)
91
94
 
92
95
  const writeTables = new Set<string>()
93
96
  for (const mutationEvent of updateResult.newEvents) {
@@ -119,7 +122,7 @@ export const makeClientSessionSyncProcessor = ({
119
122
  yield* clientSession.devtools.pullLatch.await
120
123
  }
121
124
 
122
- const updateResult = updateSyncState({
125
+ const updateResult = SyncState.updateSyncState({
123
126
  syncState: syncStateRef.current,
124
127
  payload,
125
128
  isLocalEvent,
@@ -132,6 +135,7 @@ export const makeClientSessionSyncProcessor = ({
132
135
  }
133
136
 
134
137
  syncStateRef.current = updateResult.newSyncState
138
+ syncStateUpdateQueue.offer(updateResult.newSyncState).pipe(Effect.runSync)
135
139
 
136
140
  if (updateResult._tag === 'rebase') {
137
141
  span.addEvent('pull:rebase', {
@@ -143,11 +147,11 @@ export const makeClientSessionSyncProcessor = ({
143
147
  remaining,
144
148
  })
145
149
  if (LS_DEV) {
146
- console.debug(
150
+ Effect.logDebug(
147
151
  'pull:rebase: rollback',
148
152
  updateResult.eventsToRollback.length,
149
153
  ...updateResult.eventsToRollback.map((_) => _.toJSON()),
150
- )
154
+ ).pipe(Effect.provide(runtime), Effect.runSync)
151
155
  }
152
156
 
153
157
  for (let i = updateResult.eventsToRollback.length - 1; i >= 0; i--) {
@@ -197,7 +201,14 @@ export const makeClientSessionSyncProcessor = ({
197
201
  return {
198
202
  push,
199
203
  boot,
200
- syncStateRef,
204
+ syncState: Subscribable.make({
205
+ get: Effect.gen(function* () {
206
+ const syncState = syncStateRef.current
207
+ if (syncStateRef === undefined) return shouldNeverHappen('Not initialized')
208
+ return syncState
209
+ }),
210
+ changes: Stream.fromQueue(syncStateUpdateQueue),
211
+ }),
201
212
  } satisfies ClientSessionSyncProcessor
202
213
  }
203
214
 
@@ -210,5 +221,5 @@ export interface ClientSessionSyncProcessor {
210
221
  }
211
222
  boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
212
223
 
213
- syncStateRef: { current: SyncState }
224
+ syncState: Subscribable.Subscribable<SyncState.SyncState>
214
225
  }
package/src/version.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // import packageJson from '../package.json' with { type: 'json' }
3
3
  // export const liveStoreVersion = packageJson.version
4
4
 
5
- export const liveStoreVersion = '0.3.0-dev.11' as const
5
+ export const liveStoreVersion = '0.3.0-dev.12' as const
6
6
 
7
7
  /**
8
8
  * This version number is incremented whenever the internal storage format changes in a breaking way.