@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.
- package/dist/dag/feedGraph.d.ts +4 -0
- package/dist/dag/feedGraph.js +3 -0
- package/dist/dag/feedGraph.js.map +1 -1
- package/dist/dag/feedJoins.d.ts +9 -0
- package/dist/dag/feedJoins.js +15 -0
- package/dist/dag/feedJoins.js.map +1 -1
- package/dist/dag/feedLoad.js +22 -3
- package/dist/dag/feedLoad.js.map +1 -1
- package/dist/pipelines/diagram.js +60 -6
- package/dist/pipelines/diagram.js.map +1 -1
- package/dist/pipelines/index.d.ts +29 -0
- package/dist/pipelines/index.js +33 -1
- package/dist/pipelines/index.js.map +1 -1
- package/dist/pipelines/manifest.js +1 -6
- package/dist/pipelines/manifest.js.map +1 -1
- package/dist/pipelines/packages.d.ts +44 -0
- package/dist/pipelines/packages.js +181 -0
- package/dist/pipelines/packages.js.map +1 -0
- package/dist/retrieve/watch.d.ts +16 -2
- package/dist/retrieve/watch.js +124 -54
- package/dist/retrieve/watch.js.map +1 -1
- package/dist/vfs/dispatcher.js +9 -0
- package/dist/vfs/dispatcher.js.map +1 -1
- package/dist/vfs/providers/pacsGrammar.d.ts +69 -0
- package/dist/vfs/providers/pacsGrammar.js +83 -0
- package/dist/vfs/providers/pacsGrammar.js.map +1 -0
- package/dist/vfs/providers/pacsHelpers.d.ts +11 -43
- package/dist/vfs/providers/pacsHelpers.js +21 -60
- package/dist/vfs/providers/pacsHelpers.js.map +1 -1
- package/dist/vfs/providers/proc.d.ts +14 -0
- package/dist/vfs/providers/proc.js +98 -3
- package/dist/vfs/providers/proc.js.map +1 -1
- package/dist/vfs/providers/share.d.ts +65 -0
- package/dist/vfs/providers/share.js +227 -0
- package/dist/vfs/providers/share.js.map +1 -0
- package/dist/vfs/providers/tags.d.ts +30 -0
- package/dist/vfs/providers/tags.js +86 -0
- package/dist/vfs/providers/tags.js.map +1 -0
- package/dist/vfs/providers/workflows.d.ts +60 -0
- package/dist/vfs/providers/workflows.js +172 -0
- package/dist/vfs/providers/workflows.js.map +1 -0
- 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
|
-
*
|
|
21
|
-
*
|
|
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
|
|
24
|
-
* @returns The
|
|
21
|
+
* @param pathStr - Raw path string.
|
|
22
|
+
* @returns The normalized absolute path.
|
|
25
23
|
*/
|
|
26
|
-
export declare function
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
*
|
|
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
|
|
63
|
-
* @returns The
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
*
|
|
53
|
-
* (`<prefix>_<uid>_<label>`).
|
|
47
|
+
* Parses a `cp` source path into its study, optional series, and query id.
|
|
54
48
|
*
|
|
55
|
-
* @param
|
|
56
|
-
* @
|
|
57
|
-
*
|
|
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":";;;
|
|
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`.
|