@livestore/livestore 0.0.54-dev.2 → 0.0.54-dev.22
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 +4 -4
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +10 -8
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +2 -3
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +4 -2
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts +1 -1
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +2 -2
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +3 -4
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +8 -13
- package/dist/react/LiveStoreProvider.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 +3 -3
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useRow.test.js +21 -21
- package/dist/react/useRow.test.js.map +1 -1
- package/dist/react/useTemporaryQuery.js +1 -1
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/reactive.js +1 -1
- 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/row-query.d.ts +2 -2
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +4 -4
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +23 -20
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +140 -144
- package/dist/store.js.map +1 -1
- package/package.json +5 -12
- package/src/MainDatabaseWrapper.ts +14 -8
- package/src/QueryCache.ts +1 -2
- package/src/__tests__/react/fixture.tsx +11 -9
- package/src/effect/LiveStore.ts +6 -5
- package/src/global-state.ts +2 -2
- package/src/index.ts +17 -4
- package/src/react/LiveStoreProvider.tsx +10 -18
- package/src/react/useRow.test.tsx +21 -21
- package/src/react/useRow.ts +5 -5
- package/src/react/useTemporaryQuery.ts +2 -2
- package/src/reactive.ts +3 -1
- 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.ts +19 -21
- package/src/row-query.ts +8 -8
- package/src/store.ts +214 -179
- 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,29 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
BootDb,
|
|
3
|
+
ParamsObject,
|
|
4
|
+
PreparedBindValues,
|
|
5
|
+
ResetMode,
|
|
6
|
+
StoreAdapter,
|
|
7
|
+
StoreAdapterFactory,
|
|
8
|
+
UnexpectedError,
|
|
9
|
+
} from '@livestore/common'
|
|
10
|
+
import { Devtools, getExecArgsFromMutation, prepareBindValues } from '@livestore/common'
|
|
3
11
|
import { version as liveStoreVersion } from '@livestore/common/package.json'
|
|
4
|
-
import type { LiveStoreSchema, MutationEvent
|
|
5
|
-
import {
|
|
6
|
-
import { assertNever, isPromise, makeNoopTracer,
|
|
7
|
-
import { Effect, Schema, Stream } from '@livestore/utils/effect'
|
|
12
|
+
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
13
|
+
import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
|
|
14
|
+
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
15
|
+
import { Effect, Layer, OtelTracer, Schema, Stream } from '@livestore/utils/effect'
|
|
8
16
|
import * as otel from '@opentelemetry/api'
|
|
9
17
|
import type { GraphQLSchema } from 'graphql'
|
|
10
18
|
|
|
11
|
-
import {
|
|
19
|
+
import { globalReactivityGraph } from './global-state.js'
|
|
12
20
|
import { MainDatabaseWrapper } from './MainDatabaseWrapper.js'
|
|
13
21
|
import type { StackInfo } from './react/utils/stack-info.js'
|
|
14
|
-
import type { DebugRefreshReasonBase,
|
|
15
|
-
import
|
|
22
|
+
import type { DebugRefreshReasonBase, Ref } from './reactive.js'
|
|
23
|
+
import { NOT_REFRESHED_YET } from './reactive.js'
|
|
24
|
+
import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
|
|
16
25
|
import { downloadBlob } from './utils/dev.js'
|
|
17
26
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
18
|
-
import type { ParamsObject } from './utils/util.js'
|
|
19
|
-
import { prepareBindValues } from './utils/util.js'
|
|
20
27
|
|
|
21
28
|
export type BaseGraphQLContext = {
|
|
22
29
|
queriedTables: Set<string>
|
|
@@ -29,17 +36,21 @@ export type GraphQLOptions<TContext> = {
|
|
|
29
36
|
makeContext: (db: MainDatabaseWrapper, tracer: otel.Tracer) => TContext
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
export type OtelOptions = {
|
|
40
|
+
tracer: otel.Tracer
|
|
41
|
+
rootSpanContext: otel.Context
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
export type StoreOptions<
|
|
33
45
|
TGraphQLContext extends BaseGraphQLContext,
|
|
34
46
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
35
47
|
> = {
|
|
36
48
|
adapter: StoreAdapter
|
|
37
49
|
schema: TSchema
|
|
50
|
+
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
38
51
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
dbGraph: DbGraph
|
|
42
|
-
mutationEventSchema: MutationEventSchema<any>
|
|
52
|
+
otelOptions: OtelOptions
|
|
53
|
+
reactivityGraph: ReactivityGraph
|
|
43
54
|
disableDevtools?: boolean
|
|
44
55
|
}
|
|
45
56
|
|
|
@@ -95,11 +106,8 @@ export class Store<
|
|
|
95
106
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
96
107
|
> {
|
|
97
108
|
id = uniqueStoreId()
|
|
98
|
-
|
|
109
|
+
reactivityGraph: ReactivityGraph
|
|
99
110
|
mainDbWrapper: MainDatabaseWrapper
|
|
100
|
-
// TODO refactor
|
|
101
|
-
// _proxyDb: InMemoryDatabase
|
|
102
|
-
// TODO
|
|
103
111
|
adapter: StoreAdapter
|
|
104
112
|
schema: LiveStoreSchema
|
|
105
113
|
graphQLSchema?: GraphQLSchema
|
|
@@ -109,7 +117,7 @@ export class Store<
|
|
|
109
117
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
|
110
118
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
|
111
119
|
*/
|
|
112
|
-
tableRefs: { [key: string]: Ref<null,
|
|
120
|
+
tableRefs: { [key: string]: Ref<null, QueryContext, RefreshReason> }
|
|
113
121
|
|
|
114
122
|
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
115
123
|
__processedMutationIds = new Set<string>()
|
|
@@ -124,32 +132,33 @@ export class Store<
|
|
|
124
132
|
adapter,
|
|
125
133
|
schema,
|
|
126
134
|
graphQLOptions,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
otelRootSpanContext,
|
|
130
|
-
mutationEventSchema,
|
|
135
|
+
reactivityGraph,
|
|
136
|
+
otelOptions,
|
|
131
137
|
disableDevtools,
|
|
132
138
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
133
|
-
this.mainDbWrapper = new MainDatabaseWrapper({
|
|
139
|
+
this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb })
|
|
134
140
|
this.adapter = adapter
|
|
135
141
|
this.schema = schema
|
|
136
142
|
|
|
137
143
|
// TODO refactor
|
|
138
|
-
this.__mutationEventSchema =
|
|
139
|
-
// this.mutationEventSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
|
|
144
|
+
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
140
145
|
|
|
141
146
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
142
147
|
this.tableRefs = {}
|
|
143
148
|
this.activeQueries = new ReferenceCountedSet()
|
|
144
149
|
|
|
145
|
-
const mutationsSpan =
|
|
150
|
+
const mutationsSpan = otelOptions.tracer.startSpan('LiveStore:mutations', {}, otelOptions.rootSpanContext)
|
|
146
151
|
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(), mutationsSpan)
|
|
147
152
|
|
|
148
|
-
const queriesSpan =
|
|
153
|
+
const queriesSpan = otelOptions.tracer.startSpan('LiveStore:queries', {}, otelOptions.rootSpanContext)
|
|
149
154
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
|
150
155
|
|
|
151
|
-
this.
|
|
152
|
-
this.
|
|
156
|
+
this.reactivityGraph = reactivityGraph
|
|
157
|
+
this.reactivityGraph.context = {
|
|
158
|
+
store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
|
|
159
|
+
otelTracer: otelOptions.tracer,
|
|
160
|
+
rootOtelContext: otelQueriesSpanContext,
|
|
161
|
+
}
|
|
153
162
|
|
|
154
163
|
this.adapter.coordinator.syncMutations.pipe(
|
|
155
164
|
Stream.tapSync((mutationEventDecoded) => {
|
|
@@ -165,7 +174,7 @@ export class Store<
|
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
this.otel = {
|
|
168
|
-
tracer:
|
|
177
|
+
tracer: otelOptions.tracer,
|
|
169
178
|
mutationsSpanContext: otelMuationsSpanContext,
|
|
170
179
|
queriesSpanContext: otelQueriesSpanContext,
|
|
171
180
|
}
|
|
@@ -177,7 +186,7 @@ export class Store<
|
|
|
177
186
|
// ...Array.from(dynamicallyRegisteredTables.values()).map((_) => _.sqliteDef.name),
|
|
178
187
|
)
|
|
179
188
|
const existingTableRefs = new Map(
|
|
180
|
-
Array.from(this.
|
|
189
|
+
Array.from(this.reactivityGraph.atoms.values())
|
|
181
190
|
.filter((_): _ is Ref<any, any, any> => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
182
191
|
.map((_) => [_.label!.slice('tableRef:'.length), _] as const),
|
|
183
192
|
)
|
|
@@ -196,7 +205,7 @@ export class Store<
|
|
|
196
205
|
parentSpan: otel.Span,
|
|
197
206
|
): Store<TGraphQLContext, TSchema> => {
|
|
198
207
|
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
|
|
199
|
-
return storeOptions.
|
|
208
|
+
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:store-constructor', {}, ctx, (span) => {
|
|
200
209
|
try {
|
|
201
210
|
return new Store(storeOptions)
|
|
202
211
|
} finally {
|
|
@@ -224,7 +233,7 @@ export class Store<
|
|
|
224
233
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
225
234
|
|
|
226
235
|
const label = `subscribe:${options?.label}`
|
|
227
|
-
const effect = this.
|
|
236
|
+
const effect = this.reactivityGraph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
|
|
228
237
|
|
|
229
238
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
|
230
239
|
|
|
@@ -236,7 +245,7 @@ export class Store<
|
|
|
236
245
|
const unsubscribe = () => {
|
|
237
246
|
// console.log('store unsub', query$.label)
|
|
238
247
|
try {
|
|
239
|
-
this.
|
|
248
|
+
this.reactivityGraph.destroyNode(effect)
|
|
240
249
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
241
250
|
onUnsubsubscribe?.()
|
|
242
251
|
} finally {
|
|
@@ -256,14 +265,14 @@ export class Store<
|
|
|
256
265
|
destroy = async () => {
|
|
257
266
|
for (const tableRef of Object.values(this.tableRefs)) {
|
|
258
267
|
for (const superComp of tableRef.super) {
|
|
259
|
-
this.
|
|
268
|
+
this.reactivityGraph.removeEdge(superComp, tableRef)
|
|
260
269
|
}
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
264
273
|
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
265
274
|
|
|
266
|
-
await this.adapter.coordinator.shutdown()
|
|
275
|
+
await this.adapter.coordinator.shutdown.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
267
276
|
}
|
|
268
277
|
|
|
269
278
|
mutate: {
|
|
@@ -374,7 +383,7 @@ export class Store<
|
|
|
374
383
|
},
|
|
375
384
|
)
|
|
376
385
|
|
|
377
|
-
const tablesToUpdate = [] as [Ref<null,
|
|
386
|
+
const tablesToUpdate = [] as [Ref<null, QueryContext, RefreshReason>, null][]
|
|
378
387
|
for (const tableName of writeTables) {
|
|
379
388
|
const tableRef = this.tableRefs[tableName]
|
|
380
389
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
@@ -388,7 +397,7 @@ export class Store<
|
|
|
388
397
|
}
|
|
389
398
|
|
|
390
399
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
391
|
-
this.
|
|
400
|
+
this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh })
|
|
392
401
|
} catch (e: any) {
|
|
393
402
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
394
403
|
} finally {
|
|
@@ -412,7 +421,7 @@ export class Store<
|
|
|
412
421
|
this.otel.mutationsSpanContext,
|
|
413
422
|
(span) => {
|
|
414
423
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
415
|
-
this.
|
|
424
|
+
this.reactivityGraph.runDeferredEffects({ otelContext })
|
|
416
425
|
span.end()
|
|
417
426
|
},
|
|
418
427
|
)
|
|
@@ -480,10 +489,9 @@ export class Store<
|
|
|
480
489
|
|
|
481
490
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
482
491
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
})
|
|
492
|
+
this.adapter.coordinator
|
|
493
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
494
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
487
495
|
}
|
|
488
496
|
|
|
489
497
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -509,8 +517,9 @@ export class Store<
|
|
|
509
517
|
) => {
|
|
510
518
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
511
519
|
|
|
512
|
-
|
|
513
|
-
|
|
520
|
+
this.adapter.coordinator
|
|
521
|
+
.execute(query, prepareBindValues(params, query))
|
|
522
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
514
523
|
}
|
|
515
524
|
|
|
516
525
|
select = (query: string, params: ParamsObject = {}) => {
|
|
@@ -518,7 +527,7 @@ export class Store<
|
|
|
518
527
|
}
|
|
519
528
|
|
|
520
529
|
makeTableRef = (tableName: string) =>
|
|
521
|
-
this.
|
|
530
|
+
this.reactivityGraph.makeRef(null, {
|
|
522
531
|
equal: () => false,
|
|
523
532
|
label: `tableRef:${tableName}`,
|
|
524
533
|
meta: { liveStoreRefType: 'table' },
|
|
@@ -527,14 +536,16 @@ export class Store<
|
|
|
527
536
|
private bootDevtools = () => {
|
|
528
537
|
const devtoolsChannel = Devtools.makeBroadcastChannels()
|
|
529
538
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
539
|
+
type Unsub = () => void
|
|
540
|
+
type RequestId = string
|
|
541
|
+
|
|
542
|
+
const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
|
|
543
|
+
const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
|
|
533
544
|
devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
|
|
534
545
|
const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHost)(event.data)
|
|
535
546
|
if (
|
|
536
547
|
decoded._tag === 'None' ||
|
|
537
|
-
decoded.value._tag === 'LSD.
|
|
548
|
+
decoded.value._tag === 'LSD.DevtoolsReady' ||
|
|
538
549
|
decoded.value._tag === 'LSD.DevtoolsConnected' ||
|
|
539
550
|
decoded.value.channelId !== this.adapter.coordinator.devtools.channelId
|
|
540
551
|
) {
|
|
@@ -547,12 +558,12 @@ export class Store<
|
|
|
547
558
|
devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
|
|
548
559
|
|
|
549
560
|
switch (decoded.value._tag) {
|
|
550
|
-
case 'LSD.
|
|
561
|
+
case 'LSD.ReactivityGraphSubscribe': {
|
|
551
562
|
const includeResults = decoded.value.includeResults
|
|
552
563
|
const send = () =>
|
|
553
564
|
sendToDevtools(
|
|
554
|
-
Devtools.
|
|
555
|
-
|
|
565
|
+
Devtools.ReactivityGraphRes.make({
|
|
566
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
556
567
|
requestId,
|
|
557
568
|
liveStoreVersion,
|
|
558
569
|
}),
|
|
@@ -560,9 +571,10 @@ export class Store<
|
|
|
560
571
|
|
|
561
572
|
send()
|
|
562
573
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
574
|
+
reactivityGraphSubcriptions.set(
|
|
575
|
+
requestId,
|
|
576
|
+
this.reactivityGraph.subscribeToRefresh(() => send()),
|
|
577
|
+
)
|
|
566
578
|
|
|
567
579
|
break
|
|
568
580
|
}
|
|
@@ -583,9 +595,8 @@ export class Store<
|
|
|
583
595
|
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
584
596
|
break
|
|
585
597
|
}
|
|
586
|
-
case 'LSD.
|
|
587
|
-
|
|
588
|
-
signalsSubcriptionRef.current = undefined
|
|
598
|
+
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
599
|
+
reactivityGraphSubcriptions.get(requestId)!()
|
|
589
600
|
break
|
|
590
601
|
}
|
|
591
602
|
case 'LSD.LiveQueriesSubscribe': {
|
|
@@ -598,7 +609,10 @@ export class Store<
|
|
|
598
609
|
label: q.label,
|
|
599
610
|
runs: q.runs,
|
|
600
611
|
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
601
|
-
lastestResult:
|
|
612
|
+
lastestResult:
|
|
613
|
+
q.results$.previousResult === NOT_REFRESHED_YET
|
|
614
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
615
|
+
: q.results$.previousResult,
|
|
602
616
|
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
603
617
|
})),
|
|
604
618
|
requestId,
|
|
@@ -608,19 +622,22 @@ export class Store<
|
|
|
608
622
|
|
|
609
623
|
send()
|
|
610
624
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
625
|
+
liveQueriesSubscriptions.set(
|
|
626
|
+
requestId,
|
|
627
|
+
this.reactivityGraph.subscribeToRefresh(() => send()),
|
|
628
|
+
)
|
|
614
629
|
|
|
615
630
|
break
|
|
616
631
|
}
|
|
617
632
|
case 'LSD.LiveQueriesUnsubscribe': {
|
|
618
|
-
|
|
619
|
-
liveQueriesSubscriptionRef.current = undefined
|
|
633
|
+
liveQueriesSubscriptions.get(requestId)!()
|
|
620
634
|
break
|
|
621
635
|
}
|
|
622
636
|
case 'LSD.ResetAllDataReq': {
|
|
623
|
-
await this.adapter.coordinator
|
|
637
|
+
await this.adapter.coordinator
|
|
638
|
+
.dangerouslyReset(decoded.value.mode)
|
|
639
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
640
|
+
|
|
624
641
|
sendToDevtools(Devtools.ResetAllDataRes.make({ requestId, liveStoreVersion }))
|
|
625
642
|
|
|
626
643
|
break
|
|
@@ -636,144 +653,162 @@ export class Store<
|
|
|
636
653
|
}
|
|
637
654
|
|
|
638
655
|
__devDownloadMutationLogDb = async () => {
|
|
639
|
-
const data = await this.adapter.coordinator.getMutationLogData()
|
|
656
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
640
657
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
641
658
|
}
|
|
642
659
|
|
|
643
660
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
644
|
-
dangerouslyResetStorage = (mode: ResetMode) =>
|
|
661
|
+
dangerouslyResetStorage = (mode: ResetMode) =>
|
|
662
|
+
this.adapter.coordinator.dangerouslyReset(mode).pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
666
|
+
schema: TSchema
|
|
667
|
+
adapter: StoreAdapterFactory
|
|
668
|
+
reactivityGraph?: ReactivityGraph
|
|
669
|
+
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
670
|
+
otelOptions?: Partial<OtelOptions>
|
|
671
|
+
boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
672
|
+
batchUpdates?: (run: () => void) => void
|
|
673
|
+
disableDevtools?: boolean
|
|
645
674
|
}
|
|
646
675
|
|
|
647
676
|
/** Create a new LiveStore Store */
|
|
648
677
|
export const createStore = async <
|
|
649
678
|
TGraphQLContext extends BaseGraphQLContext,
|
|
650
679
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
680
|
+
>(
|
|
681
|
+
options: CreateStoreOptions<TGraphQLContext, TSchema>,
|
|
682
|
+
): Promise<Store<TGraphQLContext, TSchema>> => createStoreEff(options).pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
683
|
+
|
|
684
|
+
export const createStoreEff = <
|
|
685
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
686
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
651
687
|
>({
|
|
652
688
|
schema,
|
|
653
689
|
graphQLOptions,
|
|
654
|
-
|
|
655
|
-
otelRootSpanContext = otel.context.active(),
|
|
690
|
+
otelOptions,
|
|
656
691
|
adapter: adapterFactory,
|
|
657
692
|
boot,
|
|
658
|
-
|
|
693
|
+
reactivityGraph = globalReactivityGraph,
|
|
659
694
|
batchUpdates,
|
|
660
695
|
disableDevtools,
|
|
661
|
-
}: {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
otelTracer?: otel.Tracer
|
|
665
|
-
otelRootSpanContext?: otel.Context
|
|
666
|
-
adapter: StoreAdapterFactory
|
|
667
|
-
boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
668
|
-
dbGraph?: DbGraph
|
|
669
|
-
batchUpdates?: (run: () => void) => void
|
|
670
|
-
disableDevtools?: boolean
|
|
671
|
-
}): Promise<Store<TGraphQLContext, TSchema>> => {
|
|
672
|
-
return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
|
|
673
|
-
try {
|
|
674
|
-
performance.mark('livestore:db-creating')
|
|
675
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
676
|
-
|
|
677
|
-
const adapterPromise = adapterFactory({ otelTracer, otelContext, schema })
|
|
678
|
-
const adapter = adapterPromise instanceof Promise ? await adapterPromise : adapterPromise
|
|
679
|
-
performance.mark('livestore:db-created')
|
|
680
|
-
performance.measure('livestore:db-create', 'livestore:db-creating', 'livestore:db-created')
|
|
681
|
-
|
|
682
|
-
if (batchUpdates !== undefined) {
|
|
683
|
-
dbGraph.effectsWrapper = batchUpdates
|
|
684
|
-
}
|
|
696
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<Store<TGraphQLContext, TSchema>, UnexpectedError> => {
|
|
697
|
+
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
698
|
+
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
685
699
|
|
|
686
|
-
|
|
700
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
701
|
+
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
702
|
+
)
|
|
687
703
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
let isInTxn = false
|
|
691
|
-
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
704
|
+
return Effect.gen(function* () {
|
|
705
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
692
706
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
stmt.execute(bindValues)
|
|
707
|
+
const adapter = yield* adapterFactory({ schema }).pipe(
|
|
708
|
+
Effect.withPerformanceMeasure('livestore:makeAdapter'),
|
|
709
|
+
Effect.withSpan('createStore:makeAdapter'),
|
|
710
|
+
)
|
|
698
711
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
}
|
|
712
|
+
if (batchUpdates !== undefined) {
|
|
713
|
+
reactivityGraph.effectsWrapper = batchUpdates
|
|
714
|
+
}
|
|
717
715
|
|
|
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
|
-
|
|
716
|
+
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
717
|
+
|
|
718
|
+
// TODO consider moving booting into the storage backend
|
|
719
|
+
if (boot !== undefined) {
|
|
720
|
+
let isInTxn = false
|
|
721
|
+
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
722
|
+
|
|
723
|
+
const bootDbImpl: BootDb = {
|
|
724
|
+
_tag: 'BootDb',
|
|
725
|
+
execute: (queryStr, bindValues) => {
|
|
726
|
+
const stmt = adapter.mainDb.prepare(queryStr)
|
|
727
|
+
stmt.execute(bindValues)
|
|
728
|
+
|
|
729
|
+
if (isInTxn === true) {
|
|
730
|
+
txnExecuteStmnts.push([queryStr, bindValues])
|
|
731
|
+
} else {
|
|
732
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
mutate: (...list) => {
|
|
736
|
+
for (const mutationEventDecoded of list) {
|
|
737
|
+
const mutationDef =
|
|
738
|
+
schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
739
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
740
|
+
|
|
741
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
742
|
+
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
743
|
+
|
|
744
|
+
for (const { statementSql, bindValues } of execArgsArr) {
|
|
745
|
+
adapter.mainDb.execute(statementSql, bindValues)
|
|
746
746
|
}
|
|
747
|
-
},
|
|
748
|
-
}
|
|
749
747
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
748
|
+
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
|
|
749
|
+
|
|
750
|
+
adapter.coordinator
|
|
751
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
752
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
select: (queryStr, bindValues) => {
|
|
756
|
+
const stmt = adapter.mainDb.prepare(queryStr)
|
|
757
|
+
return stmt.select(bindValues)
|
|
758
|
+
},
|
|
759
|
+
txn: (callback) => {
|
|
760
|
+
try {
|
|
761
|
+
isInTxn = true
|
|
762
|
+
adapter.mainDb.execute('BEGIN', undefined)
|
|
763
|
+
|
|
764
|
+
callback()
|
|
756
765
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
766
|
+
adapter.mainDb.execute('COMMIT', undefined)
|
|
767
|
+
|
|
768
|
+
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
769
|
+
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
770
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
771
|
+
}
|
|
772
|
+
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
773
|
+
} catch (e: any) {
|
|
774
|
+
adapter.mainDb.execute('ROLLBACK', undefined)
|
|
775
|
+
throw e
|
|
776
|
+
} finally {
|
|
777
|
+
isInTxn = false
|
|
778
|
+
txnExecuteStmnts = []
|
|
779
|
+
}
|
|
770
780
|
},
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const booting = boot(bootDbImpl, span)
|
|
784
|
+
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
785
|
+
if (isPromise(booting)) {
|
|
786
|
+
yield* Effect.promise(() => booting)
|
|
787
|
+
}
|
|
775
788
|
}
|
|
776
|
-
|
|
789
|
+
|
|
790
|
+
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
791
|
+
// Think about what to do about this case.
|
|
792
|
+
// await applySchema(db, schema)
|
|
793
|
+
return Store.createStore<TGraphQLContext, TSchema>(
|
|
794
|
+
{
|
|
795
|
+
adapter,
|
|
796
|
+
schema,
|
|
797
|
+
graphQLOptions,
|
|
798
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
799
|
+
reactivityGraph,
|
|
800
|
+
disableDevtools,
|
|
801
|
+
},
|
|
802
|
+
span,
|
|
803
|
+
)
|
|
804
|
+
}).pipe(
|
|
805
|
+
Effect.withSpan('createStore', {
|
|
806
|
+
parent: otelOptions?.rootSpanContext
|
|
807
|
+
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
|
|
808
|
+
: undefined,
|
|
809
|
+
}),
|
|
810
|
+
Effect.provide(TracingLive),
|
|
811
|
+
)
|
|
777
812
|
}
|
|
778
813
|
|
|
779
814
|
class ReferenceCountedSet<T> {
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates a map that has a fixed number of entries.
|
|
3
|
-
* Once hitting the bound, earliest insertions are removed
|
|
4
|
-
*/
|
|
5
|
-
export default class BoundMap<K, V> {
|
|
6
|
-
#private;
|
|
7
|
-
constructor(sizeLimit: number);
|
|
8
|
-
onEvict: ((key: K, value: V) => void) | undefined;
|
|
9
|
-
set: (key: K, value: V) => void;
|
|
10
|
-
get: (key: K) => V | undefined;
|
|
11
|
-
delete: (key: K) => void;
|
|
12
|
-
keys: () => IterableIterator<K>;
|
|
13
|
-
}
|
|
14
|
-
export declare class BoundSet<V> {
|
|
15
|
-
#private;
|
|
16
|
-
constructor(sizeLimit: number);
|
|
17
|
-
onEvict: ((key: V) => void) | undefined;
|
|
18
|
-
add: (v: V) => void;
|
|
19
|
-
[Symbol.iterator]: () => IterableIterator<V>;
|
|
20
|
-
}
|
|
21
|
-
export declare class BoundArray<V> {
|
|
22
|
-
#private;
|
|
23
|
-
constructor(sizeLimit: number);
|
|
24
|
-
onEvict: ((key: V) => void) | undefined;
|
|
25
|
-
push: (v: V) => void;
|
|
26
|
-
get: (index: number) => V | undefined;
|
|
27
|
-
delete: (index: number) => void;
|
|
28
|
-
get length(): number;
|
|
29
|
-
[Symbol.iterator]: () => IterableIterator<V>;
|
|
30
|
-
map: <T>(fn: (v: V) => T) => T[];
|
|
31
|
-
clear: () => void;
|
|
32
|
-
sort: (fn?: (a: V, b: V) => number) => V[];
|
|
33
|
-
}
|
|
34
|
-
//# sourceMappingURL=bounded-collections.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bounded-collections.d.ts","sourceRoot":"","sources":["../../src/utils/bounded-collections.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ,CAAC,CAAC,EAAE,CAAC;;gBAIpB,SAAS,EAAE,MAAM;IAI7B,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAEjD,GAAG,QAAS,CAAC,SAAS,CAAC,UAWtB;IAED,GAAG,QAAS,CAAC,KAAG,CAAC,GAAG,SAAS,CAE5B;IAED,MAAM,QAAS,CAAC,UAEf;IAED,IAAI,4BAEH;CACF;AAED,qBAAa,QAAQ,CAAC,CAAC;;gBAGT,SAAS,EAAE,MAAM;IAW7B,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAEvC,GAAG,MAAO,CAAC,UAET;IAEF,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAEhB;CACF;AAED,qBAAa,UAAU,CAAC,CAAC;;gBAIX,SAAS,EAAE,MAAM;IAI7B,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAEvC,IAAI,MAAO,CAAC,UAQX;IAED,GAAG,UAAW,MAAM,KAAG,CAAC,GAAG,SAAS,CAEnC;IAED,MAAM,UAAW,MAAM,UAEtB;IAED,IAAI,MAAM,WAET;IAED,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAEhB;IAED,GAAG,UAAW,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAG,CAAC,EAAE,CAE9B;IAED,KAAK,aAEJ;IAED,IAAI,QAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,SAElC;CACF"}
|