@livestore/livestore 0.0.54-dev.21 → 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/__tests__/react/fixture.d.ts +1 -1
- package/dist/reactive.js +1 -1
- package/dist/reactive.js.map +1 -1
- package/dist/row-query.js +2 -2
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +7 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +102 -101
- package/dist/store.js.map +1 -1
- package/package.json +5 -5
- package/src/reactive.ts +3 -1
- package/src/row-query.ts +2 -2
- package/src/store.ts +143 -119
package/src/store.ts
CHANGED
|
@@ -5,13 +5,14 @@ import type {
|
|
|
5
5
|
ResetMode,
|
|
6
6
|
StoreAdapter,
|
|
7
7
|
StoreAdapterFactory,
|
|
8
|
+
UnexpectedError,
|
|
8
9
|
} from '@livestore/common'
|
|
9
10
|
import { Devtools, getExecArgsFromMutation, prepareBindValues } from '@livestore/common'
|
|
10
11
|
import { version as liveStoreVersion } from '@livestore/common/package.json'
|
|
11
12
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
12
13
|
import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
|
|
13
14
|
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
14
|
-
import { Effect, Schema, Stream } from '@livestore/utils/effect'
|
|
15
|
+
import { Effect, Layer, OtelTracer, Schema, Stream } from '@livestore/utils/effect'
|
|
15
16
|
import * as otel from '@opentelemetry/api'
|
|
16
17
|
import type { GraphQLSchema } from 'graphql'
|
|
17
18
|
|
|
@@ -19,6 +20,7 @@ import { globalReactivityGraph } from './global-state.js'
|
|
|
19
20
|
import { MainDatabaseWrapper } from './MainDatabaseWrapper.js'
|
|
20
21
|
import type { StackInfo } from './react/utils/stack-info.js'
|
|
21
22
|
import type { DebugRefreshReasonBase, Ref } from './reactive.js'
|
|
23
|
+
import { NOT_REFRESHED_YET } from './reactive.js'
|
|
22
24
|
import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
|
|
23
25
|
import { downloadBlob } from './utils/dev.js'
|
|
24
26
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
@@ -153,7 +155,7 @@ export class Store<
|
|
|
153
155
|
|
|
154
156
|
this.reactivityGraph = reactivityGraph
|
|
155
157
|
this.reactivityGraph.context = {
|
|
156
|
-
store: this as
|
|
158
|
+
store: this as unknown as Store<BaseGraphQLContext, LiveStoreSchema>,
|
|
157
159
|
otelTracer: otelOptions.tracer,
|
|
158
160
|
rootOtelContext: otelQueriesSpanContext,
|
|
159
161
|
}
|
|
@@ -270,7 +272,7 @@ export class Store<
|
|
|
270
272
|
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
271
273
|
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
272
274
|
|
|
273
|
-
await this.adapter.coordinator.shutdown()
|
|
275
|
+
await this.adapter.coordinator.shutdown.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
mutate: {
|
|
@@ -487,10 +489,9 @@ export class Store<
|
|
|
487
489
|
|
|
488
490
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
489
491
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
})
|
|
492
|
+
this.adapter.coordinator
|
|
493
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
494
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
494
495
|
}
|
|
495
496
|
|
|
496
497
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -516,8 +517,9 @@ export class Store<
|
|
|
516
517
|
) => {
|
|
517
518
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
518
519
|
|
|
519
|
-
|
|
520
|
-
|
|
520
|
+
this.adapter.coordinator
|
|
521
|
+
.execute(query, prepareBindValues(params, query))
|
|
522
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
521
523
|
}
|
|
522
524
|
|
|
523
525
|
select = (query: string, params: ParamsObject = {}) => {
|
|
@@ -607,7 +609,10 @@ export class Store<
|
|
|
607
609
|
label: q.label,
|
|
608
610
|
runs: q.runs,
|
|
609
611
|
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
610
|
-
lastestResult:
|
|
612
|
+
lastestResult:
|
|
613
|
+
q.results$.previousResult === NOT_REFRESHED_YET
|
|
614
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
615
|
+
: q.results$.previousResult,
|
|
611
616
|
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
612
617
|
})),
|
|
613
618
|
requestId,
|
|
@@ -629,7 +634,10 @@ export class Store<
|
|
|
629
634
|
break
|
|
630
635
|
}
|
|
631
636
|
case 'LSD.ResetAllDataReq': {
|
|
632
|
-
await this.adapter.coordinator
|
|
637
|
+
await this.adapter.coordinator
|
|
638
|
+
.dangerouslyReset(decoded.value.mode)
|
|
639
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
640
|
+
|
|
633
641
|
sendToDevtools(Devtools.ResetAllDataRes.make({ requestId, liveStoreVersion }))
|
|
634
642
|
|
|
635
643
|
break
|
|
@@ -645,18 +653,37 @@ export class Store<
|
|
|
645
653
|
}
|
|
646
654
|
|
|
647
655
|
__devDownloadMutationLogDb = async () => {
|
|
648
|
-
const data = await this.adapter.coordinator.getMutationLogData()
|
|
656
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
649
657
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
650
658
|
}
|
|
651
659
|
|
|
652
660
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
653
|
-
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
|
|
654
674
|
}
|
|
655
675
|
|
|
656
676
|
/** Create a new LiveStore Store */
|
|
657
677
|
export const createStore = async <
|
|
658
678
|
TGraphQLContext extends BaseGraphQLContext,
|
|
659
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,
|
|
660
687
|
>({
|
|
661
688
|
schema,
|
|
662
689
|
graphQLOptions,
|
|
@@ -666,125 +693,122 @@ export const createStore = async <
|
|
|
666
693
|
reactivityGraph = globalReactivityGraph,
|
|
667
694
|
batchUpdates,
|
|
668
695
|
disableDevtools,
|
|
669
|
-
}: {
|
|
670
|
-
schema: TSchema
|
|
671
|
-
adapter: StoreAdapterFactory
|
|
672
|
-
reactivityGraph?: ReactivityGraph
|
|
673
|
-
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
674
|
-
otelOptions?: Partial<OtelOptions>
|
|
675
|
-
boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
676
|
-
batchUpdates?: (run: () => void) => void
|
|
677
|
-
disableDevtools?: boolean
|
|
678
|
-
}): Promise<Store<TGraphQLContext, TSchema>> => {
|
|
696
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<Store<TGraphQLContext, TSchema>, UnexpectedError> => {
|
|
679
697
|
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
680
698
|
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
681
|
-
return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
|
|
682
|
-
try {
|
|
683
|
-
performance.mark('livestore:db-creating')
|
|
684
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
685
|
-
|
|
686
|
-
const adapterPromise = adapterFactory({ otelTracer, otelContext, schema })
|
|
687
|
-
const adapter = adapterPromise instanceof Promise ? await adapterPromise : adapterPromise
|
|
688
|
-
performance.mark('livestore:db-created')
|
|
689
|
-
performance.measure('livestore:db-create', 'livestore:db-creating', 'livestore:db-created')
|
|
690
|
-
|
|
691
|
-
if (batchUpdates !== undefined) {
|
|
692
|
-
reactivityGraph.effectsWrapper = batchUpdates
|
|
693
|
-
}
|
|
694
699
|
|
|
695
|
-
|
|
700
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
701
|
+
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
702
|
+
)
|
|
696
703
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
let isInTxn = false
|
|
700
|
-
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
704
|
+
return Effect.gen(function* () {
|
|
705
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
701
706
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
stmt.execute(bindValues)
|
|
707
|
+
const adapter = yield* adapterFactory({ schema }).pipe(
|
|
708
|
+
Effect.withPerformanceMeasure('livestore:makeAdapter'),
|
|
709
|
+
Effect.withSpan('createStore:makeAdapter'),
|
|
710
|
+
)
|
|
707
711
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
void adapter.coordinator.execute(queryStr, bindValues, undefined)
|
|
712
|
-
}
|
|
713
|
-
},
|
|
714
|
-
mutate: (...list) => {
|
|
715
|
-
for (const mutationEventDecoded of list) {
|
|
716
|
-
const mutationDef =
|
|
717
|
-
schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
718
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
719
|
-
|
|
720
|
-
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
721
|
-
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
722
|
-
|
|
723
|
-
for (const { statementSql, bindValues } of execArgsArr) {
|
|
724
|
-
adapter.mainDb.execute(statementSql, bindValues)
|
|
725
|
-
}
|
|
712
|
+
if (batchUpdates !== undefined) {
|
|
713
|
+
reactivityGraph.effectsWrapper = batchUpdates
|
|
714
|
+
}
|
|
726
715
|
|
|
727
|
-
|
|
716
|
+
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
728
717
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
isInTxn = false
|
|
758
|
-
txnExecuteStmnts = []
|
|
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)
|
|
759
746
|
}
|
|
760
|
-
},
|
|
761
|
-
}
|
|
762
747
|
|
|
763
|
-
|
|
764
|
-
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
765
|
-
if (isPromise(booting)) {
|
|
766
|
-
await booting
|
|
767
|
-
}
|
|
768
|
-
}
|
|
748
|
+
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
|
|
769
749
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
{
|
|
775
|
-
adapter,
|
|
776
|
-
schema,
|
|
777
|
-
graphQLOptions,
|
|
778
|
-
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
779
|
-
reactivityGraph,
|
|
780
|
-
disableDevtools,
|
|
750
|
+
adapter.coordinator
|
|
751
|
+
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
752
|
+
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
753
|
+
}
|
|
781
754
|
},
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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()
|
|
765
|
+
|
|
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
|
+
}
|
|
780
|
+
},
|
|
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
|
+
}
|
|
786
788
|
}
|
|
787
|
-
|
|
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
|
+
)
|
|
788
812
|
}
|
|
789
813
|
|
|
790
814
|
class ReferenceCountedSet<T> {
|