@livestore/livestore 0.0.16 → 0.0.21
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 +18 -21
- package/dist/.tsbuildinfo +1 -1
- package/dist/QueryCache.d.ts +1 -1
- package/dist/QueryCache.d.ts.map +1 -1
- package/dist/QueryCache.js.map +1 -1
- package/dist/__tests__/react/fixture.d.ts +5 -4
- package/dist/__tests__/react/fixture.d.ts.map +1 -1
- package/dist/__tests__/react/fixture.js +5 -5
- package/dist/__tests__/react/fixture.js.map +1 -1
- package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
- package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
- package/dist/__tests__/react/useComponentState.test.js +68 -0
- package/dist/__tests__/react/useComponentState.test.js.map +1 -0
- package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useLQuery.test.js +38 -0
- package/dist/__tests__/react/useLQuery.test.js.map +1 -0
- package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
- package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
- package/dist/__tests__/react/useQuery.test.d.ts +2 -0
- package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
- package/dist/__tests__/react/useQuery.test.js +33 -0
- package/dist/__tests__/react/useQuery.test.js.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
- package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
- package/dist/__tests__/reactive.test.js +167 -93
- package/dist/__tests__/reactive.test.js.map +1 -1
- package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
- package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
- package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
- package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
- package/dist/inMemoryDatabase.d.ts +2 -2
- package/dist/inMemoryDatabase.d.ts.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/useComponentState.d.ts +50 -0
- package/dist/react/useComponentState.d.ts.map +1 -0
- package/dist/react/useComponentState.js +248 -0
- package/dist/react/useComponentState.js.map +1 -0
- package/dist/react/useGlobalQuery.d.ts +3 -0
- package/dist/react/useGlobalQuery.d.ts.map +1 -0
- package/dist/react/useGlobalQuery.js +26 -0
- package/dist/react/useGlobalQuery.js.map +1 -0
- package/dist/react/useGraphQL.d.ts +3 -3
- package/dist/react/useGraphQL.d.ts.map +1 -1
- package/dist/react/useGraphQL.js +10 -8
- package/dist/react/useGraphQL.js.map +1 -1
- package/dist/react/useLiveStoreComponent.d.ts +6 -6
- package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
- package/dist/react/useLiveStoreComponent.js +143 -99
- package/dist/react/useLiveStoreComponent.js.map +1 -1
- package/dist/react/useQuery.d.ts +2 -2
- package/dist/react/useQuery.d.ts.map +1 -1
- package/dist/react/useQuery.js +26 -22
- package/dist/react/useQuery.js.map +1 -1
- package/dist/react/useTemporaryQuery.d.ts +8 -0
- package/dist/react/useTemporaryQuery.d.ts.map +1 -0
- package/dist/react/useTemporaryQuery.js +17 -0
- package/dist/react/useTemporaryQuery.js.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
- package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
- package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
- package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
- package/dist/reactive.d.ts +42 -48
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +293 -186
- package/dist/reactive.js.map +1 -1
- package/dist/reactiveQueries/base-class.d.ts +28 -23
- package/dist/reactiveQueries/base-class.d.ts.map +1 -1
- package/dist/reactiveQueries/base-class.js +25 -18
- package/dist/reactiveQueries/base-class.js.map +1 -1
- package/dist/reactiveQueries/graph.d.ts +10 -0
- package/dist/reactiveQueries/graph.d.ts.map +1 -0
- package/dist/reactiveQueries/graph.js +6 -0
- package/dist/reactiveQueries/graph.js.map +1 -0
- package/dist/reactiveQueries/graphql.d.ts +34 -17
- package/dist/reactiveQueries/graphql.d.ts.map +1 -1
- package/dist/reactiveQueries/graphql.js +91 -10
- package/dist/reactiveQueries/graphql.js.map +1 -1
- package/dist/reactiveQueries/js.d.ts +16 -12
- package/dist/reactiveQueries/js.d.ts.map +1 -1
- package/dist/reactiveQueries/js.js +31 -8
- package/dist/reactiveQueries/js.js.map +1 -1
- package/dist/reactiveQueries/sql.d.ts +22 -18
- package/dist/reactiveQueries/sql.d.ts.map +1 -1
- package/dist/reactiveQueries/sql.js +82 -16
- package/dist/reactiveQueries/sql.js.map +1 -1
- package/dist/store.d.ts +12 -52
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +283 -264
- package/dist/store.js.map +1 -1
- package/package.json +10 -9
- package/src/QueryCache.ts +1 -1
- package/src/__tests__/react/fixture.tsx +12 -7
- package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
- package/src/__tests__/react/useQuery.test.tsx +48 -0
- package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
- package/src/__tests__/reactive.test.ts +193 -140
- package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
- package/src/inMemoryDatabase.ts +2 -2
- package/src/index.ts +7 -11
- package/src/react/index.ts +3 -7
- package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +89 -247
- package/src/react/useQuery.ts +29 -27
- package/src/react/useTemporaryQuery.ts +21 -0
- package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
- package/src/reactive.ts +385 -268
- package/src/reactiveQueries/base-class.ts +60 -44
- package/src/reactiveQueries/graph.ts +15 -0
- package/src/reactiveQueries/graphql.ts +145 -29
- package/src/reactiveQueries/js.ts +53 -20
- package/src/reactiveQueries/sql.ts +129 -36
- package/src/store.ts +338 -408
- package/src/react/useGraphQL.ts +0 -138
package/src/reactive.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// Effect: a side effect that runs when a value changes; return value is ignored
|
|
7
7
|
// Atom: a node returning a value that can be depended on: Ref | Thunk
|
|
8
8
|
|
|
9
|
-
// Super computation: Nodes that depend on a given node
|
|
10
|
-
// Sub computation: Nodes that a given node depends on
|
|
9
|
+
// Super computation: Nodes that depend on a given node ("downstream")
|
|
10
|
+
// Sub computation: Nodes that a given node depends on ("upstream")
|
|
11
11
|
|
|
12
12
|
// This vocabulary comes from the MiniAdapton paper linked below, although
|
|
13
13
|
// we don't actually implement the MiniAdapton algorithm because we don't need lazy recomputation.
|
|
@@ -24,68 +24,69 @@
|
|
|
24
24
|
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
25
25
|
|
|
26
26
|
import type { PrettifyFlat } from '@livestore/utils'
|
|
27
|
-
import { pick } from '@livestore/utils'
|
|
27
|
+
import { pick, shouldNeverHappen } from '@livestore/utils'
|
|
28
28
|
import type * as otel from '@opentelemetry/api'
|
|
29
29
|
import { isEqual, max, uniqueId } from 'lodash-es'
|
|
30
30
|
|
|
31
31
|
import { BoundArray } from './bounded-collections.js'
|
|
32
|
+
// import { getDurationMsFromSpan } from './otel.js'
|
|
32
33
|
|
|
33
|
-
const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
34
|
-
type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
34
|
+
export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
|
|
35
|
+
export type NOT_REFRESHED_YET = typeof NOT_REFRESHED_YET
|
|
35
36
|
|
|
36
|
-
export type GetAtom = <T>(atom: Atom<T
|
|
37
|
+
export type GetAtom = <T>(atom: Atom<T, any>, otelContext?: otel.Context) => T
|
|
37
38
|
|
|
38
39
|
export type Ref<T> = {
|
|
39
40
|
_tag: 'ref'
|
|
40
41
|
id: string
|
|
41
|
-
|
|
42
|
+
isDirty: false
|
|
43
|
+
previousResult: T
|
|
42
44
|
height: 0
|
|
43
|
-
|
|
44
|
-
sub: Set<Atom<any>> // always empty
|
|
45
|
-
super: Set<Atom<any> | Effect>
|
|
45
|
+
computeResult: () => T
|
|
46
|
+
sub: Set<Atom<any, TODO>> // always empty
|
|
47
|
+
super: Set<Atom<any, TODO> | Effect>
|
|
46
48
|
label?: string
|
|
47
49
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
48
50
|
meta?: any
|
|
49
51
|
equal: (a: T, b: T) => boolean
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
type BaseThunk<
|
|
54
|
+
type BaseThunk<TResult, TContext> = {
|
|
53
55
|
_tag: 'thunk'
|
|
54
56
|
id: string
|
|
57
|
+
isDirty: boolean
|
|
55
58
|
height: number
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
computeResult: (otelContext?: otel.Context) => TResult
|
|
60
|
+
previousResult: TResult | NOT_REFRESHED_YET
|
|
61
|
+
sub: Set<Atom<any, TContext>>
|
|
62
|
+
super: Set<Atom<any, TContext> | Effect>
|
|
59
63
|
label?: string
|
|
60
64
|
/** Container for meta information (e.g. the LiveStore Store) */
|
|
61
65
|
meta?: any
|
|
62
|
-
equal: (a:
|
|
66
|
+
equal: (a: TResult, b: TResult) => boolean
|
|
67
|
+
recomputations: number
|
|
68
|
+
|
|
69
|
+
__getResult: any
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
type UnevaluatedThunk<T> = BaseThunk<T>
|
|
66
|
-
|
|
72
|
+
type UnevaluatedThunk<T, TContext> = BaseThunk<T, TContext>
|
|
73
|
+
// & { result: NOT_REFRESHED_YET }
|
|
74
|
+
export type Thunk<T, TContext> = BaseThunk<T, TContext>
|
|
75
|
+
// & { result: T }
|
|
67
76
|
|
|
68
|
-
export type Atom<T> = Ref<T> | Thunk<T>
|
|
77
|
+
export type Atom<T, TContext> = Ref<T> | Thunk<T, TContext>
|
|
69
78
|
|
|
70
79
|
export type Effect = {
|
|
71
80
|
_tag: 'effect'
|
|
72
81
|
id: string
|
|
73
|
-
doEffect: (
|
|
74
|
-
sub: Set<Atom<any>>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
class DependencyNotReadyError extends Error {
|
|
78
|
-
constructor(message: string) {
|
|
79
|
-
super(message)
|
|
80
|
-
this.name = 'DependencyNotReadyError'
|
|
81
|
-
}
|
|
82
|
+
doEffect: (otelContext?: otel.Context) => void
|
|
83
|
+
sub: Set<Atom<any, TODO>>
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
export type Taggable<T extends string = string> = { _tag: T }
|
|
85
87
|
|
|
86
88
|
export type ReactiveGraphOptions = {
|
|
87
89
|
effectsWrapper?: (runEffects: () => void) => void
|
|
88
|
-
otelTracer: otel.Tracer
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
export type AtomDebugInfo<TDebugThunkInfo extends Taggable> = {
|
|
@@ -120,13 +121,13 @@ export type RefreshReasonWithGenericReasons<T extends Taggable> =
|
|
|
120
121
|
| { _tag: 'unknown' }
|
|
121
122
|
|
|
122
123
|
export const unknownRefreshReason = () => {
|
|
123
|
-
debugger
|
|
124
|
+
// debugger
|
|
124
125
|
return { _tag: 'unknown' as const }
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
export type SerializedAtom = Readonly<
|
|
128
129
|
PrettifyFlat<
|
|
129
|
-
Pick<Atom<unknown>, '_tag' | 'height' | 'id' | 'label' | 'meta'
|
|
130
|
+
Pick<Atom<unknown, TODO>, '_tag' | 'height' | 'id' | 'label' | 'meta'> & {
|
|
130
131
|
sub: string[]
|
|
131
132
|
super: string[]
|
|
132
133
|
}
|
|
@@ -137,45 +138,44 @@ export type SerializedEffect = Readonly<PrettifyFlat<Pick<Effect, '_tag' | 'id'>
|
|
|
137
138
|
|
|
138
139
|
type ReactiveGraphSnapshot = {
|
|
139
140
|
readonly atoms: SerializedAtom[]
|
|
140
|
-
readonly effects: SerializedEffect[]
|
|
141
|
+
// readonly effects: SerializedEffect[]
|
|
141
142
|
/** IDs of atoms and effects that are dirty */
|
|
142
|
-
readonly dirtyNodes: string[]
|
|
143
|
+
// readonly dirtyNodes: string[]
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
const uniqueNodeId = () => uniqueId('node-')
|
|
146
147
|
const uniqueRefreshInfoId = () => uniqueId('refresh-info-')
|
|
147
148
|
|
|
148
|
-
const serializeAtom = (atom: Atom<any>): SerializedAtom => ({
|
|
149
|
-
...pick(atom, ['_tag', 'height', 'id', 'label', 'meta'
|
|
149
|
+
const serializeAtom = (atom: Atom<any, TODO>): SerializedAtom => ({
|
|
150
|
+
...pick(atom, ['_tag', 'height', 'id', 'label', 'meta']),
|
|
150
151
|
sub: Array.from(atom.sub).map((a) => a.id),
|
|
151
152
|
super: Array.from(atom.super).map((a) => a.id),
|
|
152
153
|
})
|
|
153
154
|
|
|
154
|
-
const serializeEffect = (effect: Effect): SerializedEffect => pick(effect, ['_tag', 'id'])
|
|
155
|
+
// const serializeEffect = (effect: Effect): SerializedEffect => pick(effect, ['_tag', 'id'])
|
|
155
156
|
|
|
156
|
-
export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo extends Taggable> {
|
|
157
|
-
|
|
158
|
-
private effects: Set<Effect> = new Set()
|
|
159
|
-
private otelTracer: otel.Tracer
|
|
160
|
-
readonly dirtyNodes: Set<Atom<any> | Effect> = new Set()
|
|
157
|
+
export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo extends Taggable, TContext = {}> {
|
|
158
|
+
readonly atoms: Set<Atom<any, TContext>> = new Set()
|
|
161
159
|
effectsWrapper: (runEffects: () => void) => void
|
|
162
160
|
|
|
161
|
+
context: TContext | undefined
|
|
162
|
+
|
|
163
163
|
debugRefreshInfos: BoundArray<
|
|
164
164
|
RefreshDebugInfo<RefreshReasonWithGenericReasons<TDebugRefreshReason>, TDebugThunkInfo>
|
|
165
165
|
> = new BoundArray(5000)
|
|
166
166
|
|
|
167
167
|
constructor(options: ReactiveGraphOptions) {
|
|
168
168
|
this.effectsWrapper = options?.effectsWrapper ?? ((runEffects: () => void) => runEffects())
|
|
169
|
-
this.otelTracer = options.otelTracer
|
|
170
169
|
}
|
|
171
170
|
|
|
172
171
|
makeRef<T>(val: T, options?: { label?: string; meta?: unknown; equal?: (a: T, b: T) => boolean }): Ref<T> {
|
|
173
172
|
const ref: Ref<T> = {
|
|
174
173
|
_tag: 'ref',
|
|
175
174
|
id: uniqueNodeId(),
|
|
176
|
-
|
|
175
|
+
isDirty: false,
|
|
176
|
+
previousResult: val,
|
|
177
177
|
height: 0,
|
|
178
|
-
|
|
178
|
+
computeResult: () => ref.previousResult,
|
|
179
179
|
sub: new Set(),
|
|
180
180
|
super: new Set(),
|
|
181
181
|
label: options?.label,
|
|
@@ -189,8 +189,13 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
makeThunk<T>(
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
getResult_: (
|
|
193
|
+
get: GetAtom,
|
|
194
|
+
addDebugInfo: (debugInfo: TDebugThunkInfo) => void,
|
|
195
|
+
ctx: TContext,
|
|
196
|
+
otelContext: otel.Context | undefined,
|
|
197
|
+
) => T,
|
|
198
|
+
options?:
|
|
194
199
|
| {
|
|
195
200
|
label?: string
|
|
196
201
|
meta?: any
|
|
@@ -199,36 +204,98 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
199
204
|
debugRefreshReason?: RefreshReasonWithGenericReasons<TDebugRefreshReason>
|
|
200
205
|
}
|
|
201
206
|
| undefined,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
207
|
+
): Thunk<T, TContext> {
|
|
208
|
+
// const computeResult = (): T => {
|
|
209
|
+
// const getAtom = (atom: Atom<T, any>): T => {
|
|
210
|
+
// const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
|
|
211
|
+
// if (atom.isDirty) {
|
|
212
|
+
// console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
213
|
+
// const result = atom.computeResult()
|
|
214
|
+
// atom.isDirty = false
|
|
215
|
+
// atom.previousResult = result
|
|
216
|
+
// return result
|
|
217
|
+
// } else {
|
|
218
|
+
// console.log('atom is clean', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
219
|
+
// return atom.previousResult as T
|
|
220
|
+
// }
|
|
221
|
+
// }
|
|
222
|
+
|
|
223
|
+
// let resultChanged = false
|
|
224
|
+
// const debugInfoForAtom = {
|
|
225
|
+
// atom: serializeAtom(null as TODO),
|
|
226
|
+
// resultChanged,
|
|
227
|
+
// // debugInfo: unknownRefreshReason() as TDebugThunkInfo,
|
|
228
|
+
// debugInfo: { _tag: 'unknown' } as TDebugThunkInfo,
|
|
229
|
+
// durationMs: 0,
|
|
230
|
+
// } satisfies AtomDebugInfo<TDebugThunkInfo>
|
|
231
|
+
|
|
232
|
+
const addDebugInfo = (_debugInfo: TDebugThunkInfo) => {
|
|
233
|
+
// debugInfoForAtom.debugInfo = debugInfo
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// debugInfoForRefreshedAtoms.push(debugInfoForAtom)
|
|
237
|
+
|
|
238
|
+
// return getResult_(getAtom as GetAtom, addDebugInfo, this.context!)
|
|
239
|
+
// }
|
|
240
|
+
|
|
241
|
+
const thunk: UnevaluatedThunk<T, TContext> = {
|
|
205
242
|
_tag: 'thunk',
|
|
206
243
|
id: uniqueNodeId(),
|
|
207
|
-
|
|
244
|
+
previousResult: NOT_REFRESHED_YET,
|
|
245
|
+
isDirty: true,
|
|
208
246
|
height: 0,
|
|
209
|
-
|
|
247
|
+
computeResult: (otelContext) => {
|
|
248
|
+
if (thunk.isDirty) {
|
|
249
|
+
// Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
|
|
250
|
+
thunk.sub = new Set()
|
|
251
|
+
|
|
252
|
+
const compute_ = (atom: Atom<T, unknown>, otelContext: otel.Context) => {
|
|
253
|
+
this.addEdge(thunk, atom)
|
|
254
|
+
return compute(atom, otelContext)
|
|
255
|
+
}
|
|
256
|
+
const result = getResult_(
|
|
257
|
+
compute_ as GetAtom,
|
|
258
|
+
addDebugInfo,
|
|
259
|
+
this.context ?? shouldNeverHappen('No store context set yet'),
|
|
260
|
+
otelContext,
|
|
261
|
+
)
|
|
262
|
+
thunk.isDirty = false
|
|
263
|
+
thunk.previousResult = result
|
|
264
|
+
thunk.recomputations++
|
|
265
|
+
return result
|
|
266
|
+
} else {
|
|
267
|
+
return thunk.previousResult as T
|
|
268
|
+
}
|
|
269
|
+
},
|
|
210
270
|
sub: new Set(),
|
|
211
271
|
super: new Set(),
|
|
272
|
+
recomputations: 0,
|
|
212
273
|
label: options?.label,
|
|
213
274
|
meta: options?.meta,
|
|
214
275
|
equal: options?.equal ?? isEqual,
|
|
276
|
+
__getResult: getResult_,
|
|
215
277
|
}
|
|
216
278
|
|
|
217
279
|
this.atoms.add(thunk)
|
|
218
|
-
this.dirtyNodes.add(thunk)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
280
|
+
// this.dirtyNodes.add(thunk)
|
|
281
|
+
|
|
282
|
+
const debugRefreshReason = options?.debugRefreshReason ?? { _tag: 'makeThunk', label: options?.label }
|
|
283
|
+
|
|
284
|
+
const refreshDebugInfo = {
|
|
285
|
+
id: uniqueRefreshInfoId(),
|
|
286
|
+
reason: debugRefreshReason,
|
|
287
|
+
skippedRefresh: true,
|
|
288
|
+
refreshedAtoms: [],
|
|
289
|
+
durationMs: 0,
|
|
290
|
+
completedTimestamp: Date.now(),
|
|
291
|
+
graphSnapshot: this.getSnapshot(),
|
|
292
|
+
}
|
|
293
|
+
this.debugRefreshInfos.push(refreshDebugInfo)
|
|
226
294
|
|
|
227
|
-
|
|
228
|
-
return thunk as unknown as Thunk<T>
|
|
295
|
+
return thunk as unknown as Thunk<T, TContext>
|
|
229
296
|
}
|
|
230
297
|
|
|
231
|
-
destroy(node: Atom<any> | Effect) {
|
|
298
|
+
destroy(node: Atom<any, TContext> | Effect) {
|
|
232
299
|
// Recursively destroy any supercomputations
|
|
233
300
|
if (node._tag === 'ref' || node._tag === 'thunk') {
|
|
234
301
|
for (const superComp of node.super) {
|
|
@@ -241,31 +308,51 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
241
308
|
this.removeEdge(node, subComp)
|
|
242
309
|
}
|
|
243
310
|
|
|
244
|
-
if (node._tag
|
|
245
|
-
this.effects.delete(node)
|
|
246
|
-
} else {
|
|
311
|
+
if (node._tag !== 'effect') {
|
|
247
312
|
this.atoms.delete(node)
|
|
248
313
|
}
|
|
249
314
|
}
|
|
250
315
|
|
|
251
316
|
makeEffect(
|
|
252
|
-
doEffect: (get: GetAtom) => void,
|
|
253
|
-
options
|
|
254
|
-
|
|
317
|
+
doEffect: (get: GetAtom, otelContext?: otel.Context) => void,
|
|
318
|
+
options?:
|
|
319
|
+
| {
|
|
320
|
+
label?: string
|
|
321
|
+
debugRefreshReason?: RefreshReasonWithGenericReasons<TDebugRefreshReason>
|
|
322
|
+
}
|
|
323
|
+
| undefined,
|
|
255
324
|
): Effect {
|
|
256
325
|
const effect: Effect = {
|
|
257
326
|
_tag: 'effect',
|
|
258
327
|
id: uniqueNodeId(),
|
|
259
|
-
doEffect
|
|
328
|
+
doEffect: (otelContext) => {
|
|
329
|
+
// Reset previous subcomputations as we're about to re-add them as part of the `doEffect` call below
|
|
330
|
+
effect.sub = new Set()
|
|
331
|
+
|
|
332
|
+
const getAtom = (atom: Atom<any, unknown>, otelContext: otel.Context) => {
|
|
333
|
+
this.addEdge(effect, atom)
|
|
334
|
+
return compute(atom, otelContext)
|
|
335
|
+
}
|
|
336
|
+
doEffect(getAtom as GetAtom, otelContext)
|
|
337
|
+
},
|
|
260
338
|
sub: new Set(),
|
|
261
339
|
}
|
|
262
340
|
|
|
263
|
-
this.effects.add(effect)
|
|
264
|
-
this.dirtyNodes.add(effect)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
341
|
+
// this.effects.add(effect)
|
|
342
|
+
// this.dirtyNodes.add(effect)
|
|
343
|
+
|
|
344
|
+
const debugRefreshReason = options?.debugRefreshReason ?? { _tag: 'makeEffect', label: options?.label }
|
|
345
|
+
|
|
346
|
+
const refreshDebugInfo = {
|
|
347
|
+
id: uniqueRefreshInfoId(),
|
|
348
|
+
reason: debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
|
349
|
+
skippedRefresh: true,
|
|
350
|
+
refreshedAtoms: [],
|
|
351
|
+
durationMs: 0,
|
|
352
|
+
completedTimestamp: Date.now(),
|
|
353
|
+
graphSnapshot: this.getSnapshot(),
|
|
354
|
+
}
|
|
355
|
+
this.debugRefreshInfos.push(refreshDebugInfo)
|
|
269
356
|
|
|
270
357
|
return effect
|
|
271
358
|
}
|
|
@@ -273,88 +360,88 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
273
360
|
setRef<T>(
|
|
274
361
|
ref: Ref<T>,
|
|
275
362
|
val: T,
|
|
276
|
-
options
|
|
363
|
+
options?:
|
|
277
364
|
| {
|
|
278
|
-
otelHint?: string
|
|
279
|
-
skipRefresh?: boolean
|
|
280
365
|
debugRefreshReason?: TDebugRefreshReason
|
|
366
|
+
otelContext?: otel.Context
|
|
281
367
|
}
|
|
282
368
|
| undefined,
|
|
283
|
-
otelContext: otel.Context,
|
|
284
369
|
) {
|
|
285
|
-
const {
|
|
286
|
-
ref.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
refreshedAtoms: [],
|
|
295
|
-
durationMs: 0,
|
|
296
|
-
completedTimestamp: Date.now(),
|
|
297
|
-
graphSnapshot: this.getSnapshot(),
|
|
370
|
+
const { debugRefreshReason } = options ?? {}
|
|
371
|
+
ref.previousResult = val
|
|
372
|
+
|
|
373
|
+
const effectsToRefresh = new Set<Effect>()
|
|
374
|
+
markSuperCompDirtyRec(ref, effectsToRefresh)
|
|
375
|
+
|
|
376
|
+
this.effectsWrapper(() => {
|
|
377
|
+
for (const effect of effectsToRefresh) {
|
|
378
|
+
effect.doEffect(options?.otelContext)
|
|
298
379
|
}
|
|
299
|
-
|
|
300
|
-
return
|
|
301
|
-
}
|
|
380
|
+
})
|
|
302
381
|
|
|
303
|
-
|
|
382
|
+
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
|
383
|
+
id: uniqueRefreshInfoId(),
|
|
384
|
+
reason: debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
|
385
|
+
skippedRefresh: true,
|
|
386
|
+
refreshedAtoms: [],
|
|
387
|
+
durationMs: 0,
|
|
388
|
+
completedTimestamp: Date.now(),
|
|
389
|
+
graphSnapshot: this.getSnapshot(),
|
|
390
|
+
}
|
|
391
|
+
this.debugRefreshInfos.push(refreshDebugInfo)
|
|
304
392
|
}
|
|
305
393
|
|
|
306
394
|
setRefs<T>(
|
|
307
395
|
refs: [Ref<T>, T][],
|
|
308
|
-
options
|
|
396
|
+
options?:
|
|
309
397
|
| {
|
|
310
|
-
otelHint?: string
|
|
311
|
-
skipRefresh?: boolean
|
|
312
398
|
debugRefreshReason?: TDebugRefreshReason
|
|
399
|
+
otelContext?: otel.Context
|
|
313
400
|
}
|
|
314
401
|
| undefined,
|
|
315
|
-
otelContext: otel.Context,
|
|
316
402
|
) {
|
|
317
|
-
const otelHint = options?.otelHint ?? ''
|
|
318
|
-
const skipRefresh = options?.skipRefresh ?? false
|
|
319
403
|
const debugRefreshReason = options?.debugRefreshReason
|
|
404
|
+
const effectsToRefresh = new Set<Effect>()
|
|
320
405
|
for (const [ref, val] of refs) {
|
|
321
|
-
ref.
|
|
322
|
-
|
|
406
|
+
ref.previousResult = val
|
|
407
|
+
|
|
408
|
+
markSuperCompDirtyRec(ref, effectsToRefresh)
|
|
323
409
|
}
|
|
324
410
|
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
reason: debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
|
329
|
-
skippedRefresh: true,
|
|
330
|
-
refreshedAtoms: [],
|
|
331
|
-
durationMs: 0,
|
|
332
|
-
completedTimestamp: Date.now(),
|
|
333
|
-
graphSnapshot: this.getSnapshot(),
|
|
411
|
+
this.effectsWrapper(() => {
|
|
412
|
+
for (const effect of effectsToRefresh) {
|
|
413
|
+
effect.doEffect(options?.otelContext)
|
|
334
414
|
}
|
|
335
|
-
|
|
336
|
-
return
|
|
337
|
-
}
|
|
415
|
+
})
|
|
338
416
|
|
|
339
|
-
|
|
417
|
+
const refreshDebugInfo: RefreshDebugInfo<TDebugRefreshReason, TDebugThunkInfo> = {
|
|
418
|
+
id: uniqueRefreshInfoId(),
|
|
419
|
+
reason: debugRefreshReason ?? (unknownRefreshReason() as TDebugRefreshReason),
|
|
420
|
+
skippedRefresh: true,
|
|
421
|
+
refreshedAtoms: [],
|
|
422
|
+
durationMs: 0,
|
|
423
|
+
completedTimestamp: Date.now(),
|
|
424
|
+
graphSnapshot: this.getSnapshot(),
|
|
425
|
+
}
|
|
426
|
+
this.debugRefreshInfos.push(refreshDebugInfo)
|
|
340
427
|
}
|
|
341
428
|
|
|
342
|
-
get<T>(atom: Atom<T>, context: Atom<any> | Effect): T {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
429
|
+
// get<T>(atom: Atom<T, TContext>, context: Atom<any, TContext> | Effect): T {
|
|
430
|
+
// // Autotracking: if we're getting the value of an atom,
|
|
431
|
+
// // that means it's a subcomputation for the currently refreshing atom.
|
|
432
|
+
// this.addEdge(context, atom)
|
|
346
433
|
|
|
347
|
-
|
|
348
|
-
|
|
434
|
+
// const dependencyMightBeStale = context._tag !== 'effect' && context.height <= atom.height
|
|
435
|
+
// const dependencyNotRefreshedYet = atom.result === NOT_REFRESHED_YET
|
|
349
436
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
437
|
+
// if (dependencyMightBeStale || dependencyNotRefreshedYet) {
|
|
438
|
+
// throw new DependencyNotReadyError(
|
|
439
|
+
// `${this.label(context)} referenced dependency ${this.label(atom)} which isn't ready`,
|
|
440
|
+
// )
|
|
441
|
+
// }
|
|
355
442
|
|
|
356
|
-
|
|
357
|
-
}
|
|
443
|
+
// return atom.result
|
|
444
|
+
// }
|
|
358
445
|
|
|
359
446
|
/**
|
|
360
447
|
* Update the graph to be consistent with the current values of the root atoms.
|
|
@@ -363,138 +450,138 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
363
450
|
*
|
|
364
451
|
* @param roots Root atoms to start the refresh from
|
|
365
452
|
*/
|
|
366
|
-
refresh(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
): void {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
label(atom: Atom<any> | Effect) {
|
|
453
|
+
// refresh(
|
|
454
|
+
// options?:
|
|
455
|
+
// | {
|
|
456
|
+
// otelHint?: string
|
|
457
|
+
// debugRefreshReason?: RefreshReasonWithGenericReasons<TDebugRefreshReason>
|
|
458
|
+
// }
|
|
459
|
+
// | undefined,
|
|
460
|
+
// otelContext: otel.Context = otel.context.active(),
|
|
461
|
+
// ): void {
|
|
462
|
+
// const otelHint = options?.otelHint ?? ''
|
|
463
|
+
// const debugRefreshReason = options?.debugRefreshReason
|
|
464
|
+
|
|
465
|
+
// const roots = [...this.dirtyNodes]
|
|
466
|
+
|
|
467
|
+
// const debugInfoForRefreshedAtoms: AtomDebugInfo<TDebugThunkInfo>[] = []
|
|
468
|
+
|
|
469
|
+
// // if (otelHint.includes('tableName')) {
|
|
470
|
+
// // console.log('refresh', otelHint, { shouldTrace })
|
|
471
|
+
// // }
|
|
472
|
+
|
|
473
|
+
// this.otelTracer.startActiveSpan(`LiveStore.refresh:${otelHint}`, {}, otelContext, (span) => {
|
|
474
|
+
// const atomsToRefresh = roots.filter(isAtom)
|
|
475
|
+
// const effectsToRun = new Set(roots.filter(isEffect))
|
|
476
|
+
|
|
477
|
+
// span.setAttribute('livestore.hint', otelHint)
|
|
478
|
+
// span.setAttribute('livestore.rootsCount', roots.length)
|
|
479
|
+
// // span.setAttribute('sstack', new Error().stack!)
|
|
480
|
+
|
|
481
|
+
// // Sort in topological order, starting with minimum height
|
|
482
|
+
// while (atomsToRefresh.length > 0) {
|
|
483
|
+
// atomsToRefresh.sort((a, b) => a.height - b.height)
|
|
484
|
+
// const atomToRefresh = atomsToRefresh.shift()!
|
|
485
|
+
|
|
486
|
+
// // Recompute the value
|
|
487
|
+
// let resultChanged = false
|
|
488
|
+
// const debugInfoForAtom = {
|
|
489
|
+
// atom: serializeAtom(atomToRefresh),
|
|
490
|
+
// resultChanged,
|
|
491
|
+
// // debugInfo: unknownRefreshReason() as TDebugThunkInfo,
|
|
492
|
+
// debugInfo: { _tag: 'unknown' } as TDebugThunkInfo,
|
|
493
|
+
// durationMs: 0,
|
|
494
|
+
// } satisfies AtomDebugInfo<TDebugThunkInfo>
|
|
495
|
+
// try {
|
|
496
|
+
// atomToRefresh.sub = new Set()
|
|
497
|
+
// const beforeTimestamp = performance.now()
|
|
498
|
+
// const newResult = atomToRefresh.getResult(
|
|
499
|
+
// (atom) => this.get(atom, atomToRefresh),
|
|
500
|
+
// (debugInfo) => {
|
|
501
|
+
// debugInfoForAtom.debugInfo = debugInfo
|
|
502
|
+
// },
|
|
503
|
+
// this.context ?? shouldNeverHappen(`No context provided yet for ReactiveGraph`),
|
|
504
|
+
// )
|
|
505
|
+
// const afterTimestamp = performance.now()
|
|
506
|
+
// debugInfoForAtom.durationMs = afterTimestamp - beforeTimestamp
|
|
507
|
+
|
|
508
|
+
// // Determine if the result changed to do early cutoff and avoid further unnecessary updates.
|
|
509
|
+
// // Refs never depend on anything, so if a ref is being refreshed it definitely changed.
|
|
510
|
+
// // For thunks, we use a deep equality check.
|
|
511
|
+
// resultChanged =
|
|
512
|
+
// atomToRefresh._tag === 'ref' ||
|
|
513
|
+
// (atomToRefresh._tag === 'thunk' && !atomToRefresh.equal(atomToRefresh.result, newResult))
|
|
514
|
+
|
|
515
|
+
// if (resultChanged) {
|
|
516
|
+
// atomToRefresh.result = newResult
|
|
517
|
+
// }
|
|
518
|
+
|
|
519
|
+
// this.dirtyNodes.delete(atomToRefresh)
|
|
520
|
+
// } catch (e) {
|
|
521
|
+
// if (e instanceof DependencyNotReadyError) {
|
|
522
|
+
// // If we hit a dependency that wasn't ready yet,
|
|
523
|
+
// // abort this recomputation and try again later.
|
|
524
|
+
// if (!atomsToRefresh.includes(atomToRefresh)) {
|
|
525
|
+
// atomsToRefresh.push(atomToRefresh)
|
|
526
|
+
// }
|
|
527
|
+
// } else {
|
|
528
|
+
// throw e
|
|
529
|
+
// }
|
|
530
|
+
// }
|
|
531
|
+
|
|
532
|
+
// debugInfoForRefreshedAtoms.push(debugInfoForAtom)
|
|
533
|
+
|
|
534
|
+
// if (!resultChanged) {
|
|
535
|
+
// continue
|
|
536
|
+
// }
|
|
537
|
+
|
|
538
|
+
// // Schedule supercomputations
|
|
539
|
+
// for (const superComp of atomToRefresh.super) {
|
|
540
|
+
// switch (superComp._tag) {
|
|
541
|
+
// case 'ref':
|
|
542
|
+
// case 'thunk': {
|
|
543
|
+
// if (!atomsToRefresh.includes(superComp)) {
|
|
544
|
+
// atomsToRefresh.push(superComp)
|
|
545
|
+
// }
|
|
546
|
+
// break
|
|
547
|
+
// }
|
|
548
|
+
// case 'effect': {
|
|
549
|
+
// effectsToRun.add(superComp)
|
|
550
|
+
// break
|
|
551
|
+
// }
|
|
552
|
+
// }
|
|
553
|
+
// }
|
|
554
|
+
// }
|
|
555
|
+
|
|
556
|
+
// this.effectsWrapper(() => {
|
|
557
|
+
// for (const effect of effectsToRun) {
|
|
558
|
+
// effect.doEffect((atom: Atom<any, TContext>) => this.get(atom, effect))
|
|
559
|
+
// this.dirtyNodes.delete(effect)
|
|
560
|
+
// }
|
|
561
|
+
// })
|
|
562
|
+
|
|
563
|
+
// span.end()
|
|
564
|
+
|
|
565
|
+
// const spanDurationMs = getDurationMsFromSpan(span)
|
|
566
|
+
|
|
567
|
+
// const refreshDebugInfo: RefreshDebugInfo<
|
|
568
|
+
// RefreshReasonWithGenericReasons<TDebugRefreshReason>,
|
|
569
|
+
// TDebugThunkInfo
|
|
570
|
+
// > = {
|
|
571
|
+
// id: uniqueRefreshInfoId(),
|
|
572
|
+
// reason: debugRefreshReason ?? unknownRefreshReason(),
|
|
573
|
+
// refreshedAtoms: debugInfoForRefreshedAtoms,
|
|
574
|
+
// skippedRefresh: false,
|
|
575
|
+
// durationMs: spanDurationMs,
|
|
576
|
+
// completedTimestamp: Date.now(),
|
|
577
|
+
// graphSnapshot: this.getSnapshot(),
|
|
578
|
+
// }
|
|
579
|
+
|
|
580
|
+
// this.debugRefreshInfos.push(refreshDebugInfo)
|
|
581
|
+
// })
|
|
582
|
+
// }
|
|
583
|
+
|
|
584
|
+
label(atom: Atom<any, TContext> | Effect) {
|
|
498
585
|
if (atom._tag === 'effect') {
|
|
499
586
|
return `unknown effect`
|
|
500
587
|
} else {
|
|
@@ -502,19 +589,19 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
502
589
|
}
|
|
503
590
|
}
|
|
504
591
|
|
|
505
|
-
addEdge(superComp: Atom<any> | Effect, subComp: Atom<any>) {
|
|
592
|
+
addEdge(superComp: Atom<any, TContext> | Effect, subComp: Atom<any, TContext>) {
|
|
506
593
|
superComp.sub.add(subComp)
|
|
507
594
|
subComp.super.add(superComp)
|
|
508
595
|
this.updateAtomHeight(superComp)
|
|
509
596
|
}
|
|
510
597
|
|
|
511
|
-
removeEdge(superComp: Atom<any> | Effect, subComp: Atom<any>) {
|
|
598
|
+
removeEdge(superComp: Atom<any, TContext> | Effect, subComp: Atom<any, TContext>) {
|
|
512
599
|
superComp.sub.delete(subComp)
|
|
513
600
|
subComp.super.delete(superComp)
|
|
514
601
|
this.updateAtomHeight(superComp)
|
|
515
602
|
}
|
|
516
603
|
|
|
517
|
-
updateAtomHeight(atom: Atom<any> | Effect) {
|
|
604
|
+
updateAtomHeight(atom: Atom<any, TContext> | Effect) {
|
|
518
605
|
switch (atom._tag) {
|
|
519
606
|
case 'ref': {
|
|
520
607
|
atom.height = 0
|
|
@@ -532,10 +619,40 @@ export class ReactiveGraph<TDebugRefreshReason extends Taggable, TDebugThunkInfo
|
|
|
532
619
|
|
|
533
620
|
private getSnapshot = (): ReactiveGraphSnapshot => ({
|
|
534
621
|
atoms: Array.from(this.atoms).map(serializeAtom),
|
|
535
|
-
effects: Array.from(this.effects).map(serializeEffect),
|
|
536
|
-
dirtyNodes: Array.from(this.dirtyNodes).map((a) => a.id),
|
|
622
|
+
// effects: Array.from(this.effects).map(serializeEffect),
|
|
623
|
+
// dirtyNodes: Array.from(this.dirtyNodes).map((a) => a.id),
|
|
537
624
|
})
|
|
625
|
+
|
|
626
|
+
get atomsCount() {
|
|
627
|
+
return this.atoms.size
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// const isAtom = <T, TContext>(a: Atom<T, TContext> | Effect): a is Atom<T, TContext> =>
|
|
632
|
+
// a._tag === 'ref' || a._tag === 'thunk'
|
|
633
|
+
// const isEffect = <T, TContext>(a: Atom<T, TContext> | Effect): a is Effect => a._tag === 'effect'
|
|
634
|
+
|
|
635
|
+
const compute = <T>(atom: Atom<T, any>, otelContext: otel.Context): T => {
|
|
636
|
+
// const __getResult = atom._tag === 'thunk' ? atom.__getResult.toString() : ''
|
|
637
|
+
if (atom.isDirty) {
|
|
638
|
+
// console.log('atom is dirty', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
639
|
+
const result = atom.computeResult(otelContext)
|
|
640
|
+
atom.isDirty = false
|
|
641
|
+
atom.previousResult = result
|
|
642
|
+
return result
|
|
643
|
+
} else {
|
|
644
|
+
// console.log('atom is clean', atom.id, atom.label ?? '', atom._tag, __getResult)
|
|
645
|
+
return atom.previousResult as T
|
|
646
|
+
}
|
|
538
647
|
}
|
|
539
648
|
|
|
540
|
-
const
|
|
541
|
-
|
|
649
|
+
const markSuperCompDirtyRec = <T>(atom: Atom<T, any>, effectsToRefresh: Set<Effect>) => {
|
|
650
|
+
for (const superComp of atom.super) {
|
|
651
|
+
if (superComp._tag === 'thunk' || superComp._tag === 'ref') {
|
|
652
|
+
superComp.isDirty = true
|
|
653
|
+
markSuperCompDirtyRec(superComp, effectsToRefresh)
|
|
654
|
+
} else {
|
|
655
|
+
effectsToRefresh.add(superComp)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|