@livestore/livestore 0.2.0-dev.2 → 0.3.0-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/dist/.tsbuildinfo +1 -1
- package/dist/SynchronousDatabaseWrapper.d.ts +6 -1
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +14 -2
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/__tests__/fixture.d.ts +252 -0
- package/dist/__tests__/fixture.d.ts.map +1 -0
- package/dist/__tests__/fixture.js +18 -0
- package/dist/__tests__/fixture.js.map +1 -0
- package/dist/effect/LiveStore.d.ts +6 -6
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +5 -12
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +28 -23
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +2 -1
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/row-query-utils.js +1 -1
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +12 -10
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +22 -28
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -1
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +41 -19
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +9 -14
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store.d.ts +29 -28
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +147 -160
- package/dist/store/store.js.map +1 -1
- package/dist/store/store.test.d.ts +2 -0
- package/dist/store/store.test.d.ts.map +1 -0
- package/dist/store/store.test.js +27 -0
- package/dist/store/store.test.js.map +1 -0
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +3 -2
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +4 -8
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts +60 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +65 -4
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +12 -12
- package/src/SynchronousDatabaseWrapper.ts +18 -2
- package/src/ambient.d.ts +1 -1
- package/src/effect/LiveStore.ts +11 -20
- package/src/index.ts +1 -1
- package/src/live-queries/__snapshots__/db.test.ts.snap +42 -45
- package/src/live-queries/db.test.ts +2 -1
- package/src/live-queries/db.ts +28 -23
- package/src/row-query-utils.ts +1 -1
- package/src/store/create-store.ts +115 -119
- package/src/store/devtools.ts +48 -22
- package/src/store/store-types.ts +14 -14
- package/src/store/store.ts +188 -224
- package/src/utils/dev.ts +4 -2
- package/src/utils/tests/fixture.ts +4 -9
- package/src/utils/tests/otel.ts +71 -5
- package/dist/live-queries/sql.d.ts +0 -62
- package/dist/live-queries/sql.d.ts.map +0 -1
- package/dist/live-queries/sql.js +0 -175
- package/dist/live-queries/sql.js.map +0 -1
- package/dist/live-queries/sql.test.d.ts +0 -2
- package/dist/live-queries/sql.test.d.ts.map +0 -1
- package/dist/live-queries/sql.test.js +0 -285
- package/dist/live-queries/sql.test.js.map +0 -1
- package/dist/reactiveQueries/base-class.d.ts +0 -64
- package/dist/reactiveQueries/base-class.d.ts.map +0 -1
- package/dist/reactiveQueries/base-class.js +0 -31
- package/dist/reactiveQueries/base-class.js.map +0 -1
- package/dist/reactiveQueries/computed.d.ts +0 -26
- package/dist/reactiveQueries/computed.d.ts.map +0 -1
- package/dist/reactiveQueries/computed.js +0 -38
- package/dist/reactiveQueries/computed.js.map +0 -1
- package/dist/reactiveQueries/graphql.d.ts +0 -49
- package/dist/reactiveQueries/graphql.d.ts.map +0 -1
- package/dist/reactiveQueries/graphql.js +0 -122
- package/dist/reactiveQueries/graphql.js.map +0 -1
- package/dist/reactiveQueries/sql.d.ts +0 -62
- package/dist/reactiveQueries/sql.d.ts.map +0 -1
- package/dist/reactiveQueries/sql.js +0 -175
- package/dist/reactiveQueries/sql.js.map +0 -1
- package/dist/reactiveQueries/sql.test.d.ts +0 -2
- package/dist/reactiveQueries/sql.test.d.ts.map +0 -1
- package/dist/reactiveQueries/sql.test.js +0 -285
- package/dist/reactiveQueries/sql.test.js.map +0 -1
- package/dist/row-query.d.ts +0 -16
- package/dist/row-query.d.ts.map +0 -1
- package/dist/row-query.js +0 -30
- package/dist/row-query.js.map +0 -1
package/src/live-queries/db.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
prepareBindValues,
|
|
6
6
|
QueryBuilderAstSymbol,
|
|
7
7
|
replaceSessionIdSymbol,
|
|
8
|
+
UnexpectedError,
|
|
8
9
|
} from '@livestore/common'
|
|
9
10
|
import { deepEqual, shouldNeverHappen } from '@livestore/utils'
|
|
10
11
|
import { Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
@@ -144,28 +145,32 @@ export class LiveStoreDbQuery<
|
|
|
144
145
|
let queryInputRaw$OrQueryInputRaw: TQueryInputRaw | Thunk<TQueryInputRaw, QueryContext, RefreshReason>
|
|
145
146
|
|
|
146
147
|
const fromQueryBuilder = (qb: QueryBuilder.Any, otelContext: otel.Context | undefined) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
148
|
+
try {
|
|
149
|
+
const qbRes = qb.asSql()
|
|
150
|
+
const schema = getResultSchema(qb) as Schema.Schema<TResultSchema, ReadonlyArray<any>>
|
|
151
|
+
const ast = qb[QueryBuilderAstSymbol]
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
queryInputRaw: {
|
|
155
|
+
query: qbRes.query,
|
|
156
|
+
schema,
|
|
157
|
+
bindValues: qbRes.bindValues,
|
|
158
|
+
queriedTables: new Set([ast.tableDef.sqliteDef.name]),
|
|
159
|
+
queryInfo: ast._tag === 'RowQuery' ? { _tag: 'Row', table: ast.tableDef, id: ast.id } : { _tag: 'None' },
|
|
160
|
+
} satisfies TQueryInputRaw,
|
|
161
|
+
label: ast._tag === 'RowQuery' ? rowQueryLabel(ast.tableDef, ast.id) : qb.toString(),
|
|
162
|
+
execBeforeFirstRun:
|
|
163
|
+
ast._tag === 'RowQuery'
|
|
164
|
+
? makeExecBeforeFirstRun({
|
|
165
|
+
table: ast.tableDef,
|
|
166
|
+
insertValues: ast.insertValues,
|
|
167
|
+
id: ast.id,
|
|
168
|
+
otelContext,
|
|
169
|
+
})
|
|
170
|
+
: undefined,
|
|
171
|
+
}
|
|
172
|
+
} catch (cause) {
|
|
173
|
+
throw new UnexpectedError({ cause, note: `Error building query for ${qb.toString()}`, payload: { qb } })
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -274,7 +279,7 @@ export class LiveStoreDbQuery<
|
|
|
274
279
|
}
|
|
275
280
|
|
|
276
281
|
if (bindValues !== undefined) {
|
|
277
|
-
replaceSessionIdSymbol(bindValues, store.clientSession.
|
|
282
|
+
replaceSessionIdSymbol(bindValues, store.clientSession.sessionId)
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
// Establish a reactive dependency on the tables used in the query
|
package/src/row-query-utils.ts
CHANGED
|
@@ -60,6 +60,6 @@ export const makeExecBeforeFirstRun =
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// NOTE It's important that we only mutate and don't refresh here, as this function is called during a render
|
|
63
|
-
store.
|
|
63
|
+
store.mutate({ otelContext, skipRefresh: true }, table.insert({ id, ...insertValues }))
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -2,21 +2,18 @@ import type {
|
|
|
2
2
|
Adapter,
|
|
3
3
|
BootStatus,
|
|
4
4
|
ClientSession,
|
|
5
|
-
EventId,
|
|
6
5
|
IntentionalShutdownCause,
|
|
7
6
|
StoreDevtoolsChannel,
|
|
8
7
|
} from '@livestore/common'
|
|
9
|
-
import { UnexpectedError } from '@livestore/common'
|
|
10
|
-
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
11
|
-
import {
|
|
8
|
+
import { provideOtel, UnexpectedError } from '@livestore/common'
|
|
9
|
+
import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
10
|
+
import { LS_DEV } from '@livestore/utils'
|
|
11
|
+
import type { Cause } from '@livestore/utils/effect'
|
|
12
12
|
import {
|
|
13
|
-
Cause,
|
|
14
13
|
Deferred,
|
|
15
|
-
Duration,
|
|
16
14
|
Effect,
|
|
17
15
|
Exit,
|
|
18
|
-
|
|
19
|
-
Layer,
|
|
16
|
+
identity,
|
|
20
17
|
Logger,
|
|
21
18
|
LogLevel,
|
|
22
19
|
MutableHashMap,
|
|
@@ -24,29 +21,34 @@ import {
|
|
|
24
21
|
Queue,
|
|
25
22
|
Runtime,
|
|
26
23
|
Scope,
|
|
24
|
+
TaskTracing,
|
|
27
25
|
} from '@livestore/utils/effect'
|
|
26
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
28
27
|
import * as otel from '@opentelemetry/api'
|
|
29
28
|
|
|
30
29
|
import { globalReactivityGraph } from '../global-state.js'
|
|
31
30
|
import type { ReactivityGraph } from '../live-queries/base-class.js'
|
|
32
31
|
import { connectDevtoolsToStore } from './devtools.js'
|
|
33
32
|
import { Store } from './store.js'
|
|
34
|
-
import type { BaseGraphQLContext, GraphQLOptions, OtelOptions } from './store-types.js'
|
|
33
|
+
import type { BaseGraphQLContext, GraphQLOptions, OtelOptions, ShutdownDeferred } from './store-types.js'
|
|
35
34
|
|
|
36
|
-
export
|
|
35
|
+
export interface CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> {
|
|
37
36
|
schema: TSchema
|
|
38
37
|
adapter: Adapter
|
|
39
38
|
storeId: string
|
|
40
39
|
reactivityGraph?: ReactivityGraph
|
|
41
40
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
42
|
-
otelOptions?: Partial<OtelOptions>
|
|
43
41
|
boot?: (
|
|
44
42
|
store: Store<TGraphQLContext, TSchema>,
|
|
45
43
|
parentSpan: otel.Span,
|
|
46
|
-
) => void | Promise<void> | Effect.Effect<void, unknown,
|
|
44
|
+
) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer>
|
|
47
45
|
batchUpdates?: (run: () => void) => void
|
|
48
46
|
disableDevtools?: boolean
|
|
49
47
|
onBootStatus?: (status: BootStatus) => void
|
|
48
|
+
shutdownDeferred?: ShutdownDeferred
|
|
49
|
+
debug?: {
|
|
50
|
+
instanceId?: string
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
/** Create a new LiveStore Store */
|
|
@@ -55,8 +57,12 @@ export const createStorePromise = async <
|
|
|
55
57
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
56
58
|
>({
|
|
57
59
|
signal,
|
|
60
|
+
otelOptions,
|
|
58
61
|
...options
|
|
59
|
-
}: CreateStoreOptions<TGraphQLContext, TSchema> & {
|
|
62
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & {
|
|
63
|
+
signal?: AbortSignal
|
|
64
|
+
otelOptions?: Partial<OtelOptions>
|
|
65
|
+
}): Promise<Store<TGraphQLContext, TSchema>> =>
|
|
60
66
|
Effect.gen(function* () {
|
|
61
67
|
const scope = yield* Scope.make()
|
|
62
68
|
const runtime = yield* Effect.runtime()
|
|
@@ -67,12 +73,12 @@ export const createStorePromise = async <
|
|
|
67
73
|
})
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
return yield*
|
|
71
|
-
Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
|
|
72
|
-
Scope.extend(scope),
|
|
73
|
-
)
|
|
76
|
+
return yield* createStore({ ...options }).pipe(Scope.extend(scope))
|
|
74
77
|
}).pipe(
|
|
75
|
-
Effect.withSpan('createStore'
|
|
78
|
+
Effect.withSpan('createStore', {
|
|
79
|
+
attributes: { storeId: options.storeId, disableDevtools: options.disableDevtools },
|
|
80
|
+
}),
|
|
81
|
+
provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
|
|
76
82
|
Effect.tapCauseLogPretty,
|
|
77
83
|
Effect.annotateLogs({ thread: 'window' }),
|
|
78
84
|
Effect.provide(Logger.pretty),
|
|
@@ -88,127 +94,117 @@ export const createStore = <
|
|
|
88
94
|
adapter,
|
|
89
95
|
storeId,
|
|
90
96
|
graphQLOptions,
|
|
91
|
-
otelOptions,
|
|
92
97
|
boot,
|
|
93
98
|
reactivityGraph = globalReactivityGraph,
|
|
94
99
|
batchUpdates,
|
|
95
100
|
disableDevtools,
|
|
96
101
|
onBootStatus,
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
shutdownDeferred,
|
|
103
|
+
debug,
|
|
104
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<
|
|
99
105
|
Store<TGraphQLContext, TSchema>,
|
|
100
106
|
UnexpectedError,
|
|
101
|
-
Scope.Scope
|
|
102
|
-
> =>
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
Scope.Scope | OtelTracer.OtelTracer
|
|
108
|
+
> =>
|
|
109
|
+
Effect.gen(function* () {
|
|
110
|
+
const lifetimeScope = yield* Scope.make()
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
108
|
-
)
|
|
112
|
+
yield* Effect.addFinalizer((_) => Scope.close(lifetimeScope, _))
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
114
|
+
const debugInstanceId = debug?.instanceId ?? nanoid(10)
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
return yield* Effect.gen(function* () {
|
|
117
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
118
|
+
const otelRootSpanContext = otel.trace.setSpan(otel.context.active(), span)
|
|
119
|
+
const otelTracer = yield* OtelTracer.OtelTracer
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
117
|
-
Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
|
|
118
|
-
Effect.forever,
|
|
119
|
-
Effect.tapCauseLogPretty,
|
|
120
|
-
Effect.forkScoped,
|
|
121
|
-
)
|
|
121
|
+
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
yield* Queue.take(bootStatusQueue).pipe(
|
|
124
|
+
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
125
|
+
Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
|
|
126
|
+
Effect.forever,
|
|
127
|
+
Effect.tapCauseLogPretty,
|
|
128
|
+
Effect.forkScoped,
|
|
129
|
+
)
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
const storeDeferred = yield* Deferred.make<Store>()
|
|
132
|
+
|
|
133
|
+
const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const store = yield* storeDeferred
|
|
136
|
+
yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const runtime = yield* Effect.runtime<Scope.Scope>()
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const logCause =
|
|
138
|
-
Cause.isFailType(cause) && cause.error._tag === 'LiveStore.IntentionalShutdownCause'
|
|
139
|
-
? cause.toString()
|
|
140
|
-
: cause
|
|
141
|
-
yield* Effect.logDebug(`Shutting down LiveStore`, logCause)
|
|
142
|
-
|
|
143
|
-
FiberSet.clear(fiberSet).pipe(
|
|
144
|
-
Effect.andThen(() => FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
145
|
-
Effect.timeout(Duration.seconds(1)),
|
|
146
|
-
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown:clear-fiber-set', duration: 500 }),
|
|
147
|
-
Effect.catchTag('TimeoutException', (err) =>
|
|
148
|
-
Effect.logError('Store shutdown timed out. Forcing shutdown.', err).pipe(
|
|
149
|
-
Effect.andThen(FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
150
|
-
),
|
|
141
|
+
const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
|
|
142
|
+
Scope.close(lifetimeScope, Exit.failCause(cause)).pipe(
|
|
143
|
+
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
|
|
144
|
+
Effect.timeout(1000),
|
|
145
|
+
Effect.catchTag('TimeoutException', () =>
|
|
146
|
+
Effect.logError('@livestore/livestore:shutdown: Timed out after 1 second'),
|
|
151
147
|
),
|
|
152
|
-
|
|
148
|
+
Effect.tap(() => (shutdownDeferred ? Deferred.failCause(shutdownDeferred, cause) : Effect.void)),
|
|
149
|
+
Effect.tap(() => Effect.logDebug('LiveStore shutdown complete')),
|
|
150
|
+
Effect.withSpan('livestore:shutdown'),
|
|
153
151
|
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const clientSession: ClientSession = yield* adapter({
|
|
157
|
-
schema,
|
|
158
|
-
storeId,
|
|
159
|
-
devtoolsEnabled: disableDevtools !== true,
|
|
160
|
-
bootStatusQueue,
|
|
161
|
-
shutdown,
|
|
162
|
-
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
163
|
-
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
164
|
-
|
|
165
|
-
// TODO fill up with unsynced mutation events from the client session
|
|
166
|
-
const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
|
|
167
|
-
|
|
168
|
-
const store = Store.createStore<TGraphQLContext, TSchema>(
|
|
169
|
-
{
|
|
170
|
-
clientSession,
|
|
152
|
+
|
|
153
|
+
const clientSession: ClientSession = yield* adapter({
|
|
171
154
|
schema,
|
|
172
|
-
graphQLOptions,
|
|
173
|
-
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
174
|
-
reactivityGraph,
|
|
175
|
-
disableDevtools,
|
|
176
|
-
unsyncedMutationEvents,
|
|
177
|
-
fiberSet,
|
|
178
|
-
runtime,
|
|
179
|
-
// NOTE during boot we're not yet executing mutations in a batched context
|
|
180
|
-
// but only set the provided `batchUpdates` function after boot
|
|
181
|
-
batchUpdates: (run) => run(),
|
|
182
155
|
storeId,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
156
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
157
|
+
bootStatusQueue,
|
|
158
|
+
shutdown,
|
|
159
|
+
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
160
|
+
debugInstanceId,
|
|
161
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
162
|
+
|
|
163
|
+
// TODO fill up with unsynced mutation events from the client session
|
|
164
|
+
const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
|
|
165
|
+
|
|
166
|
+
const store = Store.createStore<TGraphQLContext, TSchema>(
|
|
167
|
+
{
|
|
168
|
+
clientSession,
|
|
169
|
+
schema,
|
|
170
|
+
graphQLOptions,
|
|
171
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
172
|
+
reactivityGraph,
|
|
173
|
+
disableDevtools,
|
|
174
|
+
unsyncedMutationEvents,
|
|
175
|
+
lifetimeScope,
|
|
176
|
+
runtime,
|
|
177
|
+
// NOTE during boot we're not yet executing mutations in a batched context
|
|
178
|
+
// but only set the provided `batchUpdates` function after boot
|
|
179
|
+
batchUpdates: (run) => run(),
|
|
180
|
+
storeId,
|
|
181
|
+
},
|
|
182
|
+
span,
|
|
192
183
|
)
|
|
193
|
-
}
|
|
194
184
|
|
|
195
|
-
|
|
196
|
-
|
|
185
|
+
if (boot !== undefined) {
|
|
186
|
+
// TODO also incorporate `boot` function progress into `bootStatusQueue`
|
|
187
|
+
yield* Effect.tryAll(() => boot(store, span)).pipe(
|
|
188
|
+
UnexpectedError.mapToUnexpectedError,
|
|
189
|
+
Effect.withSpan('createStore:boot'),
|
|
190
|
+
)
|
|
191
|
+
}
|
|
197
192
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
store.reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
201
|
-
}
|
|
193
|
+
// NOTE it's important to yield here to allow the forked Effect in the store constructor to run
|
|
194
|
+
yield* Effect.yieldNow()
|
|
202
195
|
|
|
203
|
-
|
|
196
|
+
if (batchUpdates !== undefined) {
|
|
197
|
+
// Replacing the default batchUpdates function with the provided one after boot
|
|
198
|
+
store.reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
199
|
+
}
|
|
204
200
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
201
|
+
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
202
|
+
|
|
203
|
+
return store
|
|
204
|
+
}).pipe(
|
|
205
|
+
Effect.withSpan('createStore', { attributes: { debugInstanceId, storeId } }),
|
|
206
|
+
Effect.annotateLogs({ debugInstanceId, storeId }),
|
|
207
|
+
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
208
|
+
Scope.extend(lifetimeScope),
|
|
209
|
+
)
|
|
210
|
+
})
|
package/src/store/devtools.ts
CHANGED
|
@@ -21,20 +21,29 @@ type Unsub = () => void
|
|
|
21
21
|
type RequestId = string
|
|
22
22
|
type SubMap = Map<RequestId, Unsub>
|
|
23
23
|
|
|
24
|
+
// When running this code in Node.js, we need to use `setTimeout` instead of `requestAnimationFrame`
|
|
25
|
+
const requestNextTick: (cb: () => void) => number =
|
|
26
|
+
globalThis.requestAnimationFrame === undefined
|
|
27
|
+
? (cb: () => void) => setTimeout(cb, 1000) as unknown as number
|
|
28
|
+
: globalThis.requestAnimationFrame
|
|
29
|
+
|
|
30
|
+
const cancelTick: (id: number) => void =
|
|
31
|
+
globalThis.cancelAnimationFrame === undefined ? (id: number) => clearTimeout(id) : globalThis.cancelAnimationFrame
|
|
32
|
+
|
|
24
33
|
export const connectDevtoolsToStore = ({
|
|
25
34
|
storeDevtoolsChannel,
|
|
26
35
|
store,
|
|
27
36
|
}: {
|
|
28
|
-
storeDevtoolsChannel: WebChannel.WebChannel<Devtools.
|
|
37
|
+
storeDevtoolsChannel: WebChannel.WebChannel<Devtools.MessageToAppClientSession, Devtools.MessageFromAppClientSession>
|
|
29
38
|
store: IStore
|
|
30
39
|
}) =>
|
|
31
40
|
Effect.gen(function* () {
|
|
32
|
-
const appHostId = store.clientSession.coordinator.devtools.appHostId
|
|
33
|
-
|
|
34
41
|
const reactivityGraphSubcriptions: SubMap = new Map()
|
|
35
42
|
const liveQueriesSubscriptions: SubMap = new Map()
|
|
36
43
|
const debugInfoHistorySubscriptions: SubMap = new Map()
|
|
37
44
|
|
|
45
|
+
const { clientId, sessionId } = store.clientSession
|
|
46
|
+
|
|
38
47
|
yield* Effect.addFinalizer(() =>
|
|
39
48
|
Effect.sync(() => {
|
|
40
49
|
reactivityGraphSubcriptions.forEach((unsub) => unsub())
|
|
@@ -43,17 +52,22 @@ export const connectDevtoolsToStore = ({
|
|
|
43
52
|
}),
|
|
44
53
|
)
|
|
45
54
|
|
|
46
|
-
const sendToDevtools = (message: Devtools.
|
|
47
|
-
storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.
|
|
55
|
+
const sendToDevtools = (message: Devtools.MessageFromAppClientSession) =>
|
|
56
|
+
storeDevtoolsChannel.send(message).pipe(Effect.tapCauseLogPretty, Effect.runFork)
|
|
48
57
|
|
|
49
|
-
const onMessage = (decodedMessage: typeof Devtools.
|
|
50
|
-
// console.
|
|
58
|
+
const onMessage = (decodedMessage: typeof Devtools.MessageToAppClientSession.Type) => {
|
|
59
|
+
// console.debug('@livestore/livestore:store:devtools:onMessage', decodedMessage)
|
|
51
60
|
|
|
52
|
-
if (decodedMessage.
|
|
61
|
+
if (decodedMessage.clientId !== clientId || decodedMessage.sessionId !== sessionId) {
|
|
53
62
|
// console.log(`Unknown message`, event)
|
|
54
63
|
return
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
if (decodedMessage._tag === 'LSD.Disconnect') {
|
|
67
|
+
// console.error('TODO handle disconnect properly in store')
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
const requestId = decodedMessage.requestId
|
|
58
72
|
|
|
59
73
|
const requestIdleCallback = globalThis.requestIdleCallback ?? ((cb: () => void) => cb())
|
|
@@ -71,7 +85,8 @@ export const connectDevtoolsToStore = ({
|
|
|
71
85
|
Devtools.ReactivityGraphRes.make({
|
|
72
86
|
reactivityGraph: store.reactivityGraph.getSnapshot({ includeResults }),
|
|
73
87
|
requestId,
|
|
74
|
-
|
|
88
|
+
clientId,
|
|
89
|
+
sessionId,
|
|
75
90
|
liveStoreVersion,
|
|
76
91
|
}),
|
|
77
92
|
),
|
|
@@ -94,7 +109,8 @@ export const connectDevtoolsToStore = ({
|
|
|
94
109
|
Devtools.DebugInfoRes.make({
|
|
95
110
|
debugInfo: store.syncDbWrapper.debugInfo,
|
|
96
111
|
requestId,
|
|
97
|
-
|
|
112
|
+
clientId,
|
|
113
|
+
sessionId,
|
|
98
114
|
liveStoreVersion,
|
|
99
115
|
}),
|
|
100
116
|
)
|
|
@@ -103,7 +119,7 @@ export const connectDevtoolsToStore = ({
|
|
|
103
119
|
case 'LSD.DebugInfoHistorySubscribe': {
|
|
104
120
|
const buffer: DebugInfo[] = []
|
|
105
121
|
let hasStopped = false
|
|
106
|
-
let
|
|
122
|
+
let tickHandle: number | undefined
|
|
107
123
|
|
|
108
124
|
const tick = () => {
|
|
109
125
|
buffer.push(store.syncDbWrapper.debugInfo)
|
|
@@ -119,7 +135,8 @@ export const connectDevtoolsToStore = ({
|
|
|
119
135
|
Devtools.DebugInfoHistoryRes.make({
|
|
120
136
|
debugInfoHistory: buffer,
|
|
121
137
|
requestId,
|
|
122
|
-
|
|
138
|
+
clientId,
|
|
139
|
+
sessionId,
|
|
123
140
|
liveStoreVersion,
|
|
124
141
|
}),
|
|
125
142
|
)
|
|
@@ -127,16 +144,17 @@ export const connectDevtoolsToStore = ({
|
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
if (hasStopped === false) {
|
|
130
|
-
|
|
147
|
+
tickHandle = requestNextTick(tick)
|
|
131
148
|
}
|
|
132
149
|
}
|
|
133
150
|
|
|
134
|
-
|
|
151
|
+
tickHandle = requestNextTick(tick)
|
|
135
152
|
|
|
136
153
|
const unsub = () => {
|
|
137
154
|
hasStopped = true
|
|
138
|
-
if (
|
|
139
|
-
|
|
155
|
+
if (tickHandle !== undefined) {
|
|
156
|
+
cancelTick(tickHandle)
|
|
157
|
+
tickHandle = undefined
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
160
|
|
|
@@ -145,23 +163,27 @@ export const connectDevtoolsToStore = ({
|
|
|
145
163
|
break
|
|
146
164
|
}
|
|
147
165
|
case 'LSD.DebugInfoHistoryUnsubscribe': {
|
|
148
|
-
|
|
166
|
+
// NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
|
|
167
|
+
// WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
168
|
+
debugInfoHistorySubscriptions.get(requestId)?.()
|
|
149
169
|
debugInfoHistorySubscriptions.delete(requestId)
|
|
150
170
|
break
|
|
151
171
|
}
|
|
152
172
|
case 'LSD.DebugInfoResetReq': {
|
|
153
173
|
store.syncDbWrapper.debugInfo.slowQueries.clear()
|
|
154
|
-
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId,
|
|
174
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
|
|
155
175
|
break
|
|
156
176
|
}
|
|
157
177
|
case 'LSD.DebugInfoRerunQueryReq': {
|
|
158
178
|
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
159
179
|
store.syncDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
160
|
-
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId,
|
|
180
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
|
|
161
181
|
break
|
|
162
182
|
}
|
|
163
183
|
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
164
|
-
|
|
184
|
+
// NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
|
|
185
|
+
// WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
186
|
+
reactivityGraphSubcriptions.get(requestId)?.()
|
|
165
187
|
break
|
|
166
188
|
}
|
|
167
189
|
case 'LSD.LiveQueriesSubscribe': {
|
|
@@ -184,7 +206,8 @@ export const connectDevtoolsToStore = ({
|
|
|
184
206
|
})),
|
|
185
207
|
requestId,
|
|
186
208
|
liveStoreVersion,
|
|
187
|
-
|
|
209
|
+
clientId,
|
|
210
|
+
sessionId,
|
|
188
211
|
}),
|
|
189
212
|
),
|
|
190
213
|
{ timeout: 500 },
|
|
@@ -200,7 +223,9 @@ export const connectDevtoolsToStore = ({
|
|
|
200
223
|
break
|
|
201
224
|
}
|
|
202
225
|
case 'LSD.LiveQueriesUnsubscribe': {
|
|
203
|
-
|
|
226
|
+
// NOTE given WebMesh channels have persistent retry behaviour, it can happen that a previous
|
|
227
|
+
// WebMesh channel will send a unsubscribe message for an old requestId. Thus the `?.()` handling.
|
|
228
|
+
liveQueriesSubscriptions.get(requestId)?.()
|
|
204
229
|
liveQueriesSubscriptions.delete(requestId)
|
|
205
230
|
break
|
|
206
231
|
}
|
|
@@ -209,6 +234,7 @@ export const connectDevtoolsToStore = ({
|
|
|
209
234
|
}
|
|
210
235
|
|
|
211
236
|
yield* storeDevtoolsChannel.listen.pipe(
|
|
237
|
+
// Stream.tapLogWithLabel('@livestore/livestore:store:devtools:onMessage'),
|
|
212
238
|
Stream.flatten(),
|
|
213
239
|
Stream.tapSync((message) => onMessage(message)),
|
|
214
240
|
Stream.runDrain,
|
package/src/store/store-types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ClientSession,
|
|
2
|
-
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
3
|
-
import type {
|
|
1
|
+
import type { ClientSession, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
|
|
2
|
+
import type { EventId, LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
3
|
+
import type { Deferred, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
|
|
4
4
|
import { Schema } from '@livestore/utils/effect'
|
|
5
5
|
import type * as otel from '@opentelemetry/api'
|
|
6
6
|
import type { GraphQLSchema } from 'graphql'
|
|
@@ -25,6 +25,11 @@ export type LiveStoreContext =
|
|
|
25
25
|
export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
|
|
26
26
|
export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
|
|
27
27
|
|
|
28
|
+
export type ShutdownDeferred = Deferred.Deferred<
|
|
29
|
+
void,
|
|
30
|
+
UnexpectedError | IntentionalShutdownCause | StoreInterrupted | StoreAbort
|
|
31
|
+
>
|
|
32
|
+
|
|
28
33
|
export type LiveStoreContextRunning = {
|
|
29
34
|
stage: 'running'
|
|
30
35
|
store: Store
|
|
@@ -58,10 +63,11 @@ export type StoreOptions<
|
|
|
58
63
|
otelOptions: OtelOptions
|
|
59
64
|
reactivityGraph: ReactivityGraph
|
|
60
65
|
disableDevtools?: boolean
|
|
61
|
-
|
|
66
|
+
lifetimeScope: Scope.Scope
|
|
62
67
|
runtime: Runtime.Runtime<Scope.Scope>
|
|
63
68
|
batchUpdates: (runUpdates: () => void) => void
|
|
64
|
-
|
|
69
|
+
// TODO validate whether we still need this
|
|
70
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
export type RefreshReason =
|
|
@@ -69,7 +75,7 @@ export type RefreshReason =
|
|
|
69
75
|
| {
|
|
70
76
|
_tag: 'mutate'
|
|
71
77
|
/** The mutations that were applied */
|
|
72
|
-
mutations: ReadonlyArray<MutationEvent.
|
|
78
|
+
mutations: ReadonlyArray<MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded>
|
|
73
79
|
|
|
74
80
|
/** The tables that were written to by the event */
|
|
75
81
|
writeTables: ReadonlyArray<string>
|
|
@@ -99,12 +105,6 @@ export type StoreOtel = {
|
|
|
99
105
|
export type StoreMutateOptions = {
|
|
100
106
|
label?: string
|
|
101
107
|
skipRefresh?: boolean
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
* When set to `false` the mutation won't be persisted in the mutation log and sync server (but still synced).
|
|
105
|
-
* This can be useful e.g. for fine-granular update events (e.g. position updates during drag & drop)
|
|
106
|
-
*
|
|
107
|
-
* @default true
|
|
108
|
-
*/
|
|
109
|
-
persisted?: boolean
|
|
108
|
+
spanLinks?: otel.Link[]
|
|
109
|
+
otelContext?: otel.Context
|
|
110
110
|
}
|