@fnndsc/cumin 3.24.0 → 3.26.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/cache/listCache.d.ts +35 -0
- package/dist/cache/listCache.js +62 -0
- package/dist/cache/listCache.js.map +1 -1
- package/dist/cache/procCache.d.ts +2 -0
- package/dist/cache/procCache.js.map +1 -1
- package/dist/cache/procCheckpoint.js +1 -0
- package/dist/cache/procCheckpoint.js.map +1 -1
- package/dist/chrisapi/contract.js +9 -1
- package/dist/chrisapi/contract.js.map +1 -1
- package/dist/connect/chrisConnection.js +7 -1
- package/dist/connect/chrisConnection.js.map +1 -1
- package/dist/filebrowser/folderMemo.d.ts +20 -0
- package/dist/filebrowser/folderMemo.js +32 -0
- package/dist/filebrowser/folderMemo.js.map +1 -0
- package/dist/groups/chrisGroups.js +51 -4
- package/dist/groups/chrisGroups.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/net/requestLedger.d.ts +78 -0
- package/dist/net/requestLedger.js +158 -0
- package/dist/net/requestLedger.js.map +1 -0
- package/dist/pacs/chrisPACS.d.ts +36 -0
- package/dist/pacs/chrisPACS.js +84 -0
- package/dist/pacs/chrisPACS.js.map +1 -1
- package/dist/pipelines/chrisPipeline.d.ts +5 -0
- package/dist/pipelines/chrisPipeline.js +107 -40
- package/dist/pipelines/chrisPipeline.js.map +1 -1
- package/dist/pipelines/pipelineMemo.d.ts +63 -0
- package/dist/pipelines/pipelineMemo.js +94 -0
- package/dist/pipelines/pipelineMemo.js.map +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.folderLookup_memoize = folderLookup_memoize;
|
|
4
|
+
const listCache_1 = require("../cache/listCache");
|
|
5
|
+
/** How a memoised client is marked, so it is wrapped once. */
|
|
6
|
+
const MEMOISED = Symbol('folderLookup_memoised');
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a client's folder lookup with the session's memo. Idempotent.
|
|
9
|
+
*
|
|
10
|
+
* @param client - A connected chrisapi Client.
|
|
11
|
+
* @returns The same client, its `getFileBrowserFolderByPath` memoised.
|
|
12
|
+
*/
|
|
13
|
+
function folderLookup_memoize(client) {
|
|
14
|
+
const wrapped = client;
|
|
15
|
+
if (wrapped[MEMOISED] === true)
|
|
16
|
+
return client;
|
|
17
|
+
// A client without the lookup (a stub, an older chrisapi) is left as it is.
|
|
18
|
+
if (typeof wrapped.getFileBrowserFolderByPath !== 'function')
|
|
19
|
+
return client;
|
|
20
|
+
wrapped[MEMOISED] = true;
|
|
21
|
+
const lookup = wrapped.getFileBrowserFolderByPath.bind(wrapped);
|
|
22
|
+
wrapped.getFileBrowserFolderByPath = (path, timeout) => {
|
|
23
|
+
const kept = (0, listCache_1.listCache_get)().folder_get(path);
|
|
24
|
+
if (kept !== null)
|
|
25
|
+
return kept;
|
|
26
|
+
const fresh = lookup(path, timeout);
|
|
27
|
+
(0, listCache_1.listCache_get)().folder_set(path, fresh);
|
|
28
|
+
return fresh;
|
|
29
|
+
};
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=folderMemo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folderMemo.js","sourceRoot":"","sources":["../../src/filebrowser/folderMemo.ts"],"names":[],"mappings":";;AA8BA,oDAeC;AAhCD,kDAAmD;AAEnD,8DAA8D;AAC9D,MAAM,QAAQ,GAAkB,MAAM,CAAC,uBAAuB,CAAC,CAAC;AAQhE;;;;;GAKG;AACH,SAAgB,oBAAoB,CAAC,MAAc;IACjD,MAAM,OAAO,GAAuB,MAA4B,CAAC;IACjE,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAC9C,4EAA4E;IAC5E,IAAI,OAAO,OAAO,CAAC,0BAA0B,KAAK,UAAU;QAAE,OAAO,MAAM,CAAC;IAC5E,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IACzB,MAAM,MAAM,GAAyD,OAAO,CAAC,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtH,OAAO,CAAC,0BAA0B,GAAG,CAAC,IAAY,EAAE,OAAgB,EAAoB,EAAE;QACxF,MAAM,IAAI,GAA4B,IAAA,yBAAa,GAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvE,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/B,MAAM,KAAK,GAAqB,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACtD,IAAA,yBAAa,GAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -14,6 +14,34 @@ const contract_js_1 = require("../chrisapi/contract.js");
|
|
|
14
14
|
const errorStack_js_1 = require("../error/errorStack.js");
|
|
15
15
|
const chrisResourceGroup_js_1 = require("../resources/chrisResourceGroup.js");
|
|
16
16
|
const result_js_1 = require("../utils/result.js");
|
|
17
|
+
/** The group items a session's groups listing served, by id, per client. */
|
|
18
|
+
const groupItems = new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* The listed group items of one client, made on first use.
|
|
21
|
+
*
|
|
22
|
+
* @param client - The connection's chrisapi client.
|
|
23
|
+
* @returns That client's items by group id.
|
|
24
|
+
*/
|
|
25
|
+
function groupItems_for(client) {
|
|
26
|
+
const kept = groupItems.get(client);
|
|
27
|
+
if (kept !== undefined)
|
|
28
|
+
return kept;
|
|
29
|
+
const made = new Map();
|
|
30
|
+
groupItems.set(client, made);
|
|
31
|
+
return made;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reads the member a membership row names, without a request.
|
|
35
|
+
*
|
|
36
|
+
* @param membership - The membership row resource.
|
|
37
|
+
* @returns The member, or null when the row carries no user id and name.
|
|
38
|
+
*/
|
|
39
|
+
function membershipMember_read(membership) {
|
|
40
|
+
const row = (0, adapter_js_1.itemData_get)(membership);
|
|
41
|
+
if (!row || typeof row.user_id !== 'number' || typeof row.user_username !== 'string')
|
|
42
|
+
return null;
|
|
43
|
+
return { id: row.user_id, username: row.user_username };
|
|
44
|
+
}
|
|
17
45
|
/**
|
|
18
46
|
* Group handler for ChRIS user groups.
|
|
19
47
|
*/
|
|
@@ -33,6 +61,12 @@ class ChRISGroupGroup extends chrisResourceGroup_js_1.ChRISResourceGroup {
|
|
|
33
61
|
errorStack_js_1.errorStack.stack_push('error', 'Not connected to ChRIS. Please log in.');
|
|
34
62
|
return null;
|
|
35
63
|
}
|
|
64
|
+
// The item the groups listing served answers first: chrisapi's getGroup
|
|
65
|
+
// is a list-by-id, and the boot's warm listed every group that way
|
|
66
|
+
// right after listing them all (73 searches on a 73-group CUBE).
|
|
67
|
+
const listed = groupItems_for(client).get(groupID);
|
|
68
|
+
if (listed !== undefined)
|
|
69
|
+
return listed;
|
|
36
70
|
const group = await client.getGroup(groupID);
|
|
37
71
|
if (!group)
|
|
38
72
|
errorStack_js_1.errorStack.stack_push('error', `Group ${groupID} was not found.`);
|
|
@@ -50,8 +84,16 @@ class ChRISGroupGroup extends chrisResourceGroup_js_1.ChRISResourceGroup {
|
|
|
50
84
|
errorStack_js_1.errorStack.stack_push('error', 'Not connected to ChRIS. Please log in.');
|
|
51
85
|
return (0, result_js_1.Err)();
|
|
52
86
|
}
|
|
87
|
+
const items = groupItems_for(client);
|
|
53
88
|
const groups = await (0, contract_js_1.listPages_drain)(async (offset, limit) => {
|
|
54
89
|
const page = await client.getGroups({ limit, offset });
|
|
90
|
+
// The items are kept by id: a group's members are read off its item.
|
|
91
|
+
const served = typeof page.getItems === 'function' ? (0, adapter_js_1.items_get)(page) : [];
|
|
92
|
+
for (const item of served) {
|
|
93
|
+
const data = (0, adapter_js_1.itemData_get)(item);
|
|
94
|
+
if (data && typeof data.id === 'number')
|
|
95
|
+
items.set(data.id, item);
|
|
96
|
+
}
|
|
55
97
|
return (0, contract_js_1.collectionPage_wrap)(page, (0, adapter_js_1.listData_get)(page));
|
|
56
98
|
});
|
|
57
99
|
return (0, result_js_1.Ok)(groups);
|
|
@@ -78,9 +120,14 @@ class ChRISGroupGroup extends chrisResourceGroup_js_1.ChRISResourceGroup {
|
|
|
78
120
|
const page = await group.getUsers({ limit, offset });
|
|
79
121
|
return (0, contract_js_1.collectionPage_wrap)(page, (0, adapter_js_1.items_get)(page));
|
|
80
122
|
})) {
|
|
81
|
-
//
|
|
82
|
-
//
|
|
123
|
+
// A membership row names its user (user_id, user_username): the
|
|
124
|
+
// page is the answer. The linked user is fetched only for a row
|
|
125
|
+
// that does not — the boot's group warm once asked CUBE for every
|
|
126
|
+
// member one by one (51 requests on the dev CUBE).
|
|
83
127
|
const pageMembers = await Promise.all(step.items.map(async (membershipResource) => {
|
|
128
|
+
const named = membershipMember_read(membershipResource);
|
|
129
|
+
if (named)
|
|
130
|
+
return named;
|
|
84
131
|
const userResource = await membershipResource.getUser();
|
|
85
132
|
const member = (0, adapter_js_1.itemData_get)(userResource);
|
|
86
133
|
if (!member)
|
|
@@ -110,8 +157,8 @@ class ChRISGroupGroup extends chrisResourceGroup_js_1.ChRISResourceGroup {
|
|
|
110
157
|
if (!group)
|
|
111
158
|
return (0, result_js_1.Err)();
|
|
112
159
|
const membershipResource = await group.adminAddUser(username);
|
|
113
|
-
const
|
|
114
|
-
|
|
160
|
+
const membership = membershipMember_read(membershipResource)
|
|
161
|
+
?? (0, adapter_js_1.itemData_get)(await membershipResource.getUser());
|
|
115
162
|
if (!membership) {
|
|
116
163
|
errorStack_js_1.errorStack.stack_push('error', `CUBE returned no membership for ${username}.`);
|
|
117
164
|
return (0, result_js_1.Err)();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chrisGroups.js","sourceRoot":"","sources":["../../src/groups/chrisGroups.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;
|
|
1
|
+
{"version":3,"file":"chrisGroups.js","sourceRoot":"","sources":["../../src/groups/chrisGroups.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -25,10 +25,12 @@ export * from "./resources/chrisResourceGroup";
|
|
|
25
25
|
export * from "./resources/chrisObjContext";
|
|
26
26
|
export * from "./resources/chrisEmbeddedResourceGroup";
|
|
27
27
|
export * from "./filebrowser/chrisFileBrowser";
|
|
28
|
+
export * from "./filebrowser/folderMemo";
|
|
28
29
|
export * from "./filebrowser/chrisFiles";
|
|
29
30
|
export * from "./filebrowser/chrisPACS";
|
|
30
31
|
export * from "./filebrowser/chrisPipeline";
|
|
31
32
|
export * from "./io/chrisIO";
|
|
33
|
+
export * from "./net/requestLedger";
|
|
32
34
|
export * from "./io/io";
|
|
33
35
|
export * from "./io/node_io";
|
|
34
36
|
export * from "./utils/keypair";
|
|
@@ -39,6 +41,7 @@ export * from "./utils/envelope";
|
|
|
39
41
|
export * from "./cache";
|
|
40
42
|
export * from "./path/chrisPath";
|
|
41
43
|
export * from "./pipelines/chrisPipeline";
|
|
44
|
+
export * from "./pipelines/pipelineMemo";
|
|
42
45
|
export * from "./users/chrisUsers";
|
|
43
46
|
export * from "./tags/chrisTags";
|
|
44
47
|
export * from "./groups/chrisGroups";
|
package/dist/index.js
CHANGED
|
@@ -43,10 +43,12 @@ __exportStar(require("./resources/chrisResourceGroup"), exports);
|
|
|
43
43
|
__exportStar(require("./resources/chrisObjContext"), exports);
|
|
44
44
|
__exportStar(require("./resources/chrisEmbeddedResourceGroup"), exports);
|
|
45
45
|
__exportStar(require("./filebrowser/chrisFileBrowser"), exports);
|
|
46
|
+
__exportStar(require("./filebrowser/folderMemo"), exports);
|
|
46
47
|
__exportStar(require("./filebrowser/chrisFiles"), exports);
|
|
47
48
|
__exportStar(require("./filebrowser/chrisPACS"), exports);
|
|
48
49
|
__exportStar(require("./filebrowser/chrisPipeline"), exports);
|
|
49
50
|
__exportStar(require("./io/chrisIO"), exports);
|
|
51
|
+
__exportStar(require("./net/requestLedger"), exports);
|
|
50
52
|
__exportStar(require("./io/io"), exports);
|
|
51
53
|
__exportStar(require("./io/node_io"), exports);
|
|
52
54
|
__exportStar(require("./utils/keypair"), exports);
|
|
@@ -57,6 +59,7 @@ __exportStar(require("./utils/envelope"), exports);
|
|
|
57
59
|
__exportStar(require("./cache"), exports);
|
|
58
60
|
__exportStar(require("./path/chrisPath"), exports);
|
|
59
61
|
__exportStar(require("./pipelines/chrisPipeline"), exports);
|
|
62
|
+
__exportStar(require("./pipelines/pipelineMemo"), exports);
|
|
60
63
|
__exportStar(require("./users/chrisUsers"), exports);
|
|
61
64
|
__exportStar(require("./tags/chrisTags"), exports);
|
|
62
65
|
__exportStar(require("./groups/chrisGroups"), exports);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAEA;;;;;;;;GAQG;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAEA;;;;;;;;GAQG;;;;;;;;;;;;;;;;;AA0HH,4CAKC;AA7HD,qCAAmB;AACnB,qDAAmC;AACnC,kDAAgC;AAChC,oDAAkC;AAClC,4DAA0C;AAC1C,yDAAuC;AACvC,yDAAuC;AACvC,mEAAiD;AACjD,6DAA2C;AAC3C,mDAAiC;AACjC,yDAAuC;AACvC,sDAAoC;AACpC,6DAA2C;AAC3C,iEAA+C;AAC/C,8DAA4C;AAC5C,yEAAuD;AACvD,iEAA+C;AAC/C,2DAAyC;AACzC,2DAAyC;AACzC,0DAAwC;AACxC,8DAA4C;AAC5C,+CAA6B;AAC7B,sDAAoC;AACpC,0CAAwB;AACxB,+CAA6B;AAC7B,kDAAgC;AAChC,qDAAmC;AACnC,iDAA+B;AAC/B,gDAA8B;AAC9B,mDAAiC;AACjC,0CAAwB;AACxB,mDAAiC;AACjC,4DAA0C;AAC1C,2DAAyC;AACzC,qDAAmC;AACnC,mDAAiC;AACjC,uDAAqC;AACrC,iEAA+C;AAC/C,6DAA2C;AAC3C,yDAAuC;AAEvC,iFAAiF;AACjF,8CAA2E;AAAlE,uGAAA,YAAY,OAAA;AAAE,uGAAA,YAAY,OAAA;AAAE,oGAAA,SAAS,OAAA;AAC9C,wCAAiH;AAAxG,gGAAA,QAAQ,OAAA;AAAE,iGAAA,SAAS,OAAA;AAAE,kGAAA,UAAU,OAAA;AAAE,yGAAA,iBAAiB,OAAA;AAAE,mGAAA,WAAW,OAAA;AAExE,yEAAyE;AACzE,0EAA0E;AAC1E,gDAc6B;AAb3B,yGAAA,aAAa,OAAA;AACb,+GAAA,mBAAmB,OAAA;AACnB,mHAAA,uBAAuB,OAAA;AACvB,8GAAA,kBAAkB,OAAA;AAClB,uGAAA,WAAW,OAAA;AACX,kHAAA,sBAAsB,OAAA;AACtB,wGAAA,YAAY,OAAA;AACZ,uHAAA,2BAA2B,OAAA;AAC3B,qHAAA,yBAAyB,OAAA;AACzB,gHAAA,oBAAoB,OAAA;AACpB,0GAAA,cAAc,OAAA;AAAE,kHAAA,sBAAsB,OAAA;AACtC,2GAAA,eAAe,OAAA;AACf,+GAAA,mBAAmB,OAAA;AAsDrB;;;;;GAKG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,OAAO,GAAW,OAAO,CAAC;IAChC,wCAAwC;IACxC,6CAA6C;IAC7C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAC5B,CAAC;AAED,gEAAgE;AAChE,qCAAqC;AACrC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** One request as the ledger saw it. */
|
|
2
|
+
export interface LedgerEntry {
|
|
3
|
+
/** Milliseconds since the ledger started. */
|
|
4
|
+
at: number;
|
|
5
|
+
method: string;
|
|
6
|
+
/** The endpoint family: `filebrowser/search`, `filebrowser/:id/children`, `plugins`, … */
|
|
7
|
+
family: string;
|
|
8
|
+
/** The path with its query, as sent (capped). */
|
|
9
|
+
path: string;
|
|
10
|
+
/** The response status; 0 when the request failed before one. */
|
|
11
|
+
status: number;
|
|
12
|
+
/** How long it took, request to response headers. */
|
|
13
|
+
ms: number;
|
|
14
|
+
}
|
|
15
|
+
/** A family's tally. */
|
|
16
|
+
export interface LedgerFamily {
|
|
17
|
+
family: string;
|
|
18
|
+
count: number;
|
|
19
|
+
/** Milliseconds spent, summed. */
|
|
20
|
+
ms: number;
|
|
21
|
+
}
|
|
22
|
+
/** What `netstat` reads out. */
|
|
23
|
+
export interface LedgerSnapshot {
|
|
24
|
+
/** When the count began: the session, or the last reset (ISO 8601). */
|
|
25
|
+
since: string;
|
|
26
|
+
/** Requests since then. */
|
|
27
|
+
total: number;
|
|
28
|
+
/** Milliseconds spent on them, summed. */
|
|
29
|
+
ms: number;
|
|
30
|
+
/** By family, most requests first. */
|
|
31
|
+
families: LedgerFamily[];
|
|
32
|
+
/** The last requests, oldest first. */
|
|
33
|
+
last: LedgerEntry[];
|
|
34
|
+
}
|
|
35
|
+
/** How many entries the ledger keeps. */
|
|
36
|
+
export declare const LEDGER_KEPT: number;
|
|
37
|
+
/**
|
|
38
|
+
* Names a request's endpoint family: the path below `/api/v1/`, ids
|
|
39
|
+
* replaced by `:id`, the query dropped, trailing slash dropped.
|
|
40
|
+
*
|
|
41
|
+
* @param path - The request path, query and all.
|
|
42
|
+
* @returns The family, or `root` for the API root.
|
|
43
|
+
*/
|
|
44
|
+
export declare function requestFamily_of(path: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Notes a request as it starts.
|
|
47
|
+
*
|
|
48
|
+
* @param request - The request object the channel carried (the key).
|
|
49
|
+
* @param method - Its method.
|
|
50
|
+
* @param path - Its path.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ledger_requestStart(request: object, method: string, path: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Notes a request as it ends, however it ended.
|
|
55
|
+
*
|
|
56
|
+
* @param request - The request object the channel carried.
|
|
57
|
+
* @param status - The response status, or 0 for a failure.
|
|
58
|
+
*/
|
|
59
|
+
export declare function ledger_requestEnd(request: object, status: number): void;
|
|
60
|
+
/**
|
|
61
|
+
* Starts listening. Idempotent: the connection calls it as it is made,
|
|
62
|
+
* and a second call changes nothing.
|
|
63
|
+
*/
|
|
64
|
+
export declare function requestLedger_start(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Forgets the count: the next snapshot counts from now. The last entries
|
|
67
|
+
* are kept so a reset mid-navigation still shows what just happened.
|
|
68
|
+
*/
|
|
69
|
+
export declare function requestLedger_reset(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Reads the ledger.
|
|
72
|
+
*
|
|
73
|
+
* @param options - `last`: how many recent entries to carry (default 20).
|
|
74
|
+
* @returns The snapshot, families most-requested first.
|
|
75
|
+
*/
|
|
76
|
+
export declare function requestLedger_snapshot(options?: {
|
|
77
|
+
last?: number;
|
|
78
|
+
}): LedgerSnapshot;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LEDGER_KEPT = void 0;
|
|
4
|
+
exports.requestFamily_of = requestFamily_of;
|
|
5
|
+
exports.ledger_requestStart = ledger_requestStart;
|
|
6
|
+
exports.ledger_requestEnd = ledger_requestEnd;
|
|
7
|
+
exports.requestLedger_start = requestLedger_start;
|
|
8
|
+
exports.requestLedger_reset = requestLedger_reset;
|
|
9
|
+
exports.requestLedger_snapshot = requestLedger_snapshot;
|
|
10
|
+
/**
|
|
11
|
+
* @file The request ledger: every call the kernel makes to CUBE, counted.
|
|
12
|
+
*
|
|
13
|
+
* A navigation that felt slow turned out to be fourteen requests where
|
|
14
|
+
* four would do, and nothing in the session could say so — the count was
|
|
15
|
+
* found with a preload hack on a private daemon. The ledger makes the
|
|
16
|
+
* wire a readout: how many requests since the session (or the last
|
|
17
|
+
* reset), by endpoint family, with the last ones timed. `netstat` reads
|
|
18
|
+
* it; smoke asserts budgets against it; an operator on a slow day asks
|
|
19
|
+
* it before guessing.
|
|
20
|
+
*
|
|
21
|
+
* It listens on Node's own diagnostics channels — the http client (what
|
|
22
|
+
* axios rides) and undici (what fetch rides) — so nothing is patched and
|
|
23
|
+
* every caller is seen.
|
|
24
|
+
*/
|
|
25
|
+
const node_diagnostics_channel_1 = require("node:diagnostics_channel");
|
|
26
|
+
/** How many entries the ledger keeps. */
|
|
27
|
+
exports.LEDGER_KEPT = 200;
|
|
28
|
+
/** A path longer than this is cut, with an ellipsis. */
|
|
29
|
+
const PATH_KEPT = 160;
|
|
30
|
+
let started = false;
|
|
31
|
+
let origin = Date.now();
|
|
32
|
+
let since = new Date(origin).toISOString();
|
|
33
|
+
let total = 0;
|
|
34
|
+
let msTotal = 0;
|
|
35
|
+
const families = new Map();
|
|
36
|
+
const entries = [];
|
|
37
|
+
const open = new WeakMap();
|
|
38
|
+
/**
|
|
39
|
+
* Names a request's endpoint family: the path below `/api/v1/`, ids
|
|
40
|
+
* replaced by `:id`, the query dropped, trailing slash dropped.
|
|
41
|
+
*
|
|
42
|
+
* @param path - The request path, query and all.
|
|
43
|
+
* @returns The family, or `root` for the API root.
|
|
44
|
+
*/
|
|
45
|
+
function requestFamily_of(path) {
|
|
46
|
+
const bare = path.split('?')[0] ?? '';
|
|
47
|
+
const below = bare.replace(/^.*?\/api\/v1\/?/, '').replace(/\/+$/, '');
|
|
48
|
+
if (below === '')
|
|
49
|
+
return 'root';
|
|
50
|
+
return below.split('/').map((segment) => (/^\d+$/.test(segment) ? ':id' : segment)).join('/');
|
|
51
|
+
}
|
|
52
|
+
const path_cap = (path) => (path.length > PATH_KEPT ? `${path.slice(0, PATH_KEPT - 1)}…` : path);
|
|
53
|
+
function entry_record(method, path, status, t0) {
|
|
54
|
+
const ms = Math.round(performance.now() - t0);
|
|
55
|
+
const family = requestFamily_of(path);
|
|
56
|
+
total += 1;
|
|
57
|
+
msTotal += ms;
|
|
58
|
+
const tally = families.get(family) ?? { family, count: 0, ms: 0 };
|
|
59
|
+
tally.count += 1;
|
|
60
|
+
tally.ms += ms;
|
|
61
|
+
families.set(family, tally);
|
|
62
|
+
entries.push({ at: Date.now() - origin, method, family, path: path_cap(path), status, ms });
|
|
63
|
+
if (entries.length > exports.LEDGER_KEPT)
|
|
64
|
+
entries.splice(0, entries.length - exports.LEDGER_KEPT);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Notes a request as it starts.
|
|
68
|
+
*
|
|
69
|
+
* @param request - The request object the channel carried (the key).
|
|
70
|
+
* @param method - Its method.
|
|
71
|
+
* @param path - Its path.
|
|
72
|
+
*/
|
|
73
|
+
function ledger_requestStart(request, method, path) {
|
|
74
|
+
open.set(request, { t0: performance.now(), method, path });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Notes a request as it ends, however it ended.
|
|
78
|
+
*
|
|
79
|
+
* @param request - The request object the channel carried.
|
|
80
|
+
* @param status - The response status, or 0 for a failure.
|
|
81
|
+
*/
|
|
82
|
+
function ledger_requestEnd(request, status) {
|
|
83
|
+
const begun = open.get(request);
|
|
84
|
+
if (begun === undefined)
|
|
85
|
+
return;
|
|
86
|
+
open.delete(request);
|
|
87
|
+
entry_record(begun.method, begun.path, status, begun.t0);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Starts listening. Idempotent: the connection calls it as it is made,
|
|
91
|
+
* and a second call changes nothing.
|
|
92
|
+
*/
|
|
93
|
+
function requestLedger_start() {
|
|
94
|
+
if (started)
|
|
95
|
+
return;
|
|
96
|
+
started = true;
|
|
97
|
+
const method_of = (request) => (typeof request.method === 'string' ? request.method : 'GET');
|
|
98
|
+
const path_of = (request) => (typeof request.path === 'string' ? request.path : '');
|
|
99
|
+
(0, node_diagnostics_channel_1.subscribe)('http.client.request.start', (message) => {
|
|
100
|
+
const { request } = message;
|
|
101
|
+
if (request !== undefined && request !== null)
|
|
102
|
+
ledger_requestStart(request, method_of(request), path_of(request));
|
|
103
|
+
});
|
|
104
|
+
(0, node_diagnostics_channel_1.subscribe)('http.client.response.finish', (message) => {
|
|
105
|
+
const { request, response } = message;
|
|
106
|
+
if (request !== undefined && request !== null)
|
|
107
|
+
ledger_requestEnd(request, typeof response?.statusCode === 'number' ? response.statusCode : 0);
|
|
108
|
+
});
|
|
109
|
+
(0, node_diagnostics_channel_1.subscribe)('http.client.request.error', (message) => {
|
|
110
|
+
const { request } = message;
|
|
111
|
+
if (request !== undefined && request !== null)
|
|
112
|
+
ledger_requestEnd(request, 0);
|
|
113
|
+
});
|
|
114
|
+
(0, node_diagnostics_channel_1.subscribe)('undici:request:create', (message) => {
|
|
115
|
+
const { request } = message;
|
|
116
|
+
if (request !== undefined && request !== null)
|
|
117
|
+
ledger_requestStart(request, method_of(request), path_of(request));
|
|
118
|
+
});
|
|
119
|
+
(0, node_diagnostics_channel_1.subscribe)('undici:request:headers', (message) => {
|
|
120
|
+
const { request, response } = message;
|
|
121
|
+
if (request !== undefined && request !== null)
|
|
122
|
+
ledger_requestEnd(request, typeof response?.statusCode === 'number' ? response.statusCode : 0);
|
|
123
|
+
});
|
|
124
|
+
(0, node_diagnostics_channel_1.subscribe)('undici:request:error', (message) => {
|
|
125
|
+
const { request } = message;
|
|
126
|
+
if (request !== undefined && request !== null)
|
|
127
|
+
ledger_requestEnd(request, 0);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Forgets the count: the next snapshot counts from now. The last entries
|
|
132
|
+
* are kept so a reset mid-navigation still shows what just happened.
|
|
133
|
+
*/
|
|
134
|
+
function requestLedger_reset() {
|
|
135
|
+
origin = Date.now();
|
|
136
|
+
since = new Date(origin).toISOString();
|
|
137
|
+
total = 0;
|
|
138
|
+
msTotal = 0;
|
|
139
|
+
families.clear();
|
|
140
|
+
entries.length = 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reads the ledger.
|
|
144
|
+
*
|
|
145
|
+
* @param options - `last`: how many recent entries to carry (default 20).
|
|
146
|
+
* @returns The snapshot, families most-requested first.
|
|
147
|
+
*/
|
|
148
|
+
function requestLedger_snapshot(options = {}) {
|
|
149
|
+
const last = Math.max(0, Math.min(exports.LEDGER_KEPT, options.last ?? 20));
|
|
150
|
+
return {
|
|
151
|
+
since,
|
|
152
|
+
total,
|
|
153
|
+
ms: msTotal,
|
|
154
|
+
families: [...families.values()].sort((a, b) => b.count - a.count || a.family.localeCompare(b.family)),
|
|
155
|
+
last: entries.slice(-last),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=requestLedger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestLedger.js","sourceRoot":"","sources":["../../src/net/requestLedger.ts"],"names":[],"mappings":"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"}
|
package/dist/pacs/chrisPACS.d.ts
CHANGED
|
@@ -208,6 +208,42 @@ export interface SeriesStorageState {
|
|
|
208
208
|
/** Display folder path (leading slash), or null when nothing arrived. */
|
|
209
209
|
folderPath: string | null;
|
|
210
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* What CUBE's own series record says of a series stored in its PACS folder:
|
|
213
|
+
* its modality and description, no file read.
|
|
214
|
+
*
|
|
215
|
+
* @property modality - The series' modality, when recorded.
|
|
216
|
+
* @property seriesDescription - The series' description, when recorded.
|
|
217
|
+
*/
|
|
218
|
+
export interface PACSSeriesFacts {
|
|
219
|
+
modality?: string;
|
|
220
|
+
seriesDescription?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The series folder a path lies in, when it lies in CUBE's PACS store
|
|
224
|
+
* (`SERVICES/PACS/<pacs>/<patient>/<study>/<series>/...`).
|
|
225
|
+
*
|
|
226
|
+
* @param path - A CFS path, with or without its leading slash.
|
|
227
|
+
* @returns The series folder (no leading slash), its PACS and its patient folder; null elsewhere.
|
|
228
|
+
*/
|
|
229
|
+
export declare function pacsSeriesFolder_of(path: string): {
|
|
230
|
+
folder: string;
|
|
231
|
+
pacs: string;
|
|
232
|
+
patient: string;
|
|
233
|
+
} | null;
|
|
234
|
+
/**
|
|
235
|
+
* What CUBE's series record says of the series a PACS path lies in. CUBE
|
|
236
|
+
* cannot be asked by folder, but a patient folder begins with the patient's
|
|
237
|
+
* ID, and the series list answers by patient: the patient's series are
|
|
238
|
+
* asked for once, and the folder matched exactly among them. A path outside
|
|
239
|
+
* the PACS store, a folder not found, or a failed ask is none.
|
|
240
|
+
*
|
|
241
|
+
* @param path - A CFS path in the PACS store.
|
|
242
|
+
* @returns The series' facts, or null.
|
|
243
|
+
*/
|
|
244
|
+
export declare function pacsSeriesFacts_ofPath(path: string): Promise<PACSSeriesFacts | null>;
|
|
245
|
+
/** Forgets the series records fetched: a test, or a new identity. */
|
|
246
|
+
export declare function pacsSeriesFacts_clear(): void;
|
|
211
247
|
/**
|
|
212
248
|
* Resolves a series' CUBE storage state: its registered file count and the
|
|
213
249
|
* folder it landed in.
|
package/dist/pacs/chrisPACS.js
CHANGED
|
@@ -20,6 +20,9 @@ exports.pacsQuery_get = pacsQuery_get;
|
|
|
20
20
|
exports.pacsRetrieve_create = pacsRetrieve_create;
|
|
21
21
|
exports.pacsRetrieves_list = pacsRetrieves_list;
|
|
22
22
|
exports.pacsRetrieve_delete = pacsRetrieve_delete;
|
|
23
|
+
exports.pacsSeriesFolder_of = pacsSeriesFolder_of;
|
|
24
|
+
exports.pacsSeriesFacts_ofPath = pacsSeriesFacts_ofPath;
|
|
25
|
+
exports.pacsSeriesFacts_clear = pacsSeriesFacts_clear;
|
|
23
26
|
exports.seriesStorage_resolve = seriesStorage_resolve;
|
|
24
27
|
exports.seriesStorage_resolveMany = seriesStorage_resolveMany;
|
|
25
28
|
exports.pacsRetrieve_statusForQuery = pacsRetrieve_statusForQuery;
|
|
@@ -380,6 +383,87 @@ async function pacsRetrieve_delete(retrieveId) {
|
|
|
380
383
|
return (0, result_js_1.Err)();
|
|
381
384
|
}
|
|
382
385
|
}
|
|
386
|
+
/** Series records already fetched, by PACS and patient folder: one ask serves every series of a patient. */
|
|
387
|
+
const seriesByPatient = new Map();
|
|
388
|
+
/**
|
|
389
|
+
* The series folder a path lies in, when it lies in CUBE's PACS store
|
|
390
|
+
* (`SERVICES/PACS/<pacs>/<patient>/<study>/<series>/...`).
|
|
391
|
+
*
|
|
392
|
+
* @param path - A CFS path, with or without its leading slash.
|
|
393
|
+
* @returns The series folder (no leading slash), its PACS and its patient folder; null elsewhere.
|
|
394
|
+
*/
|
|
395
|
+
function pacsSeriesFolder_of(path) {
|
|
396
|
+
const parts = path.replace(/^\/+/, '').split('/');
|
|
397
|
+
if (parts.length < 6 || parts[0] !== 'SERVICES' || parts[1] !== 'PACS')
|
|
398
|
+
return null;
|
|
399
|
+
const [, , pacs, patient] = parts;
|
|
400
|
+
if (pacs.length === 0 || patient.length === 0)
|
|
401
|
+
return null;
|
|
402
|
+
return { folder: parts.slice(0, 6).join('/'), pacs, patient };
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* What CUBE's series record says of the series a PACS path lies in. CUBE
|
|
406
|
+
* cannot be asked by folder, but a patient folder begins with the patient's
|
|
407
|
+
* ID, and the series list answers by patient: the patient's series are
|
|
408
|
+
* asked for once, and the folder matched exactly among them. A path outside
|
|
409
|
+
* the PACS store, a folder not found, or a failed ask is none.
|
|
410
|
+
*
|
|
411
|
+
* @param path - A CFS path in the PACS store.
|
|
412
|
+
* @returns The series' facts, or null.
|
|
413
|
+
*/
|
|
414
|
+
async function pacsSeriesFacts_ofPath(path) {
|
|
415
|
+
const at = pacsSeriesFolder_of(path);
|
|
416
|
+
if (at === null)
|
|
417
|
+
return null;
|
|
418
|
+
const patientID = at.patient.split('-')[0] ?? '';
|
|
419
|
+
if (patientID.length === 0)
|
|
420
|
+
return null;
|
|
421
|
+
const key = `${at.pacs}/${at.patient}`;
|
|
422
|
+
let asked = seriesByPatient.get(key);
|
|
423
|
+
if (asked === undefined) {
|
|
424
|
+
asked = patientSeries_fetch(at.pacs, patientID).catch(() => {
|
|
425
|
+
// A failed ask is forgotten, so the next feed of this patient asks again.
|
|
426
|
+
seriesByPatient.delete(key);
|
|
427
|
+
return new Map();
|
|
428
|
+
});
|
|
429
|
+
seriesByPatient.set(key, asked);
|
|
430
|
+
}
|
|
431
|
+
return (await asked).get(at.folder) ?? null;
|
|
432
|
+
}
|
|
433
|
+
/** Every series CUBE records for one patient in one PACS, by folder: only modality and description kept. */
|
|
434
|
+
async function patientSeries_fetch(pacs, patientID) {
|
|
435
|
+
const found = new Map();
|
|
436
|
+
const client = await chrisConnection_js_1.chrisConnection.client_get();
|
|
437
|
+
if (!client)
|
|
438
|
+
return found;
|
|
439
|
+
for (let offset = 0;; offset += SERIES_PAGE) {
|
|
440
|
+
const page = await client.getPACSSeriesList({ PatientID: patientID, pacs_identifier: pacs, limit: SERIES_PAGE, offset });
|
|
441
|
+
const items = page.getItems();
|
|
442
|
+
if (!items || items.length === 0)
|
|
443
|
+
break;
|
|
444
|
+
for (const item of items) {
|
|
445
|
+
const data = (0, adapter_js_1.itemData_get)(item);
|
|
446
|
+
const folder = data?.folder_path?.replace(/^\/+/, '');
|
|
447
|
+
if (!folder)
|
|
448
|
+
continue;
|
|
449
|
+
const facts = {};
|
|
450
|
+
const modality = (data?.Modality ?? '').trim();
|
|
451
|
+
const description = (data?.SeriesDescription ?? '').trim();
|
|
452
|
+
if (modality.length > 0)
|
|
453
|
+
facts.modality = modality;
|
|
454
|
+
if (description.length > 0)
|
|
455
|
+
facts.seriesDescription = description;
|
|
456
|
+
found.set(folder, facts);
|
|
457
|
+
}
|
|
458
|
+
if (items.length < SERIES_PAGE)
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
return found;
|
|
462
|
+
}
|
|
463
|
+
/** Forgets the series records fetched: a test, or a new identity. */
|
|
464
|
+
function pacsSeriesFacts_clear() {
|
|
465
|
+
seriesByPatient.clear();
|
|
466
|
+
}
|
|
383
467
|
/**
|
|
384
468
|
* Resolves a series' CUBE storage state: its registered file count and the
|
|
385
469
|
* folder it landed in.
|