@livestore/livestore 0.0.54-dev.5 → 0.0.55-dev.0
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/MainDatabaseWrapper.d.ts +6 -5
- package/dist/MainDatabaseWrapper.d.ts.map +1 -1
- package/dist/MainDatabaseWrapper.js +3 -3
- package/dist/MainDatabaseWrapper.js.map +1 -1
- package/dist/QueryCache.d.ts +1 -1
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +9 -27
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +12 -10
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +20 -12
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +23 -22
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/index.d.ts +1 -1
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +1 -1
- package/dist/effect/index.js.map +1 -1
- package/dist/global-state.d.ts +1 -3
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +2 -3
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/react/LiveStoreContext.d.ts +5 -2
- package/dist/react/LiveStoreContext.d.ts.map +1 -1
- package/dist/react/LiveStoreContext.js +3 -0
- package/dist/react/LiveStoreContext.js.map +1 -1
- package/dist/react/LiveStoreProvider.d.ts +8 -7
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +70 -43
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/LiveStoreProvider.test.js +33 -12
- package/dist/react/LiveStoreProvider.test.js.map +1 -1
- package/dist/react/components/LiveList.d.ts.map +1 -1
- package/dist/react/useAtom.d.ts +1 -1
- package/dist/react/useAtom.d.ts.map +1 -1
- package/dist/react/useLocalId.d.ts.map +1 -1
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +2 -2
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useRow.d.ts +2 -2
- package/dist/react/useRow.d.ts.map +1 -1
- package/dist/react/useRow.js +5 -5
- package/dist/react/useRow.js.map +1 -1
- package/dist/react/useRow.test.js +22 -22
- package/dist/react/useRow.test.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts.map +1 -1
- package/dist/react/useTemporaryQuery.js +1 -1
- package/dist/react/useTemporaryQuery.js.map +1 -1
- package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
- package/dist/reactive.d.ts +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +4 -5
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +6 -6
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +3 -3
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graphql.d.ts +8 -8
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +10 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +6 -6
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +8 -8
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +9 -10
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +12 -12
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/reactiveQueries/sql.test.js +6 -6
- package/dist/reactiveQueries/sql.test.js.map +1 -1
- package/dist/row-query.d.ts +2 -2
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +4 -38
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +41 -24
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +336 -223
- package/dist/store.js.map +1 -1
- package/dist/utils/otel.d.ts.map +1 -1
- package/package.json +10 -19
- package/src/MainDatabaseWrapper.ts +14 -8
- package/src/QueryCache.ts +1 -2
- package/src/__tests__/react/fixture.tsx +13 -11
- package/src/effect/LiveStore.ts +65 -54
- package/src/effect/index.ts +2 -1
- package/src/global-state.ts +2 -6
- package/src/index.ts +25 -7
- package/src/react/LiveStoreContext.ts +7 -2
- package/src/react/LiveStoreProvider.test.tsx +56 -14
- package/src/react/LiveStoreProvider.tsx +105 -46
- package/src/react/useQuery.ts +2 -2
- package/src/react/useRow.test.tsx +22 -22
- package/src/react/useRow.ts +7 -10
- package/src/react/useTemporaryQuery.ts +2 -2
- package/src/reactive.ts +6 -5
- package/src/reactiveQueries/base-class.ts +9 -9
- package/src/reactiveQueries/graphql.ts +19 -15
- package/src/reactiveQueries/js.ts +12 -12
- package/src/reactiveQueries/sql.test.ts +6 -6
- package/src/reactiveQueries/sql.ts +19 -21
- package/src/row-query.ts +8 -54
- package/src/store.ts +533 -284
- package/dist/utils/bounded-collections.d.ts +0 -34
- package/dist/utils/bounded-collections.d.ts.map +0 -1
- package/dist/utils/bounded-collections.js +0 -91
- package/dist/utils/bounded-collections.js.map +0 -1
- package/dist/utils/util.d.ts +0 -14
- package/dist/utils/util.d.ts.map +0 -1
- package/dist/utils/util.js +0 -19
- package/dist/utils/util.js.map +0 -1
- package/src/utils/util.ts +0 -31
package/dist/store.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { Devtools, getExecArgsFromMutation } from '@livestore/common';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Effect, Schema, Stream } from '@livestore/utils/effect';
|
|
1
|
+
import { Devtools, getExecArgsFromMutation, liveStoreVersion, prepareBindValues, UnexpectedError, } from '@livestore/common';
|
|
2
|
+
import { makeMutationEventSchemaMemo } from '@livestore/common/schema';
|
|
3
|
+
import { assertNever, makeNoopTracer, shouldNeverHappen, throttle } from '@livestore/utils';
|
|
4
|
+
import { cuid } from '@livestore/utils/cuid';
|
|
5
|
+
import { Effect, Exit, FiberSet, Inspectable, Layer, Logger, LogLevel, OtelTracer, Queue, Runtime, Schema, Scope, Stream, } from '@livestore/utils/effect';
|
|
6
6
|
import * as otel from '@opentelemetry/api';
|
|
7
|
-
import {
|
|
8
|
-
import { MainDatabaseWrapper } from './MainDatabaseWrapper.js';
|
|
7
|
+
import { globalReactivityGraph } from './global-state.js';
|
|
8
|
+
import { emptyDebugInfo as makeEmptyDebugInfo, MainDatabaseWrapper } from './MainDatabaseWrapper.js';
|
|
9
|
+
import { NOT_REFRESHED_YET } from './reactive.js';
|
|
9
10
|
import { downloadBlob } from './utils/dev.js';
|
|
10
11
|
import { getDurationMsFromSpan } from './utils/otel.js';
|
|
11
|
-
import { prepareBindValues } from './utils/util.js';
|
|
12
12
|
let storeCount = 0;
|
|
13
13
|
const uniqueStoreId = () => `store-${++storeCount}`;
|
|
14
|
-
export class Store {
|
|
14
|
+
export class Store extends Inspectable.Class {
|
|
15
15
|
id = uniqueStoreId();
|
|
16
|
-
|
|
16
|
+
devtoolsConnectionId = cuid();
|
|
17
|
+
fiberSet;
|
|
18
|
+
reactivityGraph;
|
|
17
19
|
mainDbWrapper;
|
|
18
|
-
// TODO refactor
|
|
19
|
-
// _proxyDb: InMemoryDatabase
|
|
20
|
-
// TODO
|
|
21
20
|
adapter;
|
|
22
21
|
schema;
|
|
23
22
|
graphQLSchema;
|
|
@@ -29,41 +28,42 @@ export class Store {
|
|
|
29
28
|
*/
|
|
30
29
|
tableRefs;
|
|
31
30
|
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
32
|
-
__processedMutationIds
|
|
31
|
+
__processedMutationIds;
|
|
33
32
|
__processedMutationWithoutRefreshIds = new Set();
|
|
34
33
|
/** RC-based set to see which queries are currently subscribed to */
|
|
35
34
|
activeQueries;
|
|
36
35
|
__mutationEventSchema;
|
|
37
|
-
constructor({ adapter, schema, graphQLOptions,
|
|
38
|
-
|
|
36
|
+
constructor({ adapter, schema, graphQLOptions, reactivityGraph, otelOptions, disableDevtools, __processedMutationIds, fiberSet, }) {
|
|
37
|
+
super();
|
|
38
|
+
this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb });
|
|
39
39
|
this.adapter = adapter;
|
|
40
40
|
this.schema = schema;
|
|
41
|
+
this.fiberSet = fiberSet;
|
|
41
42
|
// TODO refactor
|
|
42
|
-
this.__mutationEventSchema =
|
|
43
|
-
// this
|
|
43
|
+
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema);
|
|
44
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
45
|
+
this.__processedMutationIds = __processedMutationIds;
|
|
44
46
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
45
47
|
this.tableRefs = {};
|
|
46
48
|
this.activeQueries = new ReferenceCountedSet();
|
|
47
|
-
const mutationsSpan =
|
|
49
|
+
const mutationsSpan = otelOptions.tracer.startSpan('LiveStore:mutations', {}, otelOptions.rootSpanContext);
|
|
48
50
|
const otelMuationsSpanContext = otel.trace.setSpan(otel.context.active(), mutationsSpan);
|
|
49
|
-
const queriesSpan =
|
|
51
|
+
const queriesSpan = otelOptions.tracer.startSpan('LiveStore:queries', {}, otelOptions.rootSpanContext);
|
|
50
52
|
const otelQueriesSpanContext = otel.trace.setSpan(otel.context.active(), queriesSpan);
|
|
51
|
-
this.
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.bootDevtools();
|
|
58
|
-
}
|
|
53
|
+
this.reactivityGraph = reactivityGraph;
|
|
54
|
+
this.reactivityGraph.context = {
|
|
55
|
+
store: this,
|
|
56
|
+
otelTracer: otelOptions.tracer,
|
|
57
|
+
rootOtelContext: otelQueriesSpanContext,
|
|
58
|
+
};
|
|
59
59
|
this.otel = {
|
|
60
|
-
tracer:
|
|
60
|
+
tracer: otelOptions.tracer,
|
|
61
61
|
mutationsSpanContext: otelMuationsSpanContext,
|
|
62
62
|
queriesSpanContext: otelQueriesSpanContext,
|
|
63
63
|
};
|
|
64
64
|
// Need a set here since `schema.tables` might contain duplicates and some componentStateTables
|
|
65
65
|
const allTableNames = new Set(this.schema.tables.keys());
|
|
66
|
-
const existingTableRefs = new Map(Array.from(this.
|
|
66
|
+
const existingTableRefs = new Map(Array.from(this.reactivityGraph.atoms.values())
|
|
67
67
|
.filter((_) => _._tag === 'ref' && _.label?.startsWith('tableRef:') === true)
|
|
68
68
|
.map((_) => [_.label.slice('tableRef:'.length), _]));
|
|
69
69
|
for (const tableName of allTableNames) {
|
|
@@ -73,10 +73,28 @@ export class Store {
|
|
|
73
73
|
this.graphQLSchema = graphQLOptions.schema;
|
|
74
74
|
this.graphQLContext = graphQLOptions.makeContext(this.mainDbWrapper, this.otel.tracer);
|
|
75
75
|
}
|
|
76
|
+
Effect.gen(this, function* () {
|
|
77
|
+
yield* this.adapter.coordinator.syncMutations.pipe(Stream.tapSync((mutationEventDecoded) => {
|
|
78
|
+
this.mutate({ wasSyncMessage: true }, mutationEventDecoded);
|
|
79
|
+
}), Stream.runDrain, Effect.withSpan('LiveStore:syncMutations'), Effect.forkScoped);
|
|
80
|
+
if (disableDevtools !== true) {
|
|
81
|
+
yield* this.bootDevtools().pipe(Effect.forkScoped);
|
|
82
|
+
}
|
|
83
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
84
|
+
for (const tableRef of Object.values(this.tableRefs)) {
|
|
85
|
+
for (const superComp of tableRef.super) {
|
|
86
|
+
this.reactivityGraph.removeEdge(superComp, tableRef);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
otel.trace.getSpan(this.otel.mutationsSpanContext).end();
|
|
90
|
+
otel.trace.getSpan(this.otel.queriesSpanContext).end();
|
|
91
|
+
}));
|
|
92
|
+
yield* Effect.never;
|
|
93
|
+
}).pipe(Effect.scoped, Effect.withSpan('LiveStore:store-constructor'), FiberSet.run(fiberSet), runEffectFork);
|
|
76
94
|
}
|
|
77
95
|
static createStore = (storeOptions, parentSpan) => {
|
|
78
96
|
const ctx = otel.trace.setSpan(otel.context.active(), parentSpan);
|
|
79
|
-
return storeOptions.
|
|
97
|
+
return storeOptions.otelOptions.tracer.startActiveSpan('LiveStore:store-constructor', {}, ctx, (span) => {
|
|
80
98
|
try {
|
|
81
99
|
return new Store(storeOptions);
|
|
82
100
|
}
|
|
@@ -93,7 +111,7 @@ export class Store {
|
|
|
93
111
|
// console.log('store sub', query$.label)
|
|
94
112
|
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
95
113
|
const label = `subscribe:${options?.label}`;
|
|
96
|
-
const effect = this.
|
|
114
|
+
const effect = this.reactivityGraph.makeEffect((get) => onNewValue(get(query$.results$)), { label });
|
|
97
115
|
this.activeQueries.add(query$);
|
|
98
116
|
// Running effect right away to get initial value (unless `skipInitialRun` is set)
|
|
99
117
|
if (options?.skipInitialRun !== true) {
|
|
@@ -102,7 +120,7 @@ export class Store {
|
|
|
102
120
|
const unsubscribe = () => {
|
|
103
121
|
// console.log('store unsub', query$.label)
|
|
104
122
|
try {
|
|
105
|
-
this.
|
|
123
|
+
this.reactivityGraph.destroyNode(effect);
|
|
106
124
|
this.activeQueries.remove(query$);
|
|
107
125
|
onUnsubsubscribe?.();
|
|
108
126
|
}
|
|
@@ -118,14 +136,7 @@ export class Store {
|
|
|
118
136
|
* Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
|
|
119
137
|
*/
|
|
120
138
|
destroy = async () => {
|
|
121
|
-
|
|
122
|
-
for (const superComp of tableRef.super) {
|
|
123
|
-
this.graph.removeEdge(superComp, tableRef);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
otel.trace.getSpan(this.otel.mutationsSpanContext).end();
|
|
127
|
-
otel.trace.getSpan(this.otel.queriesSpanContext).end();
|
|
128
|
-
await this.adapter.coordinator.shutdown();
|
|
139
|
+
await FiberSet.clear(this.fiberSet).pipe(Effect.withSpan('Store:destroy'), runEffectPromise);
|
|
129
140
|
};
|
|
130
141
|
mutate = (firstMutationOrTxnFnOrOptions, ...restMutations) => {
|
|
131
142
|
let mutationsEvents;
|
|
@@ -164,6 +175,7 @@ export class Store {
|
|
|
164
175
|
// console.group('LiveStore.mutate', { skipRefresh, wasSyncMessage, label })
|
|
165
176
|
// mutationsEvents.forEach((_) => console.log(_.mutation, _.id, _.args))
|
|
166
177
|
// console.groupEnd()
|
|
178
|
+
let durationMs;
|
|
167
179
|
return this.otel.tracer.startActiveSpan('LiveStore:mutate', { attributes: { 'livestore.mutateLabel': label } }, this.otel.mutationsSpanContext, (span) => {
|
|
168
180
|
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
169
181
|
try {
|
|
@@ -184,8 +196,8 @@ export class Store {
|
|
|
184
196
|
}
|
|
185
197
|
}
|
|
186
198
|
catch (e) {
|
|
187
|
-
debugger;
|
|
188
199
|
console.error(e, mutationEvent);
|
|
200
|
+
throw e;
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
203
|
};
|
|
@@ -200,6 +212,7 @@ export class Store {
|
|
|
200
212
|
catch (e) {
|
|
201
213
|
console.error(e);
|
|
202
214
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
|
|
215
|
+
throw e;
|
|
203
216
|
}
|
|
204
217
|
finally {
|
|
205
218
|
span.end();
|
|
@@ -217,15 +230,18 @@ export class Store {
|
|
|
217
230
|
writeTables: Array.from(writeTables),
|
|
218
231
|
};
|
|
219
232
|
// Update all table refs together in a batch, to only trigger one reactive update
|
|
220
|
-
this.
|
|
233
|
+
this.reactivityGraph.setRefs(tablesToUpdate, { debugRefreshReason, otelContext, skipRefresh });
|
|
221
234
|
}
|
|
222
235
|
catch (e) {
|
|
236
|
+
console.error(e);
|
|
223
237
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: e.toString() });
|
|
238
|
+
throw e;
|
|
224
239
|
}
|
|
225
240
|
finally {
|
|
226
241
|
span.end();
|
|
227
|
-
|
|
242
|
+
durationMs = getDurationMsFromSpan(span);
|
|
228
243
|
}
|
|
244
|
+
return { durationMs };
|
|
229
245
|
});
|
|
230
246
|
};
|
|
231
247
|
/**
|
|
@@ -236,7 +252,7 @@ export class Store {
|
|
|
236
252
|
const { label } = options ?? {};
|
|
237
253
|
this.otel.tracer.startActiveSpan('LiveStore:manualRefresh', { attributes: { 'livestore.manualRefreshLabel': label } }, this.otel.mutationsSpanContext, (span) => {
|
|
238
254
|
const otelContext = otel.trace.setSpan(otel.context.active(), span);
|
|
239
|
-
this.
|
|
255
|
+
this.reactivityGraph.runDeferredEffects({ otelContext });
|
|
240
256
|
span.end();
|
|
241
257
|
});
|
|
242
258
|
};
|
|
@@ -279,10 +295,9 @@ export class Store {
|
|
|
279
295
|
const mutationEventEncoded = Schema.encodeUnknownSync(this.__mutationEventSchema)(mutationEventDecoded);
|
|
280
296
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
281
297
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
});
|
|
298
|
+
this.adapter.coordinator
|
|
299
|
+
.mutate(mutationEventEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
300
|
+
.pipe(runEffectFork);
|
|
286
301
|
}
|
|
287
302
|
// Uncomment to print a list of queries currently registered on the store
|
|
288
303
|
// console.debug(JSON.parse(JSON.stringify([...this.queries].map((q) => `${labelForKey(q.componentKey)}/${q.label}`))))
|
|
@@ -297,206 +312,302 @@ export class Store {
|
|
|
297
312
|
*/
|
|
298
313
|
execute = (query, params = {}, writeTables, otelContext) => {
|
|
299
314
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext });
|
|
300
|
-
|
|
301
|
-
this.adapter.coordinator.execute(query, prepareBindValues(params, query), parentSpan);
|
|
315
|
+
this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(runEffectFork);
|
|
302
316
|
};
|
|
303
317
|
select = (query, params = {}) => {
|
|
304
318
|
return this.mainDbWrapper.select(query, { bindValues: prepareBindValues(params, query) });
|
|
305
319
|
};
|
|
306
|
-
makeTableRef = (tableName) => this.
|
|
320
|
+
makeTableRef = (tableName) => this.reactivityGraph.makeRef(null, {
|
|
307
321
|
equal: () => false,
|
|
308
322
|
label: `tableRef:${tableName}`,
|
|
309
323
|
meta: { liveStoreRefType: 'table' },
|
|
310
324
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
325
|
+
// #region devtools
|
|
326
|
+
// TODO shutdown behaviour
|
|
327
|
+
bootDevtools = () => Effect.gen(this, function* () {
|
|
328
|
+
const sendToDevtoolsContentscript = (message) => {
|
|
329
|
+
window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*');
|
|
330
|
+
};
|
|
331
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.LoadIframe.make({}));
|
|
332
|
+
const channelId = this.adapter.coordinator.devtools.channelId;
|
|
333
|
+
const runtime = yield* Effect.runtime();
|
|
334
|
+
window.addEventListener('message', (event) => {
|
|
335
|
+
const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data);
|
|
336
|
+
if (decodedMessageRes._tag === 'None')
|
|
337
|
+
return;
|
|
338
|
+
const message = decodedMessageRes.value;
|
|
339
|
+
if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
|
|
340
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }));
|
|
323
341
|
return;
|
|
324
342
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
343
|
+
if (message.channelId !== channelId)
|
|
344
|
+
return;
|
|
345
|
+
if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
|
|
346
|
+
const reactivityGraphSubcriptions = new Map();
|
|
347
|
+
const liveQueriesSubscriptions = new Map();
|
|
348
|
+
const debugInfoHistorySubscriptions = new Map();
|
|
349
|
+
this.adapter.coordinator.devtools
|
|
350
|
+
.connect({ port: message.port, connectionId: this.devtoolsConnectionId })
|
|
351
|
+
.pipe(Effect.tapSync(({ storeMessagePort }) => {
|
|
352
|
+
// console.log('storeMessagePort', storeMessagePort)
|
|
353
|
+
storeMessagePort.addEventListener('message', (event) => {
|
|
354
|
+
const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data);
|
|
355
|
+
// console.log('storeMessagePort message', decodedMessage)
|
|
356
|
+
if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
357
|
+
// console.log(`Unknown message`, event)
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const requestId = decodedMessage.requestId;
|
|
361
|
+
const sendToDevtools = (message) => storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message));
|
|
362
|
+
const requestIdleCallback = window.requestIdleCallback ?? ((cb) => cb());
|
|
363
|
+
switch (decodedMessage._tag) {
|
|
364
|
+
case 'LSD.ReactivityGraphSubscribe': {
|
|
365
|
+
const includeResults = decodedMessage.includeResults;
|
|
366
|
+
const send = () =>
|
|
367
|
+
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
368
|
+
// to send the reactivity graph updates to the devtools
|
|
369
|
+
requestIdleCallback(() => sendToDevtools(Devtools.ReactivityGraphRes.make({
|
|
370
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
371
|
+
requestId,
|
|
372
|
+
liveStoreVersion,
|
|
373
|
+
})), { timeout: 500 });
|
|
374
|
+
send();
|
|
375
|
+
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
376
|
+
// so we throttle the updates to avoid sending too much data
|
|
377
|
+
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
378
|
+
const throttledSend = throttle(send, 20);
|
|
379
|
+
reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend));
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
case 'LSD.DebugInfoReq': {
|
|
383
|
+
sendToDevtools(Devtools.DebugInfoRes.make({
|
|
384
|
+
debugInfo: this.mainDbWrapper.debugInfo,
|
|
385
|
+
requestId,
|
|
386
|
+
liveStoreVersion,
|
|
387
|
+
}));
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case 'LSD.DebugInfoHistorySubscribe': {
|
|
391
|
+
const buffer = [];
|
|
392
|
+
let hasStopped = false;
|
|
393
|
+
let rafHandle;
|
|
394
|
+
const tick = () => {
|
|
395
|
+
buffer.push(this.mainDbWrapper.debugInfo);
|
|
396
|
+
// NOTE this resets the debug info, so all other "readers" e.g. in other `requestAnimationFrame` loops,
|
|
397
|
+
// will get the empty debug info
|
|
398
|
+
// TODO We need to come up with a more graceful way to do this. Probably via a single global
|
|
399
|
+
// `requestAnimationFrame` loop that is passed in somehow.
|
|
400
|
+
this.mainDbWrapper.debugInfo = makeEmptyDebugInfo();
|
|
401
|
+
if (buffer.length > 10) {
|
|
402
|
+
sendToDevtools(Devtools.DebugInfoHistoryRes.make({
|
|
403
|
+
debugInfoHistory: buffer,
|
|
404
|
+
requestId,
|
|
405
|
+
liveStoreVersion,
|
|
406
|
+
}));
|
|
407
|
+
buffer.length = 0;
|
|
408
|
+
}
|
|
409
|
+
if (hasStopped === false) {
|
|
410
|
+
rafHandle = requestAnimationFrame(tick);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
rafHandle = requestAnimationFrame(tick);
|
|
414
|
+
const unsub = () => {
|
|
415
|
+
hasStopped = true;
|
|
416
|
+
if (rafHandle !== undefined) {
|
|
417
|
+
cancelAnimationFrame(rafHandle);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
debugInfoHistorySubscriptions.set(requestId, unsub);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case 'LSD.DebugInfoHistoryUnsubscribe': {
|
|
424
|
+
debugInfoHistorySubscriptions.get(requestId)();
|
|
425
|
+
debugInfoHistorySubscriptions.delete(requestId);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case 'LSD.DebugInfoResetReq': {
|
|
429
|
+
this.mainDbWrapper.debugInfo.slowQueries.clear();
|
|
430
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }));
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case 'LSD.DebugInfoRerunQueryReq': {
|
|
434
|
+
const { queryStr, bindValues, queriedTables } = decodedMessage;
|
|
435
|
+
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true });
|
|
436
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }));
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
440
|
+
reactivityGraphSubcriptions.get(requestId)();
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case 'LSD.LiveQueriesSubscribe': {
|
|
444
|
+
const send = () => requestIdleCallback(() => sendToDevtools(Devtools.LiveQueriesRes.make({
|
|
445
|
+
liveQueries: [...this.activeQueries].map((q) => ({
|
|
446
|
+
_tag: q._tag,
|
|
447
|
+
id: q.id,
|
|
448
|
+
label: q.label,
|
|
449
|
+
runs: q.runs,
|
|
450
|
+
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
451
|
+
lastestResult: q.results$.previousResult === NOT_REFRESHED_YET
|
|
452
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
453
|
+
: q.results$.previousResult,
|
|
454
|
+
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
455
|
+
})),
|
|
456
|
+
requestId,
|
|
457
|
+
liveStoreVersion,
|
|
458
|
+
})), { timeout: 500 });
|
|
459
|
+
send();
|
|
460
|
+
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
461
|
+
const throttledSend = throttle(send, 20);
|
|
462
|
+
liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend));
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
case 'LSD.LiveQueriesUnsubscribe': {
|
|
466
|
+
liveQueriesSubscriptions.get(requestId)();
|
|
467
|
+
liveQueriesSubscriptions.delete(requestId);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
// No default
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
storeMessagePort.start();
|
|
474
|
+
}), Runtime.runFork(runtime));
|
|
475
|
+
return;
|
|
392
476
|
}
|
|
393
477
|
});
|
|
394
|
-
|
|
478
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }));
|
|
479
|
+
});
|
|
480
|
+
// #endregion devtools
|
|
395
481
|
__devDownloadDb = () => {
|
|
396
482
|
const data = this.mainDbWrapper.export();
|
|
397
483
|
downloadBlob(data, `livestore-${Date.now()}.db`);
|
|
398
484
|
};
|
|
399
485
|
__devDownloadMutationLogDb = async () => {
|
|
400
|
-
const data = await this.adapter.coordinator.getMutationLogData();
|
|
486
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(runEffectPromise);
|
|
401
487
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`);
|
|
402
488
|
};
|
|
403
489
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
404
|
-
dangerouslyResetStorage = (mode) => this.adapter.coordinator.dangerouslyReset(mode);
|
|
490
|
+
dangerouslyResetStorage = (mode) => this.adapter.coordinator.dangerouslyReset(mode).pipe(runEffectPromise);
|
|
491
|
+
toJSON = () => {
|
|
492
|
+
return {
|
|
493
|
+
_tag: 'Store',
|
|
494
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults: true }),
|
|
495
|
+
};
|
|
496
|
+
};
|
|
405
497
|
}
|
|
406
498
|
/** Create a new LiveStore Store */
|
|
407
|
-
export const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
adapter.mainDb.execute('ROLLBACK', undefined);
|
|
467
|
-
throw e;
|
|
499
|
+
export const createStorePromise = async ({ signal, ...options }) => Effect.gen(function* () {
|
|
500
|
+
const scope = yield* Scope.make();
|
|
501
|
+
const runtime = yield* Effect.runtime();
|
|
502
|
+
if (signal !== undefined) {
|
|
503
|
+
signal.addEventListener('abort', () => {
|
|
504
|
+
Scope.close(scope, Exit.void).pipe(Effect.tapCauseLogPretty, Runtime.runFork(runtime));
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return yield* FiberSet.make().pipe(Effect.andThen((fiberSet) => createStore({ ...options, fiberSet })), Scope.extend(scope));
|
|
508
|
+
}).pipe(Effect.withSpan('createStore'), runEffectPromise);
|
|
509
|
+
// #region createStore
|
|
510
|
+
export const createStore = ({ schema, graphQLOptions, otelOptions, adapter: adapterFactory, boot, reactivityGraph = globalReactivityGraph, batchUpdates, disableDevtools, onBootStatus, fiberSet, }) => {
|
|
511
|
+
const otelTracer = otelOptions?.tracer ?? makeNoopTracer();
|
|
512
|
+
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active();
|
|
513
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)));
|
|
514
|
+
return Effect.gen(function* () {
|
|
515
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie);
|
|
516
|
+
const bootStatusQueue = yield* Queue.unbounded().pipe(Effect.acquireRelease(Queue.shutdown));
|
|
517
|
+
yield* Queue.take(bootStatusQueue).pipe(Effect.tapSync((status) => onBootStatus?.(status)), Effect.forever, Effect.tapCauseLogPretty, Effect.forkScoped);
|
|
518
|
+
const adapter = yield* adapterFactory({
|
|
519
|
+
schema,
|
|
520
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
521
|
+
bootStatusQueue,
|
|
522
|
+
shutdown: (cause) => Effect.gen(function* () {
|
|
523
|
+
yield* Effect.logWarning(`Shutting down LiveStore`, cause);
|
|
524
|
+
// TODO close parent scope? (Needs refactor with Mike A)
|
|
525
|
+
yield* FiberSet.clear(fiberSet);
|
|
526
|
+
}).pipe(Effect.withSpan('livestore:shutdown')),
|
|
527
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'));
|
|
528
|
+
if (batchUpdates !== undefined) {
|
|
529
|
+
reactivityGraph.effectsWrapper = batchUpdates;
|
|
530
|
+
}
|
|
531
|
+
const mutationEventSchema = makeMutationEventSchemaMemo(schema);
|
|
532
|
+
const __processedMutationIds = new Set();
|
|
533
|
+
// TODO consider moving booting into the storage backend
|
|
534
|
+
if (boot !== undefined) {
|
|
535
|
+
let isInTxn = false;
|
|
536
|
+
let txnExecuteStmnts = [];
|
|
537
|
+
const bootDbImpl = {
|
|
538
|
+
_tag: 'BootDb',
|
|
539
|
+
execute: (queryStr, bindValues) => {
|
|
540
|
+
const stmt = adapter.mainDb.prepare(queryStr);
|
|
541
|
+
stmt.execute(bindValues);
|
|
542
|
+
if (isInTxn === true) {
|
|
543
|
+
txnExecuteStmnts.push([queryStr, bindValues]);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork);
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
mutate: (...list) => {
|
|
550
|
+
for (const mutationEventDecoded of list) {
|
|
551
|
+
const mutationDef = schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
552
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`);
|
|
553
|
+
__processedMutationIds.add(mutationEventDecoded.id);
|
|
554
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded });
|
|
555
|
+
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
556
|
+
for (const { statementSql, bindValues } of execArgsArr) {
|
|
557
|
+
adapter.mainDb.execute(statementSql, bindValues);
|
|
468
558
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
559
|
+
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded);
|
|
560
|
+
adapter.coordinator
|
|
561
|
+
.mutate(mutationEventEncoded, { persisted: true })
|
|
562
|
+
.pipe(runEffectFork);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
select: (queryStr, bindValues) => {
|
|
566
|
+
const stmt = adapter.mainDb.prepare(queryStr);
|
|
567
|
+
return stmt.select(bindValues);
|
|
568
|
+
},
|
|
569
|
+
txn: (callback) => {
|
|
570
|
+
try {
|
|
571
|
+
isInTxn = true;
|
|
572
|
+
adapter.mainDb.execute('BEGIN', undefined);
|
|
573
|
+
callback();
|
|
574
|
+
adapter.mainDb.execute('COMMIT', undefined);
|
|
575
|
+
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
576
|
+
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
577
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork);
|
|
472
578
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
schema,
|
|
487
|
-
graphQLOptions,
|
|
488
|
-
otelTracer,
|
|
489
|
-
otelRootSpanContext,
|
|
490
|
-
dbGraph,
|
|
491
|
-
mutationEventSchema,
|
|
492
|
-
disableDevtools,
|
|
493
|
-
}, span);
|
|
494
|
-
}
|
|
495
|
-
finally {
|
|
496
|
-
span.end();
|
|
579
|
+
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
580
|
+
}
|
|
581
|
+
catch (e) {
|
|
582
|
+
adapter.mainDb.execute('ROLLBACK', undefined);
|
|
583
|
+
throw e;
|
|
584
|
+
}
|
|
585
|
+
finally {
|
|
586
|
+
isInTxn = false;
|
|
587
|
+
txnExecuteStmnts = [];
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
yield* Effect.tryAll(() => boot(bootDbImpl, span)).pipe(UnexpectedError.mapToUnexpectedError, Effect.withSpan('createStore:boot'));
|
|
497
592
|
}
|
|
498
|
-
|
|
593
|
+
return Store.createStore({
|
|
594
|
+
adapter,
|
|
595
|
+
schema,
|
|
596
|
+
graphQLOptions,
|
|
597
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
598
|
+
reactivityGraph,
|
|
599
|
+
disableDevtools,
|
|
600
|
+
__processedMutationIds,
|
|
601
|
+
fiberSet,
|
|
602
|
+
}, span);
|
|
603
|
+
}).pipe(Effect.withSpan('createStore', {
|
|
604
|
+
parent: otelOptions?.rootSpanContext
|
|
605
|
+
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext))
|
|
606
|
+
: undefined,
|
|
607
|
+
}), Effect.provide(TracingLive));
|
|
499
608
|
};
|
|
609
|
+
// #endregion createStore
|
|
610
|
+
// TODO consider replacing with Effect's RC data structures
|
|
500
611
|
class ReferenceCountedSet {
|
|
501
612
|
map;
|
|
502
613
|
constructor() {
|
|
@@ -527,4 +638,6 @@ class ReferenceCountedSet {
|
|
|
527
638
|
}
|
|
528
639
|
}
|
|
529
640
|
}
|
|
641
|
+
const runEffectFork = (effect) => effect.pipe(Effect.tapCauseLogPretty, Effect.annotateLogs({ thread: 'window' }), Effect.provide(Logger.pretty), Logger.withMinimumLogLevel(LogLevel.Debug), Effect.runFork);
|
|
642
|
+
const runEffectPromise = (effect) => effect.pipe(Effect.tapCauseLogPretty, Effect.annotateLogs({ thread: 'window' }), Effect.provide(Logger.pretty), Logger.withMinimumLogLevel(LogLevel.Debug), Effect.runPromise);
|
|
530
643
|
//# sourceMappingURL=store.js.map
|