@hkdigital/lib-core 0.3.8 → 0.3.10

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.
@@ -110,7 +110,7 @@ export class ServiceManager extends EventEmitter {
110
110
  logConfig: config.logConfig || {}
111
111
  };
112
112
 
113
- this._setupLogging();
113
+ this.#setupLogging();
114
114
  }
115
115
 
116
116
  /**
@@ -140,7 +140,7 @@ export class ServiceManager extends EventEmitter {
140
140
  };
141
141
 
142
142
  // Track dependents
143
- entry.dependencies.forEach(dep => {
143
+ entry.dependencies.forEach((dep) => {
144
144
  const depEntry = this.services.get(dep);
145
145
  if (depEntry) {
146
146
  depEntry.dependents.add(name);
@@ -175,7 +175,7 @@ export class ServiceManager extends EventEmitter {
175
175
  entry.instance = new entry.ServiceClass(name);
176
176
 
177
177
  // Apply log level
178
- const logLevel = this._getServiceLogLevel(name);
178
+ const logLevel = this.#getServiceLogLevel(name);
179
179
  if (logLevel) {
180
180
  entry.instance.setLogLevel(logLevel);
181
181
  }
@@ -224,8 +224,7 @@ export class ServiceManager extends EventEmitter {
224
224
 
225
225
  // Start dependencies first
226
226
  for (const dep of entry.dependencies) {
227
- if (!await this.isRunning(dep)) {
228
-
227
+ if (!(await this.isRunning(dep))) {
229
228
  this.logger.debug(`Starting dependency '${dep}' for '${name}'`);
230
229
 
231
230
  const started = await this.startService(dep);
@@ -309,7 +308,7 @@ export class ServiceManager extends EventEmitter {
309
308
  this.logger.info('Starting all services');
310
309
 
311
310
  // Sort by priority and dependencies
312
- const sorted = this._topologicalSort();
311
+ const sorted = this.#topologicalSort();
313
312
  const results = new Map();
314
313
 
315
314
  for (const name of sorted) {
@@ -347,7 +346,7 @@ export class ServiceManager extends EventEmitter {
347
346
  };
348
347
 
349
348
  // Stop in reverse order
350
- const sorted = this._topologicalSort().reverse();
349
+ const sorted = this.#topologicalSort().reverse();
351
350
  const results = new Map();
352
351
 
353
352
  // Handle global timeout if specified
@@ -362,7 +361,7 @@ export class ServiceManager extends EventEmitter {
362
361
  try {
363
362
  // Race between stopping all services and timeout
364
363
  await Promise.race([
365
- this._stopAllSequentially(sorted, results, stopOptions),
364
+ this.#stopAllSequentially(sorted, results, stopOptions),
366
365
  timeoutPromise
367
366
  ]);
368
367
  } catch (error) {
@@ -380,31 +379,12 @@ export class ServiceManager extends EventEmitter {
380
379
  }
381
380
  } else {
382
381
  // No timeout, just stop sequentially
383
- await this._stopAllSequentially(sorted, results, stopOptions);
382
+ await this.#stopAllSequentially(sorted, results, stopOptions);
384
383
  }
385
384
 
386
385
  return Object.fromEntries(results);
387
386
  }
388
387
 
389
- /**
390
- * Stop services sequentially
391
- *
392
- * @private
393
- * @param {string[]} serviceNames - Ordered list of service names
394
- * @param {Map<string, boolean>} results - Results map to populate
395
- * @param {StopOptions} options - Stop options
396
- */
397
- async _stopAllSequentially(serviceNames, results, options) {
398
- for (const name of serviceNames) {
399
- try {
400
- const success = await this.stopService(name, options);
401
- results.set(name, success);
402
- } catch (error) {
403
- this.logger.error(`Error stopping '${name}'`, error);
404
- results.set(name, false);
405
- }
406
- }
407
- }
408
388
 
409
389
  /**
410
390
  * Get health status for all services
@@ -492,14 +472,41 @@ export class ServiceManager extends EventEmitter {
492
472
  return services;
493
473
  }
494
474
 
495
- // Private methods
496
475
 
497
476
  /**
498
- * Setup logging configuration based on config.dev
477
+ * Attach event listeners to forward service events
499
478
  *
500
- * @private
479
+ * @param {string} name - Service name
480
+ * @param {import('../service-base/typedef.js').ServiceInstance} instance
481
+ * Service instance
501
482
  */
502
- _setupLogging() {
483
+ _attachServiceEvents(name, instance) {
484
+ // Forward service events
485
+ instance.on('stateChanged', (data) => {
486
+ this.emit('service:stateChanged', { service: name, data });
487
+ });
488
+
489
+ instance.on('healthChanged', (data) => {
490
+ this.emit('service:healthChanged', { service: name, data });
491
+ });
492
+
493
+ instance.on('error', (data) => {
494
+ this.emit('service:error', { service: name, data });
495
+ });
496
+
497
+ // Forward log events
498
+
499
+ instance.logger.on('log', (logEvent) => {
500
+ this.emit('service:log', logEvent);
501
+ });
502
+ }
503
+
504
+ // Internal methods
505
+
506
+ /**
507
+ * Setup logging configuration based on config.dev
508
+ */
509
+ #setupLogging() {
503
510
  // Set default log levels based on config.debug flag
504
511
  if (this.config.debug) {
505
512
  this.config.logConfig.defaultLevel = DEBUG;
@@ -516,12 +523,11 @@ export class ServiceManager extends EventEmitter {
516
523
  /**
517
524
  * Get the appropriate log level for a service
518
525
  *
519
- * @private
520
526
  * @param {string} name - Service name
521
527
  *
522
528
  * @returns {string|undefined} Log level or undefined
523
529
  */
524
- _getServiceLogLevel(name) {
530
+ #getServiceLogLevel(name) {
525
531
  const config = this.config.logConfig;
526
532
 
527
533
  // Check in order of precedence:
@@ -541,43 +547,31 @@ export class ServiceManager extends EventEmitter {
541
547
  }
542
548
 
543
549
  /**
544
- * Attach event listeners to forward service events
550
+ * Stop services sequentially
545
551
  *
546
- * @private
547
- * @param {string} name - Service name
548
- * @param {import('../service-base/typedef.js').ServiceInstance} instance
549
- * Service instance
552
+ * @param {string[]} serviceNames - Ordered list of service names
553
+ * @param {Map<string, boolean>} results - Results map to populate
554
+ * @param {StopOptions} options - Stop options
550
555
  */
551
- _attachServiceEvents(name, instance) {
552
- // Forward service events
553
- instance.on('stateChanged', (data) => {
554
- this.emit('service:stateChanged', { service: name, data });
555
- });
556
-
557
- instance.on('healthChanged', (data) => {
558
- this.emit('service:healthChanged', { service: name, data });
559
- });
560
-
561
- instance.on('error', (data) => {
562
- this.emit('service:error', { service: name, data });
563
- });
564
-
565
- // Forward log events
566
-
567
- instance.logger.on('log', (logEvent) => {
568
- this.emit('service:log', logEvent);
569
- });
556
+ async #stopAllSequentially(serviceNames, results, options) {
557
+ for (const name of serviceNames) {
558
+ try {
559
+ const success = await this.stopService(name, options);
560
+ results.set(name, success);
561
+ } catch (error) {
562
+ this.logger.error(`Error stopping '${name}'`, error);
563
+ results.set(name, false);
564
+ }
565
+ }
570
566
  }
571
567
 
572
568
  /**
573
569
  * Sort services by dependencies using topological sort
574
570
  *
575
- * @private
576
- *
577
571
  * @returns {string[]} Service names in dependency order
578
572
  * @throws {Error} If circular dependencies are detected
579
573
  */
580
- _topologicalSort() {
574
+ #topologicalSort() {
581
575
  const sorted = [];
582
576
  const visited = new Set();
583
577
  const visiting = new Set();
@@ -19,11 +19,11 @@
19
19
 
20
20
  import * as v from 'valibot';
21
21
 
22
- import { isObject } from '../is/index.js';
22
+ import * as is from '../is/index.js';
23
23
 
24
24
  /** Internals */
25
25
 
26
- const ObjectSchema = v.custom(isObject, `Invalid type: Expected object`);
26
+ const ObjectSchema = v.custom(is.object, `Invalid type: Expected object`);
27
27
 
28
28
  /** Exports */
29
29
 
@@ -181,7 +181,7 @@ export function iterable(value) {
181
181
  export function store(value) {
182
182
  v.parse(
183
183
  v.custom((value) => {
184
- if (!isObject(value) || typeof value.subscribe !== 'function') {
184
+ if (!is.object(value) || typeof value.subscribe !== 'function') {
185
185
  return false;
186
186
  }
187
187
 
@@ -205,7 +205,7 @@ export function store(value) {
205
205
  export function objectNoArray(value) {
206
206
  v.parse(
207
207
  v.custom((value) => {
208
- if (!isObject(value) || value instanceof Array) {
208
+ if (!is.object(value) || value instanceof Array) {
209
209
  return false;
210
210
  }
211
211
 
@@ -224,7 +224,7 @@ export function objectNoArray(value) {
224
224
  export function objectNoFunction(value) {
225
225
  v.parse(
226
226
  v.custom((value) => {
227
- if (!isObject(value) || typeof value === 'function') {
227
+ if (!is.object(value) || typeof value === 'function') {
228
228
  return false;
229
229
  }
230
230
 
@@ -1,4 +1,4 @@
1
- import { ResponseError } from '../../errors/index.js';
1
+ import { ResponseError, HttpError } from '../../errors/index.js';
2
2
  import * as expect from '../expect/index.js';
3
3
  import { toURL } from './url.js';
4
4
 
@@ -155,11 +155,35 @@ export async function waitForAndCheckResponse(responsePromise, url) {
155
155
  response = await responsePromise;
156
156
 
157
157
  if (response && false === response.ok) {
158
- // if response.ok is false, it also indicates a network error
159
- throw new Error(`Response failed [response.ok=false]`);
158
+ // Check if this is a network error (status 0) vs HTTP error
159
+ if (response.status === 0) {
160
+ // Network error - treat as before
161
+ throw new Error(`Response failed [response.ok=false]`);
162
+ }
163
+
164
+ // HTTP error - get response body for detailed error information
165
+ const responseBody = await response.text();
166
+ let errorDetails;
167
+
168
+ try {
169
+ // Try to parse as JSON (common for API errors)
170
+ errorDetails = JSON.parse(responseBody);
171
+ } catch {
172
+ // Fallback to plain text
173
+ errorDetails = responseBody;
174
+ }
175
+
176
+ throw new HttpError(
177
+ response.status,
178
+ `HTTP ${response.status}: ${response.statusText}`,
179
+ errorDetails
180
+ );
160
181
  }
161
182
  } catch (e) {
162
- if (e instanceof TypeError || response?.ok === false) {
183
+ if (e instanceof HttpError) {
184
+ // Re-throw HttpError as-is
185
+ throw e;
186
+ } else if (e instanceof TypeError || response?.ok === false) {
163
187
  throw new ResponseError(
164
188
  `A network error occurred for request [${href(url)}]`,
165
189
  {
@@ -1,3 +1,5 @@
1
- export * as exceptions from "./exceptions/index.js";
1
+ export { rethrow } from "./exceptions/index.js";
2
2
  export * as expect from "./expect/index.js";
3
+ export * as is from "./is/index.js";
4
+ export * as object from "./object/index.js";
3
5
  export * as singleton from "./singleton/index.js";
@@ -1,3 +1,7 @@
1
- export * as exceptions from './exceptions/index.js';
1
+ export { rethrow } from './exceptions/index.js';
2
+
2
3
  export * as expect from './expect/index.js';
4
+ export * as is from './is/index.js';
5
+ export * as object from './object/index.js';
6
+
3
7
  export * as singleton from './singleton/index.js';
@@ -6,15 +6,7 @@
6
6
  *
7
7
  * @return {boolean} true if the value looks like an array
8
8
  */
9
- export function isArrayLike(item: any): boolean;
10
- /**
11
- * Check if a value is an Arguments object
12
- *
13
- * @param {any} value
14
- *
15
- * @returns {boolean} true if the value is an Arguments object
16
- */
17
- export function isArguments(value: any): boolean;
9
+ export function arrayLike(item: any): boolean;
18
10
  /**
19
11
  * Check if a value is an array that only contains primitives
20
12
  * - A primitive is a not-object value
@@ -23,7 +15,7 @@ export function isArguments(value: any): boolean;
23
15
  *
24
16
  * @return {boolean} true if the value is an array of primitives
25
17
  */
26
- export function isArrayOfPrimitives(arr: any): boolean;
18
+ export function arrayOfPrimitives(value: any): boolean;
27
19
  /**
28
20
  * Check if the supplied value is async iterable
29
21
  * - Aync iterable objects must implement the "@@asyncIterator" method
@@ -32,7 +24,7 @@ export function isArrayOfPrimitives(arr: any): boolean;
32
24
  *
33
25
  * @returns {boolean} true if the value is async iterable
34
26
  */
35
- export function isAsyncIterator(value: any): boolean;
27
+ export function asyncIterator(value: any): boolean;
36
28
  /**
37
29
  * Check if the supplied value is an async function
38
30
  * - Returns true for functions declared as "async function" or
@@ -45,7 +37,7 @@ export function isAsyncIterator(value: any): boolean;
45
37
  *
46
38
  * @returns {boolean} true if the value is an async function
47
39
  */
48
- export function isAsyncFunction(value: any): boolean;
40
+ export function asyncFunction(value: any): boolean;
49
41
  /**
50
42
  * Check if the supplied value is iterable
51
43
  * - Iterable objects must implement the "@@iterator" method
@@ -55,7 +47,7 @@ export function isAsyncFunction(value: any): boolean;
55
47
  *
56
48
  * @returns {boolean} true if the value is (not async) iterable
57
49
  */
58
- export function isIterable(value: any): boolean;
50
+ export function iterable(value: any): boolean;
59
51
  /**
60
52
  * Check if the supplied value is an object bu not a promise
61
53
  * - Promises return false
@@ -65,4 +57,13 @@ export function isIterable(value: any): boolean;
65
57
  *
66
58
  * @returns {boolean} true if the value is an Object, but not a Promise
67
59
  */
68
- export function isObject(value: any): boolean;
60
+ export function object(value: any): boolean;
61
+ export { argumentsCheck as arguments };
62
+ /**
63
+ * Check if a value is an Arguments object
64
+ *
65
+ * @param {any} value
66
+ *
67
+ * @returns {boolean} true if the value is an Arguments object
68
+ */
69
+ declare function argumentsCheck(value: any): boolean;
@@ -16,7 +16,7 @@ const objectToString = Object.prototype.toString;
16
16
  *
17
17
  * @return {boolean} true if the value looks like an array
18
18
  */
19
- export function isArrayLike(item) {
19
+ export function arrayLike(item) {
20
20
  if (!(item instanceof Object)) {
21
21
  return false;
22
22
  }
@@ -35,9 +35,10 @@ export function isArrayLike(item) {
35
35
  *
36
36
  * @returns {boolean} true if the value is an Arguments object
37
37
  */
38
- export function isArguments(value) {
38
+ function argumentsCheck(value) {
39
39
  return objectToString.call(value) === '[object Arguments]';
40
40
  }
41
+ export { argumentsCheck as arguments };
41
42
 
42
43
  /**
43
44
  * Check if a value is an array that only contains primitives
@@ -47,13 +48,13 @@ export function isArguments(value) {
47
48
  *
48
49
  * @return {boolean} true if the value is an array of primitives
49
50
  */
50
- export function isArrayOfPrimitives(arr) {
51
- if (!Array.isArray(arr)) {
51
+ export function arrayOfPrimitives(value) {
52
+ if (!Array.isArray(value)) {
52
53
  return false;
53
54
  }
54
55
 
55
- for (let j = 0, n = arr.length; j < n; j = j + 1) {
56
- if (arr[j] instanceof Object) {
56
+ for (let j = 0, n = value.length; j < n; j = j + 1) {
57
+ if (value[j] instanceof Object) {
57
58
  // current value is not a primitive
58
59
  return false;
59
60
  }
@@ -70,8 +71,11 @@ export function isArrayOfPrimitives(arr) {
70
71
  *
71
72
  * @returns {boolean} true if the value is async iterable
72
73
  */
73
- export function isAsyncIterator(value) {
74
- if (!(value instanceof Object) || typeof value[Symbol.asyncIterator] !== 'function') {
74
+ export function asyncIterator(value) {
75
+ if (
76
+ !(value instanceof Object) ||
77
+ typeof value[Symbol.asyncIterator] !== 'function'
78
+ ) {
75
79
  return false;
76
80
  }
77
81
 
@@ -90,7 +94,7 @@ export function isAsyncIterator(value) {
90
94
  *
91
95
  * @returns {boolean} true if the value is an async function
92
96
  */
93
- export function isAsyncFunction(value) {
97
+ export function asyncFunction(value) {
94
98
  if (value instanceof AsyncFunction) {
95
99
  return true;
96
100
  }
@@ -107,8 +111,11 @@ export function isAsyncFunction(value) {
107
111
  *
108
112
  * @returns {boolean} true if the value is (not async) iterable
109
113
  */
110
- export function isIterable(value) {
111
- if (!(value instanceof Object) || typeof value[Symbol.iterator] !== 'function') {
114
+ export function iterable(value) {
115
+ if (
116
+ !(value instanceof Object) ||
117
+ typeof value[Symbol.iterator] !== 'function'
118
+ ) {
112
119
  return false;
113
120
  }
114
121
 
@@ -124,7 +131,7 @@ export function isIterable(value) {
124
131
  *
125
132
  * @returns {boolean} true if the value is an Object, but not a Promise
126
133
  */
127
- export function isObject(value) {
134
+ export function object(value) {
128
135
  if (!(value instanceof Object)) {
129
136
  if (value && typeof value === 'object') {
130
137
  // e.g. obj = Object.create(null);
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @typedef {import('../../classes/data/typedef.js').IterableTreeOptions} IterableTreeOptions
3
+ */
1
4
  /**
2
5
  * Filter all iterated elements
3
6
  *
@@ -21,111 +24,76 @@ export function map(iterable: Iterable<any>, transformFn: Function): Generator;
21
24
  * Get an Iterator object that can be used to iterate over all
22
25
  * [ path, value ] entries in the object
23
26
  *
24
- * @param {Object} obj - Object to iterate
27
+ * @param {object} obj - Object to iterate
25
28
  *
26
- * @param {Object} [options] - Iteration options
29
+ * @param {object} [options] - Iteration options
27
30
  *
28
31
  * @param {boolean} [options.walkArrays=false]
29
32
  * If set to true, the iterator will also iterate through Array objects
30
33
  *
31
- * @param {object} [options.expandPathKeys=false]
34
+ * @param {boolean} [options.expandPathKeys=false]
32
35
  * If true, keys in the root object like "some.path.to" will be interpreted
33
36
  * as paths in the object
34
37
  *
35
- * @param {object} [options.outputIntermediateNodes=false]
38
+ * @param {boolean} [options.outputIntermediateNodes=false]
36
39
  * If true, intermediate object nodes (not leaves) will be outputted too
37
40
  *
38
- * @param {object} [options.ignoreEmptyObjectLeaves=false]
41
+ * @param {boolean} [options.ignoreEmptyObjectLeaves=false]
39
42
  * If true, no output will be generated for empty objects at the leaves of
40
43
  * the object tree
41
44
  *
45
+ * @param {boolean} [options.depthFirst=true]
46
+ * If true, use depth-first traversal, otherwise breadth-first
47
+ *
42
48
  * @return {Iterator} iterator object
43
49
  */
44
- export function iterateObjectEntries(obj: any, options?: {
50
+ export function iterateObjectEntries(obj: object, options?: {
45
51
  walkArrays?: boolean;
46
- expandPathKeys?: object;
47
- outputIntermediateNodes?: object;
48
- ignoreEmptyObjectLeaves?: object;
52
+ expandPathKeys?: boolean;
53
+ outputIntermediateNodes?: boolean;
54
+ ignoreEmptyObjectLeaves?: boolean;
55
+ depthFirst?: boolean;
49
56
  }): Iterator<any, any, any>;
50
57
  /**
51
58
  * Get an Iterator object that can be used to iterate over all paths in
52
59
  * the object
53
60
  *
54
- * @param {Object} obj - Object to iterate
55
- *
56
- * @param {Object} [options] - Iteration options
57
- *
58
- * @param {boolean} [options.walkArrays=false]
59
- * If set to true, the iterator will also iterate through Array objects
60
- *
61
- * @param {object} [options.expandPathKeys=false]
62
- * If true, keys in the root object like "some.path.to" will be interpreted
63
- * as paths in the object
64
- *
65
- * @param {object} [options.outputIntermediateNodes=false]
66
- * If true, intermediate object nodes (not leaves) will be outputted too
67
- *
68
- * @param {object} [options.ignoreEmptyObjectLeaves=false]
69
- * If true, no output will be generated for empty objects at the leaves of
70
- * the object tree
61
+ * @param {object} obj - Object to iterate
62
+ * @param {IterableTreeOptions & { depthFirst: boolean}} [options]
71
63
  *
72
64
  * @return {Iterator} iterator object
73
65
  */
74
- export function iterateObjectPaths(obj: any, options?: {
75
- walkArrays?: boolean;
76
- expandPathKeys?: object;
77
- outputIntermediateNodes?: object;
78
- ignoreEmptyObjectLeaves?: object;
66
+ export function iterateObjectPaths(obj: object, options?: IterableTreeOptions & {
67
+ depthFirst: boolean;
79
68
  }): Iterator<any, any, any>;
80
69
  /**
81
70
  * Get an Iterator object that can be used to iterate over all values in
82
71
  * the object (at the leaves of the object tree)
83
72
  *
84
- * @param {Object} obj - Object to iterate
85
- *
86
- * @param {Object} [options] - Iteration options
87
- *
88
- * @param {boolean} [options.walkArrays=false]
89
- * If set to true, the iterator will also iterate through Array objects
90
- *
91
- * @param {object} [options.expandPathKeys=false]
92
- * If true, keys in the root object like "some.path.to" will be interpreted
93
- * as paths in the object
94
- *
95
- * @param {object} [options.outputIntermediateNodes=false]
96
- * If true, intermediate object nodes (not leaves) will be outputted too
97
- *
98
- * @param {object} [options.ignoreEmptyObjectLeaves=false]
99
- * If true, no output will be generated for empty objects at the leaves of
100
- * the object tree
73
+ * @param {object} obj - Object to iterate
74
+ * @param {IterableTreeOptions} [options] - Iteration options
101
75
  *
102
76
  * @return {Iterator} iterator object
103
77
  */
104
- export function iterateObjectValues(obj: any, options?: {
105
- walkArrays?: boolean;
106
- expandPathKeys?: object;
107
- outputIntermediateNodes?: object;
108
- ignoreEmptyObjectLeaves?: object;
109
- }): Iterator<any, any, any>;
78
+ export function iterateObjectValues(obj: object, options?: IterableTreeOptions): Iterator<any, any, any>;
110
79
  /**
111
80
  * Get a list of objects returned by an iterator in sorted order
112
81
  *
113
- * --
114
- *
115
82
  * @note Sorting requires that all values are evaluated, so all items must be
116
83
  * iterated
117
84
  *
118
- * --
119
- *
120
- * @param {Iterable} it - Iterable items
121
- *
122
- * @param {function} getValueFn
85
+ * @param {object} _
86
+ * @param {Iterable} _.it - Iterable items
87
+ * @param {function} _.getValueFn
123
88
  * Function that gets the value from the iterated objects
124
- *
125
- * @param {boolean} [reversed=false]
89
+ * @param {boolean} [_.reversed=false]
126
90
  * Sort in reversed order
127
91
  *
128
- *
129
- * @returns {array} objects outputted in sorted order
92
+ * @returns {Promise<array>} objects outputted in sorted order
130
93
  */
131
- export function sortObjects({ it, getValueFn, reversed }: Iterable<any>): any[];
94
+ export function sortObjects({ it, getValueFn, reversed }: {
95
+ it: Iterable<any>;
96
+ getValueFn: Function;
97
+ reversed?: boolean;
98
+ }): Promise<any[]>;
99
+ export type IterableTreeOptions = import("../../classes/data/typedef.js").IterableTreeOptions;