@livestore/livestore 0.0.58-dev.0 → 0.0.58-dev.10
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/README.md +1 -117
- package/dist/.tsbuildinfo +1 -1
- package/dist/effect/LiveStore.d.ts +3 -3
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +2 -1
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +1 -1
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +1 -1
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +4 -4
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.js +2 -2
- package/dist/reactiveQueries/sql.test.js.map +1 -1
- package/dist/row-query.d.ts +3 -2
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +18 -10
- package/dist/row-query.js.map +1 -1
- package/dist/store-devtools.d.ts +2 -2
- package/dist/store-devtools.d.ts.map +1 -1
- package/dist/store-devtools.js +3 -3
- package/dist/store-devtools.js.map +1 -1
- package/dist/store.d.ts +23 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +90 -59
- package/dist/store.js.map +1 -1
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +1 -0
- package/dist/utils/dev.js.map +1 -1
- package/dist/{react/utils → utils}/stack-info.d.ts +1 -2
- package/dist/utils/stack-info.d.ts.map +1 -0
- package/dist/{react/utils → utils}/stack-info.js +1 -9
- package/dist/utils/stack-info.js.map +1 -0
- package/dist/utils/stack-info.test.d.ts.map +1 -0
- package/dist/{__tests__/react/utils → utils}/stack-info.test.js +1 -1
- package/dist/utils/stack-info.test.js.map +1 -0
- package/dist/utils/tests/fixture.d.ts +259 -0
- package/dist/utils/tests/fixture.d.ts.map +1 -0
- package/dist/utils/tests/fixture.js +33 -0
- package/dist/utils/tests/fixture.js.map +1 -0
- package/dist/utils/tests/mod.d.ts +3 -0
- package/dist/utils/tests/mod.d.ts.map +1 -0
- package/dist/utils/tests/mod.js +3 -0
- package/dist/utils/tests/mod.js.map +1 -0
- package/dist/utils/tests/otel.d.ts.map +1 -0
- package/dist/utils/tests/otel.js.map +1 -0
- package/package.json +17 -24
- package/src/ambient.d.ts +3 -0
- package/src/effect/LiveStore.ts +4 -4
- package/src/global-state.ts +5 -1
- package/src/index.ts +17 -4
- package/src/reactiveQueries/base-class.ts +1 -1
- package/src/reactiveQueries/sql.test.ts +2 -2
- package/src/reactiveQueries/sql.ts +5 -5
- package/src/row-query.ts +36 -16
- package/src/store-devtools.ts +5 -5
- package/src/store.ts +146 -78
- package/src/utils/dev.ts +1 -0
- package/src/{__tests__/react/utils → utils}/stack-info.test.ts +1 -1
- package/src/{react/utils → utils}/stack-info.ts +2 -12
- package/src/utils/tests/fixture.ts +77 -0
- package/src/utils/tests/mod.ts +2 -0
- package/tsconfig.json +1 -2
- package/vitest.config.js +0 -8
- package/dist/__tests__/react/fixture.d.ts +0 -461
- package/dist/__tests__/react/fixture.d.ts.map +0 -1
- package/dist/__tests__/react/fixture.js +0 -68
- package/dist/__tests__/react/fixture.js.map +0 -1
- package/dist/__tests__/react/utils/otel.d.ts.map +0 -1
- package/dist/__tests__/react/utils/otel.js.map +0 -1
- package/dist/__tests__/react/utils/stack-info.test.d.ts.map +0 -1
- package/dist/__tests__/react/utils/stack-info.test.js.map +0 -1
- package/dist/react/LiveStoreContext.d.ts +0 -7
- package/dist/react/LiveStoreContext.d.ts.map +0 -1
- package/dist/react/LiveStoreContext.js +0 -13
- package/dist/react/LiveStoreContext.js.map +0 -1
- package/dist/react/LiveStoreProvider.d.ts +0 -47
- package/dist/react/LiveStoreProvider.d.ts.map +0 -1
- package/dist/react/LiveStoreProvider.js +0 -169
- package/dist/react/LiveStoreProvider.js.map +0 -1
- package/dist/react/LiveStoreProvider.test.d.ts +0 -2
- package/dist/react/LiveStoreProvider.test.d.ts.map +0 -1
- package/dist/react/LiveStoreProvider.test.js +0 -62
- package/dist/react/LiveStoreProvider.test.js.map +0 -1
- package/dist/react/components/LiveList.d.ts +0 -21
- package/dist/react/components/LiveList.d.ts.map +0 -1
- package/dist/react/components/LiveList.js +0 -31
- package/dist/react/components/LiveList.js.map +0 -1
- package/dist/react/index.d.ts +0 -11
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -10
- package/dist/react/index.js.map +0 -1
- package/dist/react/useAtom.d.ts +0 -10
- package/dist/react/useAtom.d.ts.map +0 -1
- package/dist/react/useAtom.js +0 -37
- package/dist/react/useAtom.js.map +0 -1
- package/dist/react/useLocalId.d.ts +0 -10
- package/dist/react/useLocalId.d.ts.map +0 -1
- package/dist/react/useLocalId.js +0 -21
- package/dist/react/useLocalId.js.map +0 -1
- package/dist/react/useQuery.d.ts +0 -9
- package/dist/react/useQuery.d.ts.map +0 -1
- package/dist/react/useQuery.js +0 -69
- package/dist/react/useQuery.js.map +0 -1
- package/dist/react/useQuery.test.d.ts +0 -2
- package/dist/react/useQuery.test.d.ts.map +0 -1
- package/dist/react/useQuery.test.js +0 -51
- package/dist/react/useQuery.test.js.map +0 -1
- package/dist/react/useRow.d.ts +0 -46
- package/dist/react/useRow.d.ts.map +0 -1
- package/dist/react/useRow.js +0 -94
- package/dist/react/useRow.js.map +0 -1
- package/dist/react/useRow.test.d.ts +0 -2
- package/dist/react/useRow.test.d.ts.map +0 -1
- package/dist/react/useRow.test.js +0 -562
- package/dist/react/useRow.test.js.map +0 -1
- package/dist/react/useTemporaryQuery.d.ts +0 -22
- package/dist/react/useTemporaryQuery.d.ts.map +0 -1
- package/dist/react/useTemporaryQuery.js +0 -70
- package/dist/react/useTemporaryQuery.js.map +0 -1
- package/dist/react/useTemporaryQuery.test.d.ts +0 -2
- package/dist/react/useTemporaryQuery.test.d.ts.map +0 -1
- package/dist/react/useTemporaryQuery.test.js +0 -37
- package/dist/react/useTemporaryQuery.test.js.map +0 -1
- package/dist/react/utils/stack-info.d.ts.map +0 -1
- package/dist/react/utils/stack-info.js.map +0 -1
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts +0 -13
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +0 -1
- package/dist/react/utils/useStateRefWithReactiveInput.js +0 -38
- package/dist/react/utils/useStateRefWithReactiveInput.js.map +0 -1
- package/src/__tests__/react/fixture.tsx +0 -126
- package/src/react/LiveStoreContext.ts +0 -20
- package/src/react/LiveStoreProvider.test.tsx +0 -109
- package/src/react/LiveStoreProvider.tsx +0 -289
- package/src/react/components/LiveList.tsx +0 -84
- package/src/react/index.ts +0 -19
- package/src/react/useAtom.ts +0 -55
- package/src/react/useLocalId.ts +0 -33
- package/src/react/useQuery.test.tsx +0 -82
- package/src/react/useQuery.ts +0 -105
- package/src/react/useRow.test.tsx +0 -699
- package/src/react/useRow.ts +0 -180
- package/src/react/useTemporaryQuery.test.tsx +0 -56
- package/src/react/useTemporaryQuery.ts +0 -121
- package/src/react/utils/useStateRefWithReactiveInput.ts +0 -51
- /package/dist/{__tests__/react/utils → utils}/stack-info.test.d.ts +0 -0
- /package/dist/{__tests__/react/utils → utils/tests}/otel.d.ts +0 -0
- /package/dist/{__tests__/react/utils → utils/tests}/otel.js +0 -0
- /package/src/{__tests__/react/utils → utils/tests}/otel.ts +0 -0
package/src/store.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
Adapter,
|
|
2
3
|
BootDb,
|
|
3
4
|
BootStatus,
|
|
5
|
+
ClientSession,
|
|
6
|
+
EventId,
|
|
4
7
|
IntentionalShutdownCause,
|
|
5
8
|
ParamsObject,
|
|
6
9
|
PreparedBindValues,
|
|
7
|
-
StoreAdapter,
|
|
8
|
-
StoreAdapterFactory,
|
|
9
10
|
StoreDevtoolsChannel,
|
|
10
11
|
} from '@livestore/common'
|
|
11
12
|
import { getExecArgsFromMutation, prepareBindValues, UnexpectedError } from '@livestore/common'
|
|
12
13
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
isPartialMutationEvent,
|
|
16
|
+
makeMutationEventSchemaMemo,
|
|
17
|
+
SCHEMA_META_TABLE,
|
|
18
|
+
SCHEMA_MUTATIONS_META_TABLE,
|
|
19
|
+
SESSION_CHANGESET_META_TABLE,
|
|
20
|
+
} from '@livestore/common/schema'
|
|
14
21
|
import { assertNever, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
|
|
15
22
|
import {
|
|
16
23
|
Cause,
|
|
24
|
+
Data,
|
|
17
25
|
Deferred,
|
|
18
26
|
Duration,
|
|
19
27
|
Effect,
|
|
@@ -23,6 +31,7 @@ import {
|
|
|
23
31
|
Layer,
|
|
24
32
|
Logger,
|
|
25
33
|
LogLevel,
|
|
34
|
+
MutableHashMap,
|
|
26
35
|
OtelTracer,
|
|
27
36
|
Queue,
|
|
28
37
|
Runtime,
|
|
@@ -34,7 +43,6 @@ import * as otel from '@opentelemetry/api'
|
|
|
34
43
|
import type { GraphQLSchema } from 'graphql'
|
|
35
44
|
|
|
36
45
|
import { globalReactivityGraph } from './global-state.js'
|
|
37
|
-
import type { StackInfo } from './react/utils/stack-info.js'
|
|
38
46
|
import type { DebugRefreshReasonBase, Ref } from './reactive.js'
|
|
39
47
|
import type { LiveQuery, QueryContext, ReactivityGraph } from './reactiveQueries/base-class.js'
|
|
40
48
|
import { connectDevtoolsToStore } from './store-devtools.js'
|
|
@@ -42,6 +50,7 @@ import { SynchronousDatabaseWrapper } from './SynchronousDatabaseWrapper.js'
|
|
|
42
50
|
import { ReferenceCountedSet } from './utils/data-structures.js'
|
|
43
51
|
import { downloadBlob } from './utils/dev.js'
|
|
44
52
|
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
53
|
+
import type { StackInfo } from './utils/stack-info.js'
|
|
45
54
|
|
|
46
55
|
export type BaseGraphQLContext = {
|
|
47
56
|
queriedTables: Set<string>
|
|
@@ -51,7 +60,7 @@ export type BaseGraphQLContext = {
|
|
|
51
60
|
|
|
52
61
|
export type GraphQLOptions<TContext> = {
|
|
53
62
|
schema: GraphQLSchema
|
|
54
|
-
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer) => TContext
|
|
63
|
+
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
export type OtelOptions = {
|
|
@@ -63,7 +72,7 @@ export type StoreOptions<
|
|
|
63
72
|
TGraphQLContext extends BaseGraphQLContext,
|
|
64
73
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
65
74
|
> = {
|
|
66
|
-
|
|
75
|
+
clientSession: ClientSession
|
|
67
76
|
schema: TSchema
|
|
68
77
|
storeId: string
|
|
69
78
|
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
@@ -74,8 +83,8 @@ export type StoreOptions<
|
|
|
74
83
|
fiberSet: FiberSet.FiberSet
|
|
75
84
|
runtime: Runtime.Runtime<Scope.Scope>
|
|
76
85
|
batchUpdates: (runUpdates: () => void) => void
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
currentMutationEventIdRef: { current: EventId }
|
|
87
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
export type RefreshReason =
|
|
@@ -89,6 +98,7 @@ export type RefreshReason =
|
|
|
89
98
|
writeTables: ReadonlyArray<string>
|
|
90
99
|
}
|
|
91
100
|
| {
|
|
101
|
+
// TODO rename to a more appropriate name which is framework-agnostic
|
|
92
102
|
_tag: 'react'
|
|
93
103
|
api: string
|
|
94
104
|
label?: string
|
|
@@ -109,9 +119,6 @@ export type StoreOtel = {
|
|
|
109
119
|
queriesSpanContext: otel.Context
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
let storeCount = 0
|
|
113
|
-
const uniqueStoreId = () => `store-${++storeCount}`
|
|
114
|
-
|
|
115
122
|
export type StoreMutateOptions = {
|
|
116
123
|
label?: string
|
|
117
124
|
skipRefresh?: boolean
|
|
@@ -125,7 +132,9 @@ export type StoreMutateOptions = {
|
|
|
125
132
|
persisted?: boolean
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
// eslint-disable-next-line unicorn/prefer-global-this
|
|
136
|
+
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
|
137
|
+
// eslint-disable-next-line unicorn/prefer-global-this
|
|
129
138
|
window.__debugDownloadBlob = downloadBlob
|
|
130
139
|
}
|
|
131
140
|
|
|
@@ -133,10 +142,10 @@ export class Store<
|
|
|
133
142
|
TGraphQLContext extends BaseGraphQLContext = BaseGraphQLContext,
|
|
134
143
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
135
144
|
> extends Inspectable.Class {
|
|
136
|
-
|
|
145
|
+
readonly storeId: string
|
|
137
146
|
reactivityGraph: ReactivityGraph
|
|
138
147
|
syncDbWrapper: SynchronousDatabaseWrapper
|
|
139
|
-
|
|
148
|
+
clientSession: ClientSession
|
|
140
149
|
schema: LiveStoreSchema
|
|
141
150
|
graphQLSchema?: GraphQLSchema
|
|
142
151
|
graphQLContext?: TGraphQLContext
|
|
@@ -150,32 +159,39 @@ export class Store<
|
|
|
150
159
|
private fiberSet: FiberSet.FiberSet
|
|
151
160
|
private runtime: Runtime.Runtime<Scope.Scope>
|
|
152
161
|
|
|
153
|
-
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
154
|
-
private __processedMutationIds
|
|
155
|
-
private __processedMutationWithoutRefreshIds = new Set<string>()
|
|
156
|
-
|
|
157
162
|
/** RC-based set to see which queries are currently subscribed to */
|
|
158
163
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
159
164
|
|
|
165
|
+
// NOTE this is currently exposed for the Devtools databrowser to emit mutation events
|
|
160
166
|
readonly __mutationEventSchema
|
|
161
167
|
|
|
168
|
+
private currentMutationEventIdRef
|
|
169
|
+
private unsyncedMutationEvents
|
|
170
|
+
|
|
162
171
|
// #region constructor
|
|
163
172
|
private constructor({
|
|
164
|
-
|
|
173
|
+
clientSession,
|
|
165
174
|
schema,
|
|
166
175
|
graphQLOptions,
|
|
167
176
|
reactivityGraph,
|
|
168
177
|
otelOptions,
|
|
169
178
|
disableDevtools,
|
|
170
179
|
batchUpdates,
|
|
171
|
-
|
|
180
|
+
currentMutationEventIdRef,
|
|
181
|
+
unsyncedMutationEvents,
|
|
182
|
+
storeId,
|
|
172
183
|
fiberSet,
|
|
173
184
|
runtime,
|
|
174
185
|
}: StoreOptions<TGraphQLContext, TSchema>) {
|
|
175
186
|
super()
|
|
176
187
|
|
|
177
|
-
this.
|
|
178
|
-
|
|
188
|
+
this.storeId = storeId
|
|
189
|
+
|
|
190
|
+
this.currentMutationEventIdRef = currentMutationEventIdRef
|
|
191
|
+
this.unsyncedMutationEvents = unsyncedMutationEvents
|
|
192
|
+
|
|
193
|
+
this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: clientSession.syncDb })
|
|
194
|
+
this.clientSession = clientSession
|
|
179
195
|
this.schema = schema
|
|
180
196
|
|
|
181
197
|
this.fiberSet = fiberSet
|
|
@@ -184,9 +200,6 @@ export class Store<
|
|
|
184
200
|
// TODO refactor
|
|
185
201
|
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
186
202
|
|
|
187
|
-
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
188
|
-
this.__processedMutationIds = __processedMutationIds
|
|
189
|
-
|
|
190
203
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
191
204
|
this.tableRefs = {}
|
|
192
205
|
this.activeQueries = new ReferenceCountedSet()
|
|
@@ -222,7 +235,7 @@ export class Store<
|
|
|
222
235
|
isRunningInDevtools
|
|
223
236
|
? this.schema.tables.keys()
|
|
224
237
|
: Array.from(this.schema.tables.keys()).filter(
|
|
225
|
-
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE,
|
|
238
|
+
(_) => _ !== SCHEMA_META_TABLE && _ !== SCHEMA_MUTATIONS_META_TABLE && _ !== SESSION_CHANGESET_META_TABLE,
|
|
226
239
|
),
|
|
227
240
|
)
|
|
228
241
|
const existingTableRefs = new Map(
|
|
@@ -236,14 +249,20 @@ export class Store<
|
|
|
236
249
|
|
|
237
250
|
if (graphQLOptions) {
|
|
238
251
|
this.graphQLSchema = graphQLOptions.schema
|
|
239
|
-
this.graphQLContext = graphQLOptions.makeContext(
|
|
252
|
+
this.graphQLContext = graphQLOptions.makeContext(
|
|
253
|
+
this.syncDbWrapper,
|
|
254
|
+
this.otel.tracer,
|
|
255
|
+
clientSession.coordinator.sessionId,
|
|
256
|
+
)
|
|
240
257
|
}
|
|
241
258
|
|
|
242
259
|
Effect.gen(this, function* () {
|
|
243
|
-
yield* this.
|
|
244
|
-
Stream.
|
|
245
|
-
|
|
246
|
-
|
|
260
|
+
yield* this.clientSession.coordinator.syncMutations.pipe(
|
|
261
|
+
Stream.tapChunk((mutationsEventsDecodedChunk) =>
|
|
262
|
+
Effect.sync(() => {
|
|
263
|
+
this.mutate({ wasSyncMessage: true }, ...mutationsEventsDecodedChunk)
|
|
264
|
+
}),
|
|
265
|
+
),
|
|
247
266
|
Stream.runDrain,
|
|
248
267
|
Effect.interruptible,
|
|
249
268
|
Effect.withSpan('LiveStore:syncMutations'),
|
|
@@ -282,6 +301,10 @@ export class Store<
|
|
|
282
301
|
})
|
|
283
302
|
}
|
|
284
303
|
|
|
304
|
+
get sessionId(): string {
|
|
305
|
+
return this.clientSession.coordinator.sessionId
|
|
306
|
+
}
|
|
307
|
+
|
|
285
308
|
/**
|
|
286
309
|
* Subscribe to the results of a query
|
|
287
310
|
* Returns a function to cancel the subscription.
|
|
@@ -297,7 +320,7 @@ export class Store<
|
|
|
297
320
|
{ attributes: { label: options?.label, queryLabel: query$.label } },
|
|
298
321
|
options?.otelContext ?? this.otel.queriesSpanContext,
|
|
299
322
|
(span) => {
|
|
300
|
-
// console.
|
|
323
|
+
// console.debug('store sub', query$.id, query$.label)
|
|
301
324
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
302
325
|
|
|
303
326
|
const label = `subscribe:${options?.label}`
|
|
@@ -311,7 +334,7 @@ export class Store<
|
|
|
311
334
|
}
|
|
312
335
|
|
|
313
336
|
const unsubscribe = () => {
|
|
314
|
-
// console.
|
|
337
|
+
// console.debug('store unsub', query$.id, query$.label)
|
|
315
338
|
try {
|
|
316
339
|
this.reactivityGraph.destroyNode(effect)
|
|
317
340
|
this.activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
@@ -327,17 +350,21 @@ export class Store<
|
|
|
327
350
|
|
|
328
351
|
// #region mutate
|
|
329
352
|
mutate: {
|
|
330
|
-
<const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
353
|
+
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(...list: TMutationArg): void
|
|
331
354
|
(
|
|
332
|
-
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
355
|
+
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
356
|
+
...list: TMutationArg
|
|
357
|
+
) => void,
|
|
333
358
|
): void
|
|
334
|
-
<const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
359
|
+
<const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
335
360
|
options: StoreMutateOptions,
|
|
336
361
|
...list: TMutationArg
|
|
337
362
|
): void
|
|
338
363
|
(
|
|
339
364
|
options: StoreMutateOptions,
|
|
340
|
-
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.
|
|
365
|
+
txn: <const TMutationArg extends ReadonlyArray<MutationEvent.PartialForSchema<TSchema>>>(
|
|
366
|
+
...list: TMutationArg
|
|
367
|
+
) => void,
|
|
341
368
|
): void
|
|
342
369
|
} = (firstMutationOrTxnFnOrOptions: any, ...restMutations: any[]) => {
|
|
343
370
|
let mutationsEvents: MutationEvent.ForSchema<TSchema>[]
|
|
@@ -361,16 +388,14 @@ export class Store<
|
|
|
361
388
|
mutationsEvents = [firstMutationOrTxnFnOrOptions, ...restMutations]
|
|
362
389
|
}
|
|
363
390
|
|
|
364
|
-
mutationsEvents = mutationsEvents.filter(
|
|
391
|
+
mutationsEvents = mutationsEvents.filter(
|
|
392
|
+
(_) => _.id === undefined || !MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(_.id)),
|
|
393
|
+
)
|
|
365
394
|
|
|
366
395
|
if (mutationsEvents.length === 0) {
|
|
367
396
|
return
|
|
368
397
|
}
|
|
369
398
|
|
|
370
|
-
for (const mutationEvent of mutationsEvents) {
|
|
371
|
-
this.__processedMutationIds.add(mutationEvent.id)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
399
|
const label = options?.label ?? 'mutate'
|
|
375
400
|
const skipRefresh = options?.skipRefresh ?? false
|
|
376
401
|
const wasSyncMessage = options?.wasSyncMessage ?? false
|
|
@@ -380,12 +405,12 @@ export class Store<
|
|
|
380
405
|
mutationsSpan.addEvent('mutate')
|
|
381
406
|
|
|
382
407
|
// console.group('LiveStore.mutate', { skipRefresh, wasSyncMessage, label })
|
|
383
|
-
// mutationsEvents.forEach((_) => console.
|
|
408
|
+
// mutationsEvents.forEach((_) => console.debug(_.mutation, _.id, _.args))
|
|
384
409
|
// console.groupEnd()
|
|
385
410
|
|
|
386
411
|
let durationMs: number
|
|
387
412
|
|
|
388
|
-
|
|
413
|
+
const res = this.otel.tracer.startActiveSpan(
|
|
389
414
|
'LiveStore:mutate',
|
|
390
415
|
{ attributes: { 'livestore.mutateLabel': label } },
|
|
391
416
|
this.otel.mutationsSpanContext,
|
|
@@ -465,6 +490,16 @@ export class Store<
|
|
|
465
490
|
return { durationMs }
|
|
466
491
|
},
|
|
467
492
|
)
|
|
493
|
+
|
|
494
|
+
// NOTE we need to add the mutation events to the unsynced mutation events map only after running the code above
|
|
495
|
+
// so the short-circuiting in `mutateWithoutRefresh` doesn't kick in for those events
|
|
496
|
+
for (const mutationEvent of mutationsEvents) {
|
|
497
|
+
if (mutationEvent.id !== undefined) {
|
|
498
|
+
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEvent.id), mutationEvent)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return res
|
|
468
503
|
}
|
|
469
504
|
|
|
470
505
|
/**
|
|
@@ -492,19 +527,39 @@ export class Store<
|
|
|
492
527
|
* the caller must refresh queries after calling this method.
|
|
493
528
|
*/
|
|
494
529
|
mutateWithoutRefresh = (
|
|
495
|
-
|
|
530
|
+
mutationEventDecoded_: MutationEvent.ForSchema<TSchema> | MutationEvent.PartialForSchema<TSchema>,
|
|
496
531
|
options: {
|
|
497
532
|
otelContext: otel.Context
|
|
533
|
+
// TODO adjust `skip-persist` with new rebase sync strategy
|
|
498
534
|
coordinatorMode: 'default' | 'skip-coordinator' | 'skip-persist'
|
|
499
535
|
},
|
|
500
536
|
): { writeTables: ReadonlySet<string>; durationMs: number } => {
|
|
537
|
+
const mutationDef =
|
|
538
|
+
this.schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
539
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
540
|
+
|
|
541
|
+
// Needs to happen only for partial mutation events (thus a function)
|
|
542
|
+
const nextMutationEventId = () => {
|
|
543
|
+
const { id, parentId } = this.clientSession.coordinator
|
|
544
|
+
.nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
|
|
545
|
+
.pipe(Effect.runSync)
|
|
546
|
+
|
|
547
|
+
this.currentMutationEventIdRef.current = id
|
|
548
|
+
|
|
549
|
+
return { id, parentId }
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const mutationEventDecoded: MutationEvent.ForSchema<TSchema> = isPartialMutationEvent(mutationEventDecoded_)
|
|
553
|
+
? { ...mutationEventDecoded_, ...nextMutationEventId() }
|
|
554
|
+
: mutationEventDecoded_
|
|
555
|
+
|
|
501
556
|
// NOTE we also need this temporary workaround here since some code-paths use `mutateWithoutRefresh` directly
|
|
502
557
|
// e.g. the row-query functionality
|
|
503
|
-
if (this.
|
|
558
|
+
if (MutableHashMap.has(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id))) {
|
|
504
559
|
// NOTE this data should never be used
|
|
505
560
|
return { writeTables: new Set(), durationMs: 0 }
|
|
506
561
|
} else {
|
|
507
|
-
this.
|
|
562
|
+
MutableHashMap.set(this.unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
508
563
|
}
|
|
509
564
|
|
|
510
565
|
const { otelContext, coordinatorMode = 'default' } = options
|
|
@@ -524,10 +579,6 @@ export class Store<
|
|
|
524
579
|
const allWriteTables = new Set<string>()
|
|
525
580
|
let durationMsTotal = 0
|
|
526
581
|
|
|
527
|
-
const mutationDef =
|
|
528
|
-
this.schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
529
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
|
|
530
|
-
|
|
531
582
|
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
532
583
|
|
|
533
584
|
for (const {
|
|
@@ -547,9 +598,9 @@ export class Store<
|
|
|
547
598
|
|
|
548
599
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
549
600
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
550
|
-
this.
|
|
601
|
+
this.clientSession.coordinator
|
|
551
602
|
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
552
|
-
.pipe(
|
|
603
|
+
.pipe(this.runEffectFork)
|
|
553
604
|
}
|
|
554
605
|
|
|
555
606
|
// Uncomment to print a list of queries currently registered on the store
|
|
@@ -568,7 +619,7 @@ export class Store<
|
|
|
568
619
|
* This should only be used for framework-internal purposes;
|
|
569
620
|
* all app writes should go through mutate.
|
|
570
621
|
*/
|
|
571
|
-
|
|
622
|
+
__execute = (
|
|
572
623
|
query: string,
|
|
573
624
|
params: ParamsObject = {},
|
|
574
625
|
writeTables?: ReadonlySet<string>,
|
|
@@ -576,10 +627,10 @@ export class Store<
|
|
|
576
627
|
) => {
|
|
577
628
|
this.syncDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext })
|
|
578
629
|
|
|
579
|
-
this.
|
|
630
|
+
this.clientSession.coordinator.execute(query, prepareBindValues(params, query)).pipe(this.runEffectFork)
|
|
580
631
|
}
|
|
581
632
|
|
|
582
|
-
|
|
633
|
+
__select = (query: string, params: ParamsObject = {}) => {
|
|
583
634
|
return this.syncDbWrapper.select(query, { bindValues: prepareBindValues(params, query) })
|
|
584
635
|
}
|
|
585
636
|
|
|
@@ -597,7 +648,7 @@ export class Store<
|
|
|
597
648
|
|
|
598
649
|
__devDownloadMutationLogDb = () =>
|
|
599
650
|
Effect.gen(this, function* () {
|
|
600
|
-
const data = yield* this.
|
|
651
|
+
const data = yield* this.clientSession.coordinator.getMutationLogData
|
|
601
652
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
602
653
|
}).pipe(this.runEffectFork)
|
|
603
654
|
|
|
@@ -615,7 +666,7 @@ export class Store<
|
|
|
615
666
|
|
|
616
667
|
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
617
668
|
schema: TSchema
|
|
618
|
-
adapter:
|
|
669
|
+
adapter: Adapter
|
|
619
670
|
storeId: string
|
|
620
671
|
reactivityGraph?: ReactivityGraph
|
|
621
672
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
@@ -663,7 +714,7 @@ export const createStore = <
|
|
|
663
714
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
664
715
|
>({
|
|
665
716
|
schema,
|
|
666
|
-
adapter
|
|
717
|
+
adapter,
|
|
667
718
|
storeId,
|
|
668
719
|
graphQLOptions,
|
|
669
720
|
otelOptions,
|
|
@@ -734,7 +785,7 @@ export const createStore = <
|
|
|
734
785
|
)
|
|
735
786
|
}).pipe(Effect.withSpan('livestore:shutdown'))
|
|
736
787
|
|
|
737
|
-
const
|
|
788
|
+
const clientSession: ClientSession = yield* adapter({
|
|
738
789
|
schema,
|
|
739
790
|
storeId,
|
|
740
791
|
devtoolsEnabled: disableDevtools !== true,
|
|
@@ -745,7 +796,13 @@ export const createStore = <
|
|
|
745
796
|
|
|
746
797
|
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
747
798
|
|
|
748
|
-
|
|
799
|
+
// TODO get rid of this
|
|
800
|
+
// const __processedMutationIds = new Set<number>()
|
|
801
|
+
|
|
802
|
+
const currentMutationEventIdRef = { current: yield* clientSession.coordinator.getCurrentMutationEventId }
|
|
803
|
+
|
|
804
|
+
// TODO fill up with unsynced mutation events from the coordinator
|
|
805
|
+
const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
|
|
749
806
|
|
|
750
807
|
// TODO consider moving booting into the storage backend
|
|
751
808
|
if (boot !== undefined) {
|
|
@@ -755,57 +812,67 @@ export const createStore = <
|
|
|
755
812
|
const bootDbImpl: BootDb = {
|
|
756
813
|
_tag: 'BootDb',
|
|
757
814
|
execute: (queryStr, bindValues) => {
|
|
758
|
-
const stmt =
|
|
815
|
+
const stmt = clientSession.syncDb.prepare(queryStr)
|
|
759
816
|
stmt.execute(bindValues)
|
|
760
817
|
|
|
761
818
|
if (isInTxn === true) {
|
|
762
819
|
txnExecuteStmnts.push([queryStr, bindValues])
|
|
763
820
|
} else {
|
|
764
|
-
|
|
821
|
+
clientSession.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
765
822
|
}
|
|
766
823
|
},
|
|
767
824
|
mutate: (...list) => {
|
|
768
|
-
for (const
|
|
825
|
+
for (const mutationEventDecoded_ of list) {
|
|
769
826
|
const mutationDef =
|
|
770
|
-
schema.mutations.get(
|
|
771
|
-
shouldNeverHappen(`Unknown mutation type: ${
|
|
827
|
+
schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
828
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
829
|
+
|
|
830
|
+
const { id, parentId } = clientSession.coordinator
|
|
831
|
+
.nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
|
|
832
|
+
.pipe(Effect.runSync)
|
|
833
|
+
|
|
834
|
+
currentMutationEventIdRef.current = id
|
|
835
|
+
|
|
836
|
+
const mutationEventDecoded = { ...mutationEventDecoded_, id, parentId }
|
|
837
|
+
|
|
838
|
+
MutableHashMap.set(unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
772
839
|
|
|
773
|
-
__processedMutationIds.add(mutationEventDecoded.id)
|
|
840
|
+
// __processedMutationIds.add(mutationEventDecoded.id.global)
|
|
774
841
|
|
|
775
842
|
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
776
843
|
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
777
844
|
|
|
778
845
|
for (const { statementSql, bindValues } of execArgsArr) {
|
|
779
|
-
|
|
846
|
+
clientSession.syncDb.execute(statementSql, bindValues)
|
|
780
847
|
}
|
|
781
848
|
|
|
782
849
|
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
|
|
783
850
|
|
|
784
|
-
|
|
851
|
+
clientSession.coordinator
|
|
785
852
|
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
786
853
|
.pipe(runEffectFork)
|
|
787
854
|
}
|
|
788
855
|
},
|
|
789
856
|
select: (queryStr, bindValues) => {
|
|
790
|
-
const stmt =
|
|
857
|
+
const stmt = clientSession.syncDb.prepare(queryStr)
|
|
791
858
|
return stmt.select(bindValues)
|
|
792
859
|
},
|
|
793
860
|
txn: (callback) => {
|
|
794
861
|
try {
|
|
795
862
|
isInTxn = true
|
|
796
|
-
//
|
|
863
|
+
// clientSession.syncDb.execute('BEGIN TRANSACTION', undefined)
|
|
797
864
|
|
|
798
865
|
callback()
|
|
799
866
|
|
|
800
|
-
//
|
|
867
|
+
// clientSession.syncDb.execute('COMMIT', undefined)
|
|
801
868
|
|
|
802
|
-
//
|
|
869
|
+
// clientSession.coordinator.execute('BEGIN', undefined, undefined)
|
|
803
870
|
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
804
|
-
|
|
871
|
+
clientSession.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
805
872
|
}
|
|
806
|
-
//
|
|
873
|
+
// clientSession.coordinator.execute('COMMIT', undefined, undefined)
|
|
807
874
|
} catch (e: any) {
|
|
808
|
-
//
|
|
875
|
+
// clientSession.syncDb.execute('ROLLBACK', undefined)
|
|
809
876
|
throw e
|
|
810
877
|
} finally {
|
|
811
878
|
isInTxn = false
|
|
@@ -822,13 +889,14 @@ export const createStore = <
|
|
|
822
889
|
|
|
823
890
|
const store = Store.createStore<TGraphQLContext, TSchema>(
|
|
824
891
|
{
|
|
825
|
-
|
|
892
|
+
clientSession,
|
|
826
893
|
schema,
|
|
827
894
|
graphQLOptions,
|
|
828
895
|
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
829
896
|
reactivityGraph,
|
|
830
897
|
disableDevtools,
|
|
831
|
-
|
|
898
|
+
currentMutationEventIdRef,
|
|
899
|
+
unsyncedMutationEvents,
|
|
832
900
|
fiberSet,
|
|
833
901
|
runtime,
|
|
834
902
|
batchUpdates: batchUpdates ?? ((run) => run()),
|
package/src/utils/dev.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
export const originalStackLimit = Error.stackTraceLimit
|
|
4
|
-
|
|
5
1
|
export type StackInfo = {
|
|
6
2
|
frames: StackFrame[]
|
|
7
3
|
}
|
|
@@ -54,11 +50,5 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
|
|
|
54
50
|
return { frames }
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
export const
|
|
58
|
-
|
|
59
|
-
Error.stackTraceLimit = 10
|
|
60
|
-
// eslint-disable-next-line unicorn/error-message
|
|
61
|
-
const stack = new Error().stack!
|
|
62
|
-
Error.stackTraceLimit = originalStackLimit
|
|
63
|
-
return extractStackInfoFromStackTrace(stack)
|
|
64
|
-
}, [])
|
|
53
|
+
export const stackInfoToString = (stackInfo: StackInfo): string =>
|
|
54
|
+
stackInfo.frames.map((f) => `${f.name} (${f.filePath})`).join('\n')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FromInputSchema } from '@livestore/common/schema'
|
|
2
|
+
import type { Store } from '@livestore/livestore'
|
|
3
|
+
import {
|
|
4
|
+
createStore,
|
|
5
|
+
DbSchema,
|
|
6
|
+
globalReactivityGraph,
|
|
7
|
+
makeReactivityGraph,
|
|
8
|
+
makeSchema,
|
|
9
|
+
sql,
|
|
10
|
+
} from '@livestore/livestore'
|
|
11
|
+
import { Effect, FiberSet } from '@livestore/utils/effect'
|
|
12
|
+
import { makeInMemoryAdapter } from '@livestore/web'
|
|
13
|
+
import type * as otel from '@opentelemetry/api'
|
|
14
|
+
|
|
15
|
+
export type Todo = {
|
|
16
|
+
id: string
|
|
17
|
+
text: string
|
|
18
|
+
completed: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Filter = 'all' | 'active' | 'completed'
|
|
22
|
+
|
|
23
|
+
export type AppState = {
|
|
24
|
+
newTodoText: string
|
|
25
|
+
filter: Filter
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const todos = DbSchema.table(
|
|
29
|
+
'todos',
|
|
30
|
+
{
|
|
31
|
+
id: DbSchema.text({ primaryKey: true }),
|
|
32
|
+
text: DbSchema.text({ default: '', nullable: false }),
|
|
33
|
+
completed: DbSchema.boolean({ default: false, nullable: false }),
|
|
34
|
+
},
|
|
35
|
+
{ deriveMutations: true, isSingleton: false },
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
export const app = DbSchema.table('app', {
|
|
39
|
+
id: DbSchema.text({ primaryKey: true }),
|
|
40
|
+
newTodoText: DbSchema.text({ default: '', nullable: true }),
|
|
41
|
+
filter: DbSchema.text({ default: 'all', nullable: false }),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export const tables = { todos, app }
|
|
45
|
+
export const schema = makeSchema({ tables })
|
|
46
|
+
|
|
47
|
+
export interface FixtureSchema extends FromInputSchema.DeriveSchema<{ tables: typeof tables }> {}
|
|
48
|
+
|
|
49
|
+
export const makeTodoMvc = ({
|
|
50
|
+
otelTracer,
|
|
51
|
+
otelContext,
|
|
52
|
+
useGlobalReactivityGraph = true,
|
|
53
|
+
}: {
|
|
54
|
+
otelTracer?: otel.Tracer
|
|
55
|
+
otelContext?: otel.Context
|
|
56
|
+
useGlobalReactivityGraph?: boolean
|
|
57
|
+
} = {}) =>
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
const reactivityGraph = useGlobalReactivityGraph ? globalReactivityGraph : makeReactivityGraph()
|
|
60
|
+
|
|
61
|
+
const fiberSet = yield* FiberSet.make()
|
|
62
|
+
|
|
63
|
+
const store: Store<any, FixtureSchema> = yield* createStore({
|
|
64
|
+
schema,
|
|
65
|
+
storeId: 'default',
|
|
66
|
+
boot: (db) => db.execute(sql`INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');`),
|
|
67
|
+
adapter: makeInMemoryAdapter(),
|
|
68
|
+
reactivityGraph,
|
|
69
|
+
otelOptions: {
|
|
70
|
+
tracer: otelTracer,
|
|
71
|
+
rootSpanContext: otelContext,
|
|
72
|
+
},
|
|
73
|
+
fiberSet,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
return { store, reactivityGraph }
|
|
77
|
+
})
|