@zeix/cause-effect 0.18.0 → 0.18.2
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/.ai-context.md +14 -3
- package/.github/copilot-instructions.md +15 -5
- package/ARCHITECTURE.md +43 -19
- package/CHANGELOG.md +43 -0
- package/CLAUDE.md +33 -7
- package/GUIDE.md +1 -1
- package/README.md +36 -5
- package/context7.json +4 -0
- package/examples/events-sensor.ts +187 -0
- package/examples/selector-sensor.ts +173 -0
- package/index.dev.js +276 -222
- package/index.js +1 -1
- package/index.ts +4 -2
- package/package.json +2 -2
- package/skills/changelog-keeper/SKILL.md +59 -0
- package/skills/changelog-keeper/agents/openai.yaml +4 -0
- package/src/graph.ts +28 -5
- package/src/nodes/collection.ts +185 -133
- package/src/nodes/list.ts +121 -116
- package/src/nodes/memo.ts +31 -3
- package/src/nodes/sensor.ts +27 -17
- package/src/nodes/state.ts +2 -2
- package/src/nodes/store.ts +71 -72
- package/src/nodes/task.ts +31 -3
- package/test/collection.test.ts +40 -51
- package/test/list.test.ts +192 -0
- package/test/memo.test.ts +194 -0
- package/test/task.test.ts +134 -0
- package/types/index.d.ts +5 -5
- package/types/src/graph.d.ts +12 -2
- package/types/src/nodes/collection.d.ts +12 -7
- package/types/src/nodes/list.d.ts +12 -11
- package/types/src/nodes/memo.d.ts +6 -0
- package/types/src/nodes/sensor.d.ts +15 -9
- package/types/src/nodes/store.d.ts +4 -4
- package/types/src/nodes/task.d.ts +6 -0
- package/COLLECTION_REFACTORING.md +0 -161
package/src/nodes/collection.ts
CHANGED
|
@@ -9,18 +9,20 @@ import {
|
|
|
9
9
|
type Cleanup,
|
|
10
10
|
FLAG_CLEAN,
|
|
11
11
|
FLAG_DIRTY,
|
|
12
|
+
FLAG_RELINK,
|
|
12
13
|
link,
|
|
13
14
|
type MemoNode,
|
|
14
15
|
propagate,
|
|
15
16
|
refresh,
|
|
16
17
|
type Signal,
|
|
17
18
|
type SinkNode,
|
|
19
|
+
SKIP_EQUALITY,
|
|
18
20
|
TYPE_COLLECTION,
|
|
19
21
|
untrack,
|
|
20
22
|
} from '../graph'
|
|
21
|
-
import { isAsyncFunction,
|
|
23
|
+
import { isAsyncFunction, isObjectOfType, isSyncFunction } from '../util'
|
|
22
24
|
import {
|
|
23
|
-
|
|
25
|
+
getKeyGenerator,
|
|
24
26
|
isList,
|
|
25
27
|
type KeyConfig,
|
|
26
28
|
keysEqual,
|
|
@@ -57,14 +59,20 @@ type Collection<T extends {}> = {
|
|
|
57
59
|
readonly length: number
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
type CollectionChanges<T> = {
|
|
63
|
+
add?: T[]
|
|
64
|
+
change?: T[]
|
|
65
|
+
remove?: T[]
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
type CollectionOptions<T extends {}> = {
|
|
61
69
|
value?: T[]
|
|
62
70
|
keyConfig?: KeyConfig<T>
|
|
63
|
-
createItem?: (
|
|
71
|
+
createItem?: (value: T) => Signal<T>
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
type CollectionCallback = (
|
|
67
|
-
|
|
74
|
+
type CollectionCallback<T extends {}> = (
|
|
75
|
+
apply: (changes: CollectionChanges<T>) => void,
|
|
68
76
|
) => Cleanup
|
|
69
77
|
|
|
70
78
|
/* === Functions === */
|
|
@@ -99,6 +107,7 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
99
107
|
|
|
100
108
|
const isAsync = isAsyncFunction(callback)
|
|
101
109
|
const signals = new Map<string, Memo<T>>()
|
|
110
|
+
let keys: string[] = []
|
|
102
111
|
|
|
103
112
|
const addSignal = (key: string): void => {
|
|
104
113
|
const signal = isAsync
|
|
@@ -121,68 +130,107 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
121
130
|
signals.set(key, signal as Memo<T>)
|
|
122
131
|
}
|
|
123
132
|
|
|
124
|
-
// Sync
|
|
133
|
+
// Sync signals map with the given keys.
|
|
134
|
+
// Intentionally side-effectful: mutates the private signals map and keys
|
|
135
|
+
// array. Sets FLAG_RELINK on the node if keys changed.
|
|
136
|
+
function syncKeys(nextKeys: string[]): void {
|
|
137
|
+
if (!keysEqual(keys, nextKeys)) {
|
|
138
|
+
const a = new Set(keys)
|
|
139
|
+
const b = new Set(nextKeys)
|
|
140
|
+
|
|
141
|
+
for (const key of keys) if (!b.has(key)) signals.delete(key)
|
|
142
|
+
for (const key of nextKeys) if (!a.has(key)) addSignal(key)
|
|
143
|
+
keys = nextKeys
|
|
144
|
+
node.flags |= FLAG_RELINK
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Build current value from child signals.
|
|
149
|
+
// Reads source.keys() to sync the signals map and — during refresh() —
|
|
125
150
|
// to establish a graph edge from source → this node.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
for (const key of oldKeys)
|
|
138
|
-
if (!newKeySet.has(key)) signals.delete(key)
|
|
139
|
-
for (const key of newKeys) if (!oldKeySet.has(key)) addSignal(key)
|
|
151
|
+
function buildValue(): T[] {
|
|
152
|
+
syncKeys(Array.from(source.keys()))
|
|
153
|
+
const result: T[] = []
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
try {
|
|
156
|
+
const v = signals.get(key)?.get()
|
|
157
|
+
if (v != null) result.push(v)
|
|
158
|
+
} catch (e) {
|
|
159
|
+
// Skip pending async items; rethrow real errors
|
|
160
|
+
if (!(e instanceof UnsetSignalValueError)) throw e
|
|
161
|
+
}
|
|
140
162
|
}
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
141
165
|
|
|
142
|
-
|
|
166
|
+
// Shallow reference equality for value arrays — prevents unnecessary
|
|
167
|
+
// downstream propagation when re-evaluation produces the same item references
|
|
168
|
+
const valuesEqual = (a: T[], b: T[]): boolean => {
|
|
169
|
+
if (a.length !== b.length) return false
|
|
170
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false
|
|
171
|
+
return true
|
|
143
172
|
}
|
|
144
173
|
|
|
145
|
-
// Structural tracking node —
|
|
146
|
-
// fn (
|
|
147
|
-
//
|
|
148
|
-
const node: MemoNode<
|
|
149
|
-
fn:
|
|
174
|
+
// Structural tracking node — mirrors the List/Store/createCollection pattern.
|
|
175
|
+
// fn (buildValue) syncs keys then reads child signals to produce T[].
|
|
176
|
+
// Keys are tracked separately in a local variable.
|
|
177
|
+
const node: MemoNode<T[]> = {
|
|
178
|
+
fn: buildValue,
|
|
150
179
|
value: [],
|
|
151
180
|
flags: FLAG_DIRTY,
|
|
152
181
|
sources: null,
|
|
153
182
|
sourcesTail: null,
|
|
154
183
|
sinks: null,
|
|
155
184
|
sinksTail: null,
|
|
156
|
-
equals:
|
|
185
|
+
equals: valuesEqual,
|
|
157
186
|
error: undefined,
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
|
|
161
|
-
function ensureSynced(): string[] {
|
|
189
|
+
function ensureFresh(): void {
|
|
162
190
|
if (node.sources) {
|
|
163
191
|
if (node.flags) {
|
|
164
|
-
node.value = untrack(
|
|
165
|
-
node.flags
|
|
192
|
+
node.value = untrack(buildValue)
|
|
193
|
+
if (node.flags & FLAG_RELINK) {
|
|
194
|
+
// Keys changed — new child signals need graph edges.
|
|
195
|
+
// Tracked recompute so link() adds new edges and
|
|
196
|
+
// trimSources() removes stale ones without orphaning.
|
|
197
|
+
node.flags = FLAG_DIRTY
|
|
198
|
+
refresh(node as unknown as SinkNode)
|
|
199
|
+
if (node.error) throw node.error
|
|
200
|
+
} else {
|
|
201
|
+
node.flags = FLAG_CLEAN
|
|
202
|
+
}
|
|
166
203
|
}
|
|
167
|
-
} else {
|
|
204
|
+
} else if (node.sinks) {
|
|
205
|
+
// First access with a downstream subscriber — use refresh()
|
|
206
|
+
// to establish graph edges via recomputeMemo
|
|
168
207
|
refresh(node as unknown as SinkNode)
|
|
169
208
|
if (node.error) throw node.error
|
|
209
|
+
} else {
|
|
210
|
+
// No subscribers yet (e.g., chained deriveCollection init) —
|
|
211
|
+
// compute value without establishing graph edges to prevent
|
|
212
|
+
// premature watched activation on upstream sources.
|
|
213
|
+
// Keep FLAG_DIRTY so the first refresh() with a real subscriber
|
|
214
|
+
// will establish proper graph edges.
|
|
215
|
+
node.value = untrack(buildValue)
|
|
170
216
|
}
|
|
171
|
-
return node.value
|
|
172
217
|
}
|
|
173
218
|
|
|
174
|
-
// Initialize signals for current source keys
|
|
175
|
-
|
|
219
|
+
// Initialize signals for current source keys — untrack to prevent
|
|
220
|
+
// triggering watched callbacks on upstream sources during construction.
|
|
221
|
+
// The first refresh() (triggered by an effect) will establish proper
|
|
222
|
+
// graph edges; this just populates the signals map for direct access.
|
|
223
|
+
const initialKeys = Array.from(untrack(() => source.keys()))
|
|
176
224
|
for (const key of initialKeys) addSignal(key)
|
|
177
|
-
|
|
178
|
-
// Keep FLAG_DIRTY so the first refresh() establishes
|
|
225
|
+
keys = initialKeys
|
|
226
|
+
// Keep FLAG_DIRTY so the first refresh() establishes edges.
|
|
179
227
|
|
|
180
228
|
const collection: Collection<T> = {
|
|
181
229
|
[Symbol.toStringTag]: TYPE_COLLECTION,
|
|
182
230
|
[Symbol.isConcatSpreadable]: true as const,
|
|
183
231
|
|
|
184
232
|
*[Symbol.iterator]() {
|
|
185
|
-
for (const key of
|
|
233
|
+
for (const key of keys) {
|
|
186
234
|
const signal = signals.get(key)
|
|
187
235
|
if (signal) yield signal
|
|
188
236
|
}
|
|
@@ -190,32 +238,24 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
190
238
|
|
|
191
239
|
get length() {
|
|
192
240
|
if (activeSink) link(node, activeSink)
|
|
193
|
-
|
|
241
|
+
ensureFresh()
|
|
242
|
+
return keys.length
|
|
194
243
|
},
|
|
195
244
|
|
|
196
245
|
keys() {
|
|
197
246
|
if (activeSink) link(node, activeSink)
|
|
198
|
-
|
|
247
|
+
ensureFresh()
|
|
248
|
+
return keys.values()
|
|
199
249
|
},
|
|
200
250
|
|
|
201
251
|
get() {
|
|
202
252
|
if (activeSink) link(node, activeSink)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for (const key of currentKeys) {
|
|
206
|
-
try {
|
|
207
|
-
const v = signals.get(key)?.get()
|
|
208
|
-
if (v != null) result.push(v)
|
|
209
|
-
} catch (e) {
|
|
210
|
-
// Skip pending async items; rethrow real errors
|
|
211
|
-
if (!(e instanceof UnsetSignalValueError)) throw e
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return result
|
|
253
|
+
ensureFresh()
|
|
254
|
+
return node.value
|
|
215
255
|
},
|
|
216
256
|
|
|
217
257
|
at(index: number) {
|
|
218
|
-
return signals.get(
|
|
258
|
+
return signals.get(keys[index])
|
|
219
259
|
},
|
|
220
260
|
|
|
221
261
|
byKey(key: string) {
|
|
@@ -223,11 +263,11 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
223
263
|
},
|
|
224
264
|
|
|
225
265
|
keyAt(index: number) {
|
|
226
|
-
return
|
|
266
|
+
return keys[index]
|
|
227
267
|
},
|
|
228
268
|
|
|
229
269
|
indexOfKey(key: string) {
|
|
230
|
-
return
|
|
270
|
+
return keys.indexOf(key)
|
|
231
271
|
},
|
|
232
272
|
|
|
233
273
|
deriveCollection<R extends {}>(
|
|
@@ -247,37 +287,32 @@ function deriveCollection<T extends {}, U extends {}>(
|
|
|
247
287
|
|
|
248
288
|
/**
|
|
249
289
|
* Creates an externally-driven Collection with a watched lifecycle.
|
|
250
|
-
* Items are managed
|
|
290
|
+
* Items are managed via the `applyChanges(changes)` helper passed to the watched callback.
|
|
251
291
|
* The collection activates when first accessed by an effect and deactivates when no longer watched.
|
|
252
292
|
*
|
|
253
293
|
* @since 0.18.0
|
|
254
|
-
* @param
|
|
294
|
+
* @param watched - Callback invoked when the collection starts being watched, receives applyChanges helper
|
|
255
295
|
* @param options - Optional configuration including initial value, key generation, and item signal creation
|
|
256
296
|
* @returns A read-only Collection signal
|
|
257
297
|
*/
|
|
258
298
|
function createCollection<T extends {}>(
|
|
259
|
-
|
|
299
|
+
watched: CollectionCallback<T>,
|
|
260
300
|
options?: CollectionOptions<T>,
|
|
261
301
|
): Collection<T> {
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
validateCallback(TYPE_COLLECTION, start)
|
|
302
|
+
const value = options?.value ?? []
|
|
303
|
+
if (value.length) validateSignalValue(TYPE_COLLECTION, value, Array.isArray)
|
|
304
|
+
validateCallback(TYPE_COLLECTION, watched, isSyncFunction)
|
|
266
305
|
|
|
267
306
|
const signals = new Map<string, Signal<T>>()
|
|
268
307
|
const keys: string[] = []
|
|
308
|
+
const itemToKey = new Map<T, string>()
|
|
269
309
|
|
|
270
|
-
|
|
271
|
-
const keyConfig = options?.keyConfig
|
|
272
|
-
const generateKey: (item: T) => string =
|
|
273
|
-
typeof keyConfig === 'string'
|
|
274
|
-
? () => `${keyConfig}${keyCounter++}`
|
|
275
|
-
: isFunction<string>(keyConfig)
|
|
276
|
-
? (item: T) => keyConfig(item)
|
|
277
|
-
: () => String(keyCounter++)
|
|
310
|
+
const [generateKey, contentBased] = getKeyGenerator(options?.keyConfig)
|
|
278
311
|
|
|
279
|
-
const
|
|
280
|
-
|
|
312
|
+
const resolveKey = (item: T): string | undefined =>
|
|
313
|
+
itemToKey.get(item) ?? (contentBased ? generateKey(item) : undefined)
|
|
314
|
+
|
|
315
|
+
const itemFactory = options?.createItem ?? createState
|
|
281
316
|
|
|
282
317
|
// Build current value from child signals
|
|
283
318
|
function buildValue(): T[] {
|
|
@@ -296,68 +331,83 @@ function createCollection<T extends {}>(
|
|
|
296
331
|
|
|
297
332
|
const node: MemoNode<T[]> = {
|
|
298
333
|
fn: buildValue,
|
|
299
|
-
value
|
|
334
|
+
value,
|
|
300
335
|
flags: FLAG_DIRTY,
|
|
301
336
|
sources: null,
|
|
302
337
|
sourcesTail: null,
|
|
303
338
|
sinks: null,
|
|
304
339
|
sinksTail: null,
|
|
305
|
-
equals:
|
|
340
|
+
equals: SKIP_EQUALITY, // Always rebuild — structural changes are managed externally
|
|
306
341
|
error: undefined,
|
|
307
342
|
}
|
|
308
343
|
|
|
309
|
-
/** Apply external changes to the collection */
|
|
310
|
-
function applyChanges(changes: DiffResult): void {
|
|
311
|
-
if (!changes.changed) return
|
|
312
|
-
let structural = false
|
|
313
|
-
|
|
314
|
-
batch(() => {
|
|
315
|
-
// Additions
|
|
316
|
-
for (const key in changes.add) {
|
|
317
|
-
const value = changes.add[key] as T
|
|
318
|
-
signals.set(key, itemFactory(key, value))
|
|
319
|
-
if (!keys.includes(key)) keys.push(key)
|
|
320
|
-
structural = true
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Changes — only for State signals
|
|
324
|
-
for (const key in changes.change) {
|
|
325
|
-
const signal = signals.get(key)
|
|
326
|
-
if (signal && isState(signal)) {
|
|
327
|
-
signal.set(changes.change[key] as T)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Removals
|
|
332
|
-
for (const key in changes.remove) {
|
|
333
|
-
signals.delete(key)
|
|
334
|
-
const index = keys.indexOf(key)
|
|
335
|
-
if (index !== -1) keys.splice(index, 1)
|
|
336
|
-
structural = true
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (structural) {
|
|
340
|
-
node.sources = null
|
|
341
|
-
node.sourcesTail = null
|
|
342
|
-
}
|
|
343
|
-
// Reset CHECK/RUNNING before propagate; mark DIRTY so next get() rebuilds
|
|
344
|
-
node.flags = FLAG_CLEAN
|
|
345
|
-
propagate(node as unknown as SinkNode)
|
|
346
|
-
node.flags |= FLAG_DIRTY
|
|
347
|
-
})
|
|
348
|
-
}
|
|
349
|
-
|
|
350
344
|
// Initialize signals for initial value
|
|
351
|
-
for (const item of
|
|
345
|
+
for (const item of value) {
|
|
352
346
|
const key = generateKey(item)
|
|
353
|
-
signals.set(key, itemFactory(
|
|
347
|
+
signals.set(key, itemFactory(item))
|
|
348
|
+
itemToKey.set(item, key)
|
|
354
349
|
keys.push(key)
|
|
355
350
|
}
|
|
356
|
-
node.value =
|
|
351
|
+
node.value = value
|
|
357
352
|
node.flags = FLAG_DIRTY // First refresh() will establish child edges
|
|
358
353
|
|
|
359
|
-
function
|
|
360
|
-
if (
|
|
354
|
+
function subscribe(): void {
|
|
355
|
+
if (activeSink) {
|
|
356
|
+
if (!node.sinks)
|
|
357
|
+
node.stop = watched((changes: CollectionChanges<T>): void => {
|
|
358
|
+
const { add, change, remove } = changes
|
|
359
|
+
if (!add?.length && !change?.length && !remove?.length)
|
|
360
|
+
return
|
|
361
|
+
let structural = false
|
|
362
|
+
|
|
363
|
+
batch(() => {
|
|
364
|
+
// Additions
|
|
365
|
+
if (add) {
|
|
366
|
+
for (const item of add) {
|
|
367
|
+
const key = generateKey(item)
|
|
368
|
+
signals.set(key, itemFactory(item))
|
|
369
|
+
itemToKey.set(item, key)
|
|
370
|
+
if (!keys.includes(key)) keys.push(key)
|
|
371
|
+
structural = true
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Changes — only for State signals
|
|
376
|
+
if (change) {
|
|
377
|
+
for (const item of change) {
|
|
378
|
+
const key = resolveKey(item)
|
|
379
|
+
if (!key) continue
|
|
380
|
+
const signal = signals.get(key)
|
|
381
|
+
if (signal && isState(signal)) {
|
|
382
|
+
// Update reverse map: remove old reference, add new
|
|
383
|
+
itemToKey.delete(signal.get())
|
|
384
|
+
signal.set(item)
|
|
385
|
+
itemToKey.set(item, key)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Removals
|
|
391
|
+
if (remove) {
|
|
392
|
+
for (const item of remove) {
|
|
393
|
+
const key = resolveKey(item)
|
|
394
|
+
if (!key) continue
|
|
395
|
+
itemToKey.delete(item)
|
|
396
|
+
signals.delete(key)
|
|
397
|
+
const index = keys.indexOf(key)
|
|
398
|
+
if (index !== -1) keys.splice(index, 1)
|
|
399
|
+
structural = true
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Mark DIRTY so next get() rebuilds; propagate to sinks
|
|
404
|
+
node.flags = FLAG_DIRTY | (structural ? FLAG_RELINK : 0)
|
|
405
|
+
for (let e = node.sinks; e; e = e.nextSink)
|
|
406
|
+
propagate(e.sink)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
link(node, activeSink)
|
|
410
|
+
}
|
|
361
411
|
}
|
|
362
412
|
|
|
363
413
|
const collection: Collection<T> = {
|
|
@@ -372,30 +422,31 @@ function createCollection<T extends {}>(
|
|
|
372
422
|
},
|
|
373
423
|
|
|
374
424
|
get length() {
|
|
375
|
-
|
|
376
|
-
startWatching()
|
|
377
|
-
link(node, activeSink)
|
|
378
|
-
}
|
|
425
|
+
subscribe()
|
|
379
426
|
return keys.length
|
|
380
427
|
},
|
|
381
428
|
|
|
382
429
|
keys() {
|
|
383
|
-
|
|
384
|
-
startWatching()
|
|
385
|
-
link(node, activeSink)
|
|
386
|
-
}
|
|
430
|
+
subscribe()
|
|
387
431
|
return keys.values()
|
|
388
432
|
},
|
|
389
433
|
|
|
390
434
|
get() {
|
|
391
|
-
|
|
392
|
-
startWatching()
|
|
393
|
-
link(node, activeSink)
|
|
394
|
-
}
|
|
435
|
+
subscribe()
|
|
395
436
|
if (node.sources) {
|
|
396
437
|
if (node.flags) {
|
|
438
|
+
const relink = node.flags & FLAG_RELINK
|
|
397
439
|
node.value = untrack(buildValue)
|
|
398
|
-
|
|
440
|
+
if (relink) {
|
|
441
|
+
// Structural mutation added/removed child signals —
|
|
442
|
+
// tracked recompute so link() adds new edges and
|
|
443
|
+
// trimSources() removes stale ones without orphaning.
|
|
444
|
+
node.flags = FLAG_DIRTY
|
|
445
|
+
refresh(node as unknown as SinkNode)
|
|
446
|
+
if (node.error) throw node.error
|
|
447
|
+
} else {
|
|
448
|
+
node.flags = FLAG_CLEAN
|
|
449
|
+
}
|
|
399
450
|
}
|
|
400
451
|
} else {
|
|
401
452
|
refresh(node as unknown as SinkNode)
|
|
@@ -468,6 +519,7 @@ export {
|
|
|
468
519
|
isCollectionSource,
|
|
469
520
|
type Collection,
|
|
470
521
|
type CollectionCallback,
|
|
522
|
+
type CollectionChanges,
|
|
471
523
|
type CollectionOptions,
|
|
472
524
|
type CollectionSource,
|
|
473
525
|
type DeriveCollectionCallback,
|