@fnndsc/salsa 3.8.1 → 3.10.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.
Files changed (42) hide show
  1. package/dist/dag/feedGraph.d.ts +4 -0
  2. package/dist/dag/feedGraph.js +3 -0
  3. package/dist/dag/feedGraph.js.map +1 -1
  4. package/dist/dag/feedJoins.d.ts +9 -0
  5. package/dist/dag/feedJoins.js +15 -0
  6. package/dist/dag/feedJoins.js.map +1 -1
  7. package/dist/dag/feedLoad.js +22 -3
  8. package/dist/dag/feedLoad.js.map +1 -1
  9. package/dist/pipelines/diagram.js +60 -6
  10. package/dist/pipelines/diagram.js.map +1 -1
  11. package/dist/pipelines/index.d.ts +29 -0
  12. package/dist/pipelines/index.js +33 -1
  13. package/dist/pipelines/index.js.map +1 -1
  14. package/dist/pipelines/manifest.js +1 -6
  15. package/dist/pipelines/manifest.js.map +1 -1
  16. package/dist/pipelines/packages.d.ts +44 -0
  17. package/dist/pipelines/packages.js +181 -0
  18. package/dist/pipelines/packages.js.map +1 -0
  19. package/dist/retrieve/watch.d.ts +16 -2
  20. package/dist/retrieve/watch.js +124 -54
  21. package/dist/retrieve/watch.js.map +1 -1
  22. package/dist/vfs/dispatcher.js +9 -0
  23. package/dist/vfs/dispatcher.js.map +1 -1
  24. package/dist/vfs/providers/pacsGrammar.d.ts +69 -0
  25. package/dist/vfs/providers/pacsGrammar.js +83 -0
  26. package/dist/vfs/providers/pacsGrammar.js.map +1 -0
  27. package/dist/vfs/providers/pacsHelpers.d.ts +11 -43
  28. package/dist/vfs/providers/pacsHelpers.js +21 -60
  29. package/dist/vfs/providers/pacsHelpers.js.map +1 -1
  30. package/dist/vfs/providers/proc.d.ts +14 -0
  31. package/dist/vfs/providers/proc.js +98 -3
  32. package/dist/vfs/providers/proc.js.map +1 -1
  33. package/dist/vfs/providers/share.d.ts +65 -0
  34. package/dist/vfs/providers/share.js +227 -0
  35. package/dist/vfs/providers/share.js.map +1 -0
  36. package/dist/vfs/providers/tags.d.ts +30 -0
  37. package/dist/vfs/providers/tags.js +86 -0
  38. package/dist/vfs/providers/tags.js.map +1 -0
  39. package/dist/vfs/providers/workflows.d.ts +60 -0
  40. package/dist/vfs/providers/workflows.js +172 -0
  41. package/dist/vfs/providers/workflows.js.map +1 -0
  42. package/package.json +7 -2
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * @file The PACS VFS folder grammar, both directions.
4
+ *
5
+ * Query folders are named `<desc>_qid:<id>[_<user>][_no-hits]`, study
6
+ * folders `Study_<uid>_<desc>`, series folders `Series_<uid>_<desc>`. This
7
+ * module owns building those names and parsing them back, so a path a
8
+ * surface builds is always the path the listing shows.
9
+ *
10
+ * Deliberately dependency-free: test mocks forward these functions directly
11
+ * (via the package's `./pacs-grammar` subpath export) instead of maintaining
12
+ * copies, so nothing here may import anything.
13
+ *
14
+ * @module
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.queryId_extractFromFolder = queryId_extractFromFolder;
18
+ exports.queryLabel_extractFromFolder = queryLabel_extractFromFolder;
19
+ exports.folderUID_get = folderUID_get;
20
+ exports.queryFolderName_build = queryFolderName_build;
21
+ /**
22
+ * Extracts the numeric query id from a query folder name or any path
23
+ * containing one.
24
+ *
25
+ * @param folder - Folder name or full path carrying a `_qid:<id>` marker.
26
+ * @returns The query id, or NaN when no marker is present.
27
+ */
28
+ function queryId_extractFromFolder(folder) {
29
+ const match = /_qid:(\d+)/.exec(folder);
30
+ return match ? Number(match[1]) : NaN;
31
+ }
32
+ /**
33
+ * Extracts the descriptive label of a query folder, the part before its
34
+ * `_qid:` marker (the id, user, and no-hits suffixes are all dropped).
35
+ *
36
+ * @param folder - Query folder name (`<desc>_qid:<id>[_<user>][_no-hits]`).
37
+ * @returns The label, or the folder unchanged when it carries no marker.
38
+ */
39
+ function queryLabel_extractFromFolder(folder) {
40
+ return folder.replace(/_qid:\d+.*$/, "");
41
+ }
42
+ /**
43
+ * Extracts the UID portion of a study or series folder name
44
+ * (`<prefix>_<uid>_<label>`).
45
+ *
46
+ * @param folder - The folder name.
47
+ * @param prefix - The expected prefix, `Study` or `Series`.
48
+ * @returns The UID between the prefix and the first following underscore.
49
+ */
50
+ function folderUID_get(folder, prefix) {
51
+ const withoutPrefix = folder.replace(new RegExp(`^${prefix}_`), "");
52
+ return withoutPrefix.split("_")[0];
53
+ }
54
+ /**
55
+ * Builds a query folder name: `<desc>_qid:<id>[_<user>][_no-hits]`.
56
+ *
57
+ * This is the single naming authority: the listing provider and any surface
58
+ * that pre-computes a query's path must both use it, otherwise a freshly
59
+ * created query can be addressed by a name `ls` will never show.
60
+ *
61
+ * @param parts - The name's inputs. See QueryFolderNameParts.
62
+ * @returns The folder name (no leading path).
63
+ */
64
+ function queryFolderName_build(parts) {
65
+ const segments = [];
66
+ for (const [k, v] of Object.entries(parts.queryObj)) {
67
+ if (v !== undefined && v !== null && String(v).trim().length > 0)
68
+ segments.push(`${k}:${v}`);
69
+ }
70
+ let desc = segments.join("_");
71
+ if (!desc) {
72
+ const title = parts.title ?? "";
73
+ desc = title
74
+ ? title.replace(/^pacs_query_\d+_\d+$/, "query").replace(/^pacs_query_/, "")
75
+ : "query";
76
+ if (!desc)
77
+ desc = "query";
78
+ }
79
+ const userSuffix = parts.username ? `_${parts.username}` : "";
80
+ const noHitsSuffix = parts.hasResult === false ? "_no-hits" : "";
81
+ return `${desc}_qid:${parts.queryId}${userSuffix}${noHitsSuffix}`;
82
+ }
83
+ //# sourceMappingURL=pacsGrammar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pacsGrammar.js","sourceRoot":"","sources":["../../../src/vfs/providers/pacsGrammar.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AASH,8DAGC;AASD,oEAEC;AAUD,sCAGC;AAgCD,sDAgBC;AAlFD;;;;;;GAMG;AACH,SAAgB,yBAAyB,CAAC,MAAc;IACtD,MAAM,KAAK,GAA2B,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,4BAA4B,CAAC,MAAc;IACzD,OAAO,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,aAAa,CAAC,MAAc,EAAE,MAAc;IAC1D,MAAM,aAAa,GAAW,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAsBD;;;;;;;;;GASG;AACH,SAAgB,qBAAqB,CAAC,KAA2B;IAC/D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,IAAI,GAAW,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,KAAK,GAAW,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,IAAI,GAAG,KAAK;YACV,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;YAC5E,CAAC,CAAC,OAAO,CAAC;QACZ,IAAI,CAAC,IAAI;YAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,CAAC;IACD,MAAM,UAAU,GAAW,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,MAAM,YAAY,GAAW,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,OAAO,GAAG,IAAI,QAAQ,KAAK,CAAC,OAAO,GAAG,UAAU,GAAG,YAAY,EAAE,CAAC;AACpE,CAAC"}
@@ -14,55 +14,23 @@
14
14
  */
15
15
  import { Result } from "@fnndsc/cumin";
16
16
  export { tag_extractValue, studies_extractFromDecoded, series_extractFromStudy, study_findByUID, series_findByUID, } from "@fnndsc/cumin";
17
- export declare function path_normalize(pathStr: string): string;
18
- export declare function queryId_extractFromFolder(folder: string): number;
19
17
  /**
20
- * Extracts the descriptive label of a query folder, the part before its
21
- * `_qid:` marker (the id, user, and no-hits suffixes are all dropped).
18
+ * Normalizes a VFS path: guarantees a leading slash and strips one trailing
19
+ * slash (the root path keeps its single slash).
22
20
  *
23
- * @param folder - Query folder name (`<desc>_qid:<id>[_<user>][_no-hits]`).
24
- * @returns The label, or the folder unchanged when it carries no marker.
21
+ * @param pathStr - Raw path string.
22
+ * @returns The normalized absolute path.
25
23
  */
26
- export declare function queryLabel_extractFromFolder(folder: string): string;
27
- /**
28
- * Extracts the UID portion of a study or series folder name
29
- * (`<prefix>_<uid>_<label>`).
30
- *
31
- * @param folder - The folder name.
32
- * @param prefix - The expected prefix, `Study` or `Series`.
33
- * @returns The UID between the prefix and the first following underscore.
34
- */
35
- export declare function folderUID_get(folder: string, prefix: string): string;
36
- /**
37
- * Everything that determines a query folder's name.
38
- *
39
- * @property queryId - The query's numeric id.
40
- * @property queryObj - The query's key-value search terms; blank values are
41
- * skipped.
42
- * @property title - The query record's title, used as the description when
43
- * queryObj yields nothing.
44
- * @property username - Owning username, appended as `_<user>` when present.
45
- * @property hasResult - Whether the query holds a result payload; false
46
- * appends the `_no-hits` suffix. Defaults to true.
47
- */
48
- export interface QueryFolderNameParts {
49
- queryId: number | string;
50
- queryObj: Record<string, unknown>;
51
- title?: string;
52
- username?: string;
53
- hasResult?: boolean;
54
- }
24
+ export declare function path_normalize(pathStr: string): string;
25
+ export { queryId_extractFromFolder, queryLabel_extractFromFolder, folderUID_get, queryFolderName_build, } from "./pacsGrammar.js";
26
+ export type { QueryFolderNameParts } from "./pacsGrammar.js";
55
27
  /**
56
- * Builds a query folder name: `<desc>_qid:<id>[_<user>][_no-hits]`.
57
- *
58
- * This is the single naming authority: the listing provider and any surface
59
- * that pre-computes a query's path must both use it, otherwise a freshly
60
- * created query can be addressed by a name `ls` will never show.
28
+ * Parses a `cp` source path into its study, optional series, and query id.
61
29
  *
62
- * @param parts - The name's inputs. See QueryFolderNameParts.
63
- * @returns The folder name (no leading path).
30
+ * @param src - Source path naming a Study or Series directory under a query.
31
+ * @returns The parsed identifiers, or Err with a rendered reason on the
32
+ * error stack when the path is not a copyable PACS location.
64
33
  */
65
- export declare function queryFolderName_build(parts: QueryFolderNameParts): string;
66
34
  export declare function cpSrc_parse(src: string): Result<{
67
35
  studyUID: string;
68
36
  seriesUID?: string;
@@ -1,11 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.series_findByUID = exports.study_findByUID = exports.series_extractFromStudy = exports.studies_extractFromDecoded = exports.tag_extractValue = void 0;
3
+ exports.queryFolderName_build = exports.folderUID_get = exports.queryLabel_extractFromFolder = exports.queryId_extractFromFolder = exports.series_findByUID = exports.study_findByUID = exports.series_extractFromStudy = exports.studies_extractFromDecoded = exports.tag_extractValue = void 0;
4
4
  exports.path_normalize = path_normalize;
5
- exports.queryId_extractFromFolder = queryId_extractFromFolder;
6
- exports.queryLabel_extractFromFolder = queryLabel_extractFromFolder;
7
- exports.folderUID_get = folderUID_get;
8
- exports.queryFolderName_build = queryFolderName_build;
9
5
  exports.cpSrc_parse = cpSrc_parse;
10
6
  /**
11
7
  * @file Pure helpers for the PACS VFS provider.
@@ -22,73 +18,38 @@ exports.cpSrc_parse = cpSrc_parse;
22
18
  * @module
23
19
  */
24
20
  const cumin_1 = require("@fnndsc/cumin");
21
+ const pacsGrammar_js_1 = require("./pacsGrammar.js");
25
22
  var cumin_2 = require("@fnndsc/cumin");
26
23
  Object.defineProperty(exports, "tag_extractValue", { enumerable: true, get: function () { return cumin_2.tag_extractValue; } });
27
24
  Object.defineProperty(exports, "studies_extractFromDecoded", { enumerable: true, get: function () { return cumin_2.studies_extractFromDecoded; } });
28
25
  Object.defineProperty(exports, "series_extractFromStudy", { enumerable: true, get: function () { return cumin_2.series_extractFromStudy; } });
29
26
  Object.defineProperty(exports, "study_findByUID", { enumerable: true, get: function () { return cumin_2.study_findByUID; } });
30
27
  Object.defineProperty(exports, "series_findByUID", { enumerable: true, get: function () { return cumin_2.series_findByUID; } });
28
+ /**
29
+ * Normalizes a VFS path: guarantees a leading slash and strips one trailing
30
+ * slash (the root path keeps its single slash).
31
+ *
32
+ * @param pathStr - Raw path string.
33
+ * @returns The normalized absolute path.
34
+ */
31
35
  function path_normalize(pathStr) {
32
36
  let p = pathStr.startsWith("/") ? pathStr : "/" + pathStr;
33
37
  if (p.length > 1 && p.endsWith("/"))
34
38
  p = p.slice(0, -1);
35
39
  return p;
36
40
  }
37
- function queryId_extractFromFolder(folder) {
38
- const match = /_qid:(\d+)/.exec(folder);
39
- return match ? Number(match[1]) : NaN;
40
- }
41
- /**
42
- * Extracts the descriptive label of a query folder, the part before its
43
- * `_qid:` marker (the id, user, and no-hits suffixes are all dropped).
44
- *
45
- * @param folder - Query folder name (`<desc>_qid:<id>[_<user>][_no-hits]`).
46
- * @returns The label, or the folder unchanged when it carries no marker.
47
- */
48
- function queryLabel_extractFromFolder(folder) {
49
- return folder.replace(/_qid:\d+.*$/, "");
50
- }
41
+ var pacsGrammar_js_2 = require("./pacsGrammar.js");
42
+ Object.defineProperty(exports, "queryId_extractFromFolder", { enumerable: true, get: function () { return pacsGrammar_js_2.queryId_extractFromFolder; } });
43
+ Object.defineProperty(exports, "queryLabel_extractFromFolder", { enumerable: true, get: function () { return pacsGrammar_js_2.queryLabel_extractFromFolder; } });
44
+ Object.defineProperty(exports, "folderUID_get", { enumerable: true, get: function () { return pacsGrammar_js_2.folderUID_get; } });
45
+ Object.defineProperty(exports, "queryFolderName_build", { enumerable: true, get: function () { return pacsGrammar_js_2.queryFolderName_build; } });
51
46
  /**
52
- * Extracts the UID portion of a study or series folder name
53
- * (`<prefix>_<uid>_<label>`).
47
+ * Parses a `cp` source path into its study, optional series, and query id.
54
48
  *
55
- * @param folder - The folder name.
56
- * @param prefix - The expected prefix, `Study` or `Series`.
57
- * @returns The UID between the prefix and the first following underscore.
49
+ * @param src - Source path naming a Study or Series directory under a query.
50
+ * @returns The parsed identifiers, or Err with a rendered reason on the
51
+ * error stack when the path is not a copyable PACS location.
58
52
  */
59
- function folderUID_get(folder, prefix) {
60
- const withoutPrefix = folder.replace(new RegExp(`^${prefix}_`), "");
61
- return withoutPrefix.split("_")[0];
62
- }
63
- /**
64
- * Builds a query folder name: `<desc>_qid:<id>[_<user>][_no-hits]`.
65
- *
66
- * This is the single naming authority: the listing provider and any surface
67
- * that pre-computes a query's path must both use it, otherwise a freshly
68
- * created query can be addressed by a name `ls` will never show.
69
- *
70
- * @param parts - The name's inputs. See QueryFolderNameParts.
71
- * @returns The folder name (no leading path).
72
- */
73
- function queryFolderName_build(parts) {
74
- const segments = [];
75
- for (const [k, v] of Object.entries(parts.queryObj)) {
76
- if (v !== undefined && v !== null && String(v).trim().length > 0)
77
- segments.push(`${k}:${v}`);
78
- }
79
- let desc = segments.join("_");
80
- if (!desc) {
81
- const title = parts.title ?? "";
82
- desc = title
83
- ? title.replace(/^pacs_query_\d+_\d+$/, "query").replace(/^pacs_query_/, "")
84
- : "query";
85
- if (!desc)
86
- desc = "query";
87
- }
88
- const userSuffix = parts.username ? `_${parts.username}` : "";
89
- const noHitsSuffix = parts.hasResult === false ? "_no-hits" : "";
90
- return `${desc}_qid:${parts.queryId}${userSuffix}${noHitsSuffix}`;
91
- }
92
53
  function cpSrc_parse(src) {
93
54
  const absolutePath = src.startsWith("/") ? src : "/" + src;
94
55
  const parts = absolutePath.split("/").filter(Boolean);
@@ -105,12 +66,12 @@ function cpSrc_parse(src) {
105
66
  cumin_1.errorStack.stack_push("error", `cp: Invalid PACS Study folder format: '${studyFolder}'`);
106
67
  return (0, cumin_1.Err)();
107
68
  }
108
- const studyUID = folderUID_get(studyFolder, "Study");
69
+ const studyUID = (0, pacsGrammar_js_1.folderUID_get)(studyFolder, "Study");
109
70
  let seriesUID;
110
71
  if (parts.length >= queryIdx + 3 && parts[queryIdx + 2].startsWith("Series_")) {
111
- seriesUID = folderUID_get(parts[queryIdx + 2], "Series");
72
+ seriesUID = (0, pacsGrammar_js_1.folderUID_get)(parts[queryIdx + 2], "Series");
112
73
  }
113
- const queryId = queryId_extractFromFolder(parts[queryIdx]);
74
+ const queryId = (0, pacsGrammar_js_1.queryId_extractFromFolder)(parts[queryIdx]);
114
75
  if (Number.isNaN(queryId)) {
115
76
  cumin_1.errorStack.stack_push("error", `cp: Invalid query ID in path '${src}'`);
116
77
  return (0, cumin_1.Err)();
@@ -1 +1 @@
1
- {"version":3,"file":"pacsHelpers.js","sourceRoot":"","sources":["../../../src/vfs/providers/pacsHelpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pacsHelpers.js","sourceRoot":"","sources":["../../../src/vfs/providers/pacsHelpers.ts"],"names":[],"mappings":";;;AAgCA,wCAIC;AAiBD,kCAiCC;AAtFD;;;;;;;;;;;;;GAaG;AACH,yCAA4D;AAC5D,qDAA4E;AAE5E,uCAMuB;AALrB,yGAAA,gBAAgB,OAAA;AAChB,mHAAA,0BAA0B,OAAA;AAC1B,gHAAA,uBAAuB,OAAA;AACvB,wGAAA,eAAe,OAAA;AACf,yGAAA,gBAAgB,OAAA;AAGlB;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC,GAAW,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC;IAClE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,mDAK0B;AAJxB,2HAAA,yBAAyB,OAAA;AACzB,8HAAA,4BAA4B,OAAA;AAC5B,+GAAA,aAAa,OAAA;AACb,uHAAA,qBAAqB,OAAA;AAIvB;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,GAAW;IACrC,MAAM,YAAY,GAAW,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;IACnE,MAAM,KAAK,GAAa,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEhE,yEAAyE;IACzE,2EAA2E;IAC3E,0DAA0D;IAC1D,MAAM,QAAQ,GAAW,KAAK,CAAC,SAAS,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3F,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC;QACnD,kBAAU,CAAC,UAAU,CAAC,OAAO,EAAE,qBAAqB,GAAG,iEAAiE,CAAC,CAAC;QAC1H,OAAO,IAAA,WAAG,GAAE,CAAC;IACf,CAAC;IAED,MAAM,WAAW,GAAW,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,kBAAU,CAAC,UAAU,CAAC,OAAO,EAAE,0CAA0C,WAAW,GAAG,CAAC,CAAC;QACzF,OAAO,IAAA,WAAG,GAAE,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAW,IAAA,8BAAa,EAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAE7D,IAAI,SAA6B,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9E,SAAS,GAAG,IAAA,8BAAa,EAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,OAAO,GAAW,IAAA,0CAAyB,EAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,kBAAU,CAAC,UAAU,CAAC,OAAO,EAAE,iCAAiC,GAAG,GAAG,CAAC,CAAC;QACxE,OAAO,IAAA,WAAG,GAAE,CAAC;IACf,CAAC;IAED,OAAO,IAAA,UAAE,EAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AAC9C,CAAC"}
@@ -37,6 +37,12 @@ export declare function procPath_parse(pathStr: string): {
37
37
  feedID: number | null;
38
38
  instanceID: number | null;
39
39
  virtualFile: string | null;
40
+ /**
41
+ * Set when the path descends THROUGH an instance's `data` link: the
42
+ * portion after `data`, '' for the link itself. Everything under the
43
+ * link belongs to the link's CFS target, not to `/proc`.
44
+ */
45
+ dataRemainder: string | null;
40
46
  };
41
47
  /**
42
48
  * VFS provider exposing running jobs and feeds under the /proc namespace.
@@ -56,6 +62,14 @@ export declare class ProcVfsProvider implements VFSProvider {
56
62
  sort?: 'name' | 'size' | 'date' | 'owner';
57
63
  reverse?: boolean;
58
64
  }): Promise<Result<VFSItem[]>>;
65
+ /**
66
+ * Binary bytes exist only under the `data` link (an image in a job's
67
+ * output space); everything else in `/proc` is synthesized text.
68
+ *
69
+ * @param pathStr - Absolute `/proc/jobs/.../data/...` path.
70
+ * @returns The target file's bytes.
71
+ */
72
+ readBinary(pathStr: string): Promise<Result<Buffer>>;
59
73
  read(pathStr: string): Promise<Result<string>>;
60
74
  rm(pathStr: string, _options?: {
61
75
  recursive?: boolean;
@@ -28,6 +28,8 @@ exports.procTopology_await = procTopology_await;
28
28
  exports.procCache_refresh = procCache_refresh;
29
29
  const cumin_1 = require("@fnndsc/cumin");
30
30
  const index_js_1 = require("../../jobs/index.js");
31
+ const feedJoins_js_1 = require("../../dag/feedJoins.js");
32
+ const packages_js_1 = require("../../pipelines/packages.js");
31
33
  /** Virtual filenames inside each instance directory. */
32
34
  const INSTANCE_FILES = new Set(['status', 'params', 'log', 'data']);
33
35
  /** Virtual filenames inside each feed directory. */
@@ -94,6 +96,14 @@ async function cache_ensure() {
94
96
  * Loads instance topology for a single feed.
95
97
  * Uses in-flight map to prevent duplicate API calls.
96
98
  */
99
+ /** Pulls the execution metrics a CUBE list row carries, defined-only. */
100
+ function instanceMetrics_fromRow(inst) {
101
+ return {
102
+ ...(typeof inst.start_date === 'string' ? { startedAt: inst.start_date } : {}),
103
+ ...(typeof inst.end_date === 'string' ? { finishedAt: inst.end_date } : {}),
104
+ ...(typeof inst.size === 'number' ? { outputBytes: inst.size } : {}),
105
+ };
106
+ }
97
107
  async function feedInstances_load(feedID) {
98
108
  const cache = (0, cumin_1.procCache_get)();
99
109
  const client = await cumin_1.chrisConnection.client_get();
@@ -117,6 +127,7 @@ async function feedInstances_load(feedID) {
117
127
  params: null,
118
128
  outputPath: outputPath_normalize(inst.output_path) ?? undefined,
119
129
  status: String(inst.status ?? 'unknown'),
130
+ ...instanceMetrics_fromRow(inst),
120
131
  });
121
132
  }
122
133
  cache.topologyLoaded_mark(feedID);
@@ -169,11 +180,26 @@ function procPath_parse(pathStr) {
169
180
  let feedID = null;
170
181
  let instanceID = null;
171
182
  let virtualFile = null;
183
+ let dataRemainder = null;
172
184
  if (parts.length >= 1) {
173
185
  const feedMatch = parts[0].match(/^feed_(\d+)$/);
174
186
  if (feedMatch)
175
187
  feedID = parseInt(feedMatch[1], 10);
176
188
  }
189
+ // A `data` segment directly after an instance segment starts the link's
190
+ // subtree: the instance is the one before it, whatever follows is the
191
+ // remainder inside the CFS target.
192
+ for (let index = 2; index < parts.length; index++) {
193
+ if (parts[index] !== 'data')
194
+ continue;
195
+ const instMatch = parts[index - 1].match(/_(\d+)$/);
196
+ if (!instMatch)
197
+ continue;
198
+ instanceID = parseInt(instMatch[1], 10);
199
+ virtualFile = 'data';
200
+ dataRemainder = parts.slice(index + 1).join('/');
201
+ return { feedID, instanceID, virtualFile, dataRemainder };
202
+ }
177
203
  if (parts.length >= 2) {
178
204
  const lastPart = parts[parts.length - 1];
179
205
  if (FEED_FILES.has(lastPart) || INSTANCE_FILES.has(lastPart)) {
@@ -190,7 +216,7 @@ function procPath_parse(pathStr) {
190
216
  instanceID = parseInt(instMatch[1], 10);
191
217
  }
192
218
  }
193
- return { feedID, instanceID, virtualFile };
219
+ return { feedID, instanceID, virtualFile, dataRemainder };
194
220
  }
195
221
  /** Formats instance params as key=value lines. */
196
222
  function params_render(inst) {
@@ -218,6 +244,21 @@ function outputPath_normalize(value) {
218
244
  * @param instanceID - Plugin-instance ID whose output path is needed.
219
245
  * @returns Absolute output path, or null when CUBE has not recorded one.
220
246
  */
247
+ /**
248
+ * Resolves a path inside an instance's `data` link to its CFS target.
249
+ *
250
+ * @param instanceID - The instance whose output space the path enters.
251
+ * @param remainder - The path below `data`, '' for the link itself.
252
+ * @returns The absolute CFS path, or Err when the job has no output space.
253
+ */
254
+ async function dataTarget_resolve(instanceID, remainder) {
255
+ const outputPath = await instanceOutputPath_ensure(instanceID);
256
+ if (!outputPath) {
257
+ cumin_1.errorStack.stack_push('error', `No CFS output path is available for job ${instanceID}`);
258
+ return (0, cumin_1.Err)();
259
+ }
260
+ return (0, cumin_1.Ok)(remainder === '' ? outputPath : `${outputPath}/${remainder}`);
261
+ }
221
262
  async function instanceOutputPath_ensure(instanceID) {
222
263
  const cache = (0, cumin_1.procCache_get)();
223
264
  const inst = cache.instance_get(instanceID);
@@ -317,7 +358,17 @@ class ProcVfsProvider {
317
358
  });
318
359
  return (0, cumin_1.Ok)(items);
319
360
  }
320
- const { feedID, instanceID } = procPath_parse(clean);
361
+ const { feedID, instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
362
+ // Anything under an instance's `data` link belongs to the link's CFS
363
+ // target: resolve and delegate, so `ls .../data` (and deeper) shows the
364
+ // target's real contents instead of silently re-listing the instance.
365
+ if (instanceID !== null && virtualFile === 'data' && dataRemainder !== null) {
366
+ const target = await dataTarget_resolve(instanceID, dataRemainder);
367
+ if (!target.ok)
368
+ return (0, cumin_1.Err)();
369
+ const { vfsDispatcher } = await import('../dispatcher.js');
370
+ return vfsDispatcher.list(target.value, _options);
371
+ }
321
372
  // /proc/jobs/feed_N — list root instances + feed virtual files
322
373
  if (feedID !== null && instanceID === null) {
323
374
  const wasLoaded = cache.topologyLoaded_has(feedID);
@@ -391,11 +442,40 @@ class ProcVfsProvider {
391
442
  }
392
443
  return (0, cumin_1.Ok)([]);
393
444
  }
445
+ /**
446
+ * Binary bytes exist only under the `data` link (an image in a job's
447
+ * output space); everything else in `/proc` is synthesized text.
448
+ *
449
+ * @param pathStr - Absolute `/proc/jobs/.../data/...` path.
450
+ * @returns The target file's bytes.
451
+ */
452
+ async readBinary(pathStr) {
453
+ await cache_ensure();
454
+ const clean = pathStr.replace(/\/$/, '');
455
+ const { instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
456
+ if (instanceID === null || virtualFile !== 'data' || dataRemainder === null || dataRemainder === '') {
457
+ cumin_1.errorStack.stack_push('error', `${pathStr} is not a binary file.`);
458
+ return (0, cumin_1.Err)();
459
+ }
460
+ const target = await dataTarget_resolve(instanceID, dataRemainder);
461
+ if (!target.ok)
462
+ return (0, cumin_1.Err)();
463
+ const { vfsDispatcher } = await import('../dispatcher.js');
464
+ return vfsDispatcher.readBinary(target.value);
465
+ }
394
466
  async read(pathStr) {
395
467
  await cache_ensure();
396
468
  const cache = (0, cumin_1.procCache_get)();
397
469
  const clean = pathStr.replace(/\/$/, '');
398
- const { feedID, instanceID, virtualFile } = procPath_parse(clean);
470
+ const { feedID, instanceID, virtualFile, dataRemainder } = procPath_parse(clean);
471
+ // A file under the `data` link is the CFS target's file.
472
+ if (instanceID !== null && virtualFile === 'data' && dataRemainder !== null && dataRemainder !== '') {
473
+ const target = await dataTarget_resolve(instanceID, dataRemainder);
474
+ if (!target.ok)
475
+ return (0, cumin_1.Err)();
476
+ const { vfsDispatcher } = await import('../dispatcher.js');
477
+ return vfsDispatcher.read(target.value);
478
+ }
399
479
  // Feed-level virtual files
400
480
  if (feedID !== null && instanceID === null && virtualFile !== null) {
401
481
  const feed = cache.feed_get(feedID);
@@ -556,6 +636,7 @@ async function feedStatus_refresh(feedID) {
556
636
  for await (const step of (0, cumin_1.listPages_walk)((offset, limit) => (0, cumin_1.pluginInstancesPage_get)(client, { feed_id: feedID, limit, offset }), { pageSize: PAGE })) {
557
637
  for (const inst of step.items) {
558
638
  cache.status_update(Number(inst.id), String(inst.status ?? 'unknown'));
639
+ cache.instanceMetrics_set(Number(inst.id), instanceMetrics_fromRow(inst));
559
640
  }
560
641
  }
561
642
  }
@@ -606,6 +687,7 @@ async function procTopology_run(state) {
606
687
  params: null,
607
688
  outputPath: outputPath_normalize(inst.output_path) ?? undefined,
608
689
  status: String(inst.status ?? 'unknown'),
690
+ ...instanceMetrics_fromRow(inst),
609
691
  });
610
692
  state.seenInstanceIDs.add(instanceID);
611
693
  state.seenFeedIDs.add(feedID);
@@ -655,6 +737,7 @@ function procTopology_start(state) {
655
737
  if ((0, cumin_1.procCache_get)().warmupComplete) {
656
738
  procTopologyFailure = undefined;
657
739
  procTopologyResumeState = null;
740
+ procSettlementTails_run();
658
741
  }
659
742
  else {
660
743
  procTopologyFailure = 'the topology sweep ended before the index completed';
@@ -668,6 +751,17 @@ function procTopology_start(state) {
668
751
  });
669
752
  return sweep;
670
753
  }
754
+ /**
755
+ * Kicks the quiet settlement tails once the topology index is complete:
756
+ * every unresolved `ts` join resolves in the background (so first diagrams
757
+ * are pure cache, and the checkpoint persists them), and the pipeline
758
+ * package store sweeps unseen registered pipelines. Both are best-effort —
759
+ * a failure leaves the lazy paths intact.
760
+ */
761
+ function procSettlementTails_run() {
762
+ void (0, feedJoins_js_1.procJoins_sweep)().catch(() => { });
763
+ void (0, packages_js_1.pipelinePackages_sweep)().catch(() => { });
764
+ }
671
765
  /**
672
766
  * Runs a feed-scoped topology reconciliation selected after checkpoint restore.
673
767
  *
@@ -680,6 +774,7 @@ async function procTopologyScoped_run(feedIDs) {
680
774
  for (const feedID of feedIDs)
681
775
  await procCache_refresh(feedID);
682
776
  cache.warmup_complete();
777
+ procSettlementTails_run();
683
778
  }
684
779
  /**
685
780
  * Starts one scoped reconciliation and retains its targets for `proc retry`.