@nu-art/ts-common 0.401.9 → 0.500.6

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.
Files changed (43) hide show
  1. package/core/application.d.ts +1 -1
  2. package/core/application.js +4 -4
  3. package/core/error-handling.d.ts +2 -2
  4. package/core/logger/index.d.ts +1 -0
  5. package/core/logger/index.js +1 -0
  6. package/core/module-manager.d.ts +3 -3
  7. package/core/module-manager.js +3 -1
  8. package/core/module.d.ts +17 -55
  9. package/core/module.js +18 -75
  10. package/index.d.ts +1 -0
  11. package/index.js +1 -0
  12. package/mem-cache/MemCache.d.ts +57 -0
  13. package/mem-cache/MemCache.js +94 -0
  14. package/mem-storage/MemStorage.d.ts +6 -0
  15. package/mem-storage/MemStorage.js +6 -0
  16. package/package.json +7 -7
  17. package/testing/workspace-creator.d.ts +1 -1
  18. package/testing.d.ts +1 -1
  19. package/testing.js +1 -1
  20. package/utils/date-time-tools.d.ts +14 -0
  21. package/utils/date-time-tools.js +53 -0
  22. package/utils/db-object-tools.js +5 -1
  23. package/utils/hash-tools.js +3 -3
  24. package/utils/query-params.d.ts +1 -1
  25. package/utils/query-params.js +4 -4
  26. package/utils/random-tools.d.ts +0 -1
  27. package/utils/random-tools.js +0 -24
  28. package/utils/string-tools.d.ts +10 -0
  29. package/utils/string-tools.js +45 -0
  30. package/utils/tools.d.ts +6 -0
  31. package/utils/tools.js +8 -0
  32. package/utils/types.d.ts +0 -9
  33. package/validator/type-validators.d.ts +3 -1
  34. package/validator/type-validators.js +2 -0
  35. package/validator/validator-core.d.ts +1 -1
  36. package/validator/validators.d.ts +1 -2
  37. package/validator/validators.js +0 -1
  38. package/mem-storage/index.d.ts +0 -1
  39. package/mem-storage/index.js +0 -1
  40. package/testing/index.d.ts +0 -1
  41. package/testing/index.js +0 -1
  42. package/utils/index.d.ts +0 -27
  43. package/utils/index.js +0 -27
@@ -1,4 +1,4 @@
1
- import { ModuleManager } from "./module-manager.js";
1
+ import { ModuleManager } from './module-manager.js';
2
2
  /**
3
3
  * Application class that extends ModuleManager with startup callback support.
4
4
  *
@@ -15,7 +15,7 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- import { ModuleManager } from "./module-manager.js";
18
+ import { ModuleManager } from './module-manager.js';
19
19
  /**
20
20
  * Application class that extends ModuleManager with startup callback support.
21
21
  *
@@ -52,9 +52,9 @@ export class Application extends ModuleManager {
52
52
  super.build();
53
53
  onStarted && onStarted()
54
54
  .then((data) => {
55
- data && this.logInfo("data: ", data);
56
- this.logInfo("Completed");
55
+ data && this.logInfo('data: ', data);
56
+ this.logInfo('Completed');
57
57
  })
58
- .catch((err) => this.logError("Error", err));
58
+ .catch((err) => this.logError('Error', err));
59
59
  }
60
60
  }
@@ -47,7 +47,7 @@ export interface OnApplicationNotification {
47
47
  * Use this to notify all modules that implement `OnApplicationNotification`
48
48
  * about application-level events.
49
49
  */
50
- export declare const dispatch_onApplicationNotification: Dispatcher<OnApplicationNotification, "__processApplicationNotification", [errorLevel: ServerErrorSeverity, module: Module<any, any, import("../index.js").Validator<any> | import("../index.js").TypeValidator<any>>, message: ErrorMessage], void>;
50
+ export declare const dispatch_onApplicationNotification: Dispatcher<OnApplicationNotification, "__processApplicationNotification", [errorLevel: ServerErrorSeverity, module: Module<any>, message: ErrorMessage], void>;
51
51
  /**
52
52
  * Interface for modules that handle application exceptions.
53
53
  *
@@ -69,4 +69,4 @@ export interface OnApplicationException {
69
69
  * Use this to notify all modules that implement `OnApplicationException`
70
70
  * about exceptions that occur at the application level.
71
71
  */
72
- export declare const dispatch_onApplicationException: Dispatcher<OnApplicationException, "__processApplicationException", [e: CustomException, module: Module<any, any, import("../index.js").Validator<any> | import("../index.js").TypeValidator<any>>], void>;
72
+ export declare const dispatch_onApplicationException: Dispatcher<OnApplicationException, "__processApplicationException", [e: CustomException, module: Module<any>], void>;
@@ -1 +1,2 @@
1
1
  export * from '@nu-art/logger';
2
+ export { LogClient_Browser, LogClient_BrowserGroups } from '@nu-art/logger/browser';
@@ -1 +1,2 @@
1
1
  export * from '@nu-art/logger';
2
+ export { LogClient_Browser, LogClient_BrowserGroups } from '@nu-art/logger/browser';
@@ -8,7 +8,7 @@ import { Logger } from './logger/index.js';
8
8
  *
9
9
  * @internal
10
10
  */
11
- export declare function moduleResolver(): Module<any, any, import("../index.js").Validator<any> | import("../index.js").TypeValidator<any>>[];
11
+ export declare function moduleResolver(): Module<any>[];
12
12
  /**
13
13
  * Runtime function to access all registered modules.
14
14
  *
@@ -21,7 +21,7 @@ export declare const RuntimeModules: () => {
21
21
  map: <T, S>(processor: (item: T, index: number, array: T[]) => S) => S[];
22
22
  forEach: <T>(processor: (item: T, index: number, array: T[]) => void) => void;
23
23
  includes: <T>(module: T) => boolean;
24
- all: Module<any, any, import("../index.js").Validator<any> | import("../index.js").TypeValidator<any>>[];
24
+ all: Module<any>[];
25
25
  };
26
26
  /**
27
27
  * Runtime function to get the application version.
@@ -79,7 +79,7 @@ export declare class ModuleManager extends Logger {
79
79
  map: <T, S>(processor: (item: T, index: number, array: T[]) => S) => S[];
80
80
  forEach: <T>(processor: (item: T, index: number, array: T[]) => void) => void;
81
81
  includes: <T>(module: T) => boolean;
82
- all: Module<any, any, import("../index.js").Validator<any> | import("../index.js").TypeValidator<any>>[];
82
+ all: Module<any>[];
83
83
  };
84
84
  /** Singleton instance of ModuleManager */
85
85
  static instance: ModuleManager;
@@ -205,8 +205,10 @@ export class ModuleManager extends Logger {
205
205
  * @throws {BadImplementationException} If any module is undefined (cyclic import issue)
206
206
  */
207
207
  init() {
208
- if (this.config.logLevel)
208
+ if (this.config.logLevel) {
209
209
  this.setMinLevel(this.config.logLevel);
210
+ this.modules.forEach((module) => module.setMinLevel(this.config.logLevel));
211
+ }
210
212
  this.logInfo(`--------- initializing app ---------`);
211
213
  const undefinedModule = this.modules.some(module => !exists(module));
212
214
  if (undefinedModule) {
package/core/module.d.ts CHANGED
@@ -3,8 +3,11 @@
3
3
  */
4
4
  import { ModuleManager } from './module-manager.js';
5
5
  import { Logger, LogLevel } from './logger/index.js';
6
- import { ValidatorTypeResolver } from '../validator/validator-core.js';
7
- import { TimerHandler } from '../utils/date-time-tools.js';
6
+ import { TypeValidator } from '../validator/validator-core.js';
7
+ import { TS_Object } from '../utils/types.js';
8
+ type _ModuleConfig<C extends TS_Object> = C & {
9
+ minLogLevel?: LogLevel;
10
+ };
8
11
  /**
9
12
  * Base abstract class for all modules in the nu-art ecosystem.
10
13
  *
@@ -21,9 +24,8 @@ import { TimerHandler } from '../utils/date-time-tools.js';
21
24
  * 3. `init()` - Override to perform initialization logic
22
25
  * 4. `validate()` - Override to validate module state after initialization
23
26
  *
24
- * @template Config - The configuration type for this module
25
- * @template ModuleConfig - Extended config type that includes optional `minLogLevel`
26
- * @template ConfigValidator - Validator type resolver for config validation
27
+ * @template Config - The user-facing configuration type for this module.
28
+ * Internal fields (e.g. minLogLevel) are computed from Config and hidden from consumers.
27
29
  *
28
30
  * @example
29
31
  * ```typescript
@@ -40,24 +42,17 @@ import { TimerHandler } from '../utils/date-time-tools.js';
40
42
  * export const MyModule = new MyModule_Class();
41
43
  * ```
42
44
  */
43
- export declare abstract class Module<Config = any, ModuleConfig extends Config & {
44
- minLogLevel?: LogLevel;
45
- } = Config & {
46
- minLogLevel?: LogLevel;
47
- }, ConfigValidator extends ValidatorTypeResolver<ModuleConfig> = ValidatorTypeResolver<ModuleConfig>> extends Logger {
45
+ export declare abstract class Module<Config extends TS_Object = any> extends Logger {
46
+ private readonly classStack;
48
47
  private name;
49
48
  /** Module configuration, merged from default config and ModuleManager-provided config */
50
- readonly config: ModuleConfig;
49
+ protected config: _ModuleConfig<Config>;
51
50
  /** Reference to the ModuleManager instance, injected during initialization */
52
51
  protected readonly manager: ModuleManager;
53
52
  /** Flag indicating whether the module has been initialized (set by ModuleManager) */
54
53
  protected readonly initiated = false;
55
54
  /** Optional config validator, set via setConfigValidator() */
56
- protected readonly configValidator?: ConfigValidator;
57
- /** Internal map for managing debounce/throttle timeouts by key */
58
- protected timeoutMap: {
59
- [k: string]: number;
60
- };
55
+ protected configValidator?: TypeValidator<_ModuleConfig<Config>>;
61
56
  /**
62
57
  * Creates a new Module instance.
63
58
  *
@@ -68,43 +63,9 @@ export declare abstract class Module<Config = any, ModuleConfig extends Config &
68
63
  * @throws {BadImplementationException} If the class name doesn't end with `_Class`
69
64
  */
70
65
  constructor(tag?: string);
71
- /**
72
- * Debounces a function call, canceling any pending execution with the same key
73
- * and scheduling a new execution after the specified delay.
74
- *
75
- * Each call with the same key cancels the previous pending execution and resets
76
- * the timer. Useful for rate-limiting user input or API calls.
77
- *
78
- * @param handler - Function to execute after the delay
79
- * @param key - Unique key to identify this debounced operation. Multiple calls
80
- * with the same key will cancel previous pending executions.
81
- * @param ms - Delay in milliseconds before executing the handler (default: 0)
82
- *
83
- * @example
84
- * ```typescript
85
- * // Debounce search input
86
- * this.debounce(() => this.performSearch(query), 'search', 300);
87
- * ```
88
- */
89
- debounce(handler: TimerHandler, key: string, ms?: number): void;
90
- /**
91
- * Throttles a function call, ensuring it executes at most once per time period.
92
- *
93
- * Unlike debounce, throttle executes immediately if no execution is pending,
94
- * then prevents further executions until the time period expires. Useful for
95
- * limiting the frequency of expensive operations.
96
- *
97
- * @param handler - Function to execute
98
- * @param key - Unique key to identify this throttled operation
99
- * @param ms - Minimum time in milliseconds between executions (default: 0)
100
- *
101
- * @example
102
- * ```typescript
103
- * // Throttle scroll handler
104
- * this.throttle(() => this.updateScrollPosition(), 'scroll', 100);
105
- * ```
106
- */
107
- throttle(handler: TimerHandler, key: string, ms?: number): void;
66
+ protected addToClassStack: (cls: Function) => void;
67
+ isInstanceOf: <T extends Function>(cls: T) => this is T;
68
+ isInstanceType: <T extends Function>(cls: T) => this is T;
108
69
  /**
109
70
  * Sets a config validator for runtime validation of module configuration.
110
71
  *
@@ -113,7 +74,7 @@ export declare abstract class Module<Config = any, ModuleConfig extends Config &
113
74
  *
114
75
  * @param validator - Validator instance that implements ValidatorTypeResolver
115
76
  */
116
- setConfigValidator(validator: ConfigValidator): void;
77
+ setConfigValidator(validator: TypeValidator<Config>): void;
117
78
  /**
118
79
  * Sets default configuration values that will be merged with any config
119
80
  * provided by the ModuleManager.
@@ -123,7 +84,7 @@ export declare abstract class Module<Config = any, ModuleConfig extends Config &
123
84
  *
124
85
  * @param config - Partial config object to merge with existing config
125
86
  */
126
- setDefaultConfig(config: Partial<ModuleConfig>): void;
87
+ setDefaultConfig(config: Partial<_ModuleConfig<Config>>): void;
127
88
  /**
128
89
  * Gets the module name (class name without `_Class` suffix).
129
90
  */
@@ -200,3 +161,4 @@ export declare abstract class Module<Config = any, ModuleConfig extends Config &
200
161
  protected validate(): void;
201
162
  protected destroy(): Promise<void>;
202
163
  }
164
+ export {};
package/core/module.js CHANGED
@@ -17,8 +17,9 @@
17
17
  */
18
18
  import { BadImplementationException } from './exceptions/exceptions.js';
19
19
  import { merge } from '../utils/merge-tools.js';
20
- import { Logger } from './logger/index.js';
21
- import { _clearTimeout, _setTimeout } from '../utils/date-time-tools.js';
20
+ import { Logger, LogLevel } from './logger/index.js';
21
+ import { lastElement } from '../utils/array-tools.js';
22
+ import { tsValidateEnum } from '../validator/type-validators.js';
22
23
  /**
23
24
  * Base abstract class for all modules in the nu-art ecosystem.
24
25
  *
@@ -35,9 +36,8 @@ import { _clearTimeout, _setTimeout } from '../utils/date-time-tools.js';
35
36
  * 3. `init()` - Override to perform initialization logic
36
37
  * 4. `validate()` - Override to validate module state after initialization
37
38
  *
38
- * @template Config - The configuration type for this module
39
- * @template ModuleConfig - Extended config type that includes optional `minLogLevel`
40
- * @template ConfigValidator - Validator type resolver for config validation
39
+ * @template Config - The user-facing configuration type for this module.
40
+ * Internal fields (e.g. minLogLevel) are computed from Config and hidden from consumers.
41
41
  *
42
42
  * @example
43
43
  * ```typescript
@@ -55,6 +55,7 @@ import { _clearTimeout, _setTimeout } from '../utils/date-time-tools.js';
55
55
  * ```
56
56
  */
57
57
  export class Module extends Logger {
58
+ classStack = [];
58
59
  name;
59
60
  /** Module configuration, merged from default config and ModuleManager-provided config */
60
61
  config = {};
@@ -64,8 +65,6 @@ export class Module extends Logger {
64
65
  initiated = false;
65
66
  /** Optional config validator, set via setConfigValidator() */
66
67
  configValidator;
67
- /** Internal map for managing debounce/throttle timeouts by key */
68
- timeoutMap = {};
69
68
  /**
70
69
  * Creates a new Module instance.
71
70
  *
@@ -83,68 +82,15 @@ export class Module extends Logger {
83
82
  throw new BadImplementationException(`Found module named: ${this.name}, Module class MUST end with '_Class' e.g. MyModule_Class`);
84
83
  this.name = this.name.replace('_Class', '');
85
84
  }
86
- /**
87
- * Debounces a function call, canceling any pending execution with the same key
88
- * and scheduling a new execution after the specified delay.
89
- *
90
- * Each call with the same key cancels the previous pending execution and resets
91
- * the timer. Useful for rate-limiting user input or API calls.
92
- *
93
- * @param handler - Function to execute after the delay
94
- * @param key - Unique key to identify this debounced operation. Multiple calls
95
- * with the same key will cancel previous pending executions.
96
- * @param ms - Delay in milliseconds before executing the handler (default: 0)
97
- *
98
- * @example
99
- * ```typescript
100
- * // Debounce search input
101
- * this.debounce(() => this.performSearch(query), 'search', 300);
102
- * ```
103
- */
104
- debounce(handler, key, ms = 0) {
105
- _clearTimeout(this.timeoutMap[key]);
106
- this.timeoutMap[key] = _setTimeout(handler, ms);
107
- }
108
- // // possibly to add
109
- // public async debounceSync(handler: TimerHandler, key: string, ms = 0) {
110
- // _clearTimeout(this.timeoutMap[key]);
111
- //
112
- // await new Promise((resolve, reject) => {
113
- // this.timeoutMap[key] = setTimeout(async (..._args) => {
114
- // try {
115
- // await handler(..._args);
116
- // resolve();
117
- // } catch (e:any) {
118
- // reject(e);
119
- // }
120
- // }, ms) as unknown as number;
121
- // });
122
- // }
123
- /**
124
- * Throttles a function call, ensuring it executes at most once per time period.
125
- *
126
- * Unlike debounce, throttle executes immediately if no execution is pending,
127
- * then prevents further executions until the time period expires. Useful for
128
- * limiting the frequency of expensive operations.
129
- *
130
- * @param handler - Function to execute
131
- * @param key - Unique key to identify this throttled operation
132
- * @param ms - Minimum time in milliseconds between executions (default: 0)
133
- *
134
- * @example
135
- * ```typescript
136
- * // Throttle scroll handler
137
- * this.throttle(() => this.updateScrollPosition(), 'scroll', 100);
138
- * ```
139
- */
140
- throttle(handler, key, ms = 0) {
141
- if (this.timeoutMap[key])
142
- return;
143
- this.timeoutMap[key] = _setTimeout(() => {
144
- handler();
145
- delete this.timeoutMap[key];
146
- }, ms);
147
- }
85
+ addToClassStack = (cls) => {
86
+ this.classStack.push(cls.name);
87
+ };
88
+ isInstanceOf = (cls) => {
89
+ return this.classStack.includes(cls.name);
90
+ };
91
+ isInstanceType = (cls) => {
92
+ return lastElement(this.classStack) === cls.name;
93
+ };
148
94
  /**
149
95
  * Sets a config validator for runtime validation of module configuration.
150
96
  *
@@ -154,8 +100,7 @@ export class Module extends Logger {
154
100
  * @param validator - Validator instance that implements ValidatorTypeResolver
155
101
  */
156
102
  setConfigValidator(validator) {
157
- // @ts-ignore
158
- this.configValidator = validator;
103
+ this.configValidator = { ...validator, minLogLevel: tsValidateEnum(LogLevel, false) };
159
104
  }
160
105
  /**
161
106
  * Sets default configuration values that will be merged with any config
@@ -167,7 +112,6 @@ export class Module extends Logger {
167
112
  * @param config - Partial config object to merge with existing config
168
113
  */
169
114
  setDefaultConfig(config) {
170
- // @ts-ignore
171
115
  this.config = merge(this.config, config);
172
116
  }
173
117
  /**
@@ -194,8 +138,7 @@ export class Module extends Logger {
194
138
  */
195
139
  // @ts-ignore
196
140
  setConfig(config) {
197
- // @ts-ignore
198
- this.config = this.config ? merge(this.config, config) : config;
141
+ this.config = (this.config ? merge(this.config, config) : config);
199
142
  this.config.minLogLevel && this.setMinLevel(this.config.minLogLevel);
200
143
  }
201
144
  /**
@@ -209,7 +152,7 @@ export class Module extends Logger {
209
152
  // @ts-ignore
210
153
  setManager(manager) {
211
154
  // @ts-ignore
212
- this.manager = manager;
155
+ this['manager'] = manager;
213
156
  }
214
157
  /**
215
158
  * Executes an async function in a fire-and-forget manner, logging the execution.
package/index.d.ts CHANGED
@@ -35,4 +35,5 @@ export * from './validator/validator-core.js';
35
35
  export * from './validator/validators.js';
36
36
  export * from './validator/type-validators.js';
37
37
  export * from './consts/consts.js';
38
+ export * from './mem-cache/MemCache.js';
38
39
  export * from './modules/csv-serializer.js';
package/index.js CHANGED
@@ -52,4 +52,5 @@ export * from './validator/validator-core.js';
52
52
  export * from './validator/validators.js';
53
53
  export * from './validator/type-validators.js';
54
54
  export * from './consts/consts.js';
55
+ export * from './mem-cache/MemCache.js';
55
56
  export * from './modules/csv-serializer.js';
@@ -0,0 +1,57 @@
1
+ import { TypedMap } from '../utils/types.js';
2
+ /** Key reference for lookup: string id or partial object to resolve via keyToId */
3
+ export type MemCacheKeyRef = string | Record<string, unknown>;
4
+ /**
5
+ * Options for constructing a MemCache. Id extraction is pluggable; no DB or domain types.
6
+ */
7
+ export type MemCacheOptions<T extends object> = {
8
+ /** Extracts a stable string id from an item. Used for map storage and delta updates. */
9
+ getId: (item: T) => string;
10
+ /** Resolves a lookup key (string or partial object) to id. Omit if lookups are always by string id. */
11
+ keyToId?: (key: MemCacheKeyRef) => string;
12
+ };
13
+ /**
14
+ * In-memory cache with pluggable id extraction.
15
+ *
16
+ * Provides fast, synchronous access with filtering, mapping, unique lookups, and delta updates.
17
+ * Items are stored frozen. No dependency on DB or Proto; app provides getId/keyToId.
18
+ *
19
+ * @template T - Item type
20
+ */
21
+ export declare class MemCache<T extends object> {
22
+ private readonly getId;
23
+ private readonly keyToId;
24
+ loaded: boolean;
25
+ private _map;
26
+ private _array;
27
+ protected cacheFilter?: (item: Readonly<T>) => boolean;
28
+ constructor(options: MemCacheOptions<T>);
29
+ setCacheFilter: (filter: (item: Readonly<T>) => boolean) => void;
30
+ getCacheFilter: () => ((item: Readonly<T>) => boolean) | undefined;
31
+ forEach: (processor: (item: Readonly<T>) => void) => void;
32
+ clear: () => void;
33
+ /**
34
+ * Load items into cache. Items are frozen.
35
+ */
36
+ load(items: T[]): void;
37
+ private resolveKey;
38
+ uniqueAssert: (key?: MemCacheKeyRef) => Readonly<T>;
39
+ unique: (key?: MemCacheKeyRef) => Readonly<T> | undefined;
40
+ all: () => Readonly<Readonly<T>[]>;
41
+ allMutable: () => Readonly<T>[];
42
+ filter: (filter: (item: Readonly<T>, index: number, array: Readonly<T[]>) => boolean) => Readonly<T>[];
43
+ byIds: (ids: MemCacheKeyRef[]) => (Readonly<T> | undefined)[];
44
+ find: (filter: (item: Readonly<T>, index: number, array: Readonly<T[]>) => boolean) => Readonly<T> | undefined;
45
+ map: <U>(mapper: (item: Readonly<T>, index: number, array: Readonly<T[]>) => U) => U[];
46
+ sort: (map?: keyof T | (keyof T)[] | ((item: Readonly<T>) => unknown), invert?: boolean) => Readonly<T>[];
47
+ arrayToMap: (getKey: (item: Readonly<T>, index: number, map: TypedMap<Readonly<T>>) => string | number | (string | number)[], map?: TypedMap<Readonly<T>>) => TypedMap<Readonly<T>>;
48
+ /**
49
+ * Update cache after entries are deleted. Uses getId for identity.
50
+ */
51
+ onEntriesDeleted(itemsDeleted: T[]): void;
52
+ /**
53
+ * Update cache after entries are updated/created. Uses getId for identity.
54
+ */
55
+ onEntriesUpdated(itemsUpdated: T[]): void;
56
+ protected setCache(cacheArray: Readonly<T>[]): void;
57
+ }
@@ -0,0 +1,94 @@
1
+ /*
2
+ * ts-common - Core TypeScript infrastructure
3
+ * Copyright (C) 2020 Adam van der Kruk aka TacB0sS
4
+ * Licensed under the Apache License, Version 2.0
5
+ */
6
+ import { arrayToMap, sortArray } from '../utils/array-tools.js';
7
+ import { BadImplementationException } from '../core/exceptions/exceptions.js';
8
+ /**
9
+ * In-memory cache with pluggable id extraction.
10
+ *
11
+ * Provides fast, synchronous access with filtering, mapping, unique lookups, and delta updates.
12
+ * Items are stored frozen. No dependency on DB or Proto; app provides getId/keyToId.
13
+ *
14
+ * @template T - Item type
15
+ */
16
+ export class MemCache {
17
+ getId;
18
+ keyToId;
19
+ loaded = false;
20
+ _map;
21
+ _array;
22
+ cacheFilter;
23
+ constructor(options) {
24
+ this.getId = options.getId;
25
+ this.keyToId = options.keyToId;
26
+ this.clear();
27
+ }
28
+ setCacheFilter = (filter) => {
29
+ this.cacheFilter = filter;
30
+ };
31
+ getCacheFilter = () => this.cacheFilter;
32
+ forEach = (processor) => {
33
+ this._array.forEach(processor);
34
+ };
35
+ clear = () => {
36
+ this.setCache([]);
37
+ this.loaded = false;
38
+ };
39
+ /**
40
+ * Load items into cache. Items are frozen.
41
+ */
42
+ load(items) {
43
+ const frozen = items.map(item => Object.freeze(item));
44
+ this.setCache(frozen);
45
+ this.loaded = true;
46
+ }
47
+ resolveKey(key) {
48
+ if (typeof key === 'string')
49
+ return key;
50
+ if (this.keyToId)
51
+ return this.keyToId(key);
52
+ return undefined;
53
+ }
54
+ uniqueAssert = (key) => {
55
+ const item = this.unique(key);
56
+ if (!item)
57
+ throw new BadImplementationException(`Missing expected item for keys: ${JSON.stringify(key)}`);
58
+ return item;
59
+ };
60
+ unique = (key) => {
61
+ if (key === undefined)
62
+ return undefined;
63
+ const id = this.resolveKey(key);
64
+ return id !== undefined ? this._map[id] : undefined;
65
+ };
66
+ all = () => this._array;
67
+ allMutable = () => [...this._array];
68
+ filter = (filter) => this.all().filter(filter);
69
+ byIds = (ids) => ids.map(id => this.unique(id));
70
+ find = (filter) => this.all().find(filter);
71
+ map = (mapper) => this.all().map(mapper);
72
+ sort = (map = (i) => i, invert = false) => sortArray(this.allMutable(), map, invert);
73
+ arrayToMap = (getKey, map = {}) => arrayToMap(this.allMutable(), getKey, map);
74
+ /**
75
+ * Update cache after entries are deleted. Uses getId for identity.
76
+ */
77
+ onEntriesDeleted(itemsDeleted) {
78
+ const ids = new Set(itemsDeleted.map(this.getId));
79
+ this.setCache(this.filter(i => !ids.has(this.getId(i))));
80
+ }
81
+ /**
82
+ * Update cache after entries are updated/created. Uses getId for identity.
83
+ */
84
+ onEntriesUpdated(itemsUpdated) {
85
+ const frozen = itemsUpdated.map(item => Object.freeze(item));
86
+ const ids = new Set(itemsUpdated.map(this.getId));
87
+ const retained = this.filter(i => !ids.has(this.getId(i)));
88
+ this.setCache(retained.concat(frozen));
89
+ }
90
+ setCache(cacheArray) {
91
+ this._map = Object.freeze({ ...arrayToMap(cacheArray, (item) => this.getId(item)) });
92
+ this._array = Object.freeze(cacheArray);
93
+ }
94
+ }
@@ -86,6 +86,12 @@ export declare class MemStorage {
86
86
  * Provides a type-safe interface for accessing MemStorage values.
87
87
  * Supports unique keys (prevent overwriting), lazy resolution, and assertions.
88
88
  *
89
+ * **Design – never set nothing**: MemKeys must never be set to undefined or null.
90
+ * There is no point in setting something that is nothing; it would be like deleting
91
+ * or overwriting relevant state that should persist. Callers must use a meaningful
92
+ * empty value for their type (e.g. `''` for string, `{}` for object) when the logical
93
+ * value is "absent". `set()` throws if given undefined or null.
94
+ *
89
95
  * **Usage**:
90
96
  * ```typescript
91
97
  * const userKey = new MemKey<User>('user', true); // unique key
@@ -128,6 +128,12 @@ export class MemStorage {
128
128
  * Provides a type-safe interface for accessing MemStorage values.
129
129
  * Supports unique keys (prevent overwriting), lazy resolution, and assertions.
130
130
  *
131
+ * **Design – never set nothing**: MemKeys must never be set to undefined or null.
132
+ * There is no point in setting something that is nothing; it would be like deleting
133
+ * or overwriting relevant state that should persist. Callers must use a meaningful
134
+ * empty value for their type (e.g. `''` for string, `{}` for object) when the logical
135
+ * value is "absent". `set()` throws if given undefined or null.
136
+ *
131
137
  * **Usage**:
132
138
  * ```typescript
133
139
  * const userKey = new MemKey<User>('user', true); // unique key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-art/ts-common",
3
- "version": "0.401.9",
3
+ "version": "0.500.6",
4
4
  "description": "Core TypeScript infrastructure library for building modular applications with lifecycle management, logging, validation, and utilities",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -37,8 +37,8 @@
37
37
  "run-tests": "ts-mocha --timeout 50000 -p src/test/tsconfig.json src/test/run-all-tests.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@nu-art/testalot": "0.401.9",
41
- "@nu-art/logger": "0.401.9",
40
+ "@nu-art/testalot": "0.500.6",
41
+ "@nu-art/logger": "0.500.6",
42
42
  "fast-csv": "^5.0.2",
43
43
  "export-to-csv": "0.2.1",
44
44
  "moment": "^2.29.4",
@@ -61,12 +61,12 @@
61
61
  "import": "./index.js"
62
62
  },
63
63
  "./utils": {
64
- "types": "./utils/index.d.ts",
65
- "import": "./utils/index.js"
64
+ "types": "./utils-public-exports.d.ts",
65
+ "import": "./utils-public-exports.js"
66
66
  },
67
67
  "./mem-storage": {
68
- "types": "./mem-storage/index.d.ts",
69
- "import": "./mem-storage/index.js"
68
+ "types": "./mem-storage/MemStorage.d.ts",
69
+ "import": "./mem-storage/MemStorage.js"
70
70
  },
71
71
  "./testing": {
72
72
  "types": "./testing.d.ts",
@@ -1,5 +1,5 @@
1
1
  import { Logger } from '../core/logger/index.js';
2
- import { StringMap } from '../utils/index.js';
2
+ import { StringMap } from '../utils/types.js';
3
3
  /**
4
4
  * Creates test workspaces from fixture files.
5
5
  *
package/testing.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './testing/index.js';
1
+ export * from '@nu-art/testalot';
2
2
  export * from './testing/workspace-creator.js';
package/testing.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from './testing/index.js';
1
+ export * from '@nu-art/testalot';
2
2
  export * from './testing/workspace-creator.js';
@@ -7,6 +7,20 @@ export declare const Day: number;
7
7
  export declare const Week: number;
8
8
  export declare const Year: number;
9
9
  export declare const Month: number;
10
+ /**
11
+ * Parse and format durations using compact notation (s/m/h/d/w).
12
+ *
13
+ * - `parse("3h1m30s")` → `10890000` (ms)
14
+ * - `format(10890000)` → `"3h1m30s"`
15
+ */
16
+ export declare const StringFormat_Duration: {
17
+ format(ms: number): string;
18
+ parse(duration: string): number;
19
+ };
20
+ /** @see StringFormat_Duration.format */
21
+ export declare const formatDuration: (ms: number) => string;
22
+ /** @see StringFormat_Duration.parse */
23
+ export declare const parseDuration: (duration: string) => number;
10
24
  /** Predefined timestamp format strings */
11
25
  export declare const Format_HHmmss_DDMMYYYY = "HH:mm:ss_DD-MM-YYYY";
12
26
  export declare const Format_YYYYMMDD_HHmmss = "YYYY-MM-DD_HH:mm:ss";
@@ -18,6 +18,7 @@
18
18
  import utc from 'moment';
19
19
  import { exists } from './tools.js';
20
20
  import { TimeProxy } from './time-proxy.js';
21
+ import { BadImplementationException } from '../core/exceptions/exceptions.js';
21
22
  /** Time constants in milliseconds */
22
23
  export const Second = 1000;
23
24
  export const Minute = Second * 60;
@@ -26,6 +27,58 @@ export const Day = Hour * 24;
26
27
  export const Week = Day * 7;
27
28
  export const Year = Day * 365;
28
29
  export const Month = Year / 12;
30
+ const durationUnits = {
31
+ s: Second,
32
+ m: Minute,
33
+ h: Hour,
34
+ d: Day,
35
+ w: Week,
36
+ };
37
+ const durationPattern = /^(?:\d+[smhdw])+$/;
38
+ const durationSegment = /(\d+)([smhdw])/g;
39
+ const durationUnitOrder = [
40
+ { unit: 'd', ms: Day },
41
+ { unit: 'h', ms: Hour },
42
+ { unit: 'm', ms: Minute },
43
+ { unit: 's', ms: Second },
44
+ ];
45
+ /**
46
+ * Parse and format durations using compact notation (s/m/h/d/w).
47
+ *
48
+ * - `parse("3h1m30s")` → `10890000` (ms)
49
+ * - `format(10890000)` → `"3h1m30s"`
50
+ */
51
+ export const StringFormat_Duration = {
52
+ format(ms) {
53
+ if (ms <= 0)
54
+ return '0s';
55
+ let remaining = ms;
56
+ let result = '';
57
+ for (const { unit, ms: unitMs } of durationUnitOrder) {
58
+ const count = Math.floor(remaining / unitMs);
59
+ if (count > 0) {
60
+ result += `${count}${unit}`;
61
+ remaining -= count * unitMs;
62
+ }
63
+ }
64
+ return result || '0s';
65
+ },
66
+ parse(duration) {
67
+ if (!durationPattern.test(duration))
68
+ throw new BadImplementationException(`Invalid duration format: "${duration}" — expected segments like 5s, 1m, 3h1m30s`);
69
+ let total = 0;
70
+ let match;
71
+ durationSegment.lastIndex = 0;
72
+ while ((match = durationSegment.exec(duration)) !== null) {
73
+ total += parseInt(match[1]) * durationUnits[match[2]];
74
+ }
75
+ return total;
76
+ },
77
+ };
78
+ /** @see StringFormat_Duration.format */
79
+ export const formatDuration = StringFormat_Duration.format;
80
+ /** @see StringFormat_Duration.parse */
81
+ export const parseDuration = StringFormat_Duration.parse;
29
82
  /** Predefined timestamp format strings */
30
83
  export const Format_HHmmss_DDMMYYYY = 'HH:mm:ss_DD-MM-YYYY';
31
84
  export const Format_YYYYMMDD_HHmmss = 'YYYY-MM-DD_HH:mm:ss';
@@ -9,7 +9,11 @@ export const DB_OBJECT_PROP__CREATED = '__created';
9
9
  /** Database object property name for update timestamp */
10
10
  export const DB_OBJECT_PROP__UPDATED = '__updated';
11
11
  /** Array of all database object metadata keys */
12
- export const KeysOfDB_Object = [DB_OBJECT_PROP__ID, DB_OBJECT_PROP__VERSION, DB_OBJECT_PROP__CREATED, DB_OBJECT_PROP__UPDATED, '__metadata1'];
12
+ export const KeysOfDB_Object = [DB_OBJECT_PROP__ID,
13
+ DB_OBJECT_PROP__VERSION,
14
+ DB_OBJECT_PROP__CREATED,
15
+ DB_OBJECT_PROP__UPDATED,
16
+ '__metadata1'];
13
17
  /**
14
18
  * Extracts the ID from a database object.
15
19
  *
@@ -15,7 +15,7 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- import md from "node-forge";
18
+ import md from 'node-forge';
19
19
  /**
20
20
  * Computes MD5 hash of input data.
21
21
  *
@@ -84,7 +84,7 @@ export function sha512(toBeConverted) {
84
84
  * @param encoding - Target encoding (default: "base64")
85
85
  * @returns Encoded string
86
86
  */
87
- export function encode(data, encoding = "base64") {
87
+ export function encode(data, encoding = 'base64') {
88
88
  let buffer;
89
89
  if (Buffer.isBuffer(data))
90
90
  buffer = data;
@@ -102,6 +102,6 @@ export function encode(data, encoding = "base64") {
102
102
  * @param to - Target encoding (default: "utf8")
103
103
  * @returns Decoded string
104
104
  */
105
- export function decode(encoded, from = "base64", to = "utf8") {
105
+ export function decode(encoded, from = 'base64', to = 'utf8') {
106
106
  return Buffer.from(encoded, from).toString(to);
107
107
  }
@@ -12,7 +12,7 @@ export type RouteParams = {
12
12
  *
13
13
  * Converts an object of parameters into a URL-encoded query string.
14
14
  * - Functions are evaluated to get their return value
15
- * - undefined/null values result in `key=` (empty value)
15
+ * - undefined/null values are omitted entirely
16
16
  * - All values are URI-encoded
17
17
  *
18
18
  * @param params - Object with parameter keys and values
@@ -3,17 +3,17 @@
3
3
  *
4
4
  * Converts an object of parameters into a URL-encoded query string.
5
5
  * - Functions are evaluated to get their return value
6
- * - undefined/null values result in `key=` (empty value)
6
+ * - undefined/null values are omitted entirely
7
7
  * - All values are URI-encoded
8
8
  *
9
9
  * @param params - Object with parameter keys and values
10
10
  * @returns URL-encoded query string (e.g., "key1=value1&key2=value2")
11
11
  */
12
12
  export function composeQueryParams(params = {}) {
13
- return Object.keys(params).map((paramKey) => {
13
+ return Object.keys(params)
14
+ .filter(key => params[key] !== undefined && params[key] !== null)
15
+ .map((paramKey) => {
14
16
  let param = params[paramKey];
15
- if (param === undefined || param === null)
16
- return `${paramKey}=`;
17
17
  if (typeof param === 'function')
18
18
  param = param();
19
19
  return `${paramKey}=${encodeURIComponent(param)}`;
@@ -28,4 +28,3 @@ export declare function generateUUID(): string;
28
28
  * @returns 8-character string for short URL
29
29
  */
30
30
  export declare function generateShortURL(): string;
31
- export declare const generateLoremIpsum: (length: number) => string;
@@ -64,27 +64,3 @@ export function generateShortURL() {
64
64
  }
65
65
  return result;
66
66
  }
67
- const loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at mi sapien. Proin viverra massa turpis, quis vehicula nibh maximus eu. Integer mi ante, fermentum id rutrum et, condimentum sed quam. Mauris tristique scelerisque nibh eget dignissim. Nulla blandit leo sit amet sem dignissim cursus. Nulla malesuada imperdiet purus, eget dignissim ex elementum eget. Suspendisse consequat lorem eget mauris suscipit congue. Suspendisse potenti. Suspendisse rutrum ligula non ipsum posuere sollicitudin. Morbi iaculis, mauris mollis aliquet convallis, tortor nulla luctus elit, nec pretium eros risus eu sapien. Phasellus congue nunc arcu, vitae vehicula dolor tincidunt vel. Suspendisse a quam diam.\n\n' +
68
- 'Mauris a maximus libero. Ut blandit, leo in mollis condimentum, tortor massa bibendum est, ac porttitor neque felis tempus magna. Proin at nulla quis turpis laoreet posuere. Aenean at nunc nec sapien maximus viverra sed at tellus. Phasellus condimentum, leo at aliquam elementum, metus libero aliquet mi, in rutrum mauris odio ut nisl. Vivamus dignissim elit semper libero elementum, id tristique turpis eleifend. Phasellus quis erat tincidunt, luctus eros ac, vestibulum ante. Vestibulum non erat libero. Sed a risus vel enim lobortis commodo. Fusce viverra diam et nulla fermentum, vel consectetur ante accumsan. Duis lorem mi, ornare eget erat vel, ultricies finibus enim. Vivamus eget tortor sit amet nisi feugiat porta. Aliquam ullamcorper, ex non placerat euismod, sapien augue pellentesque ante, nec ullamcorper magna est a lacus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas et purus nunc. Pellentesque vel ante neque.\n\n' +
69
- 'Cras rutrum a lectus sit amet ultrices. Sed a enim dolor. Cras condimentum semper ex, in cursus dui fringilla et. Sed fringilla semper luctus. Praesent vel molestie dui. Nullam efficitur nec ligula id auctor. Integer vitae semper erat, quis sollicitudin arcu. Nulla tellus tortor, imperdiet sit amet facilisis gravida, porttitor ut massa. Duis egestas imperdiet felis vel dapibus. Maecenas vulputate tempus orci non accumsan.\n\n' +
70
- 'Integer diam ex, consequat et leo a, sagittis dignissim ante. Integer massa massa, dapibus at urna quis, vehicula facilisis mauris. Phasellus blandit neque enim. Suspendisse a lobortis sapien. Phasellus eget tellus fermentum, vestibulum nunc non, finibus sem. Aliquam sollicitudin risus non maximus pellentesque. Praesent mollis nisl vitae velit sodales vulputate. Cras vel quam quis ipsum rutrum luctus eu vel erat. Aenean efficitur viverra sapien vitae faucibus. Cras pretium ante ultrices ex varius accumsan. Nullam metus ante, dignissim ac justo in, condimentum sodales nibh. Nulla convallis justo laoreet massa vestibulum, consequat maximus risus suscipit. Proin ultrices elit velit, id tempor neque luctus in. Curabitur gravida ac ipsum sed efficitur. Ut accumsan ex dui, eget accumsan sem commodo vel.\n\n' +
71
- 'Proin nulla nisi, ullamcorper in mollis vel, malesuada at orci. Mauris vel enim pharetra, auctor mauris in, malesuada nulla. Nulla eu velit mauris. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin arcu sem, lacinia sit amet nibh interdum, aliquam sagittis est. Vivamus sed ex sed ligula tempor luctus id ut sapien. In tincidunt aliquet dolor nec placerat. Suspendisse non sodales sem, vitae convallis nisl. In id malesuada neque, ac lacinia nulla. Proin ultrices libero at tortor facilisis, eget dapibus dui imperdiet. Praesent vitae ullamcorper dolor. Morbi fringilla condimentum dolor, porttitor gravida leo condimentum at. Nam id diam vel sem dapibus vulputate. Proin ipsum enim, venenatis id mattis a, tincidunt a dolor. Ut risus ex, molestie ac ante non, accumsan laoreet felis.\n\n' +
72
- 'Cras vehicula velit sit amet varius ultrices. Sed rutrum ornare dolor. Maecenas ut nisl erat. Morbi molestie nulla eu massa dictum sagittis. Curabitur eu ipsum dolor. Phasellus at tristique ipsum. Fusce a maximus nunc. Nullam a maximus dolor, in vulputate mi.\n\n' +
73
- 'Ut et ligula ultricies, consectetur felis in, maximus libero. Quisque suscipit fringilla quam eu blandit. In ac ornare velit. Integer in pretium ligula. Nunc egestas id augue ac pharetra. Curabitur vel lorem semper, lacinia mi nec, rhoncus augue. Proin efficitur, quam ac tempus aliquam, augue mauris sagittis dui, eu bibendum dui dui sit amet nisi. Nullam mattis, erat nec pellentesque eleifend, risus dui dapibus nulla, ac efficitur eros libero sed ex. Duis venenatis blandit consectetur. Integer vulputate sem in quam ornare suscipit. Maecenas maximus ullamcorper posuere. Phasellus eleifend auctor dui eget luctus. Ut eleifend felis mauris, vitae congue dui accumsan vitae.\n\n' +
74
- 'Sed lobortis mollis purus, sed egestas orci tincidunt nec. Nunc vel varius nisl. Mauris molestie nibh et commodo pharetra. Pellentesque et ante nisi. Mauris id massa et enim tristique commodo eu in orci. In volutpat augue a nisl elementum mollis. Donec ex dui, bibendum vitae maximus non, lobortis ut odio. Suspendisse nulla massa, tincidunt in lacinia ornare, faucibus eu magna. Vestibulum sollicitudin, ex ultrices consectetur maximus, enim tellus facilisis sapien, a molestie orci ex non tortor. Morbi sit amet vulputate eros. Maecenas tempor orci at ligula interdum cursus.\n\n' +
75
- 'Praesent feugiat convallis rhoncus. Proin tristique eleifend dolor, sit amet tincidunt turpis tincidunt sed. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus luctus iaculis velit vel rutrum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus risus nunc, varius non nunc sit amet, tincidunt convallis lacus. Mauris non lectus vitae libero imperdiet fermentum. In pellentesque dui at elit hendrerit, a sodales justo posuere. Ut rutrum lacus vitae egestas porttitor.\n\n' +
76
- 'Phasellus viverra condimentum tortor quis volutpat. Aenean lobortis vulputate libero non cursus. Vestibulum tincidunt condimentum ante eget tincidunt. Curabitur vel nunc id lacus efficitur feugiat. Aliquam a molestie quam, tempor varius ligula. Mauris lobortis nulla vel elit rhoncus, a varius sapien bibendum. Aenean in euismod metus. Fusce hendrerit mattis pellentesque. Aliquam erat volutpat. Nunc felis nisi, laoreet in dignissim sed, iaculis ut massa. Mauris varius mi nisi. Curabitur eu ante pulvinar, blandit arcu vitae, accumsan elit. In sit amet est tincidunt, laoreet nunc eu, facilisis nibh. Nullam molestie, turpis in lobortis molestie, sapien justo tincidunt velit, et viverra dui nibh vitae quam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.\n\n' +
77
- 'Morbi vitae condimentum urna, sit amet mattis felis. Nam bibendum ante quis mi luctus viverra sit amet et eros. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis viverra leo nec justo vehicula, eu aliquam metus elementum. Ut ullamcorper libero ut ex hendrerit, ut ullamcorper sem venenatis. Etiam aliquam quam ac rhoncus facilisis. Integer et ligula bibendum, luctus arcu nec, congue diam. Donec fringilla augue eu molestie elementum. Duis consequat velit dolor, in elementum urna imperdiet a.\n\n' +
78
- 'Aliquam bibendum ipsum magna, at aliquam nisi ornare congue. Cras mollis libero justo, eget mattis velit commodo non. Nam sollicitudin dapibus feugiat. Integer condimentum odio vitae volutpat laoreet. Duis et odio diam. Sed ullamcorper eleifend erat. Duis magna odio, volutpat eu rhoncus vitae, malesuada id sem. Pellentesque ullamcorper libero nisl, eget vehicula orci iaculis sed.\n\n' +
79
- 'Proin dolor quam, fermentum in sem vel, elementum convallis nibh. Quisque vitae eros eu risus euismod accumsan quis non dui. Praesent vestibulum, ex ut pharetra tincidunt, neque sem sagittis nisl, ut iaculis justo mi non diam. Pellentesque eu elit id ante aliquet ullamcorper. Maecenas posuere, quam non aliquam finibus, metus neque maximus nibh, sed sodales est odio nec leo. Donec at leo dictum, condimentum odio eleifend, sollicitudin enim. Sed suscipit auctor sagittis. Proin eu efficitur dolor, nec vestibulum odio. In egestas sollicitudin semper.\n\n' +
80
- 'Fusce a sapien at dui commodo fermentum eget sed tortor. Sed eu fringilla ipsum. Nulla nec dictum neque. Vivamus sed tempor libero. Pellentesque eget eleifend ante, eu posuere erat. Donec placerat turpis a nisi congue, in convallis tellus feugiat. Aliquam hendrerit vulputate nunc, in dictum augue ultrices id. Cras facilisis sapien sit amet dignissim efficitur. Donec a est sit amet ante tempor sodales. Donec aliquam odio dui, quis aliquet nulla placerat dapibus. Donec porttitor est ut dolor bibendum consequat. Duis maximus tortor velit.\n\n' +
81
- 'Maecenas ut elit sem. Maecenas at arcu id augue laoreet mollis id a turpis. Nullam iaculis facilisis quam, nec auctor metus volutpat sit amet. Maecenas quis feugiat tortor. Nam malesuada placerat cursus. Duis sed augue et risus viverra fermentum vel quis nunc. Nulla dignissim, ante eu luctus viverra, lorem elit ultricies tellus, in consectetur turpis est vel nulla. Sed pulvinar laoreet mi quis varius. Nulla fermentum, dui ac dictum scelerisque, libero mi semper tellus, vel volutpat ante diam et urna. Etiam feugiat nisi id risus imperdiet ullamcorper. Morbi eget fermentum ipsum. Mauris mi nunc, interdum eget ante et, pharetra aliquam eros. Donec at porttitor mauris. Etiam euismod libero vitae nibh convallis tristique.\n\n' +
82
- 'Quisque congue nulla eros. Vestibulum maximus erat ac turpis viverra, id tincidunt elit hendrerit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque laoreet augue ut dolor aliquet elementum. In bibendum in mauris sit amet volutpat. Etiam vulputate lectus et ultrices faucibus. Pellentesque est massa, vestibulum at malesuada et, dignissim ac sem. Donec a lacus lacinia, sollicitudin leo id, tristique tortor. Morbi pellentesque enim non arcu lobortis, et rhoncus nibh euismod. Ut faucibus, nulla vestibulum porta egestas, sem ante molestie libero, ac fermentum augue turpis at ipsum. Nullam fermentum turpis vel malesuada rhoncus. Vestibulum eget nisl maximus, tempus mauris et, vestibulum magna.\n\n' +
83
- 'Fusce bibendum metus vel libero pretium eleifend. Proin libero orci, rhoncus sed neque ac, finibus dignissim erat. Nullam at mollis purus. Praesent vel auctor sem. Praesent nec nibh at elit ullamcorper placerat. Proin pellentesque orci ut risus egestas vehicula. Mauris blandit convallis venenatis.\n\n' +
84
- 'Cras eget imperdiet elit. Donec in felis sagittis, maximus nunc at, commodo tellus. Praesent molestie fringilla neque, nec pharetra quam aliquet ac. Sed luctus sollicitudin ante, non tempus augue ornare quis. Etiam hendrerit metus ac ipsum sollicitudin, id sodales urna volutpat. Praesent cursus finibus arcu, quis pulvinar metus mollis quis. Suspendisse in enim velit. Etiam id dolor dolor. Integer fringilla ligula ultricies, tempor leo quis, luctus nisi. Mauris quis malesuada enim. In rhoncus elementum viverra. Quisque maximus lorem sit amet molestie congue. Phasellus ut eleifend lectus. Vestibulum aliquet urna ex, sit amet cursus orci mollis ut.\n\n' +
85
- 'Sed quis aliquam urna. Integer velit sem, consequat eu fermentum eu, hendrerit sit amet mi. Nulla congue risus vel finibus consequat. Proin ornare ornare vestibulum. Aliquam varius blandit nisl, vel commodo sem sollicitudin in. Integer non consectetur purus, sit amet volutpat est. Curabitur vitae enim leo. Quisque sodales pharetra lorem, non venenatis urna luctus vel.\n\n' +
86
- 'Nulla ac magna tempus, sollicitudin nibh vitae, suscipit velit. Aliquam ultrices erat blandit velit varius, vel ullamcorper orci lacinia. Ut fermentum magna eget turpis consequat convallis. Duis blandit rhoncus cursus. Curabitur rhoncus leo vel scelerisque porttitor. Pellentesque posuere nisi tincidunt, dapibus ipsum eu, elementum lacus. Praesent condimentum gravida ex, et auctor odio maximus eu. Praesent in velit nec sem rhoncus congue at eget lorem. Proin vitae erat lacus.';
87
- export const generateLoremIpsum = (length) => {
88
- length = Math.max(Math.min(11801, length), 0);
89
- return loremIpsum.substring(0, length);
90
- };
@@ -161,3 +161,13 @@ export declare function normalizeString(string: string): string;
161
161
  * ```
162
162
  */
163
163
  export declare function convertUpperCamelCase(upperCamelCase: string, delimiter?: string): string;
164
+ /**
165
+ * Parse and format numbers using volume shorthand notation (K/M/G/T).
166
+ *
167
+ * - `parse("76M")` → `76_000_000`
168
+ * - `format(76_000_000)` → `"76M"`
169
+ */
170
+ export declare const StringFormat_Volume: {
171
+ parse(input: string): number;
172
+ format(value: number): string;
173
+ };
@@ -267,3 +267,48 @@ export function normalizeString(string) {
267
267
  export function convertUpperCamelCase(upperCamelCase, delimiter = ' ') {
268
268
  return upperCamelCase.replace(/([a-z0-9])([A-Z])/g, `$1${delimiter}$2`);
269
269
  }
270
+ const _volumeSuffixes = [
271
+ { suffix: 'T', value: 1e12 },
272
+ { suffix: 'G', value: 1e9 },
273
+ { suffix: 'M', value: 1e6 },
274
+ { suffix: 'K', value: 1e3 },
275
+ ];
276
+ const _volumeSuffixMap = Object.fromEntries(_volumeSuffixes.map(s => [s.suffix, s.value]));
277
+ const _volumePattern = /^(-?\d+\.?\d*)\s*([KMGT])?$/;
278
+ /**
279
+ * Parse and format numbers using volume shorthand notation (K/M/G/T).
280
+ *
281
+ * - `parse("76M")` → `76_000_000`
282
+ * - `format(76_000_000)` → `"76M"`
283
+ */
284
+ export const StringFormat_Volume = {
285
+ parse(input) {
286
+ const trimmed = input.trim();
287
+ if (!trimmed)
288
+ throw new Error('Empty volume input');
289
+ const match = trimmed.match(_volumePattern);
290
+ if (!match)
291
+ throw new Error(`Invalid volume format: "${input}" — expected pattern like 76M, 1.5G, 300K`);
292
+ const coefficient = Number(match[1]);
293
+ const suffix = match[2];
294
+ if (!suffix)
295
+ return coefficient;
296
+ const multiplier = _volumeSuffixMap[suffix];
297
+ if (multiplier === undefined)
298
+ throw new Error(`Unknown volume suffix: "${suffix}"`);
299
+ return coefficient * multiplier;
300
+ },
301
+ format(value) {
302
+ if (value === 0)
303
+ return '0';
304
+ for (const { suffix, value: threshold } of _volumeSuffixes) {
305
+ if (Math.abs(value) < threshold)
306
+ continue;
307
+ const coefficient = value / threshold;
308
+ const rounded = Math.round(coefficient * 100) / 100;
309
+ if (rounded * threshold === value)
310
+ return `${rounded}${suffix}`;
311
+ }
312
+ return String(value);
313
+ },
314
+ };
package/utils/tools.d.ts CHANGED
@@ -124,3 +124,9 @@ export type KeyBinder<K extends string, Type> = {
124
124
  * @returns Locked function that prevents concurrent execution
125
125
  */
126
126
  export declare function createLockedAsyncFunction(fn: () => Promise<void>): () => void;
127
+ /**
128
+ * Converts a base64 data URL to a Blob.
129
+ * @param imageAsBase64 - Data URL (e.g. "data:image/png;base64,...") or base64 string
130
+ * @returns Promise resolving to the Blob
131
+ */
132
+ export declare function base64ToBlob(imageAsBase64: string): Promise<Blob>;
package/utils/tools.js CHANGED
@@ -173,3 +173,11 @@ export function createLockedAsyncFunction(fn) {
173
173
  });
174
174
  };
175
175
  }
176
+ /**
177
+ * Converts a base64 data URL to a Blob.
178
+ * @param imageAsBase64 - Data URL (e.g. "data:image/png;base64,...") or base64 string
179
+ * @returns Promise resolving to the Blob
180
+ */
181
+ export async function base64ToBlob(imageAsBase64) {
182
+ return (await fetch(imageAsBase64)).blob();
183
+ }
package/utils/types.d.ts CHANGED
@@ -292,15 +292,6 @@ export type AuditBy = {
292
292
  /** Timestamp of the change */
293
293
  auditAt: Timestamp;
294
294
  };
295
- /**
296
- * Simplified audit information (v2).
297
- *
298
- * Only stores the auditor ID, not full audit details.
299
- */
300
- export type AuditableV2 = {
301
- /** ID of the user/system that made the change */
302
- _auditorId: string;
303
- };
304
295
  /**
305
296
  * Timestamp with formatted string representation.
306
297
  */
@@ -16,7 +16,7 @@ import { ArrayType, AuditBy, RangeTimestamp, TypedMap } from '../utils/types.js'
16
16
  * @param mandatory - Whether the object itself is required (default: true)
17
17
  * @returns Validator that validates both keys and values
18
18
  */
19
- export declare const tsValidateDynamicObject: <T extends object>(valuesValidator: ValidatorTypeResolver<T[keyof T]>, keysValidator: ValidatorTypeResolver<string>, mandatory?: boolean) => (import("./validator-core.js").ValidatorImpl<any> | ((input?: T) => InvalidResultObject<T> | undefined))[];
19
+ export declare const tsValidateDynamicObject: <T extends object>(valuesValidator: ValidatorTypeResolver<T[keyof T]>, keysValidator: ValidatorTypeResolver<keyof T>, mandatory?: boolean) => (import("./validator-core.js").ValidatorImpl<any> | ((input?: T) => InvalidResultObject<T> | undefined))[];
20
20
  /**
21
21
  * Validates input against multiple validators (union type).
22
22
  *
@@ -78,6 +78,8 @@ export declare const tsValidateAnyNumber: Validator<number>;
78
78
  export declare const tsValidateOptionalAnyNumber: Validator<number>;
79
79
  export declare const tsValidateEnum: (enumType: TypedMap<number | string>, mandatory?: boolean) => Validator<number | string>;
80
80
  export declare const tsValidateBoolean: (mandatory?: boolean) => Validator<boolean>;
81
+ export declare const tsValidateOptionalBoolean: Validator<boolean>;
82
+ export declare const tsValidateMandatoryBoolean: Validator<boolean>;
81
83
  export declare const tsValidateValue: <T>(values: T[] | ReadonlyArray<T>, mandatory?: boolean) => Validator<any>;
82
84
  export declare const tsValidateIsInRange: (ranges: [number, number][], mandatory?: boolean) => Validator<number>;
83
85
  export declare const tsValidateRange: (mandatory?: boolean) => Validator<[number, number] | undefined>;
@@ -183,6 +183,8 @@ export const tsValidateBoolean = (mandatory = true) => {
183
183
  return `Input is not a boolean! \nvalue: ${input}\ntype: ${typeof input}`;
184
184
  }];
185
185
  };
186
+ export const tsValidateOptionalBoolean = tsValidateBoolean(false);
187
+ export const tsValidateMandatoryBoolean = tsValidateBoolean();
186
188
  export const tsValidateValue = (values, mandatory = true) => {
187
189
  return [tsValidateExists(mandatory),
188
190
  (input) => {
@@ -10,7 +10,7 @@ import { CustomException } from '../core/exceptions/exceptions.js';
10
10
  *
11
11
  * This enables type-safe validation where the validator structure matches the data structure.
12
12
  */
13
- export type ValidatorTypeResolver<K> = K extends [any] ? Validator<K> : K extends [any, any] ? Validator<K> : K extends [any, any, any] ? Validator<K> : K extends [any, any, any, any] ? Validator<K> : K extends any[] ? Validator<K> : K extends TS_Object ? TypeValidator<K> | Validator<K> : Validator<K>;
13
+ export type ValidatorTypeResolver<K> = K extends (any[] | readonly any[]) ? Validator<K> : K extends TS_Object ? TypeValidator<K> | Validator<K> : Validator<K>;
14
14
  /**
15
15
  * Core validator function type.
16
16
  *
@@ -1,5 +1,5 @@
1
1
  import { Validator, ValidatorTypeResolver } from './validator-core.js';
2
- import { AuditableV2, DBPointer } from '../utils/types.js';
2
+ import { DBPointer } from '../utils/types.js';
3
3
  import { DBDef_V3 } from '../db/types.js';
4
4
  /**
5
5
  * Validates an optional array (non-mandatory).
@@ -111,7 +111,6 @@ export declare const tsValidator_LowerUpperStringWithSpaces: Validator<string>;
111
111
  export declare const tsValidator_LowerUpperStringWithDashesAndUnderscore: Validator<string>;
112
112
  export declare const tsValidator_InternationalPhoneNumber: Validator<string>;
113
113
  export declare const tsValidator_DB_RefId: Validator<string>;
114
- export declare const tsValidator_AuditableV2: ValidatorTypeResolver<AuditableV2>;
115
114
  export declare const DB_Object_validator: {
116
115
  __metadata1: import("./validator-core.js").ValidatorImpl<any>;
117
116
  _id: Validator<string>;
@@ -118,7 +118,6 @@ export const tsValidator_LowerUpperStringWithSpaces = tsValidateRegexp(/^[A-Za-z
118
118
  export const tsValidator_LowerUpperStringWithDashesAndUnderscore = tsValidateRegexp(/^[A-Za-z-_]+$/);
119
119
  export const tsValidator_InternationalPhoneNumber = tsValidateRegexp(/^\+(?:[0-9] ?){6,14}[0-9]$/);
120
120
  export const tsValidator_DB_RefId = tsValidateId(dbRefIdLength);
121
- export const tsValidator_AuditableV2 = { _auditorId: tsValidateString() };
122
121
  export const DB_Object_validator = {
123
122
  // this will be the way to handle app level context via proto.. need to rename this to __metadata once done
124
123
  __metadata1: tsValidateOptional,
@@ -1 +0,0 @@
1
- export * from './MemStorage.js';
@@ -1 +0,0 @@
1
- export * from './MemStorage.js';
@@ -1 +0,0 @@
1
- export * from '@nu-art/testalot';
package/testing/index.js DELETED
@@ -1 +0,0 @@
1
- export * from '@nu-art/testalot';
package/utils/index.d.ts DELETED
@@ -1,27 +0,0 @@
1
- export * from "./array-tools.js";
2
- export * from "./conflict-tools.js";
3
- export * from "./crypto-tools.js";
4
- export * from "./date-time-tools.js";
5
- export * from "./db-object-tools.js";
6
- export * from "./exception-tools.js";
7
- export * from "./filter-tools.js";
8
- export * from "./hash-tools.js";
9
- export * from "./index.js";
10
- export * from "./json-tools.js";
11
- export * from "./merge-tools.js";
12
- export * from "./mimetype-tools.js";
13
- export * from "./number-tools.js";
14
- export * from "./object-tools.js";
15
- export * from "./promise-tools.js";
16
- export * from "./query-params.js";
17
- export * from "./queue.js";
18
- export * from "./queue-v2.js";
19
- export * from "./random-tools.js";
20
- export * from "./storage-capacity-tools.js";
21
- export * from "./string-tools.js";
22
- export * from "./time-proxy.js";
23
- export * from "./tools.js";
24
- export * from "./types.js";
25
- export * from "./ui-tools.js";
26
- export * from "./url-tools.js";
27
- export * from "./version-tools.js";
package/utils/index.js DELETED
@@ -1,27 +0,0 @@
1
- export * from "./array-tools.js";
2
- export * from "./conflict-tools.js";
3
- export * from "./crypto-tools.js";
4
- export * from "./date-time-tools.js";
5
- export * from "./db-object-tools.js";
6
- export * from "./exception-tools.js";
7
- export * from "./filter-tools.js";
8
- export * from "./hash-tools.js";
9
- export * from "./index.js";
10
- export * from "./json-tools.js";
11
- export * from "./merge-tools.js";
12
- export * from "./mimetype-tools.js";
13
- export * from "./number-tools.js";
14
- export * from "./object-tools.js";
15
- export * from "./promise-tools.js";
16
- export * from "./query-params.js";
17
- export * from "./queue.js";
18
- export * from "./queue-v2.js";
19
- export * from "./random-tools.js";
20
- export * from "./storage-capacity-tools.js";
21
- export * from "./string-tools.js";
22
- export * from "./time-proxy.js";
23
- export * from "./tools.js";
24
- export * from "./types.js";
25
- export * from "./ui-tools.js";
26
- export * from "./url-tools.js";
27
- export * from "./version-tools.js";