@livestore/livestore 0.4.0-dev.3 → 0.4.0-dev.6
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/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +2 -4
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +7 -4
- package/dist/live-queries/db-query.js.map +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 +1 -1
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +10 -10
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +29 -24
- 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 +3 -3
- 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 +94 -88
- package/dist/store/store.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +2 -1
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +15 -14
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +20 -15
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +6 -6
- package/src/effect/LiveStore.ts +2 -4
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +268 -131
- package/src/live-queries/db-query.test.ts +13 -7
- package/src/live-queries/db-query.ts +7 -4
- package/src/mod.ts +2 -0
- package/src/reactive.test.ts +100 -0
- package/src/reactive.ts +40 -35
- package/src/store/create-store.ts +12 -4
- package/src/store/store-types.ts +5 -2
- package/src/store/store.ts +160 -147
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +31 -20
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, omitUndefineds, 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
|
},
|
@@ -208,8 +213,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
208
213
|
},
|
209
214
|
span: syncSpan,
|
210
215
|
params: {
|
211
|
-
|
212
|
-
|
216
|
+
...omitUndefineds({
|
217
|
+
leaderPushBatchSize: params.leaderPushBatchSize,
|
218
|
+
}),
|
219
|
+
...(params.simulation?.clientSessionSyncProcessor !== undefined
|
220
|
+
? { simulation: params.simulation.clientSessionSyncProcessor }
|
221
|
+
: {}),
|
213
222
|
},
|
214
223
|
confirmUnsavedChanges,
|
215
224
|
})
|
@@ -411,8 +420,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
411
420
|
Effect.sync(() =>
|
412
421
|
this.subscribe(query$, {
|
413
422
|
onUpdate: (result) => emit.single(result),
|
414
|
-
otelContext,
|
415
|
-
label: options?.label,
|
423
|
+
...omitUndefineds({ otelContext, label: options?.label }),
|
416
424
|
}),
|
417
425
|
),
|
418
426
|
(unsub) => Effect.sync(() => unsub()),
|
@@ -447,7 +455,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
447
455
|
|
448
456
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
449
457
|
const res = this.sqliteDbWrapper.cachedSelect(query.query, prepareBindValues(query.bindValues, query.query), {
|
450
|
-
otelContext: options?.otelContext,
|
458
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
451
459
|
}) as any
|
452
460
|
if (query.schema) {
|
453
461
|
return Schema.decodeSync(query.schema)(res)
|
@@ -473,11 +481,23 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
473
481
|
}
|
474
482
|
|
475
483
|
const rawRes = this.sqliteDbWrapper.cachedSelect(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
|
476
|
-
otelContext: options?.otelContext,
|
484
|
+
...omitUndefineds({ otelContext: options?.otelContext }),
|
477
485
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
478
486
|
})
|
479
487
|
|
480
|
-
|
488
|
+
const decodeResult = Schema.decodeEither(schema)(rawRes)
|
489
|
+
if (decodeResult._tag === 'Right') {
|
490
|
+
return decodeResult.right
|
491
|
+
} else {
|
492
|
+
return shouldNeverHappen(
|
493
|
+
`Failed to decode query result with for schema:`,
|
494
|
+
schema.toString(),
|
495
|
+
'raw result:',
|
496
|
+
rawRes,
|
497
|
+
'decode error:',
|
498
|
+
decodeResult.left,
|
499
|
+
)
|
500
|
+
}
|
481
501
|
} else if (query._tag === 'def') {
|
482
502
|
const query$ = query.make(this.reactivityGraph.context!)
|
483
503
|
const result = this.query(query$.value, options)
|
@@ -487,7 +507,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
487
507
|
const signal$ = query.make(this.reactivityGraph.context!)
|
488
508
|
return signal$.value.get()
|
489
509
|
} else {
|
490
|
-
return query.run({
|
510
|
+
return query.run({
|
511
|
+
...omitUndefineds({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason }),
|
512
|
+
})
|
491
513
|
}
|
492
514
|
}
|
493
515
|
|
@@ -597,84 +619,76 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
597
619
|
|
598
620
|
const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
|
599
621
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
const skipRefresh = options?.skipRefresh ?? false
|
622
|
+
Effect.gen(this, function* () {
|
623
|
+
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)
|
624
|
+
commitsSpan?.addEvent('commit')
|
625
|
+
const currentSpan = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
626
|
+
commitsSpan?.addLink({ context: currentSpan.spanContext() })
|
607
627
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
// console.group('LiveStore.commit', { skipRefresh })
|
612
|
-
// events.forEach((_) => console.debug(_.name, _.args))
|
613
|
-
// console.groupEnd()
|
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)
|
628
|
+
for (const event of events) {
|
629
|
+
replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
|
630
|
+
}
|
630
631
|
|
631
|
-
|
632
|
-
// Materialize events to state
|
633
|
-
const { writeTables } = (() => {
|
634
|
-
try {
|
635
|
-
const materializeEvents = () => this.syncProcessor.push(events, { otelContext })
|
632
|
+
if (events.length === 0) return
|
636
633
|
|
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
|
-
})()
|
634
|
+
const localRuntime = yield* Effect.runtime()
|
650
635
|
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
tablesToUpdate.push([tableRef!, null])
|
636
|
+
const materializeEventsTx = Effect.try({
|
637
|
+
try: () => {
|
638
|
+
const runMaterializeEvents = () => {
|
639
|
+
return this.syncProcessor.push(events).pipe(Runtime.runSync(localRuntime))
|
656
640
|
}
|
657
641
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
642
|
+
if (events.length > 1) {
|
643
|
+
return this.sqliteDbWrapper.txn(runMaterializeEvents)
|
644
|
+
} else {
|
645
|
+
return runMaterializeEvents()
|
662
646
|
}
|
647
|
+
},
|
648
|
+
catch: (cause) => UnexpectedError.make({ cause }),
|
649
|
+
})
|
663
650
|
|
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()
|
651
|
+
// Materialize events to state
|
652
|
+
const { writeTables } = yield* materializeEventsTx
|
672
653
|
|
673
|
-
|
674
|
-
|
654
|
+
const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
|
655
|
+
for (const tableName of writeTables) {
|
656
|
+
const tableRef = this.tableRefs[tableName]
|
657
|
+
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
658
|
+
tablesToUpdate.push([tableRef!, null])
|
659
|
+
}
|
675
660
|
|
676
|
-
|
677
|
-
|
661
|
+
const debugRefreshReason: RefreshReason = {
|
662
|
+
_tag: 'commit',
|
663
|
+
events,
|
664
|
+
writeTables: Array.from(writeTables),
|
665
|
+
}
|
666
|
+
const skipRefresh = options?.skipRefresh ?? false
|
667
|
+
|
668
|
+
// Update all table refs together in a batch, to only trigger one reactive update
|
669
|
+
this.reactivityGraph.setRefs(tablesToUpdate, {
|
670
|
+
debugRefreshReason,
|
671
|
+
skipRefresh,
|
672
|
+
otelContext: otel.trace.setSpan(otel.context.active(), currentSpan),
|
673
|
+
})
|
674
|
+
}).pipe(
|
675
|
+
Effect.withSpan('LiveStore:commit', {
|
676
|
+
root: true,
|
677
|
+
attributes: {
|
678
|
+
'livestore.eventsCount': events.length,
|
679
|
+
'livestore.eventTags': events.map((_) => _.name),
|
680
|
+
...(options?.label && { 'livestore.commitLabel': options.label }),
|
681
|
+
},
|
682
|
+
links: [
|
683
|
+
// Span link to LiveStore:commits
|
684
|
+
OtelTracer.makeSpanLink({ context: otel.trace.getSpanContext(this.otel.commitsSpanContext)! }),
|
685
|
+
// User-provided span links
|
686
|
+
...(options?.spanLinks?.map(OtelTracer.makeSpanLink) ?? []),
|
687
|
+
],
|
688
|
+
}),
|
689
|
+
Effect.tapErrorCause(Effect.logError),
|
690
|
+
Effect.catchAllCause((cause) => Effect.fork(this.shutdown(cause))),
|
691
|
+
Runtime.runSync(this.effectContext.runtime),
|
678
692
|
)
|
679
693
|
}
|
680
694
|
// #endregion commit
|
@@ -746,9 +760,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
746
760
|
*
|
747
761
|
* This is called automatically when the store was created using the React or Effect API.
|
748
762
|
*/
|
749
|
-
shutdown = (cause?: Cause.Cause<UnexpectedError>): Effect.Effect<void> => {
|
750
|
-
this.checkShutdown('shutdown')
|
751
|
-
|
763
|
+
shutdown = (cause?: Cause.Cause<UnexpectedError | MaterializeError>): Effect.Effect<void> => {
|
752
764
|
this.isShutdown = true
|
753
765
|
return this.clientSession.shutdown(
|
754
766
|
cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
|
@@ -798,14 +810,12 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
798
810
|
.pipe(this.runEffectFork)
|
799
811
|
},
|
800
812
|
|
801
|
-
syncStates: () =>
|
813
|
+
syncStates: () =>
|
802
814
|
Effect.gen(this, function* () {
|
803
815
|
const session = yield* this.syncProcessor.syncState
|
804
|
-
console.log('Session sync state:', session.toJSON())
|
805
816
|
const leader = yield* this.clientSession.leaderThread.getSyncState
|
806
|
-
|
807
|
-
}).pipe(this.
|
808
|
-
},
|
817
|
+
return { session, leader }
|
818
|
+
}).pipe(this.runEffectPromise),
|
809
819
|
|
810
820
|
version: liveStoreVersion,
|
811
821
|
|
@@ -827,6 +837,9 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
827
837
|
Runtime.runFork(this.effectContext.runtime),
|
828
838
|
)
|
829
839
|
|
840
|
+
private runEffectPromise = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
841
|
+
effect.pipe(Effect.tapCauseLogPretty, Runtime.runPromise(this.effectContext.runtime))
|
842
|
+
|
830
843
|
private getCommitArgs = (
|
831
844
|
firstEventOrTxnFnOrOptions: any,
|
832
845
|
restEvents: any[],
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
2
2
|
import { provideOtel } from '@livestore/common'
|
3
3
|
import { createStore, Events, makeSchema, State } from '@livestore/livestore'
|
4
|
+
import { omitUndefineds } from '@livestore/utils'
|
4
5
|
import { Effect, Schema } from '@livestore/utils/effect'
|
5
6
|
import type * as otel from '@opentelemetry/api'
|
6
7
|
|
@@ -71,4 +72,4 @@ export const makeTodoMvc = ({
|
|
71
72
|
})
|
72
73
|
|
73
74
|
return store
|
74
|
-
}).pipe(provideOtel({ parentSpanContext: otelContext, otelTracer: otelTracer }))
|
75
|
+
}).pipe(provideOtel(omitUndefineds({ parentSpanContext: otelContext, otelTracer: otelTracer })))
|
package/src/utils/tests/otel.ts
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
import { omitUndefineds } from '@livestore/utils'
|
1
2
|
import { identity } from '@livestore/utils/effect'
|
2
3
|
import type { Attributes } from '@opentelemetry/api'
|
3
4
|
import type { InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
4
5
|
|
5
6
|
type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
|
6
7
|
|
7
|
-
|
8
|
+
type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
|
9
|
+
|
10
|
+
const buildSimplifiedRootSpans = (
|
8
11
|
exporter: InMemorySpanExporter,
|
12
|
+
rootSpanName: string,
|
9
13
|
mapAttributes?: (attributes: Attributes) => Attributes,
|
10
|
-
): SimplifiedNestedSpan => {
|
14
|
+
): SimplifiedNestedSpan[] => {
|
11
15
|
const spans = exporter.getFinishedSpans()
|
12
16
|
const spansMap = new Map<string, NestedSpan>(spans.map((span) => [span.spanContext().spanId, { span, children: [] }]))
|
13
17
|
|
@@ -21,14 +25,12 @@ export const getSimplifiedRootSpan = (
|
|
21
25
|
}
|
22
26
|
})
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
if (createStoreSpanData === undefined) {
|
28
|
+
const rootSpanDataList = spans.filter((_) => _.name === rootSpanName)
|
29
|
+
if (rootSpanDataList.length === 0) {
|
27
30
|
throw new Error(
|
28
|
-
`Could not find
|
31
|
+
`Could not find any root spans named '${rootSpanName}'. Available spans: ${spans.map((s) => s.name).join(', ')}`,
|
29
32
|
)
|
30
33
|
}
|
31
|
-
const rootSpan = spansMap.get(createStoreSpanData.spanContext().spanId)!
|
32
34
|
|
33
35
|
const simplifySpanRec = (span: NestedSpan): SimplifiedNestedSpan =>
|
34
36
|
omitEmpty({
|
@@ -40,20 +42,29 @@ export const getSimplifiedRootSpan = (
|
|
40
42
|
.map(simplifySpanRec),
|
41
43
|
})
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
// )
|
49
|
-
|
50
|
-
const simplifiedRootSpan = simplifySpanRec(rootSpan)
|
51
|
-
|
52
|
-
// console.log('simplifiedRootSpan', simplifiedRootSpan)
|
45
|
+
return rootSpanDataList.map((rootSpanData) => {
|
46
|
+
const rootSpan = spansMap.get(rootSpanData.spanContext().spanId)!
|
47
|
+
return simplifySpanRec(rootSpan)
|
48
|
+
})
|
49
|
+
}
|
53
50
|
|
54
|
-
|
51
|
+
export const getSimplifiedRootSpan = (
|
52
|
+
exporter: InMemorySpanExporter,
|
53
|
+
rootSpanName: string,
|
54
|
+
mapAttributes?: (attributes: Attributes) => Attributes,
|
55
|
+
): SimplifiedNestedSpan => {
|
56
|
+
const results = buildSimplifiedRootSpans(exporter, rootSpanName, mapAttributes)
|
57
|
+
const firstResult = results[0]
|
58
|
+
if (!firstResult) throw new Error(`Could not find the root span named '${rootSpanName}'.`)
|
59
|
+
return firstResult
|
60
|
+
}
|
55
61
|
|
56
|
-
|
62
|
+
export const getAllSimplifiedRootSpans = (
|
63
|
+
exporter: InMemorySpanExporter,
|
64
|
+
rootSpanName: string,
|
65
|
+
mapAttributes?: (attributes: Attributes) => Attributes,
|
66
|
+
): SimplifiedNestedSpan[] => {
|
67
|
+
return buildSimplifiedRootSpans(exporter, rootSpanName, mapAttributes)
|
57
68
|
}
|
58
69
|
|
59
70
|
// const compareHrTime = (a: [number, numndber], b: [number, number]) => {
|
@@ -96,7 +107,7 @@ export const toTraceFile = (spans: ReadableSpan[]) => {
|
|
96
107
|
spans: spans.map((span) => ({
|
97
108
|
traceId: span.spanContext().traceId,
|
98
109
|
spanId: span.spanContext().spanId,
|
99
|
-
...(
|
110
|
+
...omitUndefineds({ parentSpanId: span.parentSpanContext?.spanId }),
|
100
111
|
// traceState: span.spanContext().traceState ?? '',
|
101
112
|
name: span.name,
|
102
113
|
kind: 'SPAN_KIND_INTERNAL',
|