@bitblit/ratchet-common 6.0.146-alpha → 6.0.148-alpha
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/package.json +2 -1
- package/src/2d/line-2d.ts +6 -0
- package/src/2d/matrix-factory.ts +94 -0
- package/src/2d/plane-2d-type.ts +6 -0
- package/src/2d/plane-2d.ts +7 -0
- package/src/2d/point-2d.ts +4 -0
- package/src/2d/poly-line-2d.ts +5 -0
- package/src/2d/ratchet-2d.spec.ts +205 -0
- package/src/2d/ratchet-2d.ts +350 -0
- package/src/2d/transformation-matrix.ts +19 -0
- package/src/build/build-information.ts +8 -0
- package/src/build/ratchet-common-info.ts +19 -0
- package/src/histogram/histogram-entry.ts +4 -0
- package/src/histogram/histogram.spec.ts +25 -0
- package/src/histogram/histogram.ts +61 -0
- package/src/jwt/common-jwt-token.ts +17 -0
- package/src/jwt/expired-jwt-handling.ts +5 -0
- package/src/jwt/jwt-decode-only-ratchet.ts +26 -0
- package/src/jwt/jwt-payload-expiration-ratchet.ts +45 -0
- package/src/jwt/jwt-token-base.ts +14 -0
- package/src/lang/array-ratchet.spec.ts +79 -0
- package/src/lang/array-ratchet.ts +141 -0
- package/src/lang/base64-ratchet.spec.ts +48 -0
- package/src/lang/base64-ratchet.ts +247 -0
- package/src/lang/boolean-ratchet.spec.ts +95 -0
- package/src/lang/boolean-ratchet.ts +52 -0
- package/src/lang/composite-last-success-provider.spec.ts +31 -0
- package/src/lang/composite-last-success-provider.ts +30 -0
- package/src/lang/currency-ratchet.ts +29 -0
- package/src/lang/date-ratchet.spec.ts +27 -0
- package/src/lang/date-ratchet.ts +42 -0
- package/src/lang/duration-ratchet.spec.ts +47 -0
- package/src/lang/duration-ratchet.ts +77 -0
- package/src/lang/enum-ratchet.spec.ts +45 -0
- package/src/lang/enum-ratchet.ts +41 -0
- package/src/lang/error-handling-approach.ts +6 -0
- package/src/lang/error-ratchet.spec.ts +25 -0
- package/src/lang/error-ratchet.ts +70 -0
- package/src/lang/esm-ratchet.ts +81 -0
- package/src/lang/expiring-object.spec.ts +56 -0
- package/src/lang/expiring-object.ts +84 -0
- package/src/lang/geolocation-ratchet.spec.ts +177 -0
- package/src/lang/geolocation-ratchet.ts +341 -0
- package/src/lang/global-ratchet.spec.ts +17 -0
- package/src/lang/global-ratchet.ts +105 -0
- package/src/lang/key-value.ts +8 -0
- package/src/lang/last-success-provider.ts +4 -0
- package/src/lang/map-ratchet.spec.ts +113 -0
- package/src/lang/map-ratchet.ts +220 -0
- package/src/lang/no.spec.ts +9 -0
- package/src/lang/no.ts +7 -0
- package/src/lang/number-ratchet.spec.ts +154 -0
- package/src/lang/number-ratchet.ts +253 -0
- package/src/lang/parsed-url.ts +10 -0
- package/src/lang/promise-ratchet.spec.ts +104 -0
- package/src/lang/promise-ratchet.ts +196 -0
- package/src/lang/range.ts +4 -0
- package/src/lang/require-ratchet.spec.ts +85 -0
- package/src/lang/require-ratchet.ts +68 -0
- package/src/lang/simple-arg-ratchet.spec.ts +13 -0
- package/src/lang/simple-arg-ratchet.ts +47 -0
- package/src/lang/simple-encryption-ratchet.ts +88 -0
- package/src/lang/sort-ratchet.spec.ts +58 -0
- package/src/lang/sort-ratchet.ts +50 -0
- package/src/lang/stop-watch.spec.ts +53 -0
- package/src/lang/stop-watch.ts +202 -0
- package/src/lang/string-ratchet.spec.ts +226 -0
- package/src/lang/string-ratchet.ts +676 -0
- package/src/lang/time-zone-ratchet.spec.ts +51 -0
- package/src/lang/time-zone-ratchet.ts +148 -0
- package/src/lang/timeout-token.spec.ts +12 -0
- package/src/lang/timeout-token.ts +21 -0
- package/src/lang/uint-8-array-ratchet.spec.ts +22 -0
- package/src/lang/uint-8-array-ratchet.ts +48 -0
- package/src/lang/web-stream-ratchet.spec.ts +12 -0
- package/src/lang/web-stream-ratchet.ts +96 -0
- package/src/logger/classic-single-line-log-message-formatter.ts +19 -0
- package/src/logger/log-message-builder.ts +60 -0
- package/src/logger/log-message-format-type.ts +11 -0
- package/src/logger/log-message-formatter.ts +6 -0
- package/src/logger/log-message-processor.ts +6 -0
- package/src/logger/log-message.ts +9 -0
- package/src/logger/log-snapshot.ts +6 -0
- package/src/logger/logger-instance.ts +269 -0
- package/src/logger/logger-level-name.ts +11 -0
- package/src/logger/logger-meta.ts +7 -0
- package/src/logger/logger-options.ts +14 -0
- package/src/logger/logger-output-function.ts +10 -0
- package/src/logger/logger-ring-buffer.ts +89 -0
- package/src/logger/logger-util.spec.ts +11 -0
- package/src/logger/logger-util.ts +68 -0
- package/src/logger/logger.spec.ts +177 -0
- package/src/logger/logger.ts +213 -0
- package/src/logger/none-log-message-formatter.ts +10 -0
- package/src/logger/single-line-no-level-log-message-formatter.ts +18 -0
- package/src/logger/structured-json-log-message-formatter.ts +25 -0
- package/src/mail/archive-email-result.ts +8 -0
- package/src/mail/email-attachment.ts +23 -0
- package/src/mail/mail-sending-provider.ts +21 -0
- package/src/mail/mailer-config.ts +30 -0
- package/src/mail/mailer-like.ts +38 -0
- package/src/mail/mailer-util.ts +65 -0
- package/src/mail/mailer.spec.ts +120 -0
- package/src/mail/mailer.ts +214 -0
- package/src/mail/ready-to-send-email.ts +67 -0
- package/src/mail/resolved-ready-to-send-email.ts +17 -0
- package/src/mail/send-email-result.ts +16 -0
- package/src/mail/test-mail-sending-provider.ts +35 -0
- package/src/network/browser-local-ip-provider.spec.ts +23 -0
- package/src/network/browser-local-ip-provider.ts +26 -0
- package/src/network/fixed-local-ip-provider.ts +9 -0
- package/src/network/local-ip-provider.ts +4 -0
- package/src/network/network-ratchet.spec.ts +17 -0
- package/src/network/network-ratchet.ts +209 -0
- package/src/network/remote-file-tracker/backup-result.ts +6 -0
- package/src/network/remote-file-tracker/file-transfer-result-type.ts +5 -0
- package/src/network/remote-file-tracker/file-transfer-result.ts +9 -0
- package/src/network/remote-file-tracker/remote-file-tracker-options.ts +6 -0
- package/src/network/remote-file-tracker/remote-file-tracker-push-options.ts +4 -0
- package/src/network/remote-file-tracker/remote-file-tracker.ts +117 -0
- package/src/network/remote-file-tracker/remote-file-tracking-provider.ts +19 -0
- package/src/network/remote-file-tracker/remote-status-data-and-content.ts +6 -0
- package/src/network/remote-file-tracker/remote-status-data.ts +7 -0
- package/src/network/restful-api-http-error.spec.ts +13 -0
- package/src/network/restful-api-http-error.ts +173 -0
- package/src/template/ratchet-template-renderer.ts +8 -0
- package/src/third-party/google/google-recaptcha-ratchet.spec.ts +27 -0
- package/src/third-party/google/google-recaptcha-ratchet.ts +36 -0
- package/src/third-party/twilio/twilio-ratchet.ts +92 -0
- package/src/third-party/twilio/twilio-verify-ratchet.ts +83 -0
- package/src/transform/built-in-transforms.ts +214 -0
- package/src/transform/transform-ratchet.spec.ts +134 -0
- package/src/transform/transform-ratchet.ts +88 -0
- package/src/transform/transform-rule.ts +7 -0
- package/src/tx/transaction-configuration.ts +8 -0
- package/src/tx/transaction-final-state.ts +7 -0
- package/src/tx/transaction-ratchet.spec.ts +150 -0
- package/src/tx/transaction-ratchet.ts +98 -0
- package/src/tx/transaction-result.ts +10 -0
- package/src/tx/transaction-step.ts +5 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { PromiseRatchet } from './promise-ratchet.js';
|
|
2
|
+
import { Logger } from '../logger/logger.js';
|
|
3
|
+
import { TimeoutToken } from './timeout-token.js';
|
|
4
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
const fnFalse = (_ignored) => {
|
|
8
|
+
return false;
|
|
9
|
+
};
|
|
10
|
+
const fnOn3 = (t) => {
|
|
11
|
+
Logger.info('t=%d', t);
|
|
12
|
+
return t == 3;
|
|
13
|
+
};
|
|
14
|
+
const waitAndPrint = async (t1: number, t2: string) => {
|
|
15
|
+
Logger.info('Running: %s', t2);
|
|
16
|
+
await PromiseRatchet.wait(t1 * 2);
|
|
17
|
+
return t1 * 2;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('#promiseRatchet', function () {
|
|
21
|
+
test('should timeout eventually', async () => {
|
|
22
|
+
try {
|
|
23
|
+
Logger.setLevel(LoggerLevelName.silly);
|
|
24
|
+
const result: boolean | TimeoutToken = await PromiseRatchet.waitFor(fnFalse, true, 100, 2);
|
|
25
|
+
Logger.info('Got : %s', result);
|
|
26
|
+
expect(result).toEqual(false);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
Logger.warn('Error: %s', err, err);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should succeed on 3rd try', async () => {
|
|
33
|
+
const promise: Promise<boolean> = PromiseRatchet.waitFor(fnOn3, true, 100, 4);
|
|
34
|
+
const result: boolean | TimeoutToken = await promise;
|
|
35
|
+
expect(result).toEqual(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should run 10 elements, 2 at a time', async () => {
|
|
39
|
+
Logger.setLevel(LoggerLevelName.debug);
|
|
40
|
+
const elements: any[][] = [
|
|
41
|
+
[100, 'Test1'],
|
|
42
|
+
[120, 'Test2'],
|
|
43
|
+
[130, 'Test3'],
|
|
44
|
+
[140, 'Test4'],
|
|
45
|
+
[150, 'Test5'],
|
|
46
|
+
[160, 'Test6'],
|
|
47
|
+
[170, 'Test7'],
|
|
48
|
+
[180, 'Test8'],
|
|
49
|
+
[190, 'Test9'],
|
|
50
|
+
[200, 'Test10'],
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const results: number[] = await PromiseRatchet.runBoundedParallel<number>(waitAndPrint, elements, this, 2);
|
|
54
|
+
|
|
55
|
+
Logger.info('Final results %j', results);
|
|
56
|
+
expect(results).toBeTruthy();
|
|
57
|
+
expect(results.length).toEqual(10);
|
|
58
|
+
}, 30000);
|
|
59
|
+
|
|
60
|
+
test('should run 10 waits, 2 at a time', async () => {
|
|
61
|
+
Logger.setLevel(LoggerLevelName.debug);
|
|
62
|
+
const elements: number[] = [100, 110, 120, 130, 140, 150, 160, 170, 180, 200];
|
|
63
|
+
|
|
64
|
+
const results: any[] = await PromiseRatchet.runBoundedParallelSingleParam(PromiseRatchet.wait, elements, this, 2);
|
|
65
|
+
|
|
66
|
+
Logger.info('Final results %j', results);
|
|
67
|
+
expect(results).toBeTruthy();
|
|
68
|
+
expect(results.length).toEqual(10);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Validate runBoundedParallelSingleParam params', async () => {
|
|
72
|
+
const inputSymbols: string[] = ['a', 'b', 'c'];
|
|
73
|
+
|
|
74
|
+
const output: boolean[] = await PromiseRatchet.runBoundedParallelSingleParam<boolean>(
|
|
75
|
+
async (symbol) => {
|
|
76
|
+
Logger.info('Symbol was %s', symbol);
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
inputSymbols,
|
|
80
|
+
this,
|
|
81
|
+
1,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(output).not.toBeNull();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test.skip('should run an async function as a for/each', async () => {
|
|
88
|
+
Logger.setLevel(LoggerLevelName.debug);
|
|
89
|
+
const elements: number[] = [1001, 1002, 2000];
|
|
90
|
+
const pfn: (v: any) => Promise<void> = async (v) => {
|
|
91
|
+
Logger.info('Waiting %s', v);
|
|
92
|
+
await PromiseRatchet.wait(v);
|
|
93
|
+
Logger.info('Finished %s', v);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
Logger.info('Serial test');
|
|
97
|
+
await PromiseRatchet.asyncForEachSerial(elements, pfn);
|
|
98
|
+
|
|
99
|
+
Logger.info('Parallel test');
|
|
100
|
+
await PromiseRatchet.asyncForEachParallel(elements, pfn);
|
|
101
|
+
|
|
102
|
+
Logger.info('Done');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for working with promises
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from '../logger/logger.js';
|
|
6
|
+
import { ArrayRatchet } from './array-ratchet.js';
|
|
7
|
+
import { TimeoutToken } from './timeout-token.js';
|
|
8
|
+
import { StopWatch } from './stop-watch.js';
|
|
9
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
10
|
+
|
|
11
|
+
export class PromiseRatchet {
|
|
12
|
+
/**
|
|
13
|
+
* Returns a promise that only resolves when the named event happens on the event source -
|
|
14
|
+
* the promise will return the passed object, if any, at that point. If errEventNames are specified,
|
|
15
|
+
* the promise will reject when any of those events are fired
|
|
16
|
+
* @param evtSrc Object that will fire the event
|
|
17
|
+
* @param okEvtNames Names of the event that will be considered successes
|
|
18
|
+
* @param errEvtNames Names of error events
|
|
19
|
+
* @param rval Return value, if any
|
|
20
|
+
*/
|
|
21
|
+
public static resolveOnEvent<T>(evtSrc: any, okEvtNames: string[], errEvtNames: string[] = [], rval: T = null): Promise<T> {
|
|
22
|
+
if (!evtSrc || !okEvtNames || okEvtNames.length === 0 || !evtSrc['on']) {
|
|
23
|
+
return Promise.reject('Cannot continue - missing source object or name, or the object is not an event source');
|
|
24
|
+
}
|
|
25
|
+
return new Promise<T>((res, rej) => {
|
|
26
|
+
okEvtNames.forEach((e) => {
|
|
27
|
+
evtSrc.on(e, () => {
|
|
28
|
+
res(rval);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (errEvtNames) {
|
|
33
|
+
errEvtNames.forEach((e) => {
|
|
34
|
+
evtSrc.on(e, (err) => {
|
|
35
|
+
rej(err);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static timeout<T>(srcPromise: Promise<T>, title: string, timeoutMS: number): Promise<T | TimeoutToken> {
|
|
43
|
+
return Promise.race([srcPromise, PromiseRatchet.createTimeoutPromise(title, timeoutMS) as Promise<T | TimeoutToken>]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static createTimeoutPromise(title: string, timeoutMS: number): Promise<TimeoutToken> {
|
|
47
|
+
// Create a promise that rejects in <timeoutMS> milliseconds
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
49
|
+
return new Promise<TimeoutToken>((resolve, reject) => {
|
|
50
|
+
const id = setTimeout(() => {
|
|
51
|
+
clearTimeout(id);
|
|
52
|
+
const rval: TimeoutToken = new TimeoutToken(title, timeoutMS);
|
|
53
|
+
resolve(rval);
|
|
54
|
+
}, timeoutMS);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public static async wait(time: number): Promise<void> {
|
|
59
|
+
// Ignore the returned timeout token
|
|
60
|
+
await PromiseRatchet.createTimeoutPromise('Wait ' + time, time);
|
|
61
|
+
Logger.silly('Finished wait of %d ms', time);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static dumpResult(result: any): void {
|
|
65
|
+
Logger.info('Success, result was : \n\n%s\n\n', JSON.stringify(result));
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public static dumpError(err: any): void {
|
|
70
|
+
Logger.warn('Failure, err was : \n\n%s\n\n -- \n\n%s\n\n', JSON.stringify(err), String(err));
|
|
71
|
+
console.trace();
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public static logErrorAndReturnNull(err: any): void {
|
|
76
|
+
Logger.warn('Failure, err was : \n\n%s\n\n -- \n\n%s\n\n', JSON.stringify(err), String(err));
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public static runPromiseAndDump<T>(promise: Promise<T>): void {
|
|
81
|
+
promise.then(PromiseRatchet.dumpResult).catch(PromiseRatchet.dumpError);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Waits for up to maxCycles iterations of intervalMS milliseconds for the test function to return the expected value
|
|
85
|
+
// If that happens, returns true, otherwise, returns false
|
|
86
|
+
// Also returns false if the test function throws an exception or returns null (null may NOT be the expectedValue, as
|
|
87
|
+
// it is used as the "breakout" poison pill value
|
|
88
|
+
public static async waitFor(
|
|
89
|
+
testFunction: (n: number) => any,
|
|
90
|
+
expectedValue: any,
|
|
91
|
+
intervalMS: number,
|
|
92
|
+
maxCycles: number,
|
|
93
|
+
label = 'waitFor',
|
|
94
|
+
count = 0,
|
|
95
|
+
): Promise<boolean> {
|
|
96
|
+
if (expectedValue == null || intervalMS < 50 || maxCycles < 1 || count < 0 || typeof testFunction != 'function') {
|
|
97
|
+
Logger.warn('%s: Invalid configuration for waitFor - exiting immediately', label);
|
|
98
|
+
|
|
99
|
+
Logger.warn(
|
|
100
|
+
'ExpectedValue : %s ; interval: %d ; maxCycles: %d ; test : %s',
|
|
101
|
+
expectedValue,
|
|
102
|
+
intervalMS,
|
|
103
|
+
maxCycles,
|
|
104
|
+
typeof testFunction,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let curVal: any = null;
|
|
111
|
+
try {
|
|
112
|
+
curVal = testFunction(count);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
Logger.warn('%s: Caught error while waiting, giving up : %s', label, err);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (curVal === null) {
|
|
119
|
+
Logger.debug('%s:CurVal was null - aborting', label);
|
|
120
|
+
return false;
|
|
121
|
+
} else if (curVal == expectedValue) {
|
|
122
|
+
Logger.debug('%s:Found expected value', label);
|
|
123
|
+
return true;
|
|
124
|
+
} else if (count > maxCycles) {
|
|
125
|
+
// Exceeded max cycles
|
|
126
|
+
Logger.debug('%s:Exceeded max cycles, giving up', label);
|
|
127
|
+
return false;
|
|
128
|
+
} else {
|
|
129
|
+
Logger.debug('%s : value not reached yet, waiting (count = %d of %d)', label, count, maxCycles);
|
|
130
|
+
await PromiseRatchet.wait(intervalMS);
|
|
131
|
+
return PromiseRatchet.waitFor(testFunction, expectedValue, intervalMS, maxCycles, label, count + 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public static async runBoundedParallel<T>(
|
|
136
|
+
promiseFn: (...args) => Promise<T> | undefined,
|
|
137
|
+
params: any[][],
|
|
138
|
+
context: any,
|
|
139
|
+
maxConcurrent = 1,
|
|
140
|
+
logLevel: LoggerLevelName = LoggerLevelName.debug,
|
|
141
|
+
): Promise<T[]> {
|
|
142
|
+
const sw: StopWatch = new StopWatch();
|
|
143
|
+
let rval: T[] = [];
|
|
144
|
+
let remain: any[][] = params;
|
|
145
|
+
Logger.logByLevel(logLevel, 'Processing %d total elements %d at a time', params.length, maxConcurrent);
|
|
146
|
+
|
|
147
|
+
const ctx: any = context || this;
|
|
148
|
+
let processed = 0;
|
|
149
|
+
const totalCount: number = remain.length;
|
|
150
|
+
|
|
151
|
+
while (remain.length > 0) {
|
|
152
|
+
const curBatch: any[] = remain.slice(0, Math.min(remain.length, maxConcurrent));
|
|
153
|
+
remain = remain.slice(curBatch.length);
|
|
154
|
+
|
|
155
|
+
const proms: Promise<T>[] = curBatch.map((c) => promiseFn.apply(ctx, c) as Promise<T>);
|
|
156
|
+
const output: T[] = await Promise.all(proms);
|
|
157
|
+
processed += proms.length;
|
|
158
|
+
rval = rval.concat(output);
|
|
159
|
+
|
|
160
|
+
const pct: number = processed / totalCount;
|
|
161
|
+
Logger.logByLevel(logLevel, '%d elements remain : %s', remain.length, sw.dumpExpected(pct));
|
|
162
|
+
}
|
|
163
|
+
sw.log();
|
|
164
|
+
return rval;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public static async runBoundedParallelSingleParam<T>(
|
|
168
|
+
promiseFn: (arg) => Promise<T> | undefined,
|
|
169
|
+
params: any[],
|
|
170
|
+
context: any,
|
|
171
|
+
maxConcurrent = 1,
|
|
172
|
+
logLevel: LoggerLevelName = LoggerLevelName.debug,
|
|
173
|
+
): Promise<T[]> {
|
|
174
|
+
const wrappedParams: any[][] = ArrayRatchet.wrapElementsInArray(params);
|
|
175
|
+
return PromiseRatchet.runBoundedParallel<T>(promiseFn, wrappedParams, context, maxConcurrent, logLevel);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public static async asyncForEachSerial<T, R>(array: any[], callback: (val: T, idx: number, arr: T[]) => Promise<R>): Promise<void> {
|
|
179
|
+
for (let index = 0; index < array.length; index++) {
|
|
180
|
+
await callback(array[index], index, array);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public static async asyncForEachParallel<T>(array: T[], callback: (val: T, idx: number, arr: T[]) => Promise<any>): Promise<void> {
|
|
185
|
+
const proms: Promise<any>[] = [];
|
|
186
|
+
|
|
187
|
+
for (let index = 0; index < array.length; index++) {
|
|
188
|
+
proms.push(callback(array[index], index, array));
|
|
189
|
+
}
|
|
190
|
+
await Promise.all(proms);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private constructor() {
|
|
194
|
+
// Blocked for instantiation
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { RequireRatchet } from './require-ratchet.js';
|
|
2
|
+
import { fail } from 'assert';
|
|
3
|
+
import { Logger } from '../logger/logger.js';
|
|
4
|
+
import { describe, test } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('#standardCases', function () {
|
|
7
|
+
test('should throw exception on null value', function () {
|
|
8
|
+
try {
|
|
9
|
+
RequireRatchet.notNullOrUndefined(null, 'test1');
|
|
10
|
+
fail('Should have thrown exception');
|
|
11
|
+
} catch (err) {
|
|
12
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
RequireRatchet.notNullOrUndefined(undefined, 'test2');
|
|
16
|
+
fail('Should have thrown exception');
|
|
17
|
+
} catch (err) {
|
|
18
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should throw exception on whitespace value', function () {
|
|
23
|
+
try {
|
|
24
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString('', 'test1');
|
|
25
|
+
fail('Should have thrown exception');
|
|
26
|
+
} catch (err) {
|
|
27
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
RequireRatchet.notNullUndefinedOrOnlyWhitespaceString(' ', 'test2');
|
|
31
|
+
fail('Should have thrown exception');
|
|
32
|
+
} catch (err) {
|
|
33
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('#noNullOrUndefinedValuesInArray', function () {
|
|
39
|
+
test('should throw exception on null value', function () {
|
|
40
|
+
const arr: any[] = [1, null, 'a'];
|
|
41
|
+
try {
|
|
42
|
+
RequireRatchet.noNullOrUndefinedValuesInArray(arr);
|
|
43
|
+
fail('Should have thrown exception');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('throw exception on bad length', function () {
|
|
50
|
+
const arr: any[] = [1, 2, 'a'];
|
|
51
|
+
try {
|
|
52
|
+
RequireRatchet.noNullOrUndefinedValuesInArray(arr, 4);
|
|
53
|
+
fail('Should have thrown exception');
|
|
54
|
+
} catch (err) {
|
|
55
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should not throw exception on good values', function () {
|
|
60
|
+
const arr: any[] = [1, 2, 'a'];
|
|
61
|
+
RequireRatchet.noNullOrUndefinedValuesInArray(arr, 3);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should not throw exception on good constructor values', function () {
|
|
65
|
+
const _test: ConstructorTester = new ConstructorTester('a', 'b');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('throw exception on null constructor param', function () {
|
|
69
|
+
try {
|
|
70
|
+
const _test: ConstructorTester = new ConstructorTester('a', null);
|
|
71
|
+
fail('Should have thrown exception');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
Logger.debug('Correctly threw exception : %s', err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export class ConstructorTester {
|
|
79
|
+
constructor(_testVal1: string, _testVal2: string) {
|
|
80
|
+
Logger.info('Args : %j', this.constructor);
|
|
81
|
+
//Logger.info('Args : %j', this.constructor.arguments.length);
|
|
82
|
+
|
|
83
|
+
//RequireRatchet.noNullOrUndefinedValuesInRestArgs(this.constructor.arguments, ...arguments);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for making simple assertions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class RequireRatchet {
|
|
6
|
+
public static isNullOrUndefined(ob: any): boolean {
|
|
7
|
+
return Object.is(ob, null) || Object.is(ob, undefined);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public static notNullOrUndefined(ob: any, name = 'object'): void {
|
|
11
|
+
if (RequireRatchet.isNullOrUndefined(ob)) {
|
|
12
|
+
throw new Error(name + ' may not be null or undefined');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public static notNullUndefinedOrOnlyWhitespaceString(ob: string, name = 'string'): void {
|
|
17
|
+
if (RequireRatchet.isNullOrUndefined(ob) || ob.trim() === '') {
|
|
18
|
+
throw new Error(name + ' may not be null or undefined or only whitespace string');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static notNullUndefinedOrEmptyArray(ob: any[], name = 'string'): void {
|
|
23
|
+
if (RequireRatchet.isNullOrUndefined(ob) || ob.length === 0) {
|
|
24
|
+
throw new Error(name + ' may not be null or undefined or an empty array');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static equal(ob1: any, ob2: any, message = 'Values must be equal'): void {
|
|
29
|
+
if (ob1 !== ob2) {
|
|
30
|
+
throw new Error(message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static true(ob: boolean, message = 'Value must be true'): void {
|
|
35
|
+
RequireRatchet.equal(ob, true, message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public static noNullOrUndefinedValuesInArray(arr: any[], expectedLength: number = null, customMsg: string = null): void {
|
|
39
|
+
RequireRatchet.notNullOrUndefined(arr, 'Source array may not be null');
|
|
40
|
+
if (expectedLength !== null && arr.length !== expectedLength) {
|
|
41
|
+
throw new Error(`Expected array of length ${expectedLength} but was ${arr.length} ${customMsg}`);
|
|
42
|
+
}
|
|
43
|
+
for (let i = 0; i < arr.length; i++) {
|
|
44
|
+
if (RequireRatchet.isNullOrUndefined(arr[i])) {
|
|
45
|
+
throw new Error(`Array index ${i} was null or undefined ${customMsg}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static noNullOrUndefinedValuesInRestArgs(expectedLength: number, ...restArgs: any[]): void {
|
|
51
|
+
RequireRatchet.notNullOrUndefined(restArgs, 'Source array may not be null');
|
|
52
|
+
if (expectedLength !== null && restArgs.length !== expectedLength) {
|
|
53
|
+
throw new Error(`Expected array of length ${expectedLength} but was ${restArgs.length}`);
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < restArgs.length; i++) {
|
|
56
|
+
if (RequireRatchet.isNullOrUndefined(restArgs[i])) {
|
|
57
|
+
throw new Error(`Array index ${i} was null or undefined`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public static constructorArgumentsMatchLengthAndAreNonNull(): void {
|
|
63
|
+
// eslint-disable-next-line prefer-rest-params
|
|
64
|
+
const args: any[] = Array.from(arguments);
|
|
65
|
+
const len: number = this.constructor.length;
|
|
66
|
+
return RequireRatchet.noNullOrUndefinedValuesInArray(args, len);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { SimpleArgRatchet } from './simple-arg-ratchet.js';
|
|
3
|
+
|
|
4
|
+
describe('#simpleArgRatchet', function () {
|
|
5
|
+
test('should parse multi arguments', function () {
|
|
6
|
+
const test: string[] = ['--a', 'b', '--c', 'd', '--a', 'e'];
|
|
7
|
+
const out: Record<string, string[]> = SimpleArgRatchet.parseArgs(test, ['a', 'c']);
|
|
8
|
+
expect(out).not.toBeNull;
|
|
9
|
+
expect(out['a'].length).toEqual(2);
|
|
10
|
+
expect(out['c'].length).toEqual(1);
|
|
11
|
+
expect(out['c'][0]).toEqual('d');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ErrorRatchet } from './error-ratchet.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A dirt simple arg parser - only allows the --x y format
|
|
5
|
+
*/
|
|
6
|
+
export class SimpleArgRatchet {
|
|
7
|
+
// Prevent instantiation
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
9
|
+
private constructor() {}
|
|
10
|
+
|
|
11
|
+
public static parseArgs(args: string[], validArgNames: string[]): Record<string, string[]> {
|
|
12
|
+
const rval: Record<string, string[]> = {};
|
|
13
|
+
if (args?.length) {
|
|
14
|
+
if (args.length % 2 !== 0) {
|
|
15
|
+
throw ErrorRatchet.fErr('Invalid arguments, all args must take the form --a b, but there were an odd number of arguments');
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
18
|
+
let key: string = args[i];
|
|
19
|
+
const value: string = args[i + 1];
|
|
20
|
+
if (!key.startsWith('--')) {
|
|
21
|
+
throw ErrorRatchet.fErr('Argument %s does not take the form --x', key);
|
|
22
|
+
}
|
|
23
|
+
key = key.substring(2);
|
|
24
|
+
if (!validArgNames.includes(key)) {
|
|
25
|
+
throw ErrorRatchet.fErr('Argument %s is not a valid argument (valid were %j)', key, validArgNames);
|
|
26
|
+
}
|
|
27
|
+
rval[key] = rval[key] ?? [];
|
|
28
|
+
rval[key].push(value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return rval;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static parseSingleArgs(args: string[], validArgNames: string[]): Record<string, string> {
|
|
35
|
+
const tmp: Record<string, string[]> = SimpleArgRatchet.parseArgs(args, validArgNames);
|
|
36
|
+
const rval: Record<string, string> = {};
|
|
37
|
+
Object.keys(tmp).forEach((k) => {
|
|
38
|
+
const v: string[] = tmp[k];
|
|
39
|
+
if (v.length > 1) {
|
|
40
|
+
throw ErrorRatchet.fErr('Argument %s had %d values, but should have 1', k, v.length);
|
|
41
|
+
}
|
|
42
|
+
rval[k] = v[0];
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return rval;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { RequireRatchet } from "./require-ratchet.ts";
|
|
2
|
+
import { NumberRatchet } from "./number-ratchet.ts";
|
|
3
|
+
import { Base64Ratchet } from "./base64-ratchet.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A VERY simple wrapper for doing basic AES-GCM encryption on arbitrary text -
|
|
7
|
+
* useful for slugs, etc., since the same code should work both in Node.js and
|
|
8
|
+
* most modern browsers
|
|
9
|
+
*/
|
|
10
|
+
export class SimpleEncryptionRatchet{
|
|
11
|
+
private sharedKey: Promise<CryptoKey>;
|
|
12
|
+
|
|
13
|
+
constructor(sharedRawKey: string | Promise<string>, private urlSafe:boolean = false, private ivLength: number = 12) { // Recommended for AES-GCM
|
|
14
|
+
RequireRatchet.notNullOrUndefined(sharedRawKey);
|
|
15
|
+
RequireRatchet.true(ivLength>=12, 'ivLength must be at least 12');
|
|
16
|
+
this.sharedKey = this.createSharedKey(sharedRawKey);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Converts string to ArrayBuffer
|
|
20
|
+
private strToBuf(str: string): Uint8Array<ArrayBuffer> {
|
|
21
|
+
return new TextEncoder().encode(str);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Converts ArrayBuffer to base64
|
|
25
|
+
private bufToBase64(buf: ArrayBuffer): string {
|
|
26
|
+
return btoa(String.fromCharCode(...new Uint8Array(buf)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Converts base64 to ArrayBuffer
|
|
30
|
+
private base64ToBuf(base64: string): Uint8Array<ArrayBuffer> {
|
|
31
|
+
return new Uint8Array(atob(base64).split('').map(c => c.charCodeAt(0)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// Encrypt a string with a shared key
|
|
36
|
+
public async encrypt(data: string): Promise<string> {
|
|
37
|
+
const iv:Uint8Array<ArrayBuffer> = crypto.getRandomValues(new Uint8Array(this.ivLength));
|
|
38
|
+
const encoded:Uint8Array<ArrayBuffer> = this.strToBuf(data);
|
|
39
|
+
const key: CryptoKey = await this.sharedKey;
|
|
40
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
41
|
+
{ name: "AES-GCM", iv },
|
|
42
|
+
key,
|
|
43
|
+
encoded
|
|
44
|
+
);
|
|
45
|
+
const ivMsg: string = this.bufToBase64(iv.buffer);
|
|
46
|
+
const dataMsg: string = this.bufToBase64(ciphertext);
|
|
47
|
+
|
|
48
|
+
// Format it up in a way that can be unrolled later
|
|
49
|
+
let rval: string = ivMsg.length+'K'+ivMsg+dataMsg;
|
|
50
|
+
if (this.urlSafe) {
|
|
51
|
+
rval = Base64Ratchet.encodeStringToBase64UrlString(rval);
|
|
52
|
+
}
|
|
53
|
+
return rval;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Decrypt a string with the shared key
|
|
57
|
+
public async decrypt(encryptedValueIn: string): Promise<string> {
|
|
58
|
+
const encryptedValue: string = this.urlSafe ? Base64Ratchet.decodeBase64UrlStringToString(encryptedValueIn) : encryptedValueIn;
|
|
59
|
+
const split: number = encryptedValue?.indexOf('K')
|
|
60
|
+
if (!split || split<1) {
|
|
61
|
+
throw new Error('Invalid split : '+split);
|
|
62
|
+
}
|
|
63
|
+
const ivLen: number = NumberRatchet.safeNumber(encryptedValue.substring(0, split));
|
|
64
|
+
const iv: string = encryptedValue.substring(split+1, split+1+ivLen);
|
|
65
|
+
const data: string = encryptedValue.substring(split+1+ivLen);
|
|
66
|
+
|
|
67
|
+
const key: CryptoKey = await this.sharedKey;
|
|
68
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
69
|
+
{ name: "AES-GCM", iv: this.base64ToBuf(iv) },
|
|
70
|
+
key,
|
|
71
|
+
this.base64ToBuf(data)
|
|
72
|
+
);
|
|
73
|
+
return new TextDecoder().decode(decrypted);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Generate or import a shared key
|
|
77
|
+
private async createSharedKey(rawKeyIn: string | Promise<string>): Promise<CryptoKey> {
|
|
78
|
+
const rawKey: string = typeof rawKeyIn === 'string' ? rawKeyIn : await rawKeyIn;
|
|
79
|
+
const keyMaterial = this.strToBuf(rawKey.padEnd(32, '0').slice(0, 32)); // 256-bit key
|
|
80
|
+
return crypto.subtle.importKey(
|
|
81
|
+
"raw",
|
|
82
|
+
keyMaterial,
|
|
83
|
+
"AES-GCM",
|
|
84
|
+
false,
|
|
85
|
+
["encrypt", "decrypt"]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SortRatchet } from './sort-ratchet.js';
|
|
2
|
+
import { Logger } from '../logger/logger.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#sortRatchet', function () {
|
|
6
|
+
test('sort nulls and undefined to the top', async () => {
|
|
7
|
+
const input: string[] = [null, 'a', null, 'b', null, 'c'];
|
|
8
|
+
input.sort((a, b) => SortRatchet.sortNullToTop<string>(a, b, (a, b) => a.localeCompare(b)));
|
|
9
|
+
|
|
10
|
+
expect(input.length).toEqual(6);
|
|
11
|
+
expect(input[3]).toEqual('a');
|
|
12
|
+
expect(input[0]).toBeFalsy();
|
|
13
|
+
expect(input[1]).toBeFalsy();
|
|
14
|
+
expect(input[2]).toBeFalsy();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('sort nulls and undefined to the bottom', async () => {
|
|
18
|
+
const input: string[] = ['a', null, 'b', undefined, 'c', null];
|
|
19
|
+
input.sort((a, b) => SortRatchet.sortNullToBottom<string>(a, b, (a, b) => a.localeCompare(b)));
|
|
20
|
+
|
|
21
|
+
Logger.info('%j', input);
|
|
22
|
+
|
|
23
|
+
expect(input.length).toEqual(6);
|
|
24
|
+
expect(input[0]).toEqual('a');
|
|
25
|
+
expect(input[3]).toBeFalsy();
|
|
26
|
+
expect(input[4]).toBeFalsy();
|
|
27
|
+
expect(input[5]).toBeFalsy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('sort mixed numbers and strings as numbers', async () => {
|
|
31
|
+
const input: (string | number)[] = ['1', 10, '5', 20, '-8', 35.2];
|
|
32
|
+
input.sort(SortRatchet.sortNumericStringsAsNumbers);
|
|
33
|
+
|
|
34
|
+
Logger.info('%j', input);
|
|
35
|
+
|
|
36
|
+
expect(input.length).toEqual(6);
|
|
37
|
+
expect(input[0]).toEqual('-8');
|
|
38
|
+
expect(input[1]).toEqual('1');
|
|
39
|
+
expect(input[2]).toEqual('5');
|
|
40
|
+
expect(input[3]).toEqual(10);
|
|
41
|
+
expect(input[4]).toEqual(20);
|
|
42
|
+
expect(input[5]).toEqual(35.2);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('sort mixed numbers and strings as numbers handling string as null', async () => {
|
|
46
|
+
const input: (string | number)[] = ['a', 10, 'b', 20, '-8', 35.2];
|
|
47
|
+
input.sort(SortRatchet.sortNumericStringsAsNumbers);
|
|
48
|
+
|
|
49
|
+
Logger.info('%j', input);
|
|
50
|
+
|
|
51
|
+
expect(input[0]).toEqual('-8');
|
|
52
|
+
expect(input[1]).toEqual(10);
|
|
53
|
+
expect(input[2]).toEqual(20);
|
|
54
|
+
expect(input[3]).toEqual(35.2);
|
|
55
|
+
expect(input[4]).toEqual('a');
|
|
56
|
+
expect(input[5]).toEqual('b');
|
|
57
|
+
});
|
|
58
|
+
});
|