@livestore/livestore 0.4.0-dev.9 → 0.4.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/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 +4 -3
- package/dist/SqliteDbWrapper.test.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +133 -5
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +187 -8
- 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 +110 -7
- 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 +4 -3
- package/dist/live-queries/client-document-get-query.js.map +1 -1
- package/dist/live-queries/computed.d.ts +56 -0
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +58 -2
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts.map +1 -1
- package/dist/live-queries/db-query.js +21 -19
- package/dist/live-queries/db-query.js.map +1 -1
- package/dist/live-queries/db-query.test.js +106 -23
- package/dist/live-queries/db-query.test.js.map +1 -1
- package/dist/live-queries/signal.d.ts +49 -0
- package/dist/live-queries/signal.d.ts.map +1 -1
- package/dist/live-queries/signal.js +49 -0
- package/dist/live-queries/signal.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 +3 -3
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- 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 +98 -18
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +49 -20
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -16
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +59 -18
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-eventstream.test.d.ts +2 -0
- package/dist/store/store-eventstream.test.d.ts.map +1 -0
- package/dist/store/store-eventstream.test.js +65 -0
- package/dist/store/store-eventstream.test.js.map +1 -0
- package/dist/store/store-types.d.ts +285 -27
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js +77 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store-types.test.d.ts +2 -0
- package/dist/store/store-types.test.d.ts.map +1 -0
- package/dist/store/store-types.test.js +39 -0
- package/dist/store/store-types.test.js.map +1 -0
- package/dist/store/store.d.ts +253 -66
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +442 -153
- package/dist/store/store.js.map +1 -1
- package/dist/utils/dev.d.ts.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 +20 -5
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +7 -0
- 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 -17
- package/src/QueryCache.ts +1 -1
- package/src/SqliteDbWrapper.test.ts +5 -3
- 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 +388 -13
- package/src/effect/mod.ts +13 -1
- package/src/live-queries/__snapshots__/db-query.test.ts.snap +604 -192
- package/src/live-queries/base-class.ts +126 -28
- package/src/live-queries/client-document-get-query.ts +6 -4
- package/src/live-queries/computed.ts +59 -2
- package/src/live-queries/db-query.test.ts +162 -24
- package/src/live-queries/db-query.ts +23 -20
- package/src/live-queries/signal.test.ts +3 -2
- package/src/live-queries/signal.ts +49 -0
- package/src/mod.ts +19 -2
- 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 +158 -39
- package/src/store/devtools.ts +77 -33
- package/src/store/store-eventstream.test.ts +114 -0
- package/src/store/store-types.test.ts +52 -0
- package/src/store/store-types.ts +360 -40
- package/src/store/store.ts +571 -236
- package/src/utils/dev.ts +2 -3
- package/src/utils/stack-info.ts +2 -2
- package/src/utils/tests/fixture.ts +9 -1
- package/src/utils/tests/otel.ts +8 -7
|
@@ -7,15 +7,16 @@ import {
|
|
|
7
7
|
QueryBuilderAstSymbol,
|
|
8
8
|
replaceSessionIdSymbol,
|
|
9
9
|
SessionIdSymbol,
|
|
10
|
-
|
|
10
|
+
UnknownError,
|
|
11
11
|
} from '@livestore/common'
|
|
12
|
-
import { deepEqual, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
12
|
+
import { deepEqual, objectToString, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
13
13
|
import { Equal, Hash, Predicate, Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
14
14
|
import * as otel from '@opentelemetry/api'
|
|
15
15
|
|
|
16
16
|
import type { Thunk } from '../reactive.ts'
|
|
17
17
|
import { isThunk, NOT_REFRESHED_YET } from '../reactive.ts'
|
|
18
18
|
import type { RefreshReason } from '../store/store-types.ts'
|
|
19
|
+
import { StoreInternalsSymbol } from '../store/store-types.ts'
|
|
19
20
|
import { isValidFunctionString } from '../utils/function-string.ts'
|
|
20
21
|
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.ts'
|
|
21
22
|
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.ts'
|
|
@@ -102,7 +103,7 @@ export const queryDb: {
|
|
|
102
103
|
} = (queryInput, options) => {
|
|
103
104
|
const { queryString, extraDeps } = getQueryStringAndExtraDeps(queryInput)
|
|
104
105
|
|
|
105
|
-
const hash = [queryString, options?.deps ? depsToString(options.deps) : undefined, depsToString(extraDeps)]
|
|
106
|
+
const hash = [queryString, options?.deps !== undefined ? depsToString(options.deps) : undefined, depsToString(extraDeps)]
|
|
106
107
|
.filter(Boolean)
|
|
107
108
|
.join('-')
|
|
108
109
|
|
|
@@ -111,7 +112,7 @@ export const queryDb: {
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
if (hash.trim() === '') {
|
|
114
|
-
|
|
115
|
+
return shouldNeverHappen('Invalid query hash for query:', objectToString(queryInput))
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
const label = options?.label ?? queryString
|
|
@@ -154,12 +155,12 @@ const bindValuesToDepKey = (bindValues: Bindable | undefined): DepKey => {
|
|
|
154
155
|
const getQueryStringAndExtraDeps = (
|
|
155
156
|
queryInput: QueryInput<any, any> | ((get: GetAtomResult) => QueryInput<any, any>),
|
|
156
157
|
): { queryString: string; extraDeps: DepKey } => {
|
|
157
|
-
if (isQueryBuilder(queryInput)) {
|
|
158
|
+
if (isQueryBuilder(queryInput) === true) {
|
|
158
159
|
const { query, bindValues } = queryInput.asSql()
|
|
159
160
|
return { queryString: query, extraDeps: bindValuesToDepKey(bindValues) }
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
if (isQueryInputRaw(queryInput)) {
|
|
163
|
+
if (isQueryInputRaw(queryInput) === true) {
|
|
163
164
|
return { queryString: queryInput.query, extraDeps: bindValuesToDepKey(queryInput.bindValues) }
|
|
164
165
|
}
|
|
165
166
|
|
|
@@ -167,7 +168,7 @@ const getQueryStringAndExtraDeps = (
|
|
|
167
168
|
return { queryString: queryInput.toString(), extraDeps: [] }
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
return shouldNeverHappen(`Invalid query input: ${queryInput}`)
|
|
171
|
+
return shouldNeverHappen(`Invalid query input: ${String(queryInput)}`)
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
/* An object encapsulating a reactive SQL query */
|
|
@@ -215,7 +216,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
215
216
|
|
|
216
217
|
const schemaRef: { current: Schema.Schema<any, any> | undefined } = {
|
|
217
218
|
current:
|
|
218
|
-
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) ? undefined : queryInput.schema,
|
|
219
|
+
typeof queryInput === 'function' ? undefined : isQueryBuilder(queryInput) === true ? undefined : queryInput.schema,
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
const execBeforeFirstRunRef: {
|
|
@@ -253,7 +254,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
253
254
|
: undefined,
|
|
254
255
|
}
|
|
255
256
|
} catch (cause) {
|
|
256
|
-
throw new
|
|
257
|
+
throw new UnknownError({ cause, note: `Error building query for ${qb.toString()}`, payload: { qb } })
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
|
|
@@ -269,7 +270,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
269
270
|
|
|
270
271
|
let queryInputRaw: TQueryInputRaw
|
|
271
272
|
|
|
272
|
-
if (isQueryBuilder(queryInputResult)) {
|
|
273
|
+
if (isQueryBuilder(queryInputResult) === true) {
|
|
273
274
|
const res = fromQueryBuilder(queryInputResult, otelContext)
|
|
274
275
|
queryInputRaw = res.queryInputRaw
|
|
275
276
|
// setting label dynamically here
|
|
@@ -296,7 +297,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
296
297
|
this.queryInput$ = queryInputRaw$OrQueryInputRaw
|
|
297
298
|
} else {
|
|
298
299
|
let queryInputRaw: TQueryInputRaw
|
|
299
|
-
if (isQueryBuilder(queryInput)) {
|
|
300
|
+
if (isQueryBuilder(queryInput) === true) {
|
|
300
301
|
const res = fromQueryBuilder(queryInput, otelContext)
|
|
301
302
|
queryInputRaw = res.queryInputRaw
|
|
302
303
|
label = res.label
|
|
@@ -309,7 +310,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
309
310
|
queryInputRaw$OrQueryInputRaw = queryInputRaw
|
|
310
311
|
|
|
311
312
|
// this.label = inputLabel ? this.label : `db(${})`
|
|
312
|
-
if (inputLabel === undefined && isQueryBuilder(queryInput)) {
|
|
313
|
+
if (inputLabel === undefined && isQueryBuilder(queryInput) === true) {
|
|
313
314
|
const ast = queryInput[QueryBuilderAstSymbol]
|
|
314
315
|
if (ast._tag === 'RowQuery') {
|
|
315
316
|
label = `db(${rowQueryLabel(ast.tableDef, ast.id)})`
|
|
@@ -341,7 +342,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
341
342
|
'db:...', // NOTE span name will be overridden further down
|
|
342
343
|
{
|
|
343
344
|
attributes: {
|
|
344
|
-
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label')
|
|
345
|
+
'livestore.debugRefreshReason': Predicate.hasProperty(debugRefreshReason, 'label') === true
|
|
345
346
|
? (debugRefreshReason.label as string)
|
|
346
347
|
: debugRefreshReason?._tag,
|
|
347
348
|
},
|
|
@@ -356,7 +357,7 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
356
357
|
execBeforeFirstRunRef.current = undefined
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw)
|
|
360
|
+
const queryInputResult = isThunk(queryInputRaw$OrQueryInputRaw) === true
|
|
360
361
|
? (get(queryInputRaw$OrQueryInputRaw, otelContext, debugRefreshReason) as TQueryInputRaw)
|
|
361
362
|
: (queryInputRaw$OrQueryInputRaw as TQueryInputRaw)
|
|
362
363
|
|
|
@@ -364,28 +365,30 @@ export class LiveStoreDbQuery<TResultSchema, TResult = TResultSchema> extends Li
|
|
|
364
365
|
const bindValues = queryInputResult.bindValues
|
|
365
366
|
|
|
366
367
|
if (queriedTablesRef.current === undefined) {
|
|
367
|
-
queriedTablesRef.current = store.sqliteDbWrapper.getTablesUsed(sqlString)
|
|
368
|
+
queriedTablesRef.current = store[StoreInternalsSymbol].sqliteDbWrapper.getTablesUsed(sqlString)
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
if (bindValues !== undefined) {
|
|
371
|
-
replaceSessionIdSymbol(bindValues, store.
|
|
372
|
+
replaceSessionIdSymbol(bindValues, store.sessionId)
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
// Establish a reactive dependency on the tables used in the query
|
|
375
376
|
for (const tableName of queriedTablesRef.current) {
|
|
376
|
-
const tableRef =
|
|
377
|
+
const tableRef =
|
|
378
|
+
store[StoreInternalsSymbol].tableRefs[tableName] ??
|
|
379
|
+
shouldNeverHappen(`No table ref found for ${tableName}`)
|
|
377
380
|
get(tableRef, otelContext, debugRefreshReason)
|
|
378
381
|
}
|
|
379
382
|
|
|
380
383
|
span.setAttribute('sql.query', sqlString)
|
|
381
384
|
span.updateName(`db:${sqlString.slice(0, 50)}`)
|
|
382
385
|
|
|
383
|
-
const rawDbResults = store.sqliteDbWrapper.cachedSelect
|
|
386
|
+
const rawDbResults = store[StoreInternalsSymbol].sqliteDbWrapper.cachedSelect(
|
|
384
387
|
sqlString,
|
|
385
|
-
bindValues ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
388
|
+
bindValues !== undefined ? prepareBindValues(bindValues, sqlString) : undefined,
|
|
386
389
|
{
|
|
387
|
-
queriedTables: queriedTablesRef.current,
|
|
388
390
|
otelContext,
|
|
391
|
+
...(queriedTablesRef.current !== undefined ? { queriedTables: queriedTablesRef.current } : {}),
|
|
389
392
|
},
|
|
390
393
|
)
|
|
391
394
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Effect } from '@livestore/utils/effect'
|
|
2
|
-
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
3
1
|
import { expect } from 'vitest'
|
|
4
2
|
|
|
3
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
+
import { Effect } from '@livestore/utils/effect'
|
|
5
|
+
|
|
5
6
|
import { makeTodoMvc } from '../utils/tests/fixture.ts'
|
|
6
7
|
import { computed } from './computed.ts'
|
|
7
8
|
import { signal } from './signal.ts'
|
|
@@ -6,6 +6,48 @@ import type { RefreshReason } from '../store/store-types.ts'
|
|
|
6
6
|
import type { ISignal, ReactivityGraph, ReactivityGraphContext, SignalDef } from './base-class.ts'
|
|
7
7
|
import { LiveStoreQueryBase, withRCMap } from './base-class.ts'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Creates a reactive signal for ephemeral, local-only state that isn't persisted to the database.
|
|
11
|
+
*
|
|
12
|
+
* Signals are useful for UI state that needs to trigger query re-evaluation but shouldn't be
|
|
13
|
+
* synced across clients or stored permanently—such as search filters, selected items, or
|
|
14
|
+
* temporary form values.
|
|
15
|
+
*
|
|
16
|
+
* Unlike database-backed state (via events), signals:
|
|
17
|
+
* - Are not persisted or synced
|
|
18
|
+
* - Exist only for the lifetime of the Store
|
|
19
|
+
* - Can hold any value type (primitives, objects, functions)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* // Create a signal for search text
|
|
24
|
+
* const searchText$ = signal('', { label: 'searchText' })
|
|
25
|
+
*
|
|
26
|
+
* // Create a query that depends on the signal
|
|
27
|
+
* const filteredTodos$ = queryDb(
|
|
28
|
+
* (get) => tables.todos.where({ text: { $like: `%${get(searchText$)}%` } }),
|
|
29
|
+
* { deps: [searchText$] }
|
|
30
|
+
* )
|
|
31
|
+
*
|
|
32
|
+
* // Update the signal (triggers query re-evaluation)
|
|
33
|
+
* store.setSignal(searchText$, 'buy')
|
|
34
|
+
*
|
|
35
|
+
* // Read the current value
|
|
36
|
+
* const results = store.query(filteredTodos$)
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // Counter with functional updates
|
|
42
|
+
* const count$ = signal(0, { label: 'count' })
|
|
43
|
+
*
|
|
44
|
+
* store.setSignal(count$, (prev) => prev + 1)
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param defaultValue - Initial value of the signal
|
|
48
|
+
* @param options.label - Human-readable label for debugging and devtools
|
|
49
|
+
* @returns A signal definition that can be used with `store.query()`, `store.setSignal()`, and as a dependency in other queries
|
|
50
|
+
*/
|
|
9
51
|
export const signal = <T>(
|
|
10
52
|
defaultValue: T,
|
|
11
53
|
options?: {
|
|
@@ -39,6 +81,13 @@ export const signal = <T>(
|
|
|
39
81
|
return def
|
|
40
82
|
}
|
|
41
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A live signal instance bound to a specific Store.
|
|
86
|
+
*
|
|
87
|
+
* Signal instances are created internally when you use a `SignalDef` with the Store.
|
|
88
|
+
* You typically don't construct these directly—use {@link signal} to create definitions
|
|
89
|
+
* and `store.setSignal()` / `store.query()` to interact with them.
|
|
90
|
+
*/
|
|
42
91
|
export class Signal<T> extends LiveStoreQueryBase<T> implements ISignal<T> {
|
|
43
92
|
_tag = 'signal' as const
|
|
44
93
|
readonly ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
|
package/src/mod.ts
CHANGED
|
@@ -35,14 +35,31 @@ export {
|
|
|
35
35
|
signal,
|
|
36
36
|
} from './live-queries/mod.ts'
|
|
37
37
|
export { emptyDebugInfo, SqliteDbWrapper } from './SqliteDbWrapper.ts'
|
|
38
|
-
export {
|
|
38
|
+
export {
|
|
39
|
+
type CreateStoreOptions,
|
|
40
|
+
type CreateStoreOptionsPromise,
|
|
41
|
+
createStore,
|
|
42
|
+
createStorePromise,
|
|
43
|
+
} from './store/create-store.ts'
|
|
44
|
+
export { type RegistryStoreOptions, StoreRegistry, storeOptions } from './store/StoreRegistry.ts'
|
|
39
45
|
export { Store } from './store/store.ts'
|
|
40
|
-
export type { OtelOptions, QueryDebugInfo, RefreshReason, Unsubscribe } from './store/store-types.ts'
|
|
41
46
|
export {
|
|
47
|
+
isLiveQueryDef,
|
|
48
|
+
isLiveQueryInstance,
|
|
49
|
+
isQueryable,
|
|
42
50
|
type LiveStoreContext,
|
|
43
51
|
type LiveStoreContextRunning,
|
|
44
52
|
makeShutdownDeferred,
|
|
53
|
+
type OtelOptions,
|
|
54
|
+
type Queryable,
|
|
55
|
+
type QueryDebugInfo,
|
|
56
|
+
type RefreshReason,
|
|
45
57
|
type ShutdownDeferred,
|
|
58
|
+
type StoreInternals,
|
|
59
|
+
StoreInternalsSymbol,
|
|
60
|
+
type SubscribeOptions,
|
|
61
|
+
type SyncStatus,
|
|
62
|
+
type Unsubscribe,
|
|
46
63
|
} from './store/store-types.ts'
|
|
47
64
|
export { exposeDebugUtils } from './utils/dev.ts'
|
|
48
65
|
export * from './utils/stack-info.ts'
|
package/src/reactive.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
2
3
|
import type { DebugRefreshReasonBase, DebugThunkInfo } from './reactive.ts'
|
|
3
4
|
import { ReactiveGraph } from './reactive.ts'
|
|
4
5
|
|
|
@@ -142,7 +143,7 @@ describe('a trivial graph', () => {
|
|
|
142
143
|
expect(numberOfEffectRuns).toBe(0)
|
|
143
144
|
const effect = graph.makeEffect((get) => {
|
|
144
145
|
// establish a dependency on thunk c and mutate an outside value
|
|
145
|
-
expect(get(c)).toBe(aHasChanged ? 3 : 4)
|
|
146
|
+
expect(get(c)).toBe(aHasChanged === true ? 3 : 4)
|
|
146
147
|
numberOfEffectRuns++
|
|
147
148
|
})
|
|
148
149
|
|
|
@@ -562,7 +563,7 @@ describe('bug fix: node corruption protection', () => {
|
|
|
562
563
|
|
|
563
564
|
let firstRun = true
|
|
564
565
|
const effect = graph.makeEffect((get) => {
|
|
565
|
-
if (firstRun) {
|
|
566
|
+
if (firstRun !== undefined) {
|
|
566
567
|
firstRun = false
|
|
567
568
|
graph.destroyNode(thunk1) // Destroy dependency mid-execution
|
|
568
569
|
}
|
package/src/reactive.ts
CHANGED
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
// is maintained eagerly as edges are added and removed.)
|
|
22
22
|
// - At every thunk we check value equality with the previous value and cutoff propagation if possible.
|
|
23
23
|
|
|
24
|
+
import type * as otel from '@opentelemetry/api'
|
|
25
|
+
|
|
24
26
|
import { BoundArray } from '@livestore/common'
|
|
25
27
|
import { deepEqual, omitUndefineds, shouldNeverHappen } from '@livestore/utils'
|
|
26
28
|
import type { Types } from '@livestore/utils/effect'
|
|
27
|
-
import type * as otel from '@opentelemetry/api'
|
|
28
29
|
// import { getDurationMsFromSpan } from './otel.ts'
|
|
29
30
|
|
|
30
31
|
export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
@@ -32,8 +33,8 @@ export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
|
32
33
|
|
|
33
34
|
export type GetAtom = <T>(
|
|
34
35
|
atom: Atom<T, any, any>,
|
|
35
|
-
otelContext?: otel.Context
|
|
36
|
-
debugRefreshReason?: TODO
|
|
36
|
+
otelContext?: otel.Context ,
|
|
37
|
+
debugRefreshReason?: TODO ,
|
|
37
38
|
) => T
|
|
38
39
|
|
|
39
40
|
export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
@@ -47,7 +48,7 @@ export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
|
47
48
|
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
|
48
49
|
label?: string | undefined
|
|
49
50
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
50
|
-
meta?:
|
|
51
|
+
meta?: unknown
|
|
51
52
|
equal: (a: T, b: T) => boolean
|
|
52
53
|
refreshes: number
|
|
53
54
|
}
|
|
@@ -63,7 +64,7 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshRea
|
|
|
63
64
|
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
|
64
65
|
label?: string | undefined
|
|
65
66
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
66
|
-
meta?:
|
|
67
|
+
meta?: unknown
|
|
67
68
|
equal: (a: TResult, b: TResult) => boolean
|
|
68
69
|
recomputations: number
|
|
69
70
|
|
|
@@ -78,7 +79,7 @@ export type Effect<TDebugRefreshReason extends DebugRefreshReason> = {
|
|
|
78
79
|
_tag: 'effect'
|
|
79
80
|
id: string
|
|
80
81
|
isDestroyed: boolean
|
|
81
|
-
doEffect: (otelContext?: otel.Context
|
|
82
|
+
doEffect: (otelContext?: otel.Context , debugRefreshReason?: TDebugRefreshReason ) => void
|
|
82
83
|
sub: Set<Atom<any, TODO, TODO>>
|
|
83
84
|
label?: string | undefined
|
|
84
85
|
invocations: number
|
|
@@ -91,7 +92,7 @@ export type Node<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
|
|
|
91
92
|
export const isThunk = <T, TContext, TDebugRefreshReason extends DebugRefreshReason>(
|
|
92
93
|
obj: unknown,
|
|
93
94
|
): obj is Thunk<T, TContext, TDebugRefreshReason> => {
|
|
94
|
-
return typeof obj === 'object' && obj !== null && '_tag' in obj && (obj as
|
|
95
|
+
return typeof obj === 'object' && obj !== null && '_tag' in obj && (obj as { _tag: unknown })._tag === 'thunk'
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
export type DebugThunkInfo<T extends string = string> = {
|
|
@@ -203,9 +204,7 @@ export class ReactiveGraph<
|
|
|
203
204
|
|
|
204
205
|
debugRefreshInfos: BoundArray<RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo>> = new BoundArray(200)
|
|
205
206
|
|
|
206
|
-
private currentDebugRefresh:
|
|
207
|
-
| { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
|
|
208
|
-
| undefined
|
|
207
|
+
private currentDebugRefresh: { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: number } | undefined
|
|
209
208
|
|
|
210
209
|
private deferredEffects: Map<Effect<TDebugRefreshReason>, Set<TDebugRefreshReason>> = new Map()
|
|
211
210
|
|
|
@@ -253,7 +252,7 @@ export class ReactiveGraph<
|
|
|
253
252
|
meta?: any
|
|
254
253
|
equal?: (a: T, b: T) => boolean
|
|
255
254
|
}
|
|
256
|
-
|
|
255
|
+
,
|
|
257
256
|
): Thunk<T, TContext, TDebugRefreshReason> {
|
|
258
257
|
const thunk: Thunk<T, TContext, TDebugRefreshReason> = {
|
|
259
258
|
_tag: 'thunk',
|
|
@@ -262,10 +261,10 @@ export class ReactiveGraph<
|
|
|
262
261
|
isDirty: true,
|
|
263
262
|
isDestroyed: false,
|
|
264
263
|
computeResult: (otelContext, debugRefreshReason) => {
|
|
265
|
-
if (thunk.isDirty) {
|
|
264
|
+
if (thunk.isDirty === true) {
|
|
266
265
|
const neededCurrentRefresh = this.currentDebugRefresh === undefined
|
|
267
266
|
let localDebugRefresh: { refreshedAtoms: any[]; startMs: number } | undefined
|
|
268
|
-
if (neededCurrentRefresh) {
|
|
267
|
+
if (neededCurrentRefresh === true) {
|
|
269
268
|
// Use local variable to prevent corruption from nested computations
|
|
270
269
|
localDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
|
271
270
|
this.currentDebugRefresh = localDebugRefresh
|
|
@@ -292,7 +291,7 @@ export class ReactiveGraph<
|
|
|
292
291
|
debugRefreshReason,
|
|
293
292
|
)
|
|
294
293
|
|
|
295
|
-
const resultChanged = thunk.equal(thunk.previousResult as T, result)
|
|
294
|
+
const resultChanged = ! thunk.equal(thunk.previousResult as T, result)
|
|
296
295
|
|
|
297
296
|
const debugInfoForAtom = {
|
|
298
297
|
atom: serializeAtom(thunk, false),
|
|
@@ -302,7 +301,7 @@ export class ReactiveGraph<
|
|
|
302
301
|
|
|
303
302
|
// Use currentDebugRefresh if available (could be from parent or local)
|
|
304
303
|
const debugRefresh = localDebugRefresh ?? this.currentDebugRefresh
|
|
305
|
-
if (debugRefresh) {
|
|
304
|
+
if (debugRefresh !== undefined) {
|
|
306
305
|
debugRefresh.refreshedAtoms.push(debugInfoForAtom)
|
|
307
306
|
}
|
|
308
307
|
|
|
@@ -310,7 +309,7 @@ export class ReactiveGraph<
|
|
|
310
309
|
thunk.previousResult = result
|
|
311
310
|
thunk.recomputations++
|
|
312
311
|
|
|
313
|
-
if (neededCurrentRefresh && localDebugRefresh) {
|
|
312
|
+
if (neededCurrentRefresh === true && localDebugRefresh !== undefined) {
|
|
314
313
|
// Use local reference which can't be corrupted by nested calls
|
|
315
314
|
const refreshedAtoms = localDebugRefresh.refreshedAtoms
|
|
316
315
|
const durationMs = performance.now() - localDebugRefresh.startMs
|
|
@@ -385,7 +384,7 @@ export class ReactiveGraph<
|
|
|
385
384
|
otelContext: otel.Context | undefined,
|
|
386
385
|
debugRefreshReason: DebugRefreshReason | undefined,
|
|
387
386
|
) => void,
|
|
388
|
-
options?: { label?: string }
|
|
387
|
+
options?: { label?: string } ,
|
|
389
388
|
): Effect<TDebugRefreshReason> {
|
|
390
389
|
const effect: Effect<TDebugRefreshReason> = {
|
|
391
390
|
_tag: 'effect',
|
|
@@ -429,7 +428,7 @@ export class ReactiveGraph<
|
|
|
429
428
|
debugRefreshReason?: TDebugRefreshReason
|
|
430
429
|
otelContext?: otel.Context
|
|
431
430
|
}
|
|
432
|
-
|
|
431
|
+
,
|
|
433
432
|
) {
|
|
434
433
|
this.setRefs([[ref, val]], options)
|
|
435
434
|
}
|
|
@@ -442,7 +441,7 @@ export class ReactiveGraph<
|
|
|
442
441
|
debugRefreshReason?: TDebugRefreshReason
|
|
443
442
|
otelContext?: otel.Context
|
|
444
443
|
}
|
|
445
|
-
|
|
444
|
+
,
|
|
446
445
|
) {
|
|
447
446
|
const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
|
|
448
447
|
for (const [ref, val] of refs) {
|
|
@@ -452,7 +451,7 @@ export class ReactiveGraph<
|
|
|
452
451
|
markSuperCompDirtyRec(ref, effectsToRefresh)
|
|
453
452
|
}
|
|
454
453
|
|
|
455
|
-
if (options?.skipRefresh) {
|
|
454
|
+
if (options?.skipRefresh === true) {
|
|
456
455
|
for (const effect of effectsToRefresh) {
|
|
457
456
|
if (this.deferredEffects.has(effect) === false) {
|
|
458
457
|
this.deferredEffects.set(effect, new Set())
|
|
@@ -598,11 +597,11 @@ const compute = <T>(
|
|
|
598
597
|
debugRefreshReason: DebugRefreshReason | undefined,
|
|
599
598
|
): T => {
|
|
600
599
|
// const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
|
|
601
|
-
if (atom.isDestroyed) {
|
|
600
|
+
if (atom.isDestroyed === true) {
|
|
602
601
|
shouldNeverHappen(`LiveStore Error: Attempted to compute destroyed ${atom._tag} (${atom.id}): ${atom.label ?? ''}`)
|
|
603
602
|
}
|
|
604
603
|
|
|
605
|
-
if (atom.isDirty) {
|
|
604
|
+
if (atom.isDirty === true) {
|
|
606
605
|
// console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
607
606
|
const result = atom.computeResult(otelContext, debugRefreshReason)
|
|
608
607
|
atom.isDirty = false
|
|
@@ -641,7 +640,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
|
641
640
|
super_.push(a.id)
|
|
642
641
|
}
|
|
643
642
|
|
|
644
|
-
const previousResult: EncodedOption<string> = includeResult
|
|
643
|
+
const previousResult: EncodedOption<string> = includeResult === true
|
|
645
644
|
? encodedOptionSome(
|
|
646
645
|
atom.previousResult === NOT_REFRESHED_YET ? '"SYMBOL_NOT_REFRESHED_YET"' : JSON.stringify(atom.previousResult),
|
|
647
646
|
)
|