@livestore/livestore 0.4.0-dev.21 → 0.4.0-dev.23
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/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.js +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/SqliteDbWrapper.d.ts +5 -5
- package/dist/SqliteDbWrapper.d.ts.map +1 -1
- package/dist/SqliteDbWrapper.js +8 -8
- package/dist/SqliteDbWrapper.js.map +1 -1
- package/dist/SqliteDbWrapper.test.js +2 -2
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +130 -2
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +185 -6
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/effect/LiveStore.test.d.ts +2 -0
- package/dist/effect/LiveStore.test.d.ts.map +1 -0
- package/dist/effect/LiveStore.test.js +42 -0
- package/dist/effect/LiveStore.test.js.map +1 -0
- package/dist/effect/mod.d.ts +1 -1
- package/dist/effect/mod.d.ts.map +1 -1
- package/dist/effect/mod.js +3 -1
- package/dist/effect/mod.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +3 -3
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +2 -2
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/client-document-get-query.d.ts +1 -1
- package/dist/live-queries/client-document-get-query.d.ts.map +1 -1
- package/dist/live-queries/client-document-get-query.js +1 -1
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +2 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.js +14 -14
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +2 -2
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.test.js +2 -2
- package/dist/live-queries/signal.test.js.map +1 -1
- package/dist/mod.d.ts +2 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +1 -0
- package/dist/mod.js.map +1 -1
- package/dist/reactive.d.ts +9 -9
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +9 -26
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +2 -2
- package/dist/reactive.test.js.map +1 -1
- package/dist/store/StoreRegistry.d.ts +215 -0
- package/dist/store/StoreRegistry.d.ts.map +1 -0
- package/dist/store/StoreRegistry.js +267 -0
- package/dist/store/StoreRegistry.js.map +1 -0
- package/dist/store/StoreRegistry.test.d.ts +2 -0
- package/dist/store/StoreRegistry.test.d.ts.map +1 -0
- package/dist/store/StoreRegistry.test.js +381 -0
- package/dist/store/StoreRegistry.test.js.map +1 -0
- package/dist/store/create-store.d.ts +56 -6
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +32 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +1 -1
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +16 -3
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.js +2 -2
- package/dist/store/store-eventstream.test.js.map +1 -1
- package/dist/store/store-types.d.ts +59 -9
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.js +1 -1
- package/dist/store/store-types.test.js.map +1 -1
- package/dist/store/store.d.ts +102 -6
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +148 -47
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.js.map +1 -1
- package/dist/utils/stack-info.js +2 -2
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +1 -1
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/otel.d.ts.map +1 -1
- package/dist/utils/tests/otel.js +5 -5
- package/dist/utils/tests/otel.js.map +1 -1
- package/package.json +59 -18
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +4 -2
- package/src/SqliteDbWrapper.ts +12 -11
- package/src/ambient.d.ts +0 -7
- package/src/effect/LiveStore.test.ts +61 -0
- package/src/effect/LiveStore.ts +381 -8
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +336 -231
- package/src/live-queries/base-class.ts +7 -6
- package/src/live-queries/client-document-get-query.ts +4 -2
- package/src/live-queries/computed.ts +3 -2
- package/src/live-queries/db-query.test.ts +3 -2
- package/src/live-queries/db-query.ts +15 -15
- package/src/live-queries/signal.test.ts +3 -2
- package/src/mod.ts +2 -0
- package/src/reactive.test.ts +3 -2
- package/src/reactive.ts +22 -23
- package/src/store/StoreRegistry.test.ts +540 -0
- package/src/store/StoreRegistry.ts +418 -0
- package/src/store/create-store.ts +76 -15
- package/src/store/devtools.ts +20 -6
- package/src/store/store-eventstream.test.ts +4 -2
- package/src/store/store-types.test.ts +3 -1
- package/src/store/store-types.ts +64 -13
- package/src/store/store.ts +197 -60
- package/src/utils/dev.ts +2 -2
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +2 -1
- package/src/utils/tests/otel.ts +8 -7
- package/docs/api/index.md +0 -3
- package/docs/building-with-livestore/complex-ui-state/index.md +0 -5
- package/docs/building-with-livestore/crud/index.md +0 -5
- package/docs/building-with-livestore/data-modeling/index.md +0 -1
- package/docs/building-with-livestore/debugging/index.md +0 -17
- package/docs/building-with-livestore/devtools/index.md +0 -79
- package/docs/building-with-livestore/events/index.md +0 -355
- package/docs/building-with-livestore/examples/ai-agent/index.md +0 -5
- package/docs/building-with-livestore/examples/index.md +0 -30
- package/docs/building-with-livestore/examples/todo-workspaces/index.md +0 -891
- package/docs/building-with-livestore/examples/turnbased-game/index.md +0 -7
- package/docs/building-with-livestore/opentelemetry/index.md +0 -208
- package/docs/building-with-livestore/production-checklist/index.md +0 -5
- package/docs/building-with-livestore/reactivity-system/index.md +0 -202
- package/docs/building-with-livestore/rules-for-ai-agents/index.md +0 -9
- package/docs/building-with-livestore/state/materializers/index.md +0 -300
- package/docs/building-with-livestore/state/sql-queries/index.md +0 -72
- package/docs/building-with-livestore/state/sqlite/index.md +0 -45
- package/docs/building-with-livestore/state/sqlite-schema/index.md +0 -306
- package/docs/building-with-livestore/state/sqlite-schema-effect/index.md +0 -300
- package/docs/building-with-livestore/store/index.md +0 -281
- package/docs/building-with-livestore/syncing/index.md +0 -136
- package/docs/building-with-livestore/tools/cli/index.md +0 -177
- package/docs/building-with-livestore/tools/mcp/index.md +0 -187
- package/docs/examples/cloudflare-adapter/index.md +0 -44
- package/docs/examples/expo-adapter/index.md +0 -44
- package/docs/examples/index.md +0 -55
- package/docs/examples/node-adapter/index.md +0 -44
- package/docs/examples/web-adapter/index.md +0 -52
- package/docs/framework-integrations/custom-elements/index.md +0 -142
- package/docs/framework-integrations/react-integration/index.md +0 -918
- package/docs/framework-integrations/solid-integration/index.md +0 -293
- package/docs/framework-integrations/svelte-integration/index.md +0 -42
- package/docs/framework-integrations/vue-integration/index.md +0 -294
- package/docs/getting-started/expo/index.md +0 -736
- package/docs/getting-started/node/index.md +0 -115
- package/docs/getting-started/react-web/index.md +0 -573
- package/docs/getting-started/solid/index.md +0 -3
- package/docs/getting-started/vue/index.md +0 -471
- package/docs/index.md +0 -209
- package/docs/llms.txt +0 -147
- package/docs/misc/CODE_OF_CONDUCT/index.md +0 -133
- package/docs/misc/FAQ/index.md +0 -37
- package/docs/misc/community/index.md +0 -88
- package/docs/misc/credits/index.md +0 -14
- package/docs/misc/design-partners/index.md +0 -13
- package/docs/misc/package-management/index.md +0 -21
- package/docs/misc/performance/index.md +0 -25
- package/docs/misc/resources/index.md +0 -46
- package/docs/misc/state-of-the-project/index.md +0 -37
- package/docs/misc/troubleshooting/index.md +0 -82
- package/docs/overview/concepts/index.md +0 -78
- package/docs/overview/how-livestore-works/index.md +0 -56
- package/docs/overview/introduction/index.md +0 -5
- package/docs/overview/technology-comparison/index.md +0 -40
- package/docs/overview/when-livestore/index.md +0 -81
- package/docs/overview/why-livestore/index.md +0 -5
- package/docs/patterns/ai/index.md +0 -15
- package/docs/patterns/anonymous-user-transition/index.md +0 -10
- package/docs/patterns/app-evolution/index.md +0 -72
- package/docs/patterns/auth/index.md +0 -226
- package/docs/patterns/effect/index.md +0 -1495
- package/docs/patterns/encryption/index.md +0 -6
- package/docs/patterns/external-data/index.md +0 -5
- package/docs/patterns/file-management/index.md +0 -11
- package/docs/patterns/file-structure/index.md +0 -14
- package/docs/patterns/list-ordering/index.md +0 -369
- package/docs/patterns/offline/index.md +0 -32
- package/docs/patterns/orm/index.md +0 -18
- package/docs/patterns/presence/index.md +0 -11
- package/docs/patterns/rich-text-editing/index.md +0 -11
- package/docs/patterns/server-side-clients/index.md +0 -97
- package/docs/patterns/side-effects/index.md +0 -11
- package/docs/patterns/state-machines/index.md +0 -11
- package/docs/patterns/storybook/index.md +0 -192
- package/docs/patterns/undo-redo/index.md +0 -9
- package/docs/patterns/version-control/index.md +0 -8
- package/docs/platform-adapters/cloudflare-durable-object-adapter/index.md +0 -453
- package/docs/platform-adapters/electron-adapter/index.md +0 -15
- package/docs/platform-adapters/expo-adapter/index.md +0 -245
- package/docs/platform-adapters/node-adapter/index.md +0 -160
- package/docs/platform-adapters/tauri-adapter/index.md +0 -15
- package/docs/platform-adapters/web-adapter/index.md +0 -218
- package/docs/sustainable-open-source/contributing/docs/index.md +0 -94
- package/docs/sustainable-open-source/contributing/info/index.md +0 -63
- package/docs/sustainable-open-source/contributing/monorepo/index.md +0 -195
- package/docs/sustainable-open-source/sponsoring/index.md +0 -104
- package/docs/sync-providers/cloudflare/index.md +0 -773
- package/docs/sync-providers/custom/index.md +0 -65
- package/docs/sync-providers/electricsql/index.md +0 -159
- package/docs/sync-providers/s2/index.md +0 -230
- package/docs/tutorial/0-welcome/index.md +0 -48
- package/docs/tutorial/1-setup-starter-project/index.md +0 -105
- package/docs/tutorial/2-deploy-to-cloudflare/index.md +0 -195
- package/docs/tutorial/3-read-and-write-todos-via-livestore/index.md +0 -511
- package/docs/tutorial/4-sync-data-via-cloudflare/index.md +0 -210
- package/docs/tutorial/5-expand-business-logic/index.md +0 -174
- package/docs/tutorial/6-persist-ui-state/index.md +0 -453
- package/docs/tutorial/7-next-steps/index.md +0 -22
- package/docs/understanding-livestore/design-decisions/index.md +0 -33
- package/docs/understanding-livestore/event-sourcing/index.md +0 -40
package/src/store/store.ts
CHANGED
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
prepareBindValues,
|
|
16
16
|
QueryBuilderAstSymbol,
|
|
17
17
|
replaceSessionIdSymbol,
|
|
18
|
+
type StorageMode,
|
|
19
|
+
type SyncState,
|
|
18
20
|
UnknownError,
|
|
19
21
|
} from '@livestore/common'
|
|
20
22
|
import type { StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
21
23
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
22
|
-
import { LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema'
|
|
23
|
-
import { assertNever, isDevEnv, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
24
|
+
import { EventSequenceNumber, LiveStoreEvent, resolveEventDef, SystemTables } from '@livestore/common/schema'
|
|
25
|
+
import { assertNever, isDevEnv, objectToString, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
24
26
|
import type { Scope } from '@livestore/utils/effect'
|
|
25
27
|
import {
|
|
26
28
|
Cause,
|
|
@@ -49,12 +51,13 @@ import {
|
|
|
49
51
|
type Queryable,
|
|
50
52
|
type RefreshReason,
|
|
51
53
|
type StoreCommitOptions,
|
|
54
|
+
type StoreConstructorParams,
|
|
52
55
|
type StoreEventsOptions,
|
|
53
56
|
type StoreInternals,
|
|
54
57
|
StoreInternalsSymbol,
|
|
55
|
-
type StoreOptions,
|
|
56
58
|
type StoreOtel,
|
|
57
59
|
type SubscribeOptions,
|
|
60
|
+
type SyncStatus,
|
|
58
61
|
type Unsubscribe,
|
|
59
62
|
} from './store-types.ts'
|
|
60
63
|
|
|
@@ -67,7 +70,7 @@ export type SubscribeFn = {
|
|
|
67
70
|
<TResult>(query: Queryable<TResult>, options?: SubscribeOptions<TResult>): AsyncIterable<TResult>
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
if (isDevEnv()) {
|
|
73
|
+
if (isDevEnv() === true) {
|
|
71
74
|
exposeDebugUtils()
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -90,8 +93,8 @@ export const STORE_DEFAULT_PARAMS = {
|
|
|
90
93
|
* ## Creating a Store
|
|
91
94
|
*
|
|
92
95
|
* Use `createStore` (Effect-based) or `createStorePromise` to obtain a Store instance.
|
|
93
|
-
* In React applications, use
|
|
94
|
-
*
|
|
96
|
+
* In React applications, use `StoreRegistry` with `<StoreRegistryProvider>` and the `useStore()` hook
|
|
97
|
+
* which manages the Store lifecycle.
|
|
95
98
|
*
|
|
96
99
|
* ## Querying Data
|
|
97
100
|
*
|
|
@@ -136,7 +139,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
136
139
|
readonly context: TContext
|
|
137
140
|
|
|
138
141
|
/** Options provided to the Store constructor. */
|
|
139
|
-
readonly params:
|
|
142
|
+
readonly params: StoreConstructorParams<TSchema, TContext>['params']
|
|
140
143
|
|
|
141
144
|
/**
|
|
142
145
|
* Reactive connectivity updates emitted by the backing sync backend.
|
|
@@ -157,12 +160,30 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
157
160
|
*/
|
|
158
161
|
readonly networkStatus: ClientSession['leaderThread']['networkStatus']
|
|
159
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Indicates how data is being stored.
|
|
165
|
+
*
|
|
166
|
+
* - `persisted`: Data is persisted to disk (e.g., via OPFS on web, SQLite file on native)
|
|
167
|
+
* - `in-memory`: Data is only stored in memory and will be lost on page refresh
|
|
168
|
+
*
|
|
169
|
+
* The store operates in `in-memory` mode when persistent storage is unavailable,
|
|
170
|
+
* such as in Safari/Firefox private browsing mode where OPFS is restricted.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```tsx
|
|
174
|
+
* if (store.storageMode === 'in-memory') {
|
|
175
|
+
* showWarning('Data will not be persisted in private browsing mode')
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
readonly storageMode: StorageMode
|
|
180
|
+
|
|
160
181
|
/**
|
|
161
182
|
* Store internals. Not part of the public API — shapes and semantics may change without notice.
|
|
162
183
|
*/
|
|
163
184
|
readonly [StoreInternalsSymbol]: StoreInternals
|
|
164
185
|
|
|
165
|
-
|
|
186
|
+
//#region constructor
|
|
166
187
|
constructor({
|
|
167
188
|
clientSession,
|
|
168
189
|
schema,
|
|
@@ -174,7 +195,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
174
195
|
params,
|
|
175
196
|
confirmUnsavedChanges,
|
|
176
197
|
__runningInDevtools,
|
|
177
|
-
}:
|
|
198
|
+
}: StoreConstructorParams<TSchema, TContext>) {
|
|
178
199
|
super()
|
|
179
200
|
|
|
180
201
|
this.storeId = storeId
|
|
@@ -182,15 +203,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
182
203
|
this.context = context
|
|
183
204
|
this.params = params
|
|
184
205
|
this.networkStatus = clientSession.leaderThread.networkStatus
|
|
206
|
+
this.storageMode = clientSession.leaderThread.initialState.storageMode
|
|
185
207
|
|
|
186
208
|
const reactivityGraph = makeReactivityGraph()
|
|
187
209
|
|
|
188
|
-
const syncSpan = otelOptions.tracer.startSpan('LiveStore:sync', {}, otelOptions.rootSpanContext)
|
|
189
|
-
|
|
190
210
|
const syncProcessor = makeClientSessionSyncProcessor({
|
|
191
211
|
schema,
|
|
192
212
|
clientSession,
|
|
193
|
-
runtime: effectContext.runtime,
|
|
194
213
|
materializeEvent: Effect.fn('client-session-sync-processor:materialize-event')(
|
|
195
214
|
(eventEncoded, { withChangeset, materializerHashLeader }) =>
|
|
196
215
|
// We need to use `Effect.gen` (even though we're using `Effect.fn`) so that we can pass `this` to the function
|
|
@@ -219,7 +238,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
219
238
|
event: { decoded: undefined, encoded: eventEncoded },
|
|
220
239
|
})
|
|
221
240
|
|
|
222
|
-
const materializerHash =
|
|
241
|
+
const materializerHash =
|
|
242
|
+
isDevEnv() === true ? Option.some(hashMaterializerResults(execArgsArr)) : Option.none()
|
|
223
243
|
|
|
224
244
|
// Hash mismatch detection only occurs during the pull path (when receiving events from the leader).
|
|
225
245
|
// During push path (local commits), materializerHashLeader is always Option.none(), so this condition
|
|
@@ -291,7 +311,6 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
291
311
|
}
|
|
292
312
|
reactivityGraph.setRefs(tablesToUpdate)
|
|
293
313
|
},
|
|
294
|
-
span: syncSpan,
|
|
295
314
|
params: {
|
|
296
315
|
...omitUndefineds({
|
|
297
316
|
leaderPushBatchSize: params.leaderPushBatchSize,
|
|
@@ -301,7 +320,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
301
320
|
: {}),
|
|
302
321
|
},
|
|
303
322
|
confirmUnsavedChanges,
|
|
304
|
-
})
|
|
323
|
+
}).pipe(Runtime.runSync(effectContext.runtime))
|
|
305
324
|
|
|
306
325
|
// TODO generalize the `tableRefs` concept to allow finer-grained refs
|
|
307
326
|
const tableRefs: { [key: string]: Ref<null, ReactivityGraphContext, RefreshReason> } = {}
|
|
@@ -332,7 +351,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
332
351
|
const allTableNames = new Set(
|
|
333
352
|
// NOTE we're excluding the LiveStore schema and events tables as they are not user-facing
|
|
334
353
|
// unless LiveStore is running in the devtools
|
|
335
|
-
__runningInDevtools
|
|
354
|
+
__runningInDevtools === true
|
|
336
355
|
? this.schema.state.sqlite.tables.keys()
|
|
337
356
|
: Array.from(this.schema.state.sqlite.tables.keys()).filter((_) => !SystemTables.isStateSystemTable(_)),
|
|
338
357
|
)
|
|
@@ -346,7 +365,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
346
365
|
existingTableRefs.get(tableName) ??
|
|
347
366
|
reactivityGraph.makeRef(null, {
|
|
348
367
|
equal: () => false,
|
|
349
|
-
label: `tableRef:${tableName}`,
|
|
368
|
+
label: `tableRef:${String(tableName)}`,
|
|
350
369
|
meta: { liveStoreRefType: 'table' },
|
|
351
370
|
})
|
|
352
371
|
}
|
|
@@ -362,7 +381,6 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
362
381
|
}
|
|
363
382
|
|
|
364
383
|
// End the otel spans
|
|
365
|
-
syncSpan.end()
|
|
366
384
|
commitsSpan.end()
|
|
367
385
|
queriesSpan.end()
|
|
368
386
|
}),
|
|
@@ -395,7 +413,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
395
413
|
// Initialize stable network status property from client session
|
|
396
414
|
this.networkStatus = clientSession.leaderThread.networkStatus
|
|
397
415
|
}
|
|
398
|
-
|
|
416
|
+
//#endregion constructor
|
|
399
417
|
|
|
400
418
|
/**
|
|
401
419
|
* Current session identifier for this Store instance.
|
|
@@ -418,7 +436,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
418
436
|
}
|
|
419
437
|
|
|
420
438
|
private checkShutdown = (operation: string): void => {
|
|
421
|
-
if (this[StoreInternalsSymbol].isShutdown) {
|
|
439
|
+
if (this[StoreInternalsSymbol].isShutdown === true) {
|
|
422
440
|
throw new UnknownError({
|
|
423
441
|
cause: `Store has been shut down (while performing "${operation}").`,
|
|
424
442
|
note: `You cannot perform this operation after the store has been shut down.`,
|
|
@@ -465,19 +483,25 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
465
483
|
|
|
466
484
|
return this[StoreInternalsSymbol].otel.tracer.startActiveSpan(
|
|
467
485
|
`LiveStore.subscribe`,
|
|
468
|
-
{
|
|
486
|
+
{
|
|
487
|
+
attributes: {
|
|
488
|
+
label: options?.label,
|
|
489
|
+
queryLabel: isQueryBuilder(query) === true ? query.toString() : query.label,
|
|
490
|
+
},
|
|
491
|
+
},
|
|
469
492
|
options?.otelContext ?? this[StoreInternalsSymbol].otel.queriesSpanContext,
|
|
470
493
|
(span) => {
|
|
471
494
|
const otelContext = otel.trace.setSpan(otel.context.active(), span)
|
|
472
495
|
|
|
473
|
-
const queryRcRef =
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
496
|
+
const queryRcRef =
|
|
497
|
+
isQueryBuilder(query) === true
|
|
498
|
+
? queryDb(query).make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
499
|
+
: query._tag === 'def' || query._tag === 'signal-def'
|
|
500
|
+
? query.make(this[StoreInternalsSymbol].reactivityGraph.context!)
|
|
501
|
+
: {
|
|
502
|
+
value: query,
|
|
503
|
+
deref: () => {},
|
|
504
|
+
}
|
|
481
505
|
const query$ = queryRcRef.value
|
|
482
506
|
|
|
483
507
|
const label = `subscribe:${options?.label}`
|
|
@@ -485,7 +509,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
485
509
|
const effect = this[StoreInternalsSymbol].reactivityGraph.makeEffect(
|
|
486
510
|
(get, _otelContext, debugRefreshReason) => {
|
|
487
511
|
const result = get(query$.results$, otelContext, debugRefreshReason)
|
|
488
|
-
if (suppressCallback) {
|
|
512
|
+
if (suppressCallback === true) {
|
|
489
513
|
return
|
|
490
514
|
}
|
|
491
515
|
onUpdate(result)
|
|
@@ -499,7 +523,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
499
523
|
})
|
|
500
524
|
}
|
|
501
525
|
|
|
502
|
-
if (options?.stackInfo) {
|
|
526
|
+
if (options?.stackInfo !== undefined) {
|
|
503
527
|
query$.activeSubscriptions.add(options.stackInfo)
|
|
504
528
|
}
|
|
505
529
|
|
|
@@ -507,8 +531,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
507
531
|
|
|
508
532
|
this[StoreInternalsSymbol].activeQueries.add(query$ as LiveQuery<TResult>)
|
|
509
533
|
|
|
510
|
-
if (
|
|
511
|
-
if (suppressCallback) {
|
|
534
|
+
if (query$.isDestroyed === false) {
|
|
535
|
+
if (suppressCallback === true) {
|
|
512
536
|
// We still run once to register dependencies in the reactive graph, but suppress the initial callback so the
|
|
513
537
|
// caller truly skips the first emission; subsequent runs (after commits) will call the callback.
|
|
514
538
|
runInitialEffect()
|
|
@@ -523,7 +547,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
523
547
|
this[StoreInternalsSymbol].reactivityGraph.destroyNode(effect)
|
|
524
548
|
this[StoreInternalsSymbol].activeQueries.remove(query$ as LiveQuery<TResult>)
|
|
525
549
|
|
|
526
|
-
if (options?.stackInfo) {
|
|
550
|
+
if (options?.stackInfo !== undefined) {
|
|
527
551
|
query$.activeSubscriptions.delete(options.stackInfo)
|
|
528
552
|
}
|
|
529
553
|
|
|
@@ -555,12 +579,13 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
555
579
|
const otelSpan = yield* OtelTracer.currentOtelSpan.pipe(
|
|
556
580
|
Effect.catchTag('NoSuchElementException', () => Effect.succeed(undefined)),
|
|
557
581
|
)
|
|
558
|
-
const otelContext =
|
|
582
|
+
const otelContext =
|
|
583
|
+
otelSpan !== undefined ? otel.trace.setSpan(otel.context.active(), otelSpan) : otel.context.active()
|
|
559
584
|
|
|
560
585
|
yield* Effect.acquireRelease(
|
|
561
586
|
Effect.sync(() =>
|
|
562
587
|
this.subscribe(query, (result) => emit.single(result), {
|
|
563
|
-
...
|
|
588
|
+
...options,
|
|
564
589
|
otelContext,
|
|
565
590
|
}),
|
|
566
591
|
),
|
|
@@ -597,11 +622,11 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
597
622
|
...omitUndefineds({ otelContext: options?.otelContext }),
|
|
598
623
|
},
|
|
599
624
|
) as any
|
|
600
|
-
if (query.schema) {
|
|
625
|
+
if (query.schema !== undefined) {
|
|
601
626
|
return Schema.decodeSync(query.schema)(res)
|
|
602
627
|
}
|
|
603
628
|
return res
|
|
604
|
-
} else if (isQueryBuilder(query)) {
|
|
629
|
+
} else if (isQueryBuilder(query) === true) {
|
|
605
630
|
const ast = query[QueryBuilderAstSymbol]
|
|
606
631
|
if (ast._tag === 'RowQuery') {
|
|
607
632
|
makeExecBeforeFirstRun({
|
|
@@ -616,7 +641,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
616
641
|
const schema = getResultSchema(query)
|
|
617
642
|
|
|
618
643
|
// Replace SessionIdSymbol in bind values before executing the query
|
|
619
|
-
if (sqlRes.bindValues) {
|
|
644
|
+
if (sqlRes.bindValues !== undefined) {
|
|
620
645
|
replaceSessionIdSymbol(sqlRes.bindValues, this[StoreInternalsSymbol].clientSession.sessionId)
|
|
621
646
|
}
|
|
622
647
|
|
|
@@ -634,8 +659,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
634
659
|
return decodeResult.right
|
|
635
660
|
} else {
|
|
636
661
|
return shouldNeverHappen(
|
|
637
|
-
|
|
638
|
-
schema
|
|
662
|
+
'Failed to decode query result with for schema:',
|
|
663
|
+
objectToString(schema),
|
|
639
664
|
'raw result:',
|
|
640
665
|
rawRes,
|
|
641
666
|
'decode error:',
|
|
@@ -689,7 +714,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
689
714
|
}
|
|
690
715
|
}
|
|
691
716
|
|
|
692
|
-
|
|
717
|
+
//#region commit
|
|
693
718
|
/**
|
|
694
719
|
* Commit a list of events to the store which will immediately update the local database
|
|
695
720
|
* and sync the events across other clients (similar to a `git commit`).
|
|
@@ -777,23 +802,20 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
777
802
|
|
|
778
803
|
const localRuntime = yield* Effect.runtime()
|
|
779
804
|
|
|
780
|
-
const
|
|
781
|
-
try: () => {
|
|
782
|
-
const runMaterializeEvents = () => {
|
|
783
|
-
return this[StoreInternalsSymbol].syncProcessor.push(events).pipe(Runtime.runSync(localRuntime))
|
|
784
|
-
}
|
|
805
|
+
const encodedEvents = yield* this[StoreInternalsSymbol].syncProcessor.encodeEvents(events)
|
|
785
806
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
807
|
+
const { writeTables } = yield* Effect.try({
|
|
808
|
+
try: () => {
|
|
809
|
+
const materialize = () =>
|
|
810
|
+
this[StoreInternalsSymbol].syncProcessor.materializeEvents(encodedEvents).pipe(Runtime.runSync(localRuntime))
|
|
811
|
+
return events.length > 1
|
|
812
|
+
? this[StoreInternalsSymbol].sqliteDbWrapper.txn(materialize)
|
|
813
|
+
: materialize()
|
|
791
814
|
},
|
|
792
815
|
catch: (cause) => UnknownError.make({ cause }),
|
|
793
816
|
})
|
|
794
817
|
|
|
795
|
-
|
|
796
|
-
const { writeTables } = yield* materializeEventsTx
|
|
818
|
+
yield* this[StoreInternalsSymbol].syncProcessor.push(encodedEvents)
|
|
797
819
|
|
|
798
820
|
const tablesToUpdate: [Ref<null, ReactivityGraphContext, RefreshReason>, null][] = []
|
|
799
821
|
for (const tableName of writeTables) {
|
|
@@ -837,7 +859,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
837
859
|
Runtime.runSync(this[StoreInternalsSymbol].effectContext.runtime),
|
|
838
860
|
)
|
|
839
861
|
}
|
|
840
|
-
|
|
862
|
+
//#endregion commit
|
|
841
863
|
|
|
842
864
|
/**
|
|
843
865
|
* Returns an async iterable of events from the eventlog.
|
|
@@ -918,6 +940,113 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
918
940
|
)
|
|
919
941
|
}
|
|
920
942
|
|
|
943
|
+
/**
|
|
944
|
+
* Returns the current synchronization status of the store.
|
|
945
|
+
*
|
|
946
|
+
* This is a synchronous operation that returns the sync state between the
|
|
947
|
+
* client session and the leader thread. Use this to display sync indicators
|
|
948
|
+
* or check if local changes have been pushed to the leader.
|
|
949
|
+
*
|
|
950
|
+
* @example
|
|
951
|
+
* ```ts
|
|
952
|
+
* const status = store.syncStatus()
|
|
953
|
+
* console.log(status.isSynced ? 'Synced' : `${status.pendingCount} pending`)
|
|
954
|
+
* ```
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```ts
|
|
958
|
+
* // Health check for backend connectivity
|
|
959
|
+
* const status = store.syncStatus()
|
|
960
|
+
* if (!status.isSynced && status.pendingCount > 100) {
|
|
961
|
+
* console.warn('Large backlog of unsynced events')
|
|
962
|
+
* }
|
|
963
|
+
* ```
|
|
964
|
+
*/
|
|
965
|
+
syncStatus = (): SyncStatus => {
|
|
966
|
+
this.checkShutdown('syncStatus')
|
|
967
|
+
|
|
968
|
+
const syncState = this[StoreInternalsSymbol].syncProcessor.syncState.pipe(Effect.runSync)
|
|
969
|
+
const pendingCount = syncState.pending.length
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
localHead: EventSequenceNumber.Client.toString(syncState.localHead),
|
|
973
|
+
upstreamHead: EventSequenceNumber.Client.toString(syncState.upstreamHead),
|
|
974
|
+
pendingCount,
|
|
975
|
+
isSynced: pendingCount === 0,
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Returns an Effect Stream of sync status updates.
|
|
981
|
+
*
|
|
982
|
+
* Emits the current status immediately and then whenever the sync state changes.
|
|
983
|
+
* Use this for Effect-based workflows or when you need more control over the stream.
|
|
984
|
+
*
|
|
985
|
+
* @example
|
|
986
|
+
* ```ts
|
|
987
|
+
* store.syncStatusStream().pipe(
|
|
988
|
+
* Stream.tap((status) => Effect.log(`Sync status: ${status.isSynced}`)),
|
|
989
|
+
* Stream.runDrain,
|
|
990
|
+
* )
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
syncStatusStream = (): Stream.Stream<SyncStatus> => {
|
|
994
|
+
const syncStateSubscribable = this[StoreInternalsSymbol].syncProcessor.syncState
|
|
995
|
+
|
|
996
|
+
return Stream.concat(
|
|
997
|
+
Stream.fromEffect(syncStateSubscribable.pipe(Effect.map(this.makeSyncStatus))),
|
|
998
|
+
syncStateSubscribable.changes.pipe(Stream.map(this.makeSyncStatus)),
|
|
999
|
+
)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Subscribes to sync status changes.
|
|
1004
|
+
*
|
|
1005
|
+
* The callback is invoked immediately with the current status and then
|
|
1006
|
+
* whenever the sync state changes (e.g., when events are pushed or confirmed).
|
|
1007
|
+
*
|
|
1008
|
+
* @param onUpdate - Callback invoked with the current sync status
|
|
1009
|
+
* @returns Unsubscribe function to stop receiving updates
|
|
1010
|
+
*
|
|
1011
|
+
* @example
|
|
1012
|
+
* ```ts
|
|
1013
|
+
* const unsubscribe = store.subscribeSyncStatus((status) => {
|
|
1014
|
+
* updateUI(status.isSynced ? 'Synced' : 'Syncing...')
|
|
1015
|
+
* })
|
|
1016
|
+
*
|
|
1017
|
+
* // Later, stop listening
|
|
1018
|
+
* unsubscribe()
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
subscribeSyncStatus = (onUpdate: (status: SyncStatus) => void): Unsubscribe => {
|
|
1022
|
+
this.checkShutdown('subscribeSyncStatus')
|
|
1023
|
+
|
|
1024
|
+
const fiber = this.syncStatusStream().pipe(
|
|
1025
|
+
Stream.tap((status) => Effect.sync(() => onUpdate(status))),
|
|
1026
|
+
Stream.runDrain,
|
|
1027
|
+
this.runEffectFork,
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
return () => {
|
|
1031
|
+
Fiber.interrupt(fiber).pipe(Runtime.runFork(this[StoreInternalsSymbol].effectContext.runtime))
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private makeSyncStatus = (syncState: {
|
|
1036
|
+
localHead: EventSequenceNumber.Client.Composite
|
|
1037
|
+
upstreamHead: EventSequenceNumber.Client.Composite
|
|
1038
|
+
pending: readonly any[]
|
|
1039
|
+
}): SyncStatus => {
|
|
1040
|
+
const pendingCount = syncState.pending.length
|
|
1041
|
+
|
|
1042
|
+
return {
|
|
1043
|
+
localHead: EventSequenceNumber.Client.toString(syncState.localHead),
|
|
1044
|
+
upstreamHead: EventSequenceNumber.Client.toString(syncState.upstreamHead),
|
|
1045
|
+
pendingCount,
|
|
1046
|
+
isSynced: pendingCount === 0,
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
921
1050
|
/**
|
|
922
1051
|
* This can be used in combination with `skipRefresh` when committing events.
|
|
923
1052
|
* We might need a better solution for this. Let's see.
|
|
@@ -947,7 +1076,11 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
947
1076
|
this.checkShutdown('shutdownPromise')
|
|
948
1077
|
|
|
949
1078
|
this[StoreInternalsSymbol].isShutdown = true
|
|
950
|
-
await this.shutdown(cause ? Cause.fail(cause) : undefined).pipe(
|
|
1079
|
+
await this.shutdown(cause !== undefined ? Cause.fail(cause) : undefined).pipe(
|
|
1080
|
+
this.runEffectFork,
|
|
1081
|
+
Fiber.join,
|
|
1082
|
+
Effect.runPromise,
|
|
1083
|
+
)
|
|
951
1084
|
}
|
|
952
1085
|
|
|
953
1086
|
/**
|
|
@@ -958,7 +1091,7 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
958
1091
|
shutdown = (cause?: Cause.Cause<UnknownError | MaterializeError>): Effect.Effect<void> => {
|
|
959
1092
|
this[StoreInternalsSymbol].isShutdown = true
|
|
960
1093
|
return this[StoreInternalsSymbol].clientSession.shutdown(
|
|
961
|
-
cause ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
|
|
1094
|
+
cause !== undefined ? Exit.failCause(cause) : Exit.succeed(IntentionalShutdownCause.make({ reason: 'manual' })),
|
|
962
1095
|
)
|
|
963
1096
|
}
|
|
964
1097
|
|
|
@@ -1008,7 +1141,8 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
1008
1141
|
.pipe(this.runEffectFork)
|
|
1009
1142
|
},
|
|
1010
1143
|
|
|
1011
|
-
|
|
1144
|
+
// NOTE: Explicit return type needed to avoid TS2742 (inferred type references internal path)
|
|
1145
|
+
syncStates: (): Promise<{ session: SyncState.SyncState; leader: SyncState.SyncState }> =>
|
|
1012
1146
|
Effect.gen(this, function* () {
|
|
1013
1147
|
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState
|
|
1014
1148
|
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState
|
|
@@ -1019,11 +1153,14 @@ export class Store<TSchema extends LiveStoreSchema = LiveStoreSchema.Any, TConte
|
|
|
1019
1153
|
Effect.gen(this, function* () {
|
|
1020
1154
|
const session = yield* this[StoreInternalsSymbol].syncProcessor.syncState
|
|
1021
1155
|
yield* Effect.log(
|
|
1022
|
-
`Session sync state: ${session.localHead} (upstream: ${session.upstreamHead})`,
|
|
1156
|
+
`Session sync state: ${objectToString(session.localHead)} (upstream: ${objectToString(session.upstreamHead)})`,
|
|
1023
1157
|
session.toJSON(),
|
|
1024
1158
|
)
|
|
1025
1159
|
const leader = yield* this[StoreInternalsSymbol].clientSession.leaderThread.syncState
|
|
1026
|
-
yield* Effect.log(
|
|
1160
|
+
yield* Effect.log(
|
|
1161
|
+
`Leader sync state: ${objectToString(leader.localHead)} (upstream: ${objectToString(leader.upstreamHead)})`,
|
|
1162
|
+
leader.toJSON(),
|
|
1163
|
+
)
|
|
1027
1164
|
}).pipe(this.runEffectFork)
|
|
1028
1165
|
},
|
|
1029
1166
|
|
package/src/utils/dev.ts
CHANGED
|
@@ -33,8 +33,8 @@ export const downloadURL = (data: string, fileName: string) => {
|
|
|
33
33
|
export const exposeDebugUtils = () => {
|
|
34
34
|
globalThis.__debugLiveStoreUtils = {
|
|
35
35
|
downloadBlob,
|
|
36
|
-
runSync: (effect: Effect.Effect<
|
|
37
|
-
runFork: (effect: Effect.Effect<
|
|
36
|
+
runSync: <A, E>(effect: Effect.Effect<A, E>) => Effect.runSync(effect),
|
|
37
|
+
runFork: <A, E>(effect: Effect.Effect<A, E>) => Effect.runFork(effect),
|
|
38
38
|
dumpDb: (db: SqliteDb) => {
|
|
39
39
|
const tables = db.select<{ name: string }>(`SELECT name FROM sqlite_master WHERE type='table'`)
|
|
40
40
|
for (const table of tables) {
|
package/src/utils/stack-info.ts
CHANGED
|
@@ -41,12 +41,12 @@ export const extractStackInfoFromStackTrace = (stackTrace: string): StackInfo =>
|
|
|
41
41
|
// console.debug(name, filePath)
|
|
42
42
|
|
|
43
43
|
// NOTE No idea where this `Module.` comes from - possibly a Vite thing?
|
|
44
|
-
if ((name.startsWith('use') || name.startsWith('Module.use')) && name.endsWith('QueryRef') === false) {
|
|
44
|
+
if ((name.startsWith('use') === true || name.startsWith('Module.use') === true) && name.endsWith('QueryRef') === false) {
|
|
45
45
|
hasReachedStart = true
|
|
46
46
|
// console.debug('hasReachedStart. adding one more frame.')
|
|
47
47
|
|
|
48
48
|
frames.unshift({ name: name.replace(/^Module\./, ''), filePath })
|
|
49
|
-
} else if (hasReachedStart) {
|
|
49
|
+
} else if (hasReachedStart === true) {
|
|
50
50
|
// We've reached the end of the `use*` functions, so we're adding the component name and stop
|
|
51
51
|
// Unless it's `react-stack-bottom-frame`, which we skip
|
|
52
52
|
if (name !== 'Object.react-stack-bottom-frame') {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type * as otel from '@opentelemetry/api'
|
|
2
|
+
|
|
1
3
|
import { makeInMemoryAdapter } from '@livestore/adapter-web'
|
|
2
4
|
import { provideOtel } from '@livestore/common'
|
|
3
5
|
import { createStore, Events, makeSchema, State } from '@livestore/livestore'
|
|
4
6
|
import { omitUndefineds } from '@livestore/utils'
|
|
5
7
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
6
|
-
import type * as otel from '@opentelemetry/api'
|
|
7
8
|
|
|
8
9
|
export type Todo = {
|
|
9
10
|
id: string
|
package/src/utils/tests/otel.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { omitUndefineds } from '@livestore/utils'
|
|
2
|
-
import { identity } from '@livestore/utils/effect'
|
|
3
1
|
import type { Attributes } from '@opentelemetry/api'
|
|
4
2
|
import type { InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
|
5
3
|
|
|
4
|
+
import { omitUndefineds } from '@livestore/utils'
|
|
5
|
+
import { identity } from '@livestore/utils/effect'
|
|
6
|
+
|
|
6
7
|
type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
|
|
7
8
|
|
|
8
9
|
type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
|
|
@@ -19,8 +20,8 @@ const buildSimplifiedRootSpans = (
|
|
|
19
20
|
|
|
20
21
|
spansMap.forEach((nestedSpan) => {
|
|
21
22
|
const parentId = nestedSpan.span.parentSpanContext?.spanId
|
|
22
|
-
const parentSpan = parentId ? spansMap.get(parentId) : undefined
|
|
23
|
-
if (parentSpan) {
|
|
23
|
+
const parentSpan = parentId !== undefined ? spansMap.get(parentId) : undefined
|
|
24
|
+
if (parentSpan !== undefined) {
|
|
24
25
|
parentSpan.children.push(nestedSpan)
|
|
25
26
|
}
|
|
26
27
|
})
|
|
@@ -55,7 +56,7 @@ export const getSimplifiedRootSpan = (
|
|
|
55
56
|
): SimplifiedNestedSpan => {
|
|
56
57
|
const results = buildSimplifiedRootSpans(exporter, rootSpanName, mapAttributes)
|
|
57
58
|
const firstResult = results[0]
|
|
58
|
-
if (
|
|
59
|
+
if (firstResult == null) throw new Error(`Could not find the root span named '${rootSpanName}'.`)
|
|
59
60
|
return firstResult
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -77,7 +78,7 @@ const omitEmpty = (obj: any) => {
|
|
|
77
78
|
for (const key in obj) {
|
|
78
79
|
if (
|
|
79
80
|
obj[key] !== undefined &&
|
|
80
|
-
!(Array.isArray(obj[key]) && obj[key].length === 0) &&
|
|
81
|
+
!(Array.isArray(obj[key]) === true && obj[key].length === 0) &&
|
|
81
82
|
Object.keys(obj[key]).length > 0
|
|
82
83
|
) {
|
|
83
84
|
result[key] = obj[key]
|
|
@@ -119,7 +120,7 @@ export const toTraceFile = (spans: ReadableSpan[]) => {
|
|
|
119
120
|
typeof value === 'string'
|
|
120
121
|
? { stringValue: value }
|
|
121
122
|
: typeof value === 'number'
|
|
122
|
-
? Number.isInteger(value)
|
|
123
|
+
? Number.isInteger(value) === true
|
|
123
124
|
? { intValue: value }
|
|
124
125
|
: { doubleValue: value }
|
|
125
126
|
: typeof value === 'boolean'
|
package/docs/api/index.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Data modeling
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Debugging a LiveStore app
|
|
2
|
-
|
|
3
|
-
When working on a LiveStore app you might end up in situations where you need to debug things. LiveStore is built with debuggability in mind and tries to make your life as a developer as easy as possible.
|
|
4
|
-
|
|
5
|
-
Here are a few things that LiveStore offers to help you debug your app:
|
|
6
|
-
|
|
7
|
-
- [OpenTelemetry](/building-with-livestore/opentelemetry) integration for tracing / metrics
|
|
8
|
-
- [Devtools](/building-with-livestore/devtools) for inspecting the state of the store
|
|
9
|
-
- Store helper methods
|
|
10
|
-
|
|
11
|
-
## Debugging helpers on the store
|
|
12
|
-
|
|
13
|
-
The `store` exposes a `_dev` property which contains a few helpers that can help you debug your app.
|
|
14
|
-
|
|
15
|
-
## Other recommended practices and tools
|
|
16
|
-
|
|
17
|
-
- Use the step debugger
|