@fnndsc/salsa 3.5.1 → 3.6.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/README.md +3 -3
- package/dist/connect/index.d.ts +11 -0
- package/dist/connect/index.js +11 -0
- package/dist/connect/index.js.map +1 -1
- package/dist/dag/feedJoins.js +4 -1
- package/dist/dag/feedJoins.js.map +1 -1
- package/dist/feeds/index.d.ts +15 -2
- package/dist/feeds/index.js.map +1 -1
- package/dist/files/index.d.ts +7 -0
- package/dist/files/index.js +12 -11
- package/dist/files/index.js.map +1 -1
- package/dist/files/regular_content.js +11 -3
- package/dist/files/regular_content.js.map +1 -1
- package/dist/groups/index.d.ts +59 -0
- package/dist/groups/index.js +89 -0
- package/dist/groups/index.js.map +1 -1
- package/dist/groups/membershipRevision.d.ts +19 -0
- package/dist/groups/membershipRevision.js +29 -0
- package/dist/groups/membershipRevision.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs/jobOps.js +45 -2
- package/dist/jobs/jobOps.js.map +1 -1
- package/dist/pipelines/index.d.ts +28 -5
- package/dist/pipelines/index.js +110 -48
- package/dist/pipelines/index.js.map +1 -1
- package/dist/pipelines/invocation.d.ts +80 -0
- package/dist/pipelines/invocation.js +290 -0
- package/dist/pipelines/invocation.js.map +1 -0
- package/dist/pipelines/manifest.d.ts +102 -0
- package/dist/pipelines/manifest.js +204 -0
- package/dist/pipelines/manifest.js.map +1 -0
- package/dist/plugins/index.d.ts +29 -0
- package/dist/plugins/index.js +51 -10
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/internal/plugin_executeNewFeed.d.ts +0 -14
- package/dist/plugins/internal/plugin_executeNewFeed.js +3 -3
- package/dist/plugins/internal/plugin_executeNewFeed.js.map +1 -1
- package/dist/plugins/plugin_register.js +5 -5
- package/dist/plugins/plugin_register.js.map +1 -1
- package/dist/plugins/store_import.js +1 -1
- package/dist/plugins/store_import.js.map +1 -1
- package/dist/retrieve/watch.d.ts +114 -0
- package/dist/retrieve/watch.js +360 -0
- package/dist/retrieve/watch.js.map +1 -0
- package/dist/store/index.d.ts +17 -4
- package/dist/store/index.js +9 -4
- package/dist/store/index.js.map +1 -1
- package/dist/users/index.d.ts +36 -0
- package/dist/users/index.js +50 -0
- package/dist/users/index.js.map +1 -0
- package/dist/vfs/dispatcher.d.ts +11 -0
- package/dist/vfs/dispatcher.js +37 -6
- package/dist/vfs/dispatcher.js.map +1 -1
- package/dist/vfs/index.d.ts +1 -0
- package/dist/vfs/index.js +3 -0
- package/dist/vfs/index.js.map +1 -1
- package/dist/vfs/provider.d.ts +13 -0
- package/dist/vfs/providers/etc.d.ts +2 -0
- package/dist/vfs/providers/etc.js +41 -2
- package/dist/vfs/providers/etc.js.map +1 -1
- package/dist/vfs/providers/native.js +41 -7
- package/dist/vfs/providers/native.js.map +1 -1
- package/dist/vfs/providers/pacs.d.ts +13 -1
- package/dist/vfs/providers/pacs.js +86 -107
- package/dist/vfs/providers/pacs.js.map +1 -1
- package/dist/vfs/providers/pacsHelpers.js +9 -5
- package/dist/vfs/providers/pacsHelpers.js.map +1 -1
- package/dist/vfs/providers/proc.d.ts +34 -1
- package/dist/vfs/providers/proc.js +301 -62
- package/dist/vfs/providers/proc.js.map +1 -1
- package/package.json +11 -8
|
@@ -21,19 +21,23 @@ exports.procFeed_ensureLoaded = procFeed_ensureLoaded;
|
|
|
21
21
|
exports.feedMeta_ensure = feedMeta_ensure;
|
|
22
22
|
exports.feedStatus_refresh = feedStatus_refresh;
|
|
23
23
|
exports.procTopology_warmup = procTopology_warmup;
|
|
24
|
+
exports.procTopology_reconcileFeeds = procTopology_reconcileFeeds;
|
|
25
|
+
exports.procTopology_retry = procTopology_retry;
|
|
24
26
|
exports.procTopology_status = procTopology_status;
|
|
25
27
|
exports.procTopology_await = procTopology_await;
|
|
26
28
|
exports.procCache_refresh = procCache_refresh;
|
|
27
29
|
const cumin_1 = require("@fnndsc/cumin");
|
|
28
30
|
const index_js_1 = require("../../jobs/index.js");
|
|
29
31
|
/** Virtual filenames inside each instance directory. */
|
|
30
|
-
const INSTANCE_FILES = new Set(['status', 'params', 'log']);
|
|
32
|
+
const INSTANCE_FILES = new Set(['status', 'params', 'log', 'data']);
|
|
31
33
|
/** Virtual filenames inside each feed directory. */
|
|
32
34
|
const FEED_FILES = new Set(['status', 'title']);
|
|
33
35
|
const PROC_JOBS_PREFIX = '/proc/jobs';
|
|
34
36
|
const PAGE = 100;
|
|
35
37
|
let procTopologyPromise = null;
|
|
36
38
|
let procTopologyFailure;
|
|
39
|
+
let procTopologyResumeState = null;
|
|
40
|
+
let procTopologyScopedResumeFeedIDs = null;
|
|
37
41
|
/** Converts a CUBE feed row into the cache's feed model. */
|
|
38
42
|
function procFeed_create(feed) {
|
|
39
43
|
return {
|
|
@@ -51,7 +55,7 @@ function procFeed_create(feed) {
|
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
/** Indexes one complete paginated CUBE feed collection into the shared cache. */
|
|
54
|
-
async function procFeeds_index(
|
|
58
|
+
async function procFeeds_index(page_fetch, indexed) {
|
|
55
59
|
let offset = 0;
|
|
56
60
|
let total = 0;
|
|
57
61
|
const seenFeedIDs = new Set();
|
|
@@ -62,7 +66,8 @@ async function procFeeds_index(cache, page_fetch) {
|
|
|
62
66
|
total = page.totalCount;
|
|
63
67
|
}
|
|
64
68
|
for (const feed of chunk) {
|
|
65
|
-
|
|
69
|
+
const procFeed = procFeed_create(feed);
|
|
70
|
+
indexed.set(procFeed.id, procFeed);
|
|
66
71
|
seenFeedIDs.add(Number(feed.id));
|
|
67
72
|
}
|
|
68
73
|
if (chunk.length === 0 || (total > 0 && seenFeedIDs.size >= total))
|
|
@@ -79,18 +84,20 @@ async function procFeeds_index(cache, page_fetch) {
|
|
|
79
84
|
*/
|
|
80
85
|
async function procCache_build() {
|
|
81
86
|
const cache = (0, cumin_1.procCache_get)();
|
|
82
|
-
cache.cache_clear();
|
|
83
87
|
const client = await cumin_1.chrisConnection.client_get();
|
|
84
88
|
if (!client) {
|
|
85
89
|
cumin_1.errorStack.stack_push('error', 'procCache_build: not connected');
|
|
86
|
-
|
|
90
|
+
throw new Error('not connected');
|
|
87
91
|
}
|
|
88
92
|
const typedClient = client;
|
|
89
|
-
|
|
93
|
+
const indexed = new Map();
|
|
94
|
+
await procFeeds_index(typedClient.getFeeds.bind(typedClient), indexed);
|
|
90
95
|
if (typedClient.getPublicFeeds) {
|
|
91
|
-
await procFeeds_index(
|
|
96
|
+
await procFeeds_index(typedClient.getPublicFeeds.bind(typedClient), indexed);
|
|
92
97
|
}
|
|
98
|
+
const reconciliationTargets = cache.feeds_reconcile(Array.from(indexed.values()));
|
|
93
99
|
cache.built_set();
|
|
100
|
+
return reconciliationTargets;
|
|
94
101
|
}
|
|
95
102
|
/** Ensures the feed index is built, building it on first access. */
|
|
96
103
|
async function cache_ensure() {
|
|
@@ -106,7 +113,7 @@ async function feedInstances_load(feedID) {
|
|
|
106
113
|
const cache = (0, cumin_1.procCache_get)();
|
|
107
114
|
const client = await cumin_1.chrisConnection.client_get();
|
|
108
115
|
if (!client)
|
|
109
|
-
|
|
116
|
+
throw new Error('not connected');
|
|
110
117
|
const typedClient = client;
|
|
111
118
|
const instances = new Map();
|
|
112
119
|
let offset = 0;
|
|
@@ -138,6 +145,7 @@ async function feedInstances_load(feedID) {
|
|
|
138
145
|
pluginName: String(inst.plugin_name),
|
|
139
146
|
pluginType: inst.plugin_type !== undefined ? String(inst.plugin_type) : undefined,
|
|
140
147
|
params: null,
|
|
148
|
+
outputPath: outputPath_normalize(inst.output_path) ?? undefined,
|
|
141
149
|
status: String(inst.status ?? 'unknown'),
|
|
142
150
|
});
|
|
143
151
|
}
|
|
@@ -157,8 +165,12 @@ async function feedInstances_ensureLoaded(feedID) {
|
|
|
157
165
|
return inflight;
|
|
158
166
|
const promise = feedInstances_load(feedID);
|
|
159
167
|
cache.loading_set(feedID, promise);
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
try {
|
|
169
|
+
await promise;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
cache.loading_clear(feedID);
|
|
173
|
+
}
|
|
162
174
|
}
|
|
163
175
|
// ── Aggregate status ───────────────────────────────────────────────────────
|
|
164
176
|
/** Derives aggregate feed status from stored job counters — no API call. */
|
|
@@ -218,6 +230,79 @@ function params_render(inst) {
|
|
|
218
230
|
.map(([k, v]) => `${k}=${String(v)}`)
|
|
219
231
|
.join('\n');
|
|
220
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Normalizes CUBE's output-path spelling into an absolute CFS path.
|
|
235
|
+
*
|
|
236
|
+
* @param value - Raw `output_path` value from a plugin-instance resource.
|
|
237
|
+
* @returns Absolute CFS path, or null when no usable path was supplied.
|
|
238
|
+
*/
|
|
239
|
+
function outputPath_normalize(value) {
|
|
240
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
241
|
+
return null;
|
|
242
|
+
const clean = value.trim();
|
|
243
|
+
return clean.startsWith('/') ? clean : `/${clean}`;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Resolves and memoizes the authoritative CFS output directory for one job.
|
|
247
|
+
*
|
|
248
|
+
* @param instanceID - Plugin-instance ID whose output path is needed.
|
|
249
|
+
* @returns Absolute output path, or null when CUBE has not recorded one.
|
|
250
|
+
*/
|
|
251
|
+
async function instanceOutputPath_ensure(instanceID) {
|
|
252
|
+
const cache = (0, cumin_1.procCache_get)();
|
|
253
|
+
const inst = cache.instance_get(instanceID);
|
|
254
|
+
if (!inst)
|
|
255
|
+
return null;
|
|
256
|
+
if (inst.outputPath !== undefined)
|
|
257
|
+
return inst.outputPath;
|
|
258
|
+
const client = await cumin_1.chrisConnection.client_get();
|
|
259
|
+
if (!client)
|
|
260
|
+
return null;
|
|
261
|
+
try {
|
|
262
|
+
const resource = await client
|
|
263
|
+
.getPluginInstance(instanceID);
|
|
264
|
+
const outputPath = outputPath_normalize(resource?.data?.['output_path']);
|
|
265
|
+
cache.outputPath_update(instanceID, outputPath);
|
|
266
|
+
return outputPath;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
// "No CFS output path" downstream must not mask a failed fetch.
|
|
270
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
271
|
+
cumin_1.errorStack.stack_push("warning", `Could not fetch output path for instance ${instanceID}: ${msg}`);
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Reads every page of effective parameter values from one plugin instance.
|
|
277
|
+
*
|
|
278
|
+
* @param resource - Detail resource for the instance being inspected.
|
|
279
|
+
* @returns Parameter names mapped to their recorded invocation values.
|
|
280
|
+
*/
|
|
281
|
+
async function instanceParams_fetch(resource) {
|
|
282
|
+
const params = {};
|
|
283
|
+
let offset = 0;
|
|
284
|
+
let total = 0;
|
|
285
|
+
let fetched = 0;
|
|
286
|
+
while (true) {
|
|
287
|
+
const page = await resource.getParameters({ limit: PAGE, offset });
|
|
288
|
+
const items = page.data
|
|
289
|
+
?? (page.getItems ? page.getItems().map((item) => item.data) : []);
|
|
290
|
+
if (offset === 0 && typeof page.totalCount === 'number' && page.totalCount >= 0) {
|
|
291
|
+
total = page.totalCount;
|
|
292
|
+
}
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
if (item.param_name)
|
|
295
|
+
params[item.param_name] = item.value;
|
|
296
|
+
}
|
|
297
|
+
fetched += items.length;
|
|
298
|
+
if (items.length === 0 || (total > 0 && fetched >= total))
|
|
299
|
+
break;
|
|
300
|
+
if (total === 0 && items.length < PAGE)
|
|
301
|
+
break;
|
|
302
|
+
offset += items.length;
|
|
303
|
+
}
|
|
304
|
+
return params;
|
|
305
|
+
}
|
|
221
306
|
/** Collects all instance IDs for a feed recursively. */
|
|
222
307
|
function getAllInstanceIDs_forFeed(feedID, cache) {
|
|
223
308
|
const result = [];
|
|
@@ -235,6 +320,28 @@ function getAllInstanceIDs_forFeed(feedID, cache) {
|
|
|
235
320
|
*/
|
|
236
321
|
class ProcVfsProvider {
|
|
237
322
|
prefix = PROC_JOBS_PREFIX;
|
|
323
|
+
/**
|
|
324
|
+
* Resolves the synthetic `data` link for one job only when navigation asks
|
|
325
|
+
* to follow it. This keeps `/proc` topology listings and recursive trees
|
|
326
|
+
* entirely cache-backed.
|
|
327
|
+
*
|
|
328
|
+
* @param pathStr - Absolute `/proc/jobs/.../data` path to resolve.
|
|
329
|
+
* @returns The authoritative CFS output path, or an error when absent.
|
|
330
|
+
*/
|
|
331
|
+
async linkTarget_resolve(pathStr) {
|
|
332
|
+
await cache_ensure();
|
|
333
|
+
const clean = pathStr.replace(/\/$/, '');
|
|
334
|
+
const { feedID, instanceID, virtualFile } = procPath_parse(clean);
|
|
335
|
+
if (feedID === null || instanceID === null || virtualFile !== 'data') {
|
|
336
|
+
cumin_1.errorStack.stack_push('error', `Not a /proc job data link: ${pathStr}`);
|
|
337
|
+
return (0, cumin_1.Err)();
|
|
338
|
+
}
|
|
339
|
+
const outputPath = await instanceOutputPath_ensure(instanceID);
|
|
340
|
+
if (outputPath)
|
|
341
|
+
return (0, cumin_1.Ok)(outputPath);
|
|
342
|
+
cumin_1.errorStack.stack_push('error', `No CFS output path is available for job ${instanceID}`);
|
|
343
|
+
return (0, cumin_1.Err)();
|
|
344
|
+
}
|
|
238
345
|
async list(pathStr, _options) {
|
|
239
346
|
await cache_ensure();
|
|
240
347
|
const cache = (0, cumin_1.procCache_get)();
|
|
@@ -304,6 +411,15 @@ class ProcVfsProvider {
|
|
|
304
411
|
items.push({ name: 'status', type: 'file', size: 0, owner: '', date: '' });
|
|
305
412
|
items.push({ name: 'params', type: 'file', size: 0, owner: '', date: '' });
|
|
306
413
|
items.push({ name: 'log', type: 'file', size: 0, owner: '', date: '' });
|
|
414
|
+
// `data` is structural: list it without fetching the per-instance detail
|
|
415
|
+
// endpoint. Its target is resolved only when `cd data` follows the link.
|
|
416
|
+
// A known null means CUBE has no output directory, so omit it entirely.
|
|
417
|
+
if (inst.outputPath !== null) {
|
|
418
|
+
const dataLink = { name: 'data', type: 'link', size: 0, owner: '', date: '' };
|
|
419
|
+
if (inst.outputPath)
|
|
420
|
+
dataLink.target = inst.outputPath;
|
|
421
|
+
items.push(dataLink);
|
|
422
|
+
}
|
|
307
423
|
for (const childID of childIDs) {
|
|
308
424
|
const child = cache.instance_get(childID);
|
|
309
425
|
if (!child)
|
|
@@ -354,22 +470,21 @@ class ProcVfsProvider {
|
|
|
354
470
|
}
|
|
355
471
|
if (virtualFile === 'params') {
|
|
356
472
|
if (inst.params === null) {
|
|
357
|
-
//
|
|
473
|
+
// CUBE stores the effective run values in the parameter sub-resource;
|
|
474
|
+
// the instance detail payload itself is operational metadata.
|
|
358
475
|
const client = await cumin_1.chrisConnection.client_get();
|
|
359
476
|
if (client) {
|
|
360
477
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
478
|
+
const resource = await client
|
|
479
|
+
.getPluginInstance(instanceID);
|
|
480
|
+
if (resource)
|
|
481
|
+
cache.params_update(instanceID, await instanceParams_fetch(resource));
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Deliberate absorption, adjudicated 2026-08: parameters are a
|
|
485
|
+
// lazy display enrichment; a failed fetch renders as
|
|
486
|
+
// "(no parameters)" and the next read retries.
|
|
371
487
|
}
|
|
372
|
-
catch { /* leave null */ }
|
|
373
488
|
}
|
|
374
489
|
}
|
|
375
490
|
return (0, cumin_1.Ok)(params_render(inst));
|
|
@@ -389,12 +504,21 @@ class ProcVfsProvider {
|
|
|
389
504
|
if (feedID !== null && instanceID === null) {
|
|
390
505
|
const allIDs = getAllInstanceIDs_forFeed(feedID, cache);
|
|
391
506
|
const statusMap = await (0, index_js_1.jobs_statusBatch)(allIDs);
|
|
507
|
+
// Every cancel Result is checked: reporting success while jobs kept
|
|
508
|
+
// running would leave the user believing the feed's work was stopped.
|
|
509
|
+
const failedCancels = [];
|
|
392
510
|
await Promise.all(allIDs.map(async (id) => {
|
|
393
511
|
const s = statusMap.get(id) ?? '';
|
|
394
512
|
if (!(0, cumin_1.status_isTerminal)(s)) {
|
|
395
|
-
await (0, index_js_1.job_cancel)(id);
|
|
513
|
+
const cancelResult = await (0, index_js_1.job_cancel)(id);
|
|
514
|
+
if (!cancelResult.ok)
|
|
515
|
+
failedCancels.push(id);
|
|
396
516
|
}
|
|
397
517
|
}));
|
|
518
|
+
if (failedCancels.length > 0) {
|
|
519
|
+
cumin_1.errorStack.stack_push('error', `rm: failed to cancel ${failedCancels.length} running job(s) in feed ${feedID}: instance(s) ${failedCancels.sort((a, b) => a - b).join(', ')}`);
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
398
522
|
cache.feed_remove(feedID);
|
|
399
523
|
return true;
|
|
400
524
|
}
|
|
@@ -464,7 +588,7 @@ async function feedMeta_ensure(feedID) {
|
|
|
464
588
|
return;
|
|
465
589
|
const client = await cumin_1.chrisConnection.client_get();
|
|
466
590
|
if (!client)
|
|
467
|
-
|
|
591
|
+
throw new Error('not connected');
|
|
468
592
|
const typedClient = client;
|
|
469
593
|
const page = await typedClient.getFeeds({ id: feedID, limit: 1, offset: 0 });
|
|
470
594
|
const f = (page.data ?? [])[0];
|
|
@@ -505,44 +629,42 @@ async function feedStatus_refresh(feedID) {
|
|
|
505
629
|
* Background topology warm-up — single paginated sweep of all plugin instances
|
|
506
630
|
* across all feeds. Replaces the per-feed fan-out that was O(feeds) round trips.
|
|
507
631
|
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
632
|
+
* Scoped per-feed loads may run concurrently for navigation; the completed
|
|
633
|
+
* global sweep reconciles the cache to its authoritative instance set.
|
|
634
|
+
*
|
|
635
|
+
* @param state - Private pagination and reconciliation state for this sweep.
|
|
636
|
+
* @returns A promise that settles when the complete topology has been reconciled.
|
|
511
637
|
*/
|
|
512
|
-
async function procTopology_run() {
|
|
638
|
+
async function procTopology_run(state) {
|
|
513
639
|
await cache_ensure();
|
|
514
640
|
const cache = (0, cumin_1.procCache_get)();
|
|
641
|
+
cache.lifecycle_set('reconciling');
|
|
642
|
+
if (state.total > 0) {
|
|
643
|
+
cache.warmup_progress(state.fetchedInstanceIDs.size, state.total);
|
|
644
|
+
}
|
|
515
645
|
const client = await cumin_1.chrisConnection.client_get();
|
|
516
646
|
if (!client)
|
|
517
|
-
|
|
647
|
+
throw new Error('not connected');
|
|
518
648
|
const typedClient = client;
|
|
519
|
-
let offset = 0;
|
|
520
|
-
let total = 0;
|
|
521
|
-
const seenInstanceIDs = new Set();
|
|
522
|
-
const seenFeedIDs = new Set();
|
|
523
649
|
while (true) {
|
|
524
650
|
const page = await typedClient.getPluginInstances({
|
|
525
|
-
limit: PAGE, offset,
|
|
651
|
+
limit: PAGE, offset: state.offset,
|
|
526
652
|
});
|
|
527
653
|
const chunk = page.data ?? [];
|
|
528
|
-
if (offset === 0 && typeof page.totalCount === 'number' && page.totalCount >= 0) {
|
|
529
|
-
total = page.totalCount;
|
|
530
|
-
if (total > 0)
|
|
531
|
-
cache.warmup_progress(0, total);
|
|
654
|
+
if (state.offset === 0 && typeof page.totalCount === 'number' && page.totalCount >= 0) {
|
|
655
|
+
state.total = page.totalCount;
|
|
656
|
+
if (state.total > 0)
|
|
657
|
+
cache.warmup_progress(0, state.total);
|
|
532
658
|
}
|
|
533
659
|
for (const inst of chunk) {
|
|
534
660
|
const instanceID = Number(inst.id);
|
|
535
661
|
const feedID = Number(inst.feed_id);
|
|
662
|
+
state.fetchedInstanceIDs.add(instanceID);
|
|
536
663
|
const prevID = (inst.previous_id !== null && inst.previous_id !== undefined)
|
|
537
664
|
? Number(inst.previous_id)
|
|
538
665
|
: null;
|
|
539
|
-
if (!cache.feed_get(feedID))
|
|
540
|
-
|
|
541
|
-
id: feedID, title: `feed_${feedID}`, ownerUsername: '', public: false, creationDate: '',
|
|
542
|
-
finishedJobs: 0, erroredJobs: 0, startedJobs: 0,
|
|
543
|
-
scheduledJobs: 0, cancelledJobs: 0, createdJobs: 0,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
666
|
+
if (!cache.feed_get(feedID))
|
|
667
|
+
continue;
|
|
546
668
|
cache.instance_add({
|
|
547
669
|
id: instanceID,
|
|
548
670
|
feedID,
|
|
@@ -550,36 +672,53 @@ async function procTopology_run() {
|
|
|
550
672
|
pluginName: String(inst.plugin_name),
|
|
551
673
|
pluginType: inst.plugin_type !== undefined ? String(inst.plugin_type) : undefined,
|
|
552
674
|
params: null,
|
|
675
|
+
outputPath: outputPath_normalize(inst.output_path) ?? undefined,
|
|
553
676
|
status: String(inst.status ?? 'unknown'),
|
|
554
677
|
});
|
|
555
|
-
seenInstanceIDs.add(instanceID);
|
|
556
|
-
seenFeedIDs.add(feedID);
|
|
678
|
+
state.seenInstanceIDs.add(instanceID);
|
|
679
|
+
state.seenFeedIDs.add(feedID);
|
|
557
680
|
}
|
|
558
|
-
if (total > 0)
|
|
559
|
-
cache.warmup_progress(
|
|
560
|
-
if (chunk.length === 0 || (total > 0 &&
|
|
681
|
+
if (state.total > 0)
|
|
682
|
+
cache.warmup_progress(state.fetchedInstanceIDs.size, state.total);
|
|
683
|
+
if (chunk.length === 0 || (state.total > 0 && state.fetchedInstanceIDs.size >= state.total))
|
|
561
684
|
break;
|
|
562
|
-
if (total === 0 && chunk.length < PAGE)
|
|
685
|
+
if (state.total === 0 && chunk.length < PAGE)
|
|
563
686
|
break;
|
|
564
|
-
offset += chunk.length;
|
|
687
|
+
state.offset += chunk.length;
|
|
565
688
|
}
|
|
566
|
-
|
|
689
|
+
cache.topology_reconcile(state.seenInstanceIDs);
|
|
690
|
+
for (const feedID of state.seenFeedIDs)
|
|
567
691
|
cache.topologyLoaded_mark(feedID);
|
|
692
|
+
if (state.total === 0) {
|
|
693
|
+
cache.warmup_progress(state.fetchedInstanceIDs.size, state.fetchedInstanceIDs.size);
|
|
568
694
|
}
|
|
569
|
-
if (total === 0)
|
|
570
|
-
cache.warmup_progress(seenInstanceIDs.size, seenInstanceIDs.size);
|
|
571
695
|
cache.warmup_complete();
|
|
572
696
|
}
|
|
573
697
|
/**
|
|
574
|
-
*
|
|
698
|
+
* Creates an empty continuation for a fresh global topology sweep.
|
|
575
699
|
*
|
|
576
|
-
* @returns
|
|
700
|
+
* @returns New private pagination and reconciliation state.
|
|
577
701
|
*/
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
702
|
+
function procTopologyState_create() {
|
|
703
|
+
return {
|
|
704
|
+
offset: 0,
|
|
705
|
+
total: 0,
|
|
706
|
+
fetchedInstanceIDs: new Set(),
|
|
707
|
+
seenInstanceIDs: new Set(),
|
|
708
|
+
seenFeedIDs: new Set(),
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Starts one topology sweep from the supplied private continuation state.
|
|
713
|
+
*
|
|
714
|
+
* @param state - Private pagination and reconciliation state to run or resume.
|
|
715
|
+
* @returns The in-flight topology sweep promise.
|
|
716
|
+
*/
|
|
717
|
+
function procTopology_start(state) {
|
|
581
718
|
procTopologyFailure = undefined;
|
|
582
|
-
|
|
719
|
+
procTopologyResumeState = state;
|
|
720
|
+
procTopologyScopedResumeFeedIDs = null;
|
|
721
|
+
const sweep = procTopology_run(state);
|
|
583
722
|
procTopologyPromise = sweep;
|
|
584
723
|
sweep.then(() => {
|
|
585
724
|
if (procTopologyPromise !== sweep)
|
|
@@ -587,6 +726,7 @@ function procTopology_warmup() {
|
|
|
587
726
|
procTopologyPromise = null;
|
|
588
727
|
if ((0, cumin_1.procCache_get)().warmupComplete) {
|
|
589
728
|
procTopologyFailure = undefined;
|
|
729
|
+
procTopologyResumeState = null;
|
|
590
730
|
}
|
|
591
731
|
else {
|
|
592
732
|
procTopologyFailure = 'the topology sweep ended before the index completed';
|
|
@@ -600,6 +740,94 @@ function procTopology_warmup() {
|
|
|
600
740
|
});
|
|
601
741
|
return sweep;
|
|
602
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Runs a feed-scoped topology reconciliation selected after checkpoint restore.
|
|
745
|
+
*
|
|
746
|
+
* @param feedIDs - Canonical feed IDs whose topology needs a fresh load.
|
|
747
|
+
* @returns A promise that settles after the supplied feeds are reconciled.
|
|
748
|
+
*/
|
|
749
|
+
async function procTopologyScoped_run(feedIDs) {
|
|
750
|
+
const cache = (0, cumin_1.procCache_get)();
|
|
751
|
+
cache.lifecycle_set('reconciling');
|
|
752
|
+
for (const feedID of feedIDs)
|
|
753
|
+
await procCache_refresh(feedID);
|
|
754
|
+
cache.warmup_complete();
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Starts one scoped reconciliation and retains its targets for `proc retry`.
|
|
758
|
+
*
|
|
759
|
+
* @param feedIDs - Canonical feed IDs selected by the fresh feed index.
|
|
760
|
+
* @returns The in-flight scoped reconciliation promise.
|
|
761
|
+
*/
|
|
762
|
+
function procTopologyScoped_start(feedIDs) {
|
|
763
|
+
const targets = [...feedIDs];
|
|
764
|
+
procTopologyFailure = undefined;
|
|
765
|
+
procTopologyResumeState = null;
|
|
766
|
+
procTopologyScopedResumeFeedIDs = targets;
|
|
767
|
+
const sweep = procTopologyScoped_run(targets);
|
|
768
|
+
procTopologyPromise = sweep;
|
|
769
|
+
sweep.then(() => {
|
|
770
|
+
if (procTopologyPromise !== sweep)
|
|
771
|
+
return;
|
|
772
|
+
procTopologyPromise = null;
|
|
773
|
+
if ((0, cumin_1.procCache_get)().warmupComplete) {
|
|
774
|
+
procTopologyFailure = undefined;
|
|
775
|
+
procTopologyScopedResumeFeedIDs = null;
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
procTopologyFailure = 'the scoped topology reconciliation ended before the index completed';
|
|
779
|
+
}
|
|
780
|
+
}, (error) => {
|
|
781
|
+
if (procTopologyPromise !== sweep)
|
|
782
|
+
return;
|
|
783
|
+
procTopologyPromise = null;
|
|
784
|
+
(0, cumin_1.procCache_get)().warmup_abort();
|
|
785
|
+
procTopologyFailure = error instanceof Error ? error.message : String(error);
|
|
786
|
+
});
|
|
787
|
+
return sweep;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Starts or joins the session's global plugin-instance topology sweep.
|
|
791
|
+
*
|
|
792
|
+
* @returns The single in-flight sweep promise.
|
|
793
|
+
*/
|
|
794
|
+
function procTopology_warmup() {
|
|
795
|
+
if (procTopologyPromise)
|
|
796
|
+
return procTopologyPromise;
|
|
797
|
+
return procTopology_start(procTopologyState_create());
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Reconciles topology only for feed IDs selected by a restored-cache feed-index
|
|
801
|
+
* comparison. This preserves the checkpointed topology for unchanged terminal
|
|
802
|
+
* feeds while refreshing new, changed, and active feeds from CUBE.
|
|
803
|
+
*
|
|
804
|
+
* @param feedIDs - Canonical CUBE feed IDs whose topology must be reloaded.
|
|
805
|
+
* @returns A promise that resolves after all supplied feeds have been refreshed.
|
|
806
|
+
*/
|
|
807
|
+
function procTopology_reconcileFeeds(feedIDs) {
|
|
808
|
+
if (procTopologyPromise)
|
|
809
|
+
return procTopologyPromise;
|
|
810
|
+
return procTopologyScoped_start(feedIDs);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Continues a failed topology reconciliation from its retained global page or
|
|
814
|
+
* scoped feed set.
|
|
815
|
+
*
|
|
816
|
+
* @returns The resumed sweep promise, or the active sweep when one is running.
|
|
817
|
+
* @throws {Error} When there is no failed sweep continuation to retry.
|
|
818
|
+
*/
|
|
819
|
+
function procTopology_retry() {
|
|
820
|
+
if (procTopologyPromise)
|
|
821
|
+
return procTopologyPromise;
|
|
822
|
+
if (procTopologyFailure === undefined) {
|
|
823
|
+
throw new Error('no failed topology sweep to retry');
|
|
824
|
+
}
|
|
825
|
+
if (procTopologyResumeState)
|
|
826
|
+
return procTopology_start(procTopologyResumeState);
|
|
827
|
+
if (procTopologyScopedResumeFeedIDs)
|
|
828
|
+
return procTopologyScoped_start(procTopologyScopedResumeFeedIDs);
|
|
829
|
+
throw new Error('no failed topology sweep to retry');
|
|
830
|
+
}
|
|
603
831
|
/**
|
|
604
832
|
* Reports the session topology sweep lifecycle.
|
|
605
833
|
*
|
|
@@ -627,6 +855,13 @@ async function procTopology_await() {
|
|
|
627
855
|
}
|
|
628
856
|
/**
|
|
629
857
|
* Rebuilds the ProcCache, optionally scoped to one feed.
|
|
858
|
+
*
|
|
859
|
+
* A full refresh returns feeds whose topology merits reload after the feed-index
|
|
860
|
+
* comparison. Callers performing an explicit `proc refresh` can ignore that
|
|
861
|
+
* result and run a global topology sweep; restored startup uses it to stay scoped.
|
|
862
|
+
*
|
|
863
|
+
* @param feedID - One feed to force-refresh, or undefined for the full feed index.
|
|
864
|
+
* @returns IDs whose topology is new, changed, or currently active.
|
|
630
865
|
*/
|
|
631
866
|
async function procCache_refresh(feedID) {
|
|
632
867
|
if (feedID !== undefined) {
|
|
@@ -643,6 +878,7 @@ async function procCache_refresh(feedID) {
|
|
|
643
878
|
}
|
|
644
879
|
}
|
|
645
880
|
await feedInstances_ensureLoaded(feedID);
|
|
881
|
+
return [feedID];
|
|
646
882
|
}
|
|
647
883
|
else {
|
|
648
884
|
const activeSweep = procTopologyPromise;
|
|
@@ -656,7 +892,10 @@ async function procCache_refresh(feedID) {
|
|
|
656
892
|
}
|
|
657
893
|
procTopologyPromise = null;
|
|
658
894
|
procTopologyFailure = undefined;
|
|
659
|
-
|
|
895
|
+
procTopologyResumeState = null;
|
|
896
|
+
procTopologyScopedResumeFeedIDs = null;
|
|
897
|
+
(0, cumin_1.procCache_get)().warmup_reset();
|
|
898
|
+
return procCache_build();
|
|
660
899
|
}
|
|
661
900
|
}
|
|
662
901
|
//# sourceMappingURL=proc.js.map
|