@prometheus-ags/entity-graph-core 3.0.5 → 3.1.0
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/CHANGELOG.md +2 -0
- package/README.md +248 -0
- package/dist/chunk-BE3BMRKF.mjs +938 -0
- package/dist/chunk-BE3BMRKF.mjs.map +1 -0
- package/dist/devtools.cjs +2561 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +531 -0
- package/dist/devtools.d.ts +531 -0
- package/dist/devtools.mjs +2019 -0
- package/dist/devtools.mjs.map +1 -0
- package/dist/graph-ClW3HD8B.d.cts +990 -0
- package/dist/graph-ClW3HD8B.d.ts +990 -0
- package/dist/index.cjs +352 -322
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -1059
- package/dist/index.d.ts +60 -1059
- package/dist/index.mjs +45 -917
- package/dist/index.mjs.map +1 -1
- package/fixtures/devtools/README.md +37 -0
- package/fixtures/devtools/entity-inspection-v1.json +304 -0
- package/fixtures/devtools/time-travel-v1.json +110 -0
- package/package.json +25 -1
package/dist/index.cjs
CHANGED
|
@@ -1039,6 +1039,7 @@ function collectDirtyPaths(current, original, prefix = "", acc = /* @__PURE__ */
|
|
|
1039
1039
|
|
|
1040
1040
|
// src/merge/loro.ts
|
|
1041
1041
|
var ROOT_MAP = "entity";
|
|
1042
|
+
var LORO_MODULE_SPECIFIER = "loro-crdt";
|
|
1042
1043
|
var docs = /* @__PURE__ */ new Map();
|
|
1043
1044
|
function ek2(type, id) {
|
|
1044
1045
|
return `${type}:${id}`;
|
|
@@ -1048,7 +1049,7 @@ async function createLoroMergeStrategy(loadLoro) {
|
|
|
1048
1049
|
try {
|
|
1049
1050
|
const loaded = loadLoro ? await loadLoro() : await import(
|
|
1050
1051
|
/* @vite-ignore */
|
|
1051
|
-
|
|
1052
|
+
LORO_MODULE_SPECIFIER
|
|
1052
1053
|
);
|
|
1053
1054
|
if (typeof loaded.LoroDoc !== "function") {
|
|
1054
1055
|
throw new TypeError("loro-crdt did not export a LoroDoc constructor");
|
|
@@ -1150,270 +1151,24 @@ function applySelection(row, select) {
|
|
|
1150
1151
|
return picked;
|
|
1151
1152
|
}
|
|
1152
1153
|
|
|
1153
|
-
// src/
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
1159
|
-
}
|
|
1160
|
-
var inflightByStore = /* @__PURE__ */ new WeakMap();
|
|
1161
|
-
function inflightFor(store) {
|
|
1162
|
-
let inflight = inflightByStore.get(store);
|
|
1163
|
-
if (!inflight) {
|
|
1164
|
-
inflight = /* @__PURE__ */ new Map();
|
|
1165
|
-
inflightByStore.set(store, inflight);
|
|
1166
|
-
}
|
|
1167
|
-
return inflight;
|
|
1168
|
-
}
|
|
1169
|
-
function dedupe(key, fn, store = graphStore) {
|
|
1170
|
-
const inflight = inflightFor(store);
|
|
1171
|
-
if (inflight.has(key)) return inflight.get(key);
|
|
1172
|
-
const p = fn().finally(() => inflight.delete(key));
|
|
1173
|
-
inflight.set(key, p);
|
|
1174
|
-
return p;
|
|
1175
|
-
}
|
|
1176
|
-
var subscriberStatsListeners = /* @__PURE__ */ new Set();
|
|
1177
|
-
function emitSubscriberStatsChange() {
|
|
1178
|
-
for (const cb of subscriberStatsListeners) cb();
|
|
1179
|
-
}
|
|
1180
|
-
var subscribersByStore = /* @__PURE__ */ new WeakMap();
|
|
1181
|
-
function subscribersFor(store) {
|
|
1182
|
-
let subscribers = subscribersByStore.get(store);
|
|
1183
|
-
if (!subscribers) {
|
|
1184
|
-
subscribers = /* @__PURE__ */ new Map();
|
|
1185
|
-
subscribersByStore.set(store, subscribers);
|
|
1186
|
-
}
|
|
1187
|
-
return subscribers;
|
|
1188
|
-
}
|
|
1189
|
-
function registerSubscriber(key, store = graphStore) {
|
|
1190
|
-
const subscribers = subscribersFor(store);
|
|
1191
|
-
const token = Symbol(key);
|
|
1192
|
-
if (!subscribers.has(key)) subscribers.set(key, /* @__PURE__ */ new Set());
|
|
1193
|
-
subscribers.get(key).add(token);
|
|
1194
|
-
emitSubscriberStatsChange();
|
|
1195
|
-
return token;
|
|
1196
|
-
}
|
|
1197
|
-
function unregisterSubscriber(key, token, store = graphStore) {
|
|
1198
|
-
const subscribers = subscribersFor(store);
|
|
1199
|
-
const set = subscribers.get(key);
|
|
1200
|
-
if (!set) return;
|
|
1201
|
-
set.delete(token);
|
|
1202
|
-
if (set.size === 0) subscribers.delete(key);
|
|
1203
|
-
emitSubscriberStatsChange();
|
|
1204
|
-
}
|
|
1205
|
-
function hasSubscribers(key, store = graphStore) {
|
|
1206
|
-
return (subscribersFor(store).get(key)?.size ?? 0) > 0;
|
|
1207
|
-
}
|
|
1208
|
-
var DEFAULT_OPTIONS = {
|
|
1209
|
-
defaultStaleTime: 3e4,
|
|
1210
|
-
defaultGcTime: 3e5,
|
|
1211
|
-
gcInterval: 6e4,
|
|
1212
|
-
maxRetries: 3,
|
|
1213
|
-
retryBaseDelay: 1e3,
|
|
1214
|
-
revalidateOnFocus: true,
|
|
1215
|
-
revalidateOnReconnect: true
|
|
1216
|
-
};
|
|
1217
|
-
var engineOptions = { ...DEFAULT_OPTIONS };
|
|
1218
|
-
function subscribeSubscriberStats(onChange) {
|
|
1219
|
-
subscriberStatsListeners.add(onChange);
|
|
1220
|
-
return () => subscriberStatsListeners.delete(onChange);
|
|
1221
|
-
}
|
|
1222
|
-
function getActiveSubscriberCount(store = graphStore) {
|
|
1223
|
-
let n = 0;
|
|
1224
|
-
for (const set of subscribersFor(store).values()) n += set.size;
|
|
1225
|
-
return n;
|
|
1226
|
-
}
|
|
1227
|
-
var devtoolsSubscribers = /* @__PURE__ */ new Set();
|
|
1228
|
-
function subscribeDevtoolsEvent(cb) {
|
|
1229
|
-
devtoolsSubscribers.add(cb);
|
|
1230
|
-
return () => {
|
|
1231
|
-
devtoolsSubscribers.delete(cb);
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
function notifyDevtools(event) {
|
|
1235
|
-
if (devtoolsSubscribers.size === 0) return;
|
|
1236
|
-
const listeners2 = [...devtoolsSubscribers];
|
|
1237
|
-
for (const cb of listeners2) {
|
|
1154
|
+
// src/legacy-devtools.ts
|
|
1155
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1156
|
+
function publish(event) {
|
|
1157
|
+
if (listeners.size === 0) return;
|
|
1158
|
+
for (const listener of [...listeners]) {
|
|
1238
1159
|
try {
|
|
1239
|
-
|
|
1240
|
-
} catch
|
|
1241
|
-
console.warn("[engine] devtools subscriber threw
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
var gcIntervalsByStore = /* @__PURE__ */ new Map();
|
|
1246
|
-
function runGarbageCollection(storeApi = graphStore) {
|
|
1247
|
-
const store = storeApi.getState();
|
|
1248
|
-
const { defaultGcTime: gcTime } = getEngineOptions();
|
|
1249
|
-
const now = Date.now();
|
|
1250
|
-
const toRemove = [];
|
|
1251
|
-
for (const type of Object.keys(store.entities)) {
|
|
1252
|
-
const bucket = store.entities[type];
|
|
1253
|
-
if (!bucket) continue;
|
|
1254
|
-
for (const id of Object.keys(bucket)) {
|
|
1255
|
-
const key = `${type}:${id}`;
|
|
1256
|
-
if (hasSubscribers(key, storeApi)) continue;
|
|
1257
|
-
const patch = store.patches[type]?.[id];
|
|
1258
|
-
if (patch !== void 0 && Object.keys(patch).length > 0) continue;
|
|
1259
|
-
const entityState = store.entityStates[key];
|
|
1260
|
-
if (entityState?.isFetching) continue;
|
|
1261
|
-
const lastFetched = entityState?.lastFetched;
|
|
1262
|
-
if (lastFetched == null) continue;
|
|
1263
|
-
if (now - lastFetched <= gcTime) continue;
|
|
1264
|
-
toRemove.push({ type, id });
|
|
1160
|
+
listener(event);
|
|
1161
|
+
} catch {
|
|
1162
|
+
console.warn("[engine] devtools subscriber threw");
|
|
1265
1163
|
}
|
|
1266
1164
|
}
|
|
1267
|
-
for (const { type, id } of toRemove) {
|
|
1268
|
-
store.removeEntity(type, id);
|
|
1269
|
-
store.removeIdFromAllLists(type, id);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
function stopGarbageCollector(storeApi = graphStore) {
|
|
1273
|
-
const intervalId = gcIntervalsByStore.get(storeApi);
|
|
1274
|
-
if (intervalId != null && typeof clearInterval !== "undefined") {
|
|
1275
|
-
clearInterval(intervalId);
|
|
1276
|
-
gcIntervalsByStore.delete(storeApi);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
function startGarbageCollector(storeApi = graphStore) {
|
|
1280
|
-
stopGarbageCollector(storeApi);
|
|
1281
|
-
if (typeof window === "undefined" || typeof setInterval === "undefined") return () => {
|
|
1282
|
-
};
|
|
1283
|
-
const intervalId = setInterval(
|
|
1284
|
-
() => runGarbageCollection(storeApi),
|
|
1285
|
-
getEngineOptions().gcInterval
|
|
1286
|
-
);
|
|
1287
|
-
gcIntervalsByStore.set(storeApi, intervalId);
|
|
1288
|
-
return () => stopGarbageCollector(storeApi);
|
|
1289
|
-
}
|
|
1290
|
-
function restartGarbageCollector(storeApi = graphStore) {
|
|
1291
|
-
startGarbageCollector(storeApi);
|
|
1292
|
-
}
|
|
1293
|
-
function configureEngine(opts) {
|
|
1294
|
-
engineOptions = { ...DEFAULT_OPTIONS, ...opts };
|
|
1295
|
-
const activeStores = [...gcIntervalsByStore.keys()];
|
|
1296
|
-
if (activeStores.length === 0) restartGarbageCollector();
|
|
1297
|
-
else for (const storeApi of activeStores) restartGarbageCollector(storeApi);
|
|
1298
1165
|
}
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1166
|
+
function subscribeLegacyDevtoolsEvent(listener) {
|
|
1167
|
+
listeners.add(listener);
|
|
1168
|
+
return () => listeners.delete(listener);
|
|
1301
1169
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
if (!id) return;
|
|
1305
|
-
storeApi.getState().setEntityFetching(type, id, true);
|
|
1306
|
-
const attempt = async (retries) => {
|
|
1307
|
-
try {
|
|
1308
|
-
const raw = await fetch(id);
|
|
1309
|
-
const normalized = normalize(raw);
|
|
1310
|
-
const resolvedId = normalized[idField] ?? id;
|
|
1311
|
-
storeApi.getState().upsertEntity(type, resolvedId, normalized);
|
|
1312
|
-
storeApi.getState().setEntityFetched(type, resolvedId);
|
|
1313
|
-
if (sideEffects) sideEffects(raw, storeApi);
|
|
1314
|
-
opts.onSuccess?.(normalized);
|
|
1315
|
-
} catch (err) {
|
|
1316
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1317
|
-
if (retries < engineOpts.maxRetries) {
|
|
1318
|
-
await sleep(engineOpts.retryBaseDelay * Math.pow(2, retries));
|
|
1319
|
-
return attempt(retries + 1);
|
|
1320
|
-
}
|
|
1321
|
-
storeApi.getState().setEntityError(type, id, error.message);
|
|
1322
|
-
opts.onError?.(error);
|
|
1323
|
-
}
|
|
1324
|
-
};
|
|
1325
|
-
await dedupe(`${type}:${id}`, () => attempt(0), storeApi);
|
|
1326
|
-
}
|
|
1327
|
-
async function fetchList(opts, params, engineOpts, isLoadMore = false, storeApi = graphStore) {
|
|
1328
|
-
const { type, queryKey, fetch, normalize, sideEffects, mode = "replace" } = opts;
|
|
1329
|
-
const key = serializeKey(queryKey);
|
|
1330
|
-
const store = storeApi.getState();
|
|
1331
|
-
if (!fetch || !normalize) return;
|
|
1332
|
-
if (isLoadMore) store.setListFetchingMore(key, true);
|
|
1333
|
-
else store.setListFetching(key, true);
|
|
1334
|
-
const attempt = async (retries) => {
|
|
1335
|
-
try {
|
|
1336
|
-
const response = await fetch(params);
|
|
1337
|
-
const normalized = response.items.map(normalize);
|
|
1338
|
-
const meta = { total: response.total ?? null, nextCursor: response.nextCursor ?? null, prevCursor: response.prevCursor ?? null, hasNextPage: response.hasNextPage ?? !!response.nextCursor, hasPrevPage: response.hasPrevPage ?? !!response.prevCursor, currentPage: response.page ?? null, pageSize: response.pageSize ?? null };
|
|
1339
|
-
storeApi.getState().ingestFetchedList(
|
|
1340
|
-
type,
|
|
1341
|
-
normalized.map(({ id, data }) => ({ id, data })),
|
|
1342
|
-
{ lists: [{ key, mode: mode === "append" && isLoadMore ? "append" : "replace", meta }] }
|
|
1343
|
-
);
|
|
1344
|
-
if (sideEffects) sideEffects(response.items, storeApi);
|
|
1345
|
-
opts.onSuccess?.(response);
|
|
1346
|
-
} catch (err) {
|
|
1347
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1348
|
-
if (retries < engineOpts.maxRetries) {
|
|
1349
|
-
await sleep(engineOpts.retryBaseDelay * Math.pow(2, retries));
|
|
1350
|
-
return attempt(retries + 1);
|
|
1351
|
-
}
|
|
1352
|
-
storeApi.getState().setListError(key, error.message);
|
|
1353
|
-
opts.onError?.(error);
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
await dedupe(isLoadMore ? `${key}:more` : key, () => attempt(0), storeApi);
|
|
1357
|
-
}
|
|
1358
|
-
var globalListenerAttachments = /* @__PURE__ */ new WeakMap();
|
|
1359
|
-
function releaseGlobalListenerAttachment(storeApi, attachment) {
|
|
1360
|
-
let released = false;
|
|
1361
|
-
return () => {
|
|
1362
|
-
if (released) return;
|
|
1363
|
-
released = true;
|
|
1364
|
-
const current = globalListenerAttachments.get(storeApi);
|
|
1365
|
-
if (current !== attachment) return;
|
|
1366
|
-
current.references -= 1;
|
|
1367
|
-
if (current.references > 0) return;
|
|
1368
|
-
current.dispose();
|
|
1369
|
-
globalListenerAttachments.delete(storeApi);
|
|
1370
|
-
};
|
|
1371
|
-
}
|
|
1372
|
-
function attachGlobalListeners(storeApi = graphStore) {
|
|
1373
|
-
if (typeof window === "undefined") return () => {
|
|
1374
|
-
};
|
|
1375
|
-
const existing = globalListenerAttachments.get(storeApi);
|
|
1376
|
-
if (existing) {
|
|
1377
|
-
existing.references += 1;
|
|
1378
|
-
return releaseGlobalListenerAttachment(storeApi, existing);
|
|
1379
|
-
}
|
|
1380
|
-
const stopGc = startGarbageCollector(storeApi);
|
|
1381
|
-
const revalidateAll = () => {
|
|
1382
|
-
const state = storeApi.getState();
|
|
1383
|
-
for (const key of subscribersFor(storeApi).keys()) {
|
|
1384
|
-
if (!hasSubscribers(key, storeApi)) continue;
|
|
1385
|
-
const colonIdx = key.indexOf(":");
|
|
1386
|
-
if (colonIdx === -1) continue;
|
|
1387
|
-
const type = key.slice(0, colonIdx);
|
|
1388
|
-
const id = key.slice(colonIdx + 1);
|
|
1389
|
-
state.setEntityStale(type, id, true);
|
|
1390
|
-
}
|
|
1391
|
-
};
|
|
1392
|
-
const revalidateWhenVisible = () => {
|
|
1393
|
-
if (document.visibilityState === "visible") revalidateAll();
|
|
1394
|
-
};
|
|
1395
|
-
const revalidateOnFocus = engineOptions.revalidateOnFocus;
|
|
1396
|
-
const revalidateOnReconnect = engineOptions.revalidateOnReconnect;
|
|
1397
|
-
if (revalidateOnFocus) {
|
|
1398
|
-
window.addEventListener("visibilitychange", revalidateWhenVisible);
|
|
1399
|
-
window.addEventListener("focus", revalidateAll);
|
|
1400
|
-
}
|
|
1401
|
-
if (revalidateOnReconnect) window.addEventListener("online", revalidateAll);
|
|
1402
|
-
const attachment = {
|
|
1403
|
-
references: 1,
|
|
1404
|
-
dispose: () => {
|
|
1405
|
-
if (revalidateOnFocus) {
|
|
1406
|
-
window.removeEventListener("visibilitychange", revalidateWhenVisible);
|
|
1407
|
-
window.removeEventListener("focus", revalidateAll);
|
|
1408
|
-
}
|
|
1409
|
-
if (revalidateOnReconnect) {
|
|
1410
|
-
window.removeEventListener("online", revalidateAll);
|
|
1411
|
-
}
|
|
1412
|
-
stopGc();
|
|
1413
|
-
}
|
|
1414
|
-
};
|
|
1415
|
-
globalListenerAttachments.set(storeApi, attachment);
|
|
1416
|
-
return releaseGlobalListenerAttachment(storeApi, attachment);
|
|
1170
|
+
function emitLegacyDevtoolsEvent(event) {
|
|
1171
|
+
publish(event);
|
|
1417
1172
|
}
|
|
1418
1173
|
|
|
1419
1174
|
// src/graph-actions.ts
|
|
@@ -1426,7 +1181,7 @@ function createGraphTransaction(storeApi = graphStore) {
|
|
|
1426
1181
|
upsertEntity(type, id, data) {
|
|
1427
1182
|
storeApi.getState().upsertEntity(type, id, data);
|
|
1428
1183
|
if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
|
|
1429
|
-
|
|
1184
|
+
emitLegacyDevtoolsEvent({ kind: "upsert", type, id, data, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1430
1185
|
}
|
|
1431
1186
|
return tx;
|
|
1432
1187
|
},
|
|
@@ -1441,14 +1196,14 @@ function createGraphTransaction(storeApi = graphStore) {
|
|
|
1441
1196
|
patchEntity(type, id, patch) {
|
|
1442
1197
|
storeApi.getState().patchEntity(type, id, patch);
|
|
1443
1198
|
if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
|
|
1444
|
-
|
|
1199
|
+
emitLegacyDevtoolsEvent({ kind: "patch", type, id, patch, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1445
1200
|
}
|
|
1446
1201
|
return tx;
|
|
1447
1202
|
},
|
|
1448
1203
|
clearPatch(type, id) {
|
|
1449
1204
|
storeApi.getState().clearPatch(type, id);
|
|
1450
1205
|
if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
|
|
1451
|
-
|
|
1206
|
+
emitLegacyDevtoolsEvent({ kind: "clearPatch", type, id, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1452
1207
|
}
|
|
1453
1208
|
return tx;
|
|
1454
1209
|
},
|
|
@@ -1931,7 +1686,7 @@ function computeDelay(policy, attempt) {
|
|
|
1931
1686
|
return base / 2 + Math.random() * (base / 2);
|
|
1932
1687
|
}
|
|
1933
1688
|
}
|
|
1934
|
-
function
|
|
1689
|
+
function sleep(ms) {
|
|
1935
1690
|
if (ms <= 0) return Promise.resolve();
|
|
1936
1691
|
return new Promise((r) => setTimeout(r, ms));
|
|
1937
1692
|
}
|
|
@@ -1944,7 +1699,7 @@ async function replayActionWithRetry(action, policy, storeApi = graphStore) {
|
|
|
1944
1699
|
} catch (error) {
|
|
1945
1700
|
lastError = error;
|
|
1946
1701
|
if (attempt >= policy.maxAttempts) break;
|
|
1947
|
-
await
|
|
1702
|
+
await sleep(computeDelay(policy, attempt));
|
|
1948
1703
|
}
|
|
1949
1704
|
}
|
|
1950
1705
|
try {
|
|
@@ -1976,11 +1731,275 @@ function getDefaultOnlineSource() {
|
|
|
1976
1731
|
};
|
|
1977
1732
|
}
|
|
1978
1733
|
|
|
1734
|
+
// src/engine.ts
|
|
1735
|
+
function serializeKey(key) {
|
|
1736
|
+
return JSON.stringify(key, (_, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort()) : v);
|
|
1737
|
+
}
|
|
1738
|
+
function sleep2(ms) {
|
|
1739
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
1740
|
+
}
|
|
1741
|
+
var inflightByStore = /* @__PURE__ */ new WeakMap();
|
|
1742
|
+
function inflightFor(store) {
|
|
1743
|
+
let inflight = inflightByStore.get(store);
|
|
1744
|
+
if (!inflight) {
|
|
1745
|
+
inflight = /* @__PURE__ */ new Map();
|
|
1746
|
+
inflightByStore.set(store, inflight);
|
|
1747
|
+
}
|
|
1748
|
+
return inflight;
|
|
1749
|
+
}
|
|
1750
|
+
function dedupe(key, fn, store = graphStore) {
|
|
1751
|
+
const inflight = inflightFor(store);
|
|
1752
|
+
if (inflight.has(key)) return inflight.get(key);
|
|
1753
|
+
const p = fn().finally(() => inflight.delete(key));
|
|
1754
|
+
inflight.set(key, p);
|
|
1755
|
+
return p;
|
|
1756
|
+
}
|
|
1757
|
+
var globalSubscriberStatsListeners = /* @__PURE__ */ new Set();
|
|
1758
|
+
var subscriberStatsListenersByStore = /* @__PURE__ */ new WeakMap();
|
|
1759
|
+
function subscriberStatsListenersFor(store) {
|
|
1760
|
+
let listeners2 = subscriberStatsListenersByStore.get(store);
|
|
1761
|
+
if (!listeners2) {
|
|
1762
|
+
listeners2 = /* @__PURE__ */ new Set();
|
|
1763
|
+
subscriberStatsListenersByStore.set(store, listeners2);
|
|
1764
|
+
}
|
|
1765
|
+
return listeners2;
|
|
1766
|
+
}
|
|
1767
|
+
function emitSubscriberStatsChange(store) {
|
|
1768
|
+
for (const cb of /* @__PURE__ */ new Set([
|
|
1769
|
+
...globalSubscriberStatsListeners,
|
|
1770
|
+
...subscriberStatsListenersFor(store)
|
|
1771
|
+
])) cb();
|
|
1772
|
+
}
|
|
1773
|
+
var subscribersByStore = /* @__PURE__ */ new WeakMap();
|
|
1774
|
+
function subscribersFor(store) {
|
|
1775
|
+
let subscribers = subscribersByStore.get(store);
|
|
1776
|
+
if (!subscribers) {
|
|
1777
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
1778
|
+
subscribersByStore.set(store, subscribers);
|
|
1779
|
+
}
|
|
1780
|
+
return subscribers;
|
|
1781
|
+
}
|
|
1782
|
+
function registerSubscriber(key, store = graphStore) {
|
|
1783
|
+
const subscribers = subscribersFor(store);
|
|
1784
|
+
const token = Symbol(key);
|
|
1785
|
+
if (!subscribers.has(key)) subscribers.set(key, /* @__PURE__ */ new Set());
|
|
1786
|
+
subscribers.get(key).add(token);
|
|
1787
|
+
emitSubscriberStatsChange(store);
|
|
1788
|
+
return token;
|
|
1789
|
+
}
|
|
1790
|
+
function unregisterSubscriber(key, token, store = graphStore) {
|
|
1791
|
+
const subscribers = subscribersFor(store);
|
|
1792
|
+
const set = subscribers.get(key);
|
|
1793
|
+
if (!set) return;
|
|
1794
|
+
set.delete(token);
|
|
1795
|
+
if (set.size === 0) subscribers.delete(key);
|
|
1796
|
+
emitSubscriberStatsChange(store);
|
|
1797
|
+
}
|
|
1798
|
+
function hasSubscribers(key, store = graphStore) {
|
|
1799
|
+
return (subscribersFor(store).get(key)?.size ?? 0) > 0;
|
|
1800
|
+
}
|
|
1801
|
+
var DEFAULT_OPTIONS = {
|
|
1802
|
+
defaultStaleTime: 3e4,
|
|
1803
|
+
defaultGcTime: 3e5,
|
|
1804
|
+
gcInterval: 6e4,
|
|
1805
|
+
maxRetries: 3,
|
|
1806
|
+
retryBaseDelay: 1e3,
|
|
1807
|
+
revalidateOnFocus: true,
|
|
1808
|
+
revalidateOnReconnect: true
|
|
1809
|
+
};
|
|
1810
|
+
var engineOptions = { ...DEFAULT_OPTIONS };
|
|
1811
|
+
function subscribeSubscriberStats(onChange, store) {
|
|
1812
|
+
const listeners2 = store ? subscriberStatsListenersFor(store) : globalSubscriberStatsListeners;
|
|
1813
|
+
listeners2.add(onChange);
|
|
1814
|
+
return () => listeners2.delete(onChange);
|
|
1815
|
+
}
|
|
1816
|
+
function getActiveSubscriberCount(store = graphStore) {
|
|
1817
|
+
let n = 0;
|
|
1818
|
+
for (const set of subscribersFor(store).values()) n += set.size;
|
|
1819
|
+
return n;
|
|
1820
|
+
}
|
|
1821
|
+
function subscribeDevtoolsEvent(cb) {
|
|
1822
|
+
return subscribeLegacyDevtoolsEvent(cb);
|
|
1823
|
+
}
|
|
1824
|
+
var gcIntervalsByStore = /* @__PURE__ */ new Map();
|
|
1825
|
+
function runGarbageCollection(storeApi = graphStore) {
|
|
1826
|
+
const store = storeApi.getState();
|
|
1827
|
+
const { defaultGcTime: gcTime } = getEngineOptions();
|
|
1828
|
+
const now = Date.now();
|
|
1829
|
+
const toRemove = [];
|
|
1830
|
+
for (const type of Object.keys(store.entities)) {
|
|
1831
|
+
const bucket = store.entities[type];
|
|
1832
|
+
if (!bucket) continue;
|
|
1833
|
+
for (const id of Object.keys(bucket)) {
|
|
1834
|
+
const key = `${type}:${id}`;
|
|
1835
|
+
if (hasSubscribers(key, storeApi)) continue;
|
|
1836
|
+
const patch = store.patches[type]?.[id];
|
|
1837
|
+
if (patch !== void 0 && Object.keys(patch).length > 0) continue;
|
|
1838
|
+
const entityState = store.entityStates[key];
|
|
1839
|
+
if (entityState?.isFetching) continue;
|
|
1840
|
+
const lastFetched = entityState?.lastFetched;
|
|
1841
|
+
if (lastFetched == null) continue;
|
|
1842
|
+
if (now - lastFetched <= gcTime) continue;
|
|
1843
|
+
toRemove.push({ type, id });
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
for (const { type, id } of toRemove) {
|
|
1847
|
+
store.removeEntity(type, id);
|
|
1848
|
+
store.removeIdFromAllLists(type, id);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
function stopGarbageCollector(storeApi = graphStore) {
|
|
1852
|
+
const intervalId = gcIntervalsByStore.get(storeApi);
|
|
1853
|
+
if (intervalId != null && typeof clearInterval !== "undefined") {
|
|
1854
|
+
clearInterval(intervalId);
|
|
1855
|
+
gcIntervalsByStore.delete(storeApi);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function startGarbageCollector(storeApi = graphStore) {
|
|
1859
|
+
stopGarbageCollector(storeApi);
|
|
1860
|
+
if (typeof window === "undefined" || typeof setInterval === "undefined") return () => {
|
|
1861
|
+
};
|
|
1862
|
+
const intervalId = setInterval(
|
|
1863
|
+
() => runGarbageCollection(storeApi),
|
|
1864
|
+
getEngineOptions().gcInterval
|
|
1865
|
+
);
|
|
1866
|
+
gcIntervalsByStore.set(storeApi, intervalId);
|
|
1867
|
+
return () => stopGarbageCollector(storeApi);
|
|
1868
|
+
}
|
|
1869
|
+
function restartGarbageCollector(storeApi = graphStore) {
|
|
1870
|
+
startGarbageCollector(storeApi);
|
|
1871
|
+
}
|
|
1872
|
+
function configureEngine(opts) {
|
|
1873
|
+
engineOptions = { ...DEFAULT_OPTIONS, ...opts };
|
|
1874
|
+
const activeStores = [...gcIntervalsByStore.keys()];
|
|
1875
|
+
if (activeStores.length === 0) restartGarbageCollector();
|
|
1876
|
+
else for (const storeApi of activeStores) restartGarbageCollector(storeApi);
|
|
1877
|
+
}
|
|
1878
|
+
function getEngineOptions() {
|
|
1879
|
+
return engineOptions;
|
|
1880
|
+
}
|
|
1881
|
+
async function fetchEntity(opts, engineOpts, storeApi = graphStore) {
|
|
1882
|
+
const { type, id, fetch, normalize, sideEffects, idField = "id" } = opts;
|
|
1883
|
+
if (!id) return;
|
|
1884
|
+
storeApi.getState().setEntityFetching(type, id, true);
|
|
1885
|
+
const attempt = async (retries) => {
|
|
1886
|
+
try {
|
|
1887
|
+
const raw = await fetch(id);
|
|
1888
|
+
const normalized = normalize(raw);
|
|
1889
|
+
const resolvedId = normalized[idField] ?? id;
|
|
1890
|
+
storeApi.getState().upsertEntity(type, resolvedId, normalized);
|
|
1891
|
+
storeApi.getState().setEntityFetched(type, resolvedId);
|
|
1892
|
+
if (sideEffects) sideEffects(raw, storeApi);
|
|
1893
|
+
opts.onSuccess?.(normalized);
|
|
1894
|
+
} catch (err) {
|
|
1895
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1896
|
+
if (retries < engineOpts.maxRetries) {
|
|
1897
|
+
await sleep2(engineOpts.retryBaseDelay * Math.pow(2, retries));
|
|
1898
|
+
return attempt(retries + 1);
|
|
1899
|
+
}
|
|
1900
|
+
storeApi.getState().setEntityError(type, id, error.message);
|
|
1901
|
+
opts.onError?.(error);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
await dedupe(`${type}:${id}`, () => attempt(0), storeApi);
|
|
1905
|
+
}
|
|
1906
|
+
async function fetchList(opts, params, engineOpts, isLoadMore = false, storeApi = graphStore) {
|
|
1907
|
+
const { type, queryKey, fetch, normalize, sideEffects, mode = "replace" } = opts;
|
|
1908
|
+
const key = serializeKey(queryKey);
|
|
1909
|
+
const store = storeApi.getState();
|
|
1910
|
+
if (!fetch || !normalize) return;
|
|
1911
|
+
if (isLoadMore) store.setListFetchingMore(key, true);
|
|
1912
|
+
else store.setListFetching(key, true);
|
|
1913
|
+
const attempt = async (retries) => {
|
|
1914
|
+
try {
|
|
1915
|
+
const response = await fetch(params);
|
|
1916
|
+
const normalized = response.items.map(normalize);
|
|
1917
|
+
const meta = { total: response.total ?? null, nextCursor: response.nextCursor ?? null, prevCursor: response.prevCursor ?? null, hasNextPage: response.hasNextPage ?? !!response.nextCursor, hasPrevPage: response.hasPrevPage ?? !!response.prevCursor, currentPage: response.page ?? null, pageSize: response.pageSize ?? null };
|
|
1918
|
+
storeApi.getState().ingestFetchedList(
|
|
1919
|
+
type,
|
|
1920
|
+
normalized.map(({ id, data }) => ({ id, data })),
|
|
1921
|
+
{ lists: [{ key, mode: mode === "append" && isLoadMore ? "append" : "replace", meta }] }
|
|
1922
|
+
);
|
|
1923
|
+
if (sideEffects) sideEffects(response.items, storeApi);
|
|
1924
|
+
opts.onSuccess?.(response);
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1927
|
+
if (retries < engineOpts.maxRetries) {
|
|
1928
|
+
await sleep2(engineOpts.retryBaseDelay * Math.pow(2, retries));
|
|
1929
|
+
return attempt(retries + 1);
|
|
1930
|
+
}
|
|
1931
|
+
storeApi.getState().setListError(key, error.message);
|
|
1932
|
+
opts.onError?.(error);
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
await dedupe(isLoadMore ? `${key}:more` : key, () => attempt(0), storeApi);
|
|
1936
|
+
}
|
|
1937
|
+
var globalListenerAttachments = /* @__PURE__ */ new WeakMap();
|
|
1938
|
+
function releaseGlobalListenerAttachment(storeApi, attachment) {
|
|
1939
|
+
let released = false;
|
|
1940
|
+
return () => {
|
|
1941
|
+
if (released) return;
|
|
1942
|
+
released = true;
|
|
1943
|
+
const current = globalListenerAttachments.get(storeApi);
|
|
1944
|
+
if (current !== attachment) return;
|
|
1945
|
+
current.references -= 1;
|
|
1946
|
+
if (current.references > 0) return;
|
|
1947
|
+
current.dispose();
|
|
1948
|
+
globalListenerAttachments.delete(storeApi);
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
function attachGlobalListeners(storeApi = graphStore) {
|
|
1952
|
+
if (typeof window === "undefined") return () => {
|
|
1953
|
+
};
|
|
1954
|
+
const existing = globalListenerAttachments.get(storeApi);
|
|
1955
|
+
if (existing) {
|
|
1956
|
+
existing.references += 1;
|
|
1957
|
+
return releaseGlobalListenerAttachment(storeApi, existing);
|
|
1958
|
+
}
|
|
1959
|
+
const stopGc = startGarbageCollector(storeApi);
|
|
1960
|
+
const revalidateAll = () => {
|
|
1961
|
+
const state = storeApi.getState();
|
|
1962
|
+
for (const key of subscribersFor(storeApi).keys()) {
|
|
1963
|
+
if (!hasSubscribers(key, storeApi)) continue;
|
|
1964
|
+
const colonIdx = key.indexOf(":");
|
|
1965
|
+
if (colonIdx === -1) continue;
|
|
1966
|
+
const type = key.slice(0, colonIdx);
|
|
1967
|
+
const id = key.slice(colonIdx + 1);
|
|
1968
|
+
state.setEntityStale(type, id, true);
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
const revalidateWhenVisible = () => {
|
|
1972
|
+
if (document.visibilityState === "visible") revalidateAll();
|
|
1973
|
+
};
|
|
1974
|
+
const revalidateOnFocus = engineOptions.revalidateOnFocus;
|
|
1975
|
+
const revalidateOnReconnect = engineOptions.revalidateOnReconnect;
|
|
1976
|
+
if (revalidateOnFocus) {
|
|
1977
|
+
window.addEventListener("visibilitychange", revalidateWhenVisible);
|
|
1978
|
+
window.addEventListener("focus", revalidateAll);
|
|
1979
|
+
}
|
|
1980
|
+
if (revalidateOnReconnect) window.addEventListener("online", revalidateAll);
|
|
1981
|
+
const attachment = {
|
|
1982
|
+
references: 1,
|
|
1983
|
+
dispose: () => {
|
|
1984
|
+
if (revalidateOnFocus) {
|
|
1985
|
+
window.removeEventListener("visibilitychange", revalidateWhenVisible);
|
|
1986
|
+
window.removeEventListener("focus", revalidateAll);
|
|
1987
|
+
}
|
|
1988
|
+
if (revalidateOnReconnect) {
|
|
1989
|
+
window.removeEventListener("online", revalidateAll);
|
|
1990
|
+
}
|
|
1991
|
+
stopGc();
|
|
1992
|
+
}
|
|
1993
|
+
};
|
|
1994
|
+
globalListenerAttachments.set(storeApi, attachment);
|
|
1995
|
+
return releaseGlobalListenerAttachment(storeApi, attachment);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1979
1998
|
// src/devtools-event-bus.ts
|
|
1980
1999
|
var _busInjectMap = /* @__PURE__ */ new WeakMap();
|
|
1981
|
-
var
|
|
2000
|
+
var registries = /* @__PURE__ */ new Map();
|
|
1982
2001
|
function __resetStoreRegistry() {
|
|
1983
|
-
|
|
2002
|
+
registries.clear();
|
|
1984
2003
|
}
|
|
1985
2004
|
function createDevtoolsEventBus(opts) {
|
|
1986
2005
|
const bufferSize = opts?.bufferSize ?? 500;
|
|
@@ -2095,8 +2114,10 @@ function createDevtoolsEventBus(opts) {
|
|
|
2095
2114
|
pendingBurst = [];
|
|
2096
2115
|
dispatchedCount = 0;
|
|
2097
2116
|
flushScheduled = false;
|
|
2098
|
-
|
|
2099
|
-
|
|
2117
|
+
const registry = registries.get(bus);
|
|
2118
|
+
if (registry) {
|
|
2119
|
+
for (const entry of registry.values()) {
|
|
2120
|
+
if (!entry.active) continue;
|
|
2100
2121
|
entry.active = false;
|
|
2101
2122
|
try {
|
|
2102
2123
|
entry.unsubscribeSrc();
|
|
@@ -2104,12 +2125,19 @@ function createDevtoolsEventBus(opts) {
|
|
|
2104
2125
|
}
|
|
2105
2126
|
}
|
|
2106
2127
|
}
|
|
2128
|
+
registries.delete(bus);
|
|
2129
|
+
_busInjectMap.delete(bus);
|
|
2107
2130
|
}
|
|
2108
2131
|
};
|
|
2109
2132
|
_busInjectMap.set(bus, handleEngineEvent);
|
|
2110
2133
|
return bus;
|
|
2111
2134
|
}
|
|
2112
2135
|
function registerStore(bus, source, name) {
|
|
2136
|
+
let registry = registries.get(bus);
|
|
2137
|
+
if (!registry) {
|
|
2138
|
+
registry = /* @__PURE__ */ new Map();
|
|
2139
|
+
registries.set(bus, registry);
|
|
2140
|
+
}
|
|
2113
2141
|
if (registry.get(name)?.active === true) {
|
|
2114
2142
|
throw new Error(
|
|
2115
2143
|
`[devtools-store-registry] Store name "${name}" is already registered and active`
|
|
@@ -2130,75 +2158,77 @@ function registerStore(bus, source, name) {
|
|
|
2130
2158
|
unsubscribeSrc();
|
|
2131
2159
|
};
|
|
2132
2160
|
}
|
|
2133
|
-
function getRegisteredStores() {
|
|
2134
|
-
|
|
2161
|
+
function getRegisteredStores(bus) {
|
|
2162
|
+
const entries = bus ? [...registries.get(bus)?.values() ?? []] : [...registries.values()].flatMap((registry) => [...registry.values()]);
|
|
2163
|
+
return entries.map((entry) => ({ name: entry.name, active: entry.active }));
|
|
2135
2164
|
}
|
|
2136
2165
|
|
|
2137
|
-
// src/devtools-
|
|
2138
|
-
var
|
|
2139
|
-
var
|
|
2140
|
-
var
|
|
2141
|
-
var
|
|
2142
|
-
|
|
2143
|
-
function
|
|
2144
|
-
|
|
2166
|
+
// src/devtools-compatibility-bridge.ts
|
|
2167
|
+
var BRIDGE_KEY = /* @__PURE__ */ Symbol.for("prometheus.entity-graph.devtools.compatibility.v1");
|
|
2168
|
+
var bridgeHost = globalThis;
|
|
2169
|
+
var existingBridge = bridgeHost[BRIDGE_KEY];
|
|
2170
|
+
var bridgeState = existingBridge?.version === 1 ? existingBridge : { version: 1, entries: /* @__PURE__ */ new WeakMap(), factory: null };
|
|
2171
|
+
bridgeHost[BRIDGE_KEY] = bridgeState;
|
|
2172
|
+
function getOrCreateEntry(store) {
|
|
2173
|
+
let entry = bridgeState.entries.get(store);
|
|
2174
|
+
if (!entry) {
|
|
2175
|
+
entry = {
|
|
2176
|
+
delegate: null,
|
|
2177
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
2178
|
+
unsubscribeDelegate: null,
|
|
2179
|
+
compatibilityDetach: null
|
|
2180
|
+
};
|
|
2181
|
+
bridgeState.entries.set(store, entry);
|
|
2182
|
+
}
|
|
2183
|
+
return entry;
|
|
2145
2184
|
}
|
|
2146
|
-
function
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2185
|
+
function ensureDelegate(store, entry) {
|
|
2186
|
+
const factory = bridgeState.factory;
|
|
2187
|
+
if (entry.delegate || entry.compatibilityDetach || !factory) return;
|
|
2188
|
+
entry.compatibilityDetach = factory(store);
|
|
2189
|
+
}
|
|
2190
|
+
function getGraphDevtoolsCompatibilityDelegate(store) {
|
|
2191
|
+
const entry = getOrCreateEntry(store);
|
|
2192
|
+
ensureDelegate(store, entry);
|
|
2193
|
+
return entry.delegate;
|
|
2194
|
+
}
|
|
2195
|
+
function subscribeGraphDevtoolsCompatibility(store, listener) {
|
|
2196
|
+
const entry = getOrCreateEntry(store);
|
|
2197
|
+
entry.listeners.add(listener);
|
|
2198
|
+
ensureDelegate(store, entry);
|
|
2199
|
+
return () => {
|
|
2200
|
+
entry.listeners.delete(listener);
|
|
2201
|
+
if (entry.listeners.size === 0 && entry.delegate === null) bridgeState.entries.delete(store);
|
|
2153
2202
|
};
|
|
2154
2203
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2204
|
+
|
|
2205
|
+
// src/devtools-time-travel.ts
|
|
2206
|
+
function configureTimeTravel(opts, storeApi = graphStore) {
|
|
2207
|
+
if (opts.capacity && opts.capacity > 0) {
|
|
2208
|
+
getGraphDevtoolsCompatibilityDelegate(storeApi)?.configure(opts.capacity);
|
|
2209
|
+
}
|
|
2159
2210
|
}
|
|
2160
2211
|
function recordGraphSnapshot(label, storeApi = graphStore) {
|
|
2161
|
-
|
|
2162
|
-
const snap = {
|
|
2163
|
-
seq: seqCounter++,
|
|
2164
|
-
at: Date.now(),
|
|
2165
|
-
...label !== void 0 ? { label } : {},
|
|
2166
|
-
data: cloneSlice(s)
|
|
2167
|
-
};
|
|
2168
|
-
ring.push(snap);
|
|
2169
|
-
if (ring.length > capacity) ring = ring.slice(-capacity);
|
|
2170
|
-
cursor = null;
|
|
2171
|
-
notify();
|
|
2172
|
-
return snap.seq;
|
|
2212
|
+
return getGraphDevtoolsCompatibilityDelegate(storeApi)?.capture(label) ?? -1;
|
|
2173
2213
|
}
|
|
2174
2214
|
function restoreGraphSnapshot(index, storeApi = graphStore) {
|
|
2175
|
-
|
|
2176
|
-
if (!snap) return false;
|
|
2177
|
-
const clone2 = cloneSlice(snap.data);
|
|
2178
|
-
storeApi.setState(clone2);
|
|
2179
|
-
cursor = index;
|
|
2180
|
-
notify();
|
|
2181
|
-
return true;
|
|
2215
|
+
return getGraphDevtoolsCompatibilityDelegate(storeApi)?.restoreByIndex(index) ?? false;
|
|
2182
2216
|
}
|
|
2183
2217
|
function restoreGraphSnapshotBySeq(seq, storeApi = graphStore) {
|
|
2184
|
-
|
|
2185
|
-
return index === -1 ? false : restoreGraphSnapshot(index, storeApi);
|
|
2218
|
+
return getGraphDevtoolsCompatibilityDelegate(storeApi)?.restoreByCursor(seq) ?? false;
|
|
2186
2219
|
}
|
|
2187
2220
|
function stepTimeTravel(delta, storeApi = graphStore) {
|
|
2188
|
-
|
|
2189
|
-
const target = Math.max(0, Math.min(ring.length - 1, base + delta));
|
|
2190
|
-
return restoreGraphSnapshot(target, storeApi);
|
|
2221
|
+
return getGraphDevtoolsCompatibilityDelegate(storeApi)?.step(delta) ?? false;
|
|
2191
2222
|
}
|
|
2192
|
-
function getTimeTravelState() {
|
|
2193
|
-
return {
|
|
2194
|
-
snapshots:
|
|
2195
|
-
cursor,
|
|
2196
|
-
capacity
|
|
2223
|
+
function getTimeTravelState(storeApi = graphStore) {
|
|
2224
|
+
return getGraphDevtoolsCompatibilityDelegate(storeApi)?.getState() ?? {
|
|
2225
|
+
snapshots: [],
|
|
2226
|
+
cursor: null,
|
|
2227
|
+
capacity: 50
|
|
2197
2228
|
};
|
|
2198
2229
|
}
|
|
2199
|
-
function subscribeTimeTravel(cb) {
|
|
2200
|
-
|
|
2201
|
-
return () => listeners.delete(cb);
|
|
2230
|
+
function subscribeTimeTravel(cb, storeApi = graphStore) {
|
|
2231
|
+
return subscribeGraphDevtoolsCompatibility(storeApi, cb);
|
|
2202
2232
|
}
|
|
2203
2233
|
|
|
2204
2234
|
// src/view/incremental.ts
|