@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,247 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for working with base64
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from '../logger/logger.js';
|
|
6
|
+
import { StringRatchet } from './string-ratchet.js';
|
|
7
|
+
import { ErrorRatchet } from './error-ratchet.js';
|
|
8
|
+
|
|
9
|
+
// We use uint8 arrays in here because the default javascript handling of base64 encoding/decoding is
|
|
10
|
+
// broken for anything that isn't a normal ascii string
|
|
11
|
+
|
|
12
|
+
// Base64 code in this class lifted straight from:
|
|
13
|
+
// https://gist.githubusercontent.com/enepomnyaschih/72c423f727d395eeaa09697058238727/raw/74d3cbf82481545bc26c104de2419f4ee30c7dd7/base64.js
|
|
14
|
+
// Since native javascript handling is so poor
|
|
15
|
+
export class Base64Ratchet {
|
|
16
|
+
|
|
17
|
+
public static safeObjectToBase64JSON(input: any): any {
|
|
18
|
+
return input ? Base64Ratchet.generateBase64VersionOfString(JSON.stringify(input)) : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static safeBase64JSONParse(input: string): any {
|
|
22
|
+
let rval: any = {};
|
|
23
|
+
try {
|
|
24
|
+
if (input) {
|
|
25
|
+
rval = JSON.parse(Base64Ratchet.base64StringToString(input, 'utf-8'));
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
Logger.warn('Error parsing b64/json : %s as json, got %s', input, err, err);
|
|
29
|
+
rval = {};
|
|
30
|
+
}
|
|
31
|
+
return rval;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static generateBase64VersionOfBlob(blob: Blob): Promise<string> {
|
|
35
|
+
return new Promise(function (resolve, reject) {
|
|
36
|
+
if (!blob || blob.size == 0) {
|
|
37
|
+
reject('Wont convert null or non-blob or empty blob');
|
|
38
|
+
} else {
|
|
39
|
+
const reader = new FileReader();
|
|
40
|
+
reader.onloadend = function () {
|
|
41
|
+
resolve(reader.result.toString());
|
|
42
|
+
};
|
|
43
|
+
reader.readAsDataURL(blob);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static generateBase64VersionOfString(input: string): string {
|
|
49
|
+
return Base64Ratchet.generateBase64VersionOfUint8Array(StringRatchet.stringToUint8Array(input));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static generateBase64VersionOfUint8Array(input: Uint8Array): string {
|
|
53
|
+
return Base64Ratchet.uint8ArrayToBase64String(input);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static base64StringToUint8Array(b64encoded: string): Uint8Array<ArrayBuffer> {
|
|
57
|
+
try {
|
|
58
|
+
const uint8: Uint8Array<ArrayBuffer> = Base64Ratchet.base64StringToBytes(b64encoded);
|
|
59
|
+
return uint8;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
Logger.error('Failed to decode base64: %s', b64encoded);
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public static base64StringToString(input: string, encoding = 'utf8'): string {
|
|
67
|
+
return new TextDecoder(encoding).decode(Base64Ratchet.base64StringToUint8Array(input));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private static BASE64_ABC: string[] = [
|
|
71
|
+
'A',
|
|
72
|
+
'B',
|
|
73
|
+
'C',
|
|
74
|
+
'D',
|
|
75
|
+
'E',
|
|
76
|
+
'F',
|
|
77
|
+
'G',
|
|
78
|
+
'H',
|
|
79
|
+
'I',
|
|
80
|
+
'J',
|
|
81
|
+
'K',
|
|
82
|
+
'L',
|
|
83
|
+
'M',
|
|
84
|
+
'N',
|
|
85
|
+
'O',
|
|
86
|
+
'P',
|
|
87
|
+
'Q',
|
|
88
|
+
'R',
|
|
89
|
+
'S',
|
|
90
|
+
'T',
|
|
91
|
+
'U',
|
|
92
|
+
'V',
|
|
93
|
+
'W',
|
|
94
|
+
'X',
|
|
95
|
+
'Y',
|
|
96
|
+
'Z',
|
|
97
|
+
'a',
|
|
98
|
+
'b',
|
|
99
|
+
'c',
|
|
100
|
+
'd',
|
|
101
|
+
'e',
|
|
102
|
+
'f',
|
|
103
|
+
'g',
|
|
104
|
+
'h',
|
|
105
|
+
'i',
|
|
106
|
+
'j',
|
|
107
|
+
'k',
|
|
108
|
+
'l',
|
|
109
|
+
'm',
|
|
110
|
+
'n',
|
|
111
|
+
'o',
|
|
112
|
+
'p',
|
|
113
|
+
'q',
|
|
114
|
+
'r',
|
|
115
|
+
's',
|
|
116
|
+
't',
|
|
117
|
+
'u',
|
|
118
|
+
'v',
|
|
119
|
+
'w',
|
|
120
|
+
'x',
|
|
121
|
+
'y',
|
|
122
|
+
'z',
|
|
123
|
+
'0',
|
|
124
|
+
'1',
|
|
125
|
+
'2',
|
|
126
|
+
'3',
|
|
127
|
+
'4',
|
|
128
|
+
'5',
|
|
129
|
+
'6',
|
|
130
|
+
'7',
|
|
131
|
+
'8',
|
|
132
|
+
'9',
|
|
133
|
+
'+',
|
|
134
|
+
'/',
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
// This constant can also be computed with the following algorithm:
|
|
139
|
+
const l = 256, base64codes = new Uint8Array(l);
|
|
140
|
+
for (let i = 0; i < l; ++i) {
|
|
141
|
+
base64codes[i] = 255; // invalid character
|
|
142
|
+
}
|
|
143
|
+
base64abc.forEach((char, index) => {
|
|
144
|
+
base64codes[char.charCodeAt(0)] = index;
|
|
145
|
+
});
|
|
146
|
+
base64codes["=".charCodeAt(0)] = 0; // ignored anyway, so we just need to prevent an error
|
|
147
|
+
*/
|
|
148
|
+
private static BASE64_CODES: number[] = [
|
|
149
|
+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
150
|
+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59,
|
|
151
|
+
60, 61, 255, 255, 255, 0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
|
152
|
+
255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
public static getBase64Code(charCode: number): number {
|
|
156
|
+
if (charCode >= Base64Ratchet.BASE64_CODES.length) {
|
|
157
|
+
throw new Error('Unable to parse base64 string.');
|
|
158
|
+
}
|
|
159
|
+
const code: number = Base64Ratchet.BASE64_CODES[charCode];
|
|
160
|
+
if (code === 255) {
|
|
161
|
+
throw new Error('Unable to parse base64 string.');
|
|
162
|
+
}
|
|
163
|
+
return code;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public static uint8ArrayToBase64UrlString(bytes: Uint8Array): string {
|
|
167
|
+
let tmp: string = Base64Ratchet.uint8ArrayToBase64String(bytes);
|
|
168
|
+
tmp = tmp.split('+').join('-').split('/').join('_').split('=').join('');
|
|
169
|
+
return tmp;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public static uint8ArrayToBase64String(bytes: Uint8Array): string {
|
|
173
|
+
let result = '';
|
|
174
|
+
let i: number;
|
|
175
|
+
const l: number = bytes.length;
|
|
176
|
+
for (i = 2; i < l; i += 3) {
|
|
177
|
+
result += Base64Ratchet.BASE64_ABC[bytes[i - 2] >> 2];
|
|
178
|
+
result += Base64Ratchet.BASE64_ABC[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
179
|
+
result += Base64Ratchet.BASE64_ABC[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
|
180
|
+
result += Base64Ratchet.BASE64_ABC[bytes[i] & 0x3f];
|
|
181
|
+
}
|
|
182
|
+
if (i === l + 1) {
|
|
183
|
+
// 1 octet yet to write
|
|
184
|
+
result += Base64Ratchet.BASE64_ABC[bytes[i - 2] >> 2];
|
|
185
|
+
result += Base64Ratchet.BASE64_ABC[(bytes[i - 2] & 0x03) << 4];
|
|
186
|
+
result += '==';
|
|
187
|
+
}
|
|
188
|
+
if (i === l) {
|
|
189
|
+
// 2 octets yet to write
|
|
190
|
+
result += Base64Ratchet.BASE64_ABC[bytes[i - 2] >> 2];
|
|
191
|
+
result += Base64Ratchet.BASE64_ABC[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
192
|
+
result += Base64Ratchet.BASE64_ABC[(bytes[i - 1] & 0x0f) << 2];
|
|
193
|
+
result += '=';
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public static base64StringToBytes(str: string): Uint8Array<ArrayBuffer> {
|
|
199
|
+
if (str.length % 4 !== 0) {
|
|
200
|
+
throw ErrorRatchet.fErr('Unable to parse base64 string, length: %s', str.length);
|
|
201
|
+
}
|
|
202
|
+
const index = str.indexOf('=');
|
|
203
|
+
if (index !== -1 && index < str.length - 2) {
|
|
204
|
+
throw ErrorRatchet.fErr('Unable to parse base64 string, index %s', index);
|
|
205
|
+
}
|
|
206
|
+
const missingOctets: number = str.endsWith('==') ? 2 : str.endsWith('=') ? 1 : 0;
|
|
207
|
+
const n: number = str.length;
|
|
208
|
+
const result: Uint8Array<ArrayBuffer> = new Uint8Array<ArrayBuffer>(new ArrayBuffer(3 * (n / 4)));
|
|
209
|
+
let buffer;
|
|
210
|
+
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
|
|
211
|
+
buffer =
|
|
212
|
+
(Base64Ratchet.getBase64Code(str.charCodeAt(i)) << 18) |
|
|
213
|
+
(Base64Ratchet.getBase64Code(str.charCodeAt(i + 1)) << 12) |
|
|
214
|
+
(Base64Ratchet.getBase64Code(str.charCodeAt(i + 2)) << 6) |
|
|
215
|
+
Base64Ratchet.getBase64Code(str.charCodeAt(i + 3));
|
|
216
|
+
result[j] = buffer >> 16;
|
|
217
|
+
result[j + 1] = (buffer >> 8) & 0xff;
|
|
218
|
+
result[j + 2] = buffer & 0xff;
|
|
219
|
+
}
|
|
220
|
+
return result.subarray(0, result.length - missingOctets);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public static base64UrlStringToBytes(str: string): Uint8Array<ArrayBuffer> {
|
|
224
|
+
let dec: string = str.split('-').join('+').split('_').join('/');
|
|
225
|
+
while (dec.length % 4 !== 0) {
|
|
226
|
+
dec += '=';
|
|
227
|
+
}
|
|
228
|
+
Logger.info('pre: %s post %s', str, dec);
|
|
229
|
+
return Base64Ratchet.base64StringToBytes(dec);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public static encodeStringToBase64String(str: string, encoder = new TextEncoder()): string {
|
|
233
|
+
return Base64Ratchet.uint8ArrayToBase64String(encoder.encode(str));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public static encodeStringToBase64UrlString(str: string, encoder = new TextEncoder()): string {
|
|
237
|
+
return Base64Ratchet.uint8ArrayToBase64UrlString(encoder.encode(str));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public static decodeBase64StringToString(str: string, decoder = new TextDecoder()): string {
|
|
241
|
+
return decoder.decode(Base64Ratchet.base64StringToBytes(str));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public static decodeBase64UrlStringToString(str: string, decoder = new TextDecoder()): string {
|
|
245
|
+
return decoder.decode(Base64Ratchet.base64UrlStringToBytes(str));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BooleanRatchet } from './boolean-ratchet.js';
|
|
2
|
+
import { NumberRatchet } from './number-ratchet.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#parseBool', function () {
|
|
6
|
+
test('should check all true', function () {
|
|
7
|
+
expect(BooleanRatchet.allTrue(null)).toEqual(false);
|
|
8
|
+
expect(BooleanRatchet.allTrue([])).toEqual(false);
|
|
9
|
+
expect(BooleanRatchet.allTrue([], true)).toEqual(true);
|
|
10
|
+
expect(BooleanRatchet.allTrue([true])).toEqual(true);
|
|
11
|
+
expect(BooleanRatchet.allTrue([true, true])).toEqual(true);
|
|
12
|
+
expect(BooleanRatchet.allTrue([true, false, true])).toEqual(false);
|
|
13
|
+
expect(BooleanRatchet.allTrue([false])).toEqual(false);
|
|
14
|
+
expect(BooleanRatchet.allTrue([false, false])).toEqual(false);
|
|
15
|
+
});
|
|
16
|
+
test('should check any true', function () {
|
|
17
|
+
expect(BooleanRatchet.anyTrue(null)).toEqual(false);
|
|
18
|
+
expect(BooleanRatchet.anyTrue([])).toEqual(false);
|
|
19
|
+
expect(BooleanRatchet.anyTrue([], true)).toEqual(true);
|
|
20
|
+
expect(BooleanRatchet.anyTrue([true])).toEqual(true);
|
|
21
|
+
expect(BooleanRatchet.anyTrue([true, true])).toEqual(true);
|
|
22
|
+
expect(BooleanRatchet.anyTrue([true, false, true])).toEqual(true);
|
|
23
|
+
expect(BooleanRatchet.anyTrue([false])).toEqual(false);
|
|
24
|
+
expect(BooleanRatchet.anyTrue([false, false])).toEqual(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should xxx', function () {
|
|
28
|
+
const val = 'false';
|
|
29
|
+
|
|
30
|
+
const result: boolean = BooleanRatchet.parseBool(val) || BooleanRatchet.intToBool(NumberRatchet.safeNumber(val));
|
|
31
|
+
expect(result).toEqual(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should parse the string true as true', function () {
|
|
35
|
+
const result = BooleanRatchet.parseBool('true');
|
|
36
|
+
expect(result).toEqual(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should parse the string TRUE as true', function () {
|
|
40
|
+
const result = BooleanRatchet.parseBool('TRUE');
|
|
41
|
+
expect(result).toEqual(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should parse the boolean true as true', function () {
|
|
45
|
+
const result = BooleanRatchet.parseBool(true);
|
|
46
|
+
expect(result).toEqual(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should parse the empty string as false', function () {
|
|
50
|
+
const result = BooleanRatchet.parseBool('');
|
|
51
|
+
expect(result).toEqual(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should parse null as false', function () {
|
|
55
|
+
const result = BooleanRatchet.parseBool(null);
|
|
56
|
+
expect(result).toEqual(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should parse "asdf" as false', function () {
|
|
60
|
+
const result = BooleanRatchet.parseBool('asdf');
|
|
61
|
+
expect(result).toEqual(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('#intToBool', function () {
|
|
66
|
+
test('should parse null as false', function () {
|
|
67
|
+
const result = BooleanRatchet.intToBool(null);
|
|
68
|
+
expect(result).toEqual(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should parse 0 as false', function () {
|
|
72
|
+
const result = BooleanRatchet.intToBool(0);
|
|
73
|
+
expect(result).toEqual(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should parse "0" as false', function () {
|
|
77
|
+
const result = BooleanRatchet.intToBool('0');
|
|
78
|
+
expect(result).toEqual(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should parse 1 as true', function () {
|
|
82
|
+
const result = BooleanRatchet.intToBool(1);
|
|
83
|
+
expect(result).toEqual(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should parse "1" as true', function () {
|
|
87
|
+
const result = BooleanRatchet.intToBool('1');
|
|
88
|
+
expect(result).toEqual(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('should parse "2" as true', function () {
|
|
92
|
+
const result = BooleanRatchet.intToBool('2');
|
|
93
|
+
expect(result).toEqual(true);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for working with booleans
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { NumberRatchet } from './number-ratchet.js';
|
|
6
|
+
|
|
7
|
+
export class BooleanRatchet {
|
|
8
|
+
public static allTrue(vals: boolean[], emptyArraysReturn = false): boolean {
|
|
9
|
+
let rval: boolean = null;
|
|
10
|
+
if (vals) {
|
|
11
|
+
if (vals.length > 0) {
|
|
12
|
+
rval = vals.reduce((a, i) => a && i, true);
|
|
13
|
+
} else {
|
|
14
|
+
rval = emptyArraysReturn;
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
// Array was null
|
|
18
|
+
rval = false;
|
|
19
|
+
}
|
|
20
|
+
return rval;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static anyTrue(vals: boolean[], emptyArraysReturn = false): boolean {
|
|
24
|
+
let rval: boolean = null;
|
|
25
|
+
if (vals) {
|
|
26
|
+
if (vals.length > 0) {
|
|
27
|
+
rval = vals.reduce((a, i) => a || i, false);
|
|
28
|
+
} else {
|
|
29
|
+
rval = emptyArraysReturn;
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
// Array was null
|
|
33
|
+
rval = false;
|
|
34
|
+
}
|
|
35
|
+
return rval;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public static parseBool(val: any): boolean {
|
|
39
|
+
return val === true || (val !== null && val !== undefined && typeof val === 'string' && val.toLowerCase() === 'true');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static intToBool(val: any): boolean {
|
|
43
|
+
if (val === null || val === undefined) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return NumberRatchet.safeNumber(val) !== 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static boolToInt(val: any): number {
|
|
50
|
+
return BooleanRatchet.parseBool(val) ? 1 : 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LastSuccessProvider } from './last-success-provider.js';
|
|
2
|
+
import { CompositeLastSuccessProvider } from './composite-last-success-provider.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#lastSuccess', function () {
|
|
6
|
+
const last5: LastSuccessProvider = {
|
|
7
|
+
lastSuccess(): number {
|
|
8
|
+
return 5;
|
|
9
|
+
},
|
|
10
|
+
} as LastSuccessProvider;
|
|
11
|
+
const last4: LastSuccessProvider = {
|
|
12
|
+
lastSuccess(): number {
|
|
13
|
+
return 4;
|
|
14
|
+
},
|
|
15
|
+
} as LastSuccessProvider;
|
|
16
|
+
const lastNull: LastSuccessProvider = {
|
|
17
|
+
lastSuccess(): number {
|
|
18
|
+
return null;
|
|
19
|
+
},
|
|
20
|
+
} as LastSuccessProvider;
|
|
21
|
+
|
|
22
|
+
test('should return 4 as last (min)', function () {
|
|
23
|
+
const result: number = new CompositeLastSuccessProvider([last5, last4, lastNull], false).lastSuccess();
|
|
24
|
+
expect(result).toEqual(4);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should return 5 as last (max)', function () {
|
|
28
|
+
const result: number = new CompositeLastSuccessProvider([last5, last4, lastNull], true).lastSuccess();
|
|
29
|
+
expect(result).toEqual(5);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Classes implementing this interface return a timestamp of their last success **/
|
|
2
|
+
import { LastSuccessProvider } from './last-success-provider.js';
|
|
3
|
+
|
|
4
|
+
export class CompositeLastSuccessProvider implements LastSuccessProvider {
|
|
5
|
+
private sources: LastSuccessProvider[];
|
|
6
|
+
private mostRecent: boolean;
|
|
7
|
+
|
|
8
|
+
constructor(src: LastSuccessProvider[], mostRecentSrc = true) {
|
|
9
|
+
if (!src || src.length == 0) {
|
|
10
|
+
throw Error('Cannot create composite provider with null/empty sources');
|
|
11
|
+
}
|
|
12
|
+
this.sources = src;
|
|
13
|
+
this.mostRecent = mostRecentSrc;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public lastSuccess(): number {
|
|
17
|
+
let rval: number = null;
|
|
18
|
+
this.sources.forEach((s) => {
|
|
19
|
+
const val: number = s.lastSuccess();
|
|
20
|
+
if (val != null) {
|
|
21
|
+
if (rval == null) {
|
|
22
|
+
rval = val;
|
|
23
|
+
} else {
|
|
24
|
+
rval = (val > rval && this.mostRecent) || (val < rval && !this.mostRecent) ? val : rval;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return rval;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Range } from './range';
|
|
2
|
+
|
|
3
|
+
export class CurrencyRatchet {
|
|
4
|
+
// Prevent instantiation
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
6
|
+
private constructor() {}
|
|
7
|
+
|
|
8
|
+
public static centToDollarFormat(cents: number, fractionDigits: number = 2): string {
|
|
9
|
+
return CurrencyRatchet.dollarFormat(cents / 100, fractionDigits);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public static dollarFormat(dollars: number, fractionDigits: number = 2): string {
|
|
13
|
+
const rval: string =
|
|
14
|
+
dollars === null || dollars === undefined
|
|
15
|
+
? 'Null'
|
|
16
|
+
: '$' + dollars.toLocaleString('en-US', { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits });
|
|
17
|
+
return rval;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static dollarFormatNumberRange(range: Range<number>): string {
|
|
21
|
+
let rval: string = 'N/A';
|
|
22
|
+
if (range) {
|
|
23
|
+
rval = range.low ? CurrencyRatchet.dollarFormat(range.low) : ' ^ ';
|
|
24
|
+
rval += ' - ';
|
|
25
|
+
rval += range.high ? CurrencyRatchet.dollarFormat(range.high) : ' ^ ';
|
|
26
|
+
}
|
|
27
|
+
return rval;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DateRatchet } from './date-ratchet.js';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// CAW 2021-02-26 : I used to test 1776 here, but there was an odd off-by-8 seconds problem testing dates that
|
|
5
|
+
// far in the past. No doubt some kind of weird leap second thing. Not fighting that today
|
|
6
|
+
describe('#dateRatchet', function () {
|
|
7
|
+
test('should parse my common date format', function () {
|
|
8
|
+
const dt = DateRatchet.parseDefaultDate('1976-07-04');
|
|
9
|
+
expect(dt.getFullYear()).toEqual(1976);
|
|
10
|
+
expect(dt.getMonth()).toEqual(6); // 0 based
|
|
11
|
+
expect(dt.getDate()).toEqual(4);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should parse us with slashes', function () {
|
|
15
|
+
const dt = DateRatchet.parseCommonUsDate('07/04/1976');
|
|
16
|
+
expect(dt.getFullYear()).toEqual(1976);
|
|
17
|
+
expect(dt.getMonth()).toEqual(6); // 0 based
|
|
18
|
+
expect(dt.getDate()).toEqual(4);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should parse us with dashes', function () {
|
|
22
|
+
const dt = DateRatchet.parseCommonUsDate('07-04-1976');
|
|
23
|
+
expect(dt.getFullYear()).toEqual(1976);
|
|
24
|
+
expect(dt.getMonth()).toEqual(6); // 0 based
|
|
25
|
+
expect(dt.getDate()).toEqual(4);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Functions for working with dates/moments using my preferred date format, which
|
|
5
|
+
is yyyy-MM-dd and yyyy-MM-dd_HH-mm-ss_z
|
|
6
|
+
|
|
7
|
+
If I need milliseconds I tend to just work with epochs instead
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export class DateRatchet {
|
|
11
|
+
public static COMMON_US_DATE_FORMAT = 'MM/dd/yyyy';
|
|
12
|
+
public static DEFAULT_DATE_FORMAT = 'yyyy-MM-dd';
|
|
13
|
+
public static FULL_DATE_FORMAT = 'yyyy-MM-dd_HH_mm_ss';
|
|
14
|
+
|
|
15
|
+
public static PACIFIC_TIME_ZONE = 'America/Los_Angeles';
|
|
16
|
+
public static UTC_TIME_ZONE = 'etc/UTC';
|
|
17
|
+
|
|
18
|
+
public static formatFullDate(input: Date): string {
|
|
19
|
+
return DateTime.fromJSDate(input).toFormat(DateRatchet.FULL_DATE_FORMAT);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static formatDefaultDateOnly(input: Date): string {
|
|
23
|
+
return DateTime.fromJSDate(input).toFormat(DateRatchet.DEFAULT_DATE_FORMAT);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static parseDefaultDate(input: string): Date {
|
|
27
|
+
const rval: Date = DateTime.fromFormat(input, DateRatchet.DEFAULT_DATE_FORMAT).toJSDate();
|
|
28
|
+
return rval;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static parseCommonUsDate(input: string): Date {
|
|
32
|
+
let rval: Date = null;
|
|
33
|
+
if (input) {
|
|
34
|
+
let templ: string = DateRatchet.COMMON_US_DATE_FORMAT;
|
|
35
|
+
if (input.indexOf('-') === 2) {
|
|
36
|
+
templ = templ.split('/').join('-');
|
|
37
|
+
}
|
|
38
|
+
rval = DateTime.fromFormat(input, templ).toJSDate();
|
|
39
|
+
}
|
|
40
|
+
return rval;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DurationRatchet } from './duration-ratchet.js';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#formatMsDuration', function () {
|
|
6
|
+
test('should format less than one second', function () {
|
|
7
|
+
const result = DurationRatchet.formatMsDuration(409, true);
|
|
8
|
+
expect(result).toEqual('00h00m00.409s');
|
|
9
|
+
});
|
|
10
|
+
test('should format more than one day', function () {
|
|
11
|
+
const result = DurationRatchet.formatMsDuration(1000 * 60 * 60 * 123, true);
|
|
12
|
+
expect(result).toEqual('5d03h00m00.000s');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('#colonFormatMsDuration', function () {
|
|
17
|
+
test('should format less than one second', function () {
|
|
18
|
+
const result = DurationRatchet.colonFormatMsDuration(409, true);
|
|
19
|
+
expect(result).toEqual('00:00:00.409');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should format more than ten hours', function () {
|
|
23
|
+
const result = DurationRatchet.colonFormatMsDuration(1000 * 60 * 60 * 11, false);
|
|
24
|
+
expect(result).toEqual('11:00:00');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should format more than one hundred hours', function () {
|
|
28
|
+
const result = DurationRatchet.colonFormatMsDuration(1000 * 60 * 60 * 123, false);
|
|
29
|
+
expect(result).toEqual('123:00:00');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should format 15 seconds', function () {
|
|
33
|
+
const result = DurationRatchet.colonFormatMsDuration(15000, false);
|
|
34
|
+
expect(result).toEqual('00:00:15');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('#createSteps', function () {
|
|
39
|
+
test('should create steps', function () {
|
|
40
|
+
const startEpochMS: number = DateTime.fromFormat('2019-01-01', 'yyyy-MM-dd').toJSDate().getTime();
|
|
41
|
+
const endEpochMS: number = DateTime.fromFormat('2019-01-05', 'yyyy-MM-dd').toJSDate().getTime();
|
|
42
|
+
|
|
43
|
+
const steps: string[] = DurationRatchet.createSteps(startEpochMS, endEpochMS, 'etc/GMT', 'yyyy-MM-dd', { days: 1 });
|
|
44
|
+
|
|
45
|
+
expect(steps.length).toEqual(4);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DateTime, Duration, DurationLike } from 'luxon';
|
|
2
|
+
import { NumberRatchet } from './number-ratchet.js';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Functions for working with durations (times between times)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class DurationRatchet {
|
|
9
|
+
public static thinFormatMsDuration(ms: number, includeMS = false): string {
|
|
10
|
+
const rem_ms = Math.floor(ms % 1000);
|
|
11
|
+
const seconds = Math.floor(ms / 1000) % 60;
|
|
12
|
+
const minutes = Math.floor(ms / (1000 * 60)) % 60;
|
|
13
|
+
const hours = Math.floor(ms / (1000 * 60 * 60)) % 24;
|
|
14
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
15
|
+
|
|
16
|
+
const f = NumberRatchet.leadingZeros;
|
|
17
|
+
let rval: string = hours > 0 ? f(hours, 2) + 'h' : '';
|
|
18
|
+
rval += minutes > 0 ? f(minutes, 2) + 'm' : '';
|
|
19
|
+
rval += includeMS ? f(seconds, 2) + '.' + f(rem_ms, 3) + 's' : f(seconds, 2) + 's';
|
|
20
|
+
if (days > 0) {
|
|
21
|
+
rval = days + 'd' + rval;
|
|
22
|
+
}
|
|
23
|
+
return rval;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static formatMsDuration(ms: number, includeMS = false): string {
|
|
27
|
+
const rem_ms = Math.floor(ms % 1000);
|
|
28
|
+
const seconds = Math.floor(ms / 1000) % 60;
|
|
29
|
+
const minutes = Math.floor(ms / (1000 * 60)) % 60;
|
|
30
|
+
const hours = Math.floor(ms / (1000 * 60 * 60)) % 24;
|
|
31
|
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
32
|
+
|
|
33
|
+
const f = NumberRatchet.leadingZeros;
|
|
34
|
+
let rval = f(hours, 2) + 'h' + f(minutes, 2) + 'm';
|
|
35
|
+
rval += includeMS ? f(seconds, 2) + '.' + f(rem_ms, 3) + 's' : f(seconds, 2) + 's';
|
|
36
|
+
if (days > 0) {
|
|
37
|
+
rval = days + 'd' + rval;
|
|
38
|
+
}
|
|
39
|
+
return rval;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static colonFormatMsDuration(ms: number, includeMS = false): string {
|
|
43
|
+
const rem_ms = ms % 1000;
|
|
44
|
+
const seconds = Math.floor(ms / 1000) % 60;
|
|
45
|
+
const minutes = Math.floor(ms / (1000 * 60)) % 60;
|
|
46
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
47
|
+
|
|
48
|
+
const f = NumberRatchet.leadingZeros;
|
|
49
|
+
let rval = f(hours, 2) + ':' + f(minutes, 2) + ':';
|
|
50
|
+
rval += includeMS ? f(seconds, 2) + '.' + f(rem_ms, 3) : f(seconds, 2);
|
|
51
|
+
return rval;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static daysBetween(d1: Date, d2: Date): number {
|
|
55
|
+
const dur: Duration = DateTime.fromJSDate(d1).diff(DateTime.fromJSDate(d2));
|
|
56
|
+
return dur.days;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static createSteps(
|
|
60
|
+
startEpochMS: number,
|
|
61
|
+
endEpochMS: number,
|
|
62
|
+
timezone: string,
|
|
63
|
+
outputFormat: string,
|
|
64
|
+
stepUnit: DurationLike,
|
|
65
|
+
): string[] {
|
|
66
|
+
let curDate: DateTime = DateTime.fromMillis(startEpochMS).setZone(timezone);
|
|
67
|
+
const endDate: DateTime = DateTime.fromMillis(endEpochMS);
|
|
68
|
+
|
|
69
|
+
const rval: string[] = [];
|
|
70
|
+
while (curDate < endDate) {
|
|
71
|
+
rval.push(curDate.toFormat(outputFormat));
|
|
72
|
+
curDate = curDate.plus(stepUnit);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return rval;
|
|
76
|
+
}
|
|
77
|
+
}
|