@privateaim/core-kit 0.8.33 → 0.8.36

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/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.36](https://github.com/PrivateAIM/hub/compare/v0.8.35...v0.8.36) (2026-04-21)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * devDependencies
10
+ * @privateaim/kit bumped from ^0.8.35 to ^0.8.36
11
+ * @privateaim/telemetry-kit bumped from ^0.8.35 to ^0.8.36
12
+ * peerDependencies
13
+ * @privateaim/kit bumped from ^0.8.35 to ^0.8.36
14
+ * @privateaim/telemetry-kit bumped from ^0.8.35 to ^0.8.36
15
+
16
+ ## [0.8.35](https://github.com/PrivateAIM/hub/compare/v0.8.34...v0.8.35) (2026-04-19)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * pass queueRouter to all callers subclasses and fix DatabaseModul… ([#1541](https://github.com/PrivateAIM/hub/issues/1541)) ([558f1da](https://github.com/PrivateAIM/hub/commit/558f1dafab2da1a82a5919ed47bf4c5620404971))
22
+
23
+
24
+ ### Dependencies
25
+
26
+ * The following workspace dependencies were updated
27
+ * devDependencies
28
+ * @privateaim/kit bumped from ^0.8.34 to ^0.8.35
29
+ * @privateaim/telemetry-kit bumped from ^0.8.34 to ^0.8.35
30
+ * peerDependencies
31
+ * @privateaim/kit bumped from ^0.8.34 to ^0.8.35
32
+ * @privateaim/telemetry-kit bumped from ^0.8.34 to ^0.8.35
33
+
34
+ ## [0.8.34](https://github.com/PrivateAIM/hub/compare/v0.8.33...v0.8.34) (2026-04-16)
35
+
36
+
37
+ ### Dependencies
38
+
39
+ * The following workspace dependencies were updated
40
+ * devDependencies
41
+ * @privateaim/kit bumped from ^0.8.33 to ^0.8.34
42
+ * @privateaim/telemetry-kit bumped from ^0.8.33 to ^0.8.34
43
+ * peerDependencies
44
+ * @privateaim/kit bumped from ^0.8.33 to ^0.8.34
45
+ * @privateaim/telemetry-kit bumped from ^0.8.33 to ^0.8.34
46
+
3
47
  ## [0.8.33](https://github.com/PrivateAIM/hub/compare/v0.8.32...v0.8.33) (2026-04-16)
4
48
 
5
49
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@privateaim/core-kit",
3
3
  "type": "module",
4
- "version": "0.8.33",
4
+ "version": "0.8.36",
5
5
  "license": "Apache-2.0",
6
6
  "description": "",
7
7
  "module": "./dist/index.mjs",
@@ -20,17 +20,18 @@
20
20
  },
21
21
  "scripts": {
22
22
  "build": "rimraf ./dist && tsdown",
23
- "build-watch": "rimraf ./dist && tsc -p tsconfig.build.json --watch"
23
+ "build-watch": "rimraf ./dist && tsc -p tsconfig.build.json --watch",
24
+ "test": "cross-env NODE_ENV=test vitest run --config test/vitest.config.ts"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@authup/core-kit": "^1.0.0-beta.35",
27
- "@privateaim/kit": "^0.8.33",
28
- "@privateaim/telemetry-kit": "^0.8.33"
28
+ "@privateaim/kit": "^0.8.36",
29
+ "@privateaim/telemetry-kit": "^0.8.36"
29
30
  },
30
31
  "peerDependencies": {
31
32
  "@authup/core-kit": "^1.0.0-beta.35",
32
- "@privateaim/kit": "^0.8.33",
33
- "@privateaim/telemetry-kit": "^0.8.33"
33
+ "@privateaim/kit": "^0.8.36",
34
+ "@privateaim/telemetry-kit": "^0.8.36"
34
35
  },
35
36
  "gitHead": "5d3b6f4ce1edf2383bdfbf66e913a08c8a3a2e40",
36
37
  "publishConfig": {
@@ -51,7 +51,7 @@ export class AnalysisConfiguratorCommandChecker {
51
51
  throw new AnalysisError('The analysis configuration is already unlocked.');
52
52
  }
53
53
 
54
- if (!entity.distribution_status) {
54
+ if (!entity.build_status) {
55
55
  return;
56
56
  }
57
57
 
@@ -23,8 +23,16 @@ export class AnalysisDistributorCommandChecker {
23
23
  throw new AnalysisError('The analysis is not built yet.');
24
24
  }
25
25
 
26
- if (entity.distribution_status === ProcessStatus.EXECUTED) {
27
- throw new AnalysisError('The analysis is already distributed.');
26
+ // we haven't started distribution yet.
27
+ if (!entity.distribution_status) {
28
+ return;
29
+ }
30
+
31
+ if (
32
+ entity.distribution_status !== ProcessStatus.FAILED &&
33
+ entity.distribution_status !== ProcessStatus.STOPPED
34
+ ) {
35
+ throw new AnalysisError(`The analysis can not be distributed in state ${ entity.distribution_status}`);
28
36
  }
29
37
  }
30
38
 
@@ -0,0 +1,348 @@
1
+ /*
2
+ * Copyright (c) 2025.
3
+ * Author Peter Placzek (tada5hi)
4
+ * For the full copyright and license information,
5
+ * view the LICENSE file that was distributed with this source code.
6
+ */
7
+
8
+ import { describe, expect, it } from 'vitest';
9
+ import { ProcessStatus } from '@privateaim/kit';
10
+ import type { Analysis } from '../../src/domains/analysis/entity';
11
+ import type { MasterImage } from '../../src/domains/master-image/entity';
12
+ import type { Project } from '../../src/domains/project/entity';
13
+ import type { Registry } from '../../src/domains/registry/entity';
14
+ import { AnalysisError } from '../../src/domains/analysis/error';
15
+ import { AnalysisBuilderCommandChecker } from '../../src/domains/analysis/helpers/builder';
16
+ import { AnalysisConfiguratorCommandChecker } from '../../src/domains/analysis/helpers/configurator';
17
+ import { AnalysisDistributorCommandChecker } from '../../src/domains/analysis/helpers/distributor';
18
+
19
+ function createBaseAnalysis(overrides?: Partial<Analysis>): Analysis {
20
+ return {
21
+ id: 'test-id',
22
+ name: null,
23
+ description: null,
24
+ nodes: 0,
25
+ nodes_approved: 0,
26
+ configuration_locked: false,
27
+ configuration_entrypoint_valid: true,
28
+ configuration_image_valid: true,
29
+ configuration_node_default_valid: true,
30
+ configuration_node_aggregator_valid: true,
31
+ configuration_nodes_valid: true,
32
+ build_status: null,
33
+ build_nodes_valid: true,
34
+ build_progress: null,
35
+ build_hash: null,
36
+ build_os: null,
37
+ build_size: null,
38
+ distribution_status: null,
39
+ distribution_progress: null,
40
+ execution_status: null,
41
+ execution_progress: null,
42
+ created_at: new Date(),
43
+ updated_at: new Date(),
44
+ registry: {} as Registry,
45
+ registry_id: 'registry-id',
46
+ realm_id: 'realm-id',
47
+ user_id: 'user-id',
48
+ project_id: 'project-id',
49
+ project: {} as Project,
50
+ image_command_arguments: null,
51
+ master_image_id: null,
52
+ master_image: {} as MasterImage,
53
+ ...overrides,
54
+ };
55
+ }
56
+
57
+ // -----------------------------------------------------------------------
58
+ // AnalysisConfiguratorCommandChecker
59
+ // -----------------------------------------------------------------------
60
+
61
+ describe('AnalysisConfiguratorCommandChecker', () => {
62
+ describe('canLock', () => {
63
+ it('should allow locking when all preconditions are met', () => {
64
+ const entity = createBaseAnalysis();
65
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).not.toThrow();
66
+ });
67
+
68
+ it('should throw when already locked', () => {
69
+ const entity = createBaseAnalysis({ configuration_locked: true });
70
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
71
+ });
72
+
73
+ it('should throw when build already initialized', () => {
74
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.STARTING });
75
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
76
+ });
77
+
78
+ it('should throw when default node invalid', () => {
79
+ const entity = createBaseAnalysis({ configuration_node_default_valid: false });
80
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
81
+ });
82
+
83
+ it('should throw when aggregator node invalid', () => {
84
+ const entity = createBaseAnalysis({ configuration_node_aggregator_valid: false });
85
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
86
+ });
87
+
88
+ it('should throw when entrypoint invalid', () => {
89
+ const entity = createBaseAnalysis({ configuration_entrypoint_valid: false });
90
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
91
+ });
92
+
93
+ it('should throw when image invalid', () => {
94
+ const entity = createBaseAnalysis({ configuration_image_valid: false });
95
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity)).toThrow(AnalysisError);
96
+ });
97
+
98
+ it('should check conditions in order: locked → build → default node → aggregator → entrypoint → image', () => {
99
+ const entity = createBaseAnalysis({
100
+ configuration_locked: true,
101
+ build_status: ProcessStatus.STARTING,
102
+ configuration_node_default_valid: false,
103
+ configuration_node_aggregator_valid: false,
104
+ configuration_entrypoint_valid: false,
105
+ configuration_image_valid: false,
106
+ });
107
+ expect(() => AnalysisConfiguratorCommandChecker.canLock(entity))
108
+ .toThrow('The analysis configuration is locked.');
109
+ });
110
+ });
111
+
112
+ describe('canUnlock', () => {
113
+ it('should allow unlocking when locked and no build started', () => {
114
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: null });
115
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).not.toThrow();
116
+ });
117
+
118
+ it('should throw when not locked', () => {
119
+ const entity = createBaseAnalysis({ configuration_locked: false });
120
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).toThrow(AnalysisError);
121
+ });
122
+
123
+ it('should allow unlocking when build FAILED', () => {
124
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.FAILED });
125
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).not.toThrow();
126
+ });
127
+
128
+ it('should allow unlocking when build STOPPED', () => {
129
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPED });
130
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).not.toThrow();
131
+ });
132
+
133
+ it('should allow unlocking when build STOPPING', () => {
134
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPING });
135
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).not.toThrow();
136
+ });
137
+
138
+ it('should throw when build STARTING', () => {
139
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTING });
140
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).toThrow(AnalysisError);
141
+ });
142
+
143
+ it('should throw when build STARTED', () => {
144
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTED });
145
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).toThrow(AnalysisError);
146
+ });
147
+
148
+ it('should throw when build EXECUTING', () => {
149
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTING });
150
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).toThrow(AnalysisError);
151
+ });
152
+
153
+ it('should throw when build EXECUTED', () => {
154
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTED });
155
+ expect(() => AnalysisConfiguratorCommandChecker.canUnlock(entity)).toThrow(AnalysisError);
156
+ });
157
+ });
158
+ });
159
+
160
+ // -----------------------------------------------------------------------
161
+ // AnalysisBuilderCommandChecker
162
+ // -----------------------------------------------------------------------
163
+
164
+ describe('AnalysisBuilderCommandChecker', () => {
165
+ describe('canStart', () => {
166
+ it('should allow when config locked, nodes valid, no build started', () => {
167
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: null });
168
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).not.toThrow();
169
+ });
170
+
171
+ it('should throw when config not locked', () => {
172
+ const entity = createBaseAnalysis({ configuration_locked: false });
173
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
174
+ });
175
+
176
+ it('should throw when nodes not approved', () => {
177
+ const entity = createBaseAnalysis({ configuration_locked: true, build_nodes_valid: false });
178
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
179
+ });
180
+
181
+ it('should allow retry when build FAILED', () => {
182
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.FAILED });
183
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).not.toThrow();
184
+ });
185
+
186
+ it('should allow retry when build STOPPED', () => {
187
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPED });
188
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).not.toThrow();
189
+ });
190
+
191
+ it('should throw when build STARTING', () => {
192
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTING });
193
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
194
+ });
195
+
196
+ it('should throw when build STARTED', () => {
197
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTED });
198
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
199
+ });
200
+
201
+ it('should throw when build EXECUTING', () => {
202
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTING });
203
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
204
+ });
205
+
206
+ it('should throw when build EXECUTED', () => {
207
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTED });
208
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
209
+ });
210
+
211
+ it('should throw when build STOPPING', () => {
212
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPING });
213
+ expect(() => AnalysisBuilderCommandChecker.canStart(entity)).toThrow(AnalysisError);
214
+ });
215
+ });
216
+
217
+ describe('canCheck', () => {
218
+ it('should allow when config locked and build in progress', () => {
219
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTING });
220
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
221
+ });
222
+
223
+ it('should throw when config not locked', () => {
224
+ const entity = createBaseAnalysis({ configuration_locked: false, build_status: ProcessStatus.STARTING });
225
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).toThrow(AnalysisError);
226
+ });
227
+
228
+ it('should throw when no build started', () => {
229
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: null });
230
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).toThrow(AnalysisError);
231
+ });
232
+
233
+ it('should throw when build already EXECUTED', () => {
234
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTED });
235
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).toThrow(AnalysisError);
236
+ });
237
+
238
+ it('should allow when build STARTED', () => {
239
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STARTED });
240
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
241
+ });
242
+
243
+ it('should allow when build EXECUTING', () => {
244
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.EXECUTING });
245
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
246
+ });
247
+
248
+ it('should allow when build FAILED', () => {
249
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.FAILED });
250
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
251
+ });
252
+
253
+ it('should allow when build STOPPED', () => {
254
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPED });
255
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
256
+ });
257
+
258
+ it('should allow when build STOPPING', () => {
259
+ const entity = createBaseAnalysis({ configuration_locked: true, build_status: ProcessStatus.STOPPING });
260
+ expect(() => AnalysisBuilderCommandChecker.canCheck(entity)).not.toThrow();
261
+ });
262
+ });
263
+ });
264
+
265
+ // -----------------------------------------------------------------------
266
+ // AnalysisDistributorCommandChecker
267
+ // -----------------------------------------------------------------------
268
+
269
+ describe('AnalysisDistributorCommandChecker', () => {
270
+ describe('canStart', () => {
271
+ it('should allow when build EXECUTED and no distribution started', () => {
272
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: null });
273
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).not.toThrow();
274
+ });
275
+
276
+ it('should throw when not built', () => {
277
+ const entity = createBaseAnalysis({ build_status: null });
278
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
279
+ });
280
+
281
+ it('should throw when build not EXECUTED', () => {
282
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.STARTED });
283
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
284
+ });
285
+
286
+ it('should allow retry when distribution FAILED', () => {
287
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.FAILED });
288
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).not.toThrow();
289
+ });
290
+
291
+ it('should allow retry when distribution STOPPED', () => {
292
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.STOPPED });
293
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).not.toThrow();
294
+ });
295
+
296
+ it('should throw when distribution STARTING', () => {
297
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.STARTING });
298
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
299
+ });
300
+
301
+ it('should throw when distribution STARTED', () => {
302
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.STARTED });
303
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
304
+ });
305
+
306
+ it('should throw when distribution EXECUTING', () => {
307
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.EXECUTING });
308
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
309
+ });
310
+
311
+ it('should throw when distribution EXECUTED', () => {
312
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.EXECUTED });
313
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
314
+ });
315
+
316
+ it('should throw when distribution STOPPING', () => {
317
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED, distribution_status: ProcessStatus.STOPPING });
318
+ expect(() => AnalysisDistributorCommandChecker.canStart(entity)).toThrow(AnalysisError);
319
+ });
320
+ });
321
+
322
+ describe('canCheck', () => {
323
+ it('should allow when build EXECUTED', () => {
324
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.EXECUTED });
325
+ expect(() => AnalysisDistributorCommandChecker.canCheck(entity)).not.toThrow();
326
+ });
327
+
328
+ it('should throw when build not initialized', () => {
329
+ const entity = createBaseAnalysis({ build_status: null });
330
+ expect(() => AnalysisDistributorCommandChecker.canCheck(entity)).toThrow(AnalysisError);
331
+ });
332
+
333
+ it('should throw when build not finished', () => {
334
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.STARTING });
335
+ expect(() => AnalysisDistributorCommandChecker.canCheck(entity)).toThrow(AnalysisError);
336
+ });
337
+
338
+ it('should throw when build FAILED', () => {
339
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.FAILED });
340
+ expect(() => AnalysisDistributorCommandChecker.canCheck(entity)).toThrow(AnalysisError);
341
+ });
342
+
343
+ it('should throw when build STOPPED', () => {
344
+ const entity = createBaseAnalysis({ build_status: ProcessStatus.STOPPED });
345
+ expect(() => AnalysisDistributorCommandChecker.canCheck(entity)).toThrow(AnalysisError);
346
+ });
347
+ });
348
+ });
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Copyright (c) 2025.
3
+ * Author Peter Placzek (tada5hi)
4
+ * For the full copyright and license information,
5
+ * view the LICENSE file that was distributed with this source code.
6
+ */
7
+
8
+ import swc from 'unplugin-swc';
9
+ import { defineConfig } from 'vitest/config';
10
+
11
+ export default defineConfig({
12
+ test: { include: ['test/unit/**/*.spec.ts'] },
13
+ plugins: [swc.vite()],
14
+ });