@fnndsc/salsa 4.4.4 → 4.5.1

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.
@@ -0,0 +1,276 @@
1
+ /**
2
+ * @file Filling a CUBE from a plugin index: `plugin sync`.
3
+ *
4
+ * A sync is a plan, then the plan's adds. The plan compares every descriptor
5
+ * in the index with what the connected CUBE holds and puts each plugin in
6
+ * one group:
7
+ *
8
+ * | group | what it means | action |
9
+ * |---|---|---|
10
+ * | add | not in the CUBE | add |
11
+ * | newer | the index has a newer version | add beside the older one |
12
+ * | skip | same name and version, same descriptor | nothing |
13
+ * | cubeNewer | the CUBE has a newer version | report |
14
+ * | drift | same name and version, different descriptor | report, never touched |
15
+ * | imageTaken | the image is registered under another version | report: CUBE would refuse it |
16
+ * | cubeOnly | in the CUBE, not in the index | report |
17
+ * | invalid | fails rules 1–4 of `plugin validate` | report, never uploaded |
18
+ *
19
+ * A sync never deletes and never changes a plugin already there: deleting
20
+ * cascades to its runs and to its nodes in every pipeline, and CUBE's admin
21
+ * `PUT` changes only compute resources, so a descriptor cannot be corrected
22
+ * in place anyway. Drift ignores what only reflects CUBE's defaults (a limit
23
+ * the index leaves out because it equals the default, `1000m` against a
24
+ * stored `1000`, a field CUBE fills in when it is not given).
25
+ *
26
+ * Applying uploads the plan's adds four at a time, each failure contained to
27
+ * its plugin; what landed stays, so a rerun continues where a stopped one
28
+ * left off. Every CUBE read and the upload go through {@link SyncSeams}, so
29
+ * the plan and the apply are testable without a CUBE.
30
+ *
31
+ * Key components:
32
+ * - SyncGroup / SyncPlanEntry / SyncPlan: the plan.
33
+ * - SyncResult / SyncReceipt: what applying it did.
34
+ * - SyncSeams / syncSeams_cube: the CUBE reads and the upload.
35
+ * - syncPlan_build: compare an index with the CUBE.
36
+ * - syncPlanAdds_list: the entries a plan would upload.
37
+ * - syncPlan_apply: upload them, four at a time.
38
+ * - descriptorDrift_find: how two descriptors of one version differ, defaults aside.
39
+ * - version_compare: dotted-number ordering.
40
+ *
41
+ * Typical usage:
42
+ * ```ts
43
+ * const plan: SyncPlan = await syncPlan_build(index, ['host'], syncSeams_cube);
44
+ * const receipt: SyncReceipt = await syncPlan_apply(plan, syncSeams_cube, (r: SyncResult): void => console.log(r));
45
+ * ```
46
+ *
47
+ * Dependencies: cumin's CUBE reads (plugin pages, parameters) and the
48
+ * administrative upload; the index and its local checks.
49
+ *
50
+ * @module
51
+ */
52
+ import type { PluginIndex } from './plugin_index.js';
53
+ /** The groups a plan's entries fall in, in the order a plan lists them. */
54
+ export declare const SYNC_GROUPS: readonly ["add", "newer", "skip", "cubeNewer", "drift", "imageTaken", "cubeOnly", "invalid"];
55
+ /** One group of the plan. */
56
+ export type SyncGroup = typeof SYNC_GROUPS[number];
57
+ /** The groups that upload. */
58
+ export declare const SYNC_ADD_GROUPS: readonly SyncGroup[];
59
+ /**
60
+ * One plugin's place in the plan.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const entry: SyncPlanEntry = { group: 'newer', name: 'pl-x', version: '1.2.0', cubeVersions: ['1.1.0'] };
65
+ * ```
66
+ */
67
+ export interface SyncPlanEntry {
68
+ /** Its group. */
69
+ readonly group: SyncGroup;
70
+ /** The plugin's name (the file's, for an invalid descriptor that names none). */
71
+ readonly name: string;
72
+ /** The index's version, when the entry comes from the index. */
73
+ readonly version?: string;
74
+ /** The versions the CUBE holds of this name. */
75
+ readonly cubeVersions?: string[];
76
+ /** The index file it comes from. */
77
+ readonly file?: string;
78
+ /** Why it is in a report group, in a line. */
79
+ readonly detail?: string;
80
+ /** The descriptor's text, for the entries that upload. */
81
+ readonly descriptor?: string;
82
+ }
83
+ /**
84
+ * The plan.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const plan: SyncPlan = await syncPlan_build(index, ['host'], seams);
89
+ * plan.entries.filter((e: SyncPlanEntry): boolean => e.group === 'add').length;
90
+ * ```
91
+ */
92
+ export interface SyncPlan {
93
+ /** The index's short name (`chris-plugins @ 8c9a1f6`). */
94
+ readonly label: string;
95
+ /** The index's source word. */
96
+ readonly source: string;
97
+ /** The commit read, when known. */
98
+ readonly commit: string | null;
99
+ /** How many descriptors the index holds. */
100
+ readonly indexed: number;
101
+ /** The compute resources adds are registered on. */
102
+ readonly computes: string[];
103
+ /** Every entry, by group then name. */
104
+ readonly entries: SyncPlanEntry[];
105
+ }
106
+ /**
107
+ * One upload's outcome.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const result: SyncResult = { name: 'pl-x', version: '1.2.0', ok: false, reason: 'Plugin with name pl-x and version 1.2.0 already exists.' };
112
+ * ```
113
+ */
114
+ export interface SyncResult {
115
+ /** The plugin. */
116
+ readonly name: string;
117
+ /** Its version. */
118
+ readonly version: string;
119
+ /** True when CUBE registered it. */
120
+ readonly ok: boolean;
121
+ /** CUBE's reason, when it refused. */
122
+ readonly reason?: string;
123
+ }
124
+ /**
125
+ * What applying a plan did.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const receipt: SyncReceipt = await syncPlan_apply(plan, seams, () => undefined);
130
+ * receipt.failed === 0;
131
+ * ```
132
+ */
133
+ export interface SyncReceipt {
134
+ /** The plan applied. */
135
+ readonly plan: SyncPlan;
136
+ /** Each upload's outcome, in plan order. */
137
+ readonly results: SyncResult[];
138
+ /** Plugins registered. */
139
+ readonly added: number;
140
+ /** Plugins already there, the same. */
141
+ readonly skipped: number;
142
+ /** Plugins in a report group. */
143
+ readonly reported: number;
144
+ /** Uploads that should have worked and did not. */
145
+ readonly failed: number;
146
+ /** Whether a stop (`kill %N`, Ctrl-C) ended it before every add was sent. */
147
+ readonly stopped: boolean;
148
+ /** Adds the plan had that were never sent, because it was stopped. */
149
+ readonly unsent: number;
150
+ }
151
+ /**
152
+ * One plugin row the CUBE holds.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * const row: CubePluginRow = { name: 'pl-x', version: '1.0.0', dock_image: 'fnndsc/pl-x:1.0.0' };
157
+ * ```
158
+ */
159
+ export interface CubePluginRow {
160
+ /** Its name. */
161
+ readonly name: string;
162
+ /** Its version. */
163
+ readonly version: string;
164
+ /** Its image. */
165
+ readonly dock_image: string;
166
+ }
167
+ /** What an upload answered. */
168
+ export type UploadAnswer = {
169
+ readonly ok: true;
170
+ } | {
171
+ readonly ok: false;
172
+ readonly reason: string;
173
+ };
174
+ /**
175
+ * The CUBE reads and the upload a sync needs.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const seams: SyncSeams = { ...syncSeams_cube, descriptor_upload: async () => ({ ok: true }) };
180
+ * ```
181
+ */
182
+ export interface SyncSeams {
183
+ /**
184
+ * Reads every plugin the CUBE holds.
185
+ *
186
+ * @returns The rows, every page.
187
+ */
188
+ cubePlugins_read(): Promise<CubePluginRow[]>;
189
+ /**
190
+ * Reads one registered version whole: its fields and every parameter.
191
+ *
192
+ * @param name - The plugin's name.
193
+ * @param version - The version.
194
+ * @returns The plugin's fields with `parameters`, or null when CUBE has no such version.
195
+ */
196
+ cubeDescriptor_read(name: string, version: string): Promise<Record<string, unknown> | null>;
197
+ /**
198
+ * Uploads one descriptor.
199
+ *
200
+ * @param descriptor - The descriptor's text.
201
+ * @param computes - Compute resources to register it on.
202
+ * @returns Whether CUBE registered it, and why not.
203
+ */
204
+ descriptor_upload(descriptor: string, computes: string[]): Promise<UploadAnswer>;
205
+ }
206
+ /** The connected CUBE's reads and upload. */
207
+ export declare const syncSeams_cube: SyncSeams;
208
+ /**
209
+ * Builds the plan: compares an index with the connected CUBE.
210
+ *
211
+ * @param index - The index.
212
+ * @param computes - The compute resources adds would be registered on.
213
+ * @param seams - The CUBE reads.
214
+ * @returns The plan.
215
+ * @throws {Error} When the CUBE cannot be read.
216
+ *
217
+ * @example
218
+ * ```ts
219
+ * const plan: SyncPlan = await syncPlan_build(index, ['host'], syncSeams_cube);
220
+ * ```
221
+ */
222
+ export declare function syncPlan_build(index: PluginIndex, computes: string[], seams: SyncSeams): Promise<SyncPlan>;
223
+ /**
224
+ * Lists the entries a plan would upload.
225
+ *
226
+ * @param plan - The plan.
227
+ * @returns Its add and newer entries, in plan order.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * syncPlanAdds_list(plan).length; // how many plugins a yes registers
232
+ * ```
233
+ */
234
+ export declare function syncPlanAdds_list(plan: SyncPlan): SyncPlanEntry[];
235
+ /**
236
+ * Applies a plan: uploads its adds, four at a time.
237
+ *
238
+ * @param plan - The plan.
239
+ * @param seams - The upload.
240
+ * @param note - Called once per upload as it lands, in the order they land.
241
+ * @param signal - A stop: the uploads in flight finish, no more start, and
242
+ * what landed stays (a rerun continues from it).
243
+ * @returns What it did, `stopped` when the signal ended it early.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * const receipt: SyncReceipt = await syncPlan_apply(plan, syncSeams_cube, (r: SyncResult): void => console.log(r.name, r.ok));
248
+ * ```
249
+ */
250
+ export declare function syncPlan_apply(plan: SyncPlan, seams: SyncSeams, note: (result: SyncResult) => void, signal?: AbortSignal): Promise<SyncReceipt>;
251
+ /**
252
+ * Finds how two descriptors of one version differ, CUBE's defaults aside.
253
+ *
254
+ * @param indexed - The index's descriptor.
255
+ * @param held - The CUBE's plugin fields with its `parameters`.
256
+ * @returns The differing fields (`dock_image`, `parameters.--dir.help`), empty when none.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * descriptorDrift_find({ min_cpu_limit: '1000m' }, { min_cpu_limit: 1000 }); // []
261
+ * ```
262
+ */
263
+ export declare function descriptorDrift_find(indexed: Record<string, unknown>, held: Record<string, unknown>): string[];
264
+ /**
265
+ * Orders two dotted-number versions.
266
+ *
267
+ * @param a - One version.
268
+ * @param b - The other.
269
+ * @returns Negative when `a` is older, positive when newer, zero when equal.
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * ['1.10.0', '1.2.0'].sort(version_compare); // ['1.2.0', '1.10.0']
274
+ * ```
275
+ */
276
+ export declare function version_compare(a: string, b: string): number;