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

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 (113) 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/derived-mutations.d.ts +4 -4
  6. package/dist/derived-mutations.d.ts.map +1 -1
  7. package/dist/derived-mutations.test.js.map +1 -1
  8. package/dist/devtools/devtools-bridge.d.ts +2 -1
  9. package/dist/devtools/devtools-bridge.d.ts.map +1 -1
  10. package/dist/devtools/devtools-messages.d.ts +90 -102
  11. package/dist/devtools/devtools-messages.d.ts.map +1 -1
  12. package/dist/devtools/devtools-messages.js +9 -6
  13. package/dist/devtools/devtools-messages.js.map +1 -1
  14. package/dist/leader-thread/apply-mutation.d.ts +5 -2
  15. package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
  16. package/dist/leader-thread/apply-mutation.js +37 -25
  17. package/dist/leader-thread/apply-mutation.js.map +1 -1
  18. package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
  19. package/dist/leader-thread/leader-sync-processor.js +20 -12
  20. package/dist/leader-thread/leader-sync-processor.js.map +1 -1
  21. package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
  22. package/dist/leader-thread/leader-worker-devtools.js +22 -66
  23. package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
  24. package/dist/leader-thread/make-leader-thread-layer.d.ts +4 -2
  25. package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
  26. package/dist/leader-thread/make-leader-thread-layer.js +5 -2
  27. package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
  28. package/dist/leader-thread/mutationlog.d.ts +4 -17
  29. package/dist/leader-thread/mutationlog.d.ts.map +1 -1
  30. package/dist/leader-thread/mutationlog.js +2 -1
  31. package/dist/leader-thread/mutationlog.js.map +1 -1
  32. package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
  33. package/dist/leader-thread/recreate-db.d.ts.map +1 -1
  34. package/dist/leader-thread/recreate-db.js +9 -3
  35. package/dist/leader-thread/recreate-db.js.map +1 -1
  36. package/dist/leader-thread/types.d.ts +7 -5
  37. package/dist/leader-thread/types.d.ts.map +1 -1
  38. package/dist/leader-thread/types.js.map +1 -1
  39. package/dist/mutation.d.ts +9 -2
  40. package/dist/mutation.d.ts.map +1 -1
  41. package/dist/mutation.js +5 -5
  42. package/dist/mutation.js.map +1 -1
  43. package/dist/query-builder/impl.d.ts +1 -1
  44. package/dist/rehydrate-from-mutationlog.d.ts +2 -2
  45. package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
  46. package/dist/rehydrate-from-mutationlog.js +13 -19
  47. package/dist/rehydrate-from-mutationlog.js.map +1 -1
  48. package/dist/schema/EventId.d.ts +16 -14
  49. package/dist/schema/EventId.d.ts.map +1 -1
  50. package/dist/schema/EventId.js +15 -7
  51. package/dist/schema/EventId.js.map +1 -1
  52. package/dist/schema/MutationEvent.d.ts +49 -74
  53. package/dist/schema/MutationEvent.d.ts.map +1 -1
  54. package/dist/schema/MutationEvent.js +29 -13
  55. package/dist/schema/MutationEvent.js.map +1 -1
  56. package/dist/schema/system-tables.d.ts +26 -26
  57. package/dist/schema/system-tables.d.ts.map +1 -1
  58. package/dist/schema/system-tables.js +19 -11
  59. package/dist/schema/system-tables.js.map +1 -1
  60. package/dist/schema-management/migrations.js +6 -6
  61. package/dist/schema-management/migrations.js.map +1 -1
  62. package/dist/sync/client-session-sync-processor.d.ts +4 -4
  63. package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
  64. package/dist/sync/next/history-dag-common.d.ts +1 -4
  65. package/dist/sync/next/history-dag-common.d.ts.map +1 -1
  66. package/dist/sync/next/history-dag-common.js +1 -1
  67. package/dist/sync/next/history-dag-common.js.map +1 -1
  68. package/dist/sync/next/rebase-events.d.ts +3 -3
  69. package/dist/sync/next/rebase-events.d.ts.map +1 -1
  70. package/dist/sync/next/rebase-events.js +3 -2
  71. package/dist/sync/next/rebase-events.js.map +1 -1
  72. package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
  73. package/dist/sync/next/test/mutation-fixtures.js +3 -9
  74. package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
  75. package/dist/sync/sync.d.ts +6 -6
  76. package/dist/sync/sync.d.ts.map +1 -1
  77. package/dist/sync/sync.js.map +1 -1
  78. package/dist/sync/syncstate.d.ts +10 -10
  79. package/dist/sync/syncstate.test.js +2 -6
  80. package/dist/sync/syncstate.test.js.map +1 -1
  81. package/dist/sync/validate-push-payload.d.ts +2 -2
  82. package/dist/sync/validate-push-payload.d.ts.map +1 -1
  83. package/dist/sync/validate-push-payload.js +2 -2
  84. package/dist/sync/validate-push-payload.js.map +1 -1
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/package.json +6 -5
  88. package/src/adapter-types.ts +22 -24
  89. package/src/derived-mutations.test.ts +1 -1
  90. package/src/derived-mutations.ts +9 -5
  91. package/src/devtools/devtools-bridge.ts +2 -1
  92. package/src/devtools/devtools-messages.ts +9 -6
  93. package/src/leader-thread/apply-mutation.ts +48 -30
  94. package/src/leader-thread/leader-sync-processor.ts +26 -15
  95. package/src/leader-thread/leader-worker-devtools.ts +30 -109
  96. package/src/leader-thread/make-leader-thread-layer.ts +15 -5
  97. package/src/leader-thread/mutationlog.ts +9 -5
  98. package/src/leader-thread/recreate-db.ts +9 -5
  99. package/src/leader-thread/types.ts +7 -8
  100. package/src/mutation.ts +17 -7
  101. package/src/rehydrate-from-mutationlog.ts +15 -23
  102. package/src/schema/EventId.ts +23 -9
  103. package/src/schema/MutationEvent.ts +40 -20
  104. package/src/schema/system-tables.ts +19 -11
  105. package/src/schema-management/migrations.ts +6 -6
  106. package/src/sync/client-session-sync-processor.ts +4 -4
  107. package/src/sync/next/history-dag-common.ts +1 -1
  108. package/src/sync/next/rebase-events.ts +7 -7
  109. package/src/sync/next/test/mutation-fixtures.ts +3 -10
  110. package/src/sync/sync.ts +4 -2
  111. package/src/sync/syncstate.test.ts +4 -4
  112. package/src/sync/validate-push-payload.ts +7 -4
  113. package/src/version.ts +1 -1
@@ -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) =>
@@ -24,7 +24,9 @@ export const recreateDb: Effect.Effect<
24
24
 
25
25
  // NOTE to speed up the operations below, we're creating a temporary in-memory database
26
26
  // and later we'll overwrite the persisted database with the new data
27
- const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
27
+ // TODO bring back this optimization
28
+ // const tmpSyncDb = yield* makeSyncDb({ _tag: 'in-memory' })
29
+ const tmpSyncDb = db
28
30
  yield* configureConnection(tmpSyncDb, { fkEnabled: true })
29
31
 
30
32
  const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
@@ -91,17 +93,19 @@ export const recreateDb: Effect.Effect<
91
93
  }
92
94
  }
93
95
 
96
+ // TODO bring back
94
97
  // Import the temporary in-memory database into the persistent database
95
- yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
96
- Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
97
- )
98
+ // yield* Effect.sync(() => db.import(tmpSyncDb)).pipe(
99
+ // Effect.withSpan('@livestore/common:leader-thread:recreateDb:import'),
100
+ // )
98
101
 
99
102
  // TODO maybe bring back re-using this initial snapshot to avoid calling `.export()` again
100
103
  // We've disabled this for now as it made the code too complex, as we often run syncing right after
101
104
  // so the snapshot is no longer up to date
102
105
  // const snapshotFromTmpDb = tmpSyncDb.export()
103
106
 
104
- tmpSyncDb.close()
107
+ // TODO bring back
108
+ // tmpSyncDb.close()
105
109
  }).pipe(
106
110
  Effect.scoped, // NOTE we're closing the scope here so finalizers are called when the effect is done
107
111
  Effect.withSpan('@livestore/common:leader-thread:recreateDb'),
@@ -22,7 +22,7 @@ import type {
22
22
  UnexpectedError,
23
23
  } from '../index.js'
24
24
  import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
25
- import type { PayloadUpstream, SyncState } from '../sync/syncstate.js'
25
+ import type * as SyncState from '../sync/syncstate.js'
26
26
  import type { ShutdownChannel } from './shutdown-channel.js'
27
27
 
28
28
  export type ShutdownState = 'running' | 'shutting-down'
@@ -67,7 +67,6 @@ export type DevtoolsOptions =
67
67
  makeContext: Effect.Effect<
68
68
  {
69
69
  devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
70
- shutdownChannel: ShutdownChannel
71
70
  persistenceInfo: PersistenceInfoPair
72
71
  },
73
72
  UnexpectedError,
@@ -80,18 +79,21 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
80
79
  {
81
80
  schema: LiveStoreSchema
82
81
  storeId: string
83
- originId: string
82
+ clientId: string
84
83
  makeSyncDb: MakeSynchronousDatabase
85
84
  db: LeaderDatabase
86
85
  dbLog: LeaderDatabase
87
86
  bootStatusQueue: Queue.Queue<BootStatus>
88
87
  // TODO we should find a more elegant way to handle cases which need this ref for their implementation
89
88
  shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
89
+ shutdownChannel: ShutdownChannel
90
90
  mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
91
91
  // devtools: DevtoolsContext
92
92
  syncBackend: SyncBackend | undefined
93
93
  syncProcessor: SyncProcessor
94
94
  connectedClientSessionPullQueues: PullQueueSet
95
+ /** e.g. used for `store.__dev` APIs */
96
+ extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
95
97
  }
96
98
  >() {}
97
99
 
@@ -101,10 +103,7 @@ export type InitialBlockingSyncContext = {
101
103
  }
102
104
 
103
105
  export type PullQueueItem = {
104
- // mutationEvents: ReadonlyArray<MutationEvent.AnyEncoded>
105
- // backendHead: number
106
- payload: PayloadUpstream
107
- // TODO move `remaining` into `PayloadUpstream`
106
+ payload: SyncState.PayloadUpstream
108
107
  remaining: number
109
108
  }
110
109
 
@@ -118,7 +117,7 @@ export interface SyncProcessor {
118
117
  boot: (args: {
119
118
  dbReady: Deferred.Deferred<void>
120
119
  }) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
121
- syncState: Effect.Effect<SyncState, UnexpectedError>
120
+ syncState: Effect.Effect<SyncState.SyncState, UnexpectedError>
122
121
  }
123
122
 
124
123
  export interface PullQueueSet {
package/src/mutation.ts CHANGED
@@ -8,10 +8,19 @@ import { prepareBindValues } from './util.js'
8
8
 
9
9
  export const getExecArgsFromMutation = ({
10
10
  mutationDef,
11
- mutationEventDecoded,
11
+ mutationEvent,
12
12
  }: {
13
13
  mutationDef: MutationDef.Any
14
- mutationEventDecoded: MutationEvent.Any | MutationEvent.PartialAny
14
+ /** Both encoded and decoded mutation events are supported to reduce the number of times we need to decode/encode */
15
+ mutationEvent:
16
+ | {
17
+ decoded: MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded
18
+ encoded: undefined
19
+ }
20
+ | {
21
+ decoded: undefined
22
+ encoded: MutationEvent.AnyEncoded | MutationEvent.PartialAnyEncoded
23
+ }
15
24
  }): ReadonlyArray<{
16
25
  statementSql: string
17
26
  bindValues: PreparedBindValues
@@ -23,7 +32,9 @@ export const getExecArgsFromMutation = ({
23
32
 
24
33
  switch (typeof mutationDef.sql) {
25
34
  case 'function': {
26
- const res = mutationDef.sql(mutationEventDecoded.args)
35
+ const mutationArgsDecoded =
36
+ mutationEvent.decoded?.args ?? Schema.decodeUnknownSync(mutationDef.schema)(mutationEvent.encoded!.args)
37
+ const res = mutationDef.sql(mutationArgsDecoded)
27
38
  statementRes = Array.isArray(res) ? res : [res]
28
39
  break
29
40
  }
@@ -40,10 +51,9 @@ export const getExecArgsFromMutation = ({
40
51
  return statementRes.map((statementRes) => {
41
52
  const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
42
53
 
43
- const bindValues =
44
- typeof statementRes === 'string'
45
- ? Schema.encodeUnknownSync(mutationDef.schema)(mutationEventDecoded.args)
46
- : statementRes.bindValues
54
+ const mutationArgsEncoded =
55
+ mutationEvent.encoded?.args ?? Schema.encodeUnknownSync(mutationDef.schema)(mutationEvent.decoded!.args)
56
+ const bindValues = typeof statementRes === 'string' ? mutationArgsEncoded : statementRes.bindValues
47
57
 
48
58
  const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
49
59
 
@@ -1,8 +1,8 @@
1
- import { isDevEnv, memoizeByRef, shouldNeverHappen } from '@livestore/utils'
1
+ import { memoizeByRef, shouldNeverHappen } from '@livestore/utils'
2
2
  import { Chunk, Effect, Option, Schema, Stream } from '@livestore/utils/effect'
3
3
 
4
4
  import { type MigrationOptionsFromMutationLog, type SynchronousDatabase, UnexpectedError } from './adapter-types.js'
5
- import { getExecArgsFromMutation } from './mutation.js'
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'
8
8
  import type { PreparedBindValues } from './util.js'
@@ -10,7 +10,8 @@ import { sql } from './util.js'
10
10
 
11
11
  export const rehydrateFromMutationLog = ({
12
12
  logDb,
13
- db,
13
+ // TODO re-use this db when bringing back the boot in-memory db implementation
14
+ // db,
14
15
  schema,
15
16
  migrationOptions,
16
17
  onProgress,
@@ -28,6 +29,8 @@ export const rehydrateFromMutationLog = ({
28
29
 
29
30
  const hashMutation = memoizeByRef((mutation: MutationDef.Any) => Schema.hash(mutation.schema))
30
31
 
32
+ const applyMutation = yield* makeApplyMutation
33
+
31
34
  const processMutation = (row: MutationLogMetaRow) =>
32
35
  Effect.gen(function* () {
33
36
  const mutationDef = schema.mutations.get(row.mutation) ?? shouldNeverHappen(`Unknown mutation ${row.mutation}`)
@@ -40,7 +43,10 @@ export const rehydrateFromMutationLog = ({
40
43
  )
41
44
  }
42
45
 
43
- const argsDecoded = yield* Schema.decodeUnknown(Schema.parseJson(mutationDef.schema))(row.argsJson).pipe(
46
+ const args = JSON.parse(row.argsJson)
47
+
48
+ // Checking whether the schema has changed in an incompatible way
49
+ yield* Schema.decodeUnknown(mutationDef.schema)(args).pipe(
44
50
  Effect.mapError((cause) =>
45
51
  UnexpectedError.make({
46
52
  cause,
@@ -53,28 +59,14 @@ This likely means the schema has changed in an incompatible way.
53
59
  ),
54
60
  )
55
61
 
56
- const mutationEventDecoded = {
62
+ const mutationEventEncoded = {
57
63
  id: { global: row.idGlobal, local: row.idLocal },
58
64
  parentId: { global: row.parentIdGlobal, local: row.parentIdLocal },
59
65
  mutation: row.mutation,
60
- args: argsDecoded,
61
- } satisfies MutationEvent.Any
62
-
63
- const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
64
-
65
- const makeExecuteOptions = (statementSql: string, bindValues: any) => ({
66
- onRowsChanged: (rowsChanged: number) => {
67
- if (rowsChanged === 0 && migrationOptions.logging?.excludeAffectedRows?.(statementSql) !== true) {
68
- console.warn(`Mutation "${mutationDef.name}" did not affect any rows:`, statementSql, bindValues)
69
- }
70
- },
71
- })
72
-
73
- for (const { statementSql, bindValues } of execArgsArr) {
74
- // TODO cache prepared statements for mutations
75
- db.execute(statementSql, bindValues, isDevEnv() ? makeExecuteOptions(statementSql, bindValues) : undefined)
76
- // console.log(`Re-executed mutation ${mutationSql}`, bindValues)
77
- }
66
+ args,
67
+ } satisfies MutationEvent.AnyEncoded
68
+
69
+ yield* applyMutation(mutationEventEncoded, { skipMutationLog: true })
78
70
  }).pipe(Effect.withSpan(`@livestore/common:rehydrateFromMutationLog:processMutation`))
79
71
 
80
72
  const CHUNK_SIZE = 100
@@ -1,4 +1,14 @@
1
- import { Schema } from '@livestore/utils/effect'
1
+ import { Brand, Schema } from '@livestore/utils/effect'
2
+
3
+ export type LocalEventId = Brand.Branded<number, 'LocalEventId'>
4
+ export const localEventId = Brand.nominal<LocalEventId>()
5
+ export const LocalEventId = Schema.fromBrand(localEventId)(Schema.Int)
6
+
7
+ export type GlobalEventId = Brand.Branded<number, 'GlobalEventId'>
8
+ export const globalEventId = Brand.nominal<GlobalEventId>()
9
+ export const GlobalEventId = Schema.fromBrand(globalEventId)(Schema.Int)
10
+
11
+ export const localDefault = 0 as any as LocalEventId
2
12
 
3
13
  /**
4
14
  * LiveStore event id value consisting of a globally unique event sequence number
@@ -6,11 +16,11 @@ import { Schema } from '@livestore/utils/effect'
6
16
  *
7
17
  * The local sequence number is only used for localOnly mutations and starts from 0 for each global sequence number.
8
18
  */
9
- export type EventId = { global: number; local: number }
19
+ export type EventId = { global: GlobalEventId; local: LocalEventId }
10
20
 
11
21
  export const EventId = Schema.Struct({
12
- global: Schema.Number,
13
- local: Schema.Number,
22
+ global: GlobalEventId,
23
+ local: LocalEventId,
14
24
  }).annotations({ title: 'LiveStore.EventId' })
15
25
 
16
26
  /**
@@ -27,20 +37,24 @@ export const isEqual = (a: EventId, b: EventId) => a.global === b.global && a.lo
27
37
 
28
38
  export type EventIdPair = { id: EventId; parentId: EventId }
29
39
 
30
- export const ROOT = { global: -1, local: 0 } satisfies EventId
40
+ export const ROOT = { global: -1 as any as GlobalEventId, local: localDefault } satisfies EventId
31
41
 
32
42
  export const isGreaterThan = (a: EventId, b: EventId) => {
33
43
  return a.global > b.global || (a.global === b.global && a.local > b.local)
34
44
  }
35
45
 
36
- export const nextPair = (id: EventId, isLocal: boolean) => {
46
+ export const make = (id: EventId | typeof EventId.Encoded): EventId => {
47
+ return Schema.is(EventId)(id) ? id : Schema.decodeSync(EventId)(id)
48
+ }
49
+
50
+ export const nextPair = (id: EventId, isLocal: boolean): EventIdPair => {
37
51
  if (isLocal) {
38
- return { id: { global: id.global, local: id.local + 1 }, parentId: id }
52
+ return { id: { global: id.global, local: (id.local + 1) as any as LocalEventId }, parentId: id }
39
53
  }
40
54
 
41
55
  return {
42
- id: { global: id.global + 1, local: 0 },
56
+ id: { global: (id.global + 1) as any as GlobalEventId, local: localDefault },
43
57
  // NOTE we always point to `local: 0` for non-localOnly mutations
44
- parentId: { global: id.global, local: 0 },
58
+ parentId: { global: id.global, local: localDefault },
45
59
  }
46
60
  }
@@ -30,10 +30,31 @@ export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
30
30
  parentId: EventId.EventId
31
31
  }
32
32
 
33
- export type Any = MutationEvent<MutationDef.Any>
33
+ export type AnyDecoded = MutationEvent<MutationDef.Any>
34
+ export const AnyDecoded = Schema.Struct({
35
+ mutation: Schema.String,
36
+ args: Schema.Any,
37
+ id: EventId.EventId,
38
+ parentId: EventId.EventId,
39
+ }).annotations({ title: 'MutationEvent.AnyDecoded' })
40
+
34
41
  export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
42
+ export const AnyEncoded = Schema.Struct({
43
+ mutation: Schema.String,
44
+ args: Schema.Any,
45
+ id: EventId.EventId,
46
+ parentId: EventId.EventId,
47
+ }).annotations({ title: 'MutationEvent.AnyEncoded' })
48
+
49
+ export const AnyEncodedGlobal = Schema.Struct({
50
+ mutation: Schema.String,
51
+ args: Schema.Any,
52
+ id: EventId.GlobalEventId,
53
+ parentId: EventId.GlobalEventId,
54
+ }).annotations({ title: 'MutationEvent.AnyEncodedGlobal' })
55
+ export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
35
56
 
36
- export type PartialAny = MutationEventPartial<MutationDef.Any>
57
+ export type PartialAnyDecoded = MutationEventPartial<MutationDef.Any>
37
58
  export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
38
59
 
39
60
  export type PartialForSchema<TSchema extends LiveStoreSchema> = {
@@ -44,8 +65,9 @@ export type ForSchema<TSchema extends LiveStoreSchema> = {
44
65
  [K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
45
66
  }[keyof TSchema['_MutationDefMapType']]
46
67
 
47
- export const isPartialMutationEvent = (mutationEvent: Any | PartialAny): mutationEvent is PartialAny =>
48
- 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
68
+ export const isPartialMutationEvent = (
69
+ mutationEvent: AnyDecoded | PartialAnyDecoded,
70
+ ): mutationEvent is PartialAnyDecoded => 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
49
71
 
50
72
  export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
51
73
  {
@@ -109,22 +131,7 @@ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
109
131
 
110
132
  export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
111
133
 
112
- export const Any = Schema.Struct({
113
- mutation: Schema.String,
114
- args: Schema.Any,
115
- id: EventId.EventId,
116
- parentId: EventId.EventId,
117
- }).annotations({ title: 'MutationEvent.Any' })
118
-
119
- export const DecodedAny = Schema.typeSchema(Any).annotations({
120
- title: 'MutationEvent.DecodedAny',
121
- })
122
-
123
- export const EncodedAny = Schema.encodedSchema(Any).annotations({
124
- title: 'MutationEvent.EncodedAny',
125
- })
126
-
127
- /** Equivalent to EncodedAny but with a meta field and some convenience methods */
134
+ /** Equivalent to AnyEncoded but with a meta field and some convenience methods */
128
135
  export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEvent.EncodedWithMeta')({
129
136
  mutation: Schema.String,
130
137
  args: Schema.Any,
@@ -151,6 +158,19 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
151
158
  ...this,
152
159
  ...EventId.nextPair(this.id, isLocal),
153
160
  })
161
+
162
+ static fromGlobal = (mutationEvent: AnyEncodedGlobal) =>
163
+ new EncodedWithMeta({
164
+ ...mutationEvent,
165
+ id: { global: mutationEvent.id, local: EventId.localDefault },
166
+ parentId: { global: mutationEvent.parentId, local: EventId.localDefault },
167
+ })
168
+
169
+ toGlobal = (): AnyEncodedGlobal => ({
170
+ ...this,
171
+ id: this.id.global,
172
+ parentId: this.parentId.global,
173
+ })
154
174
  }
155
175
 
156
176
  export const isEqualEncoded = (a: AnyEncoded, b: AnyEncoded) =>
@@ -1,6 +1,7 @@
1
1
  import { type SqliteAst as __SqliteAst, SqliteDsl } from '@livestore/db-schema'
2
2
  import { Schema } from '@livestore/utils/effect'
3
3
 
4
+ import * as EventId from './EventId.js'
4
5
  import type { FromTable } from './table-def.js'
5
6
  import { table } from './table-def.js'
6
7
 
@@ -46,14 +47,15 @@ export const sessionChangesetMetaTable = table(
46
47
  SESSION_CHANGESET_META_TABLE,
47
48
  {
48
49
  // TODO bring back primary key
49
- idGlobal: SqliteDsl.integer({}),
50
- idLocal: SqliteDsl.integer({}),
51
- // idGlobal: SqliteDsl.integer({ primaryKey: true }),
52
- // idLocal: SqliteDsl.integer({ primaryKey: true }),
53
- changeset: SqliteDsl.blob({}),
50
+ idGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
51
+ idLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
52
+ changeset: SqliteDsl.blob({ nullable: true }),
54
53
  debug: SqliteDsl.json({ nullable: true }),
55
54
  },
56
- { disableAutomaticIdColumn: true },
55
+ {
56
+ disableAutomaticIdColumn: true,
57
+ indexes: [{ columns: ['idGlobal', 'idLocal'], name: 'idx_session_changeset_id' }],
58
+ },
57
59
  )
58
60
 
59
61
  export type SessionChangesetMetaRow = FromTable.RowDecoded<typeof sessionChangesetMetaTable>
@@ -70,16 +72,22 @@ export const MUTATION_LOG_META_TABLE = 'mutation_log'
70
72
  export const mutationLogMetaTable = table(
71
73
  MUTATION_LOG_META_TABLE,
72
74
  {
73
- idGlobal: SqliteDsl.integer({ primaryKey: true }),
74
- idLocal: SqliteDsl.integer({ primaryKey: true }),
75
- parentIdGlobal: SqliteDsl.integer({}),
76
- parentIdLocal: SqliteDsl.integer({}),
75
+ idGlobal: SqliteDsl.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
76
+ idLocal: SqliteDsl.integer({ primaryKey: true, schema: EventId.LocalEventId }),
77
+ parentIdGlobal: SqliteDsl.integer({ schema: EventId.GlobalEventId }),
78
+ parentIdLocal: SqliteDsl.integer({ schema: EventId.LocalEventId }),
77
79
  mutation: SqliteDsl.text({}),
78
80
  argsJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Any) }),
79
81
  schemaHash: SqliteDsl.integer({}),
80
82
  syncMetadataJson: SqliteDsl.text({ schema: Schema.parseJson(Schema.Option(Schema.JsonValue)) }),
81
83
  },
82
- { disableAutomaticIdColumn: true, indexes: [] },
84
+ {
85
+ disableAutomaticIdColumn: true,
86
+ indexes: [
87
+ { columns: ['idGlobal'], name: 'idx_idGlobal' },
88
+ { columns: ['idGlobal', 'idLocal'], name: 'idx_mutationlog_id' },
89
+ ],
90
+ },
83
91
  )
84
92
 
85
93
  export type MutationLogMetaRow = FromTable.RowDecoded<typeof mutationLogMetaTable>
@@ -129,10 +129,10 @@ export const migrateTable = ({
129
129
 
130
130
  if (behaviour === 'drop-and-recreate') {
131
131
  // TODO need to possibly handle cascading deletes due to foreign keys
132
- dbExecute(db, sql`drop table if exists ${tableName}`)
133
- dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
132
+ dbExecute(db, sql`drop table if exists '${tableName}'`)
133
+ dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
134
134
  } else if (behaviour === 'create-if-not-exists') {
135
- dbExecute(db, sql`create table if not exists ${tableName} (${columnSpec}) strict`)
135
+ dbExecute(db, sql`create table if not exists '${tableName}' (${columnSpec}) strict`)
136
136
  }
137
137
 
138
138
  for (const index of tableAst.indexes) {
@@ -162,11 +162,11 @@ export const migrateTable = ({
162
162
 
163
163
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
164
164
  const uniqueStr = index.unique ? 'UNIQUE' : ''
165
- return sql`create ${uniqueStr} index if not exists ${index.name} on ${tableName} (${index.columns.join(', ')})`
165
+ return sql`create ${uniqueStr} index if not exists '${index.name}' on '${tableName}' (${index.columns.join(', ')})`
166
166
  }
167
167
 
168
168
  export const makeColumnSpec = (tableAst: SqliteAst.Table) => {
169
- const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => _.name)
169
+ const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => `'${_.name}'`)
170
170
  const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
171
171
  if (primaryKeys.length > 0) {
172
172
  columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
@@ -191,5 +191,5 @@ const toSqliteColumnSpec = (column: SqliteAst.Column) => {
191
191
  return `default ${encodedDefaultValue}`
192
192
  })()
193
193
 
194
- return `${column.name} ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
194
+ return `'${column.name}' ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
195
195
  }
@@ -3,7 +3,7 @@ import type { Scope } from '@livestore/utils/effect'
3
3
  import { Effect, Schema, Stream } from '@livestore/utils/effect'
4
4
  import * as otel from '@opentelemetry/api'
5
5
 
6
- import type { Coordinator, UnexpectedError } from '../adapter-types.js'
6
+ import type { ClientSessionLeaderThreadProxy, 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'
@@ -32,9 +32,9 @@ export const makeClientSessionSyncProcessor = ({
32
32
  schema: LiveStoreSchema
33
33
  initialLeaderHead: EventId.EventId
34
34
  pushToLeader: (batch: ReadonlyArray<MutationEvent.AnyEncoded>) => void
35
- pullFromLeader: Coordinator['mutations']['pull']
35
+ pullFromLeader: ClientSessionLeaderThreadProxy['mutations']['pull']
36
36
  applyMutation: (
37
- mutationEventDecoded: MutationEvent.PartialAny,
37
+ mutationEventDecoded: MutationEvent.PartialAnyDecoded,
38
38
  options: { otelContext: otel.Context; withChangeset: boolean },
39
39
  ) => {
40
40
  writeTables: Set<string>
@@ -196,7 +196,7 @@ export const makeClientSessionSyncProcessor = ({
196
196
 
197
197
  export interface ClientSessionSyncProcessor {
198
198
  push: (
199
- batch: ReadonlyArray<MutationEvent.PartialAny>,
199
+ batch: ReadonlyArray<MutationEvent.PartialAnyDecoded>,
200
200
  options: { otelContext: otel.Context },
201
201
  ) => {
202
202
  writeTables: Set<string>
@@ -20,7 +20,7 @@ export const emptyHistoryDag = (): HistoryDag =>
20
20
  })
21
21
 
22
22
  // TODO consider making `ROOT_ID` parent to itself
23
- export const rootParentId = { global: EventId.ROOT.global - 1, local: 0 } satisfies EventId.EventId
23
+ export const rootParentId = EventId.make({ global: EventId.ROOT.global - 1, local: EventId.localDefault })
24
24
 
25
25
  export type HistoryDagNode = {
26
26
  id: EventId.EventId
@@ -1,4 +1,4 @@
1
- import type * as EventId from '../../schema/EventId.js'
1
+ import * as EventId from '../../schema/EventId.js'
2
2
  import type * as MutationEvent from '../../schema/MutationEvent.js'
3
3
  import type { MutationDef, MutationEventFactsSnapshot } from '../../schema/mutations.js'
4
4
  import {
@@ -19,13 +19,13 @@ export type RebaseInput = {
19
19
  newRemoteEvents: RebaseEventWithConflict[]
20
20
  pendingLocalEvents: RebaseEventWithConflict[]
21
21
  validate: (args: {
22
- rebasedLocalEvents: MutationEvent.PartialAny[]
22
+ rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
23
23
  mutationDefs: Record<string, MutationDef.Any>
24
24
  }) => FactValidationResult
25
25
  }
26
26
 
27
27
  export type RebaseOutput = {
28
- rebasedLocalEvents: MutationEvent.PartialAny[]
28
+ rebasedLocalEvents: MutationEvent.PartialAnyDecoded[]
29
29
  }
30
30
 
31
31
  export type RebaseFn = (input: RebaseInput) => RebaseOutput
@@ -48,7 +48,7 @@ export const rebaseEvents = ({
48
48
  newRemoteEvents: HistoryDagNode[]
49
49
  rebaseFn: RebaseFn
50
50
  currentFactsSnapshot: MutationEventFactsSnapshot
51
- }): MutationEvent.Any[] => {
51
+ }): ReadonlyArray<MutationEvent.AnyDecoded> => {
52
52
  const initialSnapshot = new Map(currentFactsSnapshot)
53
53
  applyFactGroups(
54
54
  newRemoteEvents.map((event) => event.factsGroup),
@@ -89,10 +89,10 @@ export const rebaseEvents = ({
89
89
  return rebasedLocalEvents.map(
90
90
  (event, index) =>
91
91
  ({
92
- id: { global: headGlobalId + index + 1, local: 0 } satisfies EventId.EventId,
93
- parentId: { global: headGlobalId + index, local: 0 } satisfies EventId.EventId,
92
+ id: EventId.make({ global: headGlobalId + index + 1, local: EventId.localDefault }),
93
+ parentId: EventId.make({ global: headGlobalId + index, local: EventId.localDefault }),
94
94
  mutation: event.mutation,
95
95
  args: event.args,
96
- }) satisfies MutationEvent.Any,
96
+ }) satisfies MutationEvent.AnyDecoded,
97
97
  )
98
98
  }