@livestore/common 0.3.0-dev.10 → 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 (116) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +62 -30
  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/devtool-message-leader.d.ts +2 -0
  7. package/dist/devtools/devtool-message-leader.d.ts.map +1 -0
  8. package/dist/devtools/devtool-message-leader.js +2 -0
  9. package/dist/devtools/devtool-message-leader.js.map +1 -0
  10. package/dist/devtools/devtools-bridge.d.ts +10 -7
  11. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  12. package/dist/devtools/devtools-messages-client-session.d.ts +370 -0
  13. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
  14. package/dist/devtools/devtools-messages-client-session.js +77 -0
  15. package/dist/devtools/devtools-messages-client-session.js.map +1 -0
  16. package/dist/devtools/devtools-messages-common.d.ts +57 -0
  17. package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
  18. package/dist/devtools/devtools-messages-common.js +44 -0
  19. package/dist/devtools/devtools-messages-common.js.map +1 -0
  20. package/dist/devtools/devtools-messages-leader.d.ts +437 -0
  21. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
  22. package/dist/devtools/devtools-messages-leader.js +132 -0
  23. package/dist/devtools/devtools-messages-leader.js.map +1 -0
  24. package/dist/devtools/devtools-messages.d.ts +3 -580
  25. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  26. package/dist/devtools/devtools-messages.js +3 -174
  27. package/dist/devtools/devtools-messages.js.map +1 -1
  28. package/dist/init-singleton-tables.d.ts +2 -2
  29. package/dist/init-singleton-tables.d.ts.map +1 -1
  30. package/dist/init-singleton-tables.js.map +1 -1
  31. package/dist/leader-thread/LeaderSyncProcessor.d.ts +4 -4
  32. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
  33. package/dist/leader-thread/LeaderSyncProcessor.js +64 -36
  34. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
  35. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  36. package/dist/leader-thread/apply-mutation.js +4 -4
  37. package/dist/leader-thread/apply-mutation.js.map +1 -1
  38. package/dist/leader-thread/connection.d.ts +34 -6
  39. package/dist/leader-thread/connection.d.ts.map +1 -1
  40. package/dist/leader-thread/connection.js +22 -7
  41. package/dist/leader-thread/connection.js.map +1 -1
  42. package/dist/leader-thread/leader-worker-devtools.js +67 -36
  43. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  44. package/dist/leader-thread/make-leader-thread-layer.d.ts +6 -6
  45. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  46. package/dist/leader-thread/make-leader-thread-layer.js +38 -13
  47. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  48. package/dist/leader-thread/mutationlog.d.ts +4 -4
  49. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  50. package/dist/leader-thread/mutationlog.js +6 -6
  51. package/dist/leader-thread/mutationlog.js.map +1 -1
  52. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  53. package/dist/leader-thread/recreate-db.d.ts +4 -2
  54. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  55. package/dist/leader-thread/recreate-db.js +27 -22
  56. package/dist/leader-thread/recreate-db.js.map +1 -1
  57. package/dist/leader-thread/types.d.ts +32 -17
  58. package/dist/leader-thread/types.d.ts.map +1 -1
  59. package/dist/leader-thread/types.js +0 -2
  60. package/dist/leader-thread/types.js.map +1 -1
  61. package/dist/query-builder/api.d.ts +2 -2
  62. package/dist/query-builder/api.d.ts.map +1 -1
  63. package/dist/query-builder/impl.js.map +1 -1
  64. package/dist/query-builder/impl.test.js +16 -1
  65. package/dist/query-builder/impl.test.js.map +1 -1
  66. package/dist/query-info.d.ts +3 -3
  67. package/dist/query-info.d.ts.map +1 -1
  68. package/dist/rehydrate-from-mutationlog.d.ts +3 -3
  69. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  70. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  71. package/dist/schema/EventId.d.ts +1 -0
  72. package/dist/schema/EventId.d.ts.map +1 -1
  73. package/dist/schema/EventId.js +3 -0
  74. package/dist/schema/EventId.js.map +1 -1
  75. package/dist/schema/mutations.d.ts +1 -1
  76. package/dist/schema/system-tables.d.ts +1 -1
  77. package/dist/schema-management/common.d.ts +3 -3
  78. package/dist/schema-management/common.d.ts.map +1 -1
  79. package/dist/schema-management/common.js.map +1 -1
  80. package/dist/schema-management/migrations.d.ts +5 -5
  81. package/dist/schema-management/migrations.d.ts.map +1 -1
  82. package/dist/schema-management/migrations.js +6 -1
  83. package/dist/schema-management/migrations.js.map +1 -1
  84. package/dist/sync/ClientSessionSyncProcessor.d.ts +8 -12
  85. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
  86. package/dist/sync/ClientSessionSyncProcessor.js +31 -13
  87. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
  88. package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
  89. package/dist/version.d.ts +1 -1
  90. package/dist/version.js +1 -1
  91. package/package.json +3 -3
  92. package/src/adapter-types.ts +52 -33
  93. package/src/devtools/devtools-bridge.ts +10 -7
  94. package/src/devtools/devtools-messages-client-session.ts +125 -0
  95. package/src/devtools/devtools-messages-common.ts +81 -0
  96. package/src/devtools/devtools-messages-leader.ts +176 -0
  97. package/src/devtools/devtools-messages.ts +3 -246
  98. package/src/init-singleton-tables.ts +2 -2
  99. package/src/leader-thread/LeaderSyncProcessor.ts +94 -46
  100. package/src/leader-thread/apply-mutation.ts +5 -5
  101. package/src/leader-thread/connection.ts +54 -9
  102. package/src/leader-thread/leader-worker-devtools.ts +105 -41
  103. package/src/leader-thread/make-leader-thread-layer.ts +55 -22
  104. package/src/leader-thread/mutationlog.ts +9 -9
  105. package/src/leader-thread/recreate-db.ts +33 -24
  106. package/src/leader-thread/types.ts +38 -21
  107. package/src/query-builder/api.ts +3 -3
  108. package/src/query-builder/impl.test.ts +22 -1
  109. package/src/query-builder/impl.ts +2 -2
  110. package/src/query-info.ts +3 -3
  111. package/src/rehydrate-from-mutationlog.ts +3 -3
  112. package/src/schema/EventId.ts +4 -0
  113. package/src/schema-management/common.ts +3 -3
  114. package/src/schema-management/migrations.ts +12 -8
  115. package/src/sync/ClientSessionSyncProcessor.ts +38 -22
  116. package/src/version.ts +1 -1
@@ -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,
@@ -16,10 +15,11 @@ import type {
16
15
  BootStatus,
17
16
  Devtools,
18
17
  InvalidPushError,
19
- MakeSynchronousDatabase,
18
+ MakeSqliteDb,
19
+ MigrationsReport,
20
20
  PersistenceInfo,
21
+ SqliteDb,
21
22
  SyncBackend,
22
- SynchronousDatabase,
23
23
  UnexpectedError,
24
24
  } from '../index.js'
25
25
  import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
@@ -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
 
@@ -56,8 +49,8 @@ export type InitialSyncInfo = Option.Option<{
56
49
  // | { _tag: 'Recreate'; snapshotRef: Ref.Ref<Uint8Array | undefined>; syncInfo: InitialSyncInfo }
57
50
  // | { _tag: 'Reuse'; syncInfo: InitialSyncInfo }
58
51
 
59
- export type LeaderDatabase = SynchronousDatabase<{ dbPointer: number; persistenceInfo: PersistenceInfo }>
60
- export type PersistenceInfoPair = { db: PersistenceInfo; mutationLog: PersistenceInfo }
52
+ export type LeaderSqliteDb = SqliteDb<{ dbPointer: number; persistenceInfo: PersistenceInfo }>
53
+ export type PersistenceInfoPair = { readModel: PersistenceInfo; mutationLog: PersistenceInfo }
61
54
 
62
55
  export type DevtoolsOptions =
63
56
  | {
@@ -65,9 +58,9 @@ export type DevtoolsOptions =
65
58
  }
66
59
  | {
67
60
  enabled: true
68
- makeContext: Effect.Effect<
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,
@@ -75,26 +68,46 @@ export type DevtoolsOptions =
75
68
  >
76
69
  }
77
70
 
71
+ export type DevtoolsContext =
72
+ | {
73
+ enabled: true
74
+ // syncBackendPullLatch: Effect.Latch
75
+ // syncBackendPushLatch: Effect.Latch
76
+ syncBackendLatch: Effect.Latch
77
+ syncBackendLatchState: SubscriptionRef.SubscriptionRef<{ latchClosed: boolean }>
78
+ }
79
+ | {
80
+ enabled: false
81
+ }
82
+
78
83
  export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
79
84
  LeaderThreadCtx,
80
85
  {
81
86
  schema: LiveStoreSchema
82
87
  storeId: string
83
88
  clientId: string
84
- makeSyncDb: MakeSynchronousDatabase
85
- db: LeaderDatabase
86
- dbLog: LeaderDatabase
89
+ makeSqliteDb: MakeSqliteDb
90
+ dbReadModel: LeaderSqliteDb
91
+ dbMutationLog: LeaderSqliteDb
87
92
  bootStatusQueue: Queue.Queue<BootStatus>
88
93
  // TODO we should find a more elegant way to handle cases which need this ref for their implementation
89
94
  shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
90
95
  shutdownChannel: ShutdownChannel
91
96
  mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
92
- // devtools: DevtoolsContext
97
+ devtools: DevtoolsContext
93
98
  syncBackend: SyncBackend | undefined
94
99
  syncProcessor: LeaderSyncProcessor
95
100
  connectedClientSessionPullQueues: PullQueueSet
96
- /** e.g. used for `store.__dev` APIs */
97
- extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
101
+ initialState: {
102
+ leaderHead: EventId.EventId
103
+ migrationsReport: MigrationsReport
104
+ }
105
+ /**
106
+ * e.g. used for `store._dev` APIs
107
+ *
108
+ * This is currently separated from `.devtools` as it also needs to work when devtools are disabled
109
+ */
110
+ extraIncomingMessagesQueue: Queue.Queue<Devtools.Leader.MessageToApp>
98
111
  }
99
112
  >() {}
100
113
 
@@ -124,7 +137,11 @@ export interface LeaderSyncProcessor {
124
137
  pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
125
138
  boot: (args: {
126
139
  dbReady: Deferred.Deferred<void>
127
- }) => 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
+ >
128
145
  syncState: Subscribable.Subscribable<SyncState.SyncState>
129
146
  }
130
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)
@@ -1,7 +1,7 @@
1
1
  import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
2
  import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
3
3
 
4
- import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
4
+ import { type MigrationOptionsFromMutationLog, type SqliteDb, UnexpectedError } from './adapter-types.js'
5
5
  import { makeApplyMutation } from './leader-thread/apply-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'
@@ -16,8 +16,8 @@ export const rehydrateFromMutationLog = ({
16
16
  migrationOptions,
17
17
  onProgress,
18
18
  }: {
19
- logDb: SynchronousDatabase
20
- db: SynchronousDatabase
19
+ logDb: SqliteDb
20
+ db: SqliteDb
21
21
  schema: LiveStoreSchema
22
22
  migrationOptions: MigrationOptionsFromMutationLog
23
23
  onProgress: (_: { done: number; total: number }) => Effect.Effect<void>
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import type { SynchronousDatabase } from '../adapter-types.js'
1
+ import type { SqliteDb } from '../adapter-types.js'
2
2
  import type { ParamsObject } from '../util.js'
3
3
  import { prepareBindValues } from '../util.js'
4
4
 
@@ -6,7 +6,7 @@ import { prepareBindValues } from '../util.js'
6
6
  // will require proper scope-aware cleanup etc (for testing and apps with multiple LiveStore instances)
7
7
  // const cachedStmts = new Map<string, PreparedStatement>()
8
8
 
9
- export const dbExecute = (db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
9
+ export const dbExecute = (db: SqliteDb, queryStr: string, bindValues?: ParamsObject) => {
10
10
  // let stmt = cachedStmts.get(queryStr)
11
11
  // if (!stmt) {
12
12
  const stmt = db.prepare(queryStr)
@@ -20,7 +20,7 @@ export const dbExecute = (db: SynchronousDatabase, queryStr: string, bindValues?
20
20
  stmt.finalize()
21
21
  }
22
22
 
23
- export const dbSelect = <T>(db: SynchronousDatabase, queryStr: string, bindValues?: ParamsObject) => {
23
+ export const dbSelect = <T>(db: SqliteDb, queryStr: string, bindValues?: ParamsObject) => {
24
24
  // let stmt = cachedStmts.get(queryStr)
25
25
  // if (!stmt) {
26
26
  const stmt = db.prepare(queryStr)
@@ -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 { SynchronousDatabase } 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 {
@@ -19,7 +19,7 @@ import { validateSchema } from './validate-mutation-defs.js'
19
19
 
20
20
  const getMemoizedTimestamp = memoizeByStringifyArgs(() => new Date().toISOString())
21
21
 
22
- export const makeSchemaManager = (db: SynchronousDatabase): Effect.Effect<SchemaManager> =>
22
+ export const makeSchemaManager = (db: SqliteDb): Effect.Effect<SchemaManager> =>
23
23
  Effect.gen(function* () {
24
24
  yield* migrateTable({
25
25
  db,
@@ -51,10 +51,10 @@ export const migrateDb = ({
51
51
  schema,
52
52
  onProgress,
53
53
  }: {
54
- db: SynchronousDatabase
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 = ({
@@ -116,7 +120,7 @@ export const migrateTable = ({
116
120
  behaviour,
117
121
  skipMetaTable = false,
118
122
  }: {
119
- db: SynchronousDatabase
123
+ db: SqliteDb
120
124
  tableAst: SqliteAst.Table
121
125
  schemaHash?: number
122
126
  behaviour: 'drop-and-recreate' | 'create-if-not-exists'
@@ -1,13 +1,13 @@
1
1
  import { LS_DEV, shouldNeverHappen, TRACE_VERBOSE } from '@livestore/utils'
2
- import type { Scope } from '@livestore/utils/effect'
3
- import { Effect, Schema, Stream } from '@livestore/utils/effect'
2
+ import type { Runtime, Scope } 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
- import type { ClientSessionLeaderThreadProxy, UnexpectedError } from '../adapter-types.js'
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:
@@ -20,18 +20,16 @@ import { SyncState, updateSyncState } from './syncstate.js'
20
20
  */
21
21
  export const makeClientSessionSyncProcessor = ({
22
22
  schema,
23
- initialLeaderHead,
24
- pushToLeader,
25
- pullFromLeader,
23
+ clientSession,
24
+ runtime,
26
25
  applyMutation,
27
26
  rollback,
28
27
  refreshTables,
29
28
  span,
30
29
  }: {
31
30
  schema: LiveStoreSchema
32
- initialLeaderHead: EventId.EventId
33
- pushToLeader: (batch: ReadonlyArray<MutationEvent.AnyEncoded>) => void
34
- pullFromLeader: ClientSessionLeaderThreadProxy['mutations']['pull']
31
+ clientSession: ClientSession
32
+ runtime: Runtime.Runtime<Scope.Scope>
35
33
  applyMutation: (
36
34
  mutationEventDecoded: MutationEvent.PartialAnyDecoded,
37
35
  options: { otelContext: otel.Context; withChangeset: boolean },
@@ -46,15 +44,17 @@ export const makeClientSessionSyncProcessor = ({
46
44
  const mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
47
45
 
48
46
  const syncStateRef = {
49
- current: new SyncState({
50
- localHead: initialLeaderHead,
51
- upstreamHead: initialLeaderHead,
47
+ current: new SyncState.SyncState({
48
+ localHead: clientSession.leaderThread.initialState.leaderHead,
49
+ upstreamHead: clientSession.leaderThread.initialState.leaderHead,
52
50
  pending: [],
53
51
  // TODO init rollbackTail from leader to be ready for backend rebasing
54
52
  rollbackTail: [],
55
53
  }),
56
54
  }
57
55
 
56
+ const syncStateUpdateQueue = Queue.unbounded<SyncState.SyncState>().pipe(Effect.runSync)
57
+
58
58
  const isLocalEvent = (mutationEventEncoded: MutationEvent.EncodedWithMeta) => {
59
59
  const mutationDef = schema.mutations.get(mutationEventEncoded.mutation)!
60
60
  return mutationDef.options.localOnly
@@ -73,7 +73,7 @@ export const makeClientSessionSyncProcessor = ({
73
73
  )
74
74
  })
75
75
 
76
- const updateResult = updateSyncState({
76
+ const updateResult = SyncState.updateSyncState({
77
77
  syncState: syncStateRef.current,
78
78
  payload: { _tag: 'local-push', newEvents: encodedMutationEvents },
79
79
  isLocalEvent,
@@ -90,6 +90,7 @@ export const makeClientSessionSyncProcessor = ({
90
90
  }
91
91
 
92
92
  syncStateRef.current = updateResult.newSyncState
93
+ syncStateUpdateQueue.offer(updateResult.newSyncState).pipe(Effect.runSync)
93
94
 
94
95
  const writeTables = new Set<string>()
95
96
  for (const mutationEvent of updateResult.newEvents) {
@@ -103,7 +104,9 @@ export const makeClientSessionSyncProcessor = ({
103
104
  }
104
105
 
105
106
  // console.debug('pushToLeader', encodedMutationEvents.length, ...encodedMutationEvents.map((_) => _.toJSON()))
106
- pushToLeader(encodedMutationEvents)
107
+ clientSession.leaderThread.mutations
108
+ .push(encodedMutationEvents)
109
+ .pipe(Effect.tapCauseLogPretty, Effect.provide(runtime), Effect.runFork)
107
110
 
108
111
  return { writeTables }
109
112
  }
@@ -111,12 +114,15 @@ export const makeClientSessionSyncProcessor = ({
111
114
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
112
115
 
113
116
  const boot: ClientSessionSyncProcessor['boot'] = Effect.gen(function* () {
114
- yield* pullFromLeader.pipe(
117
+ yield* clientSession.leaderThread.mutations.pull.pipe(
115
118
  Stream.tap(({ payload, remaining }) =>
116
119
  Effect.gen(function* () {
117
120
  // console.log('pulled payload from leader', { payload, remaining })
121
+ if (clientSession.devtools.enabled) {
122
+ yield* clientSession.devtools.pullLatch.await
123
+ }
118
124
 
119
- const updateResult = updateSyncState({
125
+ const updateResult = SyncState.updateSyncState({
120
126
  syncState: syncStateRef.current,
121
127
  payload,
122
128
  isLocalEvent,
@@ -129,6 +135,7 @@ export const makeClientSessionSyncProcessor = ({
129
135
  }
130
136
 
131
137
  syncStateRef.current = updateResult.newSyncState
138
+ syncStateUpdateQueue.offer(updateResult.newSyncState).pipe(Effect.runSync)
132
139
 
133
140
  if (updateResult._tag === 'rebase') {
134
141
  span.addEvent('pull:rebase', {
@@ -140,11 +147,11 @@ export const makeClientSessionSyncProcessor = ({
140
147
  remaining,
141
148
  })
142
149
  if (LS_DEV) {
143
- console.debug(
150
+ Effect.logDebug(
144
151
  'pull:rebase: rollback',
145
152
  updateResult.eventsToRollback.length,
146
153
  ...updateResult.eventsToRollback.map((_) => _.toJSON()),
147
- )
154
+ ).pipe(Effect.provide(runtime), Effect.runSync)
148
155
  }
149
156
 
150
157
  for (let i = updateResult.eventsToRollback.length - 1; i >= 0; i--) {
@@ -155,7 +162,9 @@ export const makeClientSessionSyncProcessor = ({
155
162
  }
156
163
  }
157
164
 
158
- pushToLeader(updateResult.newSyncState.pending)
165
+ clientSession.leaderThread.mutations
166
+ .push(updateResult.newSyncState.pending)
167
+ .pipe(Effect.tapCauseLogPretty, Effect.provide(runtime), Effect.runFork)
159
168
  } else {
160
169
  span.addEvent('pull:advance', {
161
170
  payloadTag: payload._tag,
@@ -192,7 +201,14 @@ export const makeClientSessionSyncProcessor = ({
192
201
  return {
193
202
  push,
194
203
  boot,
195
- 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
+ }),
196
212
  } satisfies ClientSessionSyncProcessor
197
213
  }
198
214
 
@@ -205,5 +221,5 @@ export interface ClientSessionSyncProcessor {
205
221
  }
206
222
  boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
207
223
 
208
- syncStateRef: { current: SyncState }
224
+ syncState: Subscribable.Subscribable<SyncState.SyncState>
209
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.10' 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.