• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

live-input-vector-output-node / livon-ts / 23556372904

25 Mar 2026 06:01PM UTC coverage: 83.212% (-0.003%) from 83.215%
23556372904

push

github

cr15p1
fix(sync): notify and sync cache on runContext.set

882 of 1043 branches covered (84.56%)

Branch coverage included in aggregate %.

3 of 4 new or added lines in 1 file covered. (75.0%)

3926 of 4735 relevant lines covered (82.91%)

38.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.62
/packages/sync/src/source/source.ts
1
import { type DraftMode, type EntityId } from '../entity.js';
2
import {
3
  applyEntityRunResult,
4
  clearEntityMembership,
5
  createEntityRunContextMethods,
6
  createRunContextEntryCache,
7
  readOrCreateSharedCacheWriteQueue,
8
  createSerializedKeyCache,
9
  cloneValue,
10
  createUnitSnapshot,
11
  notifyEffectListeners,
12
  resolveInput,
13
  resolveValue,
14
  setManyEntityMembership,
15
  setOneEntityMembership,
16
  serializeKey,
17
  type EffectListener,
18
  type InputUpdater,
19
  type UnitDataEntity,
20
  type UnitDataUpdate,
21
  type ValueUpdater,
22
} from '../utils/index.js';
23

24
import {
25
  areDraftValuesEqual,
26
  getModeValue,
27
  invokeSourceCleanup,
28
  isCacheRecordExpired,
29
  isEntityArray,
30
  isEntityValue,
31
  isRecordValue,
32
  isSourceCleanup,
33
  readSourceCacheRecord,
34
  resolveCacheLruMaxEntries,
35
  resolveCacheKey,
36
  resolveCacheStorage,
37
  resolveCacheTtl,
38
  serializeSourceCacheRecord,
39
  shouldUseCache,
40
} from './helpers.js';
41
import type {
42
  ReadEntityValueById,
43
  Source,
44
  SourceConfig,
45
  SourceContext,
46
  SourceRunContext,
47
  SourceRunContextEntry,
48
  SourceRunGate,
49
  SourceUnit,
50
  SourceUnitByKeyMap,
51
  SourceUnitInternal,
52
} from './types.js';
53

54
const DEFAULT_DESTROY_DELAY = 250;
37✔
55
const DEFAULT_DRAFT_MODE: DraftMode = 'global';
37✔
56
const RUN_CONTEXT_CACHE_LIMIT = 32;
37✔
57
const SOURCE_CACHE_LRU_STORAGE_KEY_SUFFIX = '__lru__';
37✔
58

59
const createInitialSourceContext = (
37✔
60
  hasCacheStorage: boolean,
61
): SourceContext => {
62
  return {
180✔
63
    cacheState: hasCacheStorage ? 'miss' : 'disabled',
64
    error: null,
65
  };
66
};
67

68
export const source = <
37✔
69
  TInput extends object | undefined,
70
  TPayload = unknown,
71
  TData = unknown,
72
  TMeta = unknown,
73
>({
74
  entity,
75
  ttl = entity.ttl ?? 0,
76
  draft = entity.draft ?? DEFAULT_DRAFT_MODE,
77
  cache,
78
  destroyDelay = entity.destroyDelay ?? DEFAULT_DESTROY_DELAY,
79
  onDestroy,
80
  run,
81
  defaultValue,
82
}: SourceConfig<TInput, TPayload, TData, TMeta>,
83
): Source<TInput, TPayload, TData, TMeta> => {
84
  const cacheTtl = resolveCacheTtl({
211✔
85
    sourceCache: cache,
86
    entityCache: entity.cache,
87
  });
88
  const cacheLruMaxEntries = resolveCacheLruMaxEntries({
211✔
89
    sourceCache: cache,
90
    entityCache: entity.cache,
91
  });
92
  const cacheStorage = resolveCacheStorage({
211✔
93
    sourceCache: cache,
94
    entityCache: entity.cache,
95
  });
96
  const cacheWriteQueue = cacheStorage
211✔
97
    ? readOrCreateSharedCacheWriteQueue({ storage: cacheStorage })
98
    : undefined;
99
  const cacheKeyPrefix = resolveCacheKey({
211✔
100
    sourceCache: cache,
101
    entityCache: entity.cache,
102
    sourceKey: serializeKey({
103
      mode: Array.isArray(defaultValue ?? null) ? 'many' : 'one',
104
    }),
105
  });
106

107
  const unitsByKey: SourceUnitByKeyMap<TInput, TPayload, TData, TMeta> =
108
    new Map<string, SourceUnitInternal<TInput, TPayload, TData, TMeta>>();
211✔
109
  const scopedDraftsById = new Map<EntityId, UnitDataEntity<TData>>();
211✔
110
  const unitKeyCache = createSerializedKeyCache({
211✔
111
    mode: 'scoped-unit',
112
  });
113

114
  const notifyScopedUnitsById = (id: EntityId): void => {
211✔
115
    Array.from(unitsByKey.values()).forEach((unitInternal) => {
2✔
116
      const hasMembership = unitInternal.membershipIds.some((membershipId) => membershipId === id);
2✔
117
      if (!hasMembership) {
2✔
118
        return;
×
119
      }
120

121
      notifyUnit(unitInternal);
2✔
122
    });
123
  };
124

125
  const readEntityValueById: ReadEntityValueById<EntityId, UnitDataEntity<TData>> = (id) => {
211✔
126
    if (draft === 'off') {
393✔
127
      return entity.getById(id);
4✔
128
    }
129

130
    if (draft === 'global') {
389✔
131
      const globalDraft = entity.getDraftById(id);
377✔
132
      if (globalDraft) {
377✔
133
        return globalDraft;
14✔
134
      }
135

136
      return entity.getById(id);
363✔
137
    }
138

139
    const scopedDraft = scopedDraftsById.get(id);
12✔
140
    if (scopedDraft) {
12✔
141
      return scopedDraft;
2✔
142
    }
143

144
    return entity.getById(id);
10✔
145
  };
146

147
  const setDraftById = (id: EntityId, value: UnitDataEntity<TData>): void => {
211✔
148
    if (draft === 'off') {
12✔
149
      return;
×
150
    }
151

152
    if (draft === 'global') {
12✔
153
      entity.setDraftById(id, value);
10✔
154
      return;
10✔
155
    }
156

157
    scopedDraftsById.set(id, value);
2✔
158
    notifyScopedUnitsById(id);
2✔
159
  };
160

161
  const clearDraftById = (id: EntityId): void => {
211✔
162
    if (draft === 'off') {
3✔
163
      return;
×
164
    }
165

166
    if (draft === 'global') {
3✔
167
      entity.clearDraftById(id);
3✔
168
      return;
3✔
169
    }
170

171
    const existed = scopedDraftsById.delete(id);
×
172
    if (!existed) {
×
173
      return;
×
174
    }
175

176
    notifyScopedUnitsById(id);
×
177
  };
178

179
  const notifyUnit = (
211✔
180
    internal: SourceUnitInternal<TInput, TPayload, TData, TMeta>,
181
  ): void => {
182
    internal.state.value = getModeValue(internal, readEntityValueById);
364✔
183

184
    notifyEffectListeners(
364✔
185
      internal.listeners,
186
      createUnitSnapshot({
187
        value: internal.state.value,
188
        status: internal.state.status,
189
        meta: internal.state.meta,
190
        context: internal.state.context,
191
      }),
192
    );
193
  };
194

195
  const sourceFactory: Source<TInput, TPayload, TData, TMeta> = (scope) => {
211✔
196
    const key = unitKeyCache.getOrCreateKey(scope);
216✔
197
    const existingUnit = unitsByKey.get(key);
216✔
198

199
    if (existingUnit) {
216✔
200
      return existingUnit.unit;
42✔
201
    }
202

203
    const initialValue = (defaultValue ?? null) as TData;
174✔
204
    const initialPayload = undefined as TPayload;
216✔
205
    const initialMode: 'one' | 'many' = Array.isArray(initialValue) ? 'many' : 'one';
216✔
206

207
    const internal: SourceUnitInternal<TInput, TPayload, TData, TMeta> = {
216✔
208
      key,
209
      ttl,
210
      destroyDelay,
211
      scope,
212
      payload: initialPayload,
213
      state: {
214
        value: initialValue,
215
        status: 'idle',
216
        meta: null,
217
        context: createInitialSourceContext(Boolean(cacheStorage)),
218
      },
219
      mode: initialMode,
220
      modeLocked: false,
221
      hasEntityValue: false,
222
      membershipIds: [],
223
      readWrite: {
224
        subview: entity.readWrite.subview,
225
      },
226
      listeners: new Set<EffectListener<TData, TMeta | null>>(),
227
      inFlightByPayload: new Map<string, Promise<TData>>(),
228
      runSequence: 0,
229
      latestRunSequence: 0,
230
      lastRunAt: null,
231
      cleanup: null,
232
      stopped: false,
233
      destroyed: false,
234
      destroyHandled: false,
235
      unit: {} as SourceUnit<TInput, TPayload, TData, TMeta>,
236
    };
237
    const payloadKeyCache = createSerializedKeyCache({
216✔
238
      mode: 'payload-hot-path',
239
      limit: RUN_CONTEXT_CACHE_LIMIT,
240
    });
241
    const createRunContextEntry = (
216✔
242
      payload: TPayload,
243
    ): SourceRunContextEntry<TInput, TPayload, TData, TMeta> => {
244
      const gate: SourceRunGate = {
143✔
245
        isLatestRun: () => false,
×
246
      };
247
      const runContextMethods = createEntityRunContextMethods<
143✔
248
        UnitDataEntity<TData>,
249
        TData,
250
        EntityId
251
      >({
252
        entity,
253
        state: internal,
254
        isActive: () => gate.isLatestRun(),
1✔
255
        refreshValue: () => {
256
          internal.state.value = getModeValue(internal, readEntityValueById);
1✔
257
        },
258
        readValue: () => internal.state.value,
1✔
259
      });
260
      const runContextBase: SourceRunContext<
261
        TInput,
262
        TPayload,
263
        TData,
264
        TMeta
265
      > = {
143✔
266
        scope: internal.scope,
267
        payload,
268
        setMeta: (metaInput: TMeta | null | ValueUpdater<TMeta | null, TMeta | null>) => {
269
          if (!gate.isLatestRun()) {
14✔
270
            return;
×
271
          }
272

273
          const nextMeta = resolveValue(
14✔
274
            internal.state.meta,
275
            metaInput,
276
          );
277
          if (Object.is(nextMeta, internal.state.meta)) {
14✔
278
            return;
2✔
279
          }
280

281
          internal.state.meta = nextMeta;
12✔
282
          notifyUnit(internal);
12✔
283
        },
284
        set: (input: TData | ValueUpdater<TData, TData>) => {
285
          if (!gate.isLatestRun()) {
5✔
286
            return;
×
287
          }
288

289
          const nextValue = resolveValue(internal.state.value, input);
5✔
290
          applyEntityRunResult({
5✔
291
            entity,
292
            state: internal,
293
            nextValue,
294
            refreshValueFromMembership: () => {
295
              internal.state.value = getModeValue(internal, readEntityValueById);
5✔
296
            },
297
            setRawValue: (value) => {
298
              internal.state.value = value;
×
299
            },
300
            upsertOneOperation: 'runContext.set() object',
301
            upsertManyOperation: 'runContext.set() array',
302
          });
303

304
          if (!gate.isLatestRun()) {
5✔
NEW
305
            return;
×
306
          }
307

308
          notifyUnit(internal);
5✔
309
          syncCacheRecord();
5✔
310
        },
311
        reset: () => {
312
          if (!gate.isLatestRun()) {
1✔
313
            return;
×
314
          }
315

316
          resetState();
1✔
317
        },
318
        ...runContextMethods,
319
      };
320

321
      return {
143✔
322
        gate,
323
        context: runContextBase,
324
      };
325
    };
326
    const runContextEntryCache = createRunContextEntryCache<
216✔
327
      TPayload,
328
      SourceRunContextEntry<TInput, TPayload, TData, TMeta>
329
    >({
330
      createEntry: createRunContextEntry,
331
      limit: RUN_CONTEXT_CACHE_LIMIT,
332
    });
333

334
    const setContext = (input: Partial<SourceContext>): void => {
216✔
335
      const hasCacheState = Object.prototype.hasOwnProperty.call(input, 'cacheState');
550✔
336
      const hasError = Object.prototype.hasOwnProperty.call(input, 'error');
550✔
337

338
      if (
550✔
339
        (!hasCacheState || input.cacheState === internal.state.context.cacheState)
340
        && (!hasError || Object.is(input.error, internal.state.context.error))
341
      ) {
342
        return;
507✔
343
      }
344

345
      internal.state.context = {
43✔
346
        ...internal.state.context,
347
        ...input,
348
      };
349
    };
350

351
    const cacheEnabled = Boolean(
216✔
352
      cacheStorage
353
      && (cacheTtl === 'infinity' || cacheTtl > 0),
354
    );
355
    const cacheLruEnabled = Boolean(
216✔
356
      cacheEnabled
357
      && cacheLruMaxEntries > 0
358
      && cacheStorage
359
      && cacheWriteQueue,
360
    );
361
    const cacheLruStorageKey = `${cacheKeyPrefix}:${SOURCE_CACHE_LRU_STORAGE_KEY_SUFFIX}`;
216✔
362
    let cacheLruLoaded = false;
216✔
363
    let cacheLruKeys: string[] = [];
216✔
364
    let activePayloadCacheKey: string | null = null;
216✔
365

366
    const loadCacheLruState = (): void => {
216✔
367
      if (!cacheLruEnabled || cacheLruLoaded || !cacheStorage) {
3✔
368
        return;
2✔
369
      }
370

371
      cacheLruLoaded = true;
1✔
372
      const rawLruState = cacheStorage.getItem(cacheLruStorageKey);
1✔
373
      if (!rawLruState) {
1✔
374
        return;
1✔
375
      }
376

377
      try {
×
378
        const parsedLruState: unknown = JSON.parse(rawLruState);
×
379
        if (!isRecordValue(parsedLruState)) {
×
380
          return;
×
381
        }
382

383
        const keysCandidate = parsedLruState.keys;
×
384
        if (!Array.isArray(keysCandidate)) {
×
385
          return;
×
386
        }
387

388
        if (!keysCandidate.every((entry) => typeof entry === 'string')) {
×
389
          return;
×
390
        }
391

392
        cacheLruKeys = keysCandidate;
×
393
      } catch {
394
        return;
×
395
      }
396
    };
397

398
    const persistCacheLruState = (): void => {
216✔
399
      if (!cacheLruEnabled || !cacheWriteQueue) {
3✔
400
        return;
×
401
      }
402

403
      if (cacheLruKeys.length === 0) {
3✔
404
        cacheWriteQueue.enqueueRemove(cacheLruStorageKey);
×
405
        return;
×
406
      }
407

408
      cacheWriteQueue.enqueueSet(
3✔
409
        cacheLruStorageKey,
410
        () => {
411
          return JSON.stringify({
3✔
412
            keys: cacheLruKeys,
413
          });
414
        },
415
      );
416
    };
417

418
    const touchCacheLruKey = (unitCacheKey: string): void => {
216✔
419
      if (!cacheLruEnabled || !cacheWriteQueue) {
14✔
420
        return;
11✔
421
      }
422

423
      loadCacheLruState();
3✔
424
      if (cacheLruKeys[0] === unitCacheKey) {
3✔
425
        return;
×
426
      }
427

428
      const existingIndex = cacheLruKeys.findIndex((entry) => entry === unitCacheKey);
3✔
429
      if (existingIndex > 0) {
3✔
430
        const existingEntry = cacheLruKeys[existingIndex];
×
431
        if (existingEntry === undefined) {
×
432
          return;
×
433
        }
434

435
        cacheLruKeys.splice(existingIndex, 1);
×
436
        cacheLruKeys.unshift(existingEntry);
×
437
        persistCacheLruState();
×
438
        return;
×
439
      }
440

441
      cacheLruKeys.unshift(unitCacheKey);
3✔
442
      const evictedEntry = cacheLruKeys.length > cacheLruMaxEntries
3✔
443
        ? cacheLruKeys.pop()
444
        : undefined;
445
      if (evictedEntry && evictedEntry !== unitCacheKey) {
14✔
446
        cacheWriteQueue.enqueueRemove(evictedEntry);
1✔
447
      }
448

449
      persistCacheLruState();
3✔
450
    };
451

452
    const removeCacheLruKey = (unitCacheKey: string): void => {
216✔
453
      if (!cacheLruEnabled) {
4✔
454
        return;
4✔
455
      }
456

457
      loadCacheLruState();
×
458
      const existingIndex = cacheLruKeys.findIndex((entry) => entry === unitCacheKey);
×
459
      if (existingIndex < 0) {
×
460
        return;
×
461
      }
462

463
      cacheLruKeys.splice(existingIndex, 1);
×
464
      persistCacheLruState();
×
465
    };
466

467
    const resolvePayloadCacheKey = (payload: TPayload): string => {
216✔
468
      return payloadKeyCache.getOrCreateKey(payload);
342✔
469
    };
470

471
    const resolveUnitCacheKey = (payloadCacheKey: string): string => {
216✔
472
      return `${cacheKeyPrefix}:${key}:${payloadCacheKey}`;
32✔
473
    };
474

475
    const syncCacheRecord = (): void => {
216✔
476
      if (!cacheEnabled || !cacheWriteQueue) {
191✔
477
        return;
181✔
478
      }
479

480
      const payloadCacheKey = resolvePayloadCacheKey(internal.payload);
10✔
481
      const unitCacheKey = resolveUnitCacheKey(payloadCacheKey);
10✔
482
      if (!internal.hasEntityValue) {
10✔
483
        removeCacheLruKey(unitCacheKey);
1✔
484
        cacheWriteQueue.enqueueRemove(unitCacheKey);
1✔
485
        return;
1✔
486
      }
487

488
      touchCacheLruKey(unitCacheKey);
9✔
489
      cacheWriteQueue.enqueueSet(unitCacheKey, () => {
9✔
490
        const entities = internal.membershipIds
8✔
491
          .map((id) => entity.getById(id))
8✔
492
          .filter((entry): entry is UnitDataEntity<TData> => entry !== undefined);
8✔
493

494
        return serializeSourceCacheRecord({
8✔
495
          mode: internal.mode,
496
          entities,
497
          writtenAt: Date.now(),
498
        });
499
      });
500
    };
501

502
    const hydrateFromCache = (payloadCacheKey: string): boolean => {
216✔
503
      if (!cacheStorage) {
247✔
504
        setContext({
192✔
505
          cacheState: 'disabled',
506
        });
507
        return false;
192✔
508
      }
509

510
      if (!cacheEnabled) {
55✔
511
        setContext({
33✔
512
          cacheState: 'disabled',
513
        });
514
        return false;
33✔
515
      }
516

517
      const unitCacheKey = resolveUnitCacheKey(payloadCacheKey);
22✔
518
      const rawRecord = cacheStorage.getItem(unitCacheKey);
22✔
519
      const parsedRecord = readSourceCacheRecord<UnitDataEntity<TData>>(rawRecord);
22✔
520
      if (!parsedRecord) {
22✔
521
        setContext({
14✔
522
          cacheState: 'miss',
523
        });
524
        return false;
14✔
525
      }
526

527
      if (isCacheRecordExpired({ ttl: cacheTtl, writtenAt: parsedRecord.writtenAt })) {
8✔
528
        removeCacheLruKey(unitCacheKey);
3✔
529
        if (cacheWriteQueue) {
3✔
530
          cacheWriteQueue.enqueueRemove(unitCacheKey);
3✔
531
        } else {
532
          try {
×
533
            cacheStorage.removeItem(unitCacheKey);
×
534
          } catch {
535
            // Ignore storage write errors and continue with stale cache behavior.
536
          }
537
        }
538
        setContext({
3✔
539
          cacheState: 'stale',
540
        });
541
        return false;
3✔
542
      }
543

544
      touchCacheLruKey(unitCacheKey);
5✔
545
      if (parsedRecord.mode === 'one') {
5✔
546
        const firstEntity = parsedRecord.entities[0];
5✔
547
        internal.hasEntityValue = true;
5✔
548

549
        if (firstEntity) {
5✔
550
          const upsertedEntity = entity.upsertOne(firstEntity);
5✔
551
          setOneEntityMembership(internal, {
5✔
552
            entity,
553
            value: upsertedEntity,
554
            operation: 'cache rehydrate one',
555
          });
556
          internal.state.value = getModeValue(internal, readEntityValueById);
5✔
557
        } else {
558
          clearEntityMembership(internal, { clearUnitMembership: entity.clearUnitMembership });
×
559
          internal.state.value = getModeValue(internal, readEntityValueById);
×
560
        }
561
      }
562

563
      if (parsedRecord.mode === 'many') {
5✔
564
        const upsertedEntities = entity.upsertMany(parsedRecord.entities);
×
565
        setManyEntityMembership(internal, {
×
566
          entity,
567
          values: upsertedEntities,
568
          operation: 'cache rehydrate many',
569
        });
570
        internal.state.value = getModeValue(internal, readEntityValueById);
×
571
      }
572

573
      internal.state.status = 'rehydrated';
5✔
574
      setContext({
5✔
575
        cacheState: 'hit',
576
        error: null,
577
      });
578
      return true;
5✔
579
    };
580

581
    const hydrateFromPayloadCache = (payload: TPayload): boolean => {
216✔
582
      const payloadCacheKey = resolvePayloadCacheKey(payload);
332✔
583
      if (activePayloadCacheKey === payloadCacheKey) {
332✔
584
        return false;
85✔
585
      }
586

587
      activePayloadCacheKey = payloadCacheKey;
247✔
588
      return hydrateFromCache(payloadCacheKey);
247✔
589
    };
590

591
    const unregisterFromEntity = entity.registerUnit({
216✔
592
      key: internal.key,
593
      onChange: () => {
594
        if (internal.destroyed) {
35✔
595
          return;
×
596
        }
597

598
        notifyUnit(internal);
35✔
599
        syncCacheRecord();
35✔
600
      },
601
    });
602

603
    hydrateFromPayloadCache(internal.payload);
216✔
604

605
    let singleInFlightPromise: Promise<TData> | null = null;
216✔
606
    let hasSingleInFlightPayload = false;
216✔
607
    let singleInFlightPayload: TPayload | undefined;
608
    let singleInFlightPayloadKey: string | null = null;
216✔
609

610
    const clearInFlightTracking = (): void => {
216✔
611
      internal.inFlightByPayload.clear();
14✔
612
      singleInFlightPromise = null;
14✔
613
      hasSingleInFlightPayload = false;
14✔
614
      singleInFlightPayload = undefined;
14✔
615
      singleInFlightPayloadKey = null;
14✔
616
    };
617

618
    const stopInternal = (): void => {
216✔
619
      internal.runSequence += 1;
24✔
620
      internal.latestRunSequence = internal.runSequence;
24✔
621
      internal.stopped = true;
24✔
622
      invokeSourceCleanup(internal.cleanup);
24✔
623
      internal.cleanup = null;
24✔
624
    };
625

626
    const resetState = (): void => {
216✔
627
      if (internal.destroyed) {
6✔
628
        return;
×
629
      }
630

631
      const previousMembershipIds = internal.membershipIds;
6✔
632
      stopInternal();
6✔
633
      clearInFlightTracking();
6✔
634
      runContextEntryCache.clear();
6✔
635
      payloadKeyCache.clear();
6✔
636
      activePayloadCacheKey = null;
6✔
637
      internal.payload = initialPayload;
6✔
638

639
      if (draft === 'scoped') {
6✔
640
        previousMembershipIds.forEach((id) => {
×
641
          scopedDraftsById.delete(id);
×
642
        });
643
      }
644

645
      clearEntityMembership(internal, { clearUnitMembership: entity.clearUnitMembership });
6✔
646
      internal.mode = initialMode;
6✔
647
      internal.modeLocked = false;
6✔
648
      internal.lastRunAt = null;
6✔
649
      internal.stopped = false;
6✔
650
      internal.state.value = initialValue;
6✔
651
      internal.state.status = 'idle';
6✔
652
      internal.state.meta = null;
6✔
653
      internal.state.context = createInitialSourceContext(Boolean(cacheStorage));
6✔
654
      notifyUnit(internal);
6✔
655
      syncCacheRecord();
6✔
656
    };
657

658
    const destroyInternal = (): void => {
216✔
659
      if (internal.destroyed) {
9✔
660
        return;
1✔
661
      }
662

663
      stopInternal();
8✔
664
      internal.destroyed = true;
8✔
665
      runContextEntryCache.clear();
8✔
666
      payloadKeyCache.clear();
8✔
667
      activePayloadCacheKey = null;
8✔
668
      clearInFlightTracking();
8✔
669

670
      unregisterFromEntity();
8✔
671
      internal.listeners.clear();
8✔
672

673
      if (!internal.destroyHandled) {
8✔
674
        internal.destroyHandled = true;
8✔
675
        onDestroy?.({
8✔
676
          scope: internal.scope,
677
          payload: internal.payload,
678
        });
679
      }
680

681
      unitsByKey.delete(internal.key);
8✔
682
    };
683

684
    const executeRun = (
216✔
685
      isForce: boolean,
686
      payloadInput?: TPayload | InputUpdater<TPayload>,
687
    ): Promise<TData> => {
688
      if (internal.destroyed) {
161✔
689
        return Promise.resolve(internal.state.value);
2✔
690
      }
691

692
      const hasPayloadInput = payloadInput !== undefined;
159✔
693
      const nextPayload = resolveInput(internal.payload, payloadInput);
159✔
694
      if (hasPayloadInput) {
159✔
695
        internal.payload = nextPayload;
75✔
696
      }
697
      let payloadKey: string | null = null;
159✔
698
      if (singleInFlightPromise) {
159✔
699
        if (hasSingleInFlightPayload && Object.is(singleInFlightPayload, internal.payload)) {
5✔
700
          return singleInFlightPromise;
×
701
        }
702

703
        payloadKey = payloadKeyCache.getOrCreateKey(internal.payload);
5✔
704
        if (singleInFlightPayloadKey === null && hasSingleInFlightPayload) {
5✔
705
          singleInFlightPayloadKey = payloadKeyCache.getOrCreateKey(singleInFlightPayload);
4✔
706
        }
707

708
        if (singleInFlightPayloadKey === payloadKey) {
5✔
709
          return singleInFlightPromise;
1✔
710
        }
711
      }
712

713
      if (internal.inFlightByPayload.size > 0) {
158✔
714
        payloadKey ??= payloadKeyCache.getOrCreateKey(internal.payload);
1✔
715
        const inFlightForPayload = internal.inFlightByPayload.get(payloadKey);
1✔
716
        if (inFlightForPayload) {
1✔
717
          return inFlightForPayload;
×
718
        }
719
      }
720

721
      internal.stopped = false;
158✔
722
      internal.runSequence += 1;
158✔
723
      internal.latestRunSequence = internal.runSequence;
158✔
724
      const runSequence = internal.runSequence;
158✔
725
      const isLatestRun = () => {
158✔
726
        return internal.latestRunSequence === runSequence && !internal.destroyed;
634✔
727
      };
728

729
      const didHydrateFromPayloadCache = hydrateFromPayloadCache(internal.payload);
158✔
730
      if (didHydrateFromPayloadCache && isLatestRun()) {
158✔
731
        notifyUnit(internal);
1✔
732
      }
733

734
      if (!isForce && !hasPayloadInput && shouldUseCache(internal)) {
158✔
735
        return Promise.resolve(internal.state.value);
2✔
736
      }
737

738
      const shouldUseRefreshingStatus = internal.state.status === 'rehydrated'
156✔
739
        || internal.state.status === 'success';
740
      if (isLatestRun()) {
161✔
741
        internal.state.status = shouldUseRefreshingStatus ? 'refreshing' : 'loading';
156✔
742
        setContext({
156✔
743
          error: null,
744
        });
745
        notifyUnit(internal);
156✔
746
      }
747

748
      const applyRunValue = (nextValue: TData | void): void => {
156✔
749
        if (!isLatestRun()) {
146✔
750
          return;
×
751
        }
752

753
        applyEntityRunResult({
146✔
754
          entity,
755
          state: internal,
756
          nextValue,
757
          refreshValueFromMembership: () => {
758
            internal.state.value = getModeValue(internal, readEntityValueById);
142✔
759
          },
760
          setRawValue: (value) => {
761
            internal.state.value = value;
3✔
762
          },
763
        });
764
      };
765

766
      const runContextEntry = runContextEntryCache.getOrCreate(internal.payload);
156✔
767
      runContextEntry.gate.isLatestRun = isLatestRun;
156✔
768
      runContextEntry.context.payload = internal.payload;
156✔
769

770
      const usesSingleInFlight = singleInFlightPromise === null && internal.inFlightByPayload.size === 0;
156✔
771
      const trackedPayloadKey = usesSingleInFlight
161✔
772
        ? null
773
        : (payloadKey ?? payloadKeyCache.getOrCreateKey(internal.payload));
774

775
      const runPromise: Promise<TData> = Promise.resolve(run(runContextEntry.context))
161✔
776
        .then((result) => {
777
          if (!isLatestRun() && isSourceCleanup(result)) {
153✔
778
            invokeSourceCleanup(result);
3✔
779
            return internal.state.value;
3✔
780
          }
781

782
          if (!isLatestRun()) {
150✔
783
            return internal.state.value;
4✔
784
          }
785

786
          if (isSourceCleanup(result)) {
146✔
787
            if (internal.destroyed || internal.stopped) {
×
788
              invokeSourceCleanup(result);
×
789
            } else {
790
              invokeSourceCleanup(internal.cleanup);
×
791
              internal.cleanup = result;
×
792
            }
793

794
            internal.state.value = getModeValue(internal, readEntityValueById);
×
795
          } else {
796
            applyRunValue(result);
146✔
797
          }
798

799
          internal.lastRunAt = Date.now();
145✔
800
          internal.state.status = 'success';
145✔
801
          setContext({
145✔
802
            error: null,
803
          });
804
          notifyUnit(internal);
145✔
805
          syncCacheRecord();
145✔
806

807
          return internal.state.value;
145✔
808
        })
809
        .catch((error: unknown) => {
810
          if (isLatestRun()) {
2✔
811
            internal.state.status = 'error';
2✔
812
            setContext({
2✔
813
              error,
814
            });
815
            notifyUnit(internal);
2✔
816
          }
817

818
          throw error;
2✔
819
        })
820
        .finally(() => {
821
          if (usesSingleInFlight) {
154✔
822
            if (singleInFlightPromise === runPromise) {
151✔
823
              singleInFlightPromise = null;
149✔
824
              hasSingleInFlightPayload = false;
149✔
825
              singleInFlightPayload = undefined;
149✔
826
              singleInFlightPayloadKey = null;
149✔
827
            }
828
            return;
151✔
829
          }
830

831
          if (trackedPayloadKey !== null) {
3✔
832
            internal.inFlightByPayload.delete(trackedPayloadKey);
3✔
833
          }
834
        });
835

836
      if (usesSingleInFlight) {
161✔
837
        singleInFlightPromise = runPromise;
152✔
838
        hasSingleInFlightPayload = true;
152✔
839
        singleInFlightPayload = internal.payload;
152✔
840
        singleInFlightPayloadKey = null;
152✔
841
      } else {
842
        if (trackedPayloadKey !== null) {
4✔
843
          internal.inFlightByPayload.set(trackedPayloadKey, runPromise);
4✔
844
        }
845
      }
846

847
      return runPromise;
156✔
848
    };
849

850
    const unit = ((payloadInput?: TPayload | InputUpdater<TPayload>) => {
216✔
851
      internal.payload = resolveInput(internal.payload, payloadInput);
×
852
      return unit;
×
853
    }) as SourceUnit<TInput, TPayload, TData, TMeta>;
854

855
    unit.ttl = ttl;
216✔
856
    unit.cacheTtl = cacheTtl;
216✔
857
    unit.destroyDelay = destroyDelay;
216✔
858
    unit.run = (payloadInput) => executeRun(false, payloadInput);
216✔
859
    unit.get = () => {
216✔
860
      return internal.state.value;
83✔
861
    };
862
    unit.draft = {
216✔
863
      set: (input: UnitDataUpdate<TData> | ValueUpdater<TData, UnitDataUpdate<TData>>) => {
864
        if (internal.destroyed || draft === 'off') {
20✔
865
          return;
2✔
866
        }
867

868
        const previousValue: TData = getModeValue(internal, readEntityValueById);
18✔
869
        const draftSeed: TData = cloneValue(previousValue);
18✔
870
        const nextUpdate = resolveValue(draftSeed, input);
18✔
871

872
        if (Object.is(nextUpdate, previousValue)) {
18✔
873
          return;
3✔
874
        }
875

876
        if (areDraftValuesEqual(previousValue, nextUpdate)) {
15✔
877
          return;
3✔
878
        }
879

880
        if (internal.mode === 'one') {
12✔
881
          const firstId = internal.membershipIds[0];
12✔
882
          if (!firstId || !isEntityValue<UnitDataEntity<TData>>(nextUpdate)) {
12✔
883
            return;
×
884
          }
885

886
          setDraftById(firstId, cloneValue(nextUpdate));
12✔
887
          return;
12✔
888
        }
889

890
        if (internal.mode === 'many') {
×
891
          if (!isEntityArray<UnitDataEntity<TData>>(nextUpdate)) {
×
892
            return;
×
893
          }
894

895
          nextUpdate.forEach((entry) => {
×
896
            const entryId = entity.idOf(entry);
×
897
            setDraftById(entryId, cloneValue(entry));
×
898
          });
899
        }
900
      },
901
      clean: () => {
902
        if (internal.destroyed || draft === 'off') {
5✔
903
          return;
×
904
        }
905

906
        internal.membershipIds.forEach((id) => {
5✔
907
          clearDraftById(id);
3✔
908
        });
909
      },
910
    };
911
    unit.effect = (listener) => {
216✔
912
      if (internal.destroyed) {
68✔
913
        return undefined;
1✔
914
      }
915

916
      internal.listeners.add(listener);
67✔
917

918
      return () => {
67✔
919
        internal.listeners.delete(listener);
20✔
920
      };
921
    };
922
    unit.refetch = (payloadInput) => {
216✔
923
      const nextPayload = resolveInput(internal.payload, payloadInput);
5✔
924
      return sourceFactory(internal.scope).force(nextPayload);
5✔
925
    };
926
    unit.force = (payloadInput) => executeRun(true, payloadInput);
216✔
927
    unit.reset = () => {
216✔
928
      resetState();
5✔
929
    };
930
    unit.stop = () => {
216✔
931
      if (internal.destroyed) {
10✔
932
        return;
×
933
      }
934

935
      stopInternal();
10✔
936
    };
937
    unit.destroy = () => {
216✔
938
      destroyInternal();
9✔
939
    };
940

941
    Object.defineProperty(unit, 'getSnapshot', {
216✔
942
      configurable: false,
943
      enumerable: false,
944
      writable: false,
945
      value: () => {
946
        return createUnitSnapshot({
23✔
947
          value: internal.state.value,
948
          status: internal.state.status,
949
          meta: internal.state.meta,
950
          context: internal.state.context,
951
        });
952
      },
953
    });
954

955
    internal.unit = unit;
216✔
956
    unitsByKey.set(key, internal);
216✔
957

958
    return unit;
216✔
959
  };
960

961
  return sourceFactory;
211✔
962
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc