@livestore/common 0.0.0-snapshot-2b8a9de3ec1a701aca891ebc2c98eb328274ae9e → 0.0.0-snapshot-2c861249e50661661613204300b1fc0d902c2e46
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/__tests__/fixture.d.ts +83 -221
- package/dist/__tests__/fixture.d.ts.map +1 -1
- package/dist/__tests__/fixture.js +33 -11
- package/dist/__tests__/fixture.js.map +1 -1
- package/dist/adapter-types.d.ts +36 -22
- package/dist/adapter-types.d.ts.map +1 -1
- package/dist/adapter-types.js +20 -8
- package/dist/adapter-types.js.map +1 -1
- package/dist/debug-info.d.ts.map +1 -1
- package/dist/debug-info.js +1 -0
- package/dist/debug-info.js.map +1 -1
- package/dist/devtools/devtools-messages-client-session.d.ts +21 -21
- package/dist/devtools/devtools-messages-common.d.ts +13 -6
- package/dist/devtools/devtools-messages-common.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-common.js +6 -0
- package/dist/devtools/devtools-messages-common.js.map +1 -1
- package/dist/devtools/devtools-messages-leader.d.ts +46 -46
- package/dist/devtools/devtools-messages-leader.d.ts.map +1 -1
- package/dist/devtools/devtools-messages-leader.js +12 -13
- package/dist/devtools/devtools-messages-leader.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.d.ts +34 -12
- package/dist/leader-thread/LeaderSyncProcessor.d.ts.map +1 -1
- package/dist/leader-thread/LeaderSyncProcessor.js +284 -226
- package/dist/leader-thread/LeaderSyncProcessor.js.map +1 -1
- package/dist/leader-thread/apply-event.d.ts +16 -0
- package/dist/leader-thread/apply-event.d.ts.map +1 -0
- package/dist/leader-thread/apply-event.js +122 -0
- package/dist/leader-thread/apply-event.js.map +1 -0
- package/dist/leader-thread/eventlog.d.ts +27 -0
- package/dist/leader-thread/eventlog.d.ts.map +1 -0
- package/dist/leader-thread/eventlog.js +123 -0
- package/dist/leader-thread/eventlog.js.map +1 -0
- package/dist/leader-thread/leader-worker-devtools.d.ts.map +1 -1
- package/dist/leader-thread/leader-worker-devtools.js +22 -23
- package/dist/leader-thread/leader-worker-devtools.js.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.d.ts +16 -4
- package/dist/leader-thread/make-leader-thread-layer.d.ts.map +1 -1
- package/dist/leader-thread/make-leader-thread-layer.js +36 -41
- package/dist/leader-thread/make-leader-thread-layer.js.map +1 -1
- package/dist/leader-thread/mod.d.ts +1 -1
- package/dist/leader-thread/mod.d.ts.map +1 -1
- package/dist/leader-thread/mod.js +1 -1
- package/dist/leader-thread/mod.js.map +1 -1
- package/dist/leader-thread/recreate-db.d.ts.map +1 -1
- package/dist/leader-thread/recreate-db.js +7 -7
- package/dist/leader-thread/recreate-db.js.map +1 -1
- package/dist/leader-thread/types.d.ts +40 -25
- package/dist/leader-thread/types.d.ts.map +1 -1
- package/dist/leader-thread/types.js.map +1 -1
- package/dist/materializer-helper.d.ts +23 -0
- package/dist/materializer-helper.d.ts.map +1 -0
- package/dist/materializer-helper.js +70 -0
- package/dist/materializer-helper.js.map +1 -0
- package/dist/query-builder/api.d.ts +55 -50
- package/dist/query-builder/api.d.ts.map +1 -1
- package/dist/query-builder/api.js +3 -5
- package/dist/query-builder/api.js.map +1 -1
- package/dist/query-builder/astToSql.d.ts.map +1 -1
- package/dist/query-builder/astToSql.js +59 -37
- package/dist/query-builder/astToSql.js.map +1 -1
- package/dist/query-builder/impl.d.ts +2 -3
- package/dist/query-builder/impl.d.ts.map +1 -1
- package/dist/query-builder/impl.js +47 -43
- package/dist/query-builder/impl.js.map +1 -1
- package/dist/query-builder/impl.test.d.ts +86 -1
- package/dist/query-builder/impl.test.d.ts.map +1 -1
- package/dist/query-builder/impl.test.js +223 -36
- package/dist/query-builder/impl.test.js.map +1 -1
- package/dist/rehydrate-from-eventlog.d.ts +15 -0
- package/dist/rehydrate-from-eventlog.d.ts.map +1 -0
- package/dist/{rehydrate-from-mutationlog.js → rehydrate-from-eventlog.js} +27 -28
- package/dist/rehydrate-from-eventlog.js.map +1 -0
- package/dist/schema/EventDef.d.ts +136 -0
- package/dist/schema/EventDef.d.ts.map +1 -0
- package/dist/schema/EventDef.js +58 -0
- package/dist/schema/EventDef.js.map +1 -0
- package/dist/schema/EventId.d.ts +10 -1
- package/dist/schema/EventId.d.ts.map +1 -1
- package/dist/schema/EventId.js +24 -3
- package/dist/schema/EventId.js.map +1 -1
- package/dist/schema/LiveStoreEvent.d.ts +255 -0
- package/dist/schema/LiveStoreEvent.d.ts.map +1 -0
- package/dist/schema/LiveStoreEvent.js +118 -0
- package/dist/schema/LiveStoreEvent.js.map +1 -0
- package/dist/schema/client-document-def.d.ts +223 -0
- package/dist/schema/client-document-def.d.ts.map +1 -0
- package/dist/schema/client-document-def.js +164 -0
- package/dist/schema/client-document-def.js.map +1 -0
- package/dist/schema/client-document-def.test.d.ts +2 -0
- package/dist/schema/client-document-def.test.d.ts.map +1 -0
- package/dist/schema/client-document-def.test.js +161 -0
- package/dist/schema/client-document-def.test.js.map +1 -0
- package/dist/schema/db-schema/dsl/mod.d.ts.map +1 -1
- package/dist/schema/events.d.ts +2 -0
- package/dist/schema/events.d.ts.map +1 -0
- package/dist/schema/events.js +2 -0
- package/dist/schema/events.js.map +1 -0
- package/dist/schema/mod.d.ts +4 -3
- package/dist/schema/mod.d.ts.map +1 -1
- package/dist/schema/mod.js +4 -3
- package/dist/schema/mod.js.map +1 -1
- package/dist/schema/schema.d.ts +26 -22
- package/dist/schema/schema.d.ts.map +1 -1
- package/dist/schema/schema.js +45 -43
- package/dist/schema/schema.js.map +1 -1
- package/dist/schema/sqlite-state.d.ts +12 -0
- package/dist/schema/sqlite-state.d.ts.map +1 -0
- package/dist/schema/sqlite-state.js +36 -0
- package/dist/schema/sqlite-state.js.map +1 -0
- package/dist/schema/system-tables.d.ts +121 -85
- package/dist/schema/system-tables.d.ts.map +1 -1
- package/dist/schema/system-tables.js +68 -43
- package/dist/schema/system-tables.js.map +1 -1
- package/dist/schema/table-def.d.ts +26 -96
- package/dist/schema/table-def.d.ts.map +1 -1
- package/dist/schema/table-def.js +14 -64
- package/dist/schema/table-def.js.map +1 -1
- package/dist/schema/view.d.ts +3 -0
- package/dist/schema/view.d.ts.map +1 -0
- package/dist/schema/view.js +3 -0
- package/dist/schema/view.js.map +1 -0
- package/dist/schema-management/common.d.ts +4 -4
- package/dist/schema-management/common.d.ts.map +1 -1
- 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/schema-management/validate-mutation-defs.d.ts +3 -3
- package/dist/schema-management/validate-mutation-defs.d.ts.map +1 -1
- package/dist/schema-management/validate-mutation-defs.js +17 -17
- package/dist/schema-management/validate-mutation-defs.js.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.d.ts +16 -8
- package/dist/sync/ClientSessionSyncProcessor.d.ts.map +1 -1
- package/dist/sync/ClientSessionSyncProcessor.js +50 -43
- package/dist/sync/ClientSessionSyncProcessor.js.map +1 -1
- package/dist/sync/next/facts.d.ts +19 -19
- package/dist/sync/next/facts.d.ts.map +1 -1
- package/dist/sync/next/facts.js +2 -2
- package/dist/sync/next/facts.js.map +1 -1
- package/dist/sync/next/history-dag-common.d.ts +3 -3
- 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/history-dag.js +1 -1
- package/dist/sync/next/history-dag.js.map +1 -1
- package/dist/sync/next/rebase-events.d.ts +7 -7
- package/dist/sync/next/rebase-events.d.ts.map +1 -1
- package/dist/sync/next/rebase-events.js +5 -5
- package/dist/sync/next/rebase-events.js.map +1 -1
- package/dist/sync/next/test/compact-events.calculator.test.js +38 -33
- package/dist/sync/next/test/compact-events.calculator.test.js.map +1 -1
- package/dist/sync/next/test/compact-events.test.js +71 -71
- package/dist/sync/next/test/compact-events.test.js.map +1 -1
- package/dist/sync/next/test/{mutation-fixtures.d.ts → event-fixtures.d.ts} +25 -25
- package/dist/sync/next/test/event-fixtures.d.ts.map +1 -0
- package/dist/sync/next/test/{mutation-fixtures.js → event-fixtures.js} +60 -25
- package/dist/sync/next/test/event-fixtures.js.map +1 -0
- package/dist/sync/next/test/mod.d.ts +1 -1
- package/dist/sync/next/test/mod.d.ts.map +1 -1
- package/dist/sync/next/test/mod.js +1 -1
- package/dist/sync/next/test/mod.js.map +1 -1
- package/dist/sync/sync.d.ts +8 -7
- package/dist/sync/sync.d.ts.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/syncstate.d.ts +69 -93
- package/dist/sync/syncstate.d.ts.map +1 -1
- package/dist/sync/syncstate.js +143 -146
- package/dist/sync/syncstate.js.map +1 -1
- package/dist/sync/syncstate.test.js +208 -289
- 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.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/__tests__/fixture.ts +36 -15
- package/src/adapter-types.ts +34 -23
- package/src/debug-info.ts +1 -0
- package/src/devtools/devtools-messages-common.ts +9 -0
- package/src/devtools/devtools-messages-leader.ts +14 -15
- package/src/index.ts +2 -5
- package/src/leader-thread/LeaderSyncProcessor.ts +485 -389
- package/src/leader-thread/apply-event.ts +197 -0
- package/src/leader-thread/eventlog.ts +199 -0
- package/src/leader-thread/leader-worker-devtools.ts +23 -25
- package/src/leader-thread/make-leader-thread-layer.ts +68 -61
- package/src/leader-thread/mod.ts +1 -1
- package/src/leader-thread/recreate-db.ts +7 -8
- package/src/leader-thread/types.ts +39 -29
- package/src/materializer-helper.ts +110 -0
- package/src/query-builder/api.ts +76 -102
- package/src/query-builder/astToSql.ts +68 -39
- package/src/query-builder/impl.test.ts +239 -42
- package/src/query-builder/impl.ts +66 -54
- package/src/{rehydrate-from-mutationlog.ts → rehydrate-from-eventlog.ts} +37 -40
- package/src/schema/EventDef.ts +216 -0
- package/src/schema/EventId.ts +30 -4
- package/src/schema/LiveStoreEvent.ts +239 -0
- package/src/schema/client-document-def.test.ts +188 -0
- package/src/schema/client-document-def.ts +436 -0
- package/src/schema/db-schema/dsl/mod.ts +0 -1
- package/src/schema/events.ts +1 -0
- package/src/schema/mod.ts +4 -3
- package/src/schema/schema.ts +78 -68
- package/src/schema/sqlite-state.ts +62 -0
- package/src/schema/system-tables.ts +54 -46
- package/src/schema/table-def.ts +51 -209
- package/src/schema/view.ts +2 -0
- package/src/schema-management/common.ts +4 -4
- package/src/schema-management/migrations.ts +8 -9
- package/src/schema-management/validate-mutation-defs.ts +22 -24
- package/src/sync/ClientSessionSyncProcessor.ts +66 -53
- package/src/sync/next/facts.ts +31 -32
- package/src/sync/next/history-dag-common.ts +4 -4
- package/src/sync/next/history-dag.ts +1 -1
- package/src/sync/next/rebase-events.ts +13 -13
- package/src/sync/next/test/compact-events.calculator.test.ts +45 -45
- package/src/sync/next/test/compact-events.test.ts +73 -73
- package/src/sync/next/test/event-fixtures.ts +219 -0
- package/src/sync/next/test/mod.ts +1 -1
- package/src/sync/sync.ts +9 -12
- package/src/sync/syncstate.test.ts +236 -323
- package/src/sync/syncstate.ts +218 -203
- package/src/sync/validate-push-payload.ts +2 -2
- package/src/version.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/derived-mutations.d.ts +0 -109
- package/dist/derived-mutations.d.ts.map +0 -1
- package/dist/derived-mutations.js +0 -54
- package/dist/derived-mutations.js.map +0 -1
- package/dist/derived-mutations.test.d.ts +0 -2
- package/dist/derived-mutations.test.d.ts.map +0 -1
- package/dist/derived-mutations.test.js +0 -93
- package/dist/derived-mutations.test.js.map +0 -1
- package/dist/init-singleton-tables.d.ts +0 -4
- package/dist/init-singleton-tables.d.ts.map +0 -1
- package/dist/init-singleton-tables.js +0 -16
- package/dist/init-singleton-tables.js.map +0 -1
- package/dist/leader-thread/apply-mutation.d.ts +0 -11
- package/dist/leader-thread/apply-mutation.d.ts.map +0 -1
- package/dist/leader-thread/apply-mutation.js +0 -115
- package/dist/leader-thread/apply-mutation.js.map +0 -1
- package/dist/leader-thread/mutationlog.d.ts +0 -11
- package/dist/leader-thread/mutationlog.d.ts.map +0 -1
- package/dist/leader-thread/mutationlog.js +0 -31
- package/dist/leader-thread/mutationlog.js.map +0 -1
- package/dist/leader-thread/pull-queue-set.d.ts +0 -7
- package/dist/leader-thread/pull-queue-set.d.ts.map +0 -1
- package/dist/leader-thread/pull-queue-set.js +0 -48
- package/dist/leader-thread/pull-queue-set.js.map +0 -1
- package/dist/mutation.d.ts +0 -20
- package/dist/mutation.d.ts.map +0 -1
- package/dist/mutation.js +0 -68
- package/dist/mutation.js.map +0 -1
- package/dist/query-info.d.ts +0 -41
- package/dist/query-info.d.ts.map +0 -1
- package/dist/query-info.js +0 -7
- package/dist/query-info.js.map +0 -1
- package/dist/rehydrate-from-mutationlog.d.ts +0 -14
- package/dist/rehydrate-from-mutationlog.d.ts.map +0 -1
- package/dist/rehydrate-from-mutationlog.js.map +0 -1
- package/dist/schema/MutationEvent.d.ts +0 -202
- package/dist/schema/MutationEvent.d.ts.map +0 -1
- package/dist/schema/MutationEvent.js +0 -105
- package/dist/schema/MutationEvent.js.map +0 -1
- package/dist/schema/mutations.d.ts +0 -115
- package/dist/schema/mutations.d.ts.map +0 -1
- package/dist/schema/mutations.js +0 -42
- package/dist/schema/mutations.js.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.d.ts.map +0 -1
- package/dist/sync/next/test/mutation-fixtures.js.map +0 -1
- package/src/derived-mutations.test.ts +0 -101
- package/src/derived-mutations.ts +0 -170
- package/src/init-singleton-tables.ts +0 -24
- package/src/leader-thread/apply-mutation.ts +0 -187
- package/src/leader-thread/mutationlog.ts +0 -49
- package/src/leader-thread/pull-queue-set.ts +0 -67
- package/src/mutation.ts +0 -108
- package/src/query-info.ts +0 -83
- package/src/schema/MutationEvent.ts +0 -224
- package/src/schema/mutations.ts +0 -193
- package/src/sync/next/test/mutation-fixtures.ts +0 -228
|
@@ -5,14 +5,13 @@ import type { BootStatus, MakeSqliteDb, MigrationsReport, SqliteError } from '..
|
|
|
5
5
|
import { UnexpectedError } from '../adapter-types.js'
|
|
6
6
|
import type * as Devtools from '../devtools/mod.js'
|
|
7
7
|
import type { LiveStoreSchema } from '../schema/mod.js'
|
|
8
|
-
import {
|
|
9
|
-
import { migrateTable } from '../schema-management/migrations.js'
|
|
8
|
+
import { LiveStoreEvent } from '../schema/mod.js'
|
|
10
9
|
import type { InvalidPullError, IsOfflineError, SyncOptions } from '../sync/sync.js'
|
|
11
10
|
import { sql } from '../util.js'
|
|
12
|
-
import {
|
|
11
|
+
import { makeApplyEvent } from './apply-event.js'
|
|
12
|
+
import * as Eventlog from './eventlog.js'
|
|
13
13
|
import { bootDevtools } from './leader-worker-devtools.js'
|
|
14
14
|
import { makeLeaderSyncProcessor } from './LeaderSyncProcessor.js'
|
|
15
|
-
import { makePullQueueSet } from './pull-queue-set.js'
|
|
16
15
|
import { recreateDb } from './recreate-db.js'
|
|
17
16
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
|
18
17
|
import type {
|
|
@@ -24,6 +23,30 @@ import type {
|
|
|
24
23
|
} from './types.js'
|
|
25
24
|
import { LeaderThreadCtx } from './types.js'
|
|
26
25
|
|
|
26
|
+
export interface MakeLeaderThreadLayerParams {
|
|
27
|
+
storeId: string
|
|
28
|
+
syncPayload: Schema.JsonValue | undefined
|
|
29
|
+
clientId: string
|
|
30
|
+
schema: LiveStoreSchema
|
|
31
|
+
makeSqliteDb: MakeSqliteDb
|
|
32
|
+
syncOptions: SyncOptions | undefined
|
|
33
|
+
dbReadModel: LeaderSqliteDb
|
|
34
|
+
dbEventlog: LeaderSqliteDb
|
|
35
|
+
devtoolsOptions: DevtoolsOptions
|
|
36
|
+
shutdownChannel: ShutdownChannel
|
|
37
|
+
params?: {
|
|
38
|
+
localPushBatchSize?: number
|
|
39
|
+
backendPushBatchSize?: number
|
|
40
|
+
}
|
|
41
|
+
testing?: {
|
|
42
|
+
syncProcessor?: {
|
|
43
|
+
delays?: {
|
|
44
|
+
localPushProcessing?: Effect.Effect<void>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
export const makeLeaderThreadLayer = ({
|
|
28
51
|
schema,
|
|
29
52
|
storeId,
|
|
@@ -32,27 +55,21 @@ export const makeLeaderThreadLayer = ({
|
|
|
32
55
|
makeSqliteDb,
|
|
33
56
|
syncOptions,
|
|
34
57
|
dbReadModel,
|
|
35
|
-
|
|
58
|
+
dbEventlog,
|
|
36
59
|
devtoolsOptions,
|
|
37
60
|
shutdownChannel,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
clientId: string
|
|
42
|
-
schema: LiveStoreSchema
|
|
43
|
-
makeSqliteDb: MakeSqliteDb
|
|
44
|
-
syncOptions: SyncOptions | undefined
|
|
45
|
-
dbReadModel: LeaderSqliteDb
|
|
46
|
-
dbMutationLog: LeaderSqliteDb
|
|
47
|
-
devtoolsOptions: DevtoolsOptions
|
|
48
|
-
shutdownChannel: ShutdownChannel
|
|
49
|
-
}): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
|
61
|
+
params,
|
|
62
|
+
testing,
|
|
63
|
+
}: MakeLeaderThreadLayerParams): Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient> =>
|
|
50
64
|
Effect.gen(function* () {
|
|
51
65
|
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
52
66
|
|
|
53
67
|
// TODO do more validation here than just checking the count of tables
|
|
54
68
|
// Either happens on initial boot or if schema changes
|
|
55
|
-
const
|
|
69
|
+
const dbEventlogMissing =
|
|
70
|
+
dbEventlog.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
|
|
71
|
+
|
|
72
|
+
const dbReadModelMissing =
|
|
56
73
|
dbReadModel.select<{ count: number }>(sql`select count(*) as count from sqlite_master`)[0]!.count === 0
|
|
57
74
|
|
|
58
75
|
const syncBackend =
|
|
@@ -60,6 +77,11 @@ export const makeLeaderThreadLayer = ({
|
|
|
60
77
|
? undefined
|
|
61
78
|
: yield* syncOptions.backend({ storeId, clientId, payload: syncPayload })
|
|
62
79
|
|
|
80
|
+
if (syncBackend !== undefined) {
|
|
81
|
+
// We're already connecting to the sync backend concurrently
|
|
82
|
+
yield* syncBackend.connect.pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
const initialBlockingSyncContext = yield* makeInitialBlockingSyncContext({
|
|
64
86
|
initialSyncOptions: syncOptions?.initialSyncOptions ?? { _tag: 'Skip' },
|
|
65
87
|
bootStatusQueue,
|
|
@@ -67,11 +89,19 @@ export const makeLeaderThreadLayer = ({
|
|
|
67
89
|
|
|
68
90
|
const syncProcessor = yield* makeLeaderSyncProcessor({
|
|
69
91
|
schema,
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
dbEventlogMissing,
|
|
93
|
+
dbEventlog,
|
|
94
|
+
dbReadModel,
|
|
95
|
+
dbReadModelMissing,
|
|
72
96
|
initialBlockingSyncContext,
|
|
73
|
-
clientId,
|
|
74
97
|
onError: syncOptions?.onSyncError ?? 'ignore',
|
|
98
|
+
params: {
|
|
99
|
+
localPushBatchSize: params?.localPushBatchSize,
|
|
100
|
+
backendPushBatchSize: params?.backendPushBatchSize,
|
|
101
|
+
},
|
|
102
|
+
testing: {
|
|
103
|
+
delays: testing?.syncProcessor?.delays,
|
|
104
|
+
},
|
|
75
105
|
})
|
|
76
106
|
|
|
77
107
|
const extraIncomingMessagesQueue = yield* Queue.unbounded<Devtools.Leader.MessageToApp>().pipe(
|
|
@@ -86,20 +116,22 @@ export const makeLeaderThreadLayer = ({
|
|
|
86
116
|
}
|
|
87
117
|
: { enabled: false as const }
|
|
88
118
|
|
|
119
|
+
const applyEvent = yield* makeApplyEvent({ schema, dbReadModel, dbEventlog })
|
|
120
|
+
|
|
89
121
|
const ctx = {
|
|
90
122
|
schema,
|
|
91
123
|
bootStatusQueue,
|
|
92
124
|
storeId,
|
|
93
125
|
clientId,
|
|
94
126
|
dbReadModel,
|
|
95
|
-
|
|
127
|
+
dbEventlog,
|
|
96
128
|
makeSqliteDb,
|
|
97
|
-
|
|
129
|
+
eventSchema: LiveStoreEvent.makeEventDefSchema(schema),
|
|
98
130
|
shutdownStateSubRef: yield* SubscriptionRef.make<ShutdownState>('running'),
|
|
99
131
|
shutdownChannel,
|
|
100
132
|
syncBackend,
|
|
101
133
|
syncProcessor,
|
|
102
|
-
|
|
134
|
+
applyEvent,
|
|
103
135
|
extraIncomingMessagesQueue,
|
|
104
136
|
devtools: devtoolsContext,
|
|
105
137
|
// State will be set during `bootLeaderThread`
|
|
@@ -112,7 +144,7 @@ export const makeLeaderThreadLayer = ({
|
|
|
112
144
|
const layer = Layer.succeed(LeaderThreadCtx, ctx)
|
|
113
145
|
|
|
114
146
|
ctx.initialState = yield* bootLeaderThread({
|
|
115
|
-
|
|
147
|
+
dbReadModelMissing,
|
|
116
148
|
initialBlockingSyncContext,
|
|
117
149
|
devtoolsOptions,
|
|
118
150
|
}).pipe(Effect.provide(layer))
|
|
@@ -122,6 +154,7 @@ export const makeLeaderThreadLayer = ({
|
|
|
122
154
|
Effect.withSpan('@livestore/common:leader-thread:boot'),
|
|
123
155
|
Effect.withSpanScoped('@livestore/common:leader-thread'),
|
|
124
156
|
UnexpectedError.mapToUnexpectedError,
|
|
157
|
+
Effect.tapCauseLogPretty,
|
|
125
158
|
Layer.unwrapScoped,
|
|
126
159
|
)
|
|
127
160
|
|
|
@@ -135,7 +168,7 @@ const makeInitialBlockingSyncContext = ({
|
|
|
135
168
|
Effect.gen(function* () {
|
|
136
169
|
const ctx = {
|
|
137
170
|
isDone: false,
|
|
138
|
-
|
|
171
|
+
processedEvents: 0,
|
|
139
172
|
total: -1,
|
|
140
173
|
}
|
|
141
174
|
|
|
@@ -158,10 +191,10 @@ const makeInitialBlockingSyncContext = ({
|
|
|
158
191
|
ctx.total = remaining + processed
|
|
159
192
|
}
|
|
160
193
|
|
|
161
|
-
ctx.
|
|
194
|
+
ctx.processedEvents += processed
|
|
162
195
|
yield* Queue.offer(bootStatusQueue, {
|
|
163
196
|
stage: 'syncing',
|
|
164
|
-
progress: { done: ctx.
|
|
197
|
+
progress: { done: ctx.processedEvents, total: ctx.total },
|
|
165
198
|
})
|
|
166
199
|
|
|
167
200
|
if (remaining === 0 && blockingDeferred !== undefined) {
|
|
@@ -177,11 +210,11 @@ const makeInitialBlockingSyncContext = ({
|
|
|
177
210
|
* It also starts various background processes (e.g. syncing)
|
|
178
211
|
*/
|
|
179
212
|
const bootLeaderThread = ({
|
|
180
|
-
|
|
213
|
+
dbReadModelMissing,
|
|
181
214
|
initialBlockingSyncContext,
|
|
182
215
|
devtoolsOptions,
|
|
183
216
|
}: {
|
|
184
|
-
|
|
217
|
+
dbReadModelMissing: boolean
|
|
185
218
|
initialBlockingSyncContext: InitialBlockingSyncContext
|
|
186
219
|
devtoolsOptions: DevtoolsOptions
|
|
187
220
|
}): Effect.Effect<
|
|
@@ -190,46 +223,20 @@ const bootLeaderThread = ({
|
|
|
190
223
|
LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
|
|
191
224
|
> =>
|
|
192
225
|
Effect.gen(function* () {
|
|
193
|
-
const {
|
|
194
|
-
|
|
195
|
-
yield* migrateTable({
|
|
196
|
-
db: dbMutationLog,
|
|
197
|
-
behaviour: 'create-if-not-exists',
|
|
198
|
-
tableAst: mutationLogMetaTable.sqliteDef.ast,
|
|
199
|
-
skipMetaTable: true,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
yield* migrateTable({
|
|
203
|
-
db: dbMutationLog,
|
|
204
|
-
behaviour: 'create-if-not-exists',
|
|
205
|
-
tableAst: syncStatusTable.sqliteDef.ast,
|
|
206
|
-
skipMetaTable: true,
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
// Create sync status row if it doesn't exist
|
|
210
|
-
yield* execSql(
|
|
211
|
-
dbMutationLog,
|
|
212
|
-
sql`INSERT INTO ${SYNC_STATUS_TABLE} (head)
|
|
213
|
-
SELECT ${EventId.ROOT.global}
|
|
214
|
-
WHERE NOT EXISTS (SELECT 1 FROM ${SYNC_STATUS_TABLE})`,
|
|
215
|
-
{},
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
const dbReady = yield* Deferred.make<void>()
|
|
226
|
+
const { dbEventlog, bootStatusQueue, syncProcessor } = yield* LeaderThreadCtx
|
|
219
227
|
|
|
220
|
-
|
|
221
|
-
// processing any incoming mutations
|
|
222
|
-
const { initialLeaderHead } = yield* syncProcessor.boot({ dbReady })
|
|
228
|
+
yield* Eventlog.initEventlogDb(dbEventlog)
|
|
223
229
|
|
|
224
230
|
let migrationsReport: MigrationsReport
|
|
225
|
-
if (
|
|
231
|
+
if (dbReadModelMissing) {
|
|
226
232
|
const recreateResult = yield* recreateDb
|
|
227
233
|
migrationsReport = recreateResult.migrationsReport
|
|
228
234
|
} else {
|
|
229
235
|
migrationsReport = { migrations: [] }
|
|
230
236
|
}
|
|
231
237
|
|
|
232
|
-
|
|
238
|
+
// NOTE the sync processor depends on the dbs being initialized properly
|
|
239
|
+
const { initialLeaderHead } = yield* syncProcessor.boot
|
|
233
240
|
|
|
234
241
|
if (initialBlockingSyncContext.blockingDeferred !== undefined) {
|
|
235
242
|
// Provides a syncing status right away before the first pull response comes in
|
package/src/leader-thread/mod.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { HttpClient } from '@livestore/utils/effect'
|
|
|
3
3
|
import { Effect, Queue } from '@livestore/utils/effect'
|
|
4
4
|
|
|
5
5
|
import type { InvalidPullError, IsOfflineError, MigrationHooks, MigrationsReport, SqliteError } from '../index.js'
|
|
6
|
-
import {
|
|
6
|
+
import { migrateDb, rehydrateFromEventlog, UnexpectedError } from '../index.js'
|
|
7
7
|
import { configureConnection } from './connection.js'
|
|
8
8
|
import { LeaderThreadCtx } from './types.js'
|
|
9
9
|
|
|
@@ -12,7 +12,7 @@ export const recreateDb: Effect.Effect<
|
|
|
12
12
|
UnexpectedError | SqliteError | IsOfflineError | InvalidPullError,
|
|
13
13
|
LeaderThreadCtx | HttpClient.HttpClient
|
|
14
14
|
> = Effect.gen(function* () {
|
|
15
|
-
const { dbReadModel,
|
|
15
|
+
const { dbReadModel, dbEventlog, schema, bootStatusQueue, applyEvent } = yield* LeaderThreadCtx
|
|
16
16
|
|
|
17
17
|
const migrationOptions = schema.migrationOptions
|
|
18
18
|
let migrationsReport: MigrationsReport
|
|
@@ -41,25 +41,24 @@ export const recreateDb: Effect.Effect<
|
|
|
41
41
|
Queue.offer(bootStatusQueue, { stage: 'migrating', progress: { done, total } }),
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
initializeSingletonTables(schema, tmpDb)
|
|
45
|
-
|
|
46
44
|
yield* Effect.tryAll(() => hooks?.pre?.(tmpDb)).pipe(UnexpectedError.mapToUnexpectedError)
|
|
47
45
|
|
|
48
46
|
return { migrationsReport, tmpDb }
|
|
49
47
|
})
|
|
50
48
|
|
|
51
49
|
switch (migrationOptions.strategy) {
|
|
52
|
-
case 'from-
|
|
50
|
+
case 'from-eventlog': {
|
|
53
51
|
const hooks = migrationOptions.hooks
|
|
54
52
|
const initResult = yield* initDb(hooks)
|
|
55
53
|
|
|
56
54
|
migrationsReport = initResult.migrationsReport
|
|
57
55
|
|
|
58
|
-
yield*
|
|
59
|
-
db: initResult.tmpDb,
|
|
60
|
-
|
|
56
|
+
yield* rehydrateFromEventlog({
|
|
57
|
+
// db: initResult.tmpDb,
|
|
58
|
+
dbEventlog,
|
|
61
59
|
schema,
|
|
62
60
|
migrationOptions,
|
|
61
|
+
applyEvent,
|
|
63
62
|
onProgress: ({ done, total }) =>
|
|
64
63
|
Queue.offer(bootStatusQueue, { stage: 'rehydrating', progress: { done, total } }),
|
|
65
64
|
})
|
|
@@ -5,12 +5,14 @@ import type {
|
|
|
5
5
|
Option,
|
|
6
6
|
Queue,
|
|
7
7
|
Scope,
|
|
8
|
+
Stream,
|
|
8
9
|
Subscribable,
|
|
9
10
|
SubscriptionRef,
|
|
10
11
|
WebChannel,
|
|
11
12
|
} from '@livestore/utils/effect'
|
|
12
13
|
import { Context, Schema } from '@livestore/utils/effect'
|
|
13
14
|
|
|
15
|
+
import type { LeaderPullCursor, SqliteError } from '../adapter-types.js'
|
|
14
16
|
import type {
|
|
15
17
|
BootStatus,
|
|
16
18
|
Devtools,
|
|
@@ -22,7 +24,7 @@ import type {
|
|
|
22
24
|
SyncBackend,
|
|
23
25
|
UnexpectedError,
|
|
24
26
|
} from '../index.js'
|
|
25
|
-
import type { EventId,
|
|
27
|
+
import type { EventId, LiveStoreEvent, LiveStoreSchema } from '../schema/mod.js'
|
|
26
28
|
import type * as SyncState from '../sync/syncstate.js'
|
|
27
29
|
import type { ShutdownChannel } from './shutdown-channel.js'
|
|
28
30
|
|
|
@@ -50,7 +52,7 @@ export type InitialSyncInfo = Option.Option<{
|
|
|
50
52
|
// | { _tag: 'Reuse'; syncInfo: InitialSyncInfo }
|
|
51
53
|
|
|
52
54
|
export type LeaderSqliteDb = SqliteDb<{ dbPointer: number; persistenceInfo: PersistenceInfo }>
|
|
53
|
-
export type PersistenceInfoPair = { readModel: PersistenceInfo;
|
|
55
|
+
export type PersistenceInfoPair = { readModel: PersistenceInfo; eventlog: PersistenceInfo }
|
|
54
56
|
|
|
55
57
|
export type DevtoolsOptions =
|
|
56
58
|
| {
|
|
@@ -88,16 +90,16 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
|
|
|
88
90
|
clientId: string
|
|
89
91
|
makeSqliteDb: MakeSqliteDb
|
|
90
92
|
dbReadModel: LeaderSqliteDb
|
|
91
|
-
|
|
93
|
+
dbEventlog: LeaderSqliteDb
|
|
92
94
|
bootStatusQueue: Queue.Queue<BootStatus>
|
|
93
95
|
// TODO we should find a more elegant way to handle cases which need this ref for their implementation
|
|
94
96
|
shutdownStateSubRef: SubscriptionRef.SubscriptionRef<ShutdownState>
|
|
95
97
|
shutdownChannel: ShutdownChannel
|
|
96
|
-
|
|
98
|
+
eventSchema: LiveStoreEvent.ForEventDefRecord<any>
|
|
97
99
|
devtools: DevtoolsContext
|
|
98
100
|
syncBackend: SyncBackend | undefined
|
|
99
101
|
syncProcessor: LeaderSyncProcessor
|
|
100
|
-
|
|
102
|
+
applyEvent: ApplyEvent
|
|
101
103
|
initialState: {
|
|
102
104
|
leaderHead: EventId.EventId
|
|
103
105
|
migrationsReport: MigrationsReport
|
|
@@ -111,27 +113,41 @@ export class LeaderThreadCtx extends Context.Tag('LeaderThreadCtx')<
|
|
|
111
113
|
}
|
|
112
114
|
>() {}
|
|
113
115
|
|
|
116
|
+
export type ApplyEvent = (
|
|
117
|
+
eventEncoded: LiveStoreEvent.EncodedWithMeta,
|
|
118
|
+
options?: {
|
|
119
|
+
/** Needed for rehydrateFromEventlog */
|
|
120
|
+
skipEventlog?: boolean
|
|
121
|
+
},
|
|
122
|
+
) => Effect.Effect<
|
|
123
|
+
{ sessionChangeset: { _tag: 'sessionChangeset'; data: Uint8Array; debug: any } | { _tag: 'no-op' } },
|
|
124
|
+
SqliteError | UnexpectedError
|
|
125
|
+
>
|
|
126
|
+
|
|
114
127
|
export type InitialBlockingSyncContext = {
|
|
115
128
|
blockingDeferred: Deferred.Deferred<void> | undefined
|
|
116
129
|
update: (_: { remaining: number; processed: number }) => Effect.Effect<void>
|
|
117
130
|
}
|
|
118
131
|
|
|
119
|
-
export type PullQueueItem = {
|
|
120
|
-
payload: SyncState.PayloadUpstream
|
|
121
|
-
remaining: number
|
|
122
|
-
}
|
|
123
|
-
|
|
124
132
|
export interface LeaderSyncProcessor {
|
|
133
|
+
/** Used by client sessions to subscribe to upstream sync state changes */
|
|
134
|
+
pull: (args: {
|
|
135
|
+
cursor: LeaderPullCursor
|
|
136
|
+
}) => Stream.Stream<{ payload: typeof SyncState.PayloadUpstream.Type; mergeCounter: number }, UnexpectedError>
|
|
137
|
+
/** The `pullQueue` API can be used instead of `pull` when more convenient */
|
|
138
|
+
pullQueue: (args: {
|
|
139
|
+
cursor: LeaderPullCursor
|
|
140
|
+
}) => Effect.Effect<
|
|
141
|
+
Queue.Queue<{ payload: typeof SyncState.PayloadUpstream.Type; mergeCounter: number }>,
|
|
142
|
+
UnexpectedError,
|
|
143
|
+
Scope.Scope
|
|
144
|
+
>
|
|
145
|
+
|
|
146
|
+
/** Used by client sessions to push events to the leader thread */
|
|
125
147
|
push: (
|
|
126
148
|
/** `batch` needs to follow the same rules as `batch` in `SyncBackend.push` */
|
|
127
|
-
batch: ReadonlyArray<
|
|
149
|
+
batch: ReadonlyArray<LiveStoreEvent.EncodedWithMeta>,
|
|
128
150
|
options?: {
|
|
129
|
-
/**
|
|
130
|
-
* This generation number is used to automatically reject subsequent pushes
|
|
131
|
-
* of a previously rejected push from a client session. This might occur in
|
|
132
|
-
* certain concurrent scenarios.
|
|
133
|
-
*/
|
|
134
|
-
// generation: number
|
|
135
151
|
/**
|
|
136
152
|
* If true, the effect will only finish when the local push has been processed (i.e. succeeded or was rejected).
|
|
137
153
|
* @default false
|
|
@@ -140,24 +156,18 @@ export interface LeaderSyncProcessor {
|
|
|
140
156
|
},
|
|
141
157
|
) => Effect.Effect<void, LeaderAheadError>
|
|
142
158
|
|
|
159
|
+
/** Currently only used by devtools which don't provide their own event numbers */
|
|
143
160
|
pushPartial: (args: {
|
|
144
|
-
|
|
161
|
+
event: LiveStoreEvent.PartialAnyEncoded
|
|
145
162
|
clientId: string
|
|
146
163
|
sessionId: string
|
|
147
|
-
}) => Effect.Effect<void, UnexpectedError
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}) => Effect.Effect<
|
|
164
|
+
}) => Effect.Effect<void, UnexpectedError>
|
|
165
|
+
|
|
166
|
+
boot: Effect.Effect<
|
|
151
167
|
{ initialLeaderHead: EventId.EventId },
|
|
152
168
|
UnexpectedError,
|
|
153
169
|
LeaderThreadCtx | Scope.Scope | HttpClient.HttpClient
|
|
154
170
|
>
|
|
155
171
|
syncState: Subscribable.Subscribable<SyncState.SyncState>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
export interface PullQueueSet {
|
|
159
|
-
makeQueue: (
|
|
160
|
-
since: EventId.EventId,
|
|
161
|
-
) => Effect.Effect<Queue.Queue<PullQueueItem>, UnexpectedError, Scope.Scope | LeaderThreadCtx>
|
|
162
|
-
offer: (item: PullQueueItem) => Effect.Effect<void, UnexpectedError, LeaderThreadCtx>
|
|
172
|
+
getMergeCounter: () => number
|
|
163
173
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isReadonlyArray } from '@livestore/utils'
|
|
2
|
+
import { Schema } from '@livestore/utils/effect'
|
|
3
|
+
|
|
4
|
+
import { SessionIdSymbol } from './adapter-types.js'
|
|
5
|
+
import { isQueryBuilder } from './query-builder/api.js'
|
|
6
|
+
import type { EventDef, Materializer, MaterializerResult } from './schema/EventDef.js'
|
|
7
|
+
import type * as LiveStoreEvent from './schema/LiveStoreEvent.js'
|
|
8
|
+
import type { BindValues } from './sql-queries/sql-queries.js'
|
|
9
|
+
import type { PreparedBindValues } from './util.js'
|
|
10
|
+
import { prepareBindValues } from './util.js'
|
|
11
|
+
|
|
12
|
+
export const getExecArgsFromEvent = ({
|
|
13
|
+
eventDef: { eventDef, materializer },
|
|
14
|
+
event,
|
|
15
|
+
}: {
|
|
16
|
+
eventDef: {
|
|
17
|
+
eventDef: EventDef.AnyWithoutFn
|
|
18
|
+
materializer: Materializer
|
|
19
|
+
}
|
|
20
|
+
/** Both encoded and decoded events are supported to reduce the number of times we need to decode/encode */
|
|
21
|
+
event:
|
|
22
|
+
| {
|
|
23
|
+
decoded: LiveStoreEvent.AnyDecoded | LiveStoreEvent.PartialAnyDecoded
|
|
24
|
+
encoded: undefined
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
decoded: undefined
|
|
28
|
+
encoded: LiveStoreEvent.AnyEncoded | LiveStoreEvent.PartialAnyEncoded
|
|
29
|
+
}
|
|
30
|
+
}): ReadonlyArray<{
|
|
31
|
+
statementSql: string
|
|
32
|
+
bindValues: PreparedBindValues
|
|
33
|
+
writeTables: ReadonlySet<string> | undefined
|
|
34
|
+
}> => {
|
|
35
|
+
const eventArgsDecoded = event.decoded?.args ?? Schema.decodeUnknownSync(eventDef.schema)(event.encoded!.args)
|
|
36
|
+
|
|
37
|
+
const res = materializer(eventArgsDecoded, {
|
|
38
|
+
clientOnly: eventDef.options.clientOnly,
|
|
39
|
+
// TODO properly implement this
|
|
40
|
+
currentFacts: new Map(),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const statementRes = mapMaterializerResult(res)
|
|
44
|
+
|
|
45
|
+
return statementRes.map((statementRes) => {
|
|
46
|
+
const statementSql = statementRes.sql
|
|
47
|
+
|
|
48
|
+
const eventArgsEncoded = event.encoded?.args ?? Schema.encodeUnknownSync(eventDef.schema)(event.decoded!.args)
|
|
49
|
+
const bindValues = typeof statementRes === 'string' ? eventArgsEncoded : statementRes.bindValues
|
|
50
|
+
|
|
51
|
+
const writeTables = typeof statementRes === 'string' ? undefined : statementRes.writeTables
|
|
52
|
+
|
|
53
|
+
return { statementSql, bindValues: prepareBindValues(bindValues ?? {}, statementSql), writeTables }
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const mapMaterializerResult = (
|
|
58
|
+
materializerResult: MaterializerResult | ReadonlyArray<MaterializerResult>,
|
|
59
|
+
): ReadonlyArray<{
|
|
60
|
+
sql: string
|
|
61
|
+
bindValues: BindValues
|
|
62
|
+
writeTables: ReadonlySet<string> | undefined
|
|
63
|
+
}> => {
|
|
64
|
+
if (isReadonlyArray(materializerResult)) {
|
|
65
|
+
return materializerResult.flatMap(mapMaterializerResult)
|
|
66
|
+
}
|
|
67
|
+
if (isQueryBuilder(materializerResult)) {
|
|
68
|
+
const { query, bindValues } = materializerResult.asSql()
|
|
69
|
+
return [{ sql: query, bindValues: bindValues as BindValues, writeTables: undefined }]
|
|
70
|
+
} else if (typeof materializerResult === 'string') {
|
|
71
|
+
return [{ sql: materializerResult, bindValues: {} as BindValues, writeTables: undefined }]
|
|
72
|
+
} else {
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
sql: materializerResult.sql,
|
|
76
|
+
bindValues: materializerResult.bindValues,
|
|
77
|
+
writeTables: materializerResult.writeTables,
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// NOTE we should explore whether there is a more elegant solution
|
|
84
|
+
// e.g. by leveraging the schema to replace the sessionIdSymbol
|
|
85
|
+
export const replaceSessionIdSymbol = (
|
|
86
|
+
bindValues: Record<string, unknown> | ReadonlyArray<unknown>,
|
|
87
|
+
sessionId: string,
|
|
88
|
+
) => {
|
|
89
|
+
deepReplaceValue(bindValues, SessionIdSymbol, sessionId)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const deepReplaceValue = <S, R>(input: any, searchValue: S, replaceValue: R): void => {
|
|
93
|
+
if (Array.isArray(input)) {
|
|
94
|
+
for (const i in input) {
|
|
95
|
+
if (input[i] === searchValue) {
|
|
96
|
+
input[i] = replaceValue
|
|
97
|
+
} else {
|
|
98
|
+
deepReplaceValue(input[i], searchValue, replaceValue)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else if (typeof input === 'object' && input !== null) {
|
|
102
|
+
for (const key in input) {
|
|
103
|
+
if (input[key] === searchValue) {
|
|
104
|
+
input[key] = replaceValue
|
|
105
|
+
} else {
|
|
106
|
+
deepReplaceValue(input[key], searchValue, replaceValue)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|