@fnndsc/cumin 3.17.1 → 3.18.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/index.d.ts +4 -0
- package/dist/cache/index.js +12 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/queryIndex.d.ts +207 -0
- package/dist/cache/queryIndex.js +314 -0
- package/dist/cache/queryIndex.js.map +1 -0
- package/dist/cache/queryIndexCheckpoint.d.ts +55 -0
- package/dist/cache/queryIndexCheckpoint.js +159 -0
- package/dist/cache/queryIndexCheckpoint.js.map +1 -0
- package/dist/chrisapi/contract.d.ts +75 -0
- package/dist/chrisapi/contract.js +60 -0
- package/dist/chrisapi/contract.js.map +1 -1
- package/dist/feeds/chrisFeed.js +17 -5
- package/dist/feeds/chrisFeed.js.map +1 -1
- package/dist/filebrowser/chrisPipeline.js +8 -1
- package/dist/filebrowser/chrisPipeline.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/io/chrisIO.d.ts +26 -0
- package/dist/io/chrisIO.js +83 -7
- package/dist/io/chrisIO.js.map +1 -1
- package/dist/pacs/chrisPACS.d.ts +8 -0
- package/dist/pacs/chrisPACS.js.map +1 -1
- package/dist/pacs/queryIndexSweep.d.ts +69 -0
- package/dist/pacs/queryIndexSweep.js +159 -0
- package/dist/pacs/queryIndexSweep.js.map +1 -0
- package/dist/resources/chrisResources.d.ts +12 -0
- package/dist/resources/chrisResources.js +53 -3
- package/dist/resources/chrisResources.js.map +1 -1
- package/package.json +2 -2
package/dist/cache/index.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export { ListCache, listCache_get, LIST_CACHE_SIGNAL_BACKED_TTL_MS, LIST_CACHE_S
|
|
|
7
7
|
export type { CacheStats, CacheResult, CacheOptions, ListCacheSnapshot, ListCacheEntrySnapshot } from './listCache.js';
|
|
8
8
|
export { listCheckpointPath_get, listCheckpoint_restore, listCheckpoint_save, listCheckpoint_watch, } from './listCheckpoint.js';
|
|
9
9
|
export type { ListCheckpointRestoreResult } from './listCheckpoint.js';
|
|
10
|
+
export { QueryIndex, queryIndex_get, queryKey_build, queryCriteria_parse, } from './queryIndex.js';
|
|
11
|
+
export type { QueryIndexEntry, QueryIndexEntrySnapshot, QueryIndexSnapshot, QueryIndexListener, } from './queryIndex.js';
|
|
12
|
+
export { queryIndexCheckpointPath_get, queryIndexCheckpoint_restore, queryIndexCheckpoint_save, queryIndexCheckpoint_watch, QUERY_INDEX_CHECKPOINT_FLOOR_MS, } from './queryIndexCheckpoint.js';
|
|
13
|
+
export type { QueryIndexRestoreResult } from './queryIndexCheckpoint.js';
|
|
10
14
|
export { listingsForFeeds_note, listingsForFeeds_drop, listingsForRoster_note, listingInvalidation_flush, listingInvalidation_reset, LISTING_INVALIDATION_WINDOW_MS, } from './listingInvalidation.js';
|
|
11
15
|
export { ProcCache, procCache_get, status_isTerminal, feed_isActive, feedTopology_changed, PROC_TERMINAL_STATUSES, PROC_ARRIVAL_TTL_MS } from './procCache.js';
|
|
12
16
|
export type { RosterFolders, ProcInstance, ProcFeed, ProcFeedScopeCounts, ProcWarmupProgress, ProcFeedLoadProgress, ProcCacheState, ProcCacheLifecycle, ProcCacheSnapshot, ProcFeedSnapshot, ProcCacheChange, ProcCacheListener, } from './procCache.js';
|
package/dist/cache/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.procCheckpoint_watch = exports.procCheckpointRoster_save = exports.procCheckpointFeed_save = exports.procCheckpoint_save = exports.procCheckpoint_restore = exports.procCheckpointDir_get = exports.procCheckpointPath_get = exports.PROC_ARRIVAL_TTL_MS = exports.PROC_TERMINAL_STATUSES = exports.feedTopology_changed = exports.feed_isActive = exports.status_isTerminal = exports.procCache_get = exports.ProcCache = exports.LISTING_INVALIDATION_WINDOW_MS = exports.listingInvalidation_reset = exports.listingInvalidation_flush = exports.listingsForRoster_note = exports.listingsForFeeds_drop = exports.listingsForFeeds_note = exports.listCheckpoint_watch = exports.listCheckpoint_save = exports.listCheckpoint_restore = exports.listCheckpointPath_get = exports.LIST_CACHE_MAX_ENTRIES = exports.LIST_CACHE_PLUGIN_INDEX_TTL_MS = exports.LIST_CACHE_SIGNAL_FREE_TTL_MS = exports.LIST_CACHE_SIGNAL_BACKED_TTL_MS = exports.listCache_get = exports.ListCache = void 0;
|
|
3
|
+
exports.procCheckpoint_watch = exports.procCheckpointRoster_save = exports.procCheckpointFeed_save = exports.procCheckpoint_save = exports.procCheckpoint_restore = exports.procCheckpointDir_get = exports.procCheckpointPath_get = exports.PROC_ARRIVAL_TTL_MS = exports.PROC_TERMINAL_STATUSES = exports.feedTopology_changed = exports.feed_isActive = exports.status_isTerminal = exports.procCache_get = exports.ProcCache = exports.LISTING_INVALIDATION_WINDOW_MS = exports.listingInvalidation_reset = exports.listingInvalidation_flush = exports.listingsForRoster_note = exports.listingsForFeeds_drop = exports.listingsForFeeds_note = exports.QUERY_INDEX_CHECKPOINT_FLOOR_MS = exports.queryIndexCheckpoint_watch = exports.queryIndexCheckpoint_save = exports.queryIndexCheckpoint_restore = exports.queryIndexCheckpointPath_get = exports.queryCriteria_parse = exports.queryKey_build = exports.queryIndex_get = exports.QueryIndex = exports.listCheckpoint_watch = exports.listCheckpoint_save = exports.listCheckpoint_restore = exports.listCheckpointPath_get = exports.LIST_CACHE_MAX_ENTRIES = exports.LIST_CACHE_PLUGIN_INDEX_TTL_MS = exports.LIST_CACHE_SIGNAL_FREE_TTL_MS = exports.LIST_CACHE_SIGNAL_BACKED_TTL_MS = exports.listCache_get = exports.ListCache = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @file Cache module exports.
|
|
6
6
|
*
|
|
@@ -18,6 +18,17 @@ Object.defineProperty(exports, "listCheckpointPath_get", { enumerable: true, get
|
|
|
18
18
|
Object.defineProperty(exports, "listCheckpoint_restore", { enumerable: true, get: function () { return listCheckpoint_js_1.listCheckpoint_restore; } });
|
|
19
19
|
Object.defineProperty(exports, "listCheckpoint_save", { enumerable: true, get: function () { return listCheckpoint_js_1.listCheckpoint_save; } });
|
|
20
20
|
Object.defineProperty(exports, "listCheckpoint_watch", { enumerable: true, get: function () { return listCheckpoint_js_1.listCheckpoint_watch; } });
|
|
21
|
+
var queryIndex_js_1 = require("./queryIndex.js");
|
|
22
|
+
Object.defineProperty(exports, "QueryIndex", { enumerable: true, get: function () { return queryIndex_js_1.QueryIndex; } });
|
|
23
|
+
Object.defineProperty(exports, "queryIndex_get", { enumerable: true, get: function () { return queryIndex_js_1.queryIndex_get; } });
|
|
24
|
+
Object.defineProperty(exports, "queryKey_build", { enumerable: true, get: function () { return queryIndex_js_1.queryKey_build; } });
|
|
25
|
+
Object.defineProperty(exports, "queryCriteria_parse", { enumerable: true, get: function () { return queryIndex_js_1.queryCriteria_parse; } });
|
|
26
|
+
var queryIndexCheckpoint_js_1 = require("./queryIndexCheckpoint.js");
|
|
27
|
+
Object.defineProperty(exports, "queryIndexCheckpointPath_get", { enumerable: true, get: function () { return queryIndexCheckpoint_js_1.queryIndexCheckpointPath_get; } });
|
|
28
|
+
Object.defineProperty(exports, "queryIndexCheckpoint_restore", { enumerable: true, get: function () { return queryIndexCheckpoint_js_1.queryIndexCheckpoint_restore; } });
|
|
29
|
+
Object.defineProperty(exports, "queryIndexCheckpoint_save", { enumerable: true, get: function () { return queryIndexCheckpoint_js_1.queryIndexCheckpoint_save; } });
|
|
30
|
+
Object.defineProperty(exports, "queryIndexCheckpoint_watch", { enumerable: true, get: function () { return queryIndexCheckpoint_js_1.queryIndexCheckpoint_watch; } });
|
|
31
|
+
Object.defineProperty(exports, "QUERY_INDEX_CHECKPOINT_FLOOR_MS", { enumerable: true, get: function () { return queryIndexCheckpoint_js_1.QUERY_INDEX_CHECKPOINT_FLOOR_MS; } });
|
|
21
32
|
var listingInvalidation_js_1 = require("./listingInvalidation.js");
|
|
22
33
|
Object.defineProperty(exports, "listingsForFeeds_note", { enumerable: true, get: function () { return listingInvalidation_js_1.listingsForFeeds_note; } });
|
|
23
34
|
Object.defineProperty(exports, "listingsForFeeds_drop", { enumerable: true, get: function () { return listingInvalidation_js_1.listingsForFeeds_drop; } });
|
package/dist/cache/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,+CAOwB;AANtB,yGAAA,SAAS,OAAA;AACT,6GAAA,aAAa,OAAA;AACb,+HAAA,+BAA+B,OAAA;AAC/B,6HAAA,6BAA6B,OAAA;AAC7B,8HAAA,8BAA8B,OAAA;AAC9B,sHAAA,sBAAsB,OAAA;AAGxB,yDAK6B;AAJ3B,2HAAA,sBAAsB,OAAA;AACtB,2HAAA,sBAAsB,OAAA;AACtB,wHAAA,mBAAmB,OAAA;AACnB,yHAAA,oBAAoB,OAAA;AAGtB,mEAOkC;AANhC,+HAAA,qBAAqB,OAAA;AACrB,+HAAA,qBAAqB,OAAA;AACrB,gIAAA,sBAAsB,OAAA;AACtB,mIAAA,yBAAyB,OAAA;AACzB,mIAAA,yBAAyB,OAAA;AACzB,wIAAA,8BAA8B,OAAA;AAEhC,+CAA+J;AAAtJ,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,iHAAA,iBAAiB,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,oHAAA,oBAAoB,OAAA;AAAE,sHAAA,sBAAsB,OAAA;AAAE,mHAAA,mBAAmB,OAAA;AA0BtI,yDAQ6B;AAP3B,2HAAA,sBAAsB,OAAA;AACtB,0HAAA,qBAAqB,OAAA;AACrB,2HAAA,sBAAsB,OAAA;AACtB,wHAAA,mBAAmB,OAAA;AACnB,4HAAA,uBAAuB,OAAA;AACvB,8HAAA,yBAAyB,OAAA;AACzB,yHAAA,oBAAoB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;AAAA;;;;GAIG;AACH,+CAOwB;AANtB,yGAAA,SAAS,OAAA;AACT,6GAAA,aAAa,OAAA;AACb,+HAAA,+BAA+B,OAAA;AAC/B,6HAAA,6BAA6B,OAAA;AAC7B,8HAAA,8BAA8B,OAAA;AAC9B,sHAAA,sBAAsB,OAAA;AAGxB,yDAK6B;AAJ3B,2HAAA,sBAAsB,OAAA;AACtB,2HAAA,sBAAsB,OAAA;AACtB,wHAAA,mBAAmB,OAAA;AACnB,yHAAA,oBAAoB,OAAA;AAGtB,iDAKyB;AAJvB,2GAAA,UAAU,OAAA;AACV,+GAAA,cAAc,OAAA;AACd,+GAAA,cAAc,OAAA;AACd,oHAAA,mBAAmB,OAAA;AAQrB,qEAMmC;AALjC,uIAAA,4BAA4B,OAAA;AAC5B,uIAAA,4BAA4B,OAAA;AAC5B,oIAAA,yBAAyB,OAAA;AACzB,qIAAA,0BAA0B,OAAA;AAC1B,0IAAA,+BAA+B,OAAA;AAGjC,mEAOkC;AANhC,+HAAA,qBAAqB,OAAA;AACrB,+HAAA,qBAAqB,OAAA;AACrB,gIAAA,sBAAsB,OAAA;AACtB,mIAAA,yBAAyB,OAAA;AACzB,mIAAA,yBAAyB,OAAA;AACzB,wIAAA,8BAA8B,OAAA;AAEhC,+CAA+J;AAAtJ,yGAAA,SAAS,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,iHAAA,iBAAiB,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,oHAAA,oBAAoB,OAAA;AAAE,sHAAA,sBAAsB,OAAA;AAAE,mHAAA,mBAAmB,OAAA;AA0BtI,yDAQ6B;AAP3B,2HAAA,sBAAsB,OAAA;AACtB,0HAAA,qBAAqB,OAAA;AACrB,2HAAA,sBAAsB,OAAA;AACtB,wHAAA,mBAAmB,OAAA;AACnB,4HAAA,uBAAuB,OAAA;AACvB,8HAAA,yBAAyB,OAAA;AACzB,yHAAA,oBAAoB,OAAA"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The PACS query index: what has already been asked, and answered.
|
|
3
|
+
*
|
|
4
|
+
* CUBE keeps every PACS query it has run, criteria and result both, so a
|
|
5
|
+
* question asked before need not be asked again. It cannot be *searched*
|
|
6
|
+
* for, though: the queries collection accepts no parameter over the stored
|
|
7
|
+
* criteria, and every row of every page drags its compressed result along —
|
|
8
|
+
* kilobytes each, thousands of rows. Looking a question up by sweeping that
|
|
9
|
+
* collection would cost megabytes, and would cost them most often on a
|
|
10
|
+
* MISS, which is the common case for a genuinely new query. A client that
|
|
11
|
+
* searched naively would make every new query slower than not searching.
|
|
12
|
+
*
|
|
13
|
+
* So the collection is walked once, in the background, and what is learned
|
|
14
|
+
* is kept here: criteria and server in, the stored query's id out. A lookup
|
|
15
|
+
* is then a map read.
|
|
16
|
+
*
|
|
17
|
+
* The index is ADVISORY. A hit is a claim to be confirmed — the caller
|
|
18
|
+
* decodes the stored result before serving it, and any failure (the query
|
|
19
|
+
* deleted, the result unreadable) falls through to a fresh query. A stale
|
|
20
|
+
* entry can therefore cost a wasted lookup and never a wrong answer, which
|
|
21
|
+
* is what makes it safe to keep across restarts and to fill lazily.
|
|
22
|
+
*
|
|
23
|
+
* It is also the log itself. Each entry carries the criteria and owner the
|
|
24
|
+
* VFS renders into `/net/pacs/queries/<name>`, so that listing can be
|
|
25
|
+
* served from here — complete, rather than the first page of it.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
/** What the index knows about one stored query. */
|
|
30
|
+
export interface QueryIndexEntry {
|
|
31
|
+
/** The stored CUBE query's id — what a replay decodes. */
|
|
32
|
+
queryId: number;
|
|
33
|
+
/** The PACS this was asked of; part of the key, kept for the listing. */
|
|
34
|
+
server: string;
|
|
35
|
+
/** The criteria as asked, for rebuilding the log's display name. */
|
|
36
|
+
criteria: Record<string, string>;
|
|
37
|
+
/** Who asked. Part of the log's display name. */
|
|
38
|
+
owner: string;
|
|
39
|
+
/** When the query record was created — the age a replay states. */
|
|
40
|
+
answeredAt: string;
|
|
41
|
+
/**
|
|
42
|
+
* Whether it found anything.
|
|
43
|
+
*
|
|
44
|
+
* A query that found nothing is indexed *as such* rather than omitted,
|
|
45
|
+
* so a replay can refuse it knowingly. An absence decays where a hit does
|
|
46
|
+
* not: the patient is scanned next week, and "no imaging found" is the
|
|
47
|
+
* answer a clinician acts on.
|
|
48
|
+
*/
|
|
49
|
+
hasResult: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** One entry with the key it is filed under. */
|
|
52
|
+
export interface QueryIndexEntrySnapshot extends QueryIndexEntry {
|
|
53
|
+
key: string;
|
|
54
|
+
}
|
|
55
|
+
/** The whole index, and how far the back-fill has walked. */
|
|
56
|
+
export interface QueryIndexSnapshot {
|
|
57
|
+
entries: QueryIndexEntrySnapshot[];
|
|
58
|
+
/**
|
|
59
|
+
* The oldest record the back-fill has reached, as an ISO timestamp.
|
|
60
|
+
*
|
|
61
|
+
* The sweep walks newest first, so this is where it resumes — everything
|
|
62
|
+
* newer is already held. Null before the first sweep.
|
|
63
|
+
*/
|
|
64
|
+
floor: string | null;
|
|
65
|
+
}
|
|
66
|
+
/** Told after any mutation, so a checkpoint writer can debounce on it. */
|
|
67
|
+
export type QueryIndexListener = () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Builds the key a question is filed under.
|
|
70
|
+
*
|
|
71
|
+
* Two questions are the same question when their criteria sets are equal
|
|
72
|
+
* and they were asked of the same PACS. Key order and surrounding
|
|
73
|
+
* whitespace are normalized away because they are how the question was
|
|
74
|
+
* *typed*, not what it asked. Values are otherwise left exactly as given:
|
|
75
|
+
* they are the PACS's own vocabulary, and mise does not get to reinterpret
|
|
76
|
+
* them — a case fold here would silently claim that two DICOM values match
|
|
77
|
+
* when only the PACS can say so.
|
|
78
|
+
*
|
|
79
|
+
* The owner is part of the question. The index holds the whole log —
|
|
80
|
+
* every identity's queries, because that is what `/net/pacs/queries` lists
|
|
81
|
+
* — while a replay may only ever serve back an answer to the identity that
|
|
82
|
+
* asked for it. Keying by owner makes those two facts one lookup instead of
|
|
83
|
+
* a filter somebody can forget.
|
|
84
|
+
*
|
|
85
|
+
* @param criteria - The criteria as asked.
|
|
86
|
+
* @param server - The PACS identifier the query was asked of.
|
|
87
|
+
* @param owner - The identity that asked.
|
|
88
|
+
* @returns The key, stable for equal questions.
|
|
89
|
+
*/
|
|
90
|
+
export declare function queryKey_build(criteria: Record<string, string>, server: string, owner: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Parses the criteria CUBE stores as a JSON string on a query record.
|
|
93
|
+
*
|
|
94
|
+
* @param raw - The record's `query` field.
|
|
95
|
+
* @returns The criteria, or null when the field is absent or unparseable.
|
|
96
|
+
*/
|
|
97
|
+
export declare function queryCriteria_parse(raw: unknown): Record<string, string> | null;
|
|
98
|
+
/**
|
|
99
|
+
* What has already been asked of which PACS, and where the answer is kept.
|
|
100
|
+
*/
|
|
101
|
+
export declare class QueryIndex {
|
|
102
|
+
/**
|
|
103
|
+
* Every record, by its query id. This is the log: asking the same thing
|
|
104
|
+
* twice leaves two records, and both remain reachable.
|
|
105
|
+
*/
|
|
106
|
+
private entries;
|
|
107
|
+
/**
|
|
108
|
+
* Question to the newest record answering it. This is the lookup: a
|
|
109
|
+
* replay wants the most recent answer, not every answer.
|
|
110
|
+
*/
|
|
111
|
+
private byQuestion;
|
|
112
|
+
private floor;
|
|
113
|
+
private listeners;
|
|
114
|
+
/**
|
|
115
|
+
* Files a query under its question.
|
|
116
|
+
*
|
|
117
|
+
* The newest answer to a question wins: asking the same thing twice
|
|
118
|
+
* leaves the later record indexed, which is the one whose age a replay
|
|
119
|
+
* should state.
|
|
120
|
+
*
|
|
121
|
+
* @param entry - What was asked, of whom, and what came back.
|
|
122
|
+
*/
|
|
123
|
+
entry_note(entry: QueryIndexEntry): void;
|
|
124
|
+
/**
|
|
125
|
+
* Looks a question up.
|
|
126
|
+
*
|
|
127
|
+
* @param criteria - The criteria being asked.
|
|
128
|
+
* @param server - The PACS being asked.
|
|
129
|
+
* @param owner - The identity asking; another identity's answer is not
|
|
130
|
+
* an answer to this question.
|
|
131
|
+
* @returns What is known, or null when this question is new to the index.
|
|
132
|
+
*/
|
|
133
|
+
entry_find(criteria: Record<string, string>, server: string, owner: string): QueryIndexEntry | null;
|
|
134
|
+
/**
|
|
135
|
+
* Forgets one query, for when its stored answer turns out to be gone.
|
|
136
|
+
*
|
|
137
|
+
* @param criteria - The criteria it was filed under.
|
|
138
|
+
* @param server - The PACS it was asked of.
|
|
139
|
+
* @param owner - The identity that asked.
|
|
140
|
+
*/
|
|
141
|
+
entry_drop(criteria: Record<string, string>, server: string, owner: string): void;
|
|
142
|
+
/**
|
|
143
|
+
* Every record, for the callers that render the log rather than search it.
|
|
144
|
+
*
|
|
145
|
+
* This is the whole log — every query, not one per question — because
|
|
146
|
+
* that is what `/net/pacs/queries` lists and what an operator went
|
|
147
|
+
* looking for when they could not find a query they knew they had run.
|
|
148
|
+
*/
|
|
149
|
+
entries_all(): QueryIndexEntry[];
|
|
150
|
+
/** How many records the index holds. */
|
|
151
|
+
size_get(): number;
|
|
152
|
+
/** How many distinct questions those records answer. */
|
|
153
|
+
questions_count(): number;
|
|
154
|
+
/**
|
|
155
|
+
* The newest record held, which is where a resumed sweep starts.
|
|
156
|
+
*
|
|
157
|
+
* A completed sweep has walked everything down to the floor, so the only
|
|
158
|
+
* thing a later boot can be missing is what arrived since. Asking CUBE
|
|
159
|
+
* for records at or after this turns a rebuild into a top-up.
|
|
160
|
+
*
|
|
161
|
+
* @returns An ISO timestamp, or null when the index is empty.
|
|
162
|
+
*/
|
|
163
|
+
newest_get(): string | null;
|
|
164
|
+
/**
|
|
165
|
+
* The oldest record the back-fill has walked, where it resumes.
|
|
166
|
+
*
|
|
167
|
+
* @returns An ISO timestamp, or null before the first sweep.
|
|
168
|
+
*/
|
|
169
|
+
floor_get(): string | null;
|
|
170
|
+
/**
|
|
171
|
+
* Records how far back the sweep has reached.
|
|
172
|
+
*
|
|
173
|
+
* @param at - The oldest record walked, as an ISO timestamp.
|
|
174
|
+
*/
|
|
175
|
+
floor_set(at: string): void;
|
|
176
|
+
/**
|
|
177
|
+
* Creates a persistence-safe copy.
|
|
178
|
+
*
|
|
179
|
+
* @returns The snapshot.
|
|
180
|
+
*/
|
|
181
|
+
snapshot_create(): QueryIndexSnapshot;
|
|
182
|
+
/**
|
|
183
|
+
* Merges a snapshot in, keeping anything this process already learned:
|
|
184
|
+
* a query indexed since the checkpoint was written is newer than the
|
|
185
|
+
* checkpoint by definition.
|
|
186
|
+
*
|
|
187
|
+
* @param snapshot - A validated snapshot.
|
|
188
|
+
*/
|
|
189
|
+
snapshot_restore(snapshot: QueryIndexSnapshot): void;
|
|
190
|
+
/**
|
|
191
|
+
* Registers a listener told after any mutation.
|
|
192
|
+
*
|
|
193
|
+
* @param listener - The listener.
|
|
194
|
+
* @returns A function that removes it.
|
|
195
|
+
*/
|
|
196
|
+
changeListener_add(listener: QueryIndexListener): () => void;
|
|
197
|
+
/** Empties the index. For tests, and for a change of identity. */
|
|
198
|
+
reset(): void;
|
|
199
|
+
/** Tells every listener. A listener that throws must not stop the rest. */
|
|
200
|
+
private change_emit;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The process-wide query index.
|
|
204
|
+
*
|
|
205
|
+
* @returns The singleton.
|
|
206
|
+
*/
|
|
207
|
+
export declare function queryIndex_get(): QueryIndex;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file The PACS query index: what has already been asked, and answered.
|
|
4
|
+
*
|
|
5
|
+
* CUBE keeps every PACS query it has run, criteria and result both, so a
|
|
6
|
+
* question asked before need not be asked again. It cannot be *searched*
|
|
7
|
+
* for, though: the queries collection accepts no parameter over the stored
|
|
8
|
+
* criteria, and every row of every page drags its compressed result along —
|
|
9
|
+
* kilobytes each, thousands of rows. Looking a question up by sweeping that
|
|
10
|
+
* collection would cost megabytes, and would cost them most often on a
|
|
11
|
+
* MISS, which is the common case for a genuinely new query. A client that
|
|
12
|
+
* searched naively would make every new query slower than not searching.
|
|
13
|
+
*
|
|
14
|
+
* So the collection is walked once, in the background, and what is learned
|
|
15
|
+
* is kept here: criteria and server in, the stored query's id out. A lookup
|
|
16
|
+
* is then a map read.
|
|
17
|
+
*
|
|
18
|
+
* The index is ADVISORY. A hit is a claim to be confirmed — the caller
|
|
19
|
+
* decodes the stored result before serving it, and any failure (the query
|
|
20
|
+
* deleted, the result unreadable) falls through to a fresh query. A stale
|
|
21
|
+
* entry can therefore cost a wasted lookup and never a wrong answer, which
|
|
22
|
+
* is what makes it safe to keep across restarts and to fill lazily.
|
|
23
|
+
*
|
|
24
|
+
* It is also the log itself. Each entry carries the criteria and owner the
|
|
25
|
+
* VFS renders into `/net/pacs/queries/<name>`, so that listing can be
|
|
26
|
+
* served from here — complete, rather than the first page of it.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.QueryIndex = void 0;
|
|
32
|
+
exports.queryKey_build = queryKey_build;
|
|
33
|
+
exports.queryCriteria_parse = queryCriteria_parse;
|
|
34
|
+
exports.queryIndex_get = queryIndex_get;
|
|
35
|
+
/**
|
|
36
|
+
* Separates the server from the criteria in a key, and one criterion from
|
|
37
|
+
* the next.
|
|
38
|
+
*
|
|
39
|
+
* ASCII record and unit separators, written as escapes: a PACS identifier
|
|
40
|
+
* or a DICOM value may contain anything printable — including the spaces,
|
|
41
|
+
* colons and commas that would otherwise let two different questions
|
|
42
|
+
* collide on one key.
|
|
43
|
+
*/
|
|
44
|
+
const KEY_SERVER_SEPARATOR = '\u001e';
|
|
45
|
+
/** Separates one criterion from the next in a key. */
|
|
46
|
+
const KEY_PAIR_SEPARATOR = '\u001f';
|
|
47
|
+
/**
|
|
48
|
+
* Builds the key a question is filed under.
|
|
49
|
+
*
|
|
50
|
+
* Two questions are the same question when their criteria sets are equal
|
|
51
|
+
* and they were asked of the same PACS. Key order and surrounding
|
|
52
|
+
* whitespace are normalized away because they are how the question was
|
|
53
|
+
* *typed*, not what it asked. Values are otherwise left exactly as given:
|
|
54
|
+
* they are the PACS's own vocabulary, and mise does not get to reinterpret
|
|
55
|
+
* them — a case fold here would silently claim that two DICOM values match
|
|
56
|
+
* when only the PACS can say so.
|
|
57
|
+
*
|
|
58
|
+
* The owner is part of the question. The index holds the whole log —
|
|
59
|
+
* every identity's queries, because that is what `/net/pacs/queries` lists
|
|
60
|
+
* — while a replay may only ever serve back an answer to the identity that
|
|
61
|
+
* asked for it. Keying by owner makes those two facts one lookup instead of
|
|
62
|
+
* a filter somebody can forget.
|
|
63
|
+
*
|
|
64
|
+
* @param criteria - The criteria as asked.
|
|
65
|
+
* @param server - The PACS identifier the query was asked of.
|
|
66
|
+
* @param owner - The identity that asked.
|
|
67
|
+
* @returns The key, stable for equal questions.
|
|
68
|
+
*/
|
|
69
|
+
function queryKey_build(criteria, server, owner) {
|
|
70
|
+
const pairs = [];
|
|
71
|
+
for (const [name, value] of Object.entries(criteria)) {
|
|
72
|
+
const key = name.trim();
|
|
73
|
+
const held = String(value ?? '').trim();
|
|
74
|
+
if (key.length === 0 || held.length === 0)
|
|
75
|
+
continue;
|
|
76
|
+
pairs.push(`${key}=${held}`);
|
|
77
|
+
}
|
|
78
|
+
pairs.sort();
|
|
79
|
+
return `${owner.trim()}${KEY_SERVER_SEPARATOR}${server.trim()}${KEY_SERVER_SEPARATOR}${pairs.join(KEY_PAIR_SEPARATOR)}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Parses the criteria CUBE stores as a JSON string on a query record.
|
|
83
|
+
*
|
|
84
|
+
* @param raw - The record's `query` field.
|
|
85
|
+
* @returns The criteria, or null when the field is absent or unparseable.
|
|
86
|
+
*/
|
|
87
|
+
function queryCriteria_parse(raw) {
|
|
88
|
+
if (typeof raw !== 'string' || raw.trim().length === 0)
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(raw);
|
|
92
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
93
|
+
return null;
|
|
94
|
+
const criteria = {};
|
|
95
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
96
|
+
if (value === undefined || value === null)
|
|
97
|
+
continue;
|
|
98
|
+
criteria[name] = String(value);
|
|
99
|
+
}
|
|
100
|
+
return criteria;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* What has already been asked of which PACS, and where the answer is kept.
|
|
108
|
+
*/
|
|
109
|
+
class QueryIndex {
|
|
110
|
+
constructor() {
|
|
111
|
+
/**
|
|
112
|
+
* Every record, by its query id. This is the log: asking the same thing
|
|
113
|
+
* twice leaves two records, and both remain reachable.
|
|
114
|
+
*/
|
|
115
|
+
this.entries = new Map();
|
|
116
|
+
/**
|
|
117
|
+
* Question to the newest record answering it. This is the lookup: a
|
|
118
|
+
* replay wants the most recent answer, not every answer.
|
|
119
|
+
*/
|
|
120
|
+
this.byQuestion = new Map();
|
|
121
|
+
this.floor = null;
|
|
122
|
+
this.listeners = new Set();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Files a query under its question.
|
|
126
|
+
*
|
|
127
|
+
* The newest answer to a question wins: asking the same thing twice
|
|
128
|
+
* leaves the later record indexed, which is the one whose age a replay
|
|
129
|
+
* should state.
|
|
130
|
+
*
|
|
131
|
+
* @param entry - What was asked, of whom, and what came back.
|
|
132
|
+
*/
|
|
133
|
+
entry_note(entry) {
|
|
134
|
+
this.entries.set(entry.queryId, entry);
|
|
135
|
+
const key = queryKey_build(entry.criteria, entry.server, entry.owner);
|
|
136
|
+
const heldId = this.byQuestion.get(key);
|
|
137
|
+
const held = heldId === undefined ? undefined : this.entries.get(heldId);
|
|
138
|
+
// Strictly newer, so a tie leaves the incumbent in place: what this
|
|
139
|
+
// process learned outranks what a restored checkpoint claims.
|
|
140
|
+
if (held === undefined || Date.parse(entry.answeredAt) > Date.parse(held.answeredAt)) {
|
|
141
|
+
this.byQuestion.set(key, entry.queryId);
|
|
142
|
+
}
|
|
143
|
+
this.change_emit();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Looks a question up.
|
|
147
|
+
*
|
|
148
|
+
* @param criteria - The criteria being asked.
|
|
149
|
+
* @param server - The PACS being asked.
|
|
150
|
+
* @param owner - The identity asking; another identity's answer is not
|
|
151
|
+
* an answer to this question.
|
|
152
|
+
* @returns What is known, or null when this question is new to the index.
|
|
153
|
+
*/
|
|
154
|
+
entry_find(criteria, server, owner) {
|
|
155
|
+
const queryId = this.byQuestion.get(queryKey_build(criteria, server, owner));
|
|
156
|
+
if (queryId === undefined)
|
|
157
|
+
return null;
|
|
158
|
+
return this.entries.get(queryId) ?? null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Forgets one query, for when its stored answer turns out to be gone.
|
|
162
|
+
*
|
|
163
|
+
* @param criteria - The criteria it was filed under.
|
|
164
|
+
* @param server - The PACS it was asked of.
|
|
165
|
+
* @param owner - The identity that asked.
|
|
166
|
+
*/
|
|
167
|
+
entry_drop(criteria, server, owner) {
|
|
168
|
+
const key = queryKey_build(criteria, server, owner);
|
|
169
|
+
const queryId = this.byQuestion.get(key);
|
|
170
|
+
if (queryId === undefined)
|
|
171
|
+
return;
|
|
172
|
+
this.byQuestion.delete(key);
|
|
173
|
+
this.entries.delete(queryId);
|
|
174
|
+
// An older record may still answer this question; the newest of what
|
|
175
|
+
// is left takes the pointer back.
|
|
176
|
+
let heir = null;
|
|
177
|
+
for (const entry of this.entries.values()) {
|
|
178
|
+
if (queryKey_build(entry.criteria, entry.server, entry.owner) !== key)
|
|
179
|
+
continue;
|
|
180
|
+
if (heir === null || Date.parse(entry.answeredAt) > Date.parse(heir.answeredAt))
|
|
181
|
+
heir = entry;
|
|
182
|
+
}
|
|
183
|
+
if (heir !== null)
|
|
184
|
+
this.byQuestion.set(key, heir.queryId);
|
|
185
|
+
this.change_emit();
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Every record, for the callers that render the log rather than search it.
|
|
189
|
+
*
|
|
190
|
+
* This is the whole log — every query, not one per question — because
|
|
191
|
+
* that is what `/net/pacs/queries` lists and what an operator went
|
|
192
|
+
* looking for when they could not find a query they knew they had run.
|
|
193
|
+
*/
|
|
194
|
+
entries_all() {
|
|
195
|
+
return [...this.entries.values()];
|
|
196
|
+
}
|
|
197
|
+
/** How many records the index holds. */
|
|
198
|
+
size_get() {
|
|
199
|
+
return this.entries.size;
|
|
200
|
+
}
|
|
201
|
+
/** How many distinct questions those records answer. */
|
|
202
|
+
questions_count() {
|
|
203
|
+
return this.byQuestion.size;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The newest record held, which is where a resumed sweep starts.
|
|
207
|
+
*
|
|
208
|
+
* A completed sweep has walked everything down to the floor, so the only
|
|
209
|
+
* thing a later boot can be missing is what arrived since. Asking CUBE
|
|
210
|
+
* for records at or after this turns a rebuild into a top-up.
|
|
211
|
+
*
|
|
212
|
+
* @returns An ISO timestamp, or null when the index is empty.
|
|
213
|
+
*/
|
|
214
|
+
newest_get() {
|
|
215
|
+
let newest = null;
|
|
216
|
+
for (const entry of this.entries.values()) {
|
|
217
|
+
if (newest === null || Date.parse(entry.answeredAt) > Date.parse(newest)) {
|
|
218
|
+
newest = entry.answeredAt;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return newest;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The oldest record the back-fill has walked, where it resumes.
|
|
225
|
+
*
|
|
226
|
+
* @returns An ISO timestamp, or null before the first sweep.
|
|
227
|
+
*/
|
|
228
|
+
floor_get() {
|
|
229
|
+
return this.floor;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Records how far back the sweep has reached.
|
|
233
|
+
*
|
|
234
|
+
* @param at - The oldest record walked, as an ISO timestamp.
|
|
235
|
+
*/
|
|
236
|
+
floor_set(at) {
|
|
237
|
+
if (this.floor !== null && Date.parse(this.floor) <= Date.parse(at))
|
|
238
|
+
return;
|
|
239
|
+
this.floor = at;
|
|
240
|
+
this.change_emit();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Creates a persistence-safe copy.
|
|
244
|
+
*
|
|
245
|
+
* @returns The snapshot.
|
|
246
|
+
*/
|
|
247
|
+
snapshot_create() {
|
|
248
|
+
const entries = [];
|
|
249
|
+
for (const entry of this.entries.values()) {
|
|
250
|
+
entries.push({ key: queryKey_build(entry.criteria, entry.server, entry.owner), ...entry });
|
|
251
|
+
}
|
|
252
|
+
return { entries, floor: this.floor };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Merges a snapshot in, keeping anything this process already learned:
|
|
256
|
+
* a query indexed since the checkpoint was written is newer than the
|
|
257
|
+
* checkpoint by definition.
|
|
258
|
+
*
|
|
259
|
+
* @param snapshot - A validated snapshot.
|
|
260
|
+
*/
|
|
261
|
+
snapshot_restore(snapshot) {
|
|
262
|
+
for (const entry of snapshot.entries) {
|
|
263
|
+
if (this.entries.has(entry.queryId))
|
|
264
|
+
continue;
|
|
265
|
+
const { key: _key, ...held } = entry;
|
|
266
|
+
this.entry_note(held);
|
|
267
|
+
}
|
|
268
|
+
if (snapshot.floor !== null && (this.floor === null || Date.parse(snapshot.floor) < Date.parse(this.floor))) {
|
|
269
|
+
this.floor = snapshot.floor;
|
|
270
|
+
}
|
|
271
|
+
this.change_emit();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Registers a listener told after any mutation.
|
|
275
|
+
*
|
|
276
|
+
* @param listener - The listener.
|
|
277
|
+
* @returns A function that removes it.
|
|
278
|
+
*/
|
|
279
|
+
changeListener_add(listener) {
|
|
280
|
+
this.listeners.add(listener);
|
|
281
|
+
return () => { this.listeners.delete(listener); };
|
|
282
|
+
}
|
|
283
|
+
/** Empties the index. For tests, and for a change of identity. */
|
|
284
|
+
reset() {
|
|
285
|
+
this.entries.clear();
|
|
286
|
+
this.byQuestion.clear();
|
|
287
|
+
this.floor = null;
|
|
288
|
+
this.change_emit();
|
|
289
|
+
}
|
|
290
|
+
/** Tells every listener. A listener that throws must not stop the rest. */
|
|
291
|
+
change_emit() {
|
|
292
|
+
for (const listener of this.listeners) {
|
|
293
|
+
try {
|
|
294
|
+
listener();
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
/* a listener's failure is its own */
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
exports.QueryIndex = QueryIndex;
|
|
303
|
+
let queryIndex = null;
|
|
304
|
+
/**
|
|
305
|
+
* The process-wide query index.
|
|
306
|
+
*
|
|
307
|
+
* @returns The singleton.
|
|
308
|
+
*/
|
|
309
|
+
function queryIndex_get() {
|
|
310
|
+
if (queryIndex === null)
|
|
311
|
+
queryIndex = new QueryIndex();
|
|
312
|
+
return queryIndex;
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=queryIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryIndex.js","sourceRoot":"","sources":["../../src/cache/queryIndex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Minimum spacing between two writes of the query-index checkpoint. */
|
|
2
|
+
export declare const QUERY_INDEX_CHECKPOINT_FLOOR_MS: number;
|
|
3
|
+
/**
|
|
4
|
+
* Outcome of attempting to restore a query-index checkpoint.
|
|
5
|
+
*
|
|
6
|
+
* @property restored - Whether validated entries reached the index.
|
|
7
|
+
* @property count - Number of restored queries.
|
|
8
|
+
* @property writtenAt - Checkpoint creation time when restoration succeeded.
|
|
9
|
+
* @property reason - Human-readable reason when restoration was skipped.
|
|
10
|
+
*/
|
|
11
|
+
export interface QueryIndexRestoreResult {
|
|
12
|
+
restored: boolean;
|
|
13
|
+
count: number;
|
|
14
|
+
writtenAt?: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds the identity-keyed checkpoint path without exposing the identity.
|
|
19
|
+
*
|
|
20
|
+
* @param identity - Canonical ChRIS identity string.
|
|
21
|
+
* @param root - Checkpoint directory override.
|
|
22
|
+
* @returns Versioned checkpoint path.
|
|
23
|
+
*/
|
|
24
|
+
export declare function queryIndexCheckpointPath_get(identity: string, root?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Restores a matching, validated checkpoint into the index.
|
|
27
|
+
*
|
|
28
|
+
* A rejected file is not an error worth raising: the index is advisory, so
|
|
29
|
+
* an unreadable checkpoint costs a sweep and nothing else.
|
|
30
|
+
*
|
|
31
|
+
* @param identity - Canonical ChRIS identity string.
|
|
32
|
+
* @param root - Checkpoint directory override.
|
|
33
|
+
* @returns Restoration outcome without throwing for absent or invalid files.
|
|
34
|
+
*/
|
|
35
|
+
export declare function queryIndexCheckpoint_restore(identity: string, root?: string): Promise<QueryIndexRestoreResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Atomically saves the query index.
|
|
38
|
+
*
|
|
39
|
+
* @param identity - Canonical ChRIS identity string.
|
|
40
|
+
* @param root - Checkpoint directory override.
|
|
41
|
+
* @returns Promise resolved after the mode-0600 file is durable in place.
|
|
42
|
+
*/
|
|
43
|
+
export declare function queryIndexCheckpoint_save(identity: string, root?: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Starts a throttled checkpoint writer for index mutations: a write at
|
|
46
|
+
* least `delayMs` after the first pending mutation, and no more often than
|
|
47
|
+
* once per `floorMs`, the last change never dropped.
|
|
48
|
+
*
|
|
49
|
+
* @param identity - Canonical ChRIS identity string.
|
|
50
|
+
* @param root - Checkpoint directory override.
|
|
51
|
+
* @param delayMs - Debounce interval in milliseconds.
|
|
52
|
+
* @param floorMs - Minimum spacing between two writes.
|
|
53
|
+
* @returns Function that stops watching and cancels a pending save.
|
|
54
|
+
*/
|
|
55
|
+
export declare function queryIndexCheckpoint_watch(identity: string, root?: string, delayMs?: number, floorMs?: number): () => void;
|