@livestore/livestore 0.0.0-snapshot-d9d66b354a9f4cfae987725d38971992ff14e4ad → 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db
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/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +44 -9
- package/dist/SqliteDbWrapper.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 +57 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +54 -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 +34 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/{db.d.ts → db-query.d.ts} +15 -14
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/{db.js → db-query.js} +87 -43
- 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 +113 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- 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 +34 -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 +19 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +22 -18
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.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 +226 -188
- package/dist/store/store.js.map +1 -1
- 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/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 -14
- 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 -72
- package/src/live-queries/base-class.ts +151 -40
- package/src/live-queries/computed.ts +44 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +46 -33
- package/src/live-queries/{db.ts → db-query.ts} +123 -61
- package/src/live-queries/graphql.ts +46 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.test.ts +1 -1
- package/src/reactive.ts +60 -37
- 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 +362 -289
- 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/SynchronousDatabaseWrapper.d.ts +0 -36
- package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
- package/dist/SynchronousDatabaseWrapper.js.map +0 -1
- package/dist/global-state.d.ts +0 -14
- package/dist/global-state.d.ts.map +0 -1
- package/dist/global-state.js +0 -16
- package/dist/global-state.js.map +0 -1
- package/dist/live-queries/db.d.ts.map +0 -1
- package/dist/live-queries/db.js.map +0 -1
- package/dist/live-queries/db.test.d.ts +0 -2
- package/dist/live-queries/db.test.d.ts.map +0 -1
- package/dist/live-queries/db.test.js +0 -117
- package/dist/live-queries/db.test.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
|
+
defRcMap: 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,145 +565,101 @@ export class Store<
|
|
|
448
565
|
)
|
|
449
566
|
}
|
|
450
567
|
|
|
451
|
-
|
|
568
|
+
private makeTableRef = (tableName: string) =>
|
|
569
|
+
this.reactivityGraph.makeRef(null, {
|
|
570
|
+
equal: () => false,
|
|
571
|
+
label: `tableRef:${tableName}`,
|
|
572
|
+
meta: { liveStoreRefType: 'table' },
|
|
573
|
+
})
|
|
574
|
+
|
|
452
575
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
* the caller must refresh queries after calling this method.
|
|
576
|
+
* Helper methods useful during development
|
|
577
|
+
*
|
|
578
|
+
* @internal
|
|
457
579
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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)
|
|
464
586
|
},
|
|
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
587
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
}
|
|
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
|
+
},
|
|
534
594
|
|
|
535
|
-
|
|
536
|
-
|
|
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
|
+
},
|
|
537
602
|
|
|
538
|
-
|
|
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
|
+
},
|
|
539
611
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
+
},
|
|
545
617
|
|
|
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)
|
|
618
|
+
version: liveStoreVersion,
|
|
560
619
|
}
|
|
561
620
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
621
|
+
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
|
622
|
+
toJSON = () => ({
|
|
623
|
+
_tag: 'livestore.Store',
|
|
624
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
|
628
|
+
effect.pipe(Effect.forkIn(this.lifetimeScope), Effect.tapCauseLogPretty, Runtime.runFork(this.runtime))
|
|
629
|
+
|
|
630
|
+
private getMutateArgs = (
|
|
631
|
+
firstMutationOrTxnFnOrOptions: any,
|
|
632
|
+
restMutations: any[],
|
|
633
|
+
): {
|
|
634
|
+
mutationsEvents: (MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>)[]
|
|
635
|
+
options: StoreMutateOptions | undefined
|
|
636
|
+
} => {
|
|
637
|
+
let mutationsEvents: (MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>)[]
|
|
638
|
+
let options: StoreMutateOptions | undefined
|
|
573
639
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
640
|
+
if (typeof firstMutationOrTxnFnOrOptions === 'function') {
|
|
641
|
+
// TODO ensure that function is synchronous and isn't called in a async way (also write tests for this)
|
|
642
|
+
mutationsEvents = firstMutationOrTxnFnOrOptions((arg: any) => mutationsEvents.push(arg))
|
|
643
|
+
} else if (
|
|
644
|
+
firstMutationOrTxnFnOrOptions?.label !== undefined ||
|
|
645
|
+
firstMutationOrTxnFnOrOptions?.skipRefresh !== undefined ||
|
|
646
|
+
firstMutationOrTxnFnOrOptions?.otelContext !== undefined ||
|
|
647
|
+
firstMutationOrTxnFnOrOptions?.spanLinks !== undefined
|
|
648
|
+
) {
|
|
649
|
+
options = firstMutationOrTxnFnOrOptions
|
|
650
|
+
mutationsEvents = restMutations
|
|
651
|
+
} else if (firstMutationOrTxnFnOrOptions === undefined) {
|
|
652
|
+
// When `mutate` is called with no arguments (which sometimes happens when dynamically filtering mutations)
|
|
653
|
+
mutationsEvents = []
|
|
654
|
+
} else {
|
|
655
|
+
mutationsEvents = [firstMutationOrTxnFnOrOptions, ...restMutations]
|
|
656
|
+
}
|
|
579
657
|
|
|
580
|
-
|
|
658
|
+
mutationsEvents = mutationsEvents.filter(
|
|
659
|
+
// @ts-expect-error TODO
|
|
660
|
+
(_) => _.id === undefined || !MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(_.id)),
|
|
661
|
+
)
|
|
581
662
|
|
|
582
|
-
|
|
583
|
-
toJSON = () => {
|
|
584
|
-
return {
|
|
585
|
-
_tag: 'livestore.Store',
|
|
586
|
-
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
587
|
-
}
|
|
663
|
+
return { mutationsEvents, options }
|
|
588
664
|
}
|
|
589
|
-
|
|
590
|
-
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
591
|
-
effect.pipe(Effect.tapCauseLogPretty, FiberSet.run(this.fiberSet), Runtime.runFork(this.runtime))
|
|
592
665
|
}
|