@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter-types.d.ts +47 -35
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js.map +1 -1
- package/dist/derived-mutations.d.ts +4 -4
- package/dist/derived-mutations.d.ts.map +1 -1
- package/dist/derived-mutations.test.js.map +1 -1
- package/dist/devtools/devtool-message-leader.d.ts +2 -0
- package/dist/devtools/devtool-message-leader.d.ts.map +1 -0
- package/dist/devtools/devtool-message-leader.js +2 -0
- package/dist/devtools/devtool-message-leader.js.map +1 -0
- package/dist/devtools/devtools-bridge.d.ts +2 -1
- package/dist/devtools/devtools-bridge.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +297 -0
- package/dist/devtools/devtools-messages-client-session.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-client-session.js +61 -0
- package/dist/devtools/devtools-messages-client-session.js.map +1 -0
- package/dist/devtools/devtools-messages-common.d.ts +65 -0
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-common.js +35 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -0
- package/dist/devtools/devtools-messages-leader.d.ts +261 -0
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -0
- package/dist/devtools/devtools-messages-leader.js +85 -0
- package/dist/devtools/devtools-messages-leader.js.map +1 -0
- package/dist/devtools/devtools-messages.d.ts +3 -592
- package/dist/devtools/devtools-messages.d.ts.map +1 -1
- package/dist/devtools/devtools-messages.js +3 -171
- package/dist/devtools/devtools-messages.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/init-singleton-tables.d.ts +2 -2
- package/dist/init-singleton-tables.d.ts.map +1 -1
- package/dist/init-singleton-tables.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +37 -0
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -0
- package/dist/leader-thread/LeaderSyncProcessor.js +432 -0
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -0
- package/dist/leader-thread/apply-mutation.d.ts +5 -2
- package/dist/leader-thread/apply-mutation.d.ts.map +1 -1
- package/dist/leader-thread/apply-mutation.js +41 -29
- package/dist/leader-thread/apply-mutation.js.map +1 -1
- package/dist/leader-thread/connection.d.ts +4 -4
- package/dist/leader-thread/connection.d.ts.map +1 -1
- package/dist/leader-thread/connection.js +5 -5
- package/dist/leader-thread/connection.js.map +1 -1
- package/dist/leader-thread/leader-sync-processor.d.ts +2 -2
- package/dist/leader-thread/leader-sync-processor.d.ts.map +1 -1
- package/dist/leader-thread/leader-sync-processor.js +20 -12
- package/dist/leader-thread/leader-sync-processor.js.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts +1 -1
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +37 -81
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +12 -11
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +33 -14
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mutationlog.d.ts +6 -19
- package/dist/leader-thread/mutationlog.d.ts.map +1 -1
- package/dist/leader-thread/mutationlog.js +7 -6
- package/dist/leader-thread/mutationlog.js.map +1 -1
- package/dist/leader-thread/pull-queue-set.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +24 -18
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +36 -16
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/mutation.d.ts +9 -2
- package/dist/mutation.d.ts.map +1 -1
- package/dist/mutation.js +5 -5
- package/dist/mutation.js.map +1 -1
- package/dist/query-builder/impl.d.ts +1 -1
- package/dist/rehydrate-from-mutationlog.d.ts +5 -5
- package/dist/rehydrate-from-mutationlog.d.ts.map +1 -1
- package/dist/rehydrate-from-mutationlog.js +13 -19
- package/dist/rehydrate-from-mutationlog.js.map +1 -1
- package/dist/schema/EventId.d.ts +16 -14
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +15 -7
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/EventId.test.d.ts +2 -0
- package/dist/schema/EventId.test.d.ts.map +1 -0
- package/dist/schema/EventId.test.js +11 -0
- package/dist/schema/EventId.test.js.map +1 -0
- package/dist/schema/MutationEvent.d.ts +49 -80
- package/dist/schema/MutationEvent.d.ts.map +1 -1
- package/dist/schema/MutationEvent.js +32 -15
- package/dist/schema/MutationEvent.js.map +1 -1
- package/dist/schema/MutationEvent.test.d.ts +2 -0
- package/dist/schema/MutationEvent.test.d.ts.map +1 -0
- package/dist/schema/MutationEvent.test.js +2 -0
- package/dist/schema/MutationEvent.test.js.map +1 -0
- package/dist/schema/system-tables.d.ts +26 -26
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +19 -11
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema-management/common.d.ts +3 -3
- package/dist/schema-management/common.d.ts.map +1 -1
- package/dist/schema-management/common.js.map +1 -1
- package/dist/schema-management/migrations.d.ts +4 -4
- package/dist/schema-management/migrations.d.ts.map +1 -1
- package/dist/schema-management/migrations.js +6 -6
- package/dist/schema-management/migrations.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +43 -0
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -0
- package/dist/sync/ClientSessionSyncProcessor.js +141 -0
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -0
- package/dist/sync/client-session-sync-processor.d.ts +4 -4
- package/dist/sync/client-session-sync-processor.d.ts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +1 -4
- package/dist/sync/next/history-dag-common.d.ts.map +1 -1
- package/dist/sync/next/history-dag-common.js +1 -1
- package/dist/sync/next/history-dag-common.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +3 -3
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +3 -2
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts +7 -7
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +1 -1
- package/dist/sync/next/test/mutation-fixtures.js +3 -9
- package/dist/sync/next/test/mutation-fixtures.js.map +1 -1
- package/dist/sync/sync.d.ts +21 -11
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +45 -23
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +56 -12
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +125 -69
- package/dist/sync/syncstate.test.js.map +1 -1
- package/dist/sync/validate-push-payload.d.ts +2 -2
- package/dist/sync/validate-push-payload.d.ts.map +1 -1
- package/dist/sync/validate-push-payload.js +2 -2
- package/dist/sync/validate-push-payload.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -5
- package/src/adapter-types.ts +39 -40
- package/src/derived-mutations.test.ts +1 -1
- package/src/derived-mutations.ts +9 -5
- package/src/devtools/devtools-bridge.ts +2 -1
- package/src/devtools/devtools-messages-client-session.ts +109 -0
- package/src/devtools/devtools-messages-common.ts +52 -0
- package/src/devtools/devtools-messages-leader.ts +115 -0
- package/src/devtools/devtools-messages.ts +3 -243
- package/src/index.ts +0 -6
- package/src/init-singleton-tables.ts +2 -2
- package/src/leader-thread/{leader-sync-processor.ts → LeaderSyncProcessor.ts} +306 -268
- package/src/leader-thread/apply-mutation.ts +53 -35
- package/src/leader-thread/connection.ts +7 -7
- package/src/leader-thread/leader-worker-devtools.ts +52 -124
- package/src/leader-thread/make-leader-thread-layer.ts +62 -30
- package/src/leader-thread/mutationlog.ts +14 -10
- package/src/leader-thread/recreate-db.ts +24 -20
- package/src/leader-thread/types.ts +41 -20
- package/src/mutation.ts +17 -7
- package/src/rehydrate-from-mutationlog.ts +18 -26
- package/src/schema/EventId.test.ts +12 -0
- package/src/schema/EventId.ts +23 -9
- package/src/schema/MutationEvent.ts +46 -24
- package/src/schema/system-tables.ts +19 -11
- package/src/schema-management/common.ts +3 -3
- package/src/schema-management/migrations.ts +10 -10
- package/src/sync/{client-session-sync-processor.ts → ClientSessionSyncProcessor.ts} +26 -19
- package/src/sync/index.ts +1 -1
- package/src/sync/next/history-dag-common.ts +1 -1
- package/src/sync/next/rebase-events.ts +7 -7
- package/src/sync/next/test/mutation-fixtures.ts +3 -10
- package/src/sync/sync.ts +19 -6
- package/src/sync/syncstate.test.ts +127 -67
- package/src/sync/syncstate.ts +21 -19
- package/src/sync/validate-push-payload.ts +7 -4
- package/src/version.ts +1 -1
@@ -1,17 +1,20 @@
|
|
1
1
|
import { Effect, Schema } from '@livestore/utils/effect'
|
2
2
|
|
3
|
-
import type {
|
3
|
+
import type { SqliteDb } 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 = (
|
10
|
+
export const getMutationEventsSince = (
|
11
|
+
since: EventId.EventId,
|
12
|
+
): Effect.Effect<ReadonlyArray<MutationEvent.AnyEncoded>, never, LeaderThreadCtx> =>
|
10
13
|
Effect.gen(function* () {
|
11
|
-
const {
|
14
|
+
const { dbMutationLog } = yield* LeaderThreadCtx
|
12
15
|
|
13
16
|
const query = mutationLogMetaTable.query.where('idGlobal', '>=', since.global).asSql()
|
14
|
-
const pendingMutationEventsRaw =
|
17
|
+
const pendingMutationEventsRaw = dbMutationLog.select(query.query, prepareBindValues(query.bindValues, query.query))
|
15
18
|
const pendingMutationEvents = Schema.decodeUnknownSync(mutationLogMetaTable.schema.pipe(Schema.Array))(
|
16
19
|
pendingMutationEventsRaw,
|
17
20
|
)
|
@@ -26,17 +29,18 @@ export const getMutationEventsSince = (since: EventId.EventId) =>
|
|
26
29
|
.filter((_) => EventId.compare(_.id, since) > 0)
|
27
30
|
})
|
28
31
|
|
29
|
-
export const getLocalHeadFromDb = (
|
30
|
-
const res =
|
32
|
+
export const getLocalHeadFromDb = (dbMutationLog: SqliteDb): EventId.EventId => {
|
33
|
+
const res = dbMutationLog.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 = (
|
38
|
-
|
40
|
+
export const getBackendHeadFromDb = (dbMutationLog: SqliteDb): EventId.GlobalEventId =>
|
41
|
+
dbMutationLog.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
|
-
export const updateBackendHead = (
|
42
|
-
|
45
|
+
export const updateBackendHead = (dbMutationLog: SqliteDb, head: EventId.EventId) =>
|
46
|
+
dbMutationLog.execute(sql`UPDATE ${SYNC_STATUS_TABLE} SET head = ${head.global}`)
|
@@ -12,54 +12,56 @@ export const recreateDb: Effect.Effect<
|
|
12
12
|
UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
|
13
13
|
LeaderThreadCtx | HttpClient.HttpClient
|
14
14
|
> = Effect.gen(function* () {
|
15
|
-
const {
|
15
|
+
const { dbReadModel, dbMutationLog, schema, bootStatusQueue } = yield* LeaderThreadCtx
|
16
16
|
|
17
17
|
const migrationOptions = schema.migrationOptions
|
18
18
|
|
19
19
|
yield* Effect.addFinalizer(
|
20
20
|
Effect.fn('recreateDb:finalizer')(function* (ex) {
|
21
|
-
if (ex._tag === 'Failure')
|
21
|
+
if (ex._tag === 'Failure') dbReadModel.destroy()
|
22
22
|
}),
|
23
23
|
)
|
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
|
-
|
28
|
-
yield*
|
27
|
+
// TODO bring back this optimization
|
28
|
+
// const tmpDb = yield* makeSqliteDb({ _tag: 'in-memory' })
|
29
|
+
const tmpDb = dbReadModel
|
30
|
+
yield* configureConnection(tmpDb, { fkEnabled: true })
|
29
31
|
|
30
32
|
const initDb = (hooks: Partial<MigrationHooks> | undefined) =>
|
31
33
|
Effect.gen(function* () {
|
32
|
-
yield* Effect.tryAll(() => hooks?.init?.(
|
34
|
+
yield* Effect.tryAll(() => hooks?.init?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
33
35
|
|
34
36
|
yield* migrateDb({
|
35
|
-
db:
|
37
|
+
db: tmpDb,
|
36
38
|
schema,
|
37
39
|
onProgress: ({ done, total }) =>
|
38
40
|
Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
|
39
41
|
})
|
40
42
|
|
41
|
-
initializeSingletonTables(schema,
|
43
|
+
initializeSingletonTables(schema, tmpDb)
|
42
44
|
|
43
|
-
yield* Effect.tryAll(() => hooks?.pre?.(
|
45
|
+
yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
44
46
|
|
45
|
-
return
|
47
|
+
return tmpDb
|
46
48
|
})
|
47
49
|
|
48
50
|
switch (migrationOptions.strategy) {
|
49
51
|
case 'from-mutation-log': {
|
50
52
|
const hooks = migrationOptions.hooks
|
51
|
-
const
|
53
|
+
const tmpDb = yield* initDb(hooks)
|
52
54
|
|
53
55
|
yield* rehydrateFromMutationLog({
|
54
|
-
db:
|
55
|
-
logDb:
|
56
|
+
db: tmpDb,
|
57
|
+
logDb: dbMutationLog,
|
56
58
|
schema,
|
57
59
|
migrationOptions,
|
58
60
|
onProgress: ({ done, total }) =>
|
59
61
|
Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
|
60
62
|
})
|
61
63
|
|
62
|
-
yield* Effect.tryAll(() => hooks?.post?.(
|
64
|
+
yield* Effect.tryAll(() => hooks?.post?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
63
65
|
|
64
66
|
break
|
65
67
|
}
|
@@ -74,13 +76,13 @@ export const recreateDb: Effect.Effect<
|
|
74
76
|
break
|
75
77
|
}
|
76
78
|
case 'manual': {
|
77
|
-
const oldDbData =
|
79
|
+
const oldDbData = dbReadModel.export()
|
78
80
|
|
79
81
|
const newDbData = yield* Effect.tryAll(() => migrationOptions.migrate(oldDbData)).pipe(
|
80
82
|
UnexpectedError.mapToUnexpectedError,
|
81
83
|
)
|
82
84
|
|
83
|
-
|
85
|
+
tmpDb.import(newDbData)
|
84
86
|
|
85
87
|
// TODO validate schema
|
86
88
|
|
@@ -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(
|
96
|
-
|
97
|
-
)
|
98
|
+
// yield* Effect.sync(() => db.import(tmpDb)).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
|
-
// const snapshotFromTmpDb =
|
105
|
+
// const snapshotFromTmpDb = tmpDb.export()
|
103
106
|
|
104
|
-
|
107
|
+
// TODO bring back
|
108
|
+
// tmpDb.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'),
|
@@ -6,6 +6,7 @@ import type {
|
|
6
6
|
Option,
|
7
7
|
Queue,
|
8
8
|
Scope,
|
9
|
+
Subscribable,
|
9
10
|
SubscriptionRef,
|
10
11
|
WebChannel,
|
11
12
|
} from '@livestore/utils/effect'
|
@@ -15,14 +16,14 @@ import type {
|
|
15
16
|
BootStatus,
|
16
17
|
Devtools,
|
17
18
|
InvalidPushError,
|
18
|
-
|
19
|
+
MakeSqliteDb,
|
19
20
|
PersistenceInfo,
|
21
|
+
SqliteDb,
|
20
22
|
SyncBackend,
|
21
|
-
SynchronousDatabase,
|
22
23
|
UnexpectedError,
|
23
24
|
} from '../index.js'
|
24
25
|
import type { EventId, LiveStoreSchema, MutationEvent } from '../schema/mod.js'
|
25
|
-
import type
|
26
|
+
import type * as SyncState from '../sync/syncstate.js'
|
26
27
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
27
28
|
|
28
29
|
export type ShutdownState = 'running' | 'shutting-down'
|
@@ -55,8 +56,8 @@ export type InitialSyncInfo = Option.Option<{
|
|
55
56
|
// | { _tag: 'Recreate'; snapshotRef: Ref.Ref<Uint8Array | undefined>; syncInfo: InitialSyncInfo }
|
56
57
|
// | { _tag: 'Reuse'; syncInfo: InitialSyncInfo }
|
57
58
|
|
58
|
-
export type
|
59
|
-
export type PersistenceInfoPair = {
|
59
|
+
export type LeaderSqliteDb = SqliteDb<{ dbPointer: number; persistenceInfo: PersistenceInfo }>
|
60
|
+
export type PersistenceInfoPair = { readModel: PersistenceInfo; mutationLog: PersistenceInfo }
|
60
61
|
|
61
62
|
export type DevtoolsOptions =
|
62
63
|
| {
|
@@ -64,10 +65,9 @@ export type DevtoolsOptions =
|
|
64
65
|
}
|
65
66
|
| {
|
66
67
|
enabled: true
|
67
|
-
|
68
|
+
makeBootContext: Effect.Effect<
|
68
69
|
{
|
69
70
|
devtoolsWebChannel: WebChannel.WebChannel<Devtools.MessageToAppLeader, Devtools.MessageFromAppLeader>
|
70
|
-
shutdownChannel: ShutdownChannel
|
71
71
|
persistenceInfo: PersistenceInfoPair
|
72
72
|
},
|
73
73
|
UnexpectedError,
|
@@ -75,23 +75,40 @@ export type DevtoolsOptions =
|
|
75
75
|
>
|
76
76
|
}
|
77
77
|
|
78
|
+
export type DevtoolsContext =
|
79
|
+
| {
|
80
|
+
enabled: true
|
81
|
+
syncBackendPullLatch: Effect.Latch
|
82
|
+
syncBackendPushLatch: Effect.Latch
|
83
|
+
}
|
84
|
+
| {
|
85
|
+
enabled: false
|
86
|
+
}
|
87
|
+
|
78
88
|
export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
|
79
89
|
LeaderThreadCtx,
|
80
90
|
{
|
81
91
|
schema: LiveStoreSchema
|
82
92
|
storeId: string
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
93
|
+
clientId: string
|
94
|
+
makeSqliteDb: MakeSqliteDb
|
95
|
+
dbReadModel: LeaderSqliteDb
|
96
|
+
dbMutationLog: LeaderSqliteDb
|
87
97
|
bootStatusQueue: Queue.Queue<BootStatus>
|
88
98
|
// TODO we should find a more elegant way to handle cases which need this ref for their implementation
|
89
99
|
shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
|
100
|
+
shutdownChannel: ShutdownChannel
|
90
101
|
mutationEventSchema: MutationEvent.ForMutationDefRecord<any>
|
91
|
-
|
102
|
+
devtools: DevtoolsContext
|
92
103
|
syncBackend: SyncBackend | undefined
|
93
|
-
syncProcessor:
|
104
|
+
syncProcessor: LeaderSyncProcessor
|
94
105
|
connectedClientSessionPullQueues: PullQueueSet
|
106
|
+
/**
|
107
|
+
* e.g. used for `store._dev` APIs
|
108
|
+
*
|
109
|
+
* This is currently separated from `.devtools` as it also needs to work when devtools are disabled
|
110
|
+
*/
|
111
|
+
extraIncomingMessagesQueue: Queue.Queue<Devtools.MessageToAppLeader>
|
95
112
|
}
|
96
113
|
>() {}
|
97
114
|
|
@@ -101,24 +118,28 @@ export type InitialBlockingSyncContext = {
|
|
101
118
|
}
|
102
119
|
|
103
120
|
export type PullQueueItem = {
|
104
|
-
|
105
|
-
// backendHead: number
|
106
|
-
payload: PayloadUpstream
|
107
|
-
// TODO move `remaining` into `PayloadUpstream`
|
121
|
+
payload: SyncState.PayloadUpstream
|
108
122
|
remaining: number
|
109
123
|
}
|
110
124
|
|
111
|
-
export interface
|
125
|
+
export interface LeaderSyncProcessor {
|
112
126
|
push: (
|
113
127
|
/** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
|
114
128
|
batch: ReadonlyArray<MutationEvent.EncodedWithMeta>,
|
115
|
-
|
129
|
+
options?: {
|
130
|
+
/**
|
131
|
+
* If true, the effect will only finish when the local push has been processed (i.e. succeeded or was rejected).
|
132
|
+
* @default false
|
133
|
+
*/
|
134
|
+
waitForProcessing?: boolean
|
135
|
+
},
|
136
|
+
) => Effect.Effect<void, InvalidPushError>
|
116
137
|
|
117
138
|
pushPartial: (mutationEvent: MutationEvent.PartialAnyEncoded) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
|
118
139
|
boot: (args: {
|
119
140
|
dbReady: Deferred.Deferred<void>
|
120
141
|
}) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient>
|
121
|
-
syncState:
|
142
|
+
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
122
143
|
}
|
123
144
|
|
124
145
|
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
|
-
|
11
|
+
mutationEvent,
|
12
12
|
}: {
|
13
13
|
mutationDef: MutationDef.Any
|
14
|
-
|
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
|
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
|
44
|
-
|
45
|
-
|
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 {
|
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
|
5
|
-
import {
|
4
|
+
import { type MigrationOptionsFromMutationLog, type SqliteDb, UnexpectedError } from './adapter-types.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,13 +10,14 @@ 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,
|
17
18
|
}: {
|
18
|
-
logDb:
|
19
|
-
db:
|
19
|
+
logDb: SqliteDb
|
20
|
+
db: SqliteDb
|
20
21
|
schema: LiveStoreSchema
|
21
22
|
migrationOptions: MigrationOptionsFromMutationLog
|
22
23
|
onProgress: (_: { done: number; total: number }) => Effect.Effect<void>
|
@@ -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
|
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
|
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
|
61
|
-
} satisfies MutationEvent.
|
62
|
-
|
63
|
-
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Vitest } from '@livestore/utils/node-vitest'
|
2
|
+
import { expect } from 'vitest'
|
3
|
+
|
4
|
+
import { EventId } from './mod.js'
|
5
|
+
|
6
|
+
Vitest.describe('EventId', () => {
|
7
|
+
Vitest.test('nextPair', () => {
|
8
|
+
const e_0_0 = EventId.make({ global: 0, local: 0 })
|
9
|
+
expect(EventId.nextPair(e_0_0, false).id).toStrictEqual({ global: 1, local: 0 })
|
10
|
+
expect(EventId.nextPair(e_0_0, true).id).toStrictEqual({ global: 0, local: 1 })
|
11
|
+
})
|
12
|
+
})
|
package/src/schema/EventId.ts
CHANGED
@@ -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:
|
19
|
+
export type EventId = { global: GlobalEventId; local: LocalEventId }
|
10
20
|
|
11
21
|
export const EventId = Schema.Struct({
|
12
|
-
global:
|
13
|
-
local:
|
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:
|
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
|
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:
|
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:
|
58
|
+
parentId: { global: id.global, local: localDefault },
|
45
59
|
}
|
46
60
|
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { memoizeByRef } from '@livestore/utils'
|
2
|
-
import type { Deferred } from '@livestore/utils/effect'
|
3
2
|
import { Schema } from '@livestore/utils/effect'
|
4
3
|
|
5
4
|
import * as EventId from './EventId.js'
|
@@ -30,10 +29,31 @@ export type MutationEventEncoded<TMutationsDef extends MutationDef.Any> = {
|
|
30
29
|
parentId: EventId.EventId
|
31
30
|
}
|
32
31
|
|
33
|
-
export type
|
32
|
+
export type AnyDecoded = MutationEvent<MutationDef.Any>
|
33
|
+
export const AnyDecoded = Schema.Struct({
|
34
|
+
mutation: Schema.String,
|
35
|
+
args: Schema.Any,
|
36
|
+
id: EventId.EventId,
|
37
|
+
parentId: EventId.EventId,
|
38
|
+
}).annotations({ title: 'MutationEvent.AnyDecoded' })
|
39
|
+
|
34
40
|
export type AnyEncoded = MutationEventEncoded<MutationDef.Any>
|
41
|
+
export const AnyEncoded = Schema.Struct({
|
42
|
+
mutation: Schema.String,
|
43
|
+
args: Schema.Any,
|
44
|
+
id: EventId.EventId,
|
45
|
+
parentId: EventId.EventId,
|
46
|
+
}).annotations({ title: 'MutationEvent.AnyEncoded' })
|
47
|
+
|
48
|
+
export const AnyEncodedGlobal = Schema.Struct({
|
49
|
+
mutation: Schema.String,
|
50
|
+
args: Schema.Any,
|
51
|
+
id: EventId.GlobalEventId,
|
52
|
+
parentId: EventId.GlobalEventId,
|
53
|
+
}).annotations({ title: 'MutationEvent.AnyEncodedGlobal' })
|
54
|
+
export type AnyEncodedGlobal = typeof AnyEncodedGlobal.Type
|
35
55
|
|
36
|
-
export type
|
56
|
+
export type PartialAnyDecoded = MutationEventPartial<MutationDef.Any>
|
37
57
|
export type PartialAnyEncoded = MutationEventPartialEncoded<MutationDef.Any>
|
38
58
|
|
39
59
|
export type PartialForSchema<TSchema extends LiveStoreSchema> = {
|
@@ -44,8 +64,9 @@ export type ForSchema<TSchema extends LiveStoreSchema> = {
|
|
44
64
|
[K in keyof TSchema['_MutationDefMapType']]: MutationEvent<TSchema['_MutationDefMapType'][K]>
|
45
65
|
}[keyof TSchema['_MutationDefMapType']]
|
46
66
|
|
47
|
-
export const isPartialMutationEvent = (
|
48
|
-
|
67
|
+
export const isPartialMutationEvent = (
|
68
|
+
mutationEvent: AnyDecoded | PartialAnyDecoded,
|
69
|
+
): mutationEvent is PartialAnyDecoded => 'id' in mutationEvent === false && 'parentId' in mutationEvent === false
|
49
70
|
|
50
71
|
export type ForMutationDefRecord<TMutationsDefRecord extends MutationDefRecord> = Schema.Schema<
|
51
72
|
{
|
@@ -105,33 +126,21 @@ export const makeMutationEventPartialSchema = <TSchema extends LiveStoreSchema>(
|
|
105
126
|
args: def.schema,
|
106
127
|
}),
|
107
128
|
),
|
108
|
-
).annotations({ title: '
|
129
|
+
).annotations({ title: 'MutationEventPartial' }) as any
|
109
130
|
|
110
131
|
export const makeMutationEventSchemaMemo = memoizeByRef(makeMutationEventSchema)
|
111
132
|
|
112
|
-
|
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 */
|
133
|
+
/** Equivalent to AnyEncoded but with a meta field and some convenience methods */
|
128
134
|
export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEvent.EncodedWithMeta')({
|
129
135
|
mutation: Schema.String,
|
130
136
|
args: Schema.Any,
|
131
137
|
id: EventId.EventId,
|
132
138
|
parentId: EventId.EventId,
|
139
|
+
// TODO get rid of `meta` again by cleaning up the usage implementations
|
133
140
|
meta: Schema.optionalWith(
|
134
|
-
Schema.Any as Schema.Schema<{
|
141
|
+
Schema.Any as Schema.Schema<{
|
142
|
+
sessionChangeset?: Uint8Array
|
143
|
+
}>,
|
135
144
|
{ default: () => ({}) },
|
136
145
|
),
|
137
146
|
}) {
|
@@ -149,8 +158,21 @@ export class EncodedWithMeta extends Schema.Class<EncodedWithMeta>('MutationEven
|
|
149
158
|
rebase = (parentId: EventId.EventId, isLocal: boolean) =>
|
150
159
|
new EncodedWithMeta({
|
151
160
|
...this,
|
152
|
-
...EventId.nextPair(
|
161
|
+
...EventId.nextPair(parentId, isLocal),
|
153
162
|
})
|
163
|
+
|
164
|
+
static fromGlobal = (mutationEvent: AnyEncodedGlobal) =>
|
165
|
+
new EncodedWithMeta({
|
166
|
+
...mutationEvent,
|
167
|
+
id: { global: mutationEvent.id, local: EventId.localDefault },
|
168
|
+
parentId: { global: mutationEvent.parentId, local: EventId.localDefault },
|
169
|
+
})
|
170
|
+
|
171
|
+
toGlobal = (): AnyEncodedGlobal => ({
|
172
|
+
...this,
|
173
|
+
id: this.id.global,
|
174
|
+
parentId: this.parentId.global,
|
175
|
+
})
|
154
176
|
}
|
155
177
|
|
156
178
|
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
|
-
|
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
|
-
{
|
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
|
-
{
|
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>
|