@livestore/livestore 0.3.0-dev.10 → 0.3.0-dev.12
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/SqliteDbWrapper.d.ts +54 -0
- package/dist/SqliteDbWrapper.d.ts.map +1 -0
- package/dist/SqliteDbWrapper.js +211 -0
- package/dist/SqliteDbWrapper.js.map +1 -0
- package/dist/SynchronousDatabaseWrapper.d.ts +14 -5
- package/dist/SynchronousDatabaseWrapper.d.ts.map +1 -1
- package/dist/SynchronousDatabaseWrapper.js +24 -4
- package/dist/SynchronousDatabaseWrapper.js.map +1 -1
- package/dist/effect/LiveStore.d.ts +12 -8
- package/dist/effect/LiveStore.d.ts.map +1 -1
- package/dist/effect/LiveStore.js +9 -2
- package/dist/effect/LiveStore.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/live-queries/base-class.d.ts +57 -21
- package/dist/live-queries/base-class.d.ts.map +1 -1
- package/dist/live-queries/base-class.js +54 -13
- package/dist/live-queries/base-class.js.map +1 -1
- package/dist/live-queries/computed.d.ts +7 -7
- package/dist/live-queries/computed.d.ts.map +1 -1
- package/dist/live-queries/computed.js +34 -11
- package/dist/live-queries/computed.js.map +1 -1
- package/dist/live-queries/db-query.d.ts +67 -0
- package/dist/live-queries/db-query.d.ts.map +1 -0
- package/dist/live-queries/db-query.js +243 -0
- package/dist/live-queries/db-query.js.map +1 -0
- package/dist/live-queries/db-query.test.d.ts +2 -0
- package/dist/live-queries/db-query.test.d.ts.map +1 -0
- package/dist/live-queries/db-query.test.js +113 -0
- package/dist/live-queries/db-query.test.js.map +1 -0
- package/dist/live-queries/db.d.ts +12 -15
- package/dist/live-queries/db.d.ts.map +1 -1
- package/dist/live-queries/db.js +44 -25
- package/dist/live-queries/db.js.map +1 -1
- package/dist/live-queries/db.test.js +16 -14
- package/dist/live-queries/db.test.js.map +1 -1
- package/dist/live-queries/graphql.d.ts +8 -8
- package/dist/live-queries/graphql.d.ts.map +1 -1
- package/dist/live-queries/graphql.js +34 -9
- package/dist/live-queries/graphql.js.map +1 -1
- package/dist/live-queries/make-ref.d.ts +20 -0
- package/dist/live-queries/make-ref.d.ts.map +1 -0
- package/dist/live-queries/make-ref.js +33 -0
- package/dist/live-queries/make-ref.js.map +1 -0
- package/dist/reactive.d.ts +19 -13
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +22 -18
- package/dist/reactive.js.map +1 -1
- package/dist/reactive.test.js +1 -1
- package/dist/reactive.test.js.map +1 -1
- package/dist/row-query-utils.d.ts +6 -6
- package/dist/row-query-utils.d.ts.map +1 -1
- package/dist/row-query-utils.js +15 -11
- package/dist/row-query-utils.js.map +1 -1
- package/dist/store/create-store.d.ts +7 -5
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/create-store.js +21 -7
- package/dist/store/create-store.js.map +1 -1
- package/dist/store/devtools.d.ts +5 -4
- package/dist/store/devtools.d.ts.map +1 -1
- package/dist/store/devtools.js +45 -23
- package/dist/store/devtools.js.map +1 -1
- package/dist/store/store-types.d.ts +9 -4
- package/dist/store/store-types.d.ts.map +1 -1
- package/dist/store/store-types.js.map +1 -1
- package/dist/store/store.d.ts +36 -18
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +127 -75
- package/dist/store/store.js.map +1 -1
- package/dist/utils/expo.d.ts +2 -0
- package/dist/utils/expo.d.ts.map +1 -0
- package/dist/utils/expo.js +8 -0
- package/dist/utils/expo.js.map +1 -0
- package/dist/utils/function-string.d.ts +7 -0
- package/dist/utils/function-string.d.ts.map +1 -0
- package/dist/utils/function-string.js +9 -0
- package/dist/utils/function-string.js.map +1 -0
- package/dist/utils/stack-info.d.ts.map +1 -1
- package/dist/utils/stack-info.js +6 -1
- package/dist/utils/stack-info.js.map +1 -1
- package/dist/utils/stack-info.test.js +54 -1
- package/dist/utils/stack-info.test.js.map +1 -1
- package/dist/utils/tests/fixture.d.ts +2 -6
- package/dist/utils/tests/fixture.d.ts.map +1 -1
- package/dist/utils/tests/fixture.js +3 -5
- package/dist/utils/tests/fixture.js.map +1 -1
- package/dist/utils/tests/mod.d.ts +1 -0
- package/dist/utils/tests/mod.d.ts.map +1 -1
- package/dist/utils/tests/mod.js +1 -0
- package/dist/utils/tests/mod.js.map +1 -1
- package/package.json +5 -5
- package/src/{SynchronousDatabaseWrapper.ts → SqliteDbWrapper.ts} +41 -12
- package/src/effect/LiveStore.ts +22 -14
- package/src/index.ts +14 -7
- package/src/live-queries/__snapshots__/{db.test.ts.snap → db-query.test.ts.snap} +196 -45
- package/src/live-queries/base-class.ts +151 -40
- package/src/live-queries/computed.ts +44 -19
- package/src/live-queries/{db.test.ts → db-query.test.ts} +44 -32
- package/src/live-queries/{db.ts → db-query.ts} +96 -39
- package/src/live-queries/graphql.ts +46 -21
- package/src/live-queries/make-ref.ts +47 -0
- package/src/reactive.test.ts +1 -1
- package/src/reactive.ts +60 -37
- package/src/row-query-utils.ts +32 -21
- package/src/store/create-store.ts +55 -27
- package/src/store/devtools.ts +74 -29
- package/src/store/store-types.ts +6 -4
- package/src/store/store.ts +231 -121
- package/src/utils/function-string.ts +12 -0
- package/src/utils/stack-info.test.ts +58 -1
- package/src/utils/stack-info.ts +6 -1
- package/src/utils/tests/fixture.ts +2 -7
- package/src/utils/tests/mod.ts +1 -0
- package/src/global-state.ts +0 -20
|
@@ -5,13 +5,12 @@ import { Schema, TreeFormatter } from '@livestore/utils/effect'
|
|
|
5
5
|
import * as otel from '@opentelemetry/api'
|
|
6
6
|
import * as graphql from 'graphql'
|
|
7
7
|
|
|
8
|
-
import { globalReactivityGraph } from '../global-state.js'
|
|
9
8
|
import { isThunk, type Thunk } from '../reactive.js'
|
|
10
9
|
import type { Store } from '../store/store.js'
|
|
11
10
|
import type { BaseGraphQLContext, RefreshReason } from '../store/store-types.js'
|
|
12
11
|
import { getDurationMsFromSpan } from '../utils/otel.js'
|
|
13
|
-
import type {
|
|
14
|
-
import { LiveStoreQueryBase, makeGetAtomResult } from './base-class.js'
|
|
12
|
+
import type { DepKey, GetAtomResult, LiveQueryDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
13
|
+
import { depsToString, LiveStoreQueryBase, makeGetAtomResult, withRCMap } from './base-class.js'
|
|
15
14
|
|
|
16
15
|
export type MapResult<To, From> = ((res: From, get: GetAtomResult) => To) | Schema.Schema<To, From>
|
|
17
16
|
|
|
@@ -22,17 +21,36 @@ export const queryGraphQL = <
|
|
|
22
21
|
>(
|
|
23
22
|
document: DocumentNode<TResult, TVariableValues>,
|
|
24
23
|
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues),
|
|
25
|
-
{
|
|
26
|
-
label,
|
|
27
|
-
reactivityGraph,
|
|
28
|
-
map,
|
|
29
|
-
}: {
|
|
24
|
+
options: {
|
|
30
25
|
label?: string
|
|
31
|
-
reactivityGraph?: ReactivityGraph
|
|
26
|
+
// reactivityGraph?: ReactivityGraph
|
|
32
27
|
map?: MapResult<TResultMapped, TResult>
|
|
28
|
+
deps?: DepKey
|
|
33
29
|
} = {},
|
|
34
|
-
):
|
|
35
|
-
|
|
30
|
+
): LiveQueryDef<TResultMapped, QueryInfo.None> => {
|
|
31
|
+
const documentName = graphql.getOperationAST(document)?.name?.value
|
|
32
|
+
const hash = options.deps
|
|
33
|
+
? depsToString(options.deps)
|
|
34
|
+
: (documentName ?? shouldNeverHappen('No document name found and no deps provided'))
|
|
35
|
+
const label = options.label ?? documentName ?? 'graphql'
|
|
36
|
+
const map = options.map
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
_tag: 'def',
|
|
40
|
+
make: withRCMap(hash, (ctx, _otelContext) => {
|
|
41
|
+
return new LiveStoreGraphQLQuery({
|
|
42
|
+
document,
|
|
43
|
+
genVariableValues,
|
|
44
|
+
label,
|
|
45
|
+
map,
|
|
46
|
+
reactivityGraph: ctx.reactivityGraph.deref()!,
|
|
47
|
+
})
|
|
48
|
+
}),
|
|
49
|
+
label,
|
|
50
|
+
hash,
|
|
51
|
+
queryInfo: { _tag: 'None' },
|
|
52
|
+
}
|
|
53
|
+
}
|
|
36
54
|
|
|
37
55
|
export class LiveStoreGraphQLQuery<
|
|
38
56
|
TResult extends Record<string, any>,
|
|
@@ -46,13 +64,13 @@ export class LiveStoreGraphQLQuery<
|
|
|
46
64
|
document: DocumentNode<TResult, TVariableValues>
|
|
47
65
|
|
|
48
66
|
/** A reactive thunk representing the query results */
|
|
49
|
-
results$: Thunk<TResultMapped,
|
|
67
|
+
results$: Thunk<TResultMapped, ReactivityGraphContext, RefreshReason>
|
|
50
68
|
|
|
51
|
-
variableValues$: Thunk<TVariableValues,
|
|
69
|
+
variableValues$: Thunk<TVariableValues, ReactivityGraphContext, RefreshReason> | undefined
|
|
52
70
|
|
|
53
71
|
label: string
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
reactivityGraph: ReactivityGraph
|
|
56
74
|
|
|
57
75
|
queryInfo: QueryInfo.None = { _tag: 'None' }
|
|
58
76
|
|
|
@@ -68,7 +86,7 @@ export class LiveStoreGraphQLQuery<
|
|
|
68
86
|
document: DocumentNode<TResult, TVariableValues>
|
|
69
87
|
genVariableValues: TVariableValues | ((get: GetAtomResult) => TVariableValues)
|
|
70
88
|
label?: string
|
|
71
|
-
reactivityGraph
|
|
89
|
+
reactivityGraph: ReactivityGraph
|
|
72
90
|
map?: MapResult<TResultMapped, TResult>
|
|
73
91
|
}) {
|
|
74
92
|
super()
|
|
@@ -78,7 +96,7 @@ export class LiveStoreGraphQLQuery<
|
|
|
78
96
|
this.label = labelWithDefault
|
|
79
97
|
this.document = document
|
|
80
98
|
|
|
81
|
-
this.reactivityGraph = reactivityGraph
|
|
99
|
+
this.reactivityGraph = reactivityGraph
|
|
82
100
|
|
|
83
101
|
this.mapResult =
|
|
84
102
|
map === undefined
|
|
@@ -102,8 +120,10 @@ export class LiveStoreGraphQLQuery<
|
|
|
102
120
|
|
|
103
121
|
if (typeof genVariableValues === 'function') {
|
|
104
122
|
variableValues$OrvariableValues = this.reactivityGraph.makeThunk(
|
|
105
|
-
(get, _setDebugInfo,
|
|
106
|
-
return genVariableValues(
|
|
123
|
+
(get, _setDebugInfo, ctx, otelContext) => {
|
|
124
|
+
return genVariableValues(
|
|
125
|
+
makeGetAtomResult(get, ctx, otelContext ?? ctx.rootOtelContext, this.dependencyQueriesRef),
|
|
126
|
+
)
|
|
107
127
|
},
|
|
108
128
|
{ label: `${labelWithDefault}:variableValues`, meta: { liveStoreThunkType: 'graphql.variables' } },
|
|
109
129
|
)
|
|
@@ -114,9 +134,10 @@ export class LiveStoreGraphQLQuery<
|
|
|
114
134
|
|
|
115
135
|
const resultsLabel = `${labelWithDefault}:results`
|
|
116
136
|
this.results$ = this.reactivityGraph.makeThunk<TResultMapped>(
|
|
117
|
-
(get, setDebugInfo,
|
|
137
|
+
(get, setDebugInfo, ctx, otelContext, debugRefreshReason) => {
|
|
138
|
+
const { store, otelTracer, rootOtelContext } = ctx
|
|
118
139
|
const variableValues = isThunk(variableValues$OrvariableValues)
|
|
119
|
-
? (get(variableValues$OrvariableValues) as TVariableValues)
|
|
140
|
+
? (get(variableValues$OrvariableValues, otelContext, debugRefreshReason) as TVariableValues)
|
|
120
141
|
: (variableValues$OrvariableValues as TVariableValues)
|
|
121
142
|
const { result, queriedTables, durationMs } = this.queryOnce({
|
|
122
143
|
document,
|
|
@@ -124,7 +145,7 @@ export class LiveStoreGraphQLQuery<
|
|
|
124
145
|
otelContext: otelContext ?? rootOtelContext,
|
|
125
146
|
otelTracer,
|
|
126
147
|
store: store as Store<TContext>,
|
|
127
|
-
get: makeGetAtomResult(get, otelContext ?? rootOtelContext),
|
|
148
|
+
get: makeGetAtomResult(get, ctx, otelContext ?? rootOtelContext, this.dependencyQueriesRef),
|
|
128
149
|
})
|
|
129
150
|
|
|
130
151
|
// Add dependencies on any tables that were used
|
|
@@ -215,5 +236,9 @@ export class LiveStoreGraphQLQuery<
|
|
|
215
236
|
}
|
|
216
237
|
|
|
217
238
|
this.reactivityGraph.destroyNode(this.results$)
|
|
239
|
+
|
|
240
|
+
for (const query of this.dependencyQueriesRef) {
|
|
241
|
+
query.deref()
|
|
242
|
+
}
|
|
218
243
|
}
|
|
219
244
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
2
|
+
|
|
3
|
+
import type * as RG from '../reactive.js'
|
|
4
|
+
import type { RefreshReason } from '../store/store-types.js'
|
|
5
|
+
import type { ILiveQueryRef, ILiveQueryRefDef, ReactivityGraph, ReactivityGraphContext } from './base-class.js'
|
|
6
|
+
import { withRCMap } from './base-class.js'
|
|
7
|
+
|
|
8
|
+
export const makeRef = <T>(
|
|
9
|
+
defaultValue: T,
|
|
10
|
+
options?: {
|
|
11
|
+
label?: string
|
|
12
|
+
},
|
|
13
|
+
): ILiveQueryRefDef<T> => {
|
|
14
|
+
const id = nanoid()
|
|
15
|
+
return {
|
|
16
|
+
_tag: 'live-ref-def',
|
|
17
|
+
defaultValue,
|
|
18
|
+
make: withRCMap(id, (ctx) => new LiveQueryRef(defaultValue, ctx.reactivityGraph.deref()!, options)),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class LiveQueryRef<T> implements ILiveQueryRef<T> {
|
|
23
|
+
_tag = 'live-ref' as const
|
|
24
|
+
readonly ref: RG.Ref<T, ReactivityGraphContext, RefreshReason>
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private defaultValue: T,
|
|
28
|
+
readonly reactivityGraph: ReactivityGraph,
|
|
29
|
+
private options?: {
|
|
30
|
+
label?: string
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
this.ref = reactivityGraph.makeRef(defaultValue, { label: options?.label })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set = (value: T) => {
|
|
37
|
+
this.reactivityGraph.setRef(this.ref, value)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get = () => {
|
|
41
|
+
return this.ref.computeResult()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
destroy = () => {
|
|
45
|
+
this.reactivityGraph.destroyNode(this.ref)
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/reactive.test.ts
CHANGED
|
@@ -245,7 +245,7 @@ describe('a trivial graph', () => {
|
|
|
245
245
|
expect(e.isDirty).toBe(true)
|
|
246
246
|
|
|
247
247
|
expect(() => c.computeResult()).toThrowErrorMatchingInlineSnapshot(
|
|
248
|
-
`[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-
|
|
248
|
+
`[Error: This should never happen: LiveStore Error: Attempted to compute destroyed ref (node-2): b]`,
|
|
249
249
|
)
|
|
250
250
|
})
|
|
251
251
|
})
|
package/src/reactive.ts
CHANGED
|
@@ -32,7 +32,11 @@ import type * as otel from '@opentelemetry/api'
|
|
|
32
32
|
export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
33
33
|
export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
34
34
|
|
|
35
|
-
export type GetAtom = <T>(
|
|
35
|
+
export type GetAtom = <T>(
|
|
36
|
+
atom: Atom<T, any, any>,
|
|
37
|
+
otelContext?: otel.Context | undefined,
|
|
38
|
+
debugRefreshReason?: TODO | undefined,
|
|
39
|
+
) => T
|
|
36
40
|
|
|
37
41
|
export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
38
42
|
_tag: 'ref'
|
|
@@ -42,7 +46,7 @@ export type Ref<T, TContext, TDebugRefreshReason extends DebugRefreshReason> = {
|
|
|
42
46
|
previousResult: T
|
|
43
47
|
computeResult: () => T
|
|
44
48
|
sub: Set<Atom<any, TContext, TDebugRefreshReason>> // always empty
|
|
45
|
-
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect
|
|
49
|
+
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
|
46
50
|
label?: string
|
|
47
51
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
48
52
|
meta?: any
|
|
@@ -58,7 +62,7 @@ export type Thunk<TResult, TContext, TDebugRefreshReason extends DebugRefreshRea
|
|
|
58
62
|
computeResult: (otelContext?: otel.Context, debugRefreshReason?: TDebugRefreshReason) => TResult
|
|
59
63
|
previousResult: TResult | NOT_REFRESHED_YET
|
|
60
64
|
sub: Set<Atom<any, TContext, TDebugRefreshReason>>
|
|
61
|
-
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect
|
|
65
|
+
super: Set<Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>>
|
|
62
66
|
label?: string
|
|
63
67
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
64
68
|
meta?: any
|
|
@@ -72,11 +76,11 @@ export type Atom<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
|
|
|
72
76
|
| Ref<T, TContext, TDebugRefreshReason>
|
|
73
77
|
| Thunk<T, TContext, TDebugRefreshReason>
|
|
74
78
|
|
|
75
|
-
export type Effect = {
|
|
79
|
+
export type Effect<TDebugRefreshReason extends DebugRefreshReason> = {
|
|
76
80
|
_tag: 'effect'
|
|
77
81
|
id: string
|
|
78
82
|
isDestroyed: boolean
|
|
79
|
-
doEffect: (otelContext?: otel.Context) => void
|
|
83
|
+
doEffect: (otelContext?: otel.Context | undefined, debugRefreshReason?: TDebugRefreshReason | undefined) => void
|
|
80
84
|
sub: Set<Atom<any, TODO, TODO>>
|
|
81
85
|
label?: string
|
|
82
86
|
invocations: number
|
|
@@ -84,7 +88,7 @@ export type Effect = {
|
|
|
84
88
|
|
|
85
89
|
export type Node<T, TContext, TDebugRefreshReason extends DebugRefreshReason> =
|
|
86
90
|
| Atom<T, TContext, TDebugRefreshReason>
|
|
87
|
-
| Effect
|
|
91
|
+
| Effect<TDebugRefreshReason>
|
|
88
92
|
|
|
89
93
|
export const isThunk = <T, TContext, TDebugRefreshReason extends DebugRefreshReason>(
|
|
90
94
|
obj: unknown,
|
|
@@ -166,7 +170,7 @@ export type SerializedThunk = Readonly<
|
|
|
166
170
|
|
|
167
171
|
export type SerializedEffect = Readonly<
|
|
168
172
|
PrettifyFlat<
|
|
169
|
-
Pick<Effect
|
|
173
|
+
Pick<Effect<any>, '_tag' | 'id' | 'label' | 'invocations' | 'isDestroyed'> & {
|
|
170
174
|
sub: ReadonlyArray<string>
|
|
171
175
|
}
|
|
172
176
|
>
|
|
@@ -179,14 +183,14 @@ export type ReactiveGraphSnapshot = {
|
|
|
179
183
|
readonly deferredEffects: ReadonlyArray<string>
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
let nodeIdCounter = 0
|
|
183
|
-
const uniqueNodeId = () => `node-${++nodeIdCounter}`
|
|
184
|
-
let refreshInfoIdCounter = 0
|
|
185
|
-
const uniqueRefreshInfoId = () => `refresh-info-${++refreshInfoIdCounter}`
|
|
186
|
-
|
|
187
186
|
let globalGraphIdCounter = 0
|
|
188
187
|
const uniqueGraphId = () => `graph-${++globalGraphIdCounter}`
|
|
189
188
|
|
|
189
|
+
/** Used for testing */
|
|
190
|
+
export const __resetIds = () => {
|
|
191
|
+
globalGraphIdCounter = 0
|
|
192
|
+
}
|
|
193
|
+
|
|
190
194
|
export class ReactiveGraph<
|
|
191
195
|
TDebugRefreshReason extends DebugRefreshReason,
|
|
192
196
|
TDebugThunkInfo extends DebugThunkInfo,
|
|
@@ -195,7 +199,7 @@ export class ReactiveGraph<
|
|
|
195
199
|
id = uniqueGraphId()
|
|
196
200
|
|
|
197
201
|
readonly atoms: Set<Atom<any, TContext, TDebugRefreshReason>> = new Set()
|
|
198
|
-
readonly effects: Set<Effect
|
|
202
|
+
readonly effects: Set<Effect<TDebugRefreshReason>> = new Set()
|
|
199
203
|
|
|
200
204
|
context: TContext | undefined
|
|
201
205
|
|
|
@@ -205,17 +209,22 @@ export class ReactiveGraph<
|
|
|
205
209
|
| { refreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[]; startMs: DOMHighResTimeStamp }
|
|
206
210
|
| undefined
|
|
207
211
|
|
|
208
|
-
private deferredEffects: Map<Effect
|
|
212
|
+
private deferredEffects: Map<Effect<TDebugRefreshReason>, Set<TDebugRefreshReason>> = new Map()
|
|
209
213
|
|
|
210
214
|
private refreshCallbacks: Set<() => void> = new Set()
|
|
211
215
|
|
|
216
|
+
private nodeIdCounter = 0
|
|
217
|
+
private uniqueNodeId = () => `node-${++this.nodeIdCounter}`
|
|
218
|
+
private refreshInfoIdCounter = 0
|
|
219
|
+
private uniqueRefreshInfoId = () => `refresh-info-${++this.refreshInfoIdCounter}`
|
|
220
|
+
|
|
212
221
|
makeRef<T>(
|
|
213
222
|
val: T,
|
|
214
223
|
options?: { label?: string; meta?: unknown; equal?: (a: T, b: T) => boolean },
|
|
215
224
|
): Ref<T, TContext, TDebugRefreshReason> {
|
|
216
225
|
const ref: Ref<T, TContext, TDebugRefreshReason> = {
|
|
217
226
|
_tag: 'ref',
|
|
218
|
-
id: uniqueNodeId(),
|
|
227
|
+
id: this.uniqueNodeId(),
|
|
219
228
|
isDirty: false,
|
|
220
229
|
isDestroyed: false,
|
|
221
230
|
previousResult: val,
|
|
@@ -239,6 +248,7 @@ export class ReactiveGraph<
|
|
|
239
248
|
setDebugInfo: (debugInfo: TDebugThunkInfo) => void,
|
|
240
249
|
ctx: TContext,
|
|
241
250
|
otelContext: otel.Context | undefined,
|
|
251
|
+
debugRefreshReason: TDebugRefreshReason | undefined,
|
|
242
252
|
) => T,
|
|
243
253
|
options?:
|
|
244
254
|
| {
|
|
@@ -250,7 +260,7 @@ export class ReactiveGraph<
|
|
|
250
260
|
): Thunk<T, TContext, TDebugRefreshReason> {
|
|
251
261
|
const thunk: Thunk<T, TContext, TDebugRefreshReason> = {
|
|
252
262
|
_tag: 'thunk',
|
|
253
|
-
id: uniqueNodeId(),
|
|
263
|
+
id: this.uniqueNodeId(),
|
|
254
264
|
previousResult: NOT_REFRESHED_YET,
|
|
255
265
|
isDirty: true,
|
|
256
266
|
isDestroyed: false,
|
|
@@ -266,7 +276,7 @@ export class ReactiveGraph<
|
|
|
266
276
|
|
|
267
277
|
const getAtom = (atom: Atom<T, TContext, TDebugRefreshReason>, otelContext: otel.Context) => {
|
|
268
278
|
this.addEdge(thunk, atom)
|
|
269
|
-
return compute(atom, otelContext)
|
|
279
|
+
return compute(atom, otelContext, debugRefreshReason)
|
|
270
280
|
}
|
|
271
281
|
|
|
272
282
|
let debugInfo: TDebugThunkInfo | undefined = undefined
|
|
@@ -279,6 +289,7 @@ export class ReactiveGraph<
|
|
|
279
289
|
setDebugInfo,
|
|
280
290
|
this.context ?? throwContextNotSetError(this),
|
|
281
291
|
otelContext,
|
|
292
|
+
debugRefreshReason,
|
|
282
293
|
)
|
|
283
294
|
|
|
284
295
|
const resultChanged = thunk.equal(thunk.previousResult as T, result) === false
|
|
@@ -301,7 +312,7 @@ export class ReactiveGraph<
|
|
|
301
312
|
this.currentDebugRefresh = undefined
|
|
302
313
|
|
|
303
314
|
this.debugRefreshInfos.push({
|
|
304
|
-
id: uniqueRefreshInfoId(),
|
|
315
|
+
id: this.uniqueRefreshInfoId(),
|
|
305
316
|
reason: debugRefreshReason ?? ({ _tag: 'makeThunk', label: options?.label } as TDebugRefreshReason),
|
|
306
317
|
skippedRefresh: false,
|
|
307
318
|
refreshedAtoms,
|
|
@@ -365,14 +376,18 @@ export class ReactiveGraph<
|
|
|
365
376
|
}
|
|
366
377
|
|
|
367
378
|
makeEffect(
|
|
368
|
-
doEffect: (
|
|
379
|
+
doEffect: (
|
|
380
|
+
get: GetAtom,
|
|
381
|
+
otelContext: otel.Context | undefined,
|
|
382
|
+
debugRefreshReason: DebugRefreshReason | undefined,
|
|
383
|
+
) => void,
|
|
369
384
|
options?: { label?: string } | undefined,
|
|
370
|
-
): Effect {
|
|
371
|
-
const effect: Effect = {
|
|
385
|
+
): Effect<TDebugRefreshReason> {
|
|
386
|
+
const effect: Effect<TDebugRefreshReason> = {
|
|
372
387
|
_tag: 'effect',
|
|
373
|
-
id: uniqueNodeId(),
|
|
388
|
+
id: this.uniqueNodeId(),
|
|
374
389
|
isDestroyed: false,
|
|
375
|
-
doEffect: (otelContext) => {
|
|
390
|
+
doEffect: (otelContext, debugRefreshReason) => {
|
|
376
391
|
effect.invocations++
|
|
377
392
|
|
|
378
393
|
// NOTE we're not tracking any debug refresh info for effects as they're tracked by the thunks they depend on
|
|
@@ -380,12 +395,16 @@ export class ReactiveGraph<
|
|
|
380
395
|
// Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
|
|
381
396
|
effect.sub = new Set()
|
|
382
397
|
|
|
383
|
-
const getAtom = (
|
|
398
|
+
const getAtom = (
|
|
399
|
+
atom: Atom<any, TContext, TDebugRefreshReason>,
|
|
400
|
+
otelContext: otel.Context,
|
|
401
|
+
debugRefreshReason: DebugRefreshReason | undefined,
|
|
402
|
+
) => {
|
|
384
403
|
this.addEdge(effect, atom)
|
|
385
|
-
return compute(atom, otelContext)
|
|
404
|
+
return compute(atom, otelContext, debugRefreshReason)
|
|
386
405
|
}
|
|
387
406
|
|
|
388
|
-
doEffect(getAtom as GetAtom, otelContext)
|
|
407
|
+
doEffect(getAtom as GetAtom, otelContext, debugRefreshReason)
|
|
389
408
|
},
|
|
390
409
|
sub: new Set(),
|
|
391
410
|
label: options?.label,
|
|
@@ -421,7 +440,7 @@ export class ReactiveGraph<
|
|
|
421
440
|
}
|
|
422
441
|
| undefined,
|
|
423
442
|
) {
|
|
424
|
-
const effectsToRefresh = new Set<Effect
|
|
443
|
+
const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
|
|
425
444
|
for (const [ref, val] of refs) {
|
|
426
445
|
ref.previousResult = val
|
|
427
446
|
ref.refreshes++
|
|
@@ -448,7 +467,7 @@ export class ReactiveGraph<
|
|
|
448
467
|
}
|
|
449
468
|
|
|
450
469
|
private runEffects = (
|
|
451
|
-
effectsToRefresh: Set<Effect
|
|
470
|
+
effectsToRefresh: Set<Effect<TDebugRefreshReason>>,
|
|
452
471
|
options: {
|
|
453
472
|
debugRefreshReason: TDebugRefreshReason
|
|
454
473
|
otelContext?: otel.Context
|
|
@@ -459,7 +478,7 @@ export class ReactiveGraph<
|
|
|
459
478
|
this.currentDebugRefresh = { refreshedAtoms: [], startMs: performance.now() }
|
|
460
479
|
|
|
461
480
|
for (const effect of effectsToRefresh) {
|
|
462
|
-
effect.doEffect(options?.otelContext)
|
|
481
|
+
effect.doEffect(options?.otelContext, options.debugRefreshReason)
|
|
463
482
|
}
|
|
464
483
|
|
|
465
484
|
const refreshedAtoms = this.currentDebugRefresh.refreshedAtoms
|
|
@@ -467,7 +486,7 @@ export class ReactiveGraph<
|
|
|
467
486
|
this.currentDebugRefresh = undefined
|
|
468
487
|
|
|
469
488
|
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
|
470
|
-
id: uniqueRefreshInfoId(),
|
|
489
|
+
id: this.uniqueRefreshInfoId(),
|
|
471
490
|
reason: options.debugRefreshReason,
|
|
472
491
|
skippedRefresh: false,
|
|
473
492
|
refreshedAtoms,
|
|
@@ -504,7 +523,7 @@ export class ReactiveGraph<
|
|
|
504
523
|
}
|
|
505
524
|
|
|
506
525
|
addEdge(
|
|
507
|
-
superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect
|
|
526
|
+
superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
|
|
508
527
|
subComp: Atom<any, TContext, TDebugRefreshReason>,
|
|
509
528
|
) {
|
|
510
529
|
superComp.sub.add(subComp)
|
|
@@ -516,11 +535,11 @@ export class ReactiveGraph<
|
|
|
516
535
|
}
|
|
517
536
|
|
|
518
537
|
removeEdge(
|
|
519
|
-
superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect
|
|
538
|
+
superComp: Thunk<any, TContext, TDebugRefreshReason> | Effect<TDebugRefreshReason>,
|
|
520
539
|
subComp: Atom<any, TContext, TDebugRefreshReason>,
|
|
521
540
|
) {
|
|
522
541
|
superComp.sub.delete(subComp)
|
|
523
|
-
const effectsToRefresh = new Set<Effect
|
|
542
|
+
const effectsToRefresh = new Set<Effect<TDebugRefreshReason>>()
|
|
524
543
|
markSuperCompDirtyRec(subComp, effectsToRefresh)
|
|
525
544
|
|
|
526
545
|
for (const effect of effectsToRefresh) {
|
|
@@ -563,7 +582,11 @@ export class ReactiveGraph<
|
|
|
563
582
|
}
|
|
564
583
|
}
|
|
565
584
|
|
|
566
|
-
const compute = <T>(
|
|
585
|
+
const compute = <T>(
|
|
586
|
+
atom: Atom<T, unknown, any>,
|
|
587
|
+
otelContext: otel.Context,
|
|
588
|
+
debugRefreshReason: DebugRefreshReason | undefined,
|
|
589
|
+
): T => {
|
|
567
590
|
// const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
|
|
568
591
|
if (atom.isDestroyed) {
|
|
569
592
|
shouldNeverHappen(`LiveStore Error: Attempted to compute destroyed ${atom._tag} (${atom.id}): ${atom.label ?? ''}`)
|
|
@@ -571,7 +594,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
|
|
|
571
594
|
|
|
572
595
|
if (atom.isDirty) {
|
|
573
596
|
// console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
574
|
-
const result = atom.computeResult(otelContext)
|
|
597
|
+
const result = atom.computeResult(otelContext, debugRefreshReason)
|
|
575
598
|
atom.isDirty = false
|
|
576
599
|
atom.previousResult = result
|
|
577
600
|
return result
|
|
@@ -581,7 +604,7 @@ const compute = <T>(atom: Atom<T, unknown, any>, otelContext: otel.Context): T =
|
|
|
581
604
|
}
|
|
582
605
|
}
|
|
583
606
|
|
|
584
|
-
const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect
|
|
607
|
+
const markSuperCompDirtyRec = <T>(atom: Atom<T, unknown, any>, effectsToRefresh: Set<Effect<any>>) => {
|
|
585
608
|
for (const superComp of atom.super) {
|
|
586
609
|
if (superComp._tag === 'thunk') {
|
|
587
610
|
superComp.isDirty = true
|
|
@@ -644,7 +667,7 @@ const serializeAtom = (atom: Atom<any, unknown, any>, includeResult: boolean): S
|
|
|
644
667
|
}
|
|
645
668
|
|
|
646
669
|
// NOTE This function is performance-optimized (i.e. not using `pick` and `Array.from`)
|
|
647
|
-
const serializeEffect = (effect: Effect): SerializedEffect => {
|
|
670
|
+
const serializeEffect = (effect: Effect<any>): SerializedEffect => {
|
|
648
671
|
const sub: string[] = []
|
|
649
672
|
for (const a of effect.sub) {
|
|
650
673
|
sub.push(a.id)
|
package/src/row-query-utils.ts
CHANGED
|
@@ -4,28 +4,33 @@ import { DbSchema } from '@livestore/common/schema'
|
|
|
4
4
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
5
5
|
import type * as otel from '@opentelemetry/api'
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { GetResult, LiveQueryDef, ReactivityGraphContext } from './live-queries/base-class.js'
|
|
8
8
|
import { computed } from './live-queries/computed.js'
|
|
9
9
|
|
|
10
|
-
export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | undefined) =>
|
|
10
|
+
export const rowQueryLabel = (table: DbSchema.TableDefBase, id: string | SessionIdSymbol | number | undefined) =>
|
|
11
11
|
`row:${table.sqliteDef.name}${id === undefined ? '' : id === SessionIdSymbol ? `:sessionId` : `:${id}`}`
|
|
12
12
|
|
|
13
13
|
export const deriveColQuery: {
|
|
14
|
-
<
|
|
15
|
-
|
|
14
|
+
<TQueryDef extends LiveQueryDef<any, QueryInfo.None>, TCol extends keyof GetResult<TQueryDef> & string>(
|
|
15
|
+
queryDef: TQueryDef,
|
|
16
16
|
colName: TCol,
|
|
17
|
-
):
|
|
18
|
-
<
|
|
19
|
-
|
|
17
|
+
): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.None>
|
|
18
|
+
<TQueryDef extends LiveQueryDef<any, QueryInfo.Row>, TCol extends keyof GetResult<TQueryDef> & string>(
|
|
19
|
+
queryDef: TQueryDef,
|
|
20
20
|
colName: TCol,
|
|
21
|
-
):
|
|
22
|
-
} = (
|
|
23
|
-
return computed((get) => get(
|
|
24
|
-
label: `deriveColQuery:${
|
|
21
|
+
): LiveQueryDef<GetResult<TQueryDef>[TCol], QueryInfo.Col>
|
|
22
|
+
} = (queryDef: LiveQueryDef<any, QueryInfo.Row | QueryInfo.Col>, colName: string) => {
|
|
23
|
+
return computed((get) => get(queryDef)[colName], {
|
|
24
|
+
label: `deriveColQuery:${queryDef.label}:${colName}`,
|
|
25
25
|
queryInfo:
|
|
26
|
-
|
|
27
|
-
? { _tag: 'Col', table:
|
|
26
|
+
queryDef.queryInfo._tag === 'Row'
|
|
27
|
+
? { _tag: 'Col', table: queryDef.queryInfo.table, column: colName, id: queryDef.queryInfo.id }
|
|
28
28
|
: undefined,
|
|
29
|
+
deps: [
|
|
30
|
+
queryDef.queryInfo.table.sqliteDef.name,
|
|
31
|
+
queryDef.queryInfo.id === SessionIdSymbol ? 'sessionId' : queryDef.queryInfo.id,
|
|
32
|
+
queryDef.queryInfo._tag === 'Col' ? queryDef.queryInfo.column : undefined,
|
|
33
|
+
],
|
|
29
34
|
}) as any
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -36,20 +41,22 @@ export const makeExecBeforeFirstRun =
|
|
|
36
41
|
table,
|
|
37
42
|
otelContext: otelContext_,
|
|
38
43
|
}: {
|
|
39
|
-
id?: string | SessionIdSymbol
|
|
44
|
+
id?: string | SessionIdSymbol | number
|
|
40
45
|
insertValues?: any
|
|
41
46
|
table: DbSchema.TableDefBase
|
|
42
47
|
otelContext: otel.Context | undefined
|
|
43
48
|
}) =>
|
|
44
|
-
({ store }:
|
|
49
|
+
({ store }: ReactivityGraphContext) => {
|
|
45
50
|
const otelContext = otelContext_ ?? store.otel.queriesSpanContext
|
|
46
51
|
|
|
47
52
|
if (table.options.isSingleton === false) {
|
|
48
|
-
const
|
|
53
|
+
const idVal = id === SessionIdSymbol ? store.sessionId : id!
|
|
49
54
|
const rowExists =
|
|
50
|
-
store.
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
store.sqliteDbWrapper.select(
|
|
56
|
+
`SELECT 1 FROM '${table.sqliteDef.name}' WHERE id = ?`,
|
|
57
|
+
[idVal] as any as PreparedBindValues,
|
|
58
|
+
{ otelContext },
|
|
59
|
+
).length === 1
|
|
53
60
|
|
|
54
61
|
if (rowExists) return
|
|
55
62
|
|
|
@@ -59,7 +66,11 @@ export const makeExecBeforeFirstRun =
|
|
|
59
66
|
)
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
//
|
|
63
|
-
|
|
69
|
+
// It's important that we only mutate and don't refresh here, as this function might be called during a render
|
|
70
|
+
// and otherwise we might end up in a "reactive loop"
|
|
71
|
+
store.mutate(
|
|
72
|
+
{ otelContext, skipRefresh: true, label: `rowQuery:${table.sqliteDef.name}:${idVal}` },
|
|
73
|
+
table.insert({ id, ...insertValues }),
|
|
74
|
+
)
|
|
64
75
|
}
|
|
65
76
|
}
|