@livestore/livestore 0.2.0 → 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/SqliteDbWrapper.d.ts +54 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/SqliteDbWrapper.js +212 -0
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +20 -6
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +38 -6
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +252 -0
- package/dist/__tests__/fixture.d.ts.map +1 -0
- package/dist/__tests__/fixture.js +18 -0
- package/dist/__tests__/fixture.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +16 -12
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +14 -14
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +64 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +56 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +35 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/{sql.d.ts → db-query.d.ts} +19 -14
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/db-query.js +244 -0
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +123 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/db.d.ts +12 -15
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +72 -48
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +18 -15
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +35 -9
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts +20 -0
- package/dist/live-queries/make-ref.d.ts.map +1 -0
- package/dist/live-queries/make-ref.js +33 -0
- package/dist/live-queries/make-ref.js.map +1 -0
- package/dist/reactive.d.ts +15 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +15 -9
- package/dist/reactive.js.map +1 -1
- package/dist/row-query-utils.d.ts +4 -4
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +14 -10
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +13 -12
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +27 -33
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +3 -3
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +56 -34
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +18 -18
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +57 -38
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +225 -188
- package/dist/store/store.js.map +1 -1
- package/dist/store/store.test.d.ts +2 -0
- package/dist/store/store.test.d.ts.map +1 -0
- package/dist/store/store.test.js +27 -0
- package/dist/store/store.test.js.map +1 -0
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +3 -2
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/expo.d.ts +2 -0
- package/dist/utils/expo.d.ts.map +1 -0
- package/dist/utils/expo.js +8 -0
- package/dist/utils/expo.js.map +1 -0
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +2 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -13
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +60 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +65 -4
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +12 -12
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +59 -13
- package/src/ambient.d.ts +1 -1
- package/src/effect/LiveStore.ts +32 -33
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -69
- package/src/live-queries/base-class.ts +160 -40
- package/src/live-queries/computed.ts +45 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +23 -12
- package/src/live-queries/{db.ts → db-query.ts} +124 -61
- package/src/live-queries/graphql.ts +47 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.ts +52 -27
- package/src/row-query-utils.ts +29 -18
- package/src/store/create-store.ts +106 -113
- package/src/store/devtools.ts +65 -39
- package/src/store/store-types.ts +20 -18
- package/src/store/store.ts +361 -290
- package/src/utils/dev.ts +4 -2
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +6 -16
- package/src/utils/tests/mod.ts +1 -0
- package/src/utils/tests/otel.ts +71 -5
- package/dist/live-queries/sql.d.ts.map +0 -1
- package/dist/live-queries/sql.js +0 -175
- package/dist/live-queries/sql.js.map +0 -1
- package/dist/live-queries/sql.test.d.ts +0 -2
- package/dist/live-queries/sql.test.d.ts.map +0 -1
- package/dist/live-queries/sql.test.js +0 -285
- package/dist/live-queries/sql.test.js.map +0 -1
- package/dist/reactiveQueries/base-class.d.ts +0 -64
- package/dist/reactiveQueries/base-class.d.ts.map +0 -1
- package/dist/reactiveQueries/base-class.js +0 -31
- package/dist/reactiveQueries/base-class.js.map +0 -1
- package/dist/reactiveQueries/computed.d.ts +0 -26
- package/dist/reactiveQueries/computed.d.ts.map +0 -1
- package/dist/reactiveQueries/computed.js +0 -38
- package/dist/reactiveQueries/computed.js.map +0 -1
- package/dist/reactiveQueries/graphql.d.ts +0 -49
- package/dist/reactiveQueries/graphql.d.ts.map +0 -1
- package/dist/reactiveQueries/graphql.js +0 -122
- package/dist/reactiveQueries/graphql.js.map +0 -1
- package/dist/reactiveQueries/sql.d.ts +0 -62
- package/dist/reactiveQueries/sql.d.ts.map +0 -1
- package/dist/reactiveQueries/sql.js +0 -175
- package/dist/reactiveQueries/sql.js.map +0 -1
- package/dist/reactiveQueries/sql.test.d.ts +0 -2
- package/dist/reactiveQueries/sql.test.d.ts.map +0 -1
- package/dist/reactiveQueries/sql.test.js +0 -285
- package/dist/reactiveQueries/sql.test.js.map +0 -1
- package/dist/row-query.d.ts +0 -16
- package/dist/row-query.d.ts.map +0 -1
- package/dist/row-query.js +0 -30
- package/dist/row-query.js.map +0 -1
- package/src/global-state.ts +0 -20
package/src/store/store.ts
CHANGED
|
@@ -1,36 +1,72 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ClientSession,
|
|
3
|
+
ClientSessionSyncProcessor,
|
|
4
|
+
ParamsObject,
|
|
5
|
+
PreparedBindValues,
|
|
6
|
+
QueryBuilder,
|
|
7
|
+
UnexpectedError,
|
|
8
|
+
} from '@livestore/common'
|
|
2
9
|
import {
|
|
10
|
+
Devtools,
|
|
3
11
|
getExecArgsFromMutation,
|
|
4
12
|
getResultSchema,
|
|
13
|
+
IntentionalShutdownCause,
|
|
5
14
|
isQueryBuilder,
|
|
15
|
+
liveStoreVersion,
|
|
16
|
+
makeClientSessionSyncProcessor,
|
|
6
17
|
prepareBindValues,
|
|
7
18
|
QueryBuilderAstSymbol,
|
|
8
19
|
replaceSessionIdSymbol,
|
|
9
20
|
} from '@livestore/common'
|
|
10
|
-
import type { LiveStoreSchema
|
|
21
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
11
22
|
import {
|
|
12
|
-
|
|
13
|
-
makeMutationEventSchemaMemo,
|
|
23
|
+
MutationEvent,
|
|
14
24
|
SCHEMA_META_TABLE,
|
|
15
25
|
SCHEMA_MUTATIONS_META_TABLE,
|
|
16
26
|
SESSION_CHANGESET_META_TABLE,
|
|
17
27
|
} from '@livestore/common/schema'
|
|
18
|
-
import { assertNever,
|
|
28
|
+
import { assertNever, isDevEnv } from '@livestore/utils'
|
|
19
29
|
import type { Scope } from '@livestore/utils/effect'
|
|
20
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
Cause,
|
|
32
|
+
Data,
|
|
33
|
+
Effect,
|
|
34
|
+
Inspectable,
|
|
35
|
+
MutableHashMap,
|
|
36
|
+
OtelTracer,
|
|
37
|
+
Runtime,
|
|
38
|
+
Schema,
|
|
39
|
+
Stream,
|
|
40
|
+
} from '@livestore/utils/effect'
|
|
41
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
21
42
|
import * as otel from '@opentelemetry/api'
|
|
22
|
-
import type
|
|
23
|
-
|
|
24
|
-
import type {
|
|
43
|
+
import { type GraphQLSchema } from 'graphql'
|
|
44
|
+
|
|
45
|
+
import type {
|
|
46
|
+
ILiveQueryRefDef,
|
|
47
|
+
LiveQuery,
|
|
48
|
+
LiveQueryDef,
|
|
49
|
+
ReactivityGraph,
|
|
50
|
+
ReactivityGraphContext,
|
|
51
|
+
} from '../live-queries/base-class.js'
|
|
52
|
+
import { makeReactivityGraph } from '../live-queries/base-class.js'
|
|
25
53
|
import type { Ref } from '../reactive.js'
|
|
26
54
|
import { makeExecBeforeFirstRun } from '../row-query-utils.js'
|
|
27
|
-
import {
|
|
55
|
+
import { SqliteDbWrapper } from '../SqliteDbWrapper.js'
|
|
28
56
|
import { ReferenceCountedSet } from '../utils/data-structures.js'
|
|
29
57
|
import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
|
|
30
58
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
31
|
-
import type {
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
import type { StackInfo } from '../utils/stack-info.js'
|
|
60
|
+
import type {
|
|
61
|
+
BaseGraphQLContext,
|
|
62
|
+
RefreshReason,
|
|
63
|
+
StoreMutateOptions,
|
|
64
|
+
StoreOptions,
|
|
65
|
+
StoreOtel,
|
|
66
|
+
Unsubscribe,
|
|
67
|
+
} from './store-types.js'
|
|
68
|
+
|
|
69
|
+
if (isDevEnv()) {
|
|
34
70
|
exposeDebugUtils()
|
|
35
71
|
}
|
|
36
72
|
|
|
@@ -40,7 +76,7 @@ export class Store<
|
|
|
40
76
|
> extends Inspectable.Class {
|
|
41
77
|
readonly storeId: string
|
|
42
78
|
reactivityGraph: ReactivityGraph
|
|
43
|
-
|
|
79
|
+
sqliteDbWrapper: SqliteDbWrapper
|
|
44
80
|
clientSession: ClientSession
|
|
45
81
|
schema: LiveStoreSchema
|
|
46
82
|
graphQLSchema?: GraphQLSchema
|
|
@@ -50,9 +86,8 @@ export class Store<
|
|
|
50
86
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
|
51
87
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
|
52
88
|
*/
|
|
53
|
-
tableRefs: { [key: string]: Ref<null,
|
|
89
|
+
tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
|
|
54
90
|
|
|
55
|
-
private fiberSet: FiberSet.FiberSet
|
|
56
91
|
private runtime: Runtime.Runtime<Scope.Scope>
|
|
57
92
|
|
|
58
93
|
/** RC-based set to see which queries are currently subscribed to */
|
|
@@ -61,36 +96,91 @@ export class Store<
|
|
|
61
96
|
// NOTE this is currently exposed for the Devtools databrowser to emit mutation events
|
|
62
97
|
readonly __mutationEventSchema
|
|
63
98
|
private unsyncedMutationEvents
|
|
99
|
+
private syncProcessor: ClientSessionSyncProcessor
|
|
100
|
+
readonly lifetimeScope: Scope.Scope
|
|
101
|
+
|
|
102
|
+
readonly boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
|
64
103
|
|
|
65
104
|
// #region constructor
|
|
66
|
-
|
|
105
|
+
constructor({
|
|
67
106
|
clientSession,
|
|
68
107
|
schema,
|
|
69
108
|
graphQLOptions,
|
|
70
|
-
reactivityGraph,
|
|
71
109
|
otelOptions,
|
|
72
110
|
disableDevtools,
|
|
73
111
|
batchUpdates,
|
|
74
112
|
unsyncedMutationEvents,
|
|
75
113
|
storeId,
|
|
76
|
-
|
|
114
|
+
lifetimeScope,
|
|
77
115
|
runtime,
|
|
78
116
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
79
117
|
super()
|
|
80
118
|
|
|
81
119
|
this.storeId = storeId
|
|
82
|
-
|
|
83
120
|
this.unsyncedMutationEvents = unsyncedMutationEvents
|
|
84
121
|
|
|
85
|
-
this.
|
|
122
|
+
this.sqliteDbWrapper = new SqliteDbWrapper({ otel: otelOptions, db: clientSession.sqliteDb })
|
|
86
123
|
this.clientSession = clientSession
|
|
87
124
|
this.schema = schema
|
|
88
125
|
|
|
89
|
-
this.
|
|
126
|
+
this.lifetimeScope = lifetimeScope
|
|
90
127
|
this.runtime = runtime
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
const reactivityGraph = makeReactivityGraph()
|
|
130
|
+
|
|
131
|
+
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
|
|
132
|
+
|
|
133
|
+
this.syncProcessor = makeClientSessionSyncProcessor({
|
|
134
|
+
schema,
|
|
135
|
+
clientSession,
|
|
136
|
+
runtime,
|
|
137
|
+
applyMutation: (mutationEventDecoded, { otelContext, withChangeset }) => {
|
|
138
|
+
const mutationDef = schema.mutations.get(mutationEventDecoded.mutation)!
|
|
139
|
+
const execArgsArr = getExecArgsFromMutation({
|
|
140
|
+
mutationDef,
|
|
141
|
+
mutationEvent: { decoded: mutationEventDecoded, encoded: undefined },
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const writeTablesForEvent = new Set<string>()
|
|
145
|
+
|
|
146
|
+
const exec = () => {
|
|
147
|
+
for (const {
|
|
148
|
+
statementSql,
|
|
149
|
+
bindValues,
|
|
150
|
+
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
|
151
|
+
} of execArgsArr) {
|
|
152
|
+
this.sqliteDbWrapper.execute(statementSql, bindValues, { otelContext, writeTables })
|
|
153
|
+
|
|
154
|
+
// durationMsTotal += durationMs
|
|
155
|
+
writeTables.forEach((table) => writeTablesForEvent.add(table))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let sessionChangeset: Uint8Array | undefined
|
|
160
|
+
if (withChangeset === true) {
|
|
161
|
+
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
|
162
|
+
} else {
|
|
163
|
+
exec()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { writeTables: writeTablesForEvent, sessionChangeset }
|
|
167
|
+
},
|
|
168
|
+
rollback: (changeset) => {
|
|
169
|
+
this.sqliteDbWrapper.rollback(changeset)
|
|
170
|
+
},
|
|
171
|
+
refreshTables: (tables) => {
|
|
172
|
+
const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
|
|
173
|
+
for (const tableName of tables) {
|
|
174
|
+
const tableRef = this.tableRefs[tableName]
|
|
175
|
+
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
176
|
+
tablesToUpdate.push([tableRef!, null])
|
|
177
|
+
}
|
|
178
|
+
reactivityGraph.setRefs(tablesToUpdate)
|
|
179
|
+
},
|
|
180
|
+
span: syncSpan,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
this.__mutationEventSchema = MutationEvent.makeMutationEventSchemaMemo(schema)
|
|
94
184
|
|
|
95
185
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
96
186
|
this.tableRefs = {}
|
|
@@ -105,6 +195,8 @@ export class Store<
|
|
|
105
195
|
this.reactivityGraph = reactivityGraph
|
|
106
196
|
this.reactivityGraph.context = {
|
|
107
197
|
store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
|
|
198
|
+
liveQueryRCMap: new Map(),
|
|
199
|
+
reactivityGraph: new WeakRef(reactivityGraph),
|
|
108
200
|
otelTracer: otelOptions.tracer,
|
|
109
201
|
rootOtelContext: otelQueriesSpanContext,
|
|
110
202
|
effectsWrapper: batchUpdates,
|
|
@@ -141,26 +233,10 @@ export class Store<
|
|
|
141
233
|
|
|
142
234
|
if (graphQLOptions) {
|
|
143
235
|
this.graphQLSchema = graphQLOptions.schema
|
|
144
|
-
this.graphQLContext = graphQLOptions.makeContext(
|
|
145
|
-
this.syncDbWrapper,
|
|
146
|
-
this.otel.tracer,
|
|
147
|
-
clientSession.coordinator.sessionId,
|
|
148
|
-
)
|
|
236
|
+
this.graphQLContext = graphQLOptions.makeContext(this.sqliteDbWrapper, this.otel.tracer, clientSession.sessionId)
|
|
149
237
|
}
|
|
150
238
|
|
|
151
|
-
Effect.gen(this, function* () {
|
|
152
|
-
yield* this.clientSession.coordinator.syncMutations.pipe(
|
|
153
|
-
Stream.tapChunk((mutationsEventsDecodedChunk) =>
|
|
154
|
-
Effect.sync(() => {
|
|
155
|
-
this.mutate({ wasSyncMessage: true }, ...mutationsEventsDecodedChunk)
|
|
156
|
-
}),
|
|
157
|
-
),
|
|
158
|
-
Stream.runDrain,
|
|
159
|
-
Effect.interruptible,
|
|
160
|
-
Effect.withSpan('LiveStore:syncMutations'),
|
|
161
|
-
Effect.forkScoped,
|
|
162
|
-
)
|
|
163
|
-
|
|
239
|
+
this.boot = Effect.gen(this, function* () {
|
|
164
240
|
yield* Effect.addFinalizer(() =>
|
|
165
241
|
Effect.sync(() => {
|
|
166
242
|
// Remove all table refs from the reactivity graph
|
|
@@ -171,60 +247,84 @@ export class Store<
|
|
|
171
247
|
}
|
|
172
248
|
|
|
173
249
|
// End the otel spans
|
|
174
|
-
|
|
175
|
-
|
|
250
|
+
syncSpan.end()
|
|
251
|
+
mutationsSpan.end()
|
|
252
|
+
queriesSpan.end()
|
|
176
253
|
}),
|
|
177
254
|
)
|
|
178
255
|
|
|
179
|
-
yield*
|
|
180
|
-
}).pipe(Effect.scoped, Effect.withSpan('LiveStore:constructor'), this.runEffectFork)
|
|
181
|
-
}
|
|
182
|
-
// #endregion constructor
|
|
183
|
-
|
|
184
|
-
static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
|
|
185
|
-
storeOptions: StoreOptions<TGraphQLContext, TSchema>,
|
|
186
|
-
parentSpan: otel.Span,
|
|
187
|
-
): Store<TGraphQLContext, TSchema> => {
|
|
188
|
-
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
|
|
189
|
-
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:createStore', {}, ctx, (span) => {
|
|
190
|
-
try {
|
|
191
|
-
return new Store(storeOptions)
|
|
192
|
-
} finally {
|
|
193
|
-
span.end()
|
|
194
|
-
}
|
|
256
|
+
yield* this.syncProcessor.boot
|
|
195
257
|
})
|
|
196
258
|
}
|
|
259
|
+
// #endregion constructor
|
|
197
260
|
|
|
198
261
|
get sessionId(): string {
|
|
199
|
-
return this.clientSession.
|
|
262
|
+
return this.clientSession.sessionId
|
|
200
263
|
}
|
|
201
264
|
|
|
202
265
|
/**
|
|
203
266
|
* Subscribe to the results of a query
|
|
204
267
|
* Returns a function to cancel the subscription.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
|
|
272
|
+
* ```
|
|
205
273
|
*/
|
|
206
274
|
subscribe = <TResult>(
|
|
207
|
-
query
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
275
|
+
query: LiveQueryDef<TResult, any> | LiveQuery<TResult, any>,
|
|
276
|
+
options: {
|
|
277
|
+
/** Called when the query result has changed */
|
|
278
|
+
onUpdate: (value: TResult) => void
|
|
279
|
+
onSubscribe?: (query$: LiveQuery<TResult, any>) => void
|
|
280
|
+
/** Gets called after the query subscription has been removed */
|
|
281
|
+
onUnsubsubscribe?: () => void
|
|
282
|
+
label?: string
|
|
283
|
+
/**
|
|
284
|
+
* Skips the initial `onUpdate` callback
|
|
285
|
+
* @default false
|
|
286
|
+
*/
|
|
287
|
+
skipInitialRun?: boolean
|
|
288
|
+
otelContext?: otel.Context
|
|
289
|
+
/** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
|
|
290
|
+
stackInfo?: StackInfo
|
|
291
|
+
},
|
|
292
|
+
): Unsubscribe =>
|
|
212
293
|
this.otel.tracer.startActiveSpan(
|
|
213
294
|
`LiveStore.subscribe`,
|
|
214
|
-
{ attributes: { label: options?.label, queryLabel: query
|
|
295
|
+
{ attributes: { label: options?.label, queryLabel: query.label } },
|
|
215
296
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
|
216
297
|
(span) => {
|
|
217
298
|
// console.debug('store sub', query$.id, query$.label)
|
|
218
299
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
219
300
|
|
|
301
|
+
const queryRcRef =
|
|
302
|
+
query._tag === 'def'
|
|
303
|
+
? query.make(this.reactivityGraph.context!)
|
|
304
|
+
: {
|
|
305
|
+
value: query,
|
|
306
|
+
deref: () => {},
|
|
307
|
+
}
|
|
308
|
+
const query$ = queryRcRef.value
|
|
309
|
+
|
|
220
310
|
const label = `subscribe:${options?.label}`
|
|
221
|
-
const effect = this.reactivityGraph.makeEffect(
|
|
311
|
+
const effect = this.reactivityGraph.makeEffect(
|
|
312
|
+
(get, _otelContext, debugRefreshReason) =>
|
|
313
|
+
options.onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
|
|
314
|
+
{ label },
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if (options?.stackInfo) {
|
|
318
|
+
query$.activeSubscriptions.add(options.stackInfo)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
options?.onSubscribe?.(query$)
|
|
222
322
|
|
|
223
323
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
|
224
324
|
|
|
225
325
|
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
|
226
|
-
if (options?.skipInitialRun !== true) {
|
|
227
|
-
effect.doEffect(otelContext)
|
|
326
|
+
if (options?.skipInitialRun !== true && !query$.isDestroyed) {
|
|
327
|
+
effect.doEffect(otelContext, { _tag: 'subscribe.initial', label: `subscribe-initial-run:${options?.label}` })
|
|
228
328
|
}
|
|
229
329
|
|
|
230
330
|
const unsubscribe = () => {
|
|
@@ -232,7 +332,14 @@ export class Store<
|
|
|
232
332
|
try {
|
|
233
333
|
this.reactivityGraph.destroyNode(effect)
|
|
234
334
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
235
|
-
|
|
335
|
+
|
|
336
|
+
if (options?.stackInfo) {
|
|
337
|
+
query$.activeSubscriptions.delete(options.stackInfo)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
queryRcRef.deref()
|
|
341
|
+
|
|
342
|
+
options?.onUnsubsubscribe?.()
|
|
236
343
|
} finally {
|
|
237
344
|
span.end()
|
|
238
345
|
}
|
|
@@ -242,13 +349,54 @@ export class Store<
|
|
|
242
349
|
},
|
|
243
350
|
)
|
|
244
351
|
|
|
352
|
+
subscribeStream = <TResult>(
|
|
353
|
+
query$: LiveQueryDef<TResult, any>,
|
|
354
|
+
options?: { label?: string; skipInitialRun?: boolean } | undefined,
|
|
355
|
+
): Stream.Stream<TResult> =>
|
|
356
|
+
Stream.asyncPush<TResult>((emit) =>
|
|
357
|
+
Effect.gen(this, function* () {
|
|
358
|
+
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
|
|
359
|
+
Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)),
|
|
360
|
+
)
|
|
361
|
+
const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active()
|
|
362
|
+
|
|
363
|
+
yield* Effect.acquireRelease(
|
|
364
|
+
Effect.sync(() =>
|
|
365
|
+
this.subscribe(query$, {
|
|
366
|
+
onUpdate: (result) => emit.single(result),
|
|
367
|
+
otelContext,
|
|
368
|
+
label: options?.label,
|
|
369
|
+
}),
|
|
370
|
+
),
|
|
371
|
+
(unsub) => Effect.sync(() => unsub()),
|
|
372
|
+
)
|
|
373
|
+
}),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Synchronously queries the database without creating a LiveQuery.
|
|
378
|
+
* This is useful for queries that don't need to be reactive.
|
|
379
|
+
*
|
|
380
|
+
* Example: Query builder
|
|
381
|
+
* ```ts
|
|
382
|
+
* const completedTodos = store.query(tables.todo.where({ complete: true }))
|
|
383
|
+
* ```
|
|
384
|
+
*
|
|
385
|
+
* Example: Raw SQL query
|
|
386
|
+
* ```ts
|
|
387
|
+
* const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} })
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
245
390
|
query = <TResult>(
|
|
246
|
-
query:
|
|
247
|
-
|
|
391
|
+
query:
|
|
392
|
+
| QueryBuilder<TResult, any, any>
|
|
393
|
+
| LiveQuery<TResult, any>
|
|
394
|
+
| LiveQueryDef<TResult, any>
|
|
395
|
+
| { query: string; bindValues: ParamsObject },
|
|
396
|
+
options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
|
|
248
397
|
): TResult => {
|
|
249
398
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
|
250
|
-
return this.
|
|
251
|
-
bindValues: prepareBindValues(query.bindValues, query.query),
|
|
399
|
+
return this.sqliteDbWrapper.select(query.query, prepareBindValues(query.bindValues, query.query), {
|
|
252
400
|
otelContext: options?.otelContext,
|
|
253
401
|
}) as any
|
|
254
402
|
} else if (isQueryBuilder(query)) {
|
|
@@ -264,17 +412,38 @@ export class Store<
|
|
|
264
412
|
|
|
265
413
|
const sqlRes = query.asSql()
|
|
266
414
|
const schema = getResultSchema(query)
|
|
267
|
-
const rawRes = this.
|
|
268
|
-
bindValues: sqlRes.bindValues as any as PreparedBindValues,
|
|
415
|
+
const rawRes = this.sqliteDbWrapper.select(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
|
|
269
416
|
otelContext: options?.otelContext,
|
|
270
417
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
|
271
418
|
})
|
|
272
419
|
return Schema.decodeSync(schema)(rawRes)
|
|
420
|
+
} else if (query._tag === 'def') {
|
|
421
|
+
const query$ = query.make(this.reactivityGraph.context!)
|
|
422
|
+
const result = this.query(query$.value, options)
|
|
423
|
+
query$.deref()
|
|
424
|
+
return result
|
|
273
425
|
} else {
|
|
274
|
-
return query.run(options?.otelContext)
|
|
426
|
+
return query.run({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason })
|
|
275
427
|
}
|
|
276
428
|
}
|
|
277
429
|
|
|
430
|
+
// makeLive: {
|
|
431
|
+
// <T>(def: LiveQueryDef<T, any>): LiveQuery<T, any>
|
|
432
|
+
// <T>(def: ILiveQueryRefDef<T>): ILiveQueryRef<T>
|
|
433
|
+
// } = (def: any) => {
|
|
434
|
+
// if (def._tag === 'live-ref-def') {
|
|
435
|
+
// return (def as ILiveQueryRefDef<any>).make(this.reactivityGraph.context!)
|
|
436
|
+
// } else {
|
|
437
|
+
// return (def as LiveQueryDef<any, any>).make(this.reactivityGraph.context!) as any
|
|
438
|
+
// }
|
|
439
|
+
// }
|
|
440
|
+
|
|
441
|
+
setRef = <T>(refDef: ILiveQueryRefDef<T>, value: T): void => {
|
|
442
|
+
const ref = refDef.make(this.reactivityGraph.context!)
|
|
443
|
+
ref.value.set(value)
|
|
444
|
+
ref.deref()
|
|
445
|
+
}
|
|
446
|
+
|
|
278
447
|
// #region mutate
|
|
279
448
|
mutate: {
|
|
280
449
|
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(...list: TMutationArg): void
|
|
@@ -294,39 +463,15 @@ export class Store<
|
|
|
294
463
|
) => void,
|
|
295
464
|
): void
|
|
296
465
|
} = (firstMutationOrTxnFnOrOptions: any, ...restMutations: any[]) => {
|
|
297
|
-
|
|
298
|
-
let options: StoreMutateOptions | undefined
|
|
466
|
+
const { mutationsEvents, options } = this.getMutateArgs(firstMutationOrTxnFnOrOptions, restMutations)
|
|
299
467
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
mutationsEvents = firstMutationOrTxnFnOrOptions((arg: any) => mutationsEvents.push(arg))
|
|
303
|
-
} else if (
|
|
304
|
-
firstMutationOrTxnFnOrOptions?.label !== undefined ||
|
|
305
|
-
firstMutationOrTxnFnOrOptions?.skipRefresh !== undefined ||
|
|
306
|
-
firstMutationOrTxnFnOrOptions?.wasSyncMessage !== undefined ||
|
|
307
|
-
firstMutationOrTxnFnOrOptions?.persisted !== undefined
|
|
308
|
-
) {
|
|
309
|
-
options = firstMutationOrTxnFnOrOptions
|
|
310
|
-
mutationsEvents = restMutations
|
|
311
|
-
} else if (firstMutationOrTxnFnOrOptions === undefined) {
|
|
312
|
-
// When `mutate` is called with no arguments (which sometimes happens when dynamically filtering mutations)
|
|
313
|
-
mutationsEvents = []
|
|
314
|
-
} else {
|
|
315
|
-
mutationsEvents = [firstMutationOrTxnFnOrOptions, ...restMutations]
|
|
468
|
+
for (const mutationEvent of mutationsEvents) {
|
|
469
|
+
replaceSessionIdSymbol(mutationEvent.args, this.clientSession.sessionId)
|
|
316
470
|
}
|
|
317
471
|
|
|
318
|
-
mutationsEvents
|
|
319
|
-
(_) => _.id === undefined || !MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(_.id)),
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
if (mutationsEvents.length === 0) {
|
|
323
|
-
return
|
|
324
|
-
}
|
|
472
|
+
if (mutationsEvents.length === 0) return
|
|
325
473
|
|
|
326
|
-
const label = options?.label ?? 'mutate'
|
|
327
474
|
const skipRefresh = options?.skipRefresh ?? false
|
|
328
|
-
const wasSyncMessage = options?.wasSyncMessage ?? false
|
|
329
|
-
const persisted = options?.persisted ?? true
|
|
330
475
|
|
|
331
476
|
const mutationsSpan = otel.trace.getSpan(this.otel.mutationsSpanContext)!
|
|
332
477
|
mutationsSpan.addEvent('mutate')
|
|
@@ -337,59 +482,41 @@ export class Store<
|
|
|
337
482
|
|
|
338
483
|
let durationMs: number
|
|
339
484
|
|
|
340
|
-
|
|
485
|
+
return this.otel.tracer.startActiveSpan(
|
|
341
486
|
'LiveStore:mutate',
|
|
342
|
-
{
|
|
343
|
-
|
|
487
|
+
{
|
|
488
|
+
attributes: {
|
|
489
|
+
'livestore.mutationEventsCount': mutationsEvents.length,
|
|
490
|
+
'livestore.mutationEventTags': mutationsEvents.map((_) => _.mutation),
|
|
491
|
+
'livestore.mutateLabel': options?.label,
|
|
492
|
+
},
|
|
493
|
+
links: options?.spanLinks,
|
|
494
|
+
},
|
|
495
|
+
options?.otelContext ?? this.otel.mutationsSpanContext,
|
|
344
496
|
(span) => {
|
|
345
497
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
346
498
|
|
|
347
499
|
try {
|
|
348
|
-
const writeTables
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const applyMutations = () => {
|
|
359
|
-
for (const mutationEvent of mutationsEvents) {
|
|
360
|
-
try {
|
|
361
|
-
const { writeTables: writeTablesForEvent } = this.mutateWithoutRefresh(mutationEvent, {
|
|
362
|
-
otelContext,
|
|
363
|
-
// NOTE if it was a sync message, it's already coming from the coordinator, so we can skip the coordinator
|
|
364
|
-
coordinatorMode: wasSyncMessage ? 'skip-coordinator' : persisted ? 'default' : 'skip-persist',
|
|
365
|
-
})
|
|
366
|
-
for (const tableName of writeTablesForEvent) {
|
|
367
|
-
writeTables.add(tableName)
|
|
368
|
-
}
|
|
369
|
-
} catch (e: any) {
|
|
370
|
-
console.error(e, mutationEvent)
|
|
371
|
-
throw e
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (mutationsEvents.length > 1) {
|
|
377
|
-
// TODO: what to do about coordinator transaction here?
|
|
378
|
-
this.syncDbWrapper.txn(applyMutations)
|
|
379
|
-
} else {
|
|
380
|
-
applyMutations()
|
|
381
|
-
}
|
|
382
|
-
} catch (e: any) {
|
|
383
|
-
console.error(e)
|
|
384
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
385
|
-
throw e
|
|
386
|
-
} finally {
|
|
387
|
-
span.end()
|
|
500
|
+
const { writeTables } = (() => {
|
|
501
|
+
try {
|
|
502
|
+
const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
|
|
503
|
+
|
|
504
|
+
if (mutationsEvents.length > 1) {
|
|
505
|
+
// TODO: what to do about leader transaction here?
|
|
506
|
+
return this.sqliteDbWrapper.txn(applyMutations)
|
|
507
|
+
} else {
|
|
508
|
+
return applyMutations()
|
|
388
509
|
}
|
|
389
|
-
}
|
|
390
|
-
|
|
510
|
+
} catch (e: any) {
|
|
511
|
+
console.error(e)
|
|
512
|
+
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
513
|
+
throw e
|
|
514
|
+
} finally {
|
|
515
|
+
span.end()
|
|
516
|
+
}
|
|
517
|
+
})()
|
|
391
518
|
|
|
392
|
-
const tablesToUpdate = [] as [Ref<null,
|
|
519
|
+
const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
|
|
393
520
|
for (const tableName of writeTables) {
|
|
394
521
|
const tableRef = this.tableRefs[tableName]
|
|
395
522
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
@@ -417,16 +544,6 @@ export class Store<
|
|
|
417
544
|
return { durationMs }
|
|
418
545
|
},
|
|
419
546
|
)
|
|
420
|
-
|
|
421
|
-
// NOTE we need to add the mutation events to the unsynced mutation events map only after running the code above
|
|
422
|
-
// so the short-circuiting in `mutateWithoutRefresh` doesn't kick in for those events
|
|
423
|
-
for (const mutationEvent of mutationsEvents) {
|
|
424
|
-
if (mutationEvent.id !== undefined) {
|
|
425
|
-
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEvent.id), mutationEvent)
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return res
|
|
430
547
|
}
|
|
431
548
|
// #endregion mutate
|
|
432
549
|
|
|
@@ -448,117 +565,6 @@ export class Store<
|
|
|
448
565
|
)
|
|
449
566
|
}
|
|
450
567
|
|
|
451
|
-
// #region mutateWithoutRefresh
|
|
452
|
-
/**
|
|
453
|
-
* Apply a mutation to the store.
|
|
454
|
-
* Returns the tables that were affected by the event.
|
|
455
|
-
* This is an internal method that doesn't trigger a refresh;
|
|
456
|
-
* the caller must refresh queries after calling this method.
|
|
457
|
-
*/
|
|
458
|
-
mutateWithoutRefresh = (
|
|
459
|
-
mutationEventDecoded_: MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>,
|
|
460
|
-
options: {
|
|
461
|
-
otelContext: otel.Context
|
|
462
|
-
// TODO adjust `skip-persist` with new rebase sync strategy
|
|
463
|
-
coordinatorMode: 'default' | 'skip-coordinator' | 'skip-persist'
|
|
464
|
-
},
|
|
465
|
-
): { writeTables: ReadonlySet<string>; durationMs: number } => {
|
|
466
|
-
const mutationDef =
|
|
467
|
-
this.schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
468
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
469
|
-
|
|
470
|
-
// Needs to happen only for partial mutation events (thus a function)
|
|
471
|
-
const nextMutationEventId = () => {
|
|
472
|
-
const { id, parentId } = this.clientSession.coordinator
|
|
473
|
-
.nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
|
|
474
|
-
.pipe(Effect.runSync)
|
|
475
|
-
|
|
476
|
-
return { id, parentId }
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = isPartialMutationEvent(mutationEventDecoded_)
|
|
480
|
-
? { ...mutationEventDecoded_, ...nextMutationEventId() }
|
|
481
|
-
: mutationEventDecoded_
|
|
482
|
-
|
|
483
|
-
// NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
|
|
484
|
-
// e.g. the row-query functionality
|
|
485
|
-
if (MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id))) {
|
|
486
|
-
// NOTE this data should never be used
|
|
487
|
-
return { writeTables: new Set(), durationMs: 0 }
|
|
488
|
-
} else {
|
|
489
|
-
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const { otelContext, coordinatorMode = 'default' } = options
|
|
493
|
-
|
|
494
|
-
return this.otel.tracer.startActiveSpan(
|
|
495
|
-
'LiveStore:mutateWithoutRefresh',
|
|
496
|
-
{
|
|
497
|
-
attributes: {
|
|
498
|
-
'livestore.mutation': mutationEventDecoded.mutation,
|
|
499
|
-
'livestore.args': JSON.stringify(mutationEventDecoded.args, null, 2),
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
otelContext,
|
|
503
|
-
(span) => {
|
|
504
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
505
|
-
|
|
506
|
-
const allWriteTables = new Set<string>()
|
|
507
|
-
let durationMsTotal = 0
|
|
508
|
-
|
|
509
|
-
replaceSessionIdSymbol(mutationEventDecoded.args, this.clientSession.coordinator.sessionId)
|
|
510
|
-
|
|
511
|
-
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
512
|
-
|
|
513
|
-
for (const {
|
|
514
|
-
statementSql,
|
|
515
|
-
bindValues,
|
|
516
|
-
writeTables = this.syncDbWrapper.getTablesUsed(statementSql),
|
|
517
|
-
} of execArgsArr) {
|
|
518
|
-
// TODO when the store doesn't have the lock, we need wait for the coordinator to confirm the mutation
|
|
519
|
-
// before executing the mutation on the main db
|
|
520
|
-
const { durationMs } = this.syncDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
|
|
521
|
-
|
|
522
|
-
durationMsTotal += durationMs
|
|
523
|
-
writeTables.forEach((table) => allWriteTables.add(table))
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const mutationEventEncoded = Schema.encodeUnknownSync(this.__mutationEventSchema)(mutationEventDecoded)
|
|
527
|
-
|
|
528
|
-
if (coordinatorMode !== 'skip-coordinator') {
|
|
529
|
-
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
530
|
-
this.clientSession.coordinator
|
|
531
|
-
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
532
|
-
.pipe(this.runEffectFork)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Uncomment to print a list of queries currently registered on the store
|
|
536
|
-
// console.debug(JSON.parse(JSON.stringify([...this.queries].map((q) => `${labelForKey(q.componentKey)}/${q.label}`))))
|
|
537
|
-
|
|
538
|
-
span.end()
|
|
539
|
-
|
|
540
|
-
return { writeTables: allWriteTables, durationMs: durationMsTotal }
|
|
541
|
-
},
|
|
542
|
-
)
|
|
543
|
-
}
|
|
544
|
-
// #endregion mutateWithoutRefresh
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Directly execute a SQL query on the Store.
|
|
548
|
-
* This should only be used for framework-internal purposes;
|
|
549
|
-
* all app writes should go through mutate.
|
|
550
|
-
*/
|
|
551
|
-
__execute = (
|
|
552
|
-
query: string,
|
|
553
|
-
params: ParamsObject = {},
|
|
554
|
-
writeTables?: ReadonlySet<string>,
|
|
555
|
-
otelContext?: otel.Context,
|
|
556
|
-
) => {
|
|
557
|
-
this.syncDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
558
|
-
|
|
559
|
-
this.clientSession.coordinator.execute(query, prepareBindValues(params, query)).pipe(this.runEffectFork)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
568
|
private makeTableRef = (tableName: string) =>
|
|
563
569
|
this.reactivityGraph.makeRef(null, {
|
|
564
570
|
equal: () => false,
|
|
@@ -566,27 +572,92 @@ export class Store<
|
|
|
566
572
|
meta: { liveStoreRefType: 'table' },
|
|
567
573
|
})
|
|
568
574
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
575
|
+
/**
|
|
576
|
+
* Helper methods useful during development
|
|
577
|
+
*
|
|
578
|
+
* @internal
|
|
579
|
+
*/
|
|
580
|
+
_dev = {
|
|
581
|
+
downloadDb: (source: 'local' | 'leader' = 'local') => {
|
|
582
|
+
Effect.gen(this, function* () {
|
|
583
|
+
const data = source === 'local' ? this.sqliteDbWrapper.export() : yield* this.clientSession.leaderThread.export
|
|
584
|
+
downloadBlob(data, `livestore-${Date.now()}.db`)
|
|
585
|
+
}).pipe(this.runEffectFork)
|
|
586
|
+
},
|
|
573
587
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
588
|
+
downloadMutationLogDb: () => {
|
|
589
|
+
Effect.gen(this, function* () {
|
|
590
|
+
const data = yield* this.clientSession.leaderThread.getMutationLogData
|
|
591
|
+
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
592
|
+
}).pipe(this.runEffectFork)
|
|
593
|
+
},
|
|
579
594
|
|
|
580
|
-
|
|
595
|
+
hardReset: (mode: 'all-data' | 'only-app-db' = 'all-data') => {
|
|
596
|
+
Effect.gen(this, function* () {
|
|
597
|
+
yield* this.clientSession.leaderThread.sendDevtoolsMessage(
|
|
598
|
+
Devtools.ResetAllDataReq.make({ liveStoreVersion, mode, requestId: nanoid() }),
|
|
599
|
+
)
|
|
600
|
+
}).pipe(this.runEffectFork)
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
syncStates: () => {
|
|
604
|
+
Effect.gen(this, function* () {
|
|
605
|
+
const session = this.syncProcessor.syncStateRef.current
|
|
606
|
+
console.log('Session sync state:', session.toJSON())
|
|
607
|
+
const leader = yield* this.clientSession.leaderThread.getSyncState
|
|
608
|
+
console.log('Leader sync state:', leader.toJSON())
|
|
609
|
+
}).pipe(this.runEffectFork)
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
shutdown: (cause?: Cause.Cause<UnexpectedError>) => {
|
|
613
|
+
this.clientSession
|
|
614
|
+
.shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
|
|
615
|
+
.pipe(Effect.tapCauseLogPretty, Effect.provide(this.runtime), Effect.runFork)
|
|
616
|
+
},
|
|
617
|
+
}
|
|
581
618
|
|
|
582
619
|
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
|
583
|
-
toJSON = () => {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
620
|
+
toJSON = () => ({
|
|
621
|
+
_tag: 'livestore.Store',
|
|
622
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
|
626
|
+
effect.pipe(Effect.forkIn(this.lifetimeScope), Effect.tapCauseLogPretty, Runtime.runFork(this.runtime))
|
|
627
|
+
|
|
628
|
+
private getMutateArgs = (
|
|
629
|
+
firstMutationOrTxnFnOrOptions: any,
|
|
630
|
+
restMutations: any[],
|
|
631
|
+
): {
|
|
632
|
+
mutationsEvents: (MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>)[]
|
|
633
|
+
options: StoreMutateOptions | undefined
|
|
634
|
+
} => {
|
|
635
|
+
let mutationsEvents: (MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>)[]
|
|
636
|
+
let options: StoreMutateOptions | undefined
|
|
637
|
+
|
|
638
|
+
if (typeof firstMutationOrTxnFnOrOptions === 'function') {
|
|
639
|
+
// TODO ensure that function is synchronous and isn't called in a async way (also write tests for this)
|
|
640
|
+
mutationsEvents = firstMutationOrTxnFnOrOptions((arg: any) => mutationsEvents.push(arg))
|
|
641
|
+
} else if (
|
|
642
|
+
firstMutationOrTxnFnOrOptions?.label !== undefined ||
|
|
643
|
+
firstMutationOrTxnFnOrOptions?.skipRefresh !== undefined ||
|
|
644
|
+
firstMutationOrTxnFnOrOptions?.otelContext !== undefined ||
|
|
645
|
+
firstMutationOrTxnFnOrOptions?.spanLinks !== undefined
|
|
646
|
+
) {
|
|
647
|
+
options = firstMutationOrTxnFnOrOptions
|
|
648
|
+
mutationsEvents = restMutations
|
|
649
|
+
} else if (firstMutationOrTxnFnOrOptions === undefined) {
|
|
650
|
+
// When `mutate` is called with no arguments (which sometimes happens when dynamically filtering mutations)
|
|
651
|
+
mutationsEvents = []
|
|
652
|
+
} else {
|
|
653
|
+
mutationsEvents = [firstMutationOrTxnFnOrOptions, ...restMutations]
|
|
587
654
|
}
|
|
588
|
-
}
|
|
589
655
|
|
|
590
|
-
|
|
591
|
-
|
|
656
|
+
mutationsEvents = mutationsEvents.filter(
|
|
657
|
+
// @ts-expect-error TODO
|
|
658
|
+
(_) => _.id === undefined || !MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(_.id)),
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
return { mutationsEvents, options }
|
|
662
|
+
}
|
|
592
663
|
}
|