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

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 (181) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/adapter-types.d.ts +47 -35
  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/devtool-message-leader.d.ts +2 -0
  9. package/dist/devtools/devtool-message-leader.d.ts.map +1 -0
  10. package/dist/devtools/devtool-message-leader.js +2 -0
  11. package/dist/devtools/devtool-message-leader.js.map +1 -0
  12. package/dist/devtools/devtools-bridge.d.ts +2 -1
  13. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  14. package/dist/devtools/devtools-messages-client-session.d.ts +297 -0
  15. package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
  16. package/dist/devtools/devtools-messages-client-session.js +61 -0
  17. package/dist/devtools/devtools-messages-client-session.js.map +1 -0
  18. package/dist/devtools/devtools-messages-common.d.ts +65 -0
  19. package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
  20. package/dist/devtools/devtools-messages-common.js +35 -0
  21. package/dist/devtools/devtools-messages-common.js.map +1 -0
  22. package/dist/devtools/devtools-messages-leader.d.ts +261 -0
  23. package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
  24. package/dist/devtools/devtools-messages-leader.js +85 -0
  25. package/dist/devtools/devtools-messages-leader.js.map +1 -0
  26. package/dist/devtools/devtools-messages.d.ts +3 -592
  27. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  28. package/dist/devtools/devtools-messages.js +3 -171
  29. package/dist/devtools/devtools-messages.js.map +1 -1
  30. package/dist/index.d.ts +0 -4
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/init-singleton-tables.d.ts +2 -2
  33. package/dist/init-singleton-tables.d.ts.map +1 -1
  34. package/dist/init-singleton-tables.js.map +1 -1
  35. package/dist/leader-thread/LeaderSyncProcessor.d.ts +37 -0
  36. package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -0
  37. package/dist/leader-thread/LeaderSyncProcessor.js +432 -0
  38. package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -0
  39. package/dist/leader-thread/apply-mutation.d.ts +5 -2
  40. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  41. package/dist/leader-thread/apply-mutation.js +41 -29
  42. package/dist/leader-thread/apply-mutation.js.map +1 -1
  43. package/dist/leader-thread/connection.d.ts +4 -4
  44. package/dist/leader-thread/connection.d.ts.map +1 -1
  45. package/dist/leader-thread/connection.js +5 -5
  46. package/dist/leader-thread/connection.js.map +1 -1
  47. package/dist/leader-thread/leader-sync-processor.d.ts +2 -2
  48. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
  49. package/dist/leader-thread/leader-sync-processor.js +20 -12
  50. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  51. package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
  52. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  53. package/dist/leader-thread/leader-worker-devtools.js +37 -81
  54. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  55. package/dist/leader-thread/make-leader-thread-layer.d.ts +12 -11
  56. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  57. package/dist/leader-thread/make-leader-thread-layer.js +33 -14
  58. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  59. package/dist/leader-thread/mutationlog.d.ts +6 -19
  60. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  61. package/dist/leader-thread/mutationlog.js +7 -6
  62. package/dist/leader-thread/mutationlog.js.map +1 -1
  63. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  64. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  65. package/dist/leader-thread/recreate-db.js +24 -18
  66. package/dist/leader-thread/recreate-db.js.map +1 -1
  67. package/dist/leader-thread/types.d.ts +36 -16
  68. package/dist/leader-thread/types.d.ts.map +1 -1
  69. package/dist/leader-thread/types.js.map +1 -1
  70. package/dist/mutation.d.ts +9 -2
  71. package/dist/mutation.d.ts.map +1 -1
  72. package/dist/mutation.js +5 -5
  73. package/dist/mutation.js.map +1 -1
  74. package/dist/query-builder/impl.d.ts +1 -1
  75. package/dist/rehydrate-from-mutationlog.d.ts +5 -5
  76. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  77. package/dist/rehydrate-from-mutationlog.js +13 -19
  78. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  79. package/dist/schema/EventId.d.ts +16 -14
  80. package/dist/schema/EventId.d.ts.map +1 -1
  81. package/dist/schema/EventId.js +15 -7
  82. package/dist/schema/EventId.js.map +1 -1
  83. package/dist/schema/EventId.test.d.ts +2 -0
  84. package/dist/schema/EventId.test.d.ts.map +1 -0
  85. package/dist/schema/EventId.test.js +11 -0
  86. package/dist/schema/EventId.test.js.map +1 -0
  87. package/dist/schema/MutationEvent.d.ts +49 -80
  88. package/dist/schema/MutationEvent.d.ts.map +1 -1
  89. package/dist/schema/MutationEvent.js +32 -15
  90. package/dist/schema/MutationEvent.js.map +1 -1
  91. package/dist/schema/MutationEvent.test.d.ts +2 -0
  92. package/dist/schema/MutationEvent.test.d.ts.map +1 -0
  93. package/dist/schema/MutationEvent.test.js +2 -0
  94. package/dist/schema/MutationEvent.test.js.map +1 -0
  95. package/dist/schema/system-tables.d.ts +26 -26
  96. package/dist/schema/system-tables.d.ts.map +1 -1
  97. package/dist/schema/system-tables.js +19 -11
  98. package/dist/schema/system-tables.js.map +1 -1
  99. package/dist/schema-management/common.d.ts +3 -3
  100. package/dist/schema-management/common.d.ts.map +1 -1
  101. package/dist/schema-management/common.js.map +1 -1
  102. package/dist/schema-management/migrations.d.ts +4 -4
  103. package/dist/schema-management/migrations.d.ts.map +1 -1
  104. package/dist/schema-management/migrations.js +6 -6
  105. package/dist/schema-management/migrations.js.map +1 -1
  106. package/dist/sync/ClientSessionSyncProcessor.d.ts +43 -0
  107. package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -0
  108. package/dist/sync/ClientSessionSyncProcessor.js +141 -0
  109. package/dist/sync/ClientSessionSyncProcessor.js.map +1 -0
  110. package/dist/sync/client-session-sync-processor.d.ts +4 -4
  111. package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
  112. package/dist/sync/index.d.ts +1 -1
  113. package/dist/sync/index.d.ts.map +1 -1
  114. package/dist/sync/index.js +1 -1
  115. package/dist/sync/index.js.map +1 -1
  116. package/dist/sync/next/history-dag-common.d.ts +1 -4
  117. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  118. package/dist/sync/next/history-dag-common.js +1 -1
  119. package/dist/sync/next/history-dag-common.js.map +1 -1
  120. package/dist/sync/next/rebase-events.d.ts +3 -3
  121. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  122. package/dist/sync/next/rebase-events.js +3 -2
  123. package/dist/sync/next/rebase-events.js.map +1 -1
  124. package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
  125. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  126. package/dist/sync/next/test/mutation-fixtures.js +3 -9
  127. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  128. package/dist/sync/sync.d.ts +21 -11
  129. package/dist/sync/sync.d.ts.map +1 -1
  130. package/dist/sync/sync.js.map +1 -1
  131. package/dist/sync/syncstate.d.ts +45 -23
  132. package/dist/sync/syncstate.d.ts.map +1 -1
  133. package/dist/sync/syncstate.js +56 -12
  134. package/dist/sync/syncstate.js.map +1 -1
  135. package/dist/sync/syncstate.test.js +125 -69
  136. package/dist/sync/syncstate.test.js.map +1 -1
  137. package/dist/sync/validate-push-payload.d.ts +2 -2
  138. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  139. package/dist/sync/validate-push-payload.js +2 -2
  140. package/dist/sync/validate-push-payload.js.map +1 -1
  141. package/dist/version.d.ts +1 -1
  142. package/dist/version.d.ts.map +1 -1
  143. package/dist/version.js +1 -1
  144. package/dist/version.js.map +1 -1
  145. package/package.json +6 -5
  146. package/src/adapter-types.ts +39 -40
  147. package/src/derived-mutations.test.ts +1 -1
  148. package/src/derived-mutations.ts +9 -5
  149. package/src/devtools/devtools-bridge.ts +2 -1
  150. package/src/devtools/devtools-messages-client-session.ts +109 -0
  151. package/src/devtools/devtools-messages-common.ts +52 -0
  152. package/src/devtools/devtools-messages-leader.ts +115 -0
  153. package/src/devtools/devtools-messages.ts +3 -243
  154. package/src/index.ts +0 -6
  155. package/src/init-singleton-tables.ts +2 -2
  156. package/src/leader-thread/{leader-sync-processor.ts → LeaderSyncProcessor.ts} +306 -268
  157. package/src/leader-thread/apply-mutation.ts +53 -35
  158. package/src/leader-thread/connection.ts +7 -7
  159. package/src/leader-thread/leader-worker-devtools.ts +52 -124
  160. package/src/leader-thread/make-leader-thread-layer.ts +62 -30
  161. package/src/leader-thread/mutationlog.ts +14 -10
  162. package/src/leader-thread/recreate-db.ts +24 -20
  163. package/src/leader-thread/types.ts +41 -20
  164. package/src/mutation.ts +17 -7
  165. package/src/rehydrate-from-mutationlog.ts +18 -26
  166. package/src/schema/EventId.test.ts +12 -0
  167. package/src/schema/EventId.ts +23 -9
  168. package/src/schema/MutationEvent.ts +46 -24
  169. package/src/schema/system-tables.ts +19 -11
  170. package/src/schema-management/common.ts +3 -3
  171. package/src/schema-management/migrations.ts +10 -10
  172. package/src/sync/{client-session-sync-processor.ts → ClientSessionSyncProcessor.ts} +26 -19
  173. package/src/sync/index.ts +1 -1
  174. package/src/sync/next/history-dag-common.ts +1 -1
  175. package/src/sync/next/rebase-events.ts +7 -7
  176. package/src/sync/next/test/mutation-fixtures.ts +3 -10
  177. package/src/sync/sync.ts +19 -6
  178. package/src/sync/syncstate.test.ts +127 -67
  179. package/src/sync/syncstate.ts +21 -19
  180. package/src/sync/validate-push-payload.ts +7 -4
  181. package/src/version.ts +1 -1
@@ -2,21 +2,26 @@ import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
2
  import type { Scope } from '@livestore/utils/effect'
3
3
  import { Effect, Option, Schema } from '@livestore/utils/effect'
4
4
 
5
- import type { SqliteError, SynchronousDatabase, UnexpectedError } from '../index.js'
5
+ import type { SqliteDb, SqliteError, UnexpectedError } from '../index.js'
6
+ import { getExecArgsFromMutation } from '../mutation.js'
6
7
  import {
7
- getExecArgsFromMutation,
8
+ type LiveStoreSchema,
8
9
  MUTATION_LOG_META_TABLE,
10
+ type MutationEvent,
9
11
  mutationLogMetaTable,
10
12
  SESSION_CHANGESET_META_TABLE,
11
13
  sessionChangesetMetaTable,
12
- } from '../index.js'
13
- import type { LiveStoreSchema, MutationEvent } from '../schema/mod.js'
14
+ } from '../schema/mod.js'
14
15
  import { insertRow } from '../sql-queries/index.js'
15
16
  import { execSql, execSqlPrepared } from './connection.js'
16
17
  import { LeaderThreadCtx } from './types.js'
17
18
 
18
19
  export type ApplyMutation = (
19
20
  mutationEventEncoded: MutationEvent.AnyEncoded,
21
+ options?: {
22
+ /** Needed for rehydrateFromMutationLog */
23
+ skipMutationLog?: boolean
24
+ },
20
25
  ) => Effect.Effect<void, SqliteError | UnexpectedError>
21
26
 
22
27
  export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope | LeaderThreadCtx> = Effect.gen(
@@ -31,15 +36,27 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
31
36
  [...leaderThreadCtx.schema.mutations.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const),
32
37
  )
33
38
 
34
- return (mutationEventEncoded) =>
39
+ return (mutationEventEncoded, options) =>
35
40
  Effect.gen(function* () {
36
- const { mutationEventSchema, schema, db, dbLog } = leaderThreadCtx
37
- const mutationEventDecoded = Schema.decodeUnknownSync(mutationEventSchema)(mutationEventEncoded)
41
+ const { schema, dbReadModel: db, dbMutationLog } = leaderThreadCtx
42
+ const skipMutationLog = options?.skipMutationLog ?? false
38
43
 
39
- const mutationName = mutationEventDecoded.mutation
44
+ const mutationName = mutationEventEncoded.mutation
40
45
  const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
41
46
 
42
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
47
+ const execArgsArr = getExecArgsFromMutation({
48
+ mutationDef,
49
+ mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
50
+ })
51
+
52
+ // NOTE we might want to bring this back if we want to debug no-op mutations
53
+ // const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
54
+ // onRowsChanged: (rowsChanged: number) => {
55
+ // if (rowsChanged === 0) {
56
+ // console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
57
+ // }
58
+ // },
59
+ // })
43
60
 
44
61
  // console.group('[@livestore/common:leader-thread:applyMutation]', { mutationName })
45
62
 
@@ -53,31 +70,29 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
53
70
 
54
71
  const changeset = session.changeset()
55
72
  session.finish()
56
- // NOTE for no-op mutations (e.g. if the state didn't change) the changeset will be empty
57
- // TODO possibly write a null value instead of omitting the row
58
- if (changeset !== undefined && changeset.length > 0) {
59
- // TODO use prepared statements
60
- yield* execSql(
61
- db,
62
- ...insertRow({
63
- tableName: SESSION_CHANGESET_META_TABLE,
64
- columns: sessionChangesetMetaTable.sqliteDef.columns,
65
- values: {
66
- idGlobal: mutationEventEncoded.id.global,
67
- idLocal: mutationEventEncoded.id.local,
68
- changeset,
69
- debug: execArgsArr,
70
- },
71
- }),
72
- )
73
- }
73
+
74
+ // TODO use prepared statements
75
+ yield* execSql(
76
+ db,
77
+ ...insertRow({
78
+ tableName: SESSION_CHANGESET_META_TABLE,
79
+ columns: sessionChangesetMetaTable.sqliteDef.columns,
80
+ values: {
81
+ idGlobal: mutationEventEncoded.id.global,
82
+ idLocal: mutationEventEncoded.id.local,
83
+ // NOTE the changeset will be empty (i.e. null) for no-op mutations
84
+ changeset: changeset ?? null,
85
+ debug: execArgsArr,
86
+ },
87
+ }),
88
+ )
74
89
 
75
90
  // console.groupEnd()
76
91
 
77
92
  // write to mutation_log
78
- const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName, mutationEventDecoded)
79
- if (excludeFromMutationLog === false) {
80
- yield* insertIntoMutationLog(mutationEventEncoded, dbLog, mutationDefSchemaHashMap)
93
+ const excludeFromMutationLog = shouldExcludeMutationFromLog(mutationName, mutationEventEncoded)
94
+ if (skipMutationLog === false && excludeFromMutationLog === false) {
95
+ yield* insertIntoMutationLog(mutationEventEncoded, dbMutationLog, mutationDefSchemaHashMap)
81
96
  } else {
82
97
  // console.debug('[@livestore/common:leader-thread] skipping mutation log write', mutation, statementSql, bindValues)
83
98
  }
@@ -86,7 +101,7 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
86
101
  attributes: {
87
102
  mutationName: mutationEventEncoded.mutation,
88
103
  mutationId: mutationEventEncoded.id,
89
- 'span.label': mutationEventEncoded.mutation,
104
+ 'span.label': `(${mutationEventEncoded.id.global},${mutationEventEncoded.id.local}) ${mutationEventEncoded.mutation}`,
90
105
  },
91
106
  }),
92
107
  // Effect.logDuration('@livestore/common:leader-thread:applyMutation'),
@@ -96,7 +111,7 @@ export const makeApplyMutation: Effect.Effect<ApplyMutation, never, Scope.Scope
96
111
 
97
112
  const insertIntoMutationLog = (
98
113
  mutationEventEncoded: MutationEvent.AnyEncoded,
99
- dbLog: SynchronousDatabase,
114
+ dbMutationLog: SqliteDb,
100
115
  mutationDefSchemaHashMap: Map<string, number>,
101
116
  ) =>
102
117
  Effect.gen(function* () {
@@ -106,7 +121,7 @@ const insertIntoMutationLog = (
106
121
 
107
122
  // TODO use prepared statements
108
123
  yield* execSql(
109
- dbLog,
124
+ dbMutationLog,
110
125
  ...insertRow({
111
126
  tableName: MUTATION_LOG_META_TABLE,
112
127
  columns: mutationLogMetaTable.sqliteDef.columns,
@@ -132,11 +147,14 @@ const makeShouldExcludeMutationFromLog = memoizeByRef((schema: LiveStoreSchema)
132
147
  ? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
133
148
  : new Set(['livestore.RawSql'])
134
149
 
135
- return (mutationName: string, mutationEventDecoded: MutationEvent.Any): boolean => {
150
+ return (mutationName: string, mutationEventEncoded: MutationEvent.AnyEncoded): boolean => {
136
151
  if (mutationLogExclude.has(mutationName)) return true
137
152
 
138
153
  const mutationDef = schema.mutations.get(mutationName) ?? shouldNeverHappen(`Unknown mutation: ${mutationName}`)
139
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
154
+ const execArgsArr = getExecArgsFromMutation({
155
+ mutationDef,
156
+ mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
157
+ })
140
158
 
141
159
  return execArgsArr.some((_) => _.statementSql.includes('__livestore'))
142
160
  }
@@ -1,7 +1,7 @@
1
1
  // import type { WaSqlite } from '@livestore/sqlite-wasm'
2
2
  import { Effect } from '@livestore/utils/effect'
3
3
 
4
- import type { SynchronousDatabase } from '../adapter-types.js'
4
+ import type { SqliteDb } from '../adapter-types.js'
5
5
  import { SqliteError } from '../adapter-types.js'
6
6
  import type { BindValues } from '../sql-queries/index.js'
7
7
  import type { PreparedBindValues } from '../util.js'
@@ -12,9 +12,9 @@ namespace WaSqlite {
12
12
  export type SQLiteError = any
13
13
  }
14
14
 
15
- export const configureConnection = (syncDb: SynchronousDatabase, { fkEnabled }: { fkEnabled: boolean }) =>
15
+ export const configureConnection = (sqliteDb: SqliteDb, { fkEnabled }: { fkEnabled: boolean }) =>
16
16
  execSql(
17
- syncDb,
17
+ sqliteDb,
18
18
  sql`
19
19
  PRAGMA page_size=8192;
20
20
  PRAGMA journal_mode=MEMORY;
@@ -23,10 +23,10 @@ export const configureConnection = (syncDb: SynchronousDatabase, { fkEnabled }:
23
23
  {},
24
24
  )
25
25
 
26
- export const execSql = (syncDb: SynchronousDatabase, sql: string, bind: BindValues) => {
26
+ export const execSql = (sqliteDb: SqliteDb, sql: string, bind: BindValues) => {
27
27
  const bindValues = prepareBindValues(bind, sql)
28
28
  return Effect.try({
29
- try: () => syncDb.execute(sql, bindValues),
29
+ try: () => sqliteDb.execute(sql, bindValues),
30
30
  catch: (cause) =>
31
31
  new SqliteError({ cause, query: { bindValues, sql }, code: (cause as WaSqlite.SQLiteError).code }),
32
32
  }).pipe(
@@ -48,9 +48,9 @@ export const execSql = (syncDb: SynchronousDatabase, sql: string, bind: BindValu
48
48
  // }
49
49
 
50
50
  // TODO actually use prepared statements
51
- export const execSqlPrepared = (syncDb: SynchronousDatabase, sql: string, bindValues: PreparedBindValues) => {
51
+ export const execSqlPrepared = (sqliteDb: SqliteDb, sql: string, bindValues: PreparedBindValues) => {
52
52
  return Effect.try({
53
- try: () => syncDb.execute(sql, bindValues),
53
+ try: () => sqliteDb.execute(sql, bindValues),
54
54
  catch: (cause) =>
55
55
  new SqliteError({ cause, query: { bindValues, sql }, code: (cause as WaSqlite.SQLiteError).code }),
56
56
  }).pipe(
@@ -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.makeBootContext
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,32 @@ 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 {
75
+ syncBackend,
76
+ makeSqliteDb,
77
+ dbReadModel,
78
+ dbMutationLog,
79
+ shutdownStateSubRef,
80
+ shutdownChannel,
81
+ syncProcessor,
82
+ } = yield* LeaderThreadCtx
151
83
 
152
84
  type RequestId = string
153
85
  const subscriptionFiberMap = yield* FiberMap.make<RequestId>()
@@ -158,15 +90,6 @@ const listenToDevtools = ({
158
90
  // yield* Effect.logDebug('[@livestore/common:leader-thread:devtools] incomingMessage', decodedEvent)
159
91
 
160
92
  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
93
  return
171
94
  }
172
95
 
@@ -179,7 +102,7 @@ const listenToDevtools = ({
179
102
  return
180
103
  }
181
104
  case 'LSD.Leader.SnapshotReq': {
182
- const snapshot = db.export()
105
+ const snapshot = dbReadModel.export()
183
106
 
184
107
  yield* sendMessage(Devtools.SnapshotRes.make({ snapshot, ...reqPayload }))
185
108
 
@@ -191,15 +114,15 @@ const listenToDevtools = ({
191
114
  let tableNames: Set<string>
192
115
 
193
116
  try {
194
- const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
195
- tmpSyncDb.import(data)
196
- const tableNameResults = tmpSyncDb.select<{ name: string }>(
117
+ const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
118
+ tmpDb.import(data)
119
+ const tableNameResults = tmpDb.select<{ name: string }>(
197
120
  `select name from sqlite_master where type = 'table'`,
198
121
  )
199
122
 
200
123
  tableNames = new Set(tableNameResults.map((_) => _.name))
201
124
 
202
- tmpSyncDb.close()
125
+ tmpDb.close()
203
126
  } catch (e) {
204
127
  yield* Effect.logError(`Error importing database file`, e)
205
128
  yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-file' }))
@@ -210,15 +133,15 @@ const listenToDevtools = ({
210
133
  if (tableNames.has(MUTATION_LOG_META_TABLE)) {
211
134
  yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
212
135
 
213
- dbLog.import(data)
136
+ dbMutationLog.import(data)
214
137
 
215
- db.destroy()
138
+ dbReadModel.destroy()
216
139
  } else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
217
140
  yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
218
141
 
219
- db.import(data)
142
+ dbReadModel.import(data)
220
143
 
221
- dbLog.destroy()
144
+ dbMutationLog.destroy()
222
145
  } else {
223
146
  yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-database' }))
224
147
  return
@@ -226,7 +149,7 @@ const listenToDevtools = ({
226
149
 
227
150
  yield* sendMessage(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
228
151
 
229
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' }))
152
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-import' })) ?? Effect.void
230
153
 
231
154
  return
232
155
  }
@@ -235,26 +158,31 @@ const listenToDevtools = ({
235
158
 
236
159
  yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
237
160
 
238
- db.destroy()
161
+ dbReadModel.destroy()
239
162
 
240
163
  if (mode === 'all-data') {
241
- dbLog.destroy()
164
+ dbMutationLog.destroy()
242
165
  }
243
166
 
244
167
  yield* sendMessage(Devtools.ResetAllDataRes.make({ ...reqPayload }))
245
168
 
246
- yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' }))
169
+ yield* shutdownChannel.send(IntentionalShutdownCause.make({ reason: 'devtools-reset' })) ?? Effect.void
247
170
 
248
171
  return
249
172
  }
250
173
  case 'LSD.Leader.DatabaseFileInfoReq': {
174
+ if (persistenceInfo === undefined) {
175
+ console.log('[@livestore/common:leader-thread:devtools] persistenceInfo is required for this request')
176
+ return
177
+ }
178
+
251
179
  const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
252
- const dbFileSize = db.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
253
- const mutationLogFileSize = dbLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
180
+ const dbFileSize = dbReadModel.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
181
+ const mutationLogFileSize = dbMutationLog.select<{ size: number }>(dbSizeQuery, undefined)[0]!.size
254
182
 
255
183
  yield* sendMessage(
256
184
  Devtools.DatabaseFileInfoRes.make({
257
- db: { fileSize: dbFileSize, persistenceInfo: persistenceInfo.db },
185
+ readModel: { fileSize: dbFileSize, persistenceInfo: persistenceInfo.readModel },
258
186
  mutationLog: { fileSize: mutationLogFileSize, persistenceInfo: persistenceInfo.mutationLog },
259
187
  ...reqPayload,
260
188
  }),
@@ -263,7 +191,7 @@ const listenToDevtools = ({
263
191
  return
264
192
  }
265
193
  case 'LSD.Leader.MutationLogReq': {
266
- const mutationLog = dbLog.export()
194
+ const mutationLog = dbMutationLog.export()
267
195
 
268
196
  yield* sendMessage(Devtools.MutationLogRes.make({ mutationLog, ...reqPayload }))
269
197
 
@@ -1,68 +1,100 @@
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
- import type { BootStatus, MakeSynchronousDatabase, SqliteError, SynchronousDatabase } from '../adapter-types.js'
4
+ import type { BootStatus, MakeSqliteDb, SqliteError } 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'
9
- import type { InvalidPullError, IsOfflineError, SyncBackend } from '../sync/sync.js'
10
+ import type { InvalidPullError, IsOfflineError, SyncOptions } from '../sync/sync.js'
10
11
  import { sql } from '../util.js'
11
12
  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'
14
15
  import { makePullQueueSet } from './pull-queue-set.js'
15
16
  import { recreateDb } from './recreate-db.js'
16
- import type { DevtoolsOptions, InitialBlockingSyncContext, InitialSyncOptions, ShutdownState } from './types.js'
17
+ import type { ShutdownChannel } from './shutdown-channel.js'
18
+ import type {
19
+ DevtoolsOptions,
20
+ InitialBlockingSyncContext,
21
+ InitialSyncOptions,
22
+ LeaderSqliteDb,
23
+ ShutdownState,
24
+ } from './types.js'
17
25
  import { LeaderThreadCtx } from './types.js'
18
26
 
19
27
  export const makeLeaderThreadLayer = ({
20
28
  schema,
21
29
  storeId,
22
- originId,
23
- makeSyncDb,
24
- makeSyncBackend,
25
- db,
26
- dbLog,
30
+ clientId,
31
+ makeSqliteDb,
32
+ syncOptions,
33
+ dbReadModel,
34
+ dbMutationLog,
27
35
  devtoolsOptions,
28
- initialSyncOptions = { _tag: 'Skip' },
36
+ shutdownChannel,
29
37
  }: {
30
38
  storeId: string
31
- originId: string
39
+ clientId: string
32
40
  schema: LiveStoreSchema
33
- makeSyncDb: MakeSynchronousDatabase
34
- makeSyncBackend: Effect.Effect<SyncBackend, UnexpectedError, Scope.Scope> | undefined
35
- db: SynchronousDatabase
36
- dbLog: SynchronousDatabase
41
+ makeSqliteDb: MakeSqliteDb
42
+ syncOptions: SyncOptions | undefined
43
+ dbReadModel: LeaderSqliteDb
44
+ dbMutationLog: LeaderSqliteDb
37
45
  devtoolsOptions: DevtoolsOptions
38
- initialSyncOptions: InitialSyncOptions | undefined
46
+ shutdownChannel: ShutdownChannel
39
47
  }): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
40
48
  Effect.gen(function* () {
41
49
  const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
42
50
 
43
51
  // TODO do more validation here than just checking the count of tables
44
52
  // Either happens on initial boot or if schema changes
45
- const dbMissing = db.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
53
+ const dbMissing =
54
+ dbReadModel.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
46
55
 
47
- const syncBackend = makeSyncBackend === undefined ? undefined : yield* makeSyncBackend
56
+ const syncBackend = syncOptions === undefined ? undefined : yield* syncOptions.makeBackend({ storeId, clientId })
48
57
 
49
- const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({ initialSyncOptions, bootStatusQueue })
58
+ const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
59
+ initialSyncOptions: syncOptions?.initialSyncOptions ?? { _tag: 'Skip' },
60
+ bootStatusQueue,
61
+ })
62
+
63
+ const syncProcessor = yield* makeLeaderSyncProcessor({
64
+ schema,
65
+ dbMissing,
66
+ dbMutationLog,
67
+ initialBlockingSyncContext,
68
+ })
69
+
70
+ const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.MessageToAppLeader>().pipe(
71
+ Effect.acquireRelease(Queue.shutdown),
72
+ )
50
73
 
51
- const syncProcessor = yield* makeLeaderSyncProcessor({ schema, dbMissing, dbLog, initialBlockingSyncContext })
74
+ const devtoolsContext = devtoolsOptions.enabled
75
+ ? {
76
+ enabled: true as const,
77
+ syncBackendPullLatch: yield* Effect.makeLatch(true),
78
+ syncBackendPushLatch: yield* Effect.makeLatch(true),
79
+ }
80
+ : { enabled: false as const }
52
81
 
53
82
  const ctx = {
54
83
  schema,
55
84
  bootStatusQueue,
56
85
  storeId,
57
- originId,
58
- db,
59
- dbLog,
60
- makeSyncDb,
86
+ clientId,
87
+ dbReadModel,
88
+ dbMutationLog,
89
+ makeSqliteDb,
61
90
  mutationEventSchema: MutationEvent.makeMutationEventSchema(schema),
62
91
  shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
92
+ shutdownChannel,
63
93
  syncBackend,
64
94
  syncProcessor,
65
95
  connectedClientSessionPullQueues: yield* makePullQueueSet,
96
+ extraIncomingMessagesQueue,
97
+ devtools: devtoolsContext,
66
98
  } satisfies typeof LeaderThreadCtx.Service
67
99
 
68
100
  // @ts-expect-error For debugging purposes
@@ -145,17 +177,17 @@ const bootLeaderThread = ({
145
177
  LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
146
178
  > =>
147
179
  Effect.gen(function* () {
148
- const { dbLog, bootStatusQueue, syncProcessor } = yield* LeaderThreadCtx
180
+ const { dbMutationLog, bootStatusQueue, syncProcessor } = yield* LeaderThreadCtx
149
181
 
150
182
  yield* migrateTable({
151
- db: dbLog,
183
+ db: dbMutationLog,
152
184
  behaviour: 'create-if-not-exists',
153
185
  tableAst: mutationLogMetaTable.sqliteDef.ast,
154
186
  skipMetaTable: true,
155
187
  })
156
188
 
157
189
  yield* migrateTable({
158
- db: dbLog,
190
+ db: dbMutationLog,
159
191
  behaviour: 'create-if-not-exists',
160
192
  tableAst: syncStatusTable.sqliteDef.ast,
161
193
  skipMetaTable: true,
@@ -163,7 +195,7 @@ const bootLeaderThread = ({
163
195
 
164
196
  // Create sync status row if it doesn't exist
165
197
  yield* execSql(
166
- dbLog,
198
+ dbMutationLog,
167
199
  sql`INSERT INTO ${SYNC_STATUS_TABLE} (head)
168
200
  SELECT ${EventId.ROOT.global}
169
201
  WHERE NOT EXISTS (SELECT 1 FROM ${SYNC_STATUS_TABLE})`,