@livestore/livestore 0.0.54-dev.22 → 0.0.54-dev.25
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 +0 -8
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +1 -1
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +1 -0
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts +0 -2
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +0 -1
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- 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 +3 -3
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +16 -8
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/LiveStoreProvider.test.js +6 -4
- package/dist/react/LiveStoreProvider.test.js.map +1 -1
- 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 +7 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +231 -96
- package/dist/store.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/react/fixture.tsx +1 -1
- package/src/effect/LiveStore.ts +2 -1
- package/src/global-state.ts +0 -4
- package/src/index.ts +2 -1
- package/src/react/LiveStoreContext.ts +4 -0
- package/src/react/LiveStoreProvider.test.tsx +9 -4
- package/src/react/LiveStoreProvider.tsx +17 -10
- package/src/row-query.ts +5 -51
- package/src/store.ts +315 -128
package/src/store.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BootDb,
|
|
3
|
+
BootStatus,
|
|
3
4
|
ParamsObject,
|
|
4
5
|
PreparedBindValues,
|
|
5
6
|
ResetMode,
|
|
@@ -7,12 +8,12 @@ import type {
|
|
|
7
8
|
StoreAdapterFactory,
|
|
8
9
|
UnexpectedError,
|
|
9
10
|
} from '@livestore/common'
|
|
10
|
-
import { Devtools, getExecArgsFromMutation, prepareBindValues } from '@livestore/common'
|
|
11
|
-
import { version as liveStoreVersion } from '@livestore/common/package.json'
|
|
11
|
+
import { Devtools, getExecArgsFromMutation, liveStoreVersion, prepareBindValues } from '@livestore/common'
|
|
12
12
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
13
13
|
import { makeMutationEventSchemaMemo } from '@livestore/common/schema'
|
|
14
|
-
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
15
|
-
import {
|
|
14
|
+
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen, throttle } from '@livestore/utils'
|
|
15
|
+
import { cuid } from '@livestore/utils/cuid'
|
|
16
|
+
import { Effect, Layer, Logger, LogLevel, OtelTracer, Queue, Schema, Stream } from '@livestore/utils/effect'
|
|
16
17
|
import * as otel from '@opentelemetry/api'
|
|
17
18
|
import type { GraphQLSchema } from 'graphql'
|
|
18
19
|
|
|
@@ -52,6 +53,8 @@ export type StoreOptions<
|
|
|
52
53
|
otelOptions: OtelOptions
|
|
53
54
|
reactivityGraph: ReactivityGraph
|
|
54
55
|
disableDevtools?: boolean
|
|
56
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
57
|
+
__processedMutationIds: Set<string>
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
export type RefreshReason =
|
|
@@ -106,6 +109,7 @@ export class Store<
|
|
|
106
109
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
107
110
|
> {
|
|
108
111
|
id = uniqueStoreId()
|
|
112
|
+
readonly devtoolsConnectionId = cuid()
|
|
109
113
|
reactivityGraph: ReactivityGraph
|
|
110
114
|
mainDbWrapper: MainDatabaseWrapper
|
|
111
115
|
adapter: StoreAdapter
|
|
@@ -120,8 +124,8 @@ export class Store<
|
|
|
120
124
|
tableRefs: { [key: string]: Ref<null, QueryContext, RefreshReason> }
|
|
121
125
|
|
|
122
126
|
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
123
|
-
__processedMutationIds
|
|
124
|
-
__processedMutationWithoutRefreshIds = new Set<string>()
|
|
127
|
+
private __processedMutationIds
|
|
128
|
+
private __processedMutationWithoutRefreshIds = new Set<string>()
|
|
125
129
|
|
|
126
130
|
/** RC-based set to see which queries are currently subscribed to */
|
|
127
131
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
@@ -135,6 +139,7 @@ export class Store<
|
|
|
135
139
|
reactivityGraph,
|
|
136
140
|
otelOptions,
|
|
137
141
|
disableDevtools,
|
|
142
|
+
__processedMutationIds,
|
|
138
143
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
139
144
|
this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb })
|
|
140
145
|
this.adapter = adapter
|
|
@@ -143,6 +148,9 @@ export class Store<
|
|
|
143
148
|
// TODO refactor
|
|
144
149
|
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
145
150
|
|
|
151
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
152
|
+
this.__processedMutationIds = __processedMutationIds
|
|
153
|
+
|
|
146
154
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
147
155
|
this.tableRefs = {}
|
|
148
156
|
this.activeQueries = new ReferenceCountedSet()
|
|
@@ -165,8 +173,7 @@ export class Store<
|
|
|
165
173
|
this.mutate({ wasSyncMessage: true }, mutationEventDecoded)
|
|
166
174
|
}),
|
|
167
175
|
Stream.runDrain,
|
|
168
|
-
|
|
169
|
-
Effect.runFork,
|
|
176
|
+
runEffectFork,
|
|
170
177
|
)
|
|
171
178
|
|
|
172
179
|
if (disableDevtools !== true) {
|
|
@@ -180,11 +187,7 @@ export class Store<
|
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
// Need a set here since `schema.tables` might contain duplicates and some componentStateTables
|
|
183
|
-
const allTableNames = new Set(
|
|
184
|
-
this.schema.tables.keys(),
|
|
185
|
-
// TODO activate dynamic tables
|
|
186
|
-
// ...Array.from(dynamicallyRegisteredTables.values()).map((_) => _.sqliteDef.name),
|
|
187
|
-
)
|
|
190
|
+
const allTableNames = new Set(this.schema.tables.keys())
|
|
188
191
|
const existingTableRefs = new Map(
|
|
189
192
|
Array.from(this.reactivityGraph.atoms.values())
|
|
190
193
|
.filter((_): _ is Ref<any, any, any> => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
@@ -272,7 +275,7 @@ export class Store<
|
|
|
272
275
|
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
273
276
|
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
274
277
|
|
|
275
|
-
await this.adapter.coordinator.shutdown.pipe(
|
|
278
|
+
await this.adapter.coordinator.shutdown.pipe(runEffectPromise)
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
mutate: {
|
|
@@ -491,7 +494,7 @@ export class Store<
|
|
|
491
494
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
492
495
|
this.adapter.coordinator
|
|
493
496
|
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
494
|
-
.pipe(
|
|
497
|
+
.pipe(runEffectFork)
|
|
495
498
|
}
|
|
496
499
|
|
|
497
500
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -517,9 +520,7 @@ export class Store<
|
|
|
517
520
|
) => {
|
|
518
521
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
519
522
|
|
|
520
|
-
this.adapter.coordinator
|
|
521
|
-
.execute(query, prepareBindValues(params, query))
|
|
522
|
-
.pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
523
|
+
this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(runEffectFork)
|
|
523
524
|
}
|
|
524
525
|
|
|
525
526
|
select = (query: string, params: ParamsObject = {}) => {
|
|
@@ -533,118 +534,268 @@ export class Store<
|
|
|
533
534
|
meta: { liveStoreRefType: 'table' },
|
|
534
535
|
})
|
|
535
536
|
|
|
537
|
+
// TODO shutdown behaviour
|
|
536
538
|
private bootDevtools = () => {
|
|
537
|
-
const
|
|
539
|
+
const sendToDevtoolsContentscript = (
|
|
540
|
+
message: typeof Devtools.DevtoolsWindowMessage.MessageForContentscript.Type,
|
|
541
|
+
) => {
|
|
542
|
+
window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*')
|
|
543
|
+
}
|
|
538
544
|
|
|
539
|
-
|
|
540
|
-
type RequestId = string
|
|
545
|
+
const channelId = this.adapter.coordinator.devtools.channelId
|
|
541
546
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
decoded.value.channelId !== this.adapter.coordinator.devtools.channelId
|
|
551
|
-
) {
|
|
552
|
-
// console.log(`Unknown message`, event)
|
|
547
|
+
window.addEventListener('message', (event) => {
|
|
548
|
+
const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data)
|
|
549
|
+
if (decodedMessageRes._tag === 'None') return
|
|
550
|
+
|
|
551
|
+
const message = decodedMessageRes.value
|
|
552
|
+
|
|
553
|
+
if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
|
|
554
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
553
555
|
return
|
|
554
556
|
}
|
|
555
557
|
|
|
556
|
-
|
|
557
|
-
const sendToDevtools = (message: Devtools.MessageFromAppHost) =>
|
|
558
|
-
devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
|
|
559
|
-
|
|
560
|
-
switch (decoded.value._tag) {
|
|
561
|
-
case 'LSD.ReactivityGraphSubscribe': {
|
|
562
|
-
const includeResults = decoded.value.includeResults
|
|
563
|
-
const send = () =>
|
|
564
|
-
sendToDevtools(
|
|
565
|
-
Devtools.ReactivityGraphRes.make({
|
|
566
|
-
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
567
|
-
requestId,
|
|
568
|
-
liveStoreVersion,
|
|
569
|
-
}),
|
|
570
|
-
)
|
|
571
|
-
|
|
572
|
-
send()
|
|
573
|
-
|
|
574
|
-
reactivityGraphSubcriptions.set(
|
|
575
|
-
requestId,
|
|
576
|
-
this.reactivityGraph.subscribeToRefresh(() => send()),
|
|
577
|
-
)
|
|
558
|
+
if (message.channelId !== channelId) return
|
|
578
559
|
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
case 'LSD.DebugInfoResetReq': {
|
|
588
|
-
this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
589
|
-
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
590
|
-
break
|
|
591
|
-
}
|
|
592
|
-
case 'LSD.DebugInfoRerunQueryReq': {
|
|
593
|
-
const { queryStr, bindValues, queriedTables } = decoded.value
|
|
594
|
-
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
595
|
-
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
596
|
-
break
|
|
597
|
-
}
|
|
598
|
-
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
599
|
-
reactivityGraphSubcriptions.get(requestId)!()
|
|
600
|
-
break
|
|
601
|
-
}
|
|
602
|
-
case 'LSD.LiveQueriesSubscribe': {
|
|
603
|
-
const send = () =>
|
|
604
|
-
sendToDevtools(
|
|
605
|
-
Devtools.LiveQueriesRes.make({
|
|
606
|
-
liveQueries: [...this.activeQueries].map((q) => ({
|
|
607
|
-
_tag: q._tag,
|
|
608
|
-
id: q.id,
|
|
609
|
-
label: q.label,
|
|
610
|
-
runs: q.runs,
|
|
611
|
-
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
612
|
-
lastestResult:
|
|
613
|
-
q.results$.previousResult === NOT_REFRESHED_YET
|
|
614
|
-
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
615
|
-
: q.results$.previousResult,
|
|
616
|
-
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
617
|
-
})),
|
|
618
|
-
requestId,
|
|
619
|
-
liveStoreVersion,
|
|
620
|
-
}),
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
send()
|
|
624
|
-
|
|
625
|
-
liveQueriesSubscriptions.set(
|
|
626
|
-
requestId,
|
|
627
|
-
this.reactivityGraph.subscribeToRefresh(() => send()),
|
|
628
|
-
)
|
|
560
|
+
if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
|
|
561
|
+
this.adapter.coordinator.devtools.connect({ port: message.port, connectionId: this.devtoolsConnectionId }).pipe(
|
|
562
|
+
Effect.tapSync(({ storeMessagePort }) => {
|
|
563
|
+
// console.log('storeMessagePort', storeMessagePort)
|
|
564
|
+
storeMessagePort.addEventListener('message', (event) => {
|
|
565
|
+
const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data)
|
|
566
|
+
// console.log('storeMessagePort message', decodedMessage)
|
|
629
567
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
568
|
+
if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
569
|
+
// console.log(`Unknown message`, event)
|
|
570
|
+
return
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const requestId = decodedMessage.requestId
|
|
574
|
+
const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
|
|
575
|
+
storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
|
|
576
|
+
|
|
577
|
+
const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
|
|
578
|
+
|
|
579
|
+
switch (decodedMessage._tag) {
|
|
580
|
+
case 'LSD.ReactivityGraphSubscribe': {
|
|
581
|
+
const includeResults = decodedMessage.includeResults
|
|
582
|
+
|
|
583
|
+
const send = () =>
|
|
584
|
+
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
585
|
+
// to send the reactivity graph updates to the devtools
|
|
586
|
+
requestIdleCallback(
|
|
587
|
+
() =>
|
|
588
|
+
sendToDevtools(
|
|
589
|
+
Devtools.ReactivityGraphRes.make({
|
|
590
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
591
|
+
requestId,
|
|
592
|
+
liveStoreVersion,
|
|
593
|
+
}),
|
|
594
|
+
),
|
|
595
|
+
{ timeout: 500 },
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
send()
|
|
599
|
+
|
|
600
|
+
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
601
|
+
// so we throttle the updates to avoid sending too much data
|
|
602
|
+
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
603
|
+
const throttledSend = throttle(send, 20)
|
|
604
|
+
|
|
605
|
+
reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
606
|
+
|
|
607
|
+
break
|
|
608
|
+
}
|
|
609
|
+
case 'LSD.DebugInfoReq': {
|
|
610
|
+
sendToDevtools(
|
|
611
|
+
Devtools.DebugInfoRes.make({
|
|
612
|
+
debugInfo: this.mainDbWrapper.debugInfo,
|
|
613
|
+
requestId,
|
|
614
|
+
liveStoreVersion,
|
|
615
|
+
}),
|
|
616
|
+
)
|
|
617
|
+
break
|
|
618
|
+
}
|
|
619
|
+
case 'LSD.DebugInfoResetReq': {
|
|
620
|
+
this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
621
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
622
|
+
break
|
|
623
|
+
}
|
|
624
|
+
case 'LSD.DebugInfoRerunQueryReq': {
|
|
625
|
+
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
626
|
+
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
627
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
628
|
+
break
|
|
629
|
+
}
|
|
630
|
+
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
631
|
+
reactivityGraphSubcriptions.get(requestId)!()
|
|
632
|
+
break
|
|
633
|
+
}
|
|
634
|
+
case 'LSD.LiveQueriesSubscribe': {
|
|
635
|
+
const send = () =>
|
|
636
|
+
requestIdleCallback(
|
|
637
|
+
() =>
|
|
638
|
+
sendToDevtools(
|
|
639
|
+
Devtools.LiveQueriesRes.make({
|
|
640
|
+
liveQueries: [...this.activeQueries].map((q) => ({
|
|
641
|
+
_tag: q._tag,
|
|
642
|
+
id: q.id,
|
|
643
|
+
label: q.label,
|
|
644
|
+
runs: q.runs,
|
|
645
|
+
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
646
|
+
lastestResult:
|
|
647
|
+
q.results$.previousResult === NOT_REFRESHED_YET
|
|
648
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
649
|
+
: q.results$.previousResult,
|
|
650
|
+
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
651
|
+
})),
|
|
652
|
+
requestId,
|
|
653
|
+
liveStoreVersion,
|
|
654
|
+
}),
|
|
655
|
+
),
|
|
656
|
+
{ timeout: 500 },
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
send()
|
|
660
|
+
|
|
661
|
+
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
662
|
+
const throttledSend = throttle(send, 20)
|
|
663
|
+
|
|
664
|
+
liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
665
|
+
|
|
666
|
+
break
|
|
667
|
+
}
|
|
668
|
+
case 'LSD.LiveQueriesUnsubscribe': {
|
|
669
|
+
liveQueriesSubscriptions.get(requestId)!()
|
|
670
|
+
break
|
|
671
|
+
}
|
|
672
|
+
// No default
|
|
673
|
+
}
|
|
674
|
+
})
|
|
640
675
|
|
|
641
|
-
|
|
676
|
+
storeMessagePort.start()
|
|
677
|
+
}),
|
|
678
|
+
runEffectFork,
|
|
679
|
+
)
|
|
642
680
|
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
// No default
|
|
681
|
+
return
|
|
646
682
|
}
|
|
647
683
|
})
|
|
684
|
+
|
|
685
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }))
|
|
686
|
+
|
|
687
|
+
// const devtoolsChannel = Devtools.makeBroadcastChannels()
|
|
688
|
+
|
|
689
|
+
type Unsub = () => void
|
|
690
|
+
type RequestId = string
|
|
691
|
+
|
|
692
|
+
const reactivityGraphSubcriptions = new Map<RequestId, Unsub>()
|
|
693
|
+
const liveQueriesSubscriptions = new Map<RequestId, Unsub>()
|
|
694
|
+
// devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
|
|
695
|
+
// const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHostStore)(event.data)
|
|
696
|
+
// if (decoded._tag === 'None' || decoded.value.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
697
|
+
// // console.log(`Unknown message`, event)
|
|
698
|
+
// return
|
|
699
|
+
// }
|
|
700
|
+
|
|
701
|
+
// const requestId = decoded.value.requestId
|
|
702
|
+
// const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
|
|
703
|
+
// devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
|
|
704
|
+
|
|
705
|
+
// const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
|
|
706
|
+
|
|
707
|
+
// switch (decoded.value._tag) {
|
|
708
|
+
// case 'LSD.ReactivityGraphSubscribe': {
|
|
709
|
+
// const includeResults = decoded.value.includeResults
|
|
710
|
+
|
|
711
|
+
// const send = () =>
|
|
712
|
+
// // In order to not add more work to the current tick, we use requestIdleCallback
|
|
713
|
+
// // to send the reactivity graph updates to the devtools
|
|
714
|
+
// requestIdleCallback(
|
|
715
|
+
// () =>
|
|
716
|
+
// sendToDevtools(
|
|
717
|
+
// Devtools.ReactivityGraphRes.make({
|
|
718
|
+
// reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
719
|
+
// requestId,
|
|
720
|
+
// liveStoreVersion,
|
|
721
|
+
// }),
|
|
722
|
+
// ),
|
|
723
|
+
// { timeout: 500 },
|
|
724
|
+
// )
|
|
725
|
+
|
|
726
|
+
// send()
|
|
727
|
+
|
|
728
|
+
// // In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
729
|
+
// // so we throttle the updates to avoid sending too much data
|
|
730
|
+
// // This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
731
|
+
// const throttledSend = throttle(send, 20)
|
|
732
|
+
|
|
733
|
+
// reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
734
|
+
|
|
735
|
+
// break
|
|
736
|
+
// }
|
|
737
|
+
// case 'LSD.DebugInfoReq': {
|
|
738
|
+
// sendToDevtools(
|
|
739
|
+
// Devtools.DebugInfoRes.make({ debugInfo: this.mainDbWrapper.debugInfo, requestId, liveStoreVersion }),
|
|
740
|
+
// )
|
|
741
|
+
// break
|
|
742
|
+
// }
|
|
743
|
+
// case 'LSD.DebugInfoResetReq': {
|
|
744
|
+
// this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
745
|
+
// sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
746
|
+
// break
|
|
747
|
+
// }
|
|
748
|
+
// case 'LSD.DebugInfoRerunQueryReq': {
|
|
749
|
+
// const { queryStr, bindValues, queriedTables } = decoded.value
|
|
750
|
+
// this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
751
|
+
// sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
752
|
+
// break
|
|
753
|
+
// }
|
|
754
|
+
// case 'LSD.ReactivityGraphUnsubscribe': {
|
|
755
|
+
// reactivityGraphSubcriptions.get(requestId)!()
|
|
756
|
+
// break
|
|
757
|
+
// }
|
|
758
|
+
// case 'LSD.LiveQueriesSubscribe': {
|
|
759
|
+
// const send = () =>
|
|
760
|
+
// requestIdleCallback(
|
|
761
|
+
// () =>
|
|
762
|
+
// sendToDevtools(
|
|
763
|
+
// Devtools.LiveQueriesRes.make({
|
|
764
|
+
// liveQueries: [...this.activeQueries].map((q) => ({
|
|
765
|
+
// _tag: q._tag,
|
|
766
|
+
// id: q.id,
|
|
767
|
+
// label: q.label,
|
|
768
|
+
// runs: q.runs,
|
|
769
|
+
// executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
770
|
+
// lastestResult:
|
|
771
|
+
// q.results$.previousResult === NOT_REFRESHED_YET
|
|
772
|
+
// ? 'SYMBOL_NOT_REFRESHED_YET'
|
|
773
|
+
// : q.results$.previousResult,
|
|
774
|
+
// activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
775
|
+
// })),
|
|
776
|
+
// requestId,
|
|
777
|
+
// liveStoreVersion,
|
|
778
|
+
// }),
|
|
779
|
+
// ),
|
|
780
|
+
// { timeout: 500 },
|
|
781
|
+
// )
|
|
782
|
+
|
|
783
|
+
// send()
|
|
784
|
+
|
|
785
|
+
// // Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
786
|
+
// const throttledSend = throttle(send, 20)
|
|
787
|
+
|
|
788
|
+
// liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
789
|
+
|
|
790
|
+
// break
|
|
791
|
+
// }
|
|
792
|
+
// case 'LSD.LiveQueriesUnsubscribe': {
|
|
793
|
+
// liveQueriesSubscriptions.get(requestId)!()
|
|
794
|
+
// break
|
|
795
|
+
// }
|
|
796
|
+
// // No default
|
|
797
|
+
// }
|
|
798
|
+
// })
|
|
648
799
|
}
|
|
649
800
|
|
|
650
801
|
__devDownloadDb = () => {
|
|
@@ -653,13 +804,12 @@ export class Store<
|
|
|
653
804
|
}
|
|
654
805
|
|
|
655
806
|
__devDownloadMutationLogDb = async () => {
|
|
656
|
-
const data = await this.adapter.coordinator.getMutationLogData.pipe(
|
|
807
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(runEffectPromise)
|
|
657
808
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
658
809
|
}
|
|
659
810
|
|
|
660
811
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
661
|
-
dangerouslyResetStorage = (mode: ResetMode) =>
|
|
662
|
-
this.adapter.coordinator.dangerouslyReset(mode).pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
812
|
+
dangerouslyResetStorage = (mode: ResetMode) => this.adapter.coordinator.dangerouslyReset(mode).pipe(runEffectPromise)
|
|
663
813
|
}
|
|
664
814
|
|
|
665
815
|
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
@@ -671,6 +821,7 @@ export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSche
|
|
|
671
821
|
boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
|
|
672
822
|
batchUpdates?: (run: () => void) => void
|
|
673
823
|
disableDevtools?: boolean
|
|
824
|
+
onBootStatus?: (status: BootStatus) => void
|
|
674
825
|
}
|
|
675
826
|
|
|
676
827
|
/** Create a new LiveStore Store */
|
|
@@ -679,7 +830,7 @@ export const createStore = async <
|
|
|
679
830
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
680
831
|
>(
|
|
681
832
|
options: CreateStoreOptions<TGraphQLContext, TSchema>,
|
|
682
|
-
): Promise<Store<TGraphQLContext, TSchema>> => createStoreEff(options).pipe(
|
|
833
|
+
): Promise<Store<TGraphQLContext, TSchema>> => createStoreEff(options).pipe(runEffectPromise)
|
|
683
834
|
|
|
684
835
|
export const createStoreEff = <
|
|
685
836
|
TGraphQLContext extends BaseGraphQLContext,
|
|
@@ -693,6 +844,7 @@ export const createStoreEff = <
|
|
|
693
844
|
reactivityGraph = globalReactivityGraph,
|
|
694
845
|
batchUpdates,
|
|
695
846
|
disableDevtools,
|
|
847
|
+
onBootStatus,
|
|
696
848
|
}: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<Store<TGraphQLContext, TSchema>, UnexpectedError> => {
|
|
697
849
|
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
698
850
|
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
@@ -704,17 +856,29 @@ export const createStoreEff = <
|
|
|
704
856
|
return Effect.gen(function* () {
|
|
705
857
|
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
706
858
|
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
859
|
+
const bootStatusQueue = yield* Queue.unbounded<BootStatus>()
|
|
860
|
+
|
|
861
|
+
yield* Queue.take(bootStatusQueue).pipe(
|
|
862
|
+
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
863
|
+
Effect.forever,
|
|
864
|
+
Effect.tapCauseLogPretty,
|
|
865
|
+
Effect.forkScoped,
|
|
710
866
|
)
|
|
711
867
|
|
|
868
|
+
const adapter: StoreAdapter = yield* adapterFactory({
|
|
869
|
+
schema,
|
|
870
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
871
|
+
bootStatusQueue,
|
|
872
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
873
|
+
|
|
712
874
|
if (batchUpdates !== undefined) {
|
|
713
875
|
reactivityGraph.effectsWrapper = batchUpdates
|
|
714
876
|
}
|
|
715
877
|
|
|
716
878
|
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
717
879
|
|
|
880
|
+
const __processedMutationIds = new Set<string>()
|
|
881
|
+
|
|
718
882
|
// TODO consider moving booting into the storage backend
|
|
719
883
|
if (boot !== undefined) {
|
|
720
884
|
let isInTxn = false
|
|
@@ -729,7 +893,7 @@ export const createStoreEff = <
|
|
|
729
893
|
if (isInTxn === true) {
|
|
730
894
|
txnExecuteStmnts.push([queryStr, bindValues])
|
|
731
895
|
} else {
|
|
732
|
-
adapter.coordinator.execute(queryStr, bindValues).pipe(
|
|
896
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
733
897
|
}
|
|
734
898
|
},
|
|
735
899
|
mutate: (...list) => {
|
|
@@ -738,6 +902,8 @@ export const createStoreEff = <
|
|
|
738
902
|
schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
739
903
|
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
740
904
|
|
|
905
|
+
__processedMutationIds.add(mutationEventDecoded.id)
|
|
906
|
+
|
|
741
907
|
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
742
908
|
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
743
909
|
|
|
@@ -749,7 +915,7 @@ export const createStoreEff = <
|
|
|
749
915
|
|
|
750
916
|
adapter.coordinator
|
|
751
917
|
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
752
|
-
.pipe(
|
|
918
|
+
.pipe(runEffectFork)
|
|
753
919
|
}
|
|
754
920
|
},
|
|
755
921
|
select: (queryStr, bindValues) => {
|
|
@@ -767,7 +933,7 @@ export const createStoreEff = <
|
|
|
767
933
|
|
|
768
934
|
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
769
935
|
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
770
|
-
adapter.coordinator.execute(queryStr, bindValues).pipe(
|
|
936
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
771
937
|
}
|
|
772
938
|
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
773
939
|
} catch (e: any) {
|
|
@@ -798,10 +964,12 @@ export const createStoreEff = <
|
|
|
798
964
|
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
799
965
|
reactivityGraph,
|
|
800
966
|
disableDevtools,
|
|
967
|
+
__processedMutationIds,
|
|
801
968
|
},
|
|
802
969
|
span,
|
|
803
970
|
)
|
|
804
971
|
}).pipe(
|
|
972
|
+
Effect.scoped,
|
|
805
973
|
Effect.withSpan('createStore', {
|
|
806
974
|
parent: otelOptions?.rootSpanContext
|
|
807
975
|
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
|
|
@@ -811,6 +979,7 @@ export const createStoreEff = <
|
|
|
811
979
|
)
|
|
812
980
|
}
|
|
813
981
|
|
|
982
|
+
// TODO consider replacing with Effect's RC data structures
|
|
814
983
|
class ReferenceCountedSet<T> {
|
|
815
984
|
private map: Map<T, number>
|
|
816
985
|
|
|
@@ -846,3 +1015,21 @@ class ReferenceCountedSet<T> {
|
|
|
846
1015
|
}
|
|
847
1016
|
}
|
|
848
1017
|
}
|
|
1018
|
+
|
|
1019
|
+
const runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1020
|
+
effect.pipe(
|
|
1021
|
+
Effect.tapCauseLogPretty,
|
|
1022
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
1023
|
+
Effect.provide(Logger.pretty),
|
|
1024
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
1025
|
+
Effect.runFork,
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
const runEffectPromise = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
1029
|
+
effect.pipe(
|
|
1030
|
+
Effect.tapCauseLogPretty,
|
|
1031
|
+
Effect.annotateLogs({ thread: 'window' }),
|
|
1032
|
+
Effect.provide(Logger.pretty),
|
|
1033
|
+
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
1034
|
+
Effect.runPromise,
|
|
1035
|
+
)
|