@livestore/livestore 0.0.0-snapshot-b2abb1712675b77ec94147aeb6c4ffa858b97daa → 0.0.0-snapshot-d494b38b4c747ce88538b162d3be1e7a74efe171
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/src/store/store.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
} from '@livestore/common'
|
|
9
9
|
import {
|
|
10
10
|
Devtools,
|
|
11
|
-
getDurationMsFromSpan,
|
|
12
11
|
getExecStatementsFromMaterializer,
|
|
13
12
|
getResultSchema,
|
|
14
13
|
hashMaterializerResults,
|
|
@@ -115,70 +114,71 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
|
115
114
|
schema,
|
|
116
115
|
clientSession,
|
|
117
116
|
runtime: effectContext.runtime,
|
|
118
|
-
materializeEvent: (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
UnexpectedError.make({
|
|
117
|
+
materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')(
|
|
118
|
+
(eventDecoded, { withChangeset, materializerHashLeader }) =>
|
|
119
|
+
Effect.gen(this, function* () {
|
|
120
|
+
const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
|
|
121
|
+
|
|
122
|
+
const execArgsArr = getExecStatementsFromMaterializer({
|
|
123
|
+
eventDef,
|
|
124
|
+
materializer,
|
|
125
|
+
dbState: this.sqliteDbWrapper,
|
|
126
|
+
event: { decoded: eventDecoded, encoded: undefined },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const materializerHash = isDevEnv() ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
materializerHashLeader._tag === 'Some' &&
|
|
133
|
+
materializerHash._tag === 'Some' &&
|
|
134
|
+
materializerHashLeader.value !== materializerHash.value
|
|
135
|
+
) {
|
|
136
|
+
const error = UnexpectedError.make({
|
|
138
137
|
cause: `Materializer hash mismatch detected for event "${eventDecoded.name}".`,
|
|
139
138
|
note: `Please make sure your event materializer is a pure function without side effects.`,
|
|
140
|
-
}),
|
|
141
|
-
),
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const writeTablesForEvent = new Set<string>()
|
|
146
|
-
|
|
147
|
-
const exec = () => {
|
|
148
|
-
for (const {
|
|
149
|
-
statementSql,
|
|
150
|
-
bindValues,
|
|
151
|
-
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
|
152
|
-
} of execArgsArr) {
|
|
153
|
-
try {
|
|
154
|
-
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
|
|
155
|
-
} catch (cause) {
|
|
156
|
-
throw UnexpectedError.make({
|
|
157
|
-
cause,
|
|
158
|
-
note: `Error executing materializer for event "${eventDecoded.name}".\nStatement: ${statementSql}\nBind values: ${JSON.stringify(bindValues)}`,
|
|
159
139
|
})
|
|
160
|
-
}
|
|
161
140
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
141
|
+
// Fork the shutdown effect to run in the background as a daemon,
|
|
142
|
+
// ensuring it's not interrupted.
|
|
143
|
+
yield* Effect.forkDaemon(this.clientSession.shutdown(Cause.fail(error)))
|
|
144
|
+
|
|
145
|
+
// TODO: we should probably handle this more gracefully using Effect’s error channel
|
|
165
146
|
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
147
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
148
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
149
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
150
|
+
return yield* Effect.sync(() => {
|
|
151
|
+
const writeTablesForEvent = new Set<string>()
|
|
152
|
+
|
|
153
|
+
const exec = () => {
|
|
154
|
+
for (const {
|
|
155
|
+
statementSql,
|
|
156
|
+
bindValues,
|
|
157
|
+
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
|
158
|
+
} of execArgsArr) {
|
|
159
|
+
this.sqliteDbWrapper.cachedExecute(statementSql, bindValues, { otelContext, writeTables })
|
|
160
|
+
|
|
161
|
+
// durationMsTotal += durationMs
|
|
162
|
+
for (const table of writeTables) {
|
|
163
|
+
writeTablesForEvent.add(table)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
173
167
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
168
|
+
let sessionChangeset:
|
|
169
|
+
| { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
|
|
170
|
+
| { _tag: 'no-op' }
|
|
171
|
+
| { _tag: 'unset' } = { _tag: 'unset' }
|
|
172
|
+
if (withChangeset === true) {
|
|
173
|
+
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
|
174
|
+
} else {
|
|
175
|
+
exec()
|
|
176
|
+
}
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
return { writeTables: writeTablesForEvent, sessionChangeset, materializerHash }
|
|
179
|
+
})
|
|
180
|
+
}),
|
|
181
|
+
),
|
|
182
182
|
rollback: (changeset) => {
|
|
183
183
|
this.sqliteDbWrapper.rollback(changeset)
|
|
184
184
|
},
|
|
@@ -552,89 +552,80 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext =
|
|
|
552
552
|
} = (firstEventOrTxnFnOrOptions: any, ...restEvents: any[]) => {
|
|
553
553
|
const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
|
|
554
554
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)!
|
|
564
|
-
commitsSpan.addEvent('commit')
|
|
565
|
-
|
|
566
|
-
// console.group('LiveStore.commit', { skipRefresh })
|
|
567
|
-
// events.forEach((_) => console.debug(_.name, _.args))
|
|
568
|
-
// console.groupEnd()
|
|
555
|
+
Runtime.runSync(
|
|
556
|
+
this.effectContext.runtime,
|
|
557
|
+
Effect.gen(this, function* () {
|
|
558
|
+
yield* Effect.sync(() => {
|
|
559
|
+
for (const event of events) {
|
|
560
|
+
replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
|
|
561
|
+
}
|
|
562
|
+
})
|
|
569
563
|
|
|
570
|
-
|
|
564
|
+
if (events.length === 0) return
|
|
571
565
|
|
|
572
|
-
|
|
573
|
-
'LiveStore:commit',
|
|
574
|
-
{
|
|
575
|
-
attributes: {
|
|
576
|
-
'livestore.eventsCount': events.length,
|
|
577
|
-
'livestore.eventTags': events.map((_) => _.name),
|
|
578
|
-
'livestore.commitLabel': options?.label,
|
|
579
|
-
},
|
|
580
|
-
links: options?.spanLinks,
|
|
581
|
-
},
|
|
582
|
-
options?.otelContext ?? this.otel.commitsSpanContext,
|
|
583
|
-
(span) => {
|
|
584
|
-
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
566
|
+
const localRuntime = yield* Effect.runtime()
|
|
585
567
|
|
|
586
|
-
try
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
try {
|
|
590
|
-
const materializeEvents = () => {
|
|
591
|
-
return Runtime.runSync(this.effectContext.runtime, this.syncProcessor.push(events, { otelContext }))
|
|
592
|
-
}
|
|
568
|
+
const materializeEventsTx = Effect.try({
|
|
569
|
+
try: () => {
|
|
570
|
+
const materializeEvents = this.syncProcessor.push(events)
|
|
593
571
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
} else {
|
|
597
|
-
return materializeEvents()
|
|
598
|
-
}
|
|
599
|
-
} catch (e: any) {
|
|
600
|
-
console.error(e)
|
|
601
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
602
|
-
throw e
|
|
603
|
-
} finally {
|
|
604
|
-
span.end()
|
|
572
|
+
const runMaterializeEvents = () => {
|
|
573
|
+
return Runtime.runSync(localRuntime, materializeEvents)
|
|
605
574
|
}
|
|
606
|
-
})()
|
|
607
575
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
events,
|
|
618
|
-
writeTables: Array.from(writeTables),
|
|
619
|
-
}
|
|
576
|
+
if (events.length > 1) {
|
|
577
|
+
// TODO: what to do about leader transaction here?
|
|
578
|
+
return this.sqliteDbWrapper.txn(runMaterializeEvents)
|
|
579
|
+
} else {
|
|
580
|
+
return runMaterializeEvents()
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
catch: (cause) => UnexpectedError.make({ cause }),
|
|
584
|
+
})
|
|
620
585
|
|
|
621
|
-
|
|
622
|
-
this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh })
|
|
623
|
-
} catch (e: any) {
|
|
624
|
-
console.error(e)
|
|
625
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
|
626
|
-
throw e
|
|
627
|
-
} finally {
|
|
628
|
-
span.end()
|
|
586
|
+
const { writeTables } = yield* materializeEventsTx
|
|
629
587
|
|
|
630
|
-
|
|
588
|
+
const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
|
|
589
|
+
for (const tableName of writeTables) {
|
|
590
|
+
const tableRef = this.tableRefs[tableName]
|
|
591
|
+
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
|
592
|
+
tablesToUpdate.push([tableRef!, null])
|
|
631
593
|
}
|
|
632
594
|
|
|
633
|
-
|
|
634
|
-
|
|
595
|
+
const debugRefreshReason: RefreshReason = {
|
|
596
|
+
_tag: 'commit',
|
|
597
|
+
events,
|
|
598
|
+
writeTables: Array.from(writeTables),
|
|
599
|
+
}
|
|
600
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
601
|
+
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
602
|
+
const skipRefresh = options?.skipRefresh ?? false
|
|
603
|
+
|
|
604
|
+
// Update all table refs together in a batch, to only trigger one reactive update
|
|
605
|
+
yield* Effect.sync(() => {
|
|
606
|
+
this.reactivityGraph.setRefs(tablesToUpdate, {
|
|
607
|
+
debugRefreshReason,
|
|
608
|
+
otelContext,
|
|
609
|
+
skipRefresh,
|
|
610
|
+
})
|
|
611
|
+
})
|
|
612
|
+
}).pipe(
|
|
613
|
+
Effect.withSpan('LiveStore:commit', {
|
|
614
|
+
root: true,
|
|
615
|
+
attributes: {
|
|
616
|
+
'livestore.eventsCount': events.length,
|
|
617
|
+
'livestore.eventTags': events.map((_) => _.name),
|
|
618
|
+
'livestore.commitLabel': options?.label,
|
|
619
|
+
},
|
|
620
|
+
links: options?.spanLinks?.map((link) => ({
|
|
621
|
+
_tag: 'SpanLink',
|
|
622
|
+
span: OtelTracer.makeExternalSpan(link.context),
|
|
623
|
+
attributes: link.attributes ?? {},
|
|
624
|
+
})),
|
|
625
|
+
}),
|
|
626
|
+
),
|
|
635
627
|
)
|
|
636
628
|
}
|
|
637
|
-
// #endregion commit
|
|
638
629
|
|
|
639
630
|
/**
|
|
640
631
|
* Returns an async iterable of events.
|