@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,45 @@
|
|
|
1
|
+
import { EnumRatchet } from './enum-ratchet.js';
|
|
2
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('#enumRatchet', function () {
|
|
6
|
+
test('should list keys', async () => {
|
|
7
|
+
const keys: string[] = EnumRatchet.listEnumKeys(TestEnum);
|
|
8
|
+
expect(keys).toBeTruthy();
|
|
9
|
+
expect(keys.length).toBeGreaterThan(1);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should find key case insensitive', async () => {
|
|
13
|
+
const t1: TestEnum = EnumRatchet.keyToEnum<TestEnum>(TestEnum, 'a');
|
|
14
|
+
const t2: TestEnum = EnumRatchet.keyToEnum<TestEnum>(TestEnum, 'b');
|
|
15
|
+
const t3: TestEnum = EnumRatchet.keyToEnum<TestEnum>(TestEnum, 'A', true);
|
|
16
|
+
const t4: TestEnum = EnumRatchet.keyToEnum<TestEnum>(TestEnum, 'A');
|
|
17
|
+
|
|
18
|
+
const fixed: TestEnum = TestEnum.A;
|
|
19
|
+
|
|
20
|
+
expect(t1).toBeTruthy();
|
|
21
|
+
expect(t2).toBeTruthy();
|
|
22
|
+
expect(t3).toBeNull();
|
|
23
|
+
expect(t4).toEqual(fixed);
|
|
24
|
+
expect(t4 === fixed).toBeTruthy();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should parse a csv of enums', async () => {
|
|
28
|
+
const vals: TestEnum[] = EnumRatchet.parseCsvToEnumArray<TestEnum>(TestEnum, 'a, b');
|
|
29
|
+
expect(vals).toBeTruthy();
|
|
30
|
+
expect(vals.length).toEqual(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should work with psuedo-enums like ratchet uses', async () => {
|
|
34
|
+
const keys: string[] = EnumRatchet.listEnumKeys(LoggerLevelName);
|
|
35
|
+
expect(keys.length).toEqual(6);
|
|
36
|
+
expect(keys.includes('info')).toBeTruthy();
|
|
37
|
+
expect(keys.includes('xxx')).toBeFalsy();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export enum TestEnum {
|
|
42
|
+
A = 'a',
|
|
43
|
+
B = 'b',
|
|
44
|
+
C = 'c',
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { $enum, EnumWrapper } from 'ts-enum-util';
|
|
2
|
+
import { StringRatchet } from './string-ratchet.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This class is here because Typescript's enums aren't very good - they aren't typesafe, and are difficult
|
|
6
|
+
* to go back-and-forth from strings, etc. This also allows the HTTP layer to send an enum that isn't using the
|
|
7
|
+
* same case as the enum and still handle it correctly. Either way, it puts all the conversion login in one
|
|
8
|
+
* place so if typescript changes this yet again in the future there'll be a convenient place to fix them
|
|
9
|
+
* all at once
|
|
10
|
+
*/
|
|
11
|
+
export class EnumRatchet {
|
|
12
|
+
public static listEnumKeys(enumeration: any): string[] {
|
|
13
|
+
const rval: string[] = $enum(enumeration).getValues();
|
|
14
|
+
return rval;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static keyToEnum<T>(enumeration: any, val: string, caseSensitive = false): T {
|
|
18
|
+
const e: EnumWrapper = $enum(enumeration);
|
|
19
|
+
let rval: T = null;
|
|
20
|
+
if (val) {
|
|
21
|
+
rval = e.asValueOrDefault(val, null);
|
|
22
|
+
if (!rval && !caseSensitive) {
|
|
23
|
+
// Try other cases
|
|
24
|
+
const keys: string[] = EnumRatchet.listEnumKeys(enumeration);
|
|
25
|
+
const mKey: string = keys.find((k) => k.toUpperCase() === val.toUpperCase());
|
|
26
|
+
if (mKey) {
|
|
27
|
+
rval = e.asValueOrDefault(mKey, null);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return rval;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static parseCsvToEnumArray<T>(enumeration: any, input: string): T[] {
|
|
35
|
+
const split: string[] = StringRatchet.trimToEmpty(input)
|
|
36
|
+
.split(',')
|
|
37
|
+
.map((s) => s.trim());
|
|
38
|
+
const rval: T[] = split.map((s) => EnumRatchet.keyToEnum<T>(enumeration, s)).filter((s) => !!s);
|
|
39
|
+
return rval;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ErrorRatchet } from './error-ratchet.js';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('#errorRatchet', function () {
|
|
5
|
+
test('should cast to error', async () => {
|
|
6
|
+
const e1: Error = new Error('test1');
|
|
7
|
+
const e2 = 'test2';
|
|
8
|
+
|
|
9
|
+
const e1p: Error = ErrorRatchet.asErr(e1);
|
|
10
|
+
const e2p: Error = ErrorRatchet.asErr(e2);
|
|
11
|
+
const en: Error = ErrorRatchet.asErr(null);
|
|
12
|
+
|
|
13
|
+
expect(en).toBeNull();
|
|
14
|
+
expect(e1p).toEqual(e1);
|
|
15
|
+
expect(e2p).not.toEqual(e2);
|
|
16
|
+
expect(e2p instanceof Error).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should correctly stringify', async () => {
|
|
20
|
+
const flag = 'test1';
|
|
21
|
+
const e1: Error = new Error(flag);
|
|
22
|
+
const out: string = ErrorRatchet.safeStringifyErr(e1);
|
|
23
|
+
expect(out).toEqual(flag);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Functions for working with errors
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from '../logger/logger.js';
|
|
6
|
+
import { StringRatchet } from './string-ratchet.js';
|
|
7
|
+
import { ErrorHandlingApproach } from './error-handling-approach.js';
|
|
8
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
9
|
+
|
|
10
|
+
export class ErrorRatchet {
|
|
11
|
+
public static handleErrorByApproach(
|
|
12
|
+
err: any,
|
|
13
|
+
approach: ErrorHandlingApproach,
|
|
14
|
+
level: LoggerLevelName = LoggerLevelName.error,
|
|
15
|
+
formatMsg: string = 'Error: %s',
|
|
16
|
+
) {
|
|
17
|
+
if (err && approach) {
|
|
18
|
+
if (approach === ErrorHandlingApproach.LogAndSwallow || approach === ErrorHandlingApproach.LogAndPassThru) {
|
|
19
|
+
Logger.logByLevel(level, formatMsg, err, err);
|
|
20
|
+
}
|
|
21
|
+
if (approach === ErrorHandlingApproach.PassThru || approach === ErrorHandlingApproach.LogAndPassThru) {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
Logger.error('Cannot handle error %s - %s, missing either error or approach', err, approach);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static safeStringifyErr(err: any, log = true): string {
|
|
30
|
+
let rval = 'ERR WAS NULL';
|
|
31
|
+
if (err) {
|
|
32
|
+
if (err['message']) {
|
|
33
|
+
rval = err['message'];
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
rval = JSON.stringify(err);
|
|
37
|
+
} catch (err2) {
|
|
38
|
+
Logger.error('Failed to json stringify: %s', err2);
|
|
39
|
+
rval = String(err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (log) {
|
|
44
|
+
Logger.error('%s', rval, err);
|
|
45
|
+
}
|
|
46
|
+
return rval;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Mainly for Typescript 4.5+ where this is now any/unknown by default
|
|
50
|
+
public static asErr(input: any): Error {
|
|
51
|
+
let rval: Error = null;
|
|
52
|
+
if (input) {
|
|
53
|
+
if (input instanceof Error) {
|
|
54
|
+
rval = input;
|
|
55
|
+
} else {
|
|
56
|
+
rval = new Error('Force-Cast to error : ' + String(input));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return rval;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public static fErr(format: string, ...input: any[]): Error {
|
|
63
|
+
const msg: string = StringRatchet.format(format, ...input);
|
|
64
|
+
return new Error(msg);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public static throwFormattedErr(format: string, ...input: any[]): Error {
|
|
68
|
+
throw ErrorRatchet.fErr(format, ...input);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions to ease the transition to using ESM instead of CommonJS
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import url from 'url';
|
|
6
|
+
import { ErrorRatchet } from './error-ratchet.js';
|
|
7
|
+
import { Logger } from '../logger/logger.js';
|
|
8
|
+
import { StringRatchet } from './string-ratchet.js';
|
|
9
|
+
|
|
10
|
+
export class EsmRatchet {
|
|
11
|
+
private static readonly DYNAMIC_IMPORT_CACHE: Map<string, Promise<any>> = new Map<string, Promise<any>>();
|
|
12
|
+
|
|
13
|
+
public static fetchDirName(root: string): string {
|
|
14
|
+
if (!root) {
|
|
15
|
+
throw new Error('Need to provide root (should be import.meta.url)');
|
|
16
|
+
}
|
|
17
|
+
const rval: string = url.fileURLToPath(new URL('.', root));
|
|
18
|
+
return rval;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static fetchFileName(root: string): string {
|
|
22
|
+
if (!root) {
|
|
23
|
+
throw new Error('Need to provide root (should be import.meta.url)');
|
|
24
|
+
}
|
|
25
|
+
const rval: string = url.fileURLToPath(root);
|
|
26
|
+
return rval;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static async cachedTypedDynamicImport<T>(
|
|
30
|
+
libPath: string,
|
|
31
|
+
importName?: string,
|
|
32
|
+
requiredKeys?: string[],
|
|
33
|
+
swallowErrorIfMissing?: boolean,
|
|
34
|
+
): Promise<T> {
|
|
35
|
+
const cacheKey: string = StringRatchet.trimToNull(importName) ? libPath + '__' + importName : libPath;
|
|
36
|
+
let rval: Promise<T> = EsmRatchet.DYNAMIC_IMPORT_CACHE.get(cacheKey);
|
|
37
|
+
if (!rval) {
|
|
38
|
+
rval = EsmRatchet.typedDynamicImport<T>(libPath, importName, requiredKeys, swallowErrorIfMissing);
|
|
39
|
+
if (rval) {
|
|
40
|
+
EsmRatchet.DYNAMIC_IMPORT_CACHE.set(cacheKey, rval);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return rval;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static async typedDynamicImport<T>(
|
|
47
|
+
libPath: string,
|
|
48
|
+
importName?: string,
|
|
49
|
+
requiredKeys?: string[],
|
|
50
|
+
swallowErrorIfMissing?: boolean,
|
|
51
|
+
): Promise<T> {
|
|
52
|
+
let rval: T;
|
|
53
|
+
try {
|
|
54
|
+
rval = await import(libPath);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (swallowErrorIfMissing) {
|
|
57
|
+
Logger.debug('Cannot find library %s but swallow specified, returning null', libPath);
|
|
58
|
+
rval = null;
|
|
59
|
+
} else {
|
|
60
|
+
throw ErrorRatchet.fErr('Could not find the "%s" library : %s', libPath, err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (StringRatchet.trimToNull(importName)) {
|
|
64
|
+
if (rval[importName]) {
|
|
65
|
+
rval = rval[importName];
|
|
66
|
+
} else {
|
|
67
|
+
throw ErrorRatchet.fErr('Imported library %s, tried to load name %s but only found %j', libPath, importName, Object.keys(rval));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (requiredKeys?.length && rval) {
|
|
72
|
+
const keys: string[] = Object.keys(rval);
|
|
73
|
+
requiredKeys.forEach((k) => {
|
|
74
|
+
if (!keys.includes(k)) {
|
|
75
|
+
throw ErrorRatchet.fErr('Failed to import "%s" - required keys are %j, but found %j', libPath, requiredKeys, keys);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return rval;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ExpiringObject } from './expiring-object.js';
|
|
2
|
+
import { PromiseRatchet } from './promise-ratchet.js';
|
|
3
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
4
|
+
import { describe, expect, test } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('#expiringObject', function () {
|
|
7
|
+
test('should default the object', async () => {
|
|
8
|
+
const expObject: ExpiringObject<number> = new ExpiringObject<number>({
|
|
9
|
+
initialValue: 7,
|
|
10
|
+
timeToLiveMS: 50_000,
|
|
11
|
+
logLevel: LoggerLevelName.info,
|
|
12
|
+
});
|
|
13
|
+
const val: number = await expObject.fetch();
|
|
14
|
+
expect(val).toEqual(7);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should expire the object', async () => {
|
|
18
|
+
const expObject: ExpiringObject<number> = new ExpiringObject<number>({
|
|
19
|
+
initialValue: 7,
|
|
20
|
+
timeToLiveMS: 100,
|
|
21
|
+
logLevel: LoggerLevelName.info,
|
|
22
|
+
});
|
|
23
|
+
const val: number = await expObject.fetch();
|
|
24
|
+
expect(val).toEqual(7);
|
|
25
|
+
await PromiseRatchet.wait(101);
|
|
26
|
+
const val2: number = await expObject.fetch();
|
|
27
|
+
expect(val2).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should regen the object', async () => {
|
|
31
|
+
const expObject: ExpiringObject<number> = new ExpiringObject<number>({
|
|
32
|
+
initialValue: 7,
|
|
33
|
+
timeToLiveMS: 100,
|
|
34
|
+
generator: async () => 8,
|
|
35
|
+
logLevel: LoggerLevelName.info,
|
|
36
|
+
});
|
|
37
|
+
const val: number = await expObject.fetch();
|
|
38
|
+
expect(val).toEqual(7);
|
|
39
|
+
await PromiseRatchet.wait(101);
|
|
40
|
+
const val2: number = await expObject.fetch();
|
|
41
|
+
expect(val2).toEqual(8);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should return time remaining', async () => {
|
|
45
|
+
const expObject: ExpiringObject<number> = new ExpiringObject<number>({
|
|
46
|
+
initialValue: 7,
|
|
47
|
+
timeToLiveMS: 10_000,
|
|
48
|
+
logLevel: LoggerLevelName.info,
|
|
49
|
+
});
|
|
50
|
+
const val: number = await expObject.fetch();
|
|
51
|
+
expect(val).toEqual(7);
|
|
52
|
+
const rem: number = await expObject.fetchCacheObjectTimeRemainingMS();
|
|
53
|
+
expect(rem).toBeGreaterThan(0);
|
|
54
|
+
expect(rem).toBeLessThanOrEqual(10_000);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Wraps an object with an expiring wrapper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Logger } from '../logger/logger.js';
|
|
6
|
+
import { ErrorRatchet } from './error-ratchet.js';
|
|
7
|
+
import { LoggerLevelName } from '../logger/logger-level-name.js';
|
|
8
|
+
|
|
9
|
+
export class ExpiringObject<T> {
|
|
10
|
+
private _config: ExpiringObjectConfig<T>;
|
|
11
|
+
private _cacheObject: T;
|
|
12
|
+
private _lastUpdatedEpochMS: number;
|
|
13
|
+
private _timeRemainingMS: (lastUpdatedEpochMS: number) => Promise<number>;
|
|
14
|
+
|
|
15
|
+
constructor(inputConfig?: ExpiringObjectConfig<T>) {
|
|
16
|
+
this._config = Object.assign({}, this.defaultConfig(), inputConfig || {});
|
|
17
|
+
if (this._config.overrideTimeRemainingMS && this._config.timeToLiveMS) {
|
|
18
|
+
ErrorRatchet.throwFormattedErr('Cannot define both time to live and overrideTimeRemainingMS');
|
|
19
|
+
}
|
|
20
|
+
if (!this._config.overrideTimeRemainingMS && !this._config.timeToLiveMS) {
|
|
21
|
+
ErrorRatchet.throwFormattedErr('Must define exactly one of timeToLiveMS or overrideTimeRemainingMS');
|
|
22
|
+
}
|
|
23
|
+
if (this._config.initialValue) {
|
|
24
|
+
this.update(this._config.initialValue);
|
|
25
|
+
}
|
|
26
|
+
this._timeRemainingMS = this._config.overrideTimeRemainingMS || this.defaultTimeRemainingMS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private defaultConfig(): ExpiringObjectConfig<T> {
|
|
30
|
+
const rval: ExpiringObjectConfig<T> = {
|
|
31
|
+
generator: null,
|
|
32
|
+
initialValue: null,
|
|
33
|
+
logLevel: LoggerLevelName.debug,
|
|
34
|
+
overrideTimeRemainingMS: null,
|
|
35
|
+
timeToLiveMS: 1_000 * 60, // Default TTL is 1 minute
|
|
36
|
+
};
|
|
37
|
+
return rval;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private async defaultTimeRemainingMS(lastUpdatedEpochMS: number): Promise<number> {
|
|
41
|
+
let rval = 0;
|
|
42
|
+
if (lastUpdatedEpochMS) {
|
|
43
|
+
const ageMS: number = new Date().getTime() - lastUpdatedEpochMS;
|
|
44
|
+
rval = Math.max(0, this._config.timeToLiveMS - ageMS);
|
|
45
|
+
}
|
|
46
|
+
return rval;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public update(newValue: T, doNotUpdateClock = false): void {
|
|
50
|
+
this._cacheObject = newValue;
|
|
51
|
+
if (!doNotUpdateClock) {
|
|
52
|
+
this._lastUpdatedEpochMS = new Date().getTime();
|
|
53
|
+
}
|
|
54
|
+
Logger.logByLevel(this._config.logLevel, 'Updated cache value to %j', newValue);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async fetchCacheObjectTimeRemainingMS(): Promise<number> {
|
|
58
|
+
return this._timeRemainingMS(this._lastUpdatedEpochMS);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async fetch(): Promise<T> {
|
|
62
|
+
const remainMS: number = await this._timeRemainingMS(this._lastUpdatedEpochMS);
|
|
63
|
+
if (!remainMS) {
|
|
64
|
+
this._cacheObject = null;
|
|
65
|
+
this._lastUpdatedEpochMS = null;
|
|
66
|
+
}
|
|
67
|
+
if (!this._cacheObject) {
|
|
68
|
+
if (this._config.generator) {
|
|
69
|
+
const newValue: T = await this._config.generator();
|
|
70
|
+
Logger.logByLevel(this._config.logLevel, 'Auto call to generator returned %j', newValue);
|
|
71
|
+
this.update(newValue);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return this._cacheObject;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ExpiringObjectConfig<T> {
|
|
79
|
+
timeToLiveMS?: number;
|
|
80
|
+
generator?: () => Promise<T>;
|
|
81
|
+
initialValue?: T;
|
|
82
|
+
logLevel?: LoggerLevelName;
|
|
83
|
+
overrideTimeRemainingMS?: (lastUpdatedEpochMS: number) => Promise<number>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { GeolocationRatchet, RatchetGeoLocation, RatchetLocationBounds, RatchetLocationBoundsMap } from './geolocation-ratchet.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { NumberRatchet } from './number-ratchet.js';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { EsmRatchet } from './esm-ratchet.js';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
describe('#geolocationRatchet', function () {
|
|
9
|
+
test('should canonicalize', function () {
|
|
10
|
+
expect(GeolocationRatchet.combineBounds([])).toBeNull();
|
|
11
|
+
expect(GeolocationRatchet.combineBounds(null)).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should canonicalize', function () {
|
|
15
|
+
const bounds: RatchetLocationBounds = {
|
|
16
|
+
origin: {
|
|
17
|
+
lat: 4,
|
|
18
|
+
lng: 3,
|
|
19
|
+
},
|
|
20
|
+
extent: {
|
|
21
|
+
lat: 2,
|
|
22
|
+
lng: 5,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const result: RatchetLocationBounds = GeolocationRatchet.canonicalizeBounds(bounds);
|
|
27
|
+
expect(result.origin.lat).toEqual(2);
|
|
28
|
+
expect(result.origin.lng).toEqual(3);
|
|
29
|
+
expect(result.extent.lat).toEqual(4);
|
|
30
|
+
expect(result.extent.lng).toEqual(5);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
test('should generate the right distance', function() {
|
|
35
|
+
const whLat: number = 38.8976805;
|
|
36
|
+
const whLng: number = -77.0387238;
|
|
37
|
+
|
|
38
|
+
const senLat: number = 38.8956636;
|
|
39
|
+
const senLng: number = -77.0269061;
|
|
40
|
+
|
|
41
|
+
let result: number = GeolocationRatchet.distanceBetweenLocations(whLat, whLng, senLat, senLng);
|
|
42
|
+
|
|
43
|
+
result = parseFloat(result.toFixed(4));
|
|
44
|
+
expect(result).toEqual(.6506);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should generate the right offset', function() {
|
|
48
|
+
const lat: number = 37.26383;
|
|
49
|
+
const miles: number = 1;
|
|
50
|
+
|
|
51
|
+
const result1: number = GeolocationRatchet.degreeOfLatLngInMiles(lat);
|
|
52
|
+
expect(result1).toEqual(55.0509);
|
|
53
|
+
|
|
54
|
+
const result2: number = GeolocationRatchet.degreeOfLatLngInMiles(0);
|
|
55
|
+
expect(result2).toEqual(69.172);
|
|
56
|
+
|
|
57
|
+
const result3: number = GeolocationRatchet.degreeOfLatLngInMiles(lat*-1);
|
|
58
|
+
expect(result3).toEqual(55.0509);
|
|
59
|
+
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
test('should generate the right offset', function() {
|
|
64
|
+
const lat: number = 37.26383;
|
|
65
|
+
const miles: number = 1;
|
|
66
|
+
|
|
67
|
+
const result1: number = GeolocationRatchet.milesInDegLatLng(miles, lat);
|
|
68
|
+
expect(result1).toEqual(1/55.0509);
|
|
69
|
+
|
|
70
|
+
const result2: number = GeolocationRatchet.milesInDegLatLng(miles, 0);
|
|
71
|
+
expect(result2).toEqual(1/69.172);
|
|
72
|
+
|
|
73
|
+
const result3: number = GeolocationRatchet.milesInDegLatLng(miles, lat*-1);
|
|
74
|
+
expect(result3).toEqual(1/55.0509);
|
|
75
|
+
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
test('should cluster', function() {
|
|
80
|
+
const locations: RatchetGeoLocation[] = fs.readFileSync('test/data/sample_geo_locations.csv').toString()
|
|
81
|
+
.split('\n').map(line => {
|
|
82
|
+
const vals: string[] = line.split(',');
|
|
83
|
+
if (!!vals && vals.length === 2) {
|
|
84
|
+
return {
|
|
85
|
+
lat: NumberRatchet.safeNumber(vals[0]),
|
|
86
|
+
lng: NumberRatchet.safeNumber(vals[1])
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}).filter(s => !!s);
|
|
92
|
+
const bounds: RatchetLocationBounds[] = locations.map(l => GeolocationRatchet.locationToBounds(l, 10));
|
|
93
|
+
// Logger.info('Got: %j', locations);
|
|
94
|
+
const reduced: RatchetLocationBounds[] = GeolocationRatchet.clusterGeoBounds(bounds, 2, 5);
|
|
95
|
+
|
|
96
|
+
expect(reduced.length).toBeLessThanOrEqual(10);
|
|
97
|
+
|
|
98
|
+
Logger.info('Reduced %d to %d', bounds.length, reduced.length);
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
test('should build a bounds map', function() {
|
|
108
|
+
const locations: RatchetGeoLocation[] = fs.readFileSync('test/data/sample_geo_locations.csv').toString()
|
|
109
|
+
.split('\n').map(line => {
|
|
110
|
+
const vals: string[] = line.split(',');
|
|
111
|
+
if (!!vals && vals.length === 2) {
|
|
112
|
+
return {
|
|
113
|
+
lat: NumberRatchet.safeNumber(vals[0]),
|
|
114
|
+
lng: NumberRatchet.safeNumber(vals[1])
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}).filter(s => !!s);
|
|
120
|
+
const bounds: RatchetLocationBounds[] = locations.map(l => GeolocationRatchet.locationToBounds(l, 10));
|
|
121
|
+
const mapping: RatchetLocationBoundsMap = GeolocationRatchet.buildRatchetLocationBoundsMap(bounds);
|
|
122
|
+
|
|
123
|
+
expect(mapping).toBeTruthy();
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
});
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
test('should calc point in bounds', function () {
|
|
130
|
+
const input: string = fs
|
|
131
|
+
.readFileSync(path.join(EsmRatchet.fetchDirName(import.meta.url), '../../../../test-data/sample_geo_locations.csv'))
|
|
132
|
+
.toString();
|
|
133
|
+
|
|
134
|
+
const locations: RatchetGeoLocation[] = input
|
|
135
|
+
.split('\n')
|
|
136
|
+
.map((line) => {
|
|
137
|
+
const vals: string[] = line.split(',');
|
|
138
|
+
if (!!vals && vals.length === 2) {
|
|
139
|
+
return {
|
|
140
|
+
lat: NumberRatchet.safeNumber(vals[0]),
|
|
141
|
+
lng: NumberRatchet.safeNumber(vals[1]),
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.filter((s) => !!s);
|
|
148
|
+
const bounds: RatchetLocationBounds[] = locations.map((l) => GeolocationRatchet.locationToBounds(l, 10));
|
|
149
|
+
const mapping: RatchetLocationBoundsMap = GeolocationRatchet.buildRatchetLocationBoundsMap(bounds);
|
|
150
|
+
const testPoint1: RatchetGeoLocation = locations[100];
|
|
151
|
+
const testPoint2: RatchetGeoLocation = { lng: 5, lat: 5 };
|
|
152
|
+
const testPoint3: RatchetGeoLocation = {
|
|
153
|
+
lat: 40.7566,
|
|
154
|
+
lng: -73.9887,
|
|
155
|
+
};
|
|
156
|
+
const testPoint4: RatchetGeoLocation = {
|
|
157
|
+
lat: 33.74616,
|
|
158
|
+
lng: -84.3708,
|
|
159
|
+
};
|
|
160
|
+
const testPoint5: RatchetGeoLocation = {
|
|
161
|
+
lat: 37.790336,
|
|
162
|
+
lng: -122.405399,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const pt1In: boolean = GeolocationRatchet.pointInAnyBound(testPoint1, bounds);
|
|
166
|
+
const pt2In: boolean = GeolocationRatchet.pointInAnyBound(testPoint2, bounds);
|
|
167
|
+
const pt3In: boolean = GeolocationRatchet.pointInAnyBound(testPoint3, bounds);
|
|
168
|
+
const pt4In: boolean = GeolocationRatchet.pointInAnyBound(testPoint4, bounds);
|
|
169
|
+
const pt5In: boolean = GeolocationRatchet.pointInRatchetLocationBoundsMap(testPoint5, mapping);
|
|
170
|
+
|
|
171
|
+
expect(pt1In).toBeTruthy();
|
|
172
|
+
expect(pt2In).toBeFalsy();
|
|
173
|
+
expect(pt3In).toBeTruthy();
|
|
174
|
+
expect(pt4In).toBeTruthy();
|
|
175
|
+
expect(pt5In).toBeTruthy();
|
|
176
|
+
});
|
|
177
|
+
});
|