@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.
Files changed (140) hide show
  1. package/package.json +2 -1
  2. package/src/2d/line-2d.ts +6 -0
  3. package/src/2d/matrix-factory.ts +94 -0
  4. package/src/2d/plane-2d-type.ts +6 -0
  5. package/src/2d/plane-2d.ts +7 -0
  6. package/src/2d/point-2d.ts +4 -0
  7. package/src/2d/poly-line-2d.ts +5 -0
  8. package/src/2d/ratchet-2d.spec.ts +205 -0
  9. package/src/2d/ratchet-2d.ts +350 -0
  10. package/src/2d/transformation-matrix.ts +19 -0
  11. package/src/build/build-information.ts +8 -0
  12. package/src/build/ratchet-common-info.ts +19 -0
  13. package/src/histogram/histogram-entry.ts +4 -0
  14. package/src/histogram/histogram.spec.ts +25 -0
  15. package/src/histogram/histogram.ts +61 -0
  16. package/src/jwt/common-jwt-token.ts +17 -0
  17. package/src/jwt/expired-jwt-handling.ts +5 -0
  18. package/src/jwt/jwt-decode-only-ratchet.ts +26 -0
  19. package/src/jwt/jwt-payload-expiration-ratchet.ts +45 -0
  20. package/src/jwt/jwt-token-base.ts +14 -0
  21. package/src/lang/array-ratchet.spec.ts +79 -0
  22. package/src/lang/array-ratchet.ts +141 -0
  23. package/src/lang/base64-ratchet.spec.ts +48 -0
  24. package/src/lang/base64-ratchet.ts +247 -0
  25. package/src/lang/boolean-ratchet.spec.ts +95 -0
  26. package/src/lang/boolean-ratchet.ts +52 -0
  27. package/src/lang/composite-last-success-provider.spec.ts +31 -0
  28. package/src/lang/composite-last-success-provider.ts +30 -0
  29. package/src/lang/currency-ratchet.ts +29 -0
  30. package/src/lang/date-ratchet.spec.ts +27 -0
  31. package/src/lang/date-ratchet.ts +42 -0
  32. package/src/lang/duration-ratchet.spec.ts +47 -0
  33. package/src/lang/duration-ratchet.ts +77 -0
  34. package/src/lang/enum-ratchet.spec.ts +45 -0
  35. package/src/lang/enum-ratchet.ts +41 -0
  36. package/src/lang/error-handling-approach.ts +6 -0
  37. package/src/lang/error-ratchet.spec.ts +25 -0
  38. package/src/lang/error-ratchet.ts +70 -0
  39. package/src/lang/esm-ratchet.ts +81 -0
  40. package/src/lang/expiring-object.spec.ts +56 -0
  41. package/src/lang/expiring-object.ts +84 -0
  42. package/src/lang/geolocation-ratchet.spec.ts +177 -0
  43. package/src/lang/geolocation-ratchet.ts +341 -0
  44. package/src/lang/global-ratchet.spec.ts +17 -0
  45. package/src/lang/global-ratchet.ts +105 -0
  46. package/src/lang/key-value.ts +8 -0
  47. package/src/lang/last-success-provider.ts +4 -0
  48. package/src/lang/map-ratchet.spec.ts +113 -0
  49. package/src/lang/map-ratchet.ts +220 -0
  50. package/src/lang/no.spec.ts +9 -0
  51. package/src/lang/no.ts +7 -0
  52. package/src/lang/number-ratchet.spec.ts +154 -0
  53. package/src/lang/number-ratchet.ts +253 -0
  54. package/src/lang/parsed-url.ts +10 -0
  55. package/src/lang/promise-ratchet.spec.ts +104 -0
  56. package/src/lang/promise-ratchet.ts +196 -0
  57. package/src/lang/range.ts +4 -0
  58. package/src/lang/require-ratchet.spec.ts +85 -0
  59. package/src/lang/require-ratchet.ts +68 -0
  60. package/src/lang/simple-arg-ratchet.spec.ts +13 -0
  61. package/src/lang/simple-arg-ratchet.ts +47 -0
  62. package/src/lang/simple-encryption-ratchet.ts +88 -0
  63. package/src/lang/sort-ratchet.spec.ts +58 -0
  64. package/src/lang/sort-ratchet.ts +50 -0
  65. package/src/lang/stop-watch.spec.ts +53 -0
  66. package/src/lang/stop-watch.ts +202 -0
  67. package/src/lang/string-ratchet.spec.ts +226 -0
  68. package/src/lang/string-ratchet.ts +676 -0
  69. package/src/lang/time-zone-ratchet.spec.ts +51 -0
  70. package/src/lang/time-zone-ratchet.ts +148 -0
  71. package/src/lang/timeout-token.spec.ts +12 -0
  72. package/src/lang/timeout-token.ts +21 -0
  73. package/src/lang/uint-8-array-ratchet.spec.ts +22 -0
  74. package/src/lang/uint-8-array-ratchet.ts +48 -0
  75. package/src/lang/web-stream-ratchet.spec.ts +12 -0
  76. package/src/lang/web-stream-ratchet.ts +96 -0
  77. package/src/logger/classic-single-line-log-message-formatter.ts +19 -0
  78. package/src/logger/log-message-builder.ts +60 -0
  79. package/src/logger/log-message-format-type.ts +11 -0
  80. package/src/logger/log-message-formatter.ts +6 -0
  81. package/src/logger/log-message-processor.ts +6 -0
  82. package/src/logger/log-message.ts +9 -0
  83. package/src/logger/log-snapshot.ts +6 -0
  84. package/src/logger/logger-instance.ts +269 -0
  85. package/src/logger/logger-level-name.ts +11 -0
  86. package/src/logger/logger-meta.ts +7 -0
  87. package/src/logger/logger-options.ts +14 -0
  88. package/src/logger/logger-output-function.ts +10 -0
  89. package/src/logger/logger-ring-buffer.ts +89 -0
  90. package/src/logger/logger-util.spec.ts +11 -0
  91. package/src/logger/logger-util.ts +68 -0
  92. package/src/logger/logger.spec.ts +177 -0
  93. package/src/logger/logger.ts +213 -0
  94. package/src/logger/none-log-message-formatter.ts +10 -0
  95. package/src/logger/single-line-no-level-log-message-formatter.ts +18 -0
  96. package/src/logger/structured-json-log-message-formatter.ts +25 -0
  97. package/src/mail/archive-email-result.ts +8 -0
  98. package/src/mail/email-attachment.ts +23 -0
  99. package/src/mail/mail-sending-provider.ts +21 -0
  100. package/src/mail/mailer-config.ts +30 -0
  101. package/src/mail/mailer-like.ts +38 -0
  102. package/src/mail/mailer-util.ts +65 -0
  103. package/src/mail/mailer.spec.ts +120 -0
  104. package/src/mail/mailer.ts +214 -0
  105. package/src/mail/ready-to-send-email.ts +67 -0
  106. package/src/mail/resolved-ready-to-send-email.ts +17 -0
  107. package/src/mail/send-email-result.ts +16 -0
  108. package/src/mail/test-mail-sending-provider.ts +35 -0
  109. package/src/network/browser-local-ip-provider.spec.ts +23 -0
  110. package/src/network/browser-local-ip-provider.ts +26 -0
  111. package/src/network/fixed-local-ip-provider.ts +9 -0
  112. package/src/network/local-ip-provider.ts +4 -0
  113. package/src/network/network-ratchet.spec.ts +17 -0
  114. package/src/network/network-ratchet.ts +209 -0
  115. package/src/network/remote-file-tracker/backup-result.ts +6 -0
  116. package/src/network/remote-file-tracker/file-transfer-result-type.ts +5 -0
  117. package/src/network/remote-file-tracker/file-transfer-result.ts +9 -0
  118. package/src/network/remote-file-tracker/remote-file-tracker-options.ts +6 -0
  119. package/src/network/remote-file-tracker/remote-file-tracker-push-options.ts +4 -0
  120. package/src/network/remote-file-tracker/remote-file-tracker.ts +117 -0
  121. package/src/network/remote-file-tracker/remote-file-tracking-provider.ts +19 -0
  122. package/src/network/remote-file-tracker/remote-status-data-and-content.ts +6 -0
  123. package/src/network/remote-file-tracker/remote-status-data.ts +7 -0
  124. package/src/network/restful-api-http-error.spec.ts +13 -0
  125. package/src/network/restful-api-http-error.ts +173 -0
  126. package/src/template/ratchet-template-renderer.ts +8 -0
  127. package/src/third-party/google/google-recaptcha-ratchet.spec.ts +27 -0
  128. package/src/third-party/google/google-recaptcha-ratchet.ts +36 -0
  129. package/src/third-party/twilio/twilio-ratchet.ts +92 -0
  130. package/src/third-party/twilio/twilio-verify-ratchet.ts +83 -0
  131. package/src/transform/built-in-transforms.ts +214 -0
  132. package/src/transform/transform-ratchet.spec.ts +134 -0
  133. package/src/transform/transform-ratchet.ts +88 -0
  134. package/src/transform/transform-rule.ts +7 -0
  135. package/src/tx/transaction-configuration.ts +8 -0
  136. package/src/tx/transaction-final-state.ts +7 -0
  137. package/src/tx/transaction-ratchet.spec.ts +150 -0
  138. package/src/tx/transaction-ratchet.ts +98 -0
  139. package/src/tx/transaction-result.ts +10 -0
  140. 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,4 @@
1
+ export interface Range<T> {
2
+ low: T;
3
+ high: T;
4
+ }
@@ -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
+ });