@livestore/livestore 0.0.54-dev.21 → 0.0.54-dev.24
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/__tests__/react/fixture.d.ts +1 -9
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +1 -1
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +1 -0
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +1 -1
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/global-state.d.ts +0 -2
- package/dist/global-state.d.ts.map +1 -1
- package/dist/global-state.js +0 -1
- package/dist/global-state.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- 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 +3 -3
- package/dist/react/LiveStoreProvider.d.ts.map +1 -1
- package/dist/react/LiveStoreProvider.js +16 -8
- package/dist/react/LiveStoreProvider.js.map +1 -1
- package/dist/react/LiveStoreProvider.test.js +6 -4
- package/dist/react/LiveStoreProvider.test.js.map +1 -1
- package/dist/reactive.js +1 -1
- package/dist/reactive.js.map +1 -1
- package/dist/row-query.d.ts.map +1 -1
- package/dist/row-query.js +3 -37
- package/dist/row-query.js.map +1 -1
- package/dist/store.d.ts +12 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +312 -176
- package/dist/store.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/react/fixture.tsx +1 -1
- package/src/effect/LiveStore.ts +2 -1
- package/src/global-state.ts +0 -4
- package/src/index.ts +2 -1
- package/src/react/LiveStoreContext.ts +4 -0
- package/src/react/LiveStoreProvider.test.tsx +9 -4
- package/src/react/LiveStoreProvider.tsx +17 -10
- package/src/reactive.ts +3 -1
- package/src/row-query.ts +4 -50
- package/src/store.ts +435 -224
package/dist/store.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { Devtools, getExecArgsFromMutation, prepareBindValues } from '@livestore/common';
|
|
2
|
-
import { version as liveStoreVersion } from '@livestore/common/package.json';
|
|
1
|
+
import { Devtools, getExecArgsFromMutation, liveStoreVersion, prepareBindValues } from '@livestore/common';
|
|
3
2
|
import { makeMutationEventSchemaMemo } from '@livestore/common/schema';
|
|
4
|
-
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils';
|
|
5
|
-
import {
|
|
3
|
+
import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen, throttle } from '@livestore/utils';
|
|
4
|
+
import { cuid } from '@livestore/utils/cuid';
|
|
5
|
+
import { Effect, Layer, Logger, LogLevel, OtelTracer, Queue, Schema, Stream } from '@livestore/utils/effect';
|
|
6
6
|
import * as otel from '@opentelemetry/api';
|
|
7
7
|
import { globalReactivityGraph } from './global-state.js';
|
|
8
8
|
import { 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
12
|
let storeCount = 0;
|
|
12
13
|
const uniqueStoreId = () => `store-${++storeCount}`;
|
|
13
14
|
export class Store {
|
|
14
15
|
id = uniqueStoreId();
|
|
16
|
+
devtoolsConnectionId = cuid();
|
|
15
17
|
reactivityGraph;
|
|
16
18
|
mainDbWrapper;
|
|
17
19
|
adapter;
|
|
@@ -25,17 +27,19 @@ export class Store {
|
|
|
25
27
|
*/
|
|
26
28
|
tableRefs;
|
|
27
29
|
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
28
|
-
__processedMutationIds
|
|
30
|
+
__processedMutationIds;
|
|
29
31
|
__processedMutationWithoutRefreshIds = new Set();
|
|
30
32
|
/** RC-based set to see which queries are currently subscribed to */
|
|
31
33
|
activeQueries;
|
|
32
34
|
__mutationEventSchema;
|
|
33
|
-
constructor({ adapter, schema, graphQLOptions, reactivityGraph, otelOptions, disableDevtools, }) {
|
|
35
|
+
constructor({ adapter, schema, graphQLOptions, reactivityGraph, otelOptions, disableDevtools, __processedMutationIds, }) {
|
|
34
36
|
this.mainDbWrapper = new MainDatabaseWrapper({ otel: otelOptions, db: adapter.mainDb });
|
|
35
37
|
this.adapter = adapter;
|
|
36
38
|
this.schema = schema;
|
|
37
39
|
// TODO refactor
|
|
38
40
|
this.__mutationEventSchema = makeMutationEventSchemaMemo(schema);
|
|
41
|
+
// TODO remove this temporary solution and find a better way to avoid re-processing the same mutation
|
|
42
|
+
this.__processedMutationIds = __processedMutationIds;
|
|
39
43
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
40
44
|
this.tableRefs = {};
|
|
41
45
|
this.activeQueries = new ReferenceCountedSet();
|
|
@@ -51,7 +55,7 @@ export class Store {
|
|
|
51
55
|
};
|
|
52
56
|
this.adapter.coordinator.syncMutations.pipe(Stream.tapSync((mutationEventDecoded) => {
|
|
53
57
|
this.mutate({ wasSyncMessage: true }, mutationEventDecoded);
|
|
54
|
-
}), Stream.runDrain,
|
|
58
|
+
}), Stream.runDrain, runEffectFork);
|
|
55
59
|
if (disableDevtools !== true) {
|
|
56
60
|
this.bootDevtools();
|
|
57
61
|
}
|
|
@@ -124,7 +128,7 @@ export class Store {
|
|
|
124
128
|
}
|
|
125
129
|
otel.trace.getSpan(this.otel.mutationsSpanContext).end();
|
|
126
130
|
otel.trace.getSpan(this.otel.queriesSpanContext).end();
|
|
127
|
-
await this.adapter.coordinator.shutdown();
|
|
131
|
+
await this.adapter.coordinator.shutdown.pipe(runEffectPromise);
|
|
128
132
|
};
|
|
129
133
|
mutate = (firstMutationOrTxnFnOrOptions, ...restMutations) => {
|
|
130
134
|
let mutationsEvents;
|
|
@@ -278,10 +282,9 @@ export class Store {
|
|
|
278
282
|
const mutationEventEncoded = Schema.encodeUnknownSync(this.__mutationEventSchema)(mutationEventDecoded);
|
|
279
283
|
if (coordinatorMode !== 'skip-coordinator') {
|
|
280
284
|
// Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
+
this.adapter.coordinator
|
|
286
|
+
.mutate(mutationEventEncoded, { persisted: coordinatorMode !== 'skip-persist' })
|
|
287
|
+
.pipe(runEffectFork);
|
|
285
288
|
}
|
|
286
289
|
// Uncomment to print a list of queries currently registered on the store
|
|
287
290
|
// console.debug(JSON.parse(JSON.stringify([...this.queries].map((q) => `${labelForKey(q.componentKey)}/${q.label}`))))
|
|
@@ -296,8 +299,7 @@ export class Store {
|
|
|
296
299
|
*/
|
|
297
300
|
execute = (query, params = {}, writeTables, otelContext) => {
|
|
298
301
|
this.mainDbWrapper.execute(query, prepareBindValues(params, query), writeTables, { otelContext });
|
|
299
|
-
|
|
300
|
-
this.adapter.coordinator.execute(query, prepareBindValues(params, query), parentSpan);
|
|
302
|
+
this.adapter.coordinator.execute(query, prepareBindValues(params, query)).pipe(runEffectFork);
|
|
301
303
|
};
|
|
302
304
|
select = (query, params = {}) => {
|
|
303
305
|
return this.mainDbWrapper.select(query, { bindValues: prepareBindValues(params, query) });
|
|
@@ -307,191 +309,323 @@ export class Store {
|
|
|
307
309
|
label: `tableRef:${tableName}`,
|
|
308
310
|
meta: { liveStoreRefType: 'table' },
|
|
309
311
|
});
|
|
312
|
+
// TODO shutdown behaviour
|
|
310
313
|
bootDevtools = () => {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
const sendToDevtoolsContentscript = (message) => {
|
|
315
|
+
window.postMessage(Schema.encodeSync(Devtools.DevtoolsWindowMessage.MessageForContentscript)(message), '*');
|
|
316
|
+
};
|
|
317
|
+
const channelId = this.adapter.coordinator.devtools.channelId;
|
|
318
|
+
window.addEventListener('message', (event) => {
|
|
319
|
+
const decodedMessageRes = Schema.decodeOption(Devtools.DevtoolsWindowMessage.MessageForStore)(event.data);
|
|
320
|
+
if (decodedMessageRes._tag === 'None')
|
|
321
|
+
return;
|
|
322
|
+
const message = decodedMessageRes.value;
|
|
323
|
+
if (message._tag === 'LSD.WindowMessage.ContentscriptListening') {
|
|
324
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }));
|
|
321
325
|
return;
|
|
322
326
|
}
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
327
|
+
if (message.channelId !== channelId)
|
|
328
|
+
return;
|
|
329
|
+
if (message._tag === 'LSD.WindowMessage.MessagePortForStore') {
|
|
330
|
+
this.adapter.coordinator.devtools.connect({ port: message.port, connectionId: this.devtoolsConnectionId }).pipe(Effect.tapSync(({ storeMessagePort }) => {
|
|
331
|
+
// console.log('storeMessagePort', storeMessagePort)
|
|
332
|
+
storeMessagePort.addEventListener('message', (event) => {
|
|
333
|
+
const decodedMessage = Schema.decodeUnknownSync(Devtools.MessageToAppHostStore)(event.data);
|
|
334
|
+
// console.log('storeMessagePort message', decodedMessage)
|
|
335
|
+
if (decodedMessage.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
336
|
+
// console.log(`Unknown message`, event)
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const requestId = decodedMessage.requestId;
|
|
340
|
+
const sendToDevtools = (message) => storeMessagePort.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message));
|
|
341
|
+
const requestIdleCallback = window.requestIdleCallback ?? ((cb) => cb());
|
|
342
|
+
switch (decodedMessage._tag) {
|
|
343
|
+
case 'LSD.ReactivityGraphSubscribe': {
|
|
344
|
+
const includeResults = decodedMessage.includeResults;
|
|
345
|
+
const send = () =>
|
|
346
|
+
// In order to not add more work to the current tick, we use requestIdleCallback
|
|
347
|
+
// to send the reactivity graph updates to the devtools
|
|
348
|
+
requestIdleCallback(() => sendToDevtools(Devtools.ReactivityGraphRes.make({
|
|
349
|
+
reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
350
|
+
requestId,
|
|
351
|
+
liveStoreVersion,
|
|
352
|
+
})), { timeout: 500 });
|
|
353
|
+
send();
|
|
354
|
+
// In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
355
|
+
// so we throttle the updates to avoid sending too much data
|
|
356
|
+
// This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
357
|
+
const throttledSend = throttle(send, 20);
|
|
358
|
+
reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend));
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case 'LSD.DebugInfoReq': {
|
|
362
|
+
sendToDevtools(Devtools.DebugInfoRes.make({
|
|
363
|
+
debugInfo: this.mainDbWrapper.debugInfo,
|
|
364
|
+
requestId,
|
|
365
|
+
liveStoreVersion,
|
|
366
|
+
}));
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case 'LSD.DebugInfoResetReq': {
|
|
370
|
+
this.mainDbWrapper.debugInfo.slowQueries.clear();
|
|
371
|
+
sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }));
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
case 'LSD.DebugInfoRerunQueryReq': {
|
|
375
|
+
const { queryStr, bindValues, queriedTables } = decodedMessage;
|
|
376
|
+
this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true });
|
|
377
|
+
sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }));
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case 'LSD.ReactivityGraphUnsubscribe': {
|
|
381
|
+
reactivityGraphSubcriptions.get(requestId)();
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case 'LSD.LiveQueriesSubscribe': {
|
|
385
|
+
const send = () => requestIdleCallback(() => sendToDevtools(Devtools.LiveQueriesRes.make({
|
|
386
|
+
liveQueries: [...this.activeQueries].map((q) => ({
|
|
387
|
+
_tag: q._tag,
|
|
388
|
+
id: q.id,
|
|
389
|
+
label: q.label,
|
|
390
|
+
runs: q.runs,
|
|
391
|
+
executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
392
|
+
lastestResult: q.results$.previousResult === NOT_REFRESHED_YET
|
|
393
|
+
? 'SYMBOL_NOT_REFRESHED_YET'
|
|
394
|
+
: q.results$.previousResult,
|
|
395
|
+
activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
396
|
+
})),
|
|
397
|
+
requestId,
|
|
398
|
+
liveStoreVersion,
|
|
399
|
+
})), { timeout: 500 });
|
|
400
|
+
send();
|
|
401
|
+
// Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
402
|
+
const throttledSend = throttle(send, 20);
|
|
403
|
+
liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend));
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
case 'LSD.LiveQueriesUnsubscribe': {
|
|
407
|
+
liveQueriesSubscriptions.get(requestId)();
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
// No default
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
storeMessagePort.start();
|
|
414
|
+
}), runEffectFork);
|
|
415
|
+
return;
|
|
384
416
|
}
|
|
385
417
|
});
|
|
418
|
+
sendToDevtoolsContentscript(Devtools.DevtoolsWindowMessage.StoreReady.make({ channelId }));
|
|
419
|
+
const reactivityGraphSubcriptions = new Map();
|
|
420
|
+
const liveQueriesSubscriptions = new Map();
|
|
421
|
+
// devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
|
|
422
|
+
// const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHostStore)(event.data)
|
|
423
|
+
// if (decoded._tag === 'None' || decoded.value.channelId !== this.adapter.coordinator.devtools.channelId) {
|
|
424
|
+
// // console.log(`Unknown message`, event)
|
|
425
|
+
// return
|
|
426
|
+
// }
|
|
427
|
+
// const requestId = decoded.value.requestId
|
|
428
|
+
// const sendToDevtools = (message: Devtools.MessageFromAppHostStore) =>
|
|
429
|
+
// devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHostStore)(message))
|
|
430
|
+
// const requestIdleCallback = window.requestIdleCallback ?? ((cb: Function) => cb())
|
|
431
|
+
// switch (decoded.value._tag) {
|
|
432
|
+
// case 'LSD.ReactivityGraphSubscribe': {
|
|
433
|
+
// const includeResults = decoded.value.includeResults
|
|
434
|
+
// const send = () =>
|
|
435
|
+
// // In order to not add more work to the current tick, we use requestIdleCallback
|
|
436
|
+
// // to send the reactivity graph updates to the devtools
|
|
437
|
+
// requestIdleCallback(
|
|
438
|
+
// () =>
|
|
439
|
+
// sendToDevtools(
|
|
440
|
+
// Devtools.ReactivityGraphRes.make({
|
|
441
|
+
// reactivityGraph: this.reactivityGraph.getSnapshot({ includeResults }),
|
|
442
|
+
// requestId,
|
|
443
|
+
// liveStoreVersion,
|
|
444
|
+
// }),
|
|
445
|
+
// ),
|
|
446
|
+
// { timeout: 500 },
|
|
447
|
+
// )
|
|
448
|
+
// send()
|
|
449
|
+
// // In some cases, there can be A LOT of reactivity graph updates in a short period of time
|
|
450
|
+
// // so we throttle the updates to avoid sending too much data
|
|
451
|
+
// // This might need to be tweaked further and possibly be exposed to the user in some way.
|
|
452
|
+
// const throttledSend = throttle(send, 20)
|
|
453
|
+
// reactivityGraphSubcriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
454
|
+
// break
|
|
455
|
+
// }
|
|
456
|
+
// case 'LSD.DebugInfoReq': {
|
|
457
|
+
// sendToDevtools(
|
|
458
|
+
// Devtools.DebugInfoRes.make({ debugInfo: this.mainDbWrapper.debugInfo, requestId, liveStoreVersion }),
|
|
459
|
+
// )
|
|
460
|
+
// break
|
|
461
|
+
// }
|
|
462
|
+
// case 'LSD.DebugInfoResetReq': {
|
|
463
|
+
// this.mainDbWrapper.debugInfo.slowQueries.clear()
|
|
464
|
+
// sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
|
|
465
|
+
// break
|
|
466
|
+
// }
|
|
467
|
+
// case 'LSD.DebugInfoRerunQueryReq': {
|
|
468
|
+
// const { queryStr, bindValues, queriedTables } = decoded.value
|
|
469
|
+
// this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
|
|
470
|
+
// sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
|
|
471
|
+
// break
|
|
472
|
+
// }
|
|
473
|
+
// case 'LSD.ReactivityGraphUnsubscribe': {
|
|
474
|
+
// reactivityGraphSubcriptions.get(requestId)!()
|
|
475
|
+
// break
|
|
476
|
+
// }
|
|
477
|
+
// case 'LSD.LiveQueriesSubscribe': {
|
|
478
|
+
// const send = () =>
|
|
479
|
+
// requestIdleCallback(
|
|
480
|
+
// () =>
|
|
481
|
+
// sendToDevtools(
|
|
482
|
+
// Devtools.LiveQueriesRes.make({
|
|
483
|
+
// liveQueries: [...this.activeQueries].map((q) => ({
|
|
484
|
+
// _tag: q._tag,
|
|
485
|
+
// id: q.id,
|
|
486
|
+
// label: q.label,
|
|
487
|
+
// runs: q.runs,
|
|
488
|
+
// executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
|
|
489
|
+
// lastestResult:
|
|
490
|
+
// q.results$.previousResult === NOT_REFRESHED_YET
|
|
491
|
+
// ? 'SYMBOL_NOT_REFRESHED_YET'
|
|
492
|
+
// : q.results$.previousResult,
|
|
493
|
+
// activeSubscriptions: Array.from(q.activeSubscriptions),
|
|
494
|
+
// })),
|
|
495
|
+
// requestId,
|
|
496
|
+
// liveStoreVersion,
|
|
497
|
+
// }),
|
|
498
|
+
// ),
|
|
499
|
+
// { timeout: 500 },
|
|
500
|
+
// )
|
|
501
|
+
// send()
|
|
502
|
+
// // Same as in the reactivity graph subscription case above, we need to throttle the updates
|
|
503
|
+
// const throttledSend = throttle(send, 20)
|
|
504
|
+
// liveQueriesSubscriptions.set(requestId, this.reactivityGraph.subscribeToRefresh(throttledSend))
|
|
505
|
+
// break
|
|
506
|
+
// }
|
|
507
|
+
// case 'LSD.LiveQueriesUnsubscribe': {
|
|
508
|
+
// liveQueriesSubscriptions.get(requestId)!()
|
|
509
|
+
// break
|
|
510
|
+
// }
|
|
511
|
+
// // No default
|
|
512
|
+
// }
|
|
513
|
+
// })
|
|
386
514
|
};
|
|
387
515
|
__devDownloadDb = () => {
|
|
388
516
|
const data = this.mainDbWrapper.export();
|
|
389
517
|
downloadBlob(data, `livestore-${Date.now()}.db`);
|
|
390
518
|
};
|
|
391
519
|
__devDownloadMutationLogDb = async () => {
|
|
392
|
-
const data = await this.adapter.coordinator.getMutationLogData();
|
|
520
|
+
const data = await this.adapter.coordinator.getMutationLogData.pipe(runEffectPromise);
|
|
393
521
|
downloadBlob(data, `livestore-mutationlog-${Date.now()}.db`);
|
|
394
522
|
};
|
|
395
523
|
// TODO allow for graceful store reset without requiring a full page reload (which should also call .boot)
|
|
396
|
-
dangerouslyResetStorage = (mode) => this.adapter.coordinator.dangerouslyReset(mode);
|
|
524
|
+
dangerouslyResetStorage = (mode) => this.adapter.coordinator.dangerouslyReset(mode).pipe(runEffectPromise);
|
|
397
525
|
}
|
|
398
526
|
/** Create a new LiveStore Store */
|
|
399
|
-
export const createStore = async (
|
|
527
|
+
export const createStore = async (options) => createStoreEff(options).pipe(runEffectPromise);
|
|
528
|
+
export const createStoreEff = ({ schema, graphQLOptions, otelOptions, adapter: adapterFactory, boot, reactivityGraph = globalReactivityGraph, batchUpdates, disableDevtools, onBootStatus, }) => {
|
|
400
529
|
const otelTracer = otelOptions?.tracer ?? makeNoopTracer();
|
|
401
530
|
const otelRootSpanContext = otelOptions?.rootSpanContext ?? otel.context.active();
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
persisted: true,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
},
|
|
446
|
-
select: (queryStr, bindValues) => {
|
|
447
|
-
const stmt = adapter.mainDb.prepare(queryStr);
|
|
448
|
-
return stmt.select(bindValues);
|
|
449
|
-
},
|
|
450
|
-
txn: (callback) => {
|
|
451
|
-
try {
|
|
452
|
-
isInTxn = true;
|
|
453
|
-
adapter.mainDb.execute('BEGIN', undefined);
|
|
454
|
-
callback();
|
|
455
|
-
adapter.mainDb.execute('COMMIT', undefined);
|
|
456
|
-
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
457
|
-
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
458
|
-
adapter.coordinator.execute(queryStr, bindValues, undefined);
|
|
459
|
-
}
|
|
460
|
-
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
461
|
-
}
|
|
462
|
-
catch (e) {
|
|
463
|
-
adapter.mainDb.execute('ROLLBACK', undefined);
|
|
464
|
-
throw e;
|
|
531
|
+
const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(Layer.provide(Layer.sync(OtelTracer.Tracer, () => otelTracer)));
|
|
532
|
+
return Effect.gen(function* () {
|
|
533
|
+
const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.orDie);
|
|
534
|
+
const bootStatusQueue = yield* Queue.unbounded();
|
|
535
|
+
yield* Queue.take(bootStatusQueue).pipe(Effect.tapSync((status) => onBootStatus?.(status)), Effect.forever, Effect.tapCauseLogPretty, Effect.forkScoped);
|
|
536
|
+
const adapter = yield* adapterFactory({
|
|
537
|
+
schema,
|
|
538
|
+
devtoolsEnabled: disableDevtools !== true,
|
|
539
|
+
bootStatusQueue,
|
|
540
|
+
}).pipe(Effect.withPerformanceMeasure('livestore:makeAdapter'), Effect.withSpan('createStore:makeAdapter'));
|
|
541
|
+
if (batchUpdates !== undefined) {
|
|
542
|
+
reactivityGraph.effectsWrapper = batchUpdates;
|
|
543
|
+
}
|
|
544
|
+
const mutationEventSchema = makeMutationEventSchemaMemo(schema);
|
|
545
|
+
const __processedMutationIds = new Set();
|
|
546
|
+
// TODO consider moving booting into the storage backend
|
|
547
|
+
if (boot !== undefined) {
|
|
548
|
+
let isInTxn = false;
|
|
549
|
+
let txnExecuteStmnts = [];
|
|
550
|
+
const bootDbImpl = {
|
|
551
|
+
_tag: 'BootDb',
|
|
552
|
+
execute: (queryStr, bindValues) => {
|
|
553
|
+
const stmt = adapter.mainDb.prepare(queryStr);
|
|
554
|
+
stmt.execute(bindValues);
|
|
555
|
+
if (isInTxn === true) {
|
|
556
|
+
txnExecuteStmnts.push([queryStr, bindValues]);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork);
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
mutate: (...list) => {
|
|
563
|
+
for (const mutationEventDecoded of list) {
|
|
564
|
+
const mutationDef = schema.mutations.get(mutationEventDecoded.mutation) ??
|
|
565
|
+
shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`);
|
|
566
|
+
__processedMutationIds.add(mutationEventDecoded.id);
|
|
567
|
+
const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded });
|
|
568
|
+
// const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
|
|
569
|
+
for (const { statementSql, bindValues } of execArgsArr) {
|
|
570
|
+
adapter.mainDb.execute(statementSql, bindValues);
|
|
465
571
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
572
|
+
const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded);
|
|
573
|
+
adapter.coordinator
|
|
574
|
+
.mutate(mutationEventEncoded, { persisted: true })
|
|
575
|
+
.pipe(runEffectFork);
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
select: (queryStr, bindValues) => {
|
|
579
|
+
const stmt = adapter.mainDb.prepare(queryStr);
|
|
580
|
+
return stmt.select(bindValues);
|
|
581
|
+
},
|
|
582
|
+
txn: (callback) => {
|
|
583
|
+
try {
|
|
584
|
+
isInTxn = true;
|
|
585
|
+
adapter.mainDb.execute('BEGIN', undefined);
|
|
586
|
+
callback();
|
|
587
|
+
adapter.mainDb.execute('COMMIT', undefined);
|
|
588
|
+
// adapter.coordinator.execute('BEGIN', undefined, undefined)
|
|
589
|
+
for (const [queryStr, bindValues] of txnExecuteStmnts) {
|
|
590
|
+
adapter.coordinator.execute(queryStr, bindValues).pipe(runEffectFork);
|
|
469
591
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
592
|
+
// adapter.coordinator.execute('COMMIT', undefined, undefined)
|
|
593
|
+
}
|
|
594
|
+
catch (e) {
|
|
595
|
+
adapter.mainDb.execute('ROLLBACK', undefined);
|
|
596
|
+
throw e;
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
isInTxn = false;
|
|
600
|
+
txnExecuteStmnts = [];
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
const booting = boot(bootDbImpl, span);
|
|
605
|
+
// NOTE only awaiting if it's actually a promise to avoid unnecessary async/await
|
|
606
|
+
if (isPromise(booting)) {
|
|
607
|
+
yield* Effect.promise(() => booting);
|
|
477
608
|
}
|
|
478
|
-
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
479
|
-
// Think about what to do about this case.
|
|
480
|
-
// await applySchema(db, schema)
|
|
481
|
-
return Store.createStore({
|
|
482
|
-
adapter,
|
|
483
|
-
schema,
|
|
484
|
-
graphQLOptions,
|
|
485
|
-
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
486
|
-
reactivityGraph,
|
|
487
|
-
disableDevtools,
|
|
488
|
-
}, span);
|
|
489
609
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
610
|
+
// TODO: we can't apply the schema at this point, we've already loaded persisted data!
|
|
611
|
+
// Think about what to do about this case.
|
|
612
|
+
// await applySchema(db, schema)
|
|
613
|
+
return Store.createStore({
|
|
614
|
+
adapter,
|
|
615
|
+
schema,
|
|
616
|
+
graphQLOptions,
|
|
617
|
+
otelOptions: { tracer: otelTracer, rootSpanContext: otelRootSpanContext },
|
|
618
|
+
reactivityGraph,
|
|
619
|
+
disableDevtools,
|
|
620
|
+
__processedMutationIds,
|
|
621
|
+
}, span);
|
|
622
|
+
}).pipe(Effect.scoped, Effect.withSpan('createStore', {
|
|
623
|
+
parent: otelOptions?.rootSpanContext
|
|
624
|
+
? OtelTracer.makeExternalSpan(otel.trace.getSpanContext(otelOptions.rootSpanContext))
|
|
625
|
+
: undefined,
|
|
626
|
+
}), Effect.provide(TracingLive));
|
|
494
627
|
};
|
|
628
|
+
// TODO consider replacing with Effect's RC data structures
|
|
495
629
|
class ReferenceCountedSet {
|
|
496
630
|
map;
|
|
497
631
|
constructor() {
|
|
@@ -522,4 +656,6 @@ class ReferenceCountedSet {
|
|
|
522
656
|
}
|
|
523
657
|
}
|
|
524
658
|
}
|
|
659
|
+
const runEffectFork = (effect) => effect.pipe(Effect.tapCauseLogPretty, Effect.annotateLogs({ thread: 'window' }), Effect.provide(Logger.pretty), Logger.withMinimumLogLevel(LogLevel.Debug), Effect.runFork);
|
|
660
|
+
const runEffectPromise = (effect) => effect.pipe(Effect.tapCauseLogPretty, Effect.annotateLogs({ thread: 'window' }), Effect.provide(Logger.pretty), Logger.withMinimumLogLevel(LogLevel.Debug), Effect.runPromise);
|
|
525
661
|
//# sourceMappingURL=store.js.map
|