@nu-art/ts-common 0.400.4 → 0.400.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.
package/index.d.ts CHANGED
@@ -42,6 +42,7 @@ export * from './utils/ui-tools.js';
42
42
  export * from './utils/json-tools.js';
43
43
  export * from './utils/promise-tools.js';
44
44
  export * from './utils/url-tools.js';
45
+ export * from './utils/debounce.js';
45
46
  export * from './validator/validator-core.js';
46
47
  export * from './validator/validators.js';
47
48
  export * from './validator/type-validators.js';
package/index.js CHANGED
@@ -59,6 +59,7 @@ export * from './utils/ui-tools.js';
59
59
  export * from './utils/json-tools.js';
60
60
  export * from './utils/promise-tools.js';
61
61
  export * from './utils/url-tools.js';
62
+ export * from './utils/debounce.js';
62
63
  export * from './validator/validator-core.js';
63
64
  export * from './validator/validators.js';
64
65
  export * from './validator/type-validators.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nu-art/ts-common",
3
- "version": "0.400.4",
3
+ "version": "0.400.6",
4
4
  "description": "js and ts infra",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -36,9 +36,7 @@
36
36
  "node-forge": "^1.2.1",
37
37
  "uuid": "^9.0.0",
38
38
  "csv-parser": "^2.3.3",
39
- "xlsx": "^0.18.5",
40
- "jose": "^5.0.0",
41
- "pbkdf2": "3.1.2"
39
+ "jose": "^5.0.0"
42
40
  },
43
41
  "devDependencies": {
44
42
  "@types/mocha": "^10.0.6",
@@ -1,10 +1,12 @@
1
1
  import { Logger } from '../core/logger/Logger.js';
2
+ import { StringMap } from '../utils/index.js';
2
3
  export declare class TestWorkspaceCreator extends Logger {
3
4
  private readonly pathToFixtures;
4
5
  private readonly pathToWorkspace;
5
6
  constructor(pathToFixtures: string, pathToWorkspace: string);
6
- setupWorkspace(fixtures: string[], relativePath?: string, clean?: boolean): void;
7
- clearWorkspace(relativePathInWorkspace?: string): void;
8
- extractFixture(pathToFixture: string, relativePathInWorkspace?: string): void;
7
+ setupWorkspace(fixtures: string[], relativePath?: string, clean?: boolean): Promise<void>;
8
+ setupWorkspace(fixtures: string[], params: StringMap, relativePath?: string, clean?: boolean): Promise<void>;
9
+ clearWorkspace(relativePathInWorkspace?: string): Promise<void>;
10
+ extractFixture(pathToFixture: string, relativePathInWorkspace?: string, params?: StringMap): Promise<void>;
9
11
  }
10
12
  export declare function setupWorkspace(pathToWorkspaceFile: string, outputRootDir: string, clean?: boolean): void;
@@ -1,6 +1,7 @@
1
- import { mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
2
2
  import { dirname, resolve } from 'path';
3
3
  import { Logger } from '../core/logger/Logger.js';
4
+ import { FileSystemUtils } from '../utils/FileSystemUtils.js';
4
5
  const FILE_HEADER_REGEX = /^\/\/ file: (.+)$/gm;
5
6
  const INNER_FILE_HEADER_REGEX = /^(\/\/)*?\/\/\/\/ file:/gm;
6
7
  export class TestWorkspaceCreator extends Logger {
@@ -11,20 +12,38 @@ export class TestWorkspaceCreator extends Logger {
11
12
  this.pathToFixtures = pathToFixtures;
12
13
  this.pathToWorkspace = pathToWorkspace;
13
14
  }
14
- setupWorkspace(fixtures, relativePath = '', clean = true) {
15
- if (clean)
16
- this.clearWorkspace(relativePath);
15
+ async setupWorkspace(fixtures, params, relativePath, clean) {
16
+ let _clean = clean;
17
+ let _relativePath;
18
+ let _params;
19
+ if (typeof relativePath === 'boolean') {
20
+ _clean = relativePath;
21
+ }
22
+ else if (typeof relativePath === 'string') {
23
+ _relativePath = relativePath;
24
+ }
25
+ if (typeof params === 'string') {
26
+ _relativePath = params;
27
+ _params = {};
28
+ }
29
+ else {
30
+ _params = params;
31
+ }
32
+ _relativePath = _relativePath ?? '';
33
+ _clean = _clean ?? true;
34
+ if (_clean)
35
+ await this.clearWorkspace(_relativePath);
17
36
  for (const fixture of fixtures) {
18
- this.extractFixture(resolve(this.pathToFixtures, fixture), relativePath);
37
+ await this.extractFixture(resolve(this.pathToFixtures, fixture), _relativePath, _params);
19
38
  }
20
39
  }
21
- clearWorkspace(relativePathInWorkspace = '') {
40
+ async clearWorkspace(relativePathInWorkspace = '') {
22
41
  const path = resolve(this.pathToWorkspace, relativePathInWorkspace);
23
42
  this.logWarning(`Deleting folder: ${path}`);
24
- rmSync(path, { recursive: true, force: true });
43
+ await FileSystemUtils.folder.delete(path);
25
44
  }
26
- extractFixture(pathToFixture, relativePathInWorkspace = '') {
27
- const content = readFileSync(pathToFixture, 'utf8');
45
+ async extractFixture(pathToFixture, relativePathInWorkspace = '', params = {}) {
46
+ const content = await FileSystemUtils.file.template.read(pathToFixture, params);
28
47
  const matches = [...content.matchAll(FILE_HEADER_REGEX)];
29
48
  if (matches.length === 0) {
30
49
  this.logError('No matching file headers found. Aborting.');
@@ -43,8 +62,7 @@ export class TestWorkspaceCreator extends Logger {
43
62
  .slice(startIndex + currentMatch[0].length, endIndex)
44
63
  .trimStart();
45
64
  fileContent = fileContent.replace(INNER_FILE_HEADER_REGEX, '\$1// file:');
46
- mkdirSync(dirname(targetPath), { recursive: true });
47
- writeFileSync(targetPath, fileContent);
65
+ await FileSystemUtils.file.write(targetPath, fileContent);
48
66
  this.logVerbose(`Wrote: ${targetPath}`);
49
67
  }
50
68
  }
@@ -0,0 +1,32 @@
1
+ import { Logger } from '../core/logger/Logger.js';
2
+ export declare class Debounce<Args extends any[], Response> extends Logger {
3
+ private action;
4
+ private readonly waitMs;
5
+ private readonly maxWaitMs?;
6
+ private triggerIndex;
7
+ private waitTimer?;
8
+ private maxTimer?;
9
+ private latestArgs;
10
+ private actionInProgress;
11
+ private triggerPending;
12
+ constructor(action: (...args: Args) => Response, waitMs: number, maxWaitMs?: number);
13
+ private validate_WaitMs;
14
+ private validate_MaxWaitMs;
15
+ private clearTimers;
16
+ private executeAction;
17
+ /** Schedule the action to be performed (fire-and-forget). */
18
+ trigger(...args: Args): void;
19
+ /**
20
+ * Returns whether an action is currently scheduled
21
+ */
22
+ isScheduled(): boolean;
23
+ /**
24
+ * Run immediately if scheduled and return the action's value.
25
+ * - Returns Response when something was scheduled, else undefined.
26
+ * - If action throws sync here, it throws to the caller.
27
+ * - If action returns a rejected Promise, caller handles it.
28
+ */
29
+ flush(): Response | undefined;
30
+ /** Cancel any scheduled run. (Does not cancel in-flight.) */
31
+ clear(): void;
32
+ }
@@ -0,0 +1,115 @@
1
+ import { BadImplementationException } from '../core/exceptions/exceptions.js';
2
+ import { Logger } from '../core/logger/Logger.js';
3
+ import { isPromise } from './promise-tools.js';
4
+ import { exists } from './tools.js';
5
+ export class Debounce extends Logger {
6
+ action;
7
+ waitMs;
8
+ maxWaitMs;
9
+ triggerIndex = 0;
10
+ waitTimer;
11
+ maxTimer;
12
+ latestArgs = [];
13
+ actionInProgress = false;
14
+ triggerPending = false;
15
+ constructor(action, waitMs, maxWaitMs) {
16
+ super('Debounce');
17
+ this.validate_WaitMs(waitMs);
18
+ this.validate_MaxWaitMs(waitMs, maxWaitMs);
19
+ this.action = action;
20
+ this.waitMs = waitMs;
21
+ this.maxWaitMs = maxWaitMs;
22
+ }
23
+ //######################### Validation #########################
24
+ validate_WaitMs(waitMs) {
25
+ if (waitMs < 0)
26
+ throw new BadImplementationException('waitMs value must be >= 0');
27
+ }
28
+ validate_MaxWaitMs(waitMs, maxWaitMs) {
29
+ if (!exists(maxWaitMs))
30
+ return;
31
+ if (maxWaitMs <= 0)
32
+ throw new BadImplementationException('maxWaitMs value must be > 0');
33
+ if (maxWaitMs < waitMs)
34
+ this.logWarning('maxWaitMs < waitMs; the \'max\' may never matter');
35
+ }
36
+ //######################### Internal Logic #########################
37
+ clearTimers() {
38
+ if (this.waitTimer) {
39
+ clearTimeout(this.waitTimer);
40
+ this.waitTimer = undefined;
41
+ }
42
+ if (this.maxTimer) {
43
+ clearTimeout(this.maxTimer);
44
+ this.maxTimer = undefined;
45
+ }
46
+ }
47
+ executeAction(triggerIndex, immediate) {
48
+ if (triggerIndex !== this.triggerIndex)
49
+ return;
50
+ this.triggerIndex++;
51
+ const args = this.latestArgs ?? [];
52
+ this.clearTimers();
53
+ const result = this.action(...args);
54
+ if (immediate)
55
+ return result;
56
+ //Set up chain triggering if the action is asynchronous.
57
+ if (isPromise(result)) {
58
+ this.actionInProgress = true;
59
+ Promise.resolve(result).finally(() => {
60
+ this.actionInProgress = false;
61
+ //Immediately execute the action again if debounce was triggered during the current run
62
+ if (this.triggerPending) {
63
+ this.triggerPending = false;
64
+ void this.executeAction(this.triggerIndex);
65
+ }
66
+ });
67
+ }
68
+ }
69
+ //######################### Public Logic #########################
70
+ /** Schedule the action to be performed (fire-and-forget). */
71
+ trigger(...args) {
72
+ this.latestArgs = args;
73
+ //If action is in progress, queue up immediate trigger
74
+ if (this.actionInProgress) {
75
+ this.triggerPending = true;
76
+ return;
77
+ }
78
+ // Normal debounce behavior while idle:
79
+ //Reset waitTimer
80
+ if (this.waitTimer)
81
+ clearTimeout(this.waitTimer);
82
+ const triggerIndex = this.triggerIndex;
83
+ this.waitTimer = setTimeout(() => {
84
+ void this.executeAction(triggerIndex);
85
+ }, this.waitMs);
86
+ //Set maxWaitTimer if configured and does not currently exist
87
+ if (exists(this.maxWaitMs) && !exists(this.maxTimer))
88
+ this.maxTimer = setTimeout(() => {
89
+ void this.executeAction(triggerIndex);
90
+ }, this.maxWaitMs);
91
+ }
92
+ /**
93
+ * Returns whether an action is currently scheduled
94
+ */
95
+ isScheduled() {
96
+ return exists(this.waitTimer ?? this.maxTimer);
97
+ }
98
+ /**
99
+ * Run immediately if scheduled and return the action's value.
100
+ * - Returns Response when something was scheduled, else undefined.
101
+ * - If action throws sync here, it throws to the caller.
102
+ * - If action returns a rejected Promise, caller handles it.
103
+ */
104
+ flush() {
105
+ if (!this.isScheduled())
106
+ return void this.logInfo('Not scheduled');
107
+ return this.executeAction(this.triggerIndex, true);
108
+ }
109
+ /** Cancel any scheduled run. (Does not cancel in-flight.) */
110
+ clear() {
111
+ this.clearTimers();
112
+ this.triggerIndex++;
113
+ this.triggerPending = false;
114
+ }
115
+ }
@@ -1,2 +1,3 @@
1
1
  export declare const roundNumber: (number: number, digits: number) => number;
2
2
  export declare const clamp: (min: number, num: number, max: number) => number;
3
+ export declare const numberInRange: (number: number, range: [number, number]) => boolean;
@@ -20,3 +20,6 @@ export const roundNumber = (number, digits) => {
20
20
  return Math.round(number * multiple) / multiple;
21
21
  };
22
22
  export const clamp = (min, num, max) => Math.min(Math.max(num, min), max);
23
+ export const numberInRange = (number, range) => {
24
+ return range[0] <= number && number <= range[1];
25
+ };
package/utils/tools.d.ts CHANGED
@@ -19,3 +19,4 @@ export type KeyBinder<K extends string, Type> = {
19
19
  Key: K;
20
20
  Type: Type;
21
21
  };
22
+ export declare function createLockedAsyncFunction(fn: () => Promise<void>): () => void;
package/utils/tools.js CHANGED
@@ -63,3 +63,14 @@ export function freeze(item) {
63
63
  export const logicalXOR = (a, b) => {
64
64
  return (a && !b) || (!a && b);
65
65
  };
66
+ export function createLockedAsyncFunction(fn) {
67
+ let inProgress = false;
68
+ return () => {
69
+ if (inProgress)
70
+ return;
71
+ inProgress = true;
72
+ Promise.resolve(fn()).finally(() => {
73
+ inProgress = false;
74
+ });
75
+ };
76
+ }