@livestore/livestore 0.0.58-dev.13 → 0.0.58-dev.15
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/effect/LiveStore.d.ts +4 -4
- 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/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +4 -3
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/computed.d.ts +35 -0
- package/dist/reactiveQueries/computed.d.ts.map +1 -0
- package/dist/reactiveQueries/computed.js +57 -0
- package/dist/reactiveQueries/computed.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +2 -1
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.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 +1 -1
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +2 -6
- package/dist/row-query.js.map +1 -1
- package/dist/store/create-store.d.ts +28 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/create-store.js +85 -0
- package/dist/store/create-store.js.map +1 -0
- package/dist/store/devtools.d.ts +19 -0
- package/dist/store/devtools.d.ts.map +1 -0
- package/dist/store/devtools.js +141 -0
- package/dist/store/devtools.js.map +1 -0
- package/dist/store/store-context.d.ts +26 -0
- package/dist/store/store-context.d.ts.map +1 -0
- package/dist/store/store-context.js +6 -0
- package/dist/store/store-context.js.map +1 -0
- package/dist/store/store-types.d.ts +98 -0
- package/dist/store/store-types.d.ts.map +1 -0
- package/dist/store/store-types.js +6 -0
- package/dist/store/store-types.js.map +1 -0
- package/dist/store/store.d.ts +88 -0
- package/dist/store/store.d.ts.map +1 -0
- package/dist/store/store.js +367 -0
- package/dist/store/store.js.map +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +8 -8
- package/dist/store.js.map +1 -1
- package/dist/utils/dev.d.ts +1 -0
- package/dist/utils/dev.d.ts.map +1 -1
- package/dist/utils/dev.js +5 -0
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +6 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +3 -4
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +3 -3
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +6 -5
- package/src/ambient.d.ts +3 -1
- package/src/effect/LiveStore.ts +5 -5
- package/src/index.ts +5 -6
- package/src/reactiveQueries/base-class.ts +4 -3
- package/src/reactiveQueries/{js.ts → computed.ts} +3 -3
- package/src/reactiveQueries/graphql.ts +2 -1
- package/src/reactiveQueries/sql.ts +2 -2
- package/src/row-query.ts +3 -7
- package/src/store/create-store.ts +214 -0
- package/src/{store-devtools.ts → store/devtools.ts} +5 -5
- package/src/store/store-types.ts +110 -0
- package/src/{store.ts → store/store.ts} +23 -392
- package/src/utils/dev.ts +6 -0
- package/src/utils/tests/fixture.ts +10 -14
- package/src/utils/tests/otel.ts +4 -4
- package/src/store-context.ts +0 -23
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ClientSession, EventId, IntentionalShutdownCause, UnexpectedError } from '@livestore/common'
|
|
2
|
+
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
3
|
+
import type { FiberSet, MutableHashMap, Runtime, Scope } from '@livestore/utils/effect'
|
|
4
|
+
import { Schema } from '@livestore/utils/effect'
|
|
5
|
+
import type * as otel from '@opentelemetry/api'
|
|
6
|
+
import type { GraphQLSchema } from 'graphql'
|
|
7
|
+
|
|
8
|
+
import type { DebugRefreshReasonBase } from '../reactive.js'
|
|
9
|
+
import type { ReactivityGraph } from '../reactiveQueries/base-class.js'
|
|
10
|
+
import type { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
|
11
|
+
import type { StackInfo } from '../utils/stack-info.js'
|
|
12
|
+
import type { Store } from './store.js'
|
|
13
|
+
|
|
14
|
+
export type LiveStoreContext =
|
|
15
|
+
| LiveStoreContextRunning
|
|
16
|
+
| {
|
|
17
|
+
stage: 'error'
|
|
18
|
+
error: UnexpectedError | unknown
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
stage: 'shutdown'
|
|
22
|
+
cause: IntentionalShutdownCause | StoreAbort
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class StoreAbort extends Schema.TaggedError<StoreAbort>()('LiveStore.StoreAbort', {}) {}
|
|
26
|
+
export class StoreInterrupted extends Schema.TaggedError<StoreInterrupted>()('LiveStore.StoreInterrupted', {}) {}
|
|
27
|
+
|
|
28
|
+
export type LiveStoreContextRunning = {
|
|
29
|
+
stage: 'running'
|
|
30
|
+
store: Store
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type BaseGraphQLContext = {
|
|
34
|
+
queriedTables: Set<string>
|
|
35
|
+
/** Needed by Pothos Otel plugin for resolver tracing to work */
|
|
36
|
+
otelContext?: otel.Context
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type GraphQLOptions<TContext> = {
|
|
40
|
+
schema: GraphQLSchema
|
|
41
|
+
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type OtelOptions = {
|
|
45
|
+
tracer: otel.Tracer
|
|
46
|
+
rootSpanContext: otel.Context
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type StoreOptions<
|
|
50
|
+
TGraphQLContext extends BaseGraphQLContext,
|
|
51
|
+
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
52
|
+
> = {
|
|
53
|
+
clientSession: ClientSession
|
|
54
|
+
schema: TSchema
|
|
55
|
+
storeId: string
|
|
56
|
+
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
57
|
+
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
58
|
+
otelOptions: OtelOptions
|
|
59
|
+
reactivityGraph: ReactivityGraph
|
|
60
|
+
disableDevtools?: boolean
|
|
61
|
+
fiberSet: FiberSet.FiberSet
|
|
62
|
+
runtime: Runtime.Runtime<Scope.Scope>
|
|
63
|
+
batchUpdates: (runUpdates: () => void) => void
|
|
64
|
+
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type RefreshReason =
|
|
68
|
+
| DebugRefreshReasonBase
|
|
69
|
+
| {
|
|
70
|
+
_tag: 'mutate'
|
|
71
|
+
/** The mutations that were applied */
|
|
72
|
+
mutations: ReadonlyArray<MutationEvent.Any>
|
|
73
|
+
|
|
74
|
+
/** The tables that were written to by the event */
|
|
75
|
+
writeTables: ReadonlyArray<string>
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
// TODO rename to a more appropriate name which is framework-agnostic
|
|
79
|
+
_tag: 'react'
|
|
80
|
+
api: string
|
|
81
|
+
label?: string
|
|
82
|
+
stackInfo?: StackInfo
|
|
83
|
+
}
|
|
84
|
+
| { _tag: 'manual'; label?: string }
|
|
85
|
+
|
|
86
|
+
export type QueryDebugInfo = {
|
|
87
|
+
_tag: 'graphql' | 'sql' | 'computed' | 'unknown'
|
|
88
|
+
label: string
|
|
89
|
+
query: string
|
|
90
|
+
durationMs: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type StoreOtel = {
|
|
94
|
+
tracer: otel.Tracer
|
|
95
|
+
mutationsSpanContext: otel.Context
|
|
96
|
+
queriesSpanContext: otel.Context
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type StoreMutateOptions = {
|
|
100
|
+
label?: string
|
|
101
|
+
skipRefresh?: boolean
|
|
102
|
+
wasSyncMessage?: boolean
|
|
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
|
|
110
|
+
}
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
BootDb,
|
|
4
|
-
BootStatus,
|
|
5
|
-
ClientSession,
|
|
6
|
-
EventId,
|
|
7
|
-
IntentionalShutdownCause,
|
|
8
|
-
ParamsObject,
|
|
9
|
-
PreparedBindValues,
|
|
10
|
-
StoreDevtoolsChannel,
|
|
11
|
-
} from '@livestore/common'
|
|
12
|
-
import { getExecArgsFromMutation, prepareBindValues, replaceSessionIdSymbol, UnexpectedError } from '@livestore/common'
|
|
1
|
+
import type { ClientSession, ParamsObject } from '@livestore/common'
|
|
2
|
+
import { getExecArgsFromMutation, prepareBindValues, replaceSessionIdSymbol } from '@livestore/common'
|
|
13
3
|
import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
|
|
14
4
|
import {
|
|
15
5
|
isPartialMutationEvent,
|
|
@@ -18,124 +8,22 @@ import {
|
|
|
18
8
|
SCHEMA_MUTATIONS_META_TABLE,
|
|
19
9
|
SESSION_CHANGESET_META_TABLE,
|
|
20
10
|
} from '@livestore/common/schema'
|
|
21
|
-
import { assertNever,
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
Data,
|
|
25
|
-
Deferred,
|
|
26
|
-
Duration,
|
|
27
|
-
Effect,
|
|
28
|
-
Exit,
|
|
29
|
-
FiberSet,
|
|
30
|
-
Inspectable,
|
|
31
|
-
Layer,
|
|
32
|
-
Logger,
|
|
33
|
-
LogLevel,
|
|
34
|
-
MutableHashMap,
|
|
35
|
-
OtelTracer,
|
|
36
|
-
Queue,
|
|
37
|
-
Runtime,
|
|
38
|
-
Schema,
|
|
39
|
-
Scope,
|
|
40
|
-
Stream,
|
|
41
|
-
} from '@livestore/utils/effect'
|
|
11
|
+
import { assertNever, shouldNeverHappen } from '@livestore/utils'
|
|
12
|
+
import type { Scope } from '@livestore/utils/effect'
|
|
13
|
+
import { Data, Effect, FiberSet, Inspectable, MutableHashMap, Runtime, Schema, Stream } from '@livestore/utils/effect'
|
|
42
14
|
import * as otel from '@opentelemetry/api'
|
|
43
15
|
import type { GraphQLSchema } from 'graphql'
|
|
44
16
|
|
|
45
|
-
import {
|
|
46
|
-
import type {
|
|
47
|
-
import
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import { getDurationMsFromSpan } from './utils/otel.js'
|
|
53
|
-
import type { StackInfo } from './utils/stack-info.js'
|
|
54
|
-
|
|
55
|
-
export type BaseGraphQLContext = {
|
|
56
|
-
queriedTables: Set<string>
|
|
57
|
-
/** Needed by Pothos Otel plugin for resolver tracing to work */
|
|
58
|
-
otelContext?: otel.Context
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type GraphQLOptions<TContext> = {
|
|
62
|
-
schema: GraphQLSchema
|
|
63
|
-
makeContext: (db: SynchronousDatabaseWrapper, tracer: otel.Tracer, sessionId: string) => TContext
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type OtelOptions = {
|
|
67
|
-
tracer: otel.Tracer
|
|
68
|
-
rootSpanContext: otel.Context
|
|
69
|
-
}
|
|
17
|
+
import type { Ref } from '../reactive.js'
|
|
18
|
+
import type { LiveQuery, QueryContext, ReactivityGraph } from '../reactiveQueries/base-class.js'
|
|
19
|
+
import { SynchronousDatabaseWrapper } from '../SynchronousDatabaseWrapper.js'
|
|
20
|
+
import { ReferenceCountedSet } from '../utils/data-structures.js'
|
|
21
|
+
import { downloadBlob, exposeDebugUtils } from '../utils/dev.js'
|
|
22
|
+
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
23
|
+
import type { BaseGraphQLContext, RefreshReason, StoreMutateOptions, StoreOptions, StoreOtel } from './store-types.js'
|
|
70
24
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
74
|
-
> = {
|
|
75
|
-
clientSession: ClientSession
|
|
76
|
-
schema: TSchema
|
|
77
|
-
storeId: string
|
|
78
|
-
// TODO remove graphql-related stuff from store and move to GraphQL query directly
|
|
79
|
-
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
80
|
-
otelOptions: OtelOptions
|
|
81
|
-
reactivityGraph: ReactivityGraph
|
|
82
|
-
disableDevtools?: boolean
|
|
83
|
-
fiberSet: FiberSet.FiberSet
|
|
84
|
-
runtime: Runtime.Runtime<Scope.Scope>
|
|
85
|
-
batchUpdates: (runUpdates: () => void) => void
|
|
86
|
-
currentMutationEventIdRef: { current: EventId }
|
|
87
|
-
unsyncedMutationEvents: MutableHashMap.MutableHashMap<EventId, MutationEvent.ForSchema<TSchema>>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export type RefreshReason =
|
|
91
|
-
| DebugRefreshReasonBase
|
|
92
|
-
| {
|
|
93
|
-
_tag: 'mutate'
|
|
94
|
-
/** The mutations that were applied */
|
|
95
|
-
mutations: ReadonlyArray<MutationEvent.Any>
|
|
96
|
-
|
|
97
|
-
/** The tables that were written to by the event */
|
|
98
|
-
writeTables: ReadonlyArray<string>
|
|
99
|
-
}
|
|
100
|
-
| {
|
|
101
|
-
// TODO rename to a more appropriate name which is framework-agnostic
|
|
102
|
-
_tag: 'react'
|
|
103
|
-
api: string
|
|
104
|
-
label?: string
|
|
105
|
-
stackInfo?: StackInfo
|
|
106
|
-
}
|
|
107
|
-
| { _tag: 'manual'; label?: string }
|
|
108
|
-
|
|
109
|
-
export type QueryDebugInfo = {
|
|
110
|
-
_tag: 'graphql' | 'sql' | 'js' | 'unknown'
|
|
111
|
-
label: string
|
|
112
|
-
query: string
|
|
113
|
-
durationMs: number
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export type StoreOtel = {
|
|
117
|
-
tracer: otel.Tracer
|
|
118
|
-
mutationsSpanContext: otel.Context
|
|
119
|
-
queriesSpanContext: otel.Context
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export type StoreMutateOptions = {
|
|
123
|
-
label?: string
|
|
124
|
-
skipRefresh?: boolean
|
|
125
|
-
wasSyncMessage?: boolean
|
|
126
|
-
/**
|
|
127
|
-
* When set to `false` the mutation won't be persisted in the mutation log and sync server (but still synced).
|
|
128
|
-
* This can be useful e.g. for fine-granular update events (e.g. position updates during drag & drop)
|
|
129
|
-
*
|
|
130
|
-
* @default true
|
|
131
|
-
*/
|
|
132
|
-
persisted?: boolean
|
|
133
|
-
}
|
|
134
|
-
|
|
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
|
|
138
|
-
window.__debugDownloadBlob = downloadBlob
|
|
25
|
+
if (import.meta.env.DEV) {
|
|
26
|
+
exposeDebugUtils()
|
|
139
27
|
}
|
|
140
28
|
|
|
141
29
|
export class Store<
|
|
@@ -164,8 +52,6 @@ export class Store<
|
|
|
164
52
|
|
|
165
53
|
// NOTE this is currently exposed for the Devtools databrowser to emit mutation events
|
|
166
54
|
readonly __mutationEventSchema
|
|
167
|
-
|
|
168
|
-
private currentMutationEventIdRef
|
|
169
55
|
private unsyncedMutationEvents
|
|
170
56
|
|
|
171
57
|
// #region constructor
|
|
@@ -177,7 +63,6 @@ export class Store<
|
|
|
177
63
|
otelOptions,
|
|
178
64
|
disableDevtools,
|
|
179
65
|
batchUpdates,
|
|
180
|
-
currentMutationEventIdRef,
|
|
181
66
|
unsyncedMutationEvents,
|
|
182
67
|
storeId,
|
|
183
68
|
fiberSet,
|
|
@@ -187,7 +72,6 @@ export class Store<
|
|
|
187
72
|
|
|
188
73
|
this.storeId = storeId
|
|
189
74
|
|
|
190
|
-
this.currentMutationEventIdRef = currentMutationEventIdRef
|
|
191
75
|
this.unsyncedMutationEvents = unsyncedMutationEvents
|
|
192
76
|
|
|
193
77
|
this.syncDbWrapper = new SynchronousDatabaseWrapper({ otel: otelOptions, db: clientSession.syncDb })
|
|
@@ -271,18 +155,20 @@ export class Store<
|
|
|
271
155
|
|
|
272
156
|
yield* Effect.addFinalizer(() =>
|
|
273
157
|
Effect.sync(() => {
|
|
158
|
+
// Remove all table refs from the reactivity graph
|
|
274
159
|
for (const tableRef of Object.values(this.tableRefs)) {
|
|
275
160
|
for (const superComp of tableRef.super) {
|
|
276
161
|
this.reactivityGraph.removeEdge(superComp, tableRef)
|
|
277
162
|
}
|
|
278
163
|
}
|
|
279
164
|
|
|
165
|
+
// End the otel spans
|
|
280
166
|
otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
|
|
281
167
|
otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
|
|
282
168
|
}),
|
|
283
169
|
)
|
|
284
170
|
|
|
285
|
-
yield* Effect.never
|
|
171
|
+
yield* Effect.never // to keep the scope alive and bind to the parent scope
|
|
286
172
|
}).pipe(Effect.scoped, Effect.withSpan('LiveStore:constructor'), this.runEffectFork)
|
|
287
173
|
}
|
|
288
174
|
// #endregion constructor
|
|
@@ -501,6 +387,7 @@ export class Store<
|
|
|
501
387
|
|
|
502
388
|
return res
|
|
503
389
|
}
|
|
390
|
+
// #endregion mutate
|
|
504
391
|
|
|
505
392
|
/**
|
|
506
393
|
* This can be used in combination with `skipRefresh` when applying mutations.
|
|
@@ -520,6 +407,7 @@ export class Store<
|
|
|
520
407
|
)
|
|
521
408
|
}
|
|
522
409
|
|
|
410
|
+
// #region mutateWithoutRefresh
|
|
523
411
|
/**
|
|
524
412
|
* Apply a mutation to the store.
|
|
525
413
|
* Returns the tables that were affected by the event.
|
|
@@ -544,8 +432,6 @@ export class Store<
|
|
|
544
432
|
.nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
|
|
545
433
|
.pipe(Effect.runSync)
|
|
546
434
|
|
|
547
|
-
this.currentMutationEventIdRef.current = id
|
|
548
|
-
|
|
549
435
|
return { id, parentId }
|
|
550
436
|
}
|
|
551
437
|
|
|
@@ -614,7 +500,7 @@ export class Store<
|
|
|
614
500
|
},
|
|
615
501
|
)
|
|
616
502
|
}
|
|
617
|
-
// #endregion
|
|
503
|
+
// #endregion mutateWithoutRefresh
|
|
618
504
|
|
|
619
505
|
/**
|
|
620
506
|
* Directly execute a SQL query on the Store.
|
|
@@ -654,10 +540,12 @@ export class Store<
|
|
|
654
540
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`)
|
|
655
541
|
}).pipe(this.runEffectFork)
|
|
656
542
|
|
|
543
|
+
__devCurrentMutationEventId = () => this.clientSession.coordinator.getCurrentMutationEventId.pipe(Effect.runSync)
|
|
544
|
+
|
|
657
545
|
// NOTE This is needed because when booting a Store via Effect it seems to call `toJSON` in the error path
|
|
658
546
|
toJSON = () => {
|
|
659
547
|
return {
|
|
660
|
-
_tag: 'Store',
|
|
548
|
+
_tag: 'livestore.Store',
|
|
661
549
|
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
662
550
|
}
|
|
663
551
|
}
|
|
@@ -665,260 +553,3 @@ export class Store<
|
|
|
665
553
|
private runEffectFork = <A, E>(effect: Effect.Effect<A, E, never>) =>
|
|
666
554
|
effect.pipe(Effect.tapCauseLogPretty, FiberSet.run(this.fiberSet), Runtime.runFork(this.runtime))
|
|
667
555
|
}
|
|
668
|
-
|
|
669
|
-
export type CreateStoreOptions<TGraphQLContext extends BaseGraphQLContext, TSchema extends LiveStoreSchema> = {
|
|
670
|
-
schema: TSchema
|
|
671
|
-
adapter: Adapter
|
|
672
|
-
storeId: string
|
|
673
|
-
reactivityGraph?: ReactivityGraph
|
|
674
|
-
graphQLOptions?: GraphQLOptions<TGraphQLContext>
|
|
675
|
-
otelOptions?: Partial<OtelOptions>
|
|
676
|
-
boot?: (db: BootDb, parentSpan: otel.Span) => void | Promise<void> | Effect.Effect<void, unknown, otel.Tracer>
|
|
677
|
-
batchUpdates?: (run: () => void) => void
|
|
678
|
-
disableDevtools?: boolean
|
|
679
|
-
onBootStatus?: (status: BootStatus) => void
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
/** Create a new LiveStore Store */
|
|
683
|
-
export const createStorePromise = async <
|
|
684
|
-
TGraphQLContext extends BaseGraphQLContext,
|
|
685
|
-
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
686
|
-
>({
|
|
687
|
-
signal,
|
|
688
|
-
...options
|
|
689
|
-
}: CreateStoreOptions<TGraphQLContext, TSchema> & { signal?: AbortSignal }): Promise<Store<TGraphQLContext, TSchema>> =>
|
|
690
|
-
Effect.gen(function* () {
|
|
691
|
-
const scope = yield* Scope.make()
|
|
692
|
-
const runtime = yield* Effect.runtime()
|
|
693
|
-
|
|
694
|
-
if (signal !== undefined) {
|
|
695
|
-
signal.addEventListener('abort', () => {
|
|
696
|
-
Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime))
|
|
697
|
-
})
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
return yield* FiberSet.make().pipe(
|
|
701
|
-
Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })),
|
|
702
|
-
Scope.extend(scope),
|
|
703
|
-
)
|
|
704
|
-
}).pipe(
|
|
705
|
-
Effect.withSpan('createStore'),
|
|
706
|
-
Effect.tapCauseLogPretty,
|
|
707
|
-
Effect.annotateLogs({ thread: 'window' }),
|
|
708
|
-
Effect.provide(Logger.pretty),
|
|
709
|
-
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
710
|
-
Effect.runPromise,
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
// #region createStore
|
|
714
|
-
export const createStore = <
|
|
715
|
-
TGraphQLContext extends BaseGraphQLContext,
|
|
716
|
-
TSchema extends LiveStoreSchema = LiveStoreSchema,
|
|
717
|
-
>({
|
|
718
|
-
schema,
|
|
719
|
-
adapter,
|
|
720
|
-
storeId,
|
|
721
|
-
graphQLOptions,
|
|
722
|
-
otelOptions,
|
|
723
|
-
boot,
|
|
724
|
-
reactivityGraph = globalReactivityGraph,
|
|
725
|
-
batchUpdates,
|
|
726
|
-
disableDevtools,
|
|
727
|
-
onBootStatus,
|
|
728
|
-
fiberSet,
|
|
729
|
-
}: CreateStoreOptions<TGraphQLContext, TSchema> & { fiberSet: FiberSet.FiberSet }): Effect.Effect<
|
|
730
|
-
Store<TGraphQLContext, TSchema>,
|
|
731
|
-
UnexpectedError,
|
|
732
|
-
Scope.Scope
|
|
733
|
-
> => {
|
|
734
|
-
const otelTracer = otelOptions?.tracer ?? makeNoopTracer()
|
|
735
|
-
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active()
|
|
736
|
-
|
|
737
|
-
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
|
|
738
|
-
Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)),
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
return Effect.gen(function* () {
|
|
742
|
-
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie)
|
|
743
|
-
|
|
744
|
-
const bootStatusQueue = yield* Queue.unbounded<BootStatus>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
745
|
-
|
|
746
|
-
yield* Queue.take(bootStatusQueue).pipe(
|
|
747
|
-
Effect.tapSync((status) => onBootStatus?.(status)),
|
|
748
|
-
Effect.tap((status) => (status.stage === 'done' ? Queue.shutdown(bootStatusQueue) : Effect.void)),
|
|
749
|
-
Effect.forever,
|
|
750
|
-
Effect.tapCauseLogPretty,
|
|
751
|
-
Effect.forkScoped,
|
|
752
|
-
)
|
|
753
|
-
|
|
754
|
-
const storeDeferred = yield* Deferred.make<Store>()
|
|
755
|
-
|
|
756
|
-
const connectDevtoolsToStore_ = (storeDevtoolsChannel: StoreDevtoolsChannel) =>
|
|
757
|
-
Effect.gen(function* () {
|
|
758
|
-
const store = yield* Deferred.await(storeDeferred)
|
|
759
|
-
yield* connectDevtoolsToStore({ storeDevtoolsChannel, store })
|
|
760
|
-
})
|
|
761
|
-
|
|
762
|
-
const runtime = yield* Effect.runtime<Scope.Scope>()
|
|
763
|
-
|
|
764
|
-
const runEffectFork = (effect: Effect.Effect<any, any, never>) =>
|
|
765
|
-
effect.pipe(Effect.tapCauseLogPretty, FiberSet.run(fiberSet), Runtime.runFork(runtime))
|
|
766
|
-
|
|
767
|
-
// TODO close parent scope? (Needs refactor with Mike A)
|
|
768
|
-
const shutdown = (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) =>
|
|
769
|
-
Effect.gen(function* () {
|
|
770
|
-
// NOTE we're calling `cause.toString()` here to avoid triggering a `console.error` in the grouped log
|
|
771
|
-
const logCause =
|
|
772
|
-
Cause.isFailType(cause) && cause.error._tag === 'LiveStore.IntentionalShutdownCause'
|
|
773
|
-
? cause.toString()
|
|
774
|
-
: cause
|
|
775
|
-
yield* Effect.logDebug(`Shutting down LiveStore`, logCause)
|
|
776
|
-
|
|
777
|
-
FiberSet.clear(fiberSet).pipe(
|
|
778
|
-
Effect.andThen(() => FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
779
|
-
Effect.timeout(Duration.seconds(1)),
|
|
780
|
-
Effect.logWarnIfTakesLongerThan({ label: '@livestore/livestore:shutdown:clear-fiber-set', duration: 500 }),
|
|
781
|
-
Effect.catchTag('TimeoutException', (err) =>
|
|
782
|
-
Effect.logError('Store shutdown timed out. Forcing shutdown.', err).pipe(
|
|
783
|
-
Effect.andThen(FiberSet.run(fiberSet, Effect.failCause(cause))),
|
|
784
|
-
),
|
|
785
|
-
),
|
|
786
|
-
Runtime.runFork(runtime), // NOTE we need to fork this separately otherwise it will also be interrupted
|
|
787
|
-
)
|
|
788
|
-
}).pipe(Effect.withSpan('livestore:shutdown'))
|
|
789
|
-
|
|
790
|
-
const clientSession: ClientSession = yield* adapter({
|
|
791
|
-
schema,
|
|
792
|
-
storeId,
|
|
793
|
-
devtoolsEnabled: disableDevtools !== true,
|
|
794
|
-
bootStatusQueue,
|
|
795
|
-
shutdown,
|
|
796
|
-
connectDevtoolsToStore: connectDevtoolsToStore_,
|
|
797
|
-
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'))
|
|
798
|
-
|
|
799
|
-
const mutationEventSchema = makeMutationEventSchemaMemo(schema)
|
|
800
|
-
|
|
801
|
-
// TODO get rid of this
|
|
802
|
-
// const __processedMutationIds = new Set<number>()
|
|
803
|
-
|
|
804
|
-
const currentMutationEventIdRef = { current: yield* clientSession.coordinator.getCurrentMutationEventId }
|
|
805
|
-
|
|
806
|
-
// TODO fill up with unsynced mutation events from the coordinator
|
|
807
|
-
const unsyncedMutationEvents = MutableHashMap.empty<EventId, MutationEvent.ForSchema<TSchema>>()
|
|
808
|
-
|
|
809
|
-
// TODO consider moving booting into the storage backend
|
|
810
|
-
if (boot !== undefined) {
|
|
811
|
-
let isInTxn = false
|
|
812
|
-
let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
|
|
813
|
-
|
|
814
|
-
const bootDbImpl: BootDb = {
|
|
815
|
-
_tag: 'BootDb',
|
|
816
|
-
execute: (queryStr, bindValues) => {
|
|
817
|
-
const stmt = clientSession.syncDb.prepare(queryStr)
|
|
818
|
-
stmt.execute(bindValues)
|
|
819
|
-
|
|
820
|
-
if (isInTxn === true) {
|
|
821
|
-
txnExecuteStmnts.push([queryStr, bindValues])
|
|
822
|
-
} else {
|
|
823
|
-
clientSession.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
824
|
-
}
|
|
825
|
-
},
|
|
826
|
-
mutate: (...list) => {
|
|
827
|
-
for (const mutationEventDecoded_ of list) {
|
|
828
|
-
const mutationDef =
|
|
829
|
-
schema.mutations.get(mutationEventDecoded_.mutation) ??
|
|
830
|
-
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded_.mutation}`)
|
|
831
|
-
|
|
832
|
-
const { id, parentId } = clientSession.coordinator
|
|
833
|
-
.nextMutationEventIdPair({ localOnly: mutationDef.options.localOnly })
|
|
834
|
-
.pipe(Effect.runSync)
|
|
835
|
-
|
|
836
|
-
currentMutationEventIdRef.current = id
|
|
837
|
-
|
|
838
|
-
const mutationEventDecoded = { ...mutationEventDecoded_, id, parentId }
|
|
839
|
-
|
|
840
|
-
replaceSessionIdSymbol(mutationEventDecoded.args, clientSession.coordinator.sessionId)
|
|
841
|
-
|
|
842
|
-
MutableHashMap.set(unsyncedMutationEvents, Data.struct(mutationEventDecoded.id), mutationEventDecoded)
|
|
843
|
-
|
|
844
|
-
// __processedMutationIds.add(mutationEventDecoded.id.global)
|
|
845
|
-
|
|
846
|
-
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
847
|
-
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
848
|
-
|
|
849
|
-
for (const { statementSql, bindValues } of execArgsArr) {
|
|
850
|
-
clientSession.syncDb.execute(statementSql, bindValues)
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
|
|
854
|
-
|
|
855
|
-
clientSession.coordinator
|
|
856
|
-
.mutate(mutationEventEncoded as MutationEvent.AnyEncoded, { persisted: true })
|
|
857
|
-
.pipe(runEffectFork)
|
|
858
|
-
}
|
|
859
|
-
},
|
|
860
|
-
select: (queryStr, bindValues) => {
|
|
861
|
-
const stmt = clientSession.syncDb.prepare(queryStr)
|
|
862
|
-
return stmt.select(bindValues)
|
|
863
|
-
},
|
|
864
|
-
txn: (callback) => {
|
|
865
|
-
try {
|
|
866
|
-
isInTxn = true
|
|
867
|
-
// clientSession.syncDb.execute('BEGIN TRANSACTION', undefined)
|
|
868
|
-
|
|
869
|
-
callback()
|
|
870
|
-
|
|
871
|
-
// clientSession.syncDb.execute('COMMIT', undefined)
|
|
872
|
-
|
|
873
|
-
// clientSession.coordinator.execute('BEGIN', undefined, undefined)
|
|
874
|
-
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
875
|
-
clientSession.coordinator.execute(queryStr, bindValues).pipe(runEffectFork)
|
|
876
|
-
}
|
|
877
|
-
// clientSession.coordinator.execute('COMMIT', undefined, undefined)
|
|
878
|
-
} catch (e: any) {
|
|
879
|
-
// clientSession.syncDb.execute('ROLLBACK', undefined)
|
|
880
|
-
throw e
|
|
881
|
-
} finally {
|
|
882
|
-
isInTxn = false
|
|
883
|
-
txnExecuteStmnts = []
|
|
884
|
-
}
|
|
885
|
-
},
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
yield* Effect.tryAll(() => boot(bootDbImpl, span)).pipe(
|
|
889
|
-
UnexpectedError.mapToUnexpectedError,
|
|
890
|
-
Effect.withSpan('createStore:boot'),
|
|
891
|
-
)
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const store = Store.createStore<TGraphQLContext, TSchema>(
|
|
895
|
-
{
|
|
896
|
-
clientSession,
|
|
897
|
-
schema,
|
|
898
|
-
graphQLOptions,
|
|
899
|
-
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
900
|
-
reactivityGraph,
|
|
901
|
-
disableDevtools,
|
|
902
|
-
currentMutationEventIdRef,
|
|
903
|
-
unsyncedMutationEvents,
|
|
904
|
-
fiberSet,
|
|
905
|
-
runtime,
|
|
906
|
-
batchUpdates: batchUpdates ?? ((run) => run()),
|
|
907
|
-
storeId,
|
|
908
|
-
},
|
|
909
|
-
span,
|
|
910
|
-
)
|
|
911
|
-
|
|
912
|
-
yield* Deferred.succeed(storeDeferred, store as any as Store)
|
|
913
|
-
|
|
914
|
-
return store
|
|
915
|
-
}).pipe(
|
|
916
|
-
Effect.withSpan('createStore', {
|
|
917
|
-
parent: otelOptions?.rootSpanContext
|
|
918
|
-
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext)!)
|
|
919
|
-
: undefined,
|
|
920
|
-
}),
|
|
921
|
-
Effect.provide(TracingLive),
|
|
922
|
-
)
|
|
923
|
-
}
|
|
924
|
-
// #endregion createStore
|
package/src/utils/dev.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { FromInputSchema } from '@livestore/common/schema'
|
|
2
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'
|
|
3
|
+
import { createStore, DbSchema, globalReactivityGraph, makeReactivityGraph, makeSchema } from '@livestore/livestore'
|
|
11
4
|
import { Effect, FiberSet } from '@livestore/utils/effect'
|
|
12
5
|
import { makeInMemoryAdapter } from '@livestore/web'
|
|
13
6
|
import type * as otel from '@opentelemetry/api'
|
|
@@ -35,11 +28,15 @@ export const todos = DbSchema.table(
|
|
|
35
28
|
{ deriveMutations: true, isSingleton: false },
|
|
36
29
|
)
|
|
37
30
|
|
|
38
|
-
export const app = DbSchema.table(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
31
|
+
export const app = DbSchema.table(
|
|
32
|
+
'app',
|
|
33
|
+
{
|
|
34
|
+
id: DbSchema.text({ primaryKey: true, default: 'static' }),
|
|
35
|
+
newTodoText: DbSchema.text({ default: '', nullable: true }),
|
|
36
|
+
filter: DbSchema.text({ default: 'all', nullable: false }),
|
|
37
|
+
},
|
|
38
|
+
{ isSingleton: true },
|
|
39
|
+
)
|
|
43
40
|
|
|
44
41
|
export const tables = { todos, app }
|
|
45
42
|
export const schema = makeSchema({ tables })
|
|
@@ -63,7 +60,6 @@ export const makeTodoMvc = ({
|
|
|
63
60
|
const store: Store<any, FixtureSchema> = yield* createStore({
|
|
64
61
|
schema,
|
|
65
62
|
storeId: 'default',
|
|
66
|
-
boot: (db) => db.execute(sql`INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');`),
|
|
67
63
|
adapter: makeInMemoryAdapter(),
|
|
68
64
|
reactivityGraph,
|
|
69
65
|
otelOptions: {
|