@livestore/livestore 0.0.54-dev.5 → 0.0.54
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/MainDatabaseWrapper.d.ts +6 -5
- package/dist/MainDatabaseWrapper.d.ts.map +1 -1
- package/dist/MainDatabaseWrapper.js +3 -3
- package/dist/MainDatabaseWrapper.js.map +1 -1
- package/dist/QueryCache.d.ts +1 -1
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +9 -27
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +12 -10
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +18 -11
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +20 -19
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/index.d.ts +1 -1
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +1 -1
- package/dist/effect/index.js.map +1 -1
- package/dist/global-state.d.ts +1 -3
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +2 -3
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/react/LiveStoreContext.d.ts +5 -2
- package/dist/react/LiveStoreContext.d.ts.map +1 -1
- package/dist/react/LiveStoreContext.js +3 -0
- package/dist/react/LiveStoreContext.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +6 -6
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +70 -43
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/LiveStoreProvider.test.js +33 -12
- package/dist/react/LiveStoreProvider.test.js.map +1 -1
- package/dist/react/components/LiveList.d.ts.map +1 -1
- package/dist/react/useAtom.d.ts +1 -1
- package/dist/react/useAtom.d.ts.map +1 -1
- package/dist/react/useLocalId.d.ts.map +1 -1
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +2 -2
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useRow.d.ts +2 -2
- package/dist/react/useRow.d.ts.map +1 -1
- package/dist/react/useRow.js +5 -5
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useRow.test.js +22 -22
- package/dist/react/useRow.test.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js +1 -1
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/reactive.d.ts +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +4 -5
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +6 -6
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +3 -3
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +8 -8
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +10 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +6 -6
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +8 -8
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +9 -10
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +12 -12
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.js +6 -6
- package/dist/reactiveQueries/sql.test.js.map +1 -1
- package/dist/row-query.d.ts +2 -2
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +4 -38
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +35 -22
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +329 -221
- package/dist/store.js.map +1 -1
- package/dist/utils/otel.d.ts.map +1 -1
- package/package.json +10 -19
- package/src/MainDatabaseWrapper.ts +14 -8
- package/src/QueryCache.ts +1 -2
- package/src/__tests__/react/fixture.tsx +13 -11
- package/src/effect/LiveStore.ts +53 -44
- package/src/effect/index.ts +2 -1
- package/src/global-state.ts +2 -6
- package/src/index.ts +25 -7
- package/src/react/LiveStoreContext.ts +7 -2
- package/src/react/LiveStoreProvider.test.tsx +56 -14
- package/src/react/LiveStoreProvider.tsx +107 -51
- package/src/react/useQuery.ts +2 -2
- package/src/react/useRow.test.tsx +22 -22
- package/src/react/useRow.ts +7 -10
- package/src/react/useTemporaryQuery.ts +2 -2
- package/src/reactive.ts +6 -5
- package/src/reactiveQueries/base-class.ts +9 -9
- package/src/reactiveQueries/graphql.ts +19 -15
- package/src/reactiveQueries/js.ts +12 -12
- package/src/reactiveQueries/sql.test.ts +6 -6
- package/src/reactiveQueries/sql.ts +19 -21
- package/src/row-query.ts +8 -54
- package/src/store.ts +514 -284
- package/dist/utils/bounded-collections.d.ts +0 -34
- package/dist/utils/bounded-collections.d.ts.map +0 -1
- package/dist/utils/bounded-collections.js +0 -91
- package/dist/utils/bounded-collections.js.map +0 -1
- package/dist/utils/util.d.ts +0 -14
- package/dist/utils/util.d.ts.map +0 -1
- package/dist/utils/util.js +0 -19
- package/dist/utils/util.js.map +0 -1
- package/src/utils/util.ts +0 -31
package/src/store.ts
CHANGED
|
@@ -1,22 +1,48 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type {
|
|
2
|
+
BootDb,
|
|
3
|
+
BootStatus,
|
|
4
|
+
DebugInfo,
|
|
5
|
+
ParamsObject,
|
|
6
|
+
PreparedBindValues,
|
|
7
|
+
ResetMode,
|
|
8
|
+
StoreAdapter,
|
|
9
|
+
StoreAdapterFactory,
|
|
10
|
+
} from '@livestore/common'
|
|
11
|
+
import {
|
|
12
|
+
Devtools,
|
|
13
|
+
getExecArgsFromMutation,
|
|
14
|
+
liveStoreVersion,
|
|
15
|
+
prepareBindValues,
|
|
16
|
+
UnexpectedError,
|
|
17
|
+
} from '@livestore/common'
|
|
18
|
+
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
19
|
+
import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
|
|
20
|
+
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen, throttle } from '@livestore/utils'
|
|
21
|
+
import { cuid } from '@livestore/utils/cuid'
|
|
22
|
+
import {
|
|
23
|
+
Effect,
|
|
24
|
+
Exit,
|
|
25
|
+
Layer,
|
|
26
|
+
Logger,
|
|
27
|
+
LogLevel,
|
|
28
|
+
OtelTracer,
|
|
29
|
+
Queue,
|
|
30
|
+
Runtime,
|
|
31
|
+
Schema,
|
|
32
|
+
Scope,
|
|
33
|
+
Stream,
|
|
34
|
+
} from '@livestore/utils/effect'
|
|
8
35
|
import * as otel from '@opentelemetry/api'
|
|
9
36
|
import type { GraphQLSchema } from 'graphql'
|
|
10
37
|
|
|
11
|
-
import {
|
|
12
|
-
import { MainDatabaseWrapper } from './MainDatabaseWrapper.js'
|
|
38
|
+
import { globalReactivityGraph } from './global-state.js'
|
|
39
|
+
import { emptyDebugInfo as makeEmptyDebugInfo, MainDatabaseWrapper } from './MainDatabaseWrapper.js'
|
|
13
40
|
import type { StackInfo } from './react/utils/stack-info.js'
|
|
14
|
-
import type { DebugRefreshReasonBase,
|
|
15
|
-
import
|
|
41
|
+
import type { DebugRefreshReasonBase, Ref } from './reactive.js'
|
|
42
|
+
import { NOT_REFRESHED_YET } from './reactive.js'
|
|
43
|
+
import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
|
|
16
44
|
import { downloadBlob } from './utils/dev.js'
|
|
17
45
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
18
|
-
import type { ParamsObject } from './utils/util.js'
|
|
19
|
-
import { prepareBindValues } from './utils/util.js'
|
|
20
46
|
|
|
21
47
|
export type BaseGraphQLContext = {
|
|
22
48
|
queriedTables: Set<string>
|
|
@@ -29,18 +55,25 @@ export type GraphQLOptions<TContext> = {
|
|
|
29
55
|
makeContext: (db: MainDatabaseWrapper, tracer: otel.Tracer) => TContext
|
|
30
56
|
}
|
|
31
57
|
|
|
58
|
+
export type OtelOptions = {
|
|
59
|
+
tracer: otel.Tracer
|
|
60
|
+
rootSpanContext: otel.Context
|
|
61
|
+
}
|
|
62
|
+
|
|
32
63
|
export type StoreOptions<
|
|
33
64
|
TGraphQLContext extends BaseGraphQLContext,
|
|
34
65
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
35
66
|
> = {
|
|
36
67
|
adapter: StoreAdapter
|
|
37
68
|
schema: TSchema
|
|
69
|
+
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
38
70
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
dbGraph: DbGraph
|
|
42
|
-
mutationEventSchema: MutationEventSchema<any>
|
|
71
|
+
otelOptions: OtelOptions
|
|
72
|
+
reactivityGraph: ReactivityGraph
|
|
43
73
|
disableDevtools?: boolean
|
|
74
|
+
storeScope: Scope.CloseableScope
|
|
75
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
76
|
+
__processedMutationIds: Set<string>
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
export type RefreshReason =
|
|
@@ -95,11 +128,10 @@ export class Store<
|
|
|
95
128
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
96
129
|
> {
|
|
97
130
|
id = uniqueStoreId()
|
|
98
|
-
|
|
131
|
+
readonly devtoolsConnectionId = cuid()
|
|
132
|
+
private storeScope: Scope.CloseableScope
|
|
133
|
+
reactivityGraph: ReactivityGraph
|
|
99
134
|
mainDbWrapper: MainDatabaseWrapper
|
|
100
|
-
// TODO refactor
|
|
101
|
-
// _proxyDb: InMemoryDatabase
|
|
102
|
-
// TODO
|
|
103
135
|
adapter: StoreAdapter
|
|
104
136
|
schema: LiveStoreSchema
|
|
105
137
|
graphQLSchema?: GraphQLSchema
|
|
@@ -109,11 +141,11 @@ export class Store<
|
|
|
109
141
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
|
110
142
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
|
111
143
|
*/
|
|
112
|
-
tableRefs: { [key: string]: Ref<null,
|
|
144
|
+
tableRefs: { [key: string]: Ref<null, QueryContext, RefreshReason> }
|
|
113
145
|
|
|
114
146
|
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
115
|
-
__processedMutationIds
|
|
116
|
-
__processedMutationWithoutRefreshIds = new Set<string>()
|
|
147
|
+
private __processedMutationIds
|
|
148
|
+
private __processedMutationWithoutRefreshIds = new Set<string>()
|
|
117
149
|
|
|
118
150
|
/** RC-based set to see which queries are currently subscribed to */
|
|
119
151
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
@@ -124,60 +156,51 @@ export class Store<
|
|
|
124
156
|
adapter,
|
|
125
157
|
schema,
|
|
126
158
|
graphQLOptions,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
otelRootSpanContext,
|
|
130
|
-
mutationEventSchema,
|
|
159
|
+
reactivityGraph,
|
|
160
|
+
otelOptions,
|
|
131
161
|
disableDevtools,
|
|
162
|
+
__processedMutationIds,
|
|
163
|
+
storeScope,
|
|
132
164
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
133
|
-
this.mainDbWrapper = new MainDatabaseWrapper({
|
|
165
|
+
this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb })
|
|
134
166
|
this.adapter = adapter
|
|
135
167
|
this.schema = schema
|
|
136
168
|
|
|
169
|
+
this.storeScope = storeScope
|
|
170
|
+
|
|
137
171
|
// TODO refactor
|
|
138
|
-
this.__mutationEventSchema =
|
|
139
|
-
|
|
172
|
+
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
173
|
+
|
|
174
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
175
|
+
this.__processedMutationIds = __processedMutationIds
|
|
140
176
|
|
|
141
177
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
142
178
|
this.tableRefs = {}
|
|
143
179
|
this.activeQueries = new ReferenceCountedSet()
|
|
144
180
|
|
|
145
|
-
const mutationsSpan =
|
|
181
|
+
const mutationsSpan = otelOptions.tracer.startSpan('LiveStore:mutations', {}, otelOptions.rootSpanContext)
|
|
146
182
|
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(), mutationsSpan)
|
|
147
183
|
|
|
148
|
-
const queriesSpan =
|
|
184
|
+
const queriesSpan = otelOptions.tracer.startSpan('LiveStore:queries', {}, otelOptions.rootSpanContext)
|
|
149
185
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
|
150
186
|
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.mutate({ wasSyncMessage: true }, mutationEventDecoded)
|
|
157
|
-
}),
|
|
158
|
-
Stream.runDrain,
|
|
159
|
-
Effect.tapCauseLogPretty,
|
|
160
|
-
Effect.runFork,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
if (disableDevtools !== true) {
|
|
164
|
-
this.bootDevtools()
|
|
187
|
+
this.reactivityGraph = reactivityGraph
|
|
188
|
+
this.reactivityGraph.context = {
|
|
189
|
+
store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
|
|
190
|
+
otelTracer: otelOptions.tracer,
|
|
191
|
+
rootOtelContext: otelQueriesSpanContext,
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
this.otel = {
|
|
168
|
-
tracer:
|
|
195
|
+
tracer: otelOptions.tracer,
|
|
169
196
|
mutationsSpanContext: otelMuationsSpanContext,
|
|
170
197
|
queriesSpanContext: otelQueriesSpanContext,
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
// Need a set here since `schema.tables` might contain duplicates and some componentStateTables
|
|
174
|
-
const allTableNames = new Set(
|
|
175
|
-
this.schema.tables.keys(),
|
|
176
|
-
// TODO activate dynamic tables
|
|
177
|
-
// ...Array.from(dynamicallyRegisteredTables.values()).map((_) => _.sqliteDef.name),
|
|
178
|
-
)
|
|
201
|
+
const allTableNames = new Set(this.schema.tables.keys())
|
|
179
202
|
const existingTableRefs = new Map(
|
|
180
|
-
Array.from(this.
|
|
203
|
+
Array.from(this.reactivityGraph.atoms.values())
|
|
181
204
|
.filter((_): _ is Ref<any, any, any> => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
182
205
|
.map((_) => [_.label!.slice('tableRef:'.length), _] as const),
|
|
183
206
|
)
|
|
@@ -189,6 +212,34 @@ export class Store<
|
|
|
189
212
|
this.graphQLSchema = graphQLOptions.schema
|
|
190
213
|
this.graphQLContext = graphQLOptions.makeContext(this.mainDbWrapper, this.otel.tracer)
|
|
191
214
|
}
|
|
215
|
+
|
|
216
|
+
Effect.gen(this, function* () {
|
|
217
|
+
yield* this.adapter.coordinator.syncMutations.pipe(
|
|
218
|
+
Stream.tapSync((mutationEventDecoded) => {
|
|
219
|
+
this.mutate({ wasSyncMessage: true }, mutationEventDecoded)
|
|
220
|
+
}),
|
|
221
|
+
Stream.runDrain,
|
|
222
|
+
Effect.withSpan('LiveStore:syncMutations'),
|
|
223
|
+
Effect.forkScoped,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if (disableDevtools !== true) {
|
|
227
|
+
yield* this.bootDevtools().pipe(Effect.forkScoped)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
yield* Effect.addFinalizer(() =>
|
|
231
|
+
Effect.sync(() => {
|
|
232
|
+
for (const tableRef of Object.values(this.tableRefs)) {
|
|
233
|
+
for (const superComp of tableRef.super) {
|
|
234
|
+
this.reactivityGraph.removeEdge(superComp, tableRef)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
239
|
+
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
240
|
+
}),
|
|
241
|
+
)
|
|
242
|
+
}).pipe(Scope.extend(storeScope), Effect.forkIn(storeScope), Effect.scoped, runEffectFork)
|
|
192
243
|
}
|
|
193
244
|
|
|
194
245
|
static createStore = <TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema = LiveStoreSchema>(
|
|
@@ -196,7 +247,7 @@ export class Store<
|
|
|
196
247
|
parentSpan: otel.Span,
|
|
197
248
|
): Store<TGraphQLContext, TSchema> => {
|
|
198
249
|
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
|
|
199
|
-
return storeOptions.
|
|
250
|
+
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:store-constructor', {}, ctx, (span) => {
|
|
200
251
|
try {
|
|
201
252
|
return new Store(storeOptions)
|
|
202
253
|
} finally {
|
|
@@ -224,7 +275,7 @@ export class Store<
|
|
|
224
275
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
225
276
|
|
|
226
277
|
const label = `subscribe:${options?.label}`
|
|
227
|
-
const effect = this.
|
|
278
|
+
const effect = this.reactivityGraph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
|
|
228
279
|
|
|
229
280
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
|
230
281
|
|
|
@@ -236,7 +287,7 @@ export class Store<
|
|
|
236
287
|
const unsubscribe = () => {
|
|
237
288
|
// console.log('store unsub', query$.label)
|
|
238
289
|
try {
|
|
239
|
-
this.
|
|
290
|
+
this.reactivityGraph.destroyNode(effect)
|
|
240
291
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
241
292
|
onUnsubsubscribe?.()
|
|
242
293
|
} finally {
|
|
@@ -254,16 +305,7 @@ export class Store<
|
|
|
254
305
|
* Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
|
|
255
306
|
*/
|
|
256
307
|
destroy = async () => {
|
|
257
|
-
|
|
258
|
-
for (const superComp of tableRef.super) {
|
|
259
|
-
this.graph.removeEdge(superComp, tableRef)
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
264
|
-
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
265
|
-
|
|
266
|
-
await this.adapter.coordinator.shutdown()
|
|
308
|
+
await Scope.close(this.storeScope, Exit.void).pipe(Effect.withSpan('Store:destroy'), runEffectPromise)
|
|
267
309
|
}
|
|
268
310
|
|
|
269
311
|
mutate: {
|
|
@@ -323,6 +365,8 @@ export class Store<
|
|
|
323
365
|
// mutationsEvents.forEach((_) => console.log(_.mutation, _.id, _.args))
|
|
324
366
|
// console.groupEnd()
|
|
325
367
|
|
|
368
|
+
let durationMs: number
|
|
369
|
+
|
|
326
370
|
return this.otel.tracer.startActiveSpan(
|
|
327
371
|
'LiveStore:mutate',
|
|
328
372
|
{ attributes: { 'livestore.mutateLabel': label } },
|
|
@@ -353,8 +397,8 @@ export class Store<
|
|
|
353
397
|
writeTables.add(tableName)
|
|
354
398
|
}
|
|
355
399
|
} catch (e: any) {
|
|
356
|
-
debugger
|
|
357
400
|
console.error(e, mutationEvent)
|
|
401
|
+
throw e
|
|
358
402
|
}
|
|
359
403
|
}
|
|
360
404
|
}
|
|
@@ -368,13 +412,14 @@ export class Store<
|
|
|
368
412
|
} catch (e: any) {
|
|
369
413
|
console.error(e)
|
|
370
414
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
415
|
+
throw e
|
|
371
416
|
} finally {
|
|
372
417
|
span.end()
|
|
373
418
|
}
|
|
374
419
|
},
|
|
375
420
|
)
|
|
376
421
|
|
|
377
|
-
const tablesToUpdate = [] as [Ref<null,
|
|
422
|
+
const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
|
|
378
423
|
for (const tableName of writeTables) {
|
|
379
424
|
const tableRef = this.tableRefs[tableName]
|
|
380
425
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
@@ -388,14 +433,18 @@ export class Store<
|
|
|
388
433
|
}
|
|
389
434
|
|
|
390
435
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
391
|
-
this.
|
|
436
|
+
this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh })
|
|
392
437
|
} catch (e: any) {
|
|
438
|
+
console.error(e)
|
|
393
439
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
440
|
+
throw e
|
|
394
441
|
} finally {
|
|
395
442
|
span.end()
|
|
396
443
|
|
|
397
|
-
|
|
444
|
+
durationMs = getDurationMsFromSpan(span)
|
|
398
445
|
}
|
|
446
|
+
|
|
447
|
+
return { durationMs }
|
|
399
448
|
},
|
|
400
449
|
)
|
|
401
450
|
}
|
|
@@ -412,7 +461,7 @@ export class Store<
|
|
|
412
461
|
this.otel.mutationsSpanContext,
|
|
413
462
|
(span) => {
|
|
414
463
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
415
|
-
this.
|
|
464
|
+
this.reactivityGraph.runDeferredEffects({ otelContext })
|
|
416
465
|
span.end()
|
|
417
466
|
},
|
|
418
467
|
)
|
|
@@ -480,10 +529,9 @@ export class Store<
|
|
|
480
529
|
|
|
481
530
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
482
531
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
})
|
|
532
|
+
this.adapter.coordinator
|
|
533
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
534
|
+
.pipe(runEffectFork)
|
|
487
535
|
}
|
|
488
536
|
|
|
489
537
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -509,8 +557,7 @@ export class Store<
|
|
|
509
557
|
) => {
|
|
510
558
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
511
559
|
|
|
512
|
-
|
|
513
|
-
this.adapter.coordinator.execute(query, prepareBindValues(params, query), parentSpan)
|
|
560
|
+
this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(runEffectFork)
|
|
514
561
|
}
|
|
515
562
|
|
|
516
563
|
select = (query: string, params: ParamsObject = {}) => {
|
|
@@ -518,117 +565,223 @@ export class Store<
|
|
|
518
565
|
}
|
|
519
566
|
|
|
520
567
|
makeTableRef = (tableName: string) =>
|
|
521
|
-
this.
|
|
568
|
+
this.reactivityGraph.makeRef(null, {
|
|
522
569
|
equal: () => false,
|
|
523
570
|
label: `tableRef:${tableName}`,
|
|
524
571
|
meta: { liveStoreRefType: 'table' },
|
|
525
572
|
})
|
|
526
573
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHost)(event.data)
|
|
535
|
-
if (
|
|
536
|
-
decoded._tag === 'None' ||
|
|
537
|
-
decoded.value._tag === 'LSD.DevtoolsReadyBroadcast' ||
|
|
538
|
-
decoded.value._tag === 'LSD.DevtoolsConnected' ||
|
|
539
|
-
decoded.value.channelId !== this.adapter.coordinator.devtools.channelId
|
|
540
|
-
) {
|
|
541
|
-
// console.log(`Unknown message`, event)
|
|
542
|
-
return
|
|
574
|
+
// TODO shutdown behaviour
|
|
575
|
+
private bootDevtools = () =>
|
|
576
|
+
Effect.gen(this, function* () {
|
|
577
|
+
const sendToDevtoolsContentscript = (
|
|
578
|
+
message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
|
|
579
|
+
) => {
|
|
580
|
+
window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
|
|
543
581
|
}
|
|
544
582
|
|
|
545
|
-
|
|
546
|
-
const sendToDevtools = (message: Devtools.MessageFromAppHost) =>
|
|
547
|
-
devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
|
|
548
|
-
|
|
549
|
-
switch (decoded.value._tag) {
|
|
550
|
-
case 'LSD.SignalsSubscribe': {
|
|
551
|
-
const includeResults = decoded.value.includeResults
|
|
552
|
-
const send = () =>
|
|
553
|
-
sendToDevtools(
|
|
554
|
-
Devtools.SignalsRes.make({
|
|
555
|
-
signals: this.graph.getSnapshot({ includeResults }),
|
|
556
|
-
requestId,
|
|
557
|
-
liveStoreVersion,
|
|
558
|
-
}),
|
|
559
|
-
)
|
|
583
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.LoadIframe.make({}))
|
|
560
584
|
|
|
561
|
-
|
|
585
|
+
const channelId = this.adapter.coordinator.devtools.channelId
|
|
562
586
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
587
|
+
window.addEventListener('message', (event) => {
|
|
588
|
+
const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
|
|
589
|
+
if (decodedMessageRes._tag === 'None') return
|
|
566
590
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
)
|
|
573
|
-
break
|
|
574
|
-
}
|
|
575
|
-
case 'LSD.DebugInfoResetReq': {
|
|
576
|
-
this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
577
|
-
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
578
|
-
break
|
|
579
|
-
}
|
|
580
|
-
case 'LSD.DebugInfoRerunQueryReq': {
|
|
581
|
-
const { queryStr, bindValues, queriedTables } = decoded.value
|
|
582
|
-
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
583
|
-
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
584
|
-
break
|
|
585
|
-
}
|
|
586
|
-
case 'LSD.SignalsUnsubscribe': {
|
|
587
|
-
signalsSubcriptionRef.current!()
|
|
588
|
-
signalsSubcriptionRef.current = undefined
|
|
589
|
-
break
|
|
591
|
+
const message = decodedMessageRes.value
|
|
592
|
+
|
|
593
|
+
if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
|
|
594
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
595
|
+
return
|
|
590
596
|
}
|
|
591
|
-
case 'LSD.LiveQueriesSubscribe': {
|
|
592
|
-
const send = () =>
|
|
593
|
-
sendToDevtools(
|
|
594
|
-
Devtools.LiveQueriesRes.make({
|
|
595
|
-
liveQueries: [...this.activeQueries].map((q) => ({
|
|
596
|
-
_tag: q._tag,
|
|
597
|
-
id: q.id,
|
|
598
|
-
label: q.label,
|
|
599
|
-
runs: q.runs,
|
|
600
|
-
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
601
|
-
lastestResult: q.results$.previousResult,
|
|
602
|
-
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
603
|
-
})),
|
|
604
|
-
requestId,
|
|
605
|
-
liveStoreVersion,
|
|
606
|
-
}),
|
|
607
|
-
)
|
|
608
597
|
|
|
609
|
-
|
|
598
|
+
if (message.channelId !== channelId) return
|
|
610
599
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
600
|
+
if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
|
|
601
|
+
type Unsub = () => void
|
|
602
|
+
type RequestId = string
|
|
614
603
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
604
|
+
const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
|
|
605
|
+
const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
|
|
606
|
+
const debugInfoHistorySubscriptions = new Map<RequestId, Unsub>()
|
|
607
|
+
|
|
608
|
+
this.adapter.coordinator.devtools
|
|
609
|
+
.connect({ port: message.port, connectionId: this.devtoolsConnectionId })
|
|
610
|
+
.pipe(
|
|
611
|
+
Effect.tapSync(({ storeMessagePort }) => {
|
|
612
|
+
// console.log('storeMessagePort', storeMessagePort)
|
|
613
|
+
storeMessagePort.addEventListener('message', (event) => {
|
|
614
|
+
const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
|
|
615
|
+
// console.log('storeMessagePort message', decodedMessage)
|
|
616
|
+
|
|
617
|
+
if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
618
|
+
// console.log(`Unknown message`, event)
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const requestId = decodedMessage.requestId
|
|
623
|
+
const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
|
|
624
|
+
storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
|
|
625
|
+
|
|
626
|
+
const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
|
|
627
|
+
|
|
628
|
+
switch (decodedMessage._tag) {
|
|
629
|
+
case 'LSD.ReactivityGraphSubscribe': {
|
|
630
|
+
const includeResults = decodedMessage.includeResults
|
|
631
|
+
|
|
632
|
+
const send = () =>
|
|
633
|
+
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
634
|
+
// to send the reactivity graph updates to the devtools
|
|
635
|
+
requestIdleCallback(
|
|
636
|
+
() =>
|
|
637
|
+
sendToDevtools(
|
|
638
|
+
Devtools.ReactivityGraphRes.make({
|
|
639
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
640
|
+
requestId,
|
|
641
|
+
liveStoreVersion,
|
|
642
|
+
}),
|
|
643
|
+
),
|
|
644
|
+
{ timeout: 500 },
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
send()
|
|
648
|
+
|
|
649
|
+
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
650
|
+
// so we throttle the updates to avoid sending too much data
|
|
651
|
+
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
652
|
+
const throttledSend = throttle(send, 20)
|
|
653
|
+
|
|
654
|
+
reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
655
|
+
|
|
656
|
+
break
|
|
657
|
+
}
|
|
658
|
+
case 'LSD.DebugInfoReq': {
|
|
659
|
+
sendToDevtools(
|
|
660
|
+
Devtools.DebugInfoRes.make({
|
|
661
|
+
debugInfo: this.mainDbWrapper.debugInfo,
|
|
662
|
+
requestId,
|
|
663
|
+
liveStoreVersion,
|
|
664
|
+
}),
|
|
665
|
+
)
|
|
666
|
+
break
|
|
667
|
+
}
|
|
668
|
+
case 'LSD.DebugInfoHistorySubscribe': {
|
|
669
|
+
const buffer: DebugInfo[] = []
|
|
670
|
+
let hasStopped = false
|
|
671
|
+
let rafHandle: number | undefined
|
|
672
|
+
|
|
673
|
+
const tick = () => {
|
|
674
|
+
buffer.push(this.mainDbWrapper.debugInfo)
|
|
675
|
+
|
|
676
|
+
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
677
|
+
// will get the empty debug info
|
|
678
|
+
// TODO We need to come up with a more graceful way to do this. Probably via a single global
|
|
679
|
+
// `requestAnimationFrame` loop that is passed in somehow.
|
|
680
|
+
this.mainDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
681
|
+
|
|
682
|
+
if (buffer.length > 10) {
|
|
683
|
+
sendToDevtools(
|
|
684
|
+
Devtools.DebugInfoHistoryRes.make({
|
|
685
|
+
debugInfoHistory: buffer,
|
|
686
|
+
requestId,
|
|
687
|
+
liveStoreVersion,
|
|
688
|
+
}),
|
|
689
|
+
)
|
|
690
|
+
buffer.length = 0
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (hasStopped === false) {
|
|
694
|
+
rafHandle = requestAnimationFrame(tick)
|
|
695
|
+
}
|
|
696
|
+
}
|
|
625
697
|
|
|
626
|
-
|
|
698
|
+
rafHandle = requestAnimationFrame(tick)
|
|
699
|
+
|
|
700
|
+
const unsub = () => {
|
|
701
|
+
hasStopped = true
|
|
702
|
+
if (rafHandle !== undefined) {
|
|
703
|
+
cancelAnimationFrame(rafHandle)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
debugInfoHistorySubscriptions.set(requestId, unsub)
|
|
708
|
+
|
|
709
|
+
break
|
|
710
|
+
}
|
|
711
|
+
case 'LSD.DebugInfoHistoryUnsubscribe': {
|
|
712
|
+
debugInfoHistorySubscriptions.get(requestId)!()
|
|
713
|
+
debugInfoHistorySubscriptions.delete(requestId)
|
|
714
|
+
break
|
|
715
|
+
}
|
|
716
|
+
case 'LSD.DebugInfoResetReq': {
|
|
717
|
+
this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
718
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
719
|
+
break
|
|
720
|
+
}
|
|
721
|
+
case 'LSD.DebugInfoRerunQueryReq': {
|
|
722
|
+
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
723
|
+
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
724
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
725
|
+
break
|
|
726
|
+
}
|
|
727
|
+
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
728
|
+
reactivityGraphSubcriptions.get(requestId)!()
|
|
729
|
+
break
|
|
730
|
+
}
|
|
731
|
+
case 'LSD.LiveQueriesSubscribe': {
|
|
732
|
+
const send = () =>
|
|
733
|
+
requestIdleCallback(
|
|
734
|
+
() =>
|
|
735
|
+
sendToDevtools(
|
|
736
|
+
Devtools.LiveQueriesRes.make({
|
|
737
|
+
liveQueries: [...this.activeQueries].map((q) => ({
|
|
738
|
+
_tag: q._tag,
|
|
739
|
+
id: q.id,
|
|
740
|
+
label: q.label,
|
|
741
|
+
runs: q.runs,
|
|
742
|
+
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
743
|
+
lastestResult:
|
|
744
|
+
q.results$.previousResult === NOT_REFRESHED_YET
|
|
745
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
746
|
+
: q.results$.previousResult,
|
|
747
|
+
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
748
|
+
})),
|
|
749
|
+
requestId,
|
|
750
|
+
liveStoreVersion,
|
|
751
|
+
}),
|
|
752
|
+
),
|
|
753
|
+
{ timeout: 500 },
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
send()
|
|
757
|
+
|
|
758
|
+
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
759
|
+
const throttledSend = throttle(send, 20)
|
|
760
|
+
|
|
761
|
+
liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
762
|
+
|
|
763
|
+
break
|
|
764
|
+
}
|
|
765
|
+
case 'LSD.LiveQueriesUnsubscribe': {
|
|
766
|
+
liveQueriesSubscriptions.get(requestId)!()
|
|
767
|
+
liveQueriesSubscriptions.delete(requestId)
|
|
768
|
+
break
|
|
769
|
+
}
|
|
770
|
+
// No default
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
storeMessagePort.start()
|
|
775
|
+
}),
|
|
776
|
+
runEffectFork,
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
return
|
|
627
780
|
}
|
|
628
|
-
|
|
629
|
-
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
630
784
|
})
|
|
631
|
-
}
|
|
632
785
|
|
|
633
786
|
__devDownloadDb = () => {
|
|
634
787
|
const data = this.mainDbWrapper.export()
|
|
@@ -636,146 +789,205 @@ export class Store<
|
|
|
636
789
|
}
|
|
637
790
|
|
|
638
791
|
__devDownloadMutationLogDb = async () => {
|
|
639
|
-
const data = await this.adapter.coordinator.getMutationLogData()
|
|
792
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(runEffectPromise)
|
|
640
793
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
641
794
|
}
|
|
642
795
|
|
|
643
796
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
644
|
-
dangerouslyResetStorage = (mode: ResetMode) => this.adapter.coordinator.dangerouslyReset(mode)
|
|
797
|
+
dangerouslyResetStorage = (mode: ResetMode) => this.adapter.coordinator.dangerouslyReset(mode).pipe(runEffectPromise)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
801
|
+
schema: TSchema
|
|
802
|
+
adapter: StoreAdapterFactory
|
|
803
|
+
reactivityGraph?: ReactivityGraph
|
|
804
|
+
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
805
|
+
otelOptions?: Partial<OtelOptions>
|
|
806
|
+
boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
807
|
+
batchUpdates?: (run: () => void) => void
|
|
808
|
+
disableDevtools?: boolean
|
|
809
|
+
onBootStatus?: (status: BootStatus) => void
|
|
645
810
|
}
|
|
646
811
|
|
|
647
812
|
/** Create a new LiveStore Store */
|
|
648
|
-
export const
|
|
813
|
+
export const createStorePromise = async <
|
|
814
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
815
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
816
|
+
>({
|
|
817
|
+
signal,
|
|
818
|
+
...options
|
|
819
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
|
|
820
|
+
Effect.gen(function* () {
|
|
821
|
+
const scope = yield* Scope.make()
|
|
822
|
+
const runtime = yield* Effect.runtime()
|
|
823
|
+
|
|
824
|
+
if (signal !== undefined) {
|
|
825
|
+
signal.addEventListener('abort', () => {
|
|
826
|
+
Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
|
|
827
|
+
})
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return yield* createStore({ ...options, storeScope: scope }).pipe(Scope.extend(scope))
|
|
831
|
+
}).pipe(Effect.withSpan('createStore'), runEffectPromise)
|
|
832
|
+
|
|
833
|
+
export const createStore = <
|
|
649
834
|
TGraphQLContext extends BaseGraphQLContext,
|
|
650
835
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
651
836
|
>({
|
|
652
837
|
schema,
|
|
653
838
|
graphQLOptions,
|
|
654
|
-
|
|
655
|
-
otelRootSpanContext = otel.context.active(),
|
|
839
|
+
otelOptions,
|
|
656
840
|
adapter: adapterFactory,
|
|
657
841
|
boot,
|
|
658
|
-
|
|
842
|
+
reactivityGraph = globalReactivityGraph,
|
|
659
843
|
batchUpdates,
|
|
660
844
|
disableDevtools,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
845
|
+
onBootStatus,
|
|
846
|
+
storeScope,
|
|
847
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & { storeScope: Scope.CloseableScope }): Effect.Effect<
|
|
848
|
+
Store<TGraphQLContext, TSchema>,
|
|
849
|
+
UnexpectedError,
|
|
850
|
+
Scope.Scope
|
|
851
|
+
> => {
|
|
852
|
+
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
853
|
+
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
854
|
+
|
|
855
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
856
|
+
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
return Effect.gen(function* () {
|
|
860
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
861
|
+
|
|
862
|
+
const bootStatusQueue = yield* Queue.unbounded<BootStatus>()
|
|
863
|
+
|
|
864
|
+
yield* Queue.take(bootStatusQueue).pipe(
|
|
865
|
+
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
866
|
+
Effect.forever,
|
|
867
|
+
Effect.tapCauseLogPretty,
|
|
868
|
+
Effect.forkScoped,
|
|
869
|
+
)
|
|
685
870
|
|
|
686
|
-
|
|
871
|
+
const adapter: StoreAdapter = yield* adapterFactory({
|
|
872
|
+
schema,
|
|
873
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
874
|
+
bootStatusQueue,
|
|
875
|
+
shutdown: (cause) => Scope.close(storeScope, Exit.failCause(cause)),
|
|
876
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
687
877
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
878
|
+
if (batchUpdates !== undefined) {
|
|
879
|
+
reactivityGraph.effectsWrapper = batchUpdates
|
|
880
|
+
}
|
|
692
881
|
|
|
693
|
-
|
|
694
|
-
_tag: 'BootDb',
|
|
695
|
-
execute: (queryStr, bindValues) => {
|
|
696
|
-
const stmt = adapter.mainDb.prepare(queryStr)
|
|
697
|
-
stmt.execute(bindValues)
|
|
882
|
+
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
698
883
|
|
|
699
|
-
|
|
700
|
-
txnExecuteStmnts.push([queryStr, bindValues])
|
|
701
|
-
} else {
|
|
702
|
-
void adapter.coordinator.execute(queryStr, bindValues, undefined)
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
mutate: (...list) => {
|
|
706
|
-
for (const mutationEventDecoded of list) {
|
|
707
|
-
const mutationDef =
|
|
708
|
-
schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
709
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
710
|
-
|
|
711
|
-
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
712
|
-
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
713
|
-
|
|
714
|
-
for (const { statementSql, bindValues } of execArgsArr) {
|
|
715
|
-
adapter.mainDb.execute(statementSql, bindValues)
|
|
716
|
-
}
|
|
884
|
+
const __processedMutationIds = new Set<string>()
|
|
717
885
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
886
|
+
// TODO consider moving booting into the storage backend
|
|
887
|
+
if (boot !== undefined) {
|
|
888
|
+
let isInTxn = false
|
|
889
|
+
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
890
|
+
|
|
891
|
+
const bootDbImpl: BootDb = {
|
|
892
|
+
_tag: 'BootDb',
|
|
893
|
+
execute: (queryStr, bindValues) => {
|
|
894
|
+
const stmt = adapter.mainDb.prepare(queryStr)
|
|
895
|
+
stmt.execute(bindValues)
|
|
896
|
+
|
|
897
|
+
if (isInTxn === true) {
|
|
898
|
+
txnExecuteStmnts.push([queryStr, bindValues])
|
|
899
|
+
} else {
|
|
900
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
mutate: (...list) => {
|
|
904
|
+
for (const mutationEventDecoded of list) {
|
|
905
|
+
const mutationDef =
|
|
906
|
+
schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
907
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
908
|
+
|
|
909
|
+
__processedMutationIds.add(mutationEventDecoded.id)
|
|
910
|
+
|
|
911
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
912
|
+
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
913
|
+
|
|
914
|
+
for (const { statementSql, bindValues } of execArgsArr) {
|
|
915
|
+
adapter.mainDb.execute(statementSql, bindValues)
|
|
746
916
|
}
|
|
747
|
-
},
|
|
748
|
-
}
|
|
749
917
|
|
|
750
|
-
|
|
751
|
-
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
752
|
-
if (isPromise(booting)) {
|
|
753
|
-
await booting
|
|
754
|
-
}
|
|
755
|
-
}
|
|
918
|
+
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
|
|
756
919
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
{
|
|
762
|
-
adapter: adapter,
|
|
763
|
-
schema,
|
|
764
|
-
graphQLOptions,
|
|
765
|
-
otelTracer,
|
|
766
|
-
otelRootSpanContext,
|
|
767
|
-
dbGraph,
|
|
768
|
-
mutationEventSchema,
|
|
769
|
-
disableDevtools,
|
|
920
|
+
adapter.coordinator
|
|
921
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
922
|
+
.pipe(runEffectFork)
|
|
923
|
+
}
|
|
770
924
|
},
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
925
|
+
select: (queryStr, bindValues) => {
|
|
926
|
+
const stmt = adapter.mainDb.prepare(queryStr)
|
|
927
|
+
return stmt.select(bindValues)
|
|
928
|
+
},
|
|
929
|
+
txn: (callback) => {
|
|
930
|
+
try {
|
|
931
|
+
isInTxn = true
|
|
932
|
+
adapter.mainDb.execute('BEGIN', undefined)
|
|
933
|
+
|
|
934
|
+
callback()
|
|
935
|
+
|
|
936
|
+
adapter.mainDb.execute('COMMIT', undefined)
|
|
937
|
+
|
|
938
|
+
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
939
|
+
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
940
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
941
|
+
}
|
|
942
|
+
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
943
|
+
} catch (e: any) {
|
|
944
|
+
adapter.mainDb.execute('ROLLBACK', undefined)
|
|
945
|
+
throw e
|
|
946
|
+
} finally {
|
|
947
|
+
isInTxn = false
|
|
948
|
+
txnExecuteStmnts = []
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const booting = yield* Effect.try({
|
|
954
|
+
try: () => boot(bootDbImpl, span),
|
|
955
|
+
catch: (cause) => new UnexpectedError({ cause }),
|
|
956
|
+
})
|
|
957
|
+
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
958
|
+
if (isPromise(booting)) {
|
|
959
|
+
yield* Effect.tryPromise({ try: () => booting, catch: (cause) => new UnexpectedError({ cause }) })
|
|
960
|
+
}
|
|
775
961
|
}
|
|
776
|
-
|
|
962
|
+
|
|
963
|
+
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
964
|
+
// Think about what to do about this case.
|
|
965
|
+
// await applySchema(db, schema)
|
|
966
|
+
return Store.createStore<TGraphQLContext, TSchema>(
|
|
967
|
+
{
|
|
968
|
+
adapter,
|
|
969
|
+
schema,
|
|
970
|
+
graphQLOptions,
|
|
971
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
972
|
+
reactivityGraph,
|
|
973
|
+
disableDevtools,
|
|
974
|
+
__processedMutationIds,
|
|
975
|
+
storeScope,
|
|
976
|
+
},
|
|
977
|
+
span,
|
|
978
|
+
)
|
|
979
|
+
}).pipe(
|
|
980
|
+
// Effect.scoped,
|
|
981
|
+
Effect.withSpan('createStore', {
|
|
982
|
+
parent: otelOptions?.rootSpanContext
|
|
983
|
+
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
|
|
984
|
+
: undefined,
|
|
985
|
+
}),
|
|
986
|
+
Effect.provide(TracingLive),
|
|
987
|
+
)
|
|
777
988
|
}
|
|
778
989
|
|
|
990
|
+
// TODO consider replacing with Effect's RC data structures
|
|
779
991
|
class ReferenceCountedSet<T> {
|
|
780
992
|
private map: Map<T, number>
|
|
781
993
|
|
|
@@ -811,3 +1023,21 @@ class ReferenceCountedSet<T> {
|
|
|
811
1023
|
}
|
|
812
1024
|
}
|
|
813
1025
|
}
|
|
1026
|
+
|
|
1027
|
+
const runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1028
|
+
effect.pipe(
|
|
1029
|
+
Effect.tapCauseLogPretty,
|
|
1030
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
1031
|
+
Effect.provide(Logger.pretty),
|
|
1032
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
1033
|
+
Effect.runFork,
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
const runEffectPromise = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1037
|
+
effect.pipe(
|
|
1038
|
+
Effect.tapCauseLogPretty,
|
|
1039
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
1040
|
+
Effect.provide(Logger.pretty),
|
|
1041
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
1042
|
+
Effect.runPromise,
|
|
1043
|
+
)
|