@livestore/livestore 0.2.0 → 0.3.0-dev.11
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/SqliteDbWrapper.d.ts +54 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/SqliteDbWrapper.js +212 -0
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +20 -6
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +38 -6
- 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 +16 -12
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +14 -14
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +64 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +56 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +35 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/{sql.d.ts → db-query.d.ts} +19 -14
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/db-query.js +244 -0
- 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 +123 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/db.d.ts +12 -15
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +72 -48
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +18 -15
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +35 -9
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts +20 -0
- package/dist/live-queries/make-ref.d.ts.map +1 -0
- package/dist/live-queries/make-ref.js +33 -0
- package/dist/live-queries/make-ref.js.map +1 -0
- package/dist/reactive.d.ts +15 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +15 -9
- package/dist/reactive.js.map +1 -1
- package/dist/row-query-utils.d.ts +4 -4
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +14 -10
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +13 -12
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +27 -33
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +3 -3
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +56 -34
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +18 -18
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +57 -38
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +225 -188
- 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/expo.d.ts +2 -0
- package/dist/utils/expo.d.ts.map +1 -0
- package/dist/utils/expo.js +8 -0
- package/dist/utils/expo.js.map +1 -0
- 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 +2 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -13
- 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 +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 → SqliteDbWrapper.ts} +59 -13
- package/src/ambient.d.ts +1 -1
- package/src/effect/LiveStore.ts +32 -33
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +220 -69
- package/src/live-queries/base-class.ts +160 -40
- package/src/live-queries/computed.ts +45 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +23 -12
- package/src/live-queries/{db.ts → db-query.ts} +124 -61
- package/src/live-queries/graphql.ts +47 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.ts +52 -27
- package/src/row-query-utils.ts +29 -18
- package/src/store/create-store.ts +106 -113
- package/src/store/devtools.ts +65 -39
- package/src/store/store-types.ts +20 -18
- package/src/store/store.ts +361 -290
- package/src/utils/dev.ts +4 -2
- 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 +6 -16
- package/src/utils/tests/mod.ts +1 -0
- package/src/utils/tests/otel.ts +71 -5
- 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/global-state.ts +0 -20
|
@@ -2,20 +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
|
-
|
|
16
|
+
identity,
|
|
19
17
|
Layer,
|
|
20
18
|
Logger,
|
|
21
19
|
LogLevel,
|
|
@@ -24,29 +22,32 @@ import {
|
|
|
24
22
|
Queue,
|
|
25
23
|
Runtime,
|
|
26
24
|
Scope,
|
|
25
|
+
TaskTracing,
|
|
27
26
|
} from '@livestore/utils/effect'
|
|
27
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
28
28
|
import * as otel from '@opentelemetry/api'
|
|
29
29
|
|
|
30
|
-
import {
|
|
31
|
-
import type { ReactivityGraph } from '../live-queries/base-class.js'
|
|
30
|
+
import { LiveStoreContextRunning } from '../effect/index.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
|
-
reactivityGraph?: ReactivityGraph
|
|
41
39
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
42
|
-
otelOptions?: Partial<OtelOptions>
|
|
43
40
|
boot?: (
|
|
44
41
|
store: Store<TGraphQLContext, TSchema>,
|
|
45
42
|
parentSpan: otel.Span,
|
|
46
|
-
) => void | Promise<void> | Effect.Effect<void, unknown,
|
|
43
|
+
) => void | Promise<void> | Effect.Effect<void, unknown, OtelTracer.OtelTracer | LiveStoreContextRunning>
|
|
47
44
|
batchUpdates?: (run: () => void) => void
|
|
48
45
|
disableDevtools?: boolean
|
|
49
46
|
onBootStatus?: (status: BootStatus) => void
|
|
47
|
+
shutdownDeferred?: ShutdownDeferred
|
|
48
|
+
debug?: {
|
|
49
|
+
instanceId?: string
|
|
50
|
+
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/** Create a new LiveStore Store */
|
|
@@ -55,8 +56,12 @@ export const createStorePromise = async <
|
|
|
55
56
|
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
56
57
|
>({
|
|
57
58
|
signal,
|
|
59
|
+
otelOptions,
|
|
58
60
|
...options
|
|
59
|
-
}: CreateStoreOptions<TGraphQLContext, TSchema> & {
|
|
61
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema> & {
|
|
62
|
+
signal?: AbortSignal
|
|
63
|
+
otelOptions?: Partial<OtelOptions>
|
|
64
|
+
}): Promise<Store<TGraphQLContext, TSchema>> =>
|
|
60
65
|
Effect.gen(function* () {
|
|
61
66
|
const scope = yield* Scope.make()
|
|
62
67
|
const runtime = yield* Effect.runtime()
|
|
@@ -67,12 +72,12 @@ export const createStorePromise = async <
|
|
|
67
72
|
})
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
return yield*
|
|
71
|
-
Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
|
|
72
|
-
Scope.extend(scope),
|
|
73
|
-
)
|
|
75
|
+
return yield* createStore({ ...options }).pipe(Scope.extend(scope))
|
|
74
76
|
}).pipe(
|
|
75
|
-
Effect.withSpan('createStore'
|
|
77
|
+
Effect.withSpan('createStore', {
|
|
78
|
+
attributes: { storeId: options.storeId, disableDevtools: options.disableDevtools },
|
|
79
|
+
}),
|
|
80
|
+
provideOtel({ parentSpanContext: otelOptions?.rootSpanContext, otelTracer: otelOptions?.tracer }),
|
|
76
81
|
Effect.tapCauseLogPretty,
|
|
77
82
|
Effect.annotateLogs({ thread: 'window' }),
|
|
78
83
|
Effect.provide(Logger.pretty),
|
|
@@ -88,127 +93,115 @@ export const createStore = <
|
|
|
88
93
|
adapter,
|
|
89
94
|
storeId,
|
|
90
95
|
graphQLOptions,
|
|
91
|
-
otelOptions,
|
|
92
96
|
boot,
|
|
93
|
-
reactivityGraph = globalReactivityGraph,
|
|
94
97
|
batchUpdates,
|
|
95
98
|
disableDevtools,
|
|
96
99
|
onBootStatus,
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
shutdownDeferred,
|
|
101
|
+
debug,
|
|
102
|
+
}: CreateStoreOptions<TGraphQLContext, TSchema>): Effect.Effect<
|
|
99
103
|
Store<TGraphQLContext, TSchema>,
|
|
100
104
|
UnexpectedError,
|
|
101
|
-
Scope.Scope
|
|
102
|
-
> =>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
Scope.Scope | OtelTracer.OtelTracer
|
|
106
|
+
> =>
|
|
107
|
+
Effect.gen(function* () {
|
|
108
|
+
const lifetimeScope = yield* Scope.make()
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
108
|
-
)
|
|
110
|
+
yield* Effect.addFinalizer((_) => Scope.close(lifetimeScope, _))
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
112
|
+
const debugInstanceId = debug?.instanceId ?? nanoid(10)
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
return yield* Effect.gen(function* () {
|
|
115
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
116
|
+
const otelRootSpanContext = otel.trace.setSpan(otel.context.active(), span)
|
|
117
|
+
const otelTracer = yield* OtelTracer.OtelTracer
|
|
114
118
|
|
|
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
|
-
)
|
|
119
|
+
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
122
120
|
|
|
123
|
-
|
|
121
|
+
yield* Queue.take(bootStatusQueue).pipe(
|
|
122
|
+
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
123
|
+
Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
|
|
124
|
+
Effect.forever,
|
|
125
|
+
Effect.tapCauseLogPretty,
|
|
126
|
+
Effect.forkScoped,
|
|
127
|
+
)
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
Effect.gen(function* () {
|
|
127
|
-
const store = yield* Deferred.await(storeDeferred)
|
|
128
|
-
yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
|
|
129
|
-
})
|
|
129
|
+
const storeDeferred = yield* Deferred.make<Store>()
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
),
|
|
131
|
+
const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const store = yield* storeDeferred
|
|
134
|
+
yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const runtime = yield* Effect.runtime<Scope.Scope>()
|
|
138
|
+
|
|
139
|
+
const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
|
|
140
|
+
Scope.close(lifetimeScope, Exit.failCause(cause)).pipe(
|
|
141
|
+
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown', duration: 500 }),
|
|
142
|
+
Effect.timeout(1000),
|
|
143
|
+
Effect.catchTag('TimeoutException', () =>
|
|
144
|
+
Effect.logError('@livestore/livestore:shutdown: Timed out after 1 second'),
|
|
151
145
|
),
|
|
152
|
-
|
|
146
|
+
Effect.tap(() => (shutdownDeferred ? Deferred.failCause(shutdownDeferred, cause) : Effect.void)),
|
|
147
|
+
Effect.tap(() => Effect.logDebug('LiveStore shutdown complete')),
|
|
148
|
+
Effect.withSpan('livestore:shutdown'),
|
|
153
149
|
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
{
|
|
150
|
+
|
|
151
|
+
const clientSession: ClientSession = yield* adapter({
|
|
152
|
+
schema,
|
|
153
|
+
storeId,
|
|
154
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
155
|
+
bootStatusQueue,
|
|
156
|
+
shutdown,
|
|
157
|
+
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
158
|
+
debugInstanceId,
|
|
159
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
160
|
+
|
|
161
|
+
// TODO fill up with unsynced mutation events from the client session
|
|
162
|
+
const unsyncedMutationEvents = MutableHashMap.empty<EventId.EventId, MutationEvent.ForSchema<TSchema>>()
|
|
163
|
+
|
|
164
|
+
const store = new Store<TGraphQLContext, TSchema>({
|
|
170
165
|
clientSession,
|
|
171
166
|
schema,
|
|
172
167
|
graphQLOptions,
|
|
173
168
|
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
174
|
-
reactivityGraph,
|
|
175
169
|
disableDevtools,
|
|
176
170
|
unsyncedMutationEvents,
|
|
177
|
-
|
|
171
|
+
lifetimeScope,
|
|
178
172
|
runtime,
|
|
179
173
|
// NOTE during boot we're not yet executing mutations in a batched context
|
|
180
174
|
// but only set the provided `batchUpdates` function after boot
|
|
181
175
|
batchUpdates: (run) => run(),
|
|
182
176
|
storeId,
|
|
183
|
-
}
|
|
184
|
-
span,
|
|
185
|
-
)
|
|
177
|
+
})
|
|
186
178
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Effect.
|
|
192
|
-
|
|
193
|
-
|
|
179
|
+
yield* store.boot
|
|
180
|
+
|
|
181
|
+
if (boot !== undefined) {
|
|
182
|
+
// TODO also incorporate `boot` function progress into `bootStatusQueue`
|
|
183
|
+
yield* Effect.tryAll(() => boot(store, span)).pipe(
|
|
184
|
+
UnexpectedError.mapToUnexpectedError,
|
|
185
|
+
Effect.provide(Layer.succeed(LiveStoreContextRunning, { stage: 'running', store: store as any as Store })),
|
|
186
|
+
Effect.withSpan('createStore:boot'),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
194
189
|
|
|
195
|
-
|
|
196
|
-
|
|
190
|
+
// NOTE it's important to yield here to allow the forked Effect in the store constructor to run
|
|
191
|
+
yield* Effect.yieldNow()
|
|
197
192
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
if (batchUpdates !== undefined) {
|
|
194
|
+
// Replacing the default batchUpdates function with the provided one after boot
|
|
195
|
+
store.reactivityGraph.context!.effectsWrapper = batchUpdates
|
|
196
|
+
}
|
|
202
197
|
|
|
203
|
-
|
|
198
|
+
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
204
199
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
)
|
|
214
|
-
}
|
|
200
|
+
return store
|
|
201
|
+
}).pipe(
|
|
202
|
+
Effect.withSpan('createStore', { attributes: { debugInstanceId, storeId } }),
|
|
203
|
+
Effect.annotateLogs({ debugInstanceId, storeId }),
|
|
204
|
+
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
205
|
+
Scope.extend(lifetimeScope),
|
|
206
|
+
)
|
|
207
|
+
})
|
package/src/store/devtools.ts
CHANGED
|
@@ -6,14 +6,14 @@ import { Effect, Stream } from '@livestore/utils/effect'
|
|
|
6
6
|
|
|
7
7
|
import type { LiveQuery, ReactivityGraph } from '../live-queries/base-class.js'
|
|
8
8
|
import { NOT_REFRESHED_YET } from '../reactive.js'
|
|
9
|
-
import type {
|
|
10
|
-
import { emptyDebugInfo as makeEmptyDebugInfo } from '../
|
|
9
|
+
import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
|
|
10
|
+
import { emptyDebugInfo as makeEmptyDebugInfo } from '../SqliteDbWrapper.js'
|
|
11
11
|
import type { ReferenceCountedSet } from '../utils/data-structures.js'
|
|
12
12
|
|
|
13
13
|
type IStore = {
|
|
14
14
|
clientSession: ClientSession
|
|
15
15
|
reactivityGraph: ReactivityGraph
|
|
16
|
-
|
|
16
|
+
sqliteDbWrapper: SqliteDbWrapper
|
|
17
17
|
activeQueries: ReferenceCountedSet<LiveQuery<any>>
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -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,23 +52,28 @@ 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())
|
|
60
74
|
|
|
61
75
|
switch (decodedMessage._tag) {
|
|
62
|
-
case 'LSD.ReactivityGraphSubscribe': {
|
|
76
|
+
case 'LSD.ClientSession.ReactivityGraphSubscribe': {
|
|
63
77
|
const includeResults = decodedMessage.includeResults
|
|
64
78
|
|
|
65
79
|
const send = () =>
|
|
@@ -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
|
),
|
|
@@ -89,37 +104,39 @@ export const connectDevtoolsToStore = ({
|
|
|
89
104
|
|
|
90
105
|
break
|
|
91
106
|
}
|
|
92
|
-
case 'LSD.DebugInfoReq': {
|
|
107
|
+
case 'LSD.ClientSession.DebugInfoReq': {
|
|
93
108
|
sendToDevtools(
|
|
94
109
|
Devtools.DebugInfoRes.make({
|
|
95
|
-
debugInfo: store.
|
|
110
|
+
debugInfo: store.sqliteDbWrapper.debugInfo,
|
|
96
111
|
requestId,
|
|
97
|
-
|
|
112
|
+
clientId,
|
|
113
|
+
sessionId,
|
|
98
114
|
liveStoreVersion,
|
|
99
115
|
}),
|
|
100
116
|
)
|
|
101
117
|
break
|
|
102
118
|
}
|
|
103
|
-
case 'LSD.DebugInfoHistorySubscribe': {
|
|
119
|
+
case 'LSD.ClientSession.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
|
-
buffer.push(store.
|
|
125
|
+
buffer.push(store.sqliteDbWrapper.debugInfo)
|
|
110
126
|
|
|
111
127
|
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
112
128
|
// will get the empty debug info
|
|
113
129
|
// TODO We need to come up with a more graceful way to do store. Probably via a single global
|
|
114
130
|
// `requestAnimationFrame` loop that is passed in somehow.
|
|
115
|
-
store.
|
|
131
|
+
store.sqliteDbWrapper.debugInfo = makeEmptyDebugInfo()
|
|
116
132
|
|
|
117
133
|
if (buffer.length > 10) {
|
|
118
134
|
sendToDevtools(
|
|
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
|
|
|
@@ -144,27 +162,31 @@ export const connectDevtoolsToStore = ({
|
|
|
144
162
|
|
|
145
163
|
break
|
|
146
164
|
}
|
|
147
|
-
case 'LSD.DebugInfoHistoryUnsubscribe': {
|
|
148
|
-
|
|
165
|
+
case 'LSD.ClientSession.DebugInfoHistoryUnsubscribe': {
|
|
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
|
-
case 'LSD.DebugInfoResetReq': {
|
|
153
|
-
store.
|
|
154
|
-
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId,
|
|
172
|
+
case 'LSD.ClientSession.DebugInfoResetReq': {
|
|
173
|
+
store.sqliteDbWrapper.debugInfo.slowQueries.clear()
|
|
174
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
|
|
155
175
|
break
|
|
156
176
|
}
|
|
157
|
-
case 'LSD.DebugInfoRerunQueryReq': {
|
|
177
|
+
case 'LSD.ClientSession.DebugInfoRerunQueryReq': {
|
|
158
178
|
const { queryStr, bindValues, queriedTables } = decodedMessage
|
|
159
|
-
store.
|
|
160
|
-
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId,
|
|
179
|
+
store.sqliteDbWrapper.select(queryStr, bindValues, { queriedTables, skipCache: true })
|
|
180
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, clientId, sessionId, liveStoreVersion }))
|
|
161
181
|
break
|
|
162
182
|
}
|
|
163
|
-
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
164
|
-
|
|
183
|
+
case 'LSD.ClientSession.ReactivityGraphUnsubscribe': {
|
|
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
|
-
case 'LSD.LiveQueriesSubscribe': {
|
|
189
|
+
case 'LSD.ClientSession.LiveQueriesSubscribe': {
|
|
168
190
|
const send = () =>
|
|
169
191
|
requestIdleCallback(
|
|
170
192
|
() =>
|
|
@@ -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 },
|
|
@@ -199,8 +222,10 @@ export const connectDevtoolsToStore = ({
|
|
|
199
222
|
|
|
200
223
|
break
|
|
201
224
|
}
|
|
202
|
-
case 'LSD.LiveQueriesUnsubscribe': {
|
|
203
|
-
|
|
225
|
+
case 'LSD.ClientSession.LiveQueriesUnsubscribe': {
|
|
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,13 +1,12 @@
|
|
|
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'
|
|
7
7
|
|
|
8
|
-
import type { ReactivityGraph } from '../live-queries/base-class.js'
|
|
9
8
|
import type { DebugRefreshReasonBase } from '../reactive.js'
|
|
10
|
-
import type {
|
|
9
|
+
import type { SqliteDbWrapper } from '../SqliteDbWrapper.js'
|
|
11
10
|
import type { StackInfo } from '../utils/stack-info.js'
|
|
12
11
|
import type { Store } from './store.js'
|
|
13
12
|
|
|
@@ -25,6 +24,11 @@ export type LiveStoreContext =
|
|
|
25
24
|
export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
|
|
26
25
|
export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
|
|
27
26
|
|
|
27
|
+
export type ShutdownDeferred = Deferred.Deferred<
|
|
28
|
+
void,
|
|
29
|
+
UnexpectedError | IntentionalShutdownCause | StoreInterrupted | StoreAbort
|
|
30
|
+
>
|
|
31
|
+
|
|
28
32
|
export type LiveStoreContextRunning = {
|
|
29
33
|
stage: 'running'
|
|
30
34
|
store: Store
|
|
@@ -38,7 +42,7 @@ export type BaseGraphQLContext = {
|
|
|
38
42
|
|
|
39
43
|
export type GraphQLOptions<TContext> = {
|
|
40
44
|
schema: GraphQLSchema
|
|
41
|
-
makeContext: (db:
|
|
45
|
+
makeContext: (db: SqliteDbWrapper, tracer: otel.Tracer, sessionId: string) => TContext
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export type OtelOptions = {
|
|
@@ -56,12 +60,12 @@ export type StoreOptions<
|
|
|
56
60
|
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
57
61
|
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
58
62
|
otelOptions: OtelOptions
|
|
59
|
-
reactivityGraph: ReactivityGraph
|
|
60
63
|
disableDevtools?: boolean
|
|
61
|
-
|
|
64
|
+
lifetimeScope: Scope.Scope
|
|
62
65
|
runtime: Runtime.Runtime<Scope.Scope>
|
|
63
66
|
batchUpdates: (runUpdates: () => void) => void
|
|
64
|
-
|
|
67
|
+
// TODO validate whether we still need this
|
|
68
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId.EventId, MutationEvent.ForSchema<TSchema>>
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
export type RefreshReason =
|
|
@@ -69,7 +73,7 @@ export type RefreshReason =
|
|
|
69
73
|
| {
|
|
70
74
|
_tag: 'mutate'
|
|
71
75
|
/** The mutations that were applied */
|
|
72
|
-
mutations: ReadonlyArray<MutationEvent.
|
|
76
|
+
mutations: ReadonlyArray<MutationEvent.AnyDecoded | MutationEvent.PartialAnyDecoded>
|
|
73
77
|
|
|
74
78
|
/** The tables that were written to by the event */
|
|
75
79
|
writeTables: ReadonlyArray<string>
|
|
@@ -81,6 +85,8 @@ export type RefreshReason =
|
|
|
81
85
|
label?: string
|
|
82
86
|
stackInfo?: StackInfo
|
|
83
87
|
}
|
|
88
|
+
| { _tag: 'subscribe.initial'; label?: string }
|
|
89
|
+
| { _tag: 'subscribe.update'; label?: string }
|
|
84
90
|
| { _tag: 'manual'; label?: string }
|
|
85
91
|
|
|
86
92
|
export type QueryDebugInfo = {
|
|
@@ -99,12 +105,8 @@ 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
|
}
|
|
111
|
+
|
|
112
|
+
export type Unsubscribe = () => void
|