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