@livestore/livestore 0.3.0-dev.4 → 0.3.0-dev.40
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/QueryCache.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +60 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/{SynchronousDatabaseWrapper.js → SqliteDbWrapper.js} +69 -34
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +6 -34
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +10 -12
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/mod.d.ts +3 -0
- package/dist/effect/mod.d.ts.map +1 -0
- package/dist/effect/mod.js +3 -0
- package/dist/effect/mod.js.map +1 -0
- package/dist/internal/mod.d.ts +3 -0
- package/dist/internal/mod.d.ts.map +1 -0
- package/dist/internal/mod.js +3 -0
- package/dist/internal/mod.js.map +1 -0
- package/dist/live-queries/base-class.d.ts +65 -27
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +54 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +12 -0
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -0
- package/dist/live-queries/client-document-get-query.js +18 -0
- package/dist/live-queries/client-document-get-query.js.map +1 -0
- package/dist/live-queries/computed.d.ts +12 -14
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +37 -15
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +64 -0
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/{db.js → db-query.js} +83 -41
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +133 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/mod.d.ts +5 -0
- package/dist/live-queries/mod.d.ts.map +1 -0
- package/dist/live-queries/mod.js +5 -0
- package/dist/live-queries/mod.js.map +1 -0
- package/dist/live-queries/signal.d.ts +20 -0
- package/dist/live-queries/signal.d.ts.map +1 -0
- package/dist/live-queries/signal.js +33 -0
- package/dist/live-queries/signal.js.map +1 -0
- package/dist/live-queries/signal.test.d.ts +2 -0
- package/dist/live-queries/signal.test.d.ts.map +1 -0
- package/dist/live-queries/signal.test.js +17 -0
- package/dist/live-queries/signal.test.js.map +1 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +13 -0
- package/dist/mod.js.map +1 -0
- package/dist/reactive.d.ts +23 -17
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +23 -19
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/create-store.d.ts +70 -12
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +69 -19
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -4
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +103 -47
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +32 -42
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +2 -5
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +104 -39
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +261 -214
- package/dist/store/store.js.map +1 -1
- package/dist/utils/data-structures.d.ts.map +1 -1
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +6 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +59 -216
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +23 -18
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +8 -3
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +29 -26
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +92 -42
- package/src/effect/LiveStore.ts +27 -64
- package/src/effect/{index.ts → mod.ts} +2 -3
- package/src/internal/mod.ts +2 -0
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -45
- package/src/live-queries/base-class.ts +152 -50
- package/src/live-queries/client-document-get-query.ts +52 -0
- package/src/live-queries/computed.ts +51 -33
- package/src/live-queries/db-query.test.ts +192 -0
- package/src/live-queries/{db.ts → db-query.ts} +140 -82
- package/src/live-queries/mod.ts +4 -0
- package/src/live-queries/signal.test.ts +25 -0
- package/src/live-queries/signal.ts +47 -0
- package/src/mod.ts +42 -0
- package/src/reactive.test.ts +1 -1
- package/src/reactive.ts +66 -43
- package/src/store/create-store.ts +187 -59
- package/src/store/devtools.ts +136 -54
- package/src/store/store-types.ts +31 -43
- package/src/store/store.ts +385 -309
- package/src/utils/dev.ts +6 -1
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +22 -31
- package/src/utils/tests/mod.ts +1 -0
- package/src/utils/tests/otel.ts +10 -3
- package/dist/SynchronousDatabaseWrapper.d.ts +0 -41
- package/dist/SynchronousDatabaseWrapper.d.ts.map +0 -1
- package/dist/SynchronousDatabaseWrapper.js.map +0 -1
- package/dist/effect/index.d.ts +0 -2
- package/dist/effect/index.d.ts.map +0 -1
- package/dist/effect/index.js +0 -2
- package/dist/effect/index.js.map +0 -1
- package/dist/global-state.d.ts +0 -14
- package/dist/global-state.d.ts.map +0 -1
- package/dist/global-state.js +0 -16
- package/dist/global-state.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -16
- package/dist/index.js.map +0 -1
- package/dist/live-queries/db.d.ts +0 -66
- package/dist/live-queries/db.d.ts.map +0 -1
- package/dist/live-queries/db.js.map +0 -1
- package/dist/live-queries/db.test.d.ts +0 -2
- package/dist/live-queries/db.test.d.ts.map +0 -1
- package/dist/live-queries/db.test.js +0 -118
- package/dist/live-queries/db.test.js.map +0 -1
- package/dist/live-queries/graphql.d.ts +0 -49
- package/dist/live-queries/graphql.d.ts.map +0 -1
- package/dist/live-queries/graphql.js +0 -122
- package/dist/live-queries/graphql.js.map +0 -1
- package/dist/row-query-utils.d.ts +0 -17
- package/dist/row-query-utils.d.ts.map +0 -1
- package/dist/row-query-utils.js +0 -31
- package/dist/row-query-utils.js.map +0 -1
- package/dist/utils/otel.d.ts +0 -4
- package/dist/utils/otel.d.ts.map +0 -1
- package/dist/utils/otel.js +0 -6
- package/dist/utils/otel.js.map +0 -1
- package/src/global-state.ts +0 -20
- package/src/index.ts +0 -66
- package/src/live-queries/db.test.ts +0 -154
- package/src/live-queries/graphql.ts +0 -219
- package/src/row-query-utils.ts +0 -66
- package/src/utils/otel.ts +0 -9
- package/tsconfig.json +0 -18
- package/vitest.config.js +0 -9
package/src/store/store.ts
CHANGED
@@ -7,112 +7,115 @@ import type {
|
|
7
7
|
UnexpectedError,
|
8
8
|
} from '@livestore/common'
|
9
9
|
import {
|
10
|
-
|
10
|
+
Devtools,
|
11
|
+
getDurationMsFromSpan,
|
12
|
+
getExecArgsFromEvent,
|
11
13
|
getResultSchema,
|
12
14
|
IntentionalShutdownCause,
|
13
15
|
isQueryBuilder,
|
16
|
+
liveStoreVersion,
|
14
17
|
makeClientSessionSyncProcessor,
|
15
18
|
prepareBindValues,
|
16
19
|
QueryBuilderAstSymbol,
|
17
20
|
replaceSessionIdSymbol,
|
18
21
|
} from '@livestore/common'
|
19
22
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
20
|
-
import {
|
21
|
-
MutationEvent,
|
22
|
-
SCHEMA_META_TABLE,
|
23
|
-
SCHEMA_MUTATIONS_META_TABLE,
|
24
|
-
SESSION_CHANGESET_META_TABLE,
|
25
|
-
} from '@livestore/common/schema'
|
23
|
+
import { getEventDef, LiveStoreEvent, SystemTables } from '@livestore/common/schema'
|
26
24
|
import { assertNever, isDevEnv } from '@livestore/utils'
|
27
25
|
import type { Scope } from '@livestore/utils/effect'
|
28
|
-
import { Cause,
|
26
|
+
import { Cause, Effect, Inspectable, OtelTracer, Runtime, Schema, Stream } from '@livestore/utils/effect'
|
27
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
29
28
|
import * as otel from '@opentelemetry/api'
|
30
|
-
import { type GraphQLSchema } from 'graphql'
|
31
29
|
|
32
|
-
import type {
|
30
|
+
import type {
|
31
|
+
LiveQuery,
|
32
|
+
LiveQueryDef,
|
33
|
+
ReactivityGraph,
|
34
|
+
ReactivityGraphContext,
|
35
|
+
SignalDef,
|
36
|
+
} from '../live-queries/base-class.js'
|
37
|
+
import { makeReactivityGraph } from '../live-queries/base-class.js'
|
38
|
+
import { makeExecBeforeFirstRun } from '../live-queries/client-document-get-query.js'
|
33
39
|
import type { Ref } from '../reactive.js'
|
34
|
-
import {
|
35
|
-
import { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
40
|
+
import { SqliteDbWrapper } from '../SqliteDbWrapper.js'
|
36
41
|
import { ReferenceCountedSet } from '../utils/data-structures.js'
|
37
42
|
import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
|
38
|
-
import {
|
39
|
-
import type {
|
43
|
+
import type { StackInfo } from '../utils/stack-info.js'
|
44
|
+
import type { RefreshReason, StoreCommitOptions, StoreOptions, StoreOtel, Unsubscribe } from './store-types.js'
|
40
45
|
|
41
46
|
if (isDevEnv()) {
|
42
47
|
exposeDebugUtils()
|
43
48
|
}
|
44
49
|
|
45
|
-
export class Store<
|
46
|
-
TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext,
|
47
|
-
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
48
|
-
> extends Inspectable.Class {
|
50
|
+
export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema, TContext = {}> extends Inspectable.Class {
|
49
51
|
readonly storeId: string
|
50
52
|
reactivityGraph: ReactivityGraph
|
51
|
-
|
53
|
+
sqliteDbWrapper: SqliteDbWrapper
|
52
54
|
clientSession: ClientSession
|
53
55
|
schema: LiveStoreSchema
|
54
|
-
|
55
|
-
graphQLContext?: TGraphQLContext
|
56
|
+
context: TContext
|
56
57
|
otel: StoreOtel
|
57
58
|
/**
|
58
59
|
* Note we're using `Ref<null>` here as we don't care about the value but only about *that* something has changed.
|
59
60
|
* This only works in combination with `equal: () => false` which will always trigger a refresh.
|
60
61
|
*/
|
61
|
-
tableRefs: { [key: string]: Ref<null,
|
62
|
+
tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> }
|
62
63
|
|
63
|
-
private
|
64
|
+
private effectContext: {
|
65
|
+
runtime: Runtime.Runtime<Scope.Scope>
|
66
|
+
lifetimeScope: Scope.Scope
|
67
|
+
}
|
64
68
|
|
65
69
|
/** RC-based set to see which queries are currently subscribed to */
|
66
70
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
67
71
|
|
68
|
-
// NOTE this is currently exposed for the Devtools databrowser to
|
69
|
-
readonly
|
70
|
-
|
71
|
-
|
72
|
-
readonly
|
72
|
+
// NOTE this is currently exposed for the Devtools databrowser to commit events
|
73
|
+
readonly __eventSchema
|
74
|
+
readonly syncProcessor: ClientSessionSyncProcessor
|
75
|
+
|
76
|
+
readonly boot: Effect.Effect<void, UnexpectedError, Scope.Scope>
|
73
77
|
|
74
78
|
// #region constructor
|
75
|
-
|
79
|
+
constructor({
|
76
80
|
clientSession,
|
77
81
|
schema,
|
78
|
-
graphQLOptions,
|
79
|
-
reactivityGraph,
|
80
82
|
otelOptions,
|
81
|
-
|
83
|
+
context,
|
82
84
|
batchUpdates,
|
83
|
-
unsyncedMutationEvents,
|
84
85
|
storeId,
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
effectContext,
|
87
|
+
params,
|
88
|
+
confirmUnsavedChanges,
|
89
|
+
__runningInDevtools,
|
90
|
+
}: StoreOptions<TSchema, TContext>) {
|
88
91
|
super()
|
89
92
|
|
90
93
|
this.storeId = storeId
|
91
|
-
this.unsyncedMutationEvents = unsyncedMutationEvents
|
92
94
|
|
93
|
-
this.
|
95
|
+
this.sqliteDbWrapper = new SqliteDbWrapper({ otel: otelOptions, db: clientSession.sqliteDb })
|
94
96
|
this.clientSession = clientSession
|
95
97
|
this.schema = schema
|
98
|
+
this.context = context
|
99
|
+
|
100
|
+
this.effectContext = effectContext
|
96
101
|
|
97
|
-
|
98
|
-
this.runtime = runtime
|
102
|
+
const reactivityGraph = makeReactivityGraph()
|
99
103
|
|
100
104
|
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
|
101
105
|
|
102
106
|
this.syncProcessor = makeClientSessionSyncProcessor({
|
103
107
|
schema,
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
108
|
+
clientSession,
|
109
|
+
runtime: effectContext.runtime,
|
110
|
+
materializeEvent: (eventDecoded, { otelContext, withChangeset }) => {
|
111
|
+
const { eventDef, materializer } = getEventDef(schema, eventDecoded.name)
|
112
|
+
|
113
|
+
const execArgsArr = getExecArgsFromEvent({
|
114
|
+
eventDef,
|
115
|
+
materializer,
|
116
|
+
db: this.sqliteDbWrapper,
|
117
|
+
event: { decoded: eventDecoded, encoded: undefined },
|
118
|
+
})
|
116
119
|
|
117
120
|
const writeTablesForEvent = new Set<string>()
|
118
121
|
|
@@ -120,18 +123,21 @@ export class Store<
|
|
120
123
|
for (const {
|
121
124
|
statementSql,
|
122
125
|
bindValues,
|
123
|
-
writeTables = this.
|
126
|
+
writeTables = this.sqliteDbWrapper.getTablesUsed(statementSql),
|
124
127
|
} of execArgsArr) {
|
125
|
-
this.
|
128
|
+
this.sqliteDbWrapper.execute(statementSql, bindValues, { otelContext, writeTables })
|
126
129
|
|
127
130
|
// durationMsTotal += durationMs
|
128
131
|
writeTables.forEach((table) => writeTablesForEvent.add(table))
|
129
132
|
}
|
130
133
|
}
|
131
134
|
|
132
|
-
let sessionChangeset:
|
135
|
+
let sessionChangeset:
|
136
|
+
| { _tag: 'sessionChangeset'; data: Uint8Array; debug: any }
|
137
|
+
| { _tag: 'no-op' }
|
138
|
+
| { _tag: 'unset' } = { _tag: 'unset' }
|
133
139
|
if (withChangeset === true) {
|
134
|
-
sessionChangeset = this.
|
140
|
+
sessionChangeset = this.sqliteDbWrapper.withChangeset(exec).changeset
|
135
141
|
} else {
|
136
142
|
exec()
|
137
143
|
}
|
@@ -139,35 +145,41 @@ export class Store<
|
|
139
145
|
return { writeTables: writeTablesForEvent, sessionChangeset }
|
140
146
|
},
|
141
147
|
rollback: (changeset) => {
|
142
|
-
this.
|
148
|
+
this.sqliteDbWrapper.rollback(changeset)
|
143
149
|
},
|
144
150
|
refreshTables: (tables) => {
|
145
|
-
const tablesToUpdate = [] as [Ref<null,
|
151
|
+
const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
|
146
152
|
for (const tableName of tables) {
|
147
153
|
const tableRef = this.tableRefs[tableName]
|
148
154
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
149
155
|
tablesToUpdate.push([tableRef!, null])
|
150
156
|
}
|
151
|
-
|
157
|
+
reactivityGraph.setRefs(tablesToUpdate)
|
152
158
|
},
|
153
159
|
span: syncSpan,
|
160
|
+
params: {
|
161
|
+
leaderPushBatchSize: params.leaderPushBatchSize,
|
162
|
+
},
|
163
|
+
confirmUnsavedChanges,
|
154
164
|
})
|
155
165
|
|
156
|
-
this.
|
166
|
+
this.__eventSchema = LiveStoreEvent.makeEventDefSchemaMemo(schema)
|
157
167
|
|
158
168
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
159
169
|
this.tableRefs = {}
|
160
170
|
this.activeQueries = new ReferenceCountedSet()
|
161
171
|
|
162
|
-
const
|
163
|
-
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(),
|
172
|
+
const commitsSpan = otelOptions.tracer.startSpan('LiveStore:commits', {}, otelOptions.rootSpanContext)
|
173
|
+
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(), commitsSpan)
|
164
174
|
|
165
175
|
const queriesSpan = otelOptions.tracer.startSpan('LiveStore:queries', {}, otelOptions.rootSpanContext)
|
166
176
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan)
|
167
177
|
|
168
178
|
this.reactivityGraph = reactivityGraph
|
169
179
|
this.reactivityGraph.context = {
|
170
|
-
store: this as unknown as Store<
|
180
|
+
store: this as unknown as Store<LiveStoreSchema>,
|
181
|
+
defRcMap: new Map(),
|
182
|
+
reactivityGraph: new WeakRef(reactivityGraph),
|
171
183
|
otelTracer: otelOptions.tracer,
|
172
184
|
rootOtelContext: otelQueriesSpanContext,
|
173
185
|
effectsWrapper: batchUpdates,
|
@@ -175,23 +187,18 @@ export class Store<
|
|
175
187
|
|
176
188
|
this.otel = {
|
177
189
|
tracer: otelOptions.tracer,
|
178
|
-
|
190
|
+
rootSpanContext: otelOptions.rootSpanContext,
|
191
|
+
commitsSpanContext: otelMuationsSpanContext,
|
179
192
|
queriesSpanContext: otelQueriesSpanContext,
|
180
193
|
}
|
181
194
|
|
182
|
-
// TODO find a better way to detect if we're running LiveStore in the LiveStore devtools
|
183
|
-
// But for now this is a good enough approximation with little downsides
|
184
|
-
const isRunningInDevtools = disableDevtools === true
|
185
|
-
|
186
195
|
// Need a set here since `schema.tables` might contain duplicates and some componentStateTables
|
187
196
|
const allTableNames = new Set(
|
188
|
-
// NOTE we're excluding the LiveStore schema and
|
197
|
+
// NOTE we're excluding the LiveStore schema and events tables as they are not user-facing
|
189
198
|
// unless LiveStore is running in the devtools
|
190
|
-
|
191
|
-
? this.schema.tables.keys()
|
192
|
-
: Array.from(this.schema.tables.keys()).filter(
|
193
|
-
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE && _ !== SESSION_CHANGESET_META_TABLE,
|
194
|
-
),
|
199
|
+
__runningInDevtools
|
200
|
+
? this.schema.state.sqlite.tables.keys()
|
201
|
+
: Array.from(this.schema.state.sqlite.tables.keys()).filter((_) => !SystemTables.isStateSystemTable(_)),
|
195
202
|
)
|
196
203
|
const existingTableRefs = new Map(
|
197
204
|
Array.from(this.reactivityGraph.atoms.values())
|
@@ -199,19 +206,16 @@ export class Store<
|
|
199
206
|
.map((_) => [_.label!.slice('tableRef:'.length), _] as const),
|
200
207
|
)
|
201
208
|
for (const tableName of allTableNames) {
|
202
|
-
this.tableRefs[tableName] =
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
this.otel.tracer,
|
210
|
-
clientSession.coordinator.sessionId,
|
211
|
-
)
|
209
|
+
this.tableRefs[tableName] =
|
210
|
+
existingTableRefs.get(tableName) ??
|
211
|
+
this.reactivityGraph.makeRef(null, {
|
212
|
+
equal: () => false,
|
213
|
+
label: `tableRef:${tableName}`,
|
214
|
+
meta: { liveStoreRefType: 'table' },
|
215
|
+
})
|
212
216
|
}
|
213
217
|
|
214
|
-
Effect.gen(this, function* () {
|
218
|
+
this.boot = Effect.gen(this, function* () {
|
215
219
|
yield* Effect.addFinalizer(() =>
|
216
220
|
Effect.sync(() => {
|
217
221
|
// Remove all table refs from the reactivity graph
|
@@ -223,60 +227,87 @@ export class Store<
|
|
223
227
|
|
224
228
|
// End the otel spans
|
225
229
|
syncSpan.end()
|
226
|
-
|
230
|
+
commitsSpan.end()
|
227
231
|
queriesSpan.end()
|
228
232
|
}),
|
229
233
|
)
|
230
234
|
|
231
235
|
yield* this.syncProcessor.boot
|
232
|
-
})
|
236
|
+
})
|
233
237
|
}
|
234
238
|
// #endregion constructor
|
235
239
|
|
236
|
-
|
237
|
-
|
238
|
-
parentSpan: otel.Span,
|
239
|
-
): Store<TGraphQLContext, TSchema> => {
|
240
|
-
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan)
|
241
|
-
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:createStore', {}, ctx, (span) => {
|
242
|
-
try {
|
243
|
-
return new Store(storeOptions)
|
244
|
-
} finally {
|
245
|
-
span.end()
|
246
|
-
}
|
247
|
-
})
|
240
|
+
get sessionId(): string {
|
241
|
+
return this.clientSession.sessionId
|
248
242
|
}
|
249
243
|
|
250
|
-
get
|
251
|
-
return this.clientSession.
|
244
|
+
get clientId(): string {
|
245
|
+
return this.clientSession.clientId
|
252
246
|
}
|
253
247
|
|
254
248
|
/**
|
255
249
|
* Subscribe to the results of a query
|
256
250
|
* Returns a function to cancel the subscription.
|
251
|
+
*
|
252
|
+
* @example
|
253
|
+
* ```ts
|
254
|
+
* const unsubscribe = store.subscribe(query$, { onUpdate: (result) => console.log(result) })
|
255
|
+
* ```
|
257
256
|
*/
|
258
257
|
subscribe = <TResult>(
|
259
|
-
query
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
258
|
+
query: LiveQueryDef<TResult> | LiveQuery<TResult>,
|
259
|
+
options: {
|
260
|
+
/** Called when the query result has changed */
|
261
|
+
onUpdate: (value: TResult) => void
|
262
|
+
onSubscribe?: (query$: LiveQuery<TResult>) => void
|
263
|
+
/** Gets called after the query subscription has been removed */
|
264
|
+
onUnsubsubscribe?: () => void
|
265
|
+
label?: string
|
266
|
+
/**
|
267
|
+
* Skips the initial `onUpdate` callback
|
268
|
+
* @default false
|
269
|
+
*/
|
270
|
+
skipInitialRun?: boolean
|
271
|
+
otelContext?: otel.Context
|
272
|
+
/** If provided, the stack info will be added to the `activeSubscriptions` set of the query */
|
273
|
+
stackInfo?: StackInfo
|
274
|
+
},
|
275
|
+
): Unsubscribe =>
|
264
276
|
this.otel.tracer.startActiveSpan(
|
265
277
|
`LiveStore.subscribe`,
|
266
|
-
{ attributes: { label: options?.label, queryLabel: query
|
278
|
+
{ attributes: { label: options?.label, queryLabel: query.label } },
|
267
279
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
268
280
|
(span) => {
|
269
281
|
// console.debug('store sub', query$.id, query$.label)
|
270
282
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
271
283
|
|
284
|
+
const queryRcRef =
|
285
|
+
query._tag === 'def'
|
286
|
+
? query.make(this.reactivityGraph.context!)
|
287
|
+
: {
|
288
|
+
value: query,
|
289
|
+
deref: () => {},
|
290
|
+
}
|
291
|
+
const query$ = queryRcRef.value
|
292
|
+
|
272
293
|
const label = `subscribe:${options?.label}`
|
273
|
-
const effect = this.reactivityGraph.makeEffect(
|
294
|
+
const effect = this.reactivityGraph.makeEffect(
|
295
|
+
(get, _otelContext, debugRefreshReason) =>
|
296
|
+
options.onUpdate(get(query$.results$, otelContext, debugRefreshReason)),
|
297
|
+
{ label },
|
298
|
+
)
|
299
|
+
|
300
|
+
if (options?.stackInfo) {
|
301
|
+
query$.activeSubscriptions.add(options.stackInfo)
|
302
|
+
}
|
303
|
+
|
304
|
+
options?.onSubscribe?.(query$)
|
274
305
|
|
275
306
|
this.activeQueries.add(query$ as LiveQuery<TResult>)
|
276
307
|
|
277
308
|
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
278
|
-
if (options?.skipInitialRun !== true) {
|
279
|
-
effect.doEffect(otelContext)
|
309
|
+
if (options?.skipInitialRun !== true && !query$.isDestroyed) {
|
310
|
+
effect.doEffect(otelContext, { _tag: 'subscribe.initial', label: `subscribe-initial-run:${options?.label}` })
|
280
311
|
}
|
281
312
|
|
282
313
|
const unsubscribe = () => {
|
@@ -284,7 +315,14 @@ export class Store<
|
|
284
315
|
try {
|
285
316
|
this.reactivityGraph.destroyNode(effect)
|
286
317
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
287
|
-
|
318
|
+
|
319
|
+
if (options?.stackInfo) {
|
320
|
+
query$.activeSubscriptions.delete(options.stackInfo)
|
321
|
+
}
|
322
|
+
|
323
|
+
queryRcRef.deref()
|
324
|
+
|
325
|
+
options?.onUnsubsubscribe?.()
|
288
326
|
} finally {
|
289
327
|
span.end()
|
290
328
|
}
|
@@ -294,6 +332,30 @@ export class Store<
|
|
294
332
|
},
|
295
333
|
)
|
296
334
|
|
335
|
+
subscribeStream = <TResult>(
|
336
|
+
query$: LiveQueryDef<TResult>,
|
337
|
+
options?: { label?: string; skipInitialRun?: boolean } | undefined,
|
338
|
+
): Stream.Stream<TResult> =>
|
339
|
+
Stream.asyncPush<TResult>((emit) =>
|
340
|
+
Effect.gen(this, function* () {
|
341
|
+
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
|
342
|
+
Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)),
|
343
|
+
)
|
344
|
+
const otelContext = otelSpan ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active()
|
345
|
+
|
346
|
+
yield* Effect.acquireRelease(
|
347
|
+
Effect.sync(() =>
|
348
|
+
this.subscribe(query$, {
|
349
|
+
onUpdate: (result) => emit.single(result),
|
350
|
+
otelContext,
|
351
|
+
label: options?.label,
|
352
|
+
}),
|
353
|
+
),
|
354
|
+
(unsub) => Effect.sync(() => unsub()),
|
355
|
+
)
|
356
|
+
}),
|
357
|
+
)
|
358
|
+
|
297
359
|
/**
|
298
360
|
* Synchronously queries the database without creating a LiveQuery.
|
299
361
|
* This is useful for queries that don't need to be reactive.
|
@@ -309,12 +371,15 @@ export class Store<
|
|
309
371
|
* ```
|
310
372
|
*/
|
311
373
|
query = <TResult>(
|
312
|
-
query:
|
313
|
-
|
374
|
+
query:
|
375
|
+
| QueryBuilder<TResult, any, any>
|
376
|
+
| LiveQuery<TResult>
|
377
|
+
| LiveQueryDef<TResult>
|
378
|
+
| { query: string; bindValues: ParamsObject },
|
379
|
+
options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason },
|
314
380
|
): TResult => {
|
315
381
|
if (typeof query === 'object' && 'query' in query && 'bindValues' in query) {
|
316
|
-
return this.
|
317
|
-
bindValues: prepareBindValues(query.bindValues, query.query),
|
382
|
+
return this.sqliteDbWrapper.select(query.query, prepareBindValues(query.bindValues, query.query), {
|
318
383
|
otelContext: options?.otelContext,
|
319
384
|
}) as any
|
320
385
|
} else if (isQueryBuilder(query)) {
|
@@ -323,99 +388,167 @@ export class Store<
|
|
323
388
|
makeExecBeforeFirstRun({
|
324
389
|
table: ast.tableDef,
|
325
390
|
id: ast.id,
|
326
|
-
|
391
|
+
explicitDefaultValues: ast.explicitDefaultValues,
|
327
392
|
otelContext: options?.otelContext,
|
328
393
|
})(this.reactivityGraph.context!)
|
329
394
|
}
|
330
395
|
|
331
396
|
const sqlRes = query.asSql()
|
332
397
|
const schema = getResultSchema(query)
|
333
|
-
const rawRes = this.
|
334
|
-
bindValues: sqlRes.bindValues as any as PreparedBindValues,
|
398
|
+
const rawRes = this.sqliteDbWrapper.select(sqlRes.query, sqlRes.bindValues as any as PreparedBindValues, {
|
335
399
|
otelContext: options?.otelContext,
|
336
400
|
queriedTables: new Set([query[QueryBuilderAstSymbol].tableDef.sqliteDef.name]),
|
337
401
|
})
|
402
|
+
|
338
403
|
return Schema.decodeSync(schema)(rawRes)
|
404
|
+
} else if (query._tag === 'def') {
|
405
|
+
const query$ = query.make(this.reactivityGraph.context!)
|
406
|
+
const result = this.query(query$.value, options)
|
407
|
+
query$.deref()
|
408
|
+
return result
|
339
409
|
} else {
|
340
|
-
return query.run(options?.otelContext)
|
410
|
+
return query.run({ otelContext: options?.otelContext, debugRefreshReason: options?.debugRefreshReason })
|
411
|
+
}
|
412
|
+
}
|
413
|
+
|
414
|
+
setSignal = <T>(signalDef: SignalDef<T>, value: T): void => {
|
415
|
+
const signalRef = signalDef.make(this.reactivityGraph.context!)
|
416
|
+
signalRef.value.set(value)
|
417
|
+
|
418
|
+
// The current implementation of signals i.e. the separation into `signal-def` and `signal`
|
419
|
+
// can lead to a situation where a reffed signal is immediately de-reffed when calling `store.setSignal`,
|
420
|
+
// in case there is nothing else holding a reference to the signal which leads to the set value being "lost".
|
421
|
+
// To avoid this, we don't deref the signal here if this set call is the only reference to the signal.
|
422
|
+
// Hopefully this won't lead to any issues in the future. 🤞
|
423
|
+
if (signalRef.rc > 1) {
|
424
|
+
signalRef.deref()
|
341
425
|
}
|
342
426
|
}
|
343
427
|
|
344
|
-
// #region
|
345
|
-
|
346
|
-
|
428
|
+
// #region commit
|
429
|
+
/**
|
430
|
+
* Commit a list of events to the store which will immediately update the local database
|
431
|
+
* and sync the events across other clients (similar to a `git commit`).
|
432
|
+
*
|
433
|
+
* @example
|
434
|
+
* ```ts
|
435
|
+
* store.commit(events.todoCreated({ id: nanoid(), text: 'Make coffee' }))
|
436
|
+
* ```
|
437
|
+
*
|
438
|
+
* You can call `commit` with multiple events to apply them in a single database transaction.
|
439
|
+
*
|
440
|
+
* @example
|
441
|
+
* ```ts
|
442
|
+
* const todoId = nanoid()
|
443
|
+
* store.commit(
|
444
|
+
* events.todoCreated({ id: todoId, text: 'Make coffee' }),
|
445
|
+
* events.todoCompleted({ id: todoId }))
|
446
|
+
* ```
|
447
|
+
*
|
448
|
+
* For more advanced transaction scenarios, you can pass a synchronous function to `commit` which will receive a callback
|
449
|
+
* to which you can pass multiple events to be committed in the same database transaction.
|
450
|
+
* Under the hood this will simply collect all events and apply them in a single database transaction.
|
451
|
+
*
|
452
|
+
* @example
|
453
|
+
* ```ts
|
454
|
+
* store.commit((commit) => {
|
455
|
+
* const todoId = nanoid()
|
456
|
+
* if (Math.random() > 0.5) {
|
457
|
+
* commit(events.todoCreated({ id: todoId, text: 'Make coffee' }))
|
458
|
+
* } else {
|
459
|
+
* commit(events.todoCompleted({ id: todoId }))
|
460
|
+
* }
|
461
|
+
* })
|
462
|
+
* ```
|
463
|
+
*
|
464
|
+
* When committing a large batch of events, you can also skip the database refresh to improve performance
|
465
|
+
* and call `store.manualRefresh()` after all events have been committed.
|
466
|
+
*
|
467
|
+
* @example
|
468
|
+
* ```ts
|
469
|
+
* const todos = [
|
470
|
+
* { id: nanoid(), text: 'Make coffee' },
|
471
|
+
* { id: nanoid(), text: 'Buy groceries' },
|
472
|
+
* // ... 1000 more todos
|
473
|
+
* ]
|
474
|
+
* for (const todo of todos) {
|
475
|
+
* store.commit({ skipRefresh: true }, events.todoCreated({ id: todo.id, text: todo.text }))
|
476
|
+
* }
|
477
|
+
* store.manualRefresh()
|
478
|
+
* ```
|
479
|
+
*/
|
480
|
+
commit: {
|
481
|
+
<const TCommitArg extends ReadonlyArray<LiveStoreEvent.PartialForSchema<TSchema>>>(...list: TCommitArg): void
|
347
482
|
(
|
348
|
-
txn: <const
|
349
|
-
...list:
|
483
|
+
txn: <const TCommitArg extends ReadonlyArray<LiveStoreEvent.PartialForSchema<TSchema>>>(
|
484
|
+
...list: TCommitArg
|
350
485
|
) => void,
|
351
486
|
): void
|
352
|
-
<const
|
353
|
-
options:
|
354
|
-
...list:
|
487
|
+
<const TCommitArg extends ReadonlyArray<LiveStoreEvent.PartialForSchema<TSchema>>>(
|
488
|
+
options: StoreCommitOptions,
|
489
|
+
...list: TCommitArg
|
355
490
|
): void
|
356
491
|
(
|
357
|
-
options:
|
358
|
-
txn: <const
|
359
|
-
...list:
|
492
|
+
options: StoreCommitOptions,
|
493
|
+
txn: <const TCommitArg extends ReadonlyArray<LiveStoreEvent.PartialForSchema<TSchema>>>(
|
494
|
+
...list: TCommitArg
|
360
495
|
) => void,
|
361
496
|
): void
|
362
|
-
} = (
|
363
|
-
const {
|
497
|
+
} = (firstEventOrTxnFnOrOptions: any, ...restEvents: any[]) => {
|
498
|
+
const { events, options } = this.getCommitArgs(firstEventOrTxnFnOrOptions, restEvents)
|
364
499
|
|
365
|
-
for (const
|
366
|
-
replaceSessionIdSymbol(
|
500
|
+
for (const event of events) {
|
501
|
+
replaceSessionIdSymbol(event.args, this.clientSession.sessionId)
|
367
502
|
}
|
368
503
|
|
369
|
-
if (
|
504
|
+
if (events.length === 0) return
|
370
505
|
|
371
|
-
const label = options?.label ?? 'mutate'
|
372
506
|
const skipRefresh = options?.skipRefresh ?? false
|
373
507
|
|
374
|
-
const
|
375
|
-
|
508
|
+
const commitsSpan = otel.trace.getSpan(this.otel.commitsSpanContext)!
|
509
|
+
commitsSpan.addEvent('commit')
|
376
510
|
|
377
|
-
// console.group('LiveStore.
|
378
|
-
//
|
511
|
+
// console.group('LiveStore.commit', { skipRefresh, wasSyncMessage, label })
|
512
|
+
// events.forEach((_) => console.debug(_.name, _.id, _.args))
|
379
513
|
// console.groupEnd()
|
380
514
|
|
381
515
|
let durationMs: number
|
382
516
|
|
383
517
|
return this.otel.tracer.startActiveSpan(
|
384
|
-
'LiveStore:
|
385
|
-
{
|
386
|
-
|
518
|
+
'LiveStore:commit',
|
519
|
+
{
|
520
|
+
attributes: {
|
521
|
+
'livestore.eventsCount': events.length,
|
522
|
+
'livestore.eventTags': events.map((_) => _.name),
|
523
|
+
'livestore.commitLabel': options?.label,
|
524
|
+
},
|
525
|
+
links: options?.spanLinks,
|
526
|
+
},
|
527
|
+
options?.otelContext ?? this.otel.commitsSpanContext,
|
387
528
|
(span) => {
|
388
529
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
389
530
|
|
390
531
|
try {
|
391
|
-
const { writeTables } =
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
const applyMutations = () => this.syncProcessor.push(mutationsEvents, { otelContext })
|
401
|
-
|
402
|
-
if (mutationsEvents.length > 1) {
|
403
|
-
// TODO: what to do about coordinator transaction here?
|
404
|
-
return this.syncDbWrapper.txn(applyMutations)
|
405
|
-
} else {
|
406
|
-
return applyMutations()
|
407
|
-
}
|
408
|
-
} catch (e: any) {
|
409
|
-
console.error(e)
|
410
|
-
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
411
|
-
throw e
|
412
|
-
} finally {
|
413
|
-
span.end()
|
532
|
+
const { writeTables } = (() => {
|
533
|
+
try {
|
534
|
+
const materializeEvents = () => this.syncProcessor.push(events, { otelContext })
|
535
|
+
|
536
|
+
if (events.length > 1) {
|
537
|
+
// TODO: what to do about leader transaction here?
|
538
|
+
return this.sqliteDbWrapper.txn(materializeEvents)
|
539
|
+
} else {
|
540
|
+
return materializeEvents()
|
414
541
|
}
|
415
|
-
}
|
416
|
-
|
542
|
+
} catch (e: any) {
|
543
|
+
console.error(e)
|
544
|
+
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() })
|
545
|
+
throw e
|
546
|
+
} finally {
|
547
|
+
span.end()
|
548
|
+
}
|
549
|
+
})()
|
417
550
|
|
418
|
-
const tablesToUpdate = [] as [Ref<null,
|
551
|
+
const tablesToUpdate = [] as [Ref<null, ReactivityGraphContext, RefreshReason>, null][]
|
419
552
|
for (const tableName of writeTables) {
|
420
553
|
const tableRef = this.tableRefs[tableName]
|
421
554
|
assertNever(tableRef !== undefined, `No table ref found for ${tableName}`)
|
@@ -423,8 +556,8 @@ export class Store<
|
|
423
556
|
}
|
424
557
|
|
425
558
|
const debugRefreshReason = {
|
426
|
-
_tag: '
|
427
|
-
|
559
|
+
_tag: 'commit' as const,
|
560
|
+
events,
|
428
561
|
writeTables: Array.from(writeTables),
|
429
562
|
}
|
430
563
|
|
@@ -444,10 +577,10 @@ export class Store<
|
|
444
577
|
},
|
445
578
|
)
|
446
579
|
}
|
447
|
-
// #endregion
|
580
|
+
// #endregion commit
|
448
581
|
|
449
582
|
/**
|
450
|
-
* This can be used in combination with `skipRefresh` when
|
583
|
+
* This can be used in combination with `skipRefresh` when committing events.
|
451
584
|
* We might need a better solution for this. Let's see.
|
452
585
|
*/
|
453
586
|
manualRefresh = (options?: { label?: string }) => {
|
@@ -455,7 +588,7 @@ export class Store<
|
|
455
588
|
this.otel.tracer.startActiveSpan(
|
456
589
|
'LiveStore:manualRefresh',
|
457
590
|
{ attributes: { 'livestore.manualRefreshLabel': label } },
|
458
|
-
this.otel.
|
591
|
+
this.otel.commitsSpanContext,
|
459
592
|
(span) => {
|
460
593
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
461
594
|
this.reactivityGraph.runDeferredEffects({ otelContext })
|
@@ -464,119 +597,57 @@ export class Store<
|
|
464
597
|
)
|
465
598
|
}
|
466
599
|
|
467
|
-
// #region mutateWithoutRefresh
|
468
600
|
/**
|
469
|
-
*
|
470
|
-
*
|
471
|
-
* This is
|
472
|
-
* the caller must refresh queries after calling this method.
|
601
|
+
* Shuts down the store and closes the client session.
|
602
|
+
*
|
603
|
+
* This is called automatically when the store was created using the React or Effect API.
|
473
604
|
*/
|
474
|
-
|
475
|
-
|
476
|
-
// options: {
|
477
|
-
// otelContext: otel.Context
|
478
|
-
// },
|
479
|
-
// ): { writeTables: ReadonlySet<string>; durationMs: number } => {
|
480
|
-
// // const mutationDef =
|
481
|
-
// // this.schema.mutations.get(mutationEventDecoded.mutation) ??
|
482
|
-
// // shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
483
|
-
|
484
|
-
// // // const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = isPartialMutationEvent(mutationEventDecoded_)
|
485
|
-
// // // ? { ...mutationEventDecoded_, ...nextMutationEventId() }
|
486
|
-
// // // : mutationEventDecoded_
|
487
|
-
|
488
|
-
// // // NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
|
489
|
-
// // // e.g. the row-query functionality
|
490
|
-
// // if (Predicate.hasProperty(mutationEventDecoded, 'id')) {
|
491
|
-
// // if (MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id))) {
|
492
|
-
// // // NOTE this data should never be used
|
493
|
-
// // return { writeTables: new Set(), durationMs: 0 }
|
494
|
-
// // } else {
|
495
|
-
// // MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
496
|
-
// // }
|
497
|
-
// // }
|
498
|
-
|
499
|
-
// const { otelContext } = options
|
500
|
-
|
501
|
-
// return this.otel.tracer.startActiveSpan(
|
502
|
-
// 'LiveStore:mutateWithoutRefresh',
|
503
|
-
// {
|
504
|
-
// attributes: {
|
505
|
-
// 'livestore.mutation': mutationEventDecoded.mutation,
|
506
|
-
// // TODO(performance) add flag to disable this
|
507
|
-
// 'livestore.args': JSON.stringify(mutationEventDecoded.args, null, 2),
|
508
|
-
// },
|
509
|
-
// },
|
510
|
-
// otelContext,
|
511
|
-
// (span) => {
|
512
|
-
// const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
513
|
-
|
514
|
-
// const allWriteTables = new Set<string>()
|
515
|
-
// let durationMsTotal = 0
|
516
|
-
|
517
|
-
// replaceSessionIdSymbol(mutationEventDecoded.args, this.clientSession.coordinator.sessionId)
|
518
|
-
|
519
|
-
// const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
520
|
-
|
521
|
-
// for (const {
|
522
|
-
// statementSql,
|
523
|
-
// bindValues,
|
524
|
-
// writeTables = this.syncDbWrapper.getTablesUsed(statementSql),
|
525
|
-
// } of execArgsArr) {
|
526
|
-
// const { durationMs } = this.syncDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
|
527
|
-
|
528
|
-
// durationMsTotal += durationMs
|
529
|
-
// writeTables.forEach((table) => allWriteTables.add(table))
|
530
|
-
// }
|
531
|
-
|
532
|
-
// span.end()
|
533
|
-
|
534
|
-
// return { writeTables: allWriteTables, durationMs: durationMsTotal }
|
535
|
-
// },
|
536
|
-
// )
|
537
|
-
// }
|
538
|
-
// #endregion mutateWithoutRefresh
|
539
|
-
|
540
|
-
private makeTableRef = (tableName: string) =>
|
541
|
-
this.reactivityGraph.makeRef(null, {
|
542
|
-
equal: () => false,
|
543
|
-
label: `tableRef:${tableName}`,
|
544
|
-
meta: { liveStoreRefType: 'table' },
|
545
|
-
})
|
605
|
+
shutdown = (cause?: Cause.Cause<UnexpectedError>) =>
|
606
|
+
this.clientSession.shutdown(cause ?? Cause.fail(IntentionalShutdownCause.make({ reason: 'manual' })))
|
546
607
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
}
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
608
|
+
/**
|
609
|
+
* Helper methods useful during development
|
610
|
+
*
|
611
|
+
* @internal
|
612
|
+
*/
|
613
|
+
_dev = {
|
614
|
+
downloadDb: (source: 'local' | 'leader' = 'local') => {
|
615
|
+
Effect.gen(this, function* () {
|
616
|
+
const data = source === 'local' ? this.sqliteDbWrapper.export() : yield* this.clientSession.leaderThread.export
|
617
|
+
downloadBlob(data, `livestore-${Date.now()}.db`)
|
618
|
+
}).pipe(this.runEffectFork)
|
619
|
+
},
|
620
|
+
|
621
|
+
downloadEventlogDb: () => {
|
622
|
+
Effect.gen(this, function* () {
|
623
|
+
const data = yield* this.clientSession.leaderThread.getEventlogData
|
624
|
+
downloadBlob(data, `livestore-eventlog-${Date.now()}.db`)
|
625
|
+
}).pipe(this.runEffectFork)
|
626
|
+
},
|
627
|
+
|
628
|
+
hardReset: (mode: 'all-data' | 'only-app-db' = 'all-data') => {
|
629
|
+
Effect.gen(this, function* () {
|
630
|
+
const clientId = this.clientSession.clientId
|
631
|
+
yield* this.clientSession.leaderThread.sendDevtoolsMessage(
|
632
|
+
Devtools.Leader.ResetAllData.Request.make({ liveStoreVersion, mode, requestId: nanoid(), clientId }),
|
633
|
+
)
|
634
|
+
}).pipe(this.runEffectFork)
|
635
|
+
},
|
636
|
+
|
637
|
+
syncStates: () => {
|
638
|
+
Effect.gen(this, function* () {
|
639
|
+
const session = yield* this.syncProcessor.syncState
|
640
|
+
console.log('Session sync state:', session.toJSON())
|
641
|
+
const leader = yield* this.clientSession.leaderThread.getSyncState
|
642
|
+
console.log('Leader sync state:', leader.toJSON())
|
643
|
+
}).pipe(this.runEffectFork)
|
644
|
+
},
|
645
|
+
|
646
|
+
version: liveStoreVersion,
|
647
|
+
|
648
|
+
otel: {
|
649
|
+
rootSpanContext: () => otel.trace.getSpan(this.otel.rootSpanContext)?.spanContext(),
|
650
|
+
},
|
580
651
|
}
|
581
652
|
|
582
653
|
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
@@ -586,41 +657,46 @@ export class Store<
|
|
586
657
|
})
|
587
658
|
|
588
659
|
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, Scope.Scope>) =>
|
589
|
-
effect.pipe(
|
660
|
+
effect.pipe(
|
661
|
+
Effect.forkIn(this.effectContext.lifetimeScope),
|
662
|
+
Effect.tapCauseLogPretty,
|
663
|
+
Runtime.runFork(this.effectContext.runtime),
|
664
|
+
)
|
590
665
|
|
591
|
-
private
|
592
|
-
|
593
|
-
|
666
|
+
private getCommitArgs = (
|
667
|
+
firstEventOrTxnFnOrOptions: any,
|
668
|
+
restEvents: any[],
|
594
669
|
): {
|
595
|
-
|
596
|
-
options:
|
670
|
+
events: LiveStoreEvent.PartialForSchema<TSchema>[]
|
671
|
+
options: StoreCommitOptions | undefined
|
597
672
|
} => {
|
598
|
-
let
|
599
|
-
let options:
|
673
|
+
let events: LiveStoreEvent.PartialForSchema<TSchema>[]
|
674
|
+
let options: StoreCommitOptions | undefined
|
600
675
|
|
601
|
-
if (typeof
|
676
|
+
if (typeof firstEventOrTxnFnOrOptions === 'function') {
|
602
677
|
// TODO ensure that function is synchronous and isn't called in a async way (also write tests for this)
|
603
|
-
|
678
|
+
events = firstEventOrTxnFnOrOptions((arg: any) => events.push(arg))
|
604
679
|
} else if (
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
680
|
+
firstEventOrTxnFnOrOptions?.label !== undefined ||
|
681
|
+
firstEventOrTxnFnOrOptions?.skipRefresh !== undefined ||
|
682
|
+
firstEventOrTxnFnOrOptions?.otelContext !== undefined ||
|
683
|
+
firstEventOrTxnFnOrOptions?.spanLinks !== undefined
|
609
684
|
) {
|
610
|
-
options =
|
611
|
-
|
612
|
-
} else if (
|
613
|
-
// When `
|
614
|
-
|
685
|
+
options = firstEventOrTxnFnOrOptions
|
686
|
+
events = restEvents
|
687
|
+
} else if (firstEventOrTxnFnOrOptions === undefined) {
|
688
|
+
// When `commit` is called with no arguments (which sometimes happens when dynamically filtering events)
|
689
|
+
events = []
|
615
690
|
} else {
|
616
|
-
|
691
|
+
events = [firstEventOrTxnFnOrOptions, ...restEvents]
|
617
692
|
}
|
618
693
|
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
694
|
+
// for (const event of events) {
|
695
|
+
// if (event.args.id === SessionIdSymbol) {
|
696
|
+
// event.args.id = this.clientSession.sessionId
|
697
|
+
// }
|
698
|
+
// }
|
623
699
|
|
624
|
-
return {
|
700
|
+
return { events, options }
|
625
701
|
}
|
626
702
|
}
|