mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
fix(satellites): retain cached catalog provenance
This commit is contained in:
1 parent
de2138e84c
commit
b43970eecc
8 files changed
+313
-5
No files matched your search
@@ -817,7 +817,11 @@ Viewport limits, saturation retry, placement, query caps and controls are retain
|
|||||||
|
|
||||||
Satellite and mission layers expose separate factories through `./layers/satellites`
|
Satellite and mission layers expose separate factories through `./layers/satellites`
|
||||||
and `./layers/launches`. Each owns its catalog, render state, tracking, interaction
|
and `./layers/launches`. Each owns its catalog, render state, tracking, interaction
|
||||||
and teardown. Source adapters retain the existing CelesTrak and Launch Library
|
and teardown. Satellite core and dense catalogs retain upstream stale-cache
|
||||||
|
provenance only for admitted records; duplicate or invalid entries cannot taint
|
||||||
|
a fresh winning record. Dense removal clears only its own provenance, and a
|
||||||
|
fresh core rebuild or lifecycle reset replaces the corresponding ownership.
|
||||||
|
Source adapters retain the existing CelesTrak and Launch Library
|
||||||
endpoints; standalone entries provide scene services and the satellite dependency.
|
endpoints; standalone entries provide scene services and the satellite dependency.
|
||||||
The mission factory separates paths, replay, camera, panel, overlays and placement.
|
The mission factory separates paths, replay, camera, panel, overlays and placement.
|
||||||
Launch Library pad coordinates retain unknown values: missing fields use the
|
Launch Library pad coordinates retain unknown values: missing fields use the
|
||||||
|
|||||||
@@ -446,6 +446,7 @@
|
|||||||
"src/data/satellites.js",
|
"src/data/satellites.js",
|
||||||
"src/data/rocketLaunches.js",
|
"src/data/rocketLaunches.js",
|
||||||
"src/layers/satellites/source.test.mjs",
|
"src/layers/satellites/source.test.mjs",
|
||||||
|
"src/layers/satellites/stale.test.mjs",
|
||||||
"src/layers/launches/source.test.mjs",
|
"src/layers/launches/source.test.mjs",
|
||||||
"src/layers/launches/ingestion.test.mjs",
|
"src/layers/launches/ingestion.test.mjs",
|
||||||
"src/layers/installations/lifecycle.js",
|
"src/layers/installations/lifecycle.js",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
|
|||||||
});
|
});
|
||||||
layerState._points.set(noradId, point);
|
layerState._points.set(noradId, point);
|
||||||
layerState._denseIds.push(noradId);
|
layerState._denseIds.push(noradId);
|
||||||
|
layerState._denseStale ||= Boolean(res.stale);
|
||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
// Yield to the event loop between chunks.
|
// Yield to the event loop between chunks.
|
||||||
@@ -226,6 +227,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
|
|||||||
layerState._catalog.delete(noradId);
|
layerState._catalog.delete(noradId);
|
||||||
}
|
}
|
||||||
layerState._denseIds = [];
|
layerState._denseIds = [];
|
||||||
|
layerState._denseStale = false;
|
||||||
layerState._denseCursor = 0;
|
layerState._denseCursor = 0;
|
||||||
layerState._count = layerState._points.size;
|
layerState._count = layerState._points.size;
|
||||||
layerState._catalogRevision++;
|
layerState._catalogRevision++;
|
||||||
|
|||||||
Vendored
+5
-2
@@ -545,16 +545,19 @@ export function createControls({ state: layerState, services, parts, source }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getStats() {
|
getStats() {
|
||||||
|
const stale = Boolean(layerState._coreStale || layerState._denseStale);
|
||||||
return {
|
return {
|
||||||
count: layerState._count,
|
count: layerState._count,
|
||||||
lastUpdate: layerState._lastUpdate,
|
lastUpdate: layerState._lastUpdate,
|
||||||
stale: false,
|
stale,
|
||||||
status:
|
status:
|
||||||
layerState._lastError === 'CelesTrak unreachable'
|
layerState._lastError === 'CelesTrak unreachable'
|
||||||
? 'unavailable'
|
? 'unavailable'
|
||||||
: layerState._lastError
|
: layerState._lastError
|
||||||
? 'degraded'
|
? 'degraded'
|
||||||
: 'nominal',
|
: stale
|
||||||
|
? 'stale'
|
||||||
|
: 'nominal',
|
||||||
error: layerState._lastError,
|
error: layerState._lastError,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ export function createIngestion({
|
|||||||
if (!res.ok) return { ...groupDef, entries: [], ok: false };
|
if (!res.ok) return { ...groupDef, entries: [], ok: false };
|
||||||
const entries = parts.orbits.parseTLE(res.text);
|
const entries = parts.orbits.parseTLE(res.text);
|
||||||
updateSignal.throwIfAborted();
|
updateSignal.throwIfAborted();
|
||||||
return { ...groupDef, entries, ok: entries.length > 0 };
|
return {
|
||||||
|
...groupDef,
|
||||||
|
entries,
|
||||||
|
ok: entries.length > 0,
|
||||||
|
stale: Boolean(res.stale),
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (updateSignal.aborted || error?.name === 'AbortError')
|
if (updateSignal.aborted || error?.name === 'AbortError')
|
||||||
throw error;
|
throw error;
|
||||||
@@ -91,6 +96,7 @@ export function createIngestion({
|
|||||||
// catalog or those labels stay stale for the life of the session.
|
// catalog or those labels stay stale for the life of the session.
|
||||||
layerState._detectionObjects.clear();
|
layerState._detectionObjects.clear();
|
||||||
layerState._denseIds = [];
|
layerState._denseIds = [];
|
||||||
|
layerState._denseStale = false;
|
||||||
layerState._denseCursor = 0;
|
layerState._denseCursor = 0;
|
||||||
layerState._denseLoadController?.abort();
|
layerState._denseLoadController?.abort();
|
||||||
layerState._denseLoadController = null;
|
layerState._denseLoadController = null;
|
||||||
@@ -102,11 +108,13 @@ export function createIngestion({
|
|||||||
// Process all TLE entries in CATALOG_GROUPS order
|
// Process all TLE entries in CATALOG_GROUPS order
|
||||||
const allEntries = [];
|
const allEntries = [];
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
for (const e of r.entries) allEntries.push({ ...e, group: r.tag });
|
for (const e of r.entries)
|
||||||
|
allEntries.push({ ...e, group: r.tag, stale: r.stale });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate by NORAD ID — first (most specific) group tag wins
|
// Deduplicate by NORAD ID — first (most specific) group tag wins
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
let coreStale = false;
|
||||||
|
|
||||||
for (const entry of allEntries) {
|
for (const entry of allEntries) {
|
||||||
const satrec = twoline2satrec(entry.line1, entry.line2);
|
const satrec = twoline2satrec(entry.line1, entry.line2);
|
||||||
@@ -123,6 +131,8 @@ export function createIngestion({
|
|||||||
group: entry.group,
|
group: entry.group,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
coreStale ||= entry.stale;
|
||||||
|
|
||||||
// Propagate initial position
|
// Propagate initial position
|
||||||
const pos = parts.orbits.propagatePosition(satrec, now);
|
const pos = parts.orbits.propagatePosition(satrec, now);
|
||||||
if (!pos) continue;
|
if (!pos) continue;
|
||||||
@@ -157,6 +167,7 @@ export function createIngestion({
|
|||||||
parts.labels._syncIssOverlay();
|
parts.labels._syncIssOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layerState._coreStale = coreStale;
|
||||||
layerState._count = layerState._points.size;
|
layerState._count = layerState._points.size;
|
||||||
layerState._catalogRevision++;
|
layerState._catalogRevision++;
|
||||||
layerState._lastUpdate = Date.now();
|
layerState._lastUpdate = Date.now();
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export function createLifecycle({
|
|||||||
layerState._orbitPaths = new Map();
|
layerState._orbitPaths = new Map();
|
||||||
layerState._count = 0;
|
layerState._count = 0;
|
||||||
layerState._lastUpdate = null;
|
layerState._lastUpdate = null;
|
||||||
|
layerState._coreStale = false;
|
||||||
|
layerState._denseStale = false;
|
||||||
layerState._trackedNorad = null;
|
layerState._trackedNorad = null;
|
||||||
parts.tracking._cancelPendingTrackingRestore();
|
parts.tracking._cancelPendingTrackingRestore();
|
||||||
layerState._trackedEntity = null;
|
layerState._trackedEntity = null;
|
||||||
@@ -162,6 +164,8 @@ export function createLifecycle({
|
|||||||
layerState._rowControlsListener = null;
|
layerState._rowControlsListener = null;
|
||||||
layerState._count = 0;
|
layerState._count = 0;
|
||||||
layerState._lastUpdate = null;
|
layerState._lastUpdate = null;
|
||||||
|
layerState._coreStale = false;
|
||||||
|
layerState._denseStale = false;
|
||||||
layerState._lastError = null;
|
layerState._lastError = null;
|
||||||
layerState._lastFocusUpdate = 0;
|
layerState._lastFocusUpdate = 0;
|
||||||
layerState._activeFocusCount = 0;
|
layerState._activeFocusCount = 0;
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import http from 'node:http';
|
||||||
|
import { EventEmitter, once } from 'node:events';
|
||||||
|
import test from 'node:test';
|
||||||
|
import * as Cesium from 'cesium';
|
||||||
|
import { createSatelliteSource } from './source.js';
|
||||||
|
import { createState } from './state.js';
|
||||||
|
import { createControls } from './controls.js';
|
||||||
|
import { createOrbits } from './orbits.js';
|
||||||
|
import { createLabels } from './labels.js';
|
||||||
|
import { createTracking } from './tracking.js';
|
||||||
|
import { createRendering } from './rendering.js';
|
||||||
|
import { createCatalog } from './catalog.js';
|
||||||
|
import { createLifecycle } from './lifecycle.js';
|
||||||
|
import { createInteraction } from './interaction.js';
|
||||||
|
import { createIngestion } from './ingestion.js';
|
||||||
|
import { layerFeedState } from '../../data/feedState.js';
|
||||||
|
|
||||||
|
const GROUPS = [
|
||||||
|
'stations',
|
||||||
|
'visual',
|
||||||
|
'gps-ops',
|
||||||
|
'glo-ops',
|
||||||
|
'galileo',
|
||||||
|
'geo',
|
||||||
|
'starlink',
|
||||||
|
];
|
||||||
|
|
||||||
|
function tle(id, invalid = false) {
|
||||||
|
// Owned synthetic catalog IDs over one valid element set; never live requests.
|
||||||
|
const motion = invalid ? ' 0.00000000' : '15.48899044';
|
||||||
|
return `OWNED ORBIT FIXTURE ${id}\n1 ${id}U 98067A 26289.78971908 .00007803 00000+0 15012-3 0 9995\n2 ${id} 51.6326 58.3278 0006748 271.9059 88.1156 ${motion}590607`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fixture(t) {
|
||||||
|
const policy = {
|
||||||
|
stale: new Set(),
|
||||||
|
invalid: new Set(),
|
||||||
|
ids: {},
|
||||||
|
status: 200,
|
||||||
|
hold: false,
|
||||||
|
};
|
||||||
|
const held = new EventEmitter();
|
||||||
|
let heldCount = 0;
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const group = req.url.split('/').at(-1);
|
||||||
|
if (policy.hold) {
|
||||||
|
held.emit('request', ++heldCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeHead(policy.status, {
|
||||||
|
'Content-Type': 'text/plain',
|
||||||
|
'X-TLE-Cache': policy.stale.has(group) ? 'STALE-ERROR' : 'MISS',
|
||||||
|
});
|
||||||
|
res.end(
|
||||||
|
tle(
|
||||||
|
policy.ids[group] ?? 25545 + GROUPS.indexOf(group),
|
||||||
|
policy.invalid.has(group),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||||
|
const source = createSatelliteSource({
|
||||||
|
fetchImpl: (path, options) =>
|
||||||
|
fetch(`http://127.0.0.1:${server.address().port}${path}`, options),
|
||||||
|
});
|
||||||
|
// Application host services and native EventTargets; actual Cesium collections,
|
||||||
|
// input handler, events, SGP4, ingestion and lifecycle run without a WebGL scene.
|
||||||
|
const noop = () => {};
|
||||||
|
const services = {
|
||||||
|
overlays: {
|
||||||
|
setOverlayEntries: noop,
|
||||||
|
setOverlaySourceVisible: noop,
|
||||||
|
clearOverlaySource: noop,
|
||||||
|
},
|
||||||
|
layerState: { isExplicitLayerStateOrigin: () => false },
|
||||||
|
focus: {
|
||||||
|
clearFocusTarget: noop,
|
||||||
|
publishFocusTargetFromCachedPosition: noop,
|
||||||
|
getFocusTarget: () => null,
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
clearTrackedSubjectContext: noop,
|
||||||
|
getContextStore: () => ({ entities: new Map() }),
|
||||||
|
refreshTrackedSubjectContext: noop,
|
||||||
|
selectTrackedSubjectContext: noop,
|
||||||
|
},
|
||||||
|
readout: { refreshTrackedReadout: noop },
|
||||||
|
render: { holdContinuousRender: noop, releaseContinuousRender: noop },
|
||||||
|
picking: { registerPickOwner: noop, unregisterPickOwner: noop },
|
||||||
|
};
|
||||||
|
const previousDocument = globalThis.document;
|
||||||
|
globalThis.document = new EventTarget();
|
||||||
|
const state = createState({ services });
|
||||||
|
const parts = {};
|
||||||
|
const context = { state, services, parts, source };
|
||||||
|
for (const [name, factory] of Object.entries({
|
||||||
|
controls: createControls,
|
||||||
|
orbits: createOrbits,
|
||||||
|
labels: createLabels,
|
||||||
|
tracking: createTracking,
|
||||||
|
rendering: createRendering,
|
||||||
|
catalog: createCatalog,
|
||||||
|
interaction: createInteraction,
|
||||||
|
lifecycle: createLifecycle,
|
||||||
|
ingestion: createIngestion,
|
||||||
|
})) {
|
||||||
|
parts[name] = factory(context);
|
||||||
|
}
|
||||||
|
const viewer = {
|
||||||
|
scene: {
|
||||||
|
canvas: new EventTarget(),
|
||||||
|
primitives: new Cesium.PrimitiveCollection(),
|
||||||
|
preRender: new Cesium.Event(),
|
||||||
|
},
|
||||||
|
trackedEntityChanged: new Cesium.Event(),
|
||||||
|
};
|
||||||
|
await parts.lifecycle.methods.init(viewer);
|
||||||
|
t.after(async () => {
|
||||||
|
if (state._pointCollection) parts.lifecycle.methods.destroy(viewer);
|
||||||
|
viewer.scene.primitives.destroy();
|
||||||
|
if (previousDocument === undefined) delete globalThis.document;
|
||||||
|
else globalThis.document = previousDocument;
|
||||||
|
server.closeAllConnections();
|
||||||
|
await new Promise((resolve) => server.close(resolve));
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
parts,
|
||||||
|
viewer,
|
||||||
|
policy,
|
||||||
|
update: (options) => parts.ingestion.methods.update(viewer, options),
|
||||||
|
stats: () => parts.controls.methods.getStats(),
|
||||||
|
feed: () => layerFeedState(parts.controls.methods.getStats()),
|
||||||
|
async waitHeld(count) {
|
||||||
|
while (heldCount < count)
|
||||||
|
await once(held, 'request', { signal: AbortSignal.timeout(3000) });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('stale core records surface through actual HTTP, ingestion and feed state, then recover', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
f.policy.stale = new Set(GROUPS.slice(0, 6));
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.stats().stale, true);
|
||||||
|
assert.equal(f.feed(), 'stale');
|
||||||
|
f.policy.stale = new Set(['visual']);
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.feed(), 'stale');
|
||||||
|
f.policy.stale.clear();
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().stale, false);
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a stale duplicate cannot taint its earlier fresh winning record', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
f.policy.stale.add('visual');
|
||||||
|
f.policy.ids.visual = 25545;
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 5);
|
||||||
|
assert.equal(f.state._catalog.get(25545).group, 'stations');
|
||||||
|
assert.equal(f.stats().stale, false);
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
f.policy.stale = new Set(['stations']);
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 5);
|
||||||
|
assert.equal(
|
||||||
|
f.stats().stale,
|
||||||
|
true,
|
||||||
|
'a later fresh duplicate does not replace the stale winner',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid stale orbital elements are not admitted as provenance', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
f.policy.stale.add('visual');
|
||||||
|
f.policy.invalid.add('visual');
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 5);
|
||||||
|
assert.equal(f.state._catalog.has(25546), false);
|
||||||
|
assert.equal(f.stats().stale, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failed and aborted core refreshes keep provenance of retained records', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
f.policy.stale.add('stations');
|
||||||
|
await f.update();
|
||||||
|
const priorTime = f.stats().lastUpdate;
|
||||||
|
f.policy.status = 503;
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.stats().stale, true);
|
||||||
|
assert.equal(f.stats().status, 'unavailable');
|
||||||
|
assert.equal(f.stats().lastUpdate, priorTime);
|
||||||
|
f.policy.hold = true;
|
||||||
|
const controller = new AbortController();
|
||||||
|
const work = f.update({ signal: controller.signal });
|
||||||
|
await f.waitHeld(6);
|
||||||
|
controller.abort();
|
||||||
|
await assert.rejects(work, { name: 'AbortError' });
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.stats().stale, true);
|
||||||
|
assert.equal(f.stats().lastUpdate, priorTime);
|
||||||
|
assert.equal(f.state._activeUpdateControllers.size, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dense provenance belongs to its admitted records and clears on removal', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
await f.update();
|
||||||
|
f.policy.stale.add('starlink');
|
||||||
|
f.state._params.catalog = 'dense';
|
||||||
|
assert.equal((await f.parts.catalog._loadDenseCatalog()).status, 'ready');
|
||||||
|
assert.equal(f.stats().count, 7);
|
||||||
|
assert.equal(f.feed(), 'stale');
|
||||||
|
f.state._params.catalog = 'core';
|
||||||
|
f.parts.catalog._removeDenseCatalog();
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
f.policy.stale = new Set(['stations']);
|
||||||
|
await f.update();
|
||||||
|
f.state._params.catalog = 'dense';
|
||||||
|
await f.parts.catalog._loadDenseCatalog();
|
||||||
|
f.state._params.catalog = 'core';
|
||||||
|
f.parts.catalog._removeDenseCatalog();
|
||||||
|
assert.equal(
|
||||||
|
f.feed(),
|
||||||
|
'stale',
|
||||||
|
'removing dense data must not clear core provenance',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cancelling dense replacement preserves admitted stale extras until rebuild', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
await f.update();
|
||||||
|
f.policy.stale.add('starlink');
|
||||||
|
f.state._params.catalog = 'dense';
|
||||||
|
await f.parts.catalog._loadDenseCatalog();
|
||||||
|
f.policy.hold = true;
|
||||||
|
const controller = new AbortController();
|
||||||
|
const work = f.parts.catalog._loadDenseCatalog({ signal: controller.signal });
|
||||||
|
await f.waitHeld(1);
|
||||||
|
controller.abort();
|
||||||
|
assert.equal((await work).status, 'cancelled');
|
||||||
|
assert.equal(f.stats().count, 7);
|
||||||
|
assert.equal(f.feed(), 'stale');
|
||||||
|
f.policy.hold = false;
|
||||||
|
f.policy.stale.clear();
|
||||||
|
f.state._params.catalog = 'core';
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy aborts an in-flight response and initialization starts with new ownership', async (t) => {
|
||||||
|
const f = await fixture(t);
|
||||||
|
f.policy.stale.add('stations');
|
||||||
|
await f.update();
|
||||||
|
f.policy.hold = true;
|
||||||
|
const work = f.update();
|
||||||
|
await f.waitHeld(6);
|
||||||
|
const rejected = assert.rejects(work, { name: 'AbortError' });
|
||||||
|
f.parts.lifecycle.methods.destroy(f.viewer);
|
||||||
|
assert.equal(f.stats().count, 0);
|
||||||
|
assert.equal(f.stats().stale, false);
|
||||||
|
await f.parts.lifecycle.methods.init(f.viewer);
|
||||||
|
await rejected;
|
||||||
|
assert.equal(f.stats().stale, false);
|
||||||
|
f.policy.hold = false;
|
||||||
|
f.policy.stale.clear();
|
||||||
|
await f.update();
|
||||||
|
assert.equal(f.stats().count, 6);
|
||||||
|
assert.equal(f.feed(), 'nominal');
|
||||||
|
});
|
||||||
@@ -40,6 +40,10 @@ export function createState({ services }) {
|
|||||||
|
|
||||||
state._lastError = null;
|
state._lastError = null;
|
||||||
|
|
||||||
|
// Cache provenance belongs to admitted core and dense records separately.
|
||||||
|
state._coreStale = false;
|
||||||
|
state._denseStale = false;
|
||||||
|
|
||||||
state._activeUpdateControllers = new Set();
|
state._activeUpdateControllers = new Set();
|
||||||
|
|
||||||
state._denseLoadController = null;
|
state._denseLoadController = null;
|
||||||
|
|||||||
Reference in new issue
Block a user