@livestore/livestore 0.4.0-dev.2 → 0.4.0-dev.5
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/live-queries/db-query.test.js +13 -7
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +19 -10
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +80 -0
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/store-types.d.ts +2 -2
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +3 -2
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +82 -81
- package/dist/store/store.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +2 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +18 -14
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +6 -6
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +268 -131
- package/src/live-queries/db-query.test.ts +13 -7
- package/src/mod.ts +1 -0
- package/src/reactive.test.ts +100 -0
- package/src/reactive.ts +19 -10
- package/src/store/create-store.ts +9 -1
- package/src/store/store-types.ts +5 -2
- package/src/store/store.ts +148 -140
- package/src/utils/tests/otel.ts +29 -19
package/src/reactive.ts
CHANGED
@@ -211,10 +211,8 @@ export class ReactiveGraph<
|
|
211
211
|
|
212
212
|
private refreshCallbacks: Set<() => void> = new Set()
|
213
213
|
|
214
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
|
215
214
|
private nodeIdCounter = 0
|
216
215
|
private uniqueNodeId = () => `node-${++this.nodeIdCounter}`
|
217
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: for debugging
|
218
216
|
private refreshInfoIdCounter = 0
|
219
217
|
private uniqueRefreshInfoId = () => `refresh-info-${++this.refreshInfoIdCounter}`
|
220
218
|
|
@@ -267,8 +265,11 @@ export class ReactiveGraph<
|
|
267
265
|
computeResult: (otelContext, debugRefreshReason) => {
|
268
266
|
if (thunk.isDirty) {
|
269
267
|
const neededCurrentRefresh = this.currentDebugRefresh === undefined
|
268
|
+
let localDebugRefresh: { refreshedAtoms: any[]; startMs: number } | undefined
|
270
269
|
if (neededCurrentRefresh) {
|
271
|
-
|
270
|
+
// Use local variable to prevent corruption from nested computations
|
271
|
+
localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
272
|
+
this.currentDebugRefresh = localDebugRefresh
|
272
273
|
}
|
273
274
|
|
274
275
|
// Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
|
@@ -300,15 +301,20 @@ export class ReactiveGraph<
|
|
300
301
|
debugInfo: debugInfo ?? (unknownRefreshReason() as TDebugThunkInfo),
|
301
302
|
} satisfies AtomDebugInfo<TDebugThunkInfo>
|
302
303
|
|
303
|
-
|
304
|
+
// Use currentDebugRefresh if available (could be from parent or local)
|
305
|
+
const debugRefresh = localDebugRefresh ?? this.currentDebugRefresh
|
306
|
+
if (debugRefresh) {
|
307
|
+
debugRefresh.refreshedAtoms.push(debugInfoForAtom)
|
308
|
+
}
|
304
309
|
|
305
310
|
thunk.isDirty = false
|
306
311
|
thunk.previousResult = result
|
307
312
|
thunk.recomputations++
|
308
313
|
|
309
|
-
if (neededCurrentRefresh) {
|
310
|
-
|
311
|
-
const
|
314
|
+
if (neededCurrentRefresh && localDebugRefresh) {
|
315
|
+
// Use local reference which can't be corrupted by nested calls
|
316
|
+
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
317
|
+
const durationMs = performance.now() - localDebugRefresh.startMs
|
312
318
|
this.currentDebugRefresh = undefined
|
313
319
|
|
314
320
|
this.debugRefreshInfos.push({
|
@@ -475,14 +481,17 @@ export class ReactiveGraph<
|
|
475
481
|
) => {
|
476
482
|
const effectsWrapper = this.context?.effectsWrapper ?? ((runEffects: () => void) => runEffects())
|
477
483
|
effectsWrapper(() => {
|
478
|
-
|
484
|
+
// Capture debug state in local variable to prevent corruption from nested runEffects
|
485
|
+
const localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
486
|
+
this.currentDebugRefresh = localDebugRefresh
|
479
487
|
|
480
488
|
for (const effect of effectsToRefresh) {
|
481
489
|
effect.doEffect(options?.otelContext, options.debugRefreshReason)
|
482
490
|
}
|
483
491
|
|
484
|
-
|
485
|
-
const
|
492
|
+
// Use local reference which can't be corrupted by nested calls
|
493
|
+
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
494
|
+
const durationMs = performance.now() - localDebugRefresh.startMs
|
486
495
|
this.currentDebugRefresh = undefined
|
487
496
|
|
488
497
|
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
@@ -5,6 +5,9 @@ import {
|
|
5
5
|
type ClientSessionDevtoolsChannel,
|
6
6
|
type ClientSessionSyncProcessorSimulationParams,
|
7
7
|
type IntentionalShutdownCause,
|
8
|
+
type InvalidPullError,
|
9
|
+
type IsOfflineError,
|
10
|
+
type MaterializeError,
|
8
11
|
type MigrationsReport,
|
9
12
|
provideOtel,
|
10
13
|
type SyncError,
|
@@ -217,7 +220,12 @@ export const createStore = <TSchema extends LiveStoreSchema = LiveStoreSchema.An
|
|
217
220
|
|
218
221
|
const runtime = yield* Effect.runtime<Scope.Scope>()
|
219
222
|
|
220
|
-
const shutdown = (
|
223
|
+
const shutdown = (
|
224
|
+
exit: Exit.Exit<
|
225
|
+
IntentionalShutdownCause,
|
226
|
+
UnexpectedError | MaterializeError | SyncError | InvalidPullError | IsOfflineError
|
227
|
+
>,
|
228
|
+
) =>
|
221
229
|
Effect.gen(function* () {
|
222
230
|
yield* Scope.close(lifetimeScope, exit).pipe(
|
223
231
|
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
|
package/src/store/store-types.ts
CHANGED
@@ -2,6 +2,9 @@ import type {
|
|
2
2
|
ClientSession,
|
3
3
|
ClientSessionSyncProcessorSimulationParams,
|
4
4
|
IntentionalShutdownCause,
|
5
|
+
InvalidPullError,
|
6
|
+
IsOfflineError,
|
7
|
+
MaterializeError,
|
5
8
|
StoreInterrupted,
|
6
9
|
SyncError,
|
7
10
|
UnexpectedError,
|
@@ -28,11 +31,11 @@ export type LiveStoreContext =
|
|
28
31
|
|
29
32
|
export type ShutdownDeferred = Deferred.Deferred<
|
30
33
|
IntentionalShutdownCause,
|
31
|
-
UnexpectedError | SyncError | StoreInterrupted
|
34
|
+
UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
|
32
35
|
>
|
33
36
|
export const makeShutdownDeferred: Effect.Effect<ShutdownDeferred> = Deferred.make<
|
34
37
|
IntentionalShutdownCause,
|
35
|
-
UnexpectedError | SyncError | StoreInterrupted
|
38
|
+
UnexpectedError | SyncError | StoreInterrupted | MaterializeError | InvalidPullError | IsOfflineError
|
36
39
|
>()
|
37
40
|
|
38
41
|
export type LiveStoreContextRunning = {
|
package/src/store/store.ts
CHANGED
@@ -3,13 +3,14 @@ import {
|
|
3
3
|
type ClientSession,
|
4
4
|
type ClientSessionSyncProcessor,
|
5
5
|
Devtools,
|
6
|
-
getDurationMsFromSpan,
|
7
6
|
getExecStatementsFromMaterializer,
|
8
7
|
getResultSchema,
|
9
8
|
hashMaterializerResults,
|
10
9
|
IntentionalShutdownCause,
|
11
10
|
isQueryBuilder,
|
12
11
|
liveStoreVersion,
|
12
|
+
MaterializeError,
|
13
|
+
MaterializerHashMismatchError,
|
13
14
|
makeClientSessionSyncProcessor,
|
14
15
|
type PreparedBindValues,
|
15
16
|
prepareBindValues,
|
@@ -20,7 +21,7 @@ import {
|
|
20
21
|
} from '@livestore/common'
|
21
22
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
22
23
|
import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema'
|
23
|
-
import { assertNever, isDevEnv, notYetImplemented } from '@livestore/utils'
|
24
|
+
import { assertNever, isDevEnv, notYetImplemented, shouldNeverHappen } from '@livestore/utils'
|
24
25
|
import type { Scope } from '@livestore/utils/effect'
|
25
26
|
import {
|
26
27
|
Cause,
|
@@ -128,72 +129,76 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
128
129
|
schema,
|
129
130
|
clientSession,
|
130
131
|
runtime: effectContext.runtime,
|
131
|
-
materializeEvent: (
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
)
|
155
|
-
|
156
|
-
}
|
157
|
-
|
158
|
-
const writeTablesForEvent = new Set<string>()
|
159
|
-
|
160
|
-
const exec = () => {
|
161
|
-
for (const {
|
162
|
-
statementSql,
|
163
|
-
bindValues,
|
164
|
-
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
165
|
-
} of execArgsArr) {
|
166
|
-
try {
|
167
|
-
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
|
168
|
-
} catch (cause) {
|
169
|
-
throw UnexpectedError.make({
|
170
|
-
cause,
|
171
|
-
note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
|
172
|
-
})
|
132
|
+
materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')(
|
133
|
+
(eventDecoded, { withChangeset, materializerHashLeader }) =>
|
134
|
+
// We need to use `Effect.gen` (even though we're using `Effect.fn`) so that we can pass `this` to the function
|
135
|
+
Effect.gen(this, function* () {
|
136
|
+
const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
|
137
|
+
|
138
|
+
const execArgsArr = getExecStatementsFromMaterializer({
|
139
|
+
eventDef,
|
140
|
+
materializer,
|
141
|
+
dbState: this.sqliteDbWrapper,
|
142
|
+
event: { decoded: eventDecoded, encoded: undefined },
|
143
|
+
})
|
144
|
+
|
145
|
+
const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
|
146
|
+
|
147
|
+
// Hash mismatch detection only occurs during the pull path (when receiving events from the leader).
|
148
|
+
// During push path (local commits), materializerHashLeader is always Option.none(), so this condition
|
149
|
+
// will never be met. The check happens when the same event comes back from the leader during sync,
|
150
|
+
// allowing us to compare the leader's computed hash with our local re-materialization hash.
|
151
|
+
if (
|
152
|
+
materializerHashLeader._tag === 'Some' &&
|
153
|
+
materializerHash._tag === 'Some' &&
|
154
|
+
materializerHashLeader.value !== materializerHash.value
|
155
|
+
) {
|
156
|
+
return yield* MaterializerHashMismatchError.make({ eventName: eventDecoded.name })
|
173
157
|
}
|
174
158
|
|
175
|
-
|
176
|
-
|
177
|
-
|
159
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
160
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
161
|
+
|
162
|
+
const writeTablesForEvent = new Set<string>()
|
163
|
+
|
164
|
+
const exec = () => {
|
165
|
+
for (const {
|
166
|
+
statementSql,
|
167
|
+
bindValues,
|
168
|
+
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
169
|
+
} of execArgsArr) {
|
170
|
+
try {
|
171
|
+
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
|
172
|
+
} catch (cause) {
|
173
|
+
// TOOD refactor with `SqliteError`
|
174
|
+
throw UnexpectedError.make({
|
175
|
+
cause,
|
176
|
+
note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
|
177
|
+
})
|
178
|
+
}
|
179
|
+
|
180
|
+
// durationMsTotal += durationMs
|
181
|
+
for (const table of writeTables) {
|
182
|
+
writeTablesForEvent.add(table)
|
183
|
+
}
|
184
|
+
|
185
|
+
this.sqliteDbWrapper.debug.head = eventDecoded.seqNum
|
186
|
+
}
|
178
187
|
}
|
179
188
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
if (withChangeset === true) {
|
190
|
-
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
191
|
-
} else {
|
192
|
-
exec()
|
193
|
-
}
|
189
|
+
let sessionChangeset:
|
190
|
+
| { _tag: 'sessionChangeset'; data: Uint8Array<ArrayBuffer>; debug: any }
|
191
|
+
| { _tag: 'no-op' }
|
192
|
+
| { _tag: 'unset' } = { _tag: 'unset' }
|
193
|
+
if (withChangeset === true) {
|
194
|
+
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
195
|
+
} else {
|
196
|
+
exec()
|
197
|
+
}
|
194
198
|
|
195
|
-
|
196
|
-
|
199
|
+
return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash }
|
200
|
+
}).pipe(Effect.mapError((cause) => MaterializeError.make({ cause }))),
|
201
|
+
),
|
197
202
|
rollback: (changeset) => {
|
198
203
|
this.sqliteDbWrapper.rollback(changeset)
|
199
204
|
},
|
@@ -477,7 +482,19 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
477
482
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
478
483
|
})
|
479
484
|
|
480
|
-
|
485
|
+
const decodeResult = Schema.decodeEither(schema)(rawRes)
|
486
|
+
if (decodeResult._tag === 'Right') {
|
487
|
+
return decodeResult.right
|
488
|
+
} else {
|
489
|
+
return shouldNeverHappen(
|
490
|
+
`Failed to decode query result with for schema:`,
|
491
|
+
schema.toString(),
|
492
|
+
'raw result:',
|
493
|
+
rawRes,
|
494
|
+
'decode error:',
|
495
|
+
decodeResult.left,
|
496
|
+
)
|
497
|
+
}
|
481
498
|
} else if (query._tag === 'def') {
|
482
499
|
const query$ = query.make(this.reactivityGraph.context!)
|
483
500
|
const result = this.query(query$.value, options)
|
@@ -597,84 +614,76 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
597
614
|
|
598
615
|
const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
|
599
616
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
const skipRefresh = options?.skipRefresh ?? false
|
607
|
-
|
608
|
-
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)!
|
609
|
-
commitsSpan.addEvent('commit')
|
617
|
+
Effect.gen(this, function* () {
|
618
|
+
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)
|
619
|
+
commitsSpan?.addEvent('commit')
|
620
|
+
const currentSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
621
|
+
commitsSpan?.addLink({ context: currentSpan.spanContext() })
|
610
622
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
let durationMs: number
|
616
|
-
|
617
|
-
return this.otel.tracer.startActiveSpan(
|
618
|
-
'LiveStore:commit',
|
619
|
-
{
|
620
|
-
attributes: {
|
621
|
-
'livestore.eventsCount': events.length,
|
622
|
-
'livestore.eventTags': events.map((_) => _.name),
|
623
|
-
'livestore.commitLabel': options?.label,
|
624
|
-
},
|
625
|
-
links: options?.spanLinks,
|
626
|
-
},
|
627
|
-
options?.otelContext ?? this.otel.commitsSpanContext,
|
628
|
-
(span) => {
|
629
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
623
|
+
for (const event of events) {
|
624
|
+
replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
|
625
|
+
}
|
630
626
|
|
631
|
-
|
632
|
-
// Materialize events to state
|
633
|
-
const { writeTables } = (() => {
|
634
|
-
try {
|
635
|
-
const materializeEvents = () => this.syncProcessor.push(events, { otelContext })
|
627
|
+
if (events.length === 0) return
|
636
628
|
|
637
|
-
|
638
|
-
return this.sqliteDbWrapper.txn(materializeEvents)
|
639
|
-
} else {
|
640
|
-
return materializeEvents()
|
641
|
-
}
|
642
|
-
} catch (e: any) {
|
643
|
-
console.error(e)
|
644
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
645
|
-
throw e
|
646
|
-
} finally {
|
647
|
-
span.end()
|
648
|
-
}
|
649
|
-
})()
|
629
|
+
const localRuntime = yield* Effect.runtime()
|
650
630
|
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
tablesToUpdate.push([tableRef!, null])
|
631
|
+
const materializeEventsTx = Effect.try({
|
632
|
+
try: () => {
|
633
|
+
const runMaterializeEvents = () => {
|
634
|
+
return this.syncProcessor.push(events).pipe(Runtime.runSync(localRuntime))
|
656
635
|
}
|
657
636
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
637
|
+
if (events.length > 1) {
|
638
|
+
return this.sqliteDbWrapper.txn(runMaterializeEvents)
|
639
|
+
} else {
|
640
|
+
return runMaterializeEvents()
|
662
641
|
}
|
642
|
+
},
|
643
|
+
catch: (cause) => UnexpectedError.make({ cause }),
|
644
|
+
})
|
663
645
|
|
664
|
-
|
665
|
-
|
666
|
-
} catch (e: any) {
|
667
|
-
console.error(e)
|
668
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
669
|
-
throw e
|
670
|
-
} finally {
|
671
|
-
span.end()
|
646
|
+
// Materialize events to state
|
647
|
+
const { writeTables } = yield* materializeEventsTx
|
672
648
|
|
673
|
-
|
674
|
-
|
649
|
+
const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
|
650
|
+
for (const tableName of writeTables) {
|
651
|
+
const tableRef = this.tableRefs[tableName]
|
652
|
+
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
653
|
+
tablesToUpdate.push([tableRef!, null])
|
654
|
+
}
|
675
655
|
|
676
|
-
|
677
|
-
|
656
|
+
const debugRefreshReason: RefreshReason = {
|
657
|
+
_tag: 'commit',
|
658
|
+
events,
|
659
|
+
writeTables: Array.from(writeTables),
|
660
|
+
}
|
661
|
+
const skipRefresh = options?.skipRefresh ?? false
|
662
|
+
|
663
|
+
// Update all table refs together in a batch, to only trigger one reactive update
|
664
|
+
this.reactivityGraph.setRefs(tablesToUpdate, {
|
665
|
+
debugRefreshReason,
|
666
|
+
skipRefresh,
|
667
|
+
otelContext: otel.trace.setSpan(otel.context.active(), currentSpan),
|
668
|
+
})
|
669
|
+
}).pipe(
|
670
|
+
Effect.withSpan('LiveStore:commit', {
|
671
|
+
root: true,
|
672
|
+
attributes: {
|
673
|
+
'livestore.eventsCount': events.length,
|
674
|
+
'livestore.eventTags': events.map((_) => _.name),
|
675
|
+
...(options?.label && { 'livestore.commitLabel': options.label }),
|
676
|
+
},
|
677
|
+
links: [
|
678
|
+
// Span link to LiveStore:commits
|
679
|
+
OtelTracer.makeSpanLink({ context: otel.trace.getSpanContext(this.otel.commitsSpanContext)! }),
|
680
|
+
// User-provided span links
|
681
|
+
...(options?.spanLinks?.map(OtelTracer.makeSpanLink) ?? []),
|
682
|
+
],
|
683
|
+
}),
|
684
|
+
Effect.tapErrorCause(Effect.logError),
|
685
|
+
Effect.catchAllCause((cause) => Effect.fork(this.shutdown(cause))),
|
686
|
+
Runtime.runSync(this.effectContext.runtime),
|
678
687
|
)
|
679
688
|
}
|
680
689
|
// #endregion commit
|
@@ -746,9 +755,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
746
755
|
*
|
747
756
|
* This is called automatically when the store was created using the React or Effect API.
|
748
757
|
*/
|
749
|
-
shutdown = (cause?: Cause.Cause<UnexpectedError>): Effect.Effect<void> => {
|
750
|
-
this.checkShutdown('shutdown')
|
751
|
-
|
758
|
+
shutdown = (cause?: Cause.Cause<UnexpectedError | MaterializeError>): Effect.Effect<void> => {
|
752
759
|
this.isShutdown = true
|
753
760
|
return this.clientSession.shutdown(
|
754
761
|
cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
|
@@ -798,14 +805,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
798
805
|
.pipe(this.runEffectFork)
|
799
806
|
},
|
800
807
|
|
801
|
-
syncStates: () =>
|
808
|
+
syncStates: () =>
|
802
809
|
Effect.gen(this, function* () {
|
803
810
|
const session = yield* this.syncProcessor.syncState
|
804
|
-
console.log('Session sync state:', session.toJSON())
|
805
811
|
const leader = yield* this.clientSession.leaderThread.getSyncState
|
806
|
-
|
807
|
-
}).pipe(this.
|
808
|
-
},
|
812
|
+
return { session, leader }
|
813
|
+
}).pipe(this.runEffectPromise),
|
809
814
|
|
810
815
|
version: liveStoreVersion,
|
811
816
|
|
@@ -827,6 +832,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
827
832
|
Runtime.runFork(this.effectContext.runtime),
|
828
833
|
)
|
829
834
|
|
835
|
+
private runEffectPromise = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
836
|
+
effect.pipe(Effect.tapCauseLogPretty, Runtime.runPromise(this.effectContext.runtime))
|
837
|
+
|
830
838
|
private getCommitArgs = (
|
831
839
|
firstEventOrTxnFnOrOptions: any,
|
832
840
|
restEvents: any[],
|
package/src/utils/tests/otel.ts
CHANGED
@@ -4,10 +4,13 @@ import type { InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trac
|
|
4
4
|
|
5
5
|
type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
|
6
6
|
|
7
|
-
|
7
|
+
type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
|
8
|
+
|
9
|
+
const buildSimplifiedRootSpans = (
|
8
10
|
exporter: InMemorySpanExporter,
|
11
|
+
rootSpanName: string,
|
9
12
|
mapAttributes?: (attributes: Attributes) => Attributes,
|
10
|
-
): SimplifiedNestedSpan => {
|
13
|
+
): SimplifiedNestedSpan[] => {
|
11
14
|
const spans = exporter.getFinishedSpans()
|
12
15
|
const spansMap = new Map<string, NestedSpan>(spans.map((span) => [span.spanContext().spanId, { span, children: [] }]))
|
13
16
|
|
@@ -21,14 +24,12 @@ export const getSimplifiedRootSpan = (
|
|
21
24
|
}
|
22
25
|
})
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
if (createStoreSpanData === undefined) {
|
27
|
+
const rootSpanDataList = spans.filter((_) => _.name === rootSpanName)
|
28
|
+
if (rootSpanDataList.length === 0) {
|
27
29
|
throw new Error(
|
28
|
-
`Could not find
|
30
|
+
`Could not find any root spans named '${rootSpanName}'. Available spans: ${spans.map((s) => s.name).join(', ')}`,
|
29
31
|
)
|
30
32
|
}
|
31
|
-
const rootSpan = spansMap.get(createStoreSpanData.spanContext().spanId)!
|
32
33
|
|
33
34
|
const simplifySpanRec = (span: NestedSpan): SimplifiedNestedSpan =>
|
34
35
|
omitEmpty({
|
@@ -40,20 +41,29 @@ export const getSimplifiedRootSpan = (
|
|
40
41
|
.map(simplifySpanRec),
|
41
42
|
})
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
// )
|
49
|
-
|
50
|
-
const simplifiedRootSpan = simplifySpanRec(rootSpan)
|
51
|
-
|
52
|
-
// console.log('simplifiedRootSpan', simplifiedRootSpan)
|
44
|
+
return rootSpanDataList.map((rootSpanData) => {
|
45
|
+
const rootSpan = spansMap.get(rootSpanData.spanContext().spanId)!
|
46
|
+
return simplifySpanRec(rootSpan)
|
47
|
+
})
|
48
|
+
}
|
53
49
|
|
54
|
-
|
50
|
+
export const getSimplifiedRootSpan = (
|
51
|
+
exporter: InMemorySpanExporter,
|
52
|
+
rootSpanName: string,
|
53
|
+
mapAttributes?: (attributes: Attributes) => Attributes,
|
54
|
+
): SimplifiedNestedSpan => {
|
55
|
+
const results = buildSimplifiedRootSpans(exporter, rootSpanName, mapAttributes)
|
56
|
+
const firstResult = results[0]
|
57
|
+
if (!firstResult) throw new Error(`Could not find the root span named '${rootSpanName}'.`)
|
58
|
+
return firstResult
|
59
|
+
}
|
55
60
|
|
56
|
-
|
61
|
+
export const getAllSimplifiedRootSpans = (
|
62
|
+
exporter: InMemorySpanExporter,
|
63
|
+
rootSpanName: string,
|
64
|
+
mapAttributes?: (attributes: Attributes) => Attributes,
|
65
|
+
): SimplifiedNestedSpan[] => {
|
66
|
+
return buildSimplifiedRootSpans(exporter, rootSpanName, mapAttributes)
|
57
67
|
}
|
58
68
|
|
59
69
|
// const compareHrTime = (a: [number, numndber], b: [number, number]) => {
|