@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,19 @@
1
+ /*
2
+ From https://graphicmaths.com/pure/matrices/matrix-2d-transformations/
3
+
4
+ Matrix is the form:
5
+
6
+ a b u
7
+ c d v
8
+ 0 0 1
9
+
10
+ */
11
+
12
+ export interface TransformationMatrix {
13
+ a: number;
14
+ b: number;
15
+ c: number;
16
+ d: number;
17
+ u?: number;
18
+ v?: number;
19
+ }
@@ -0,0 +1,8 @@
1
+ export interface BuildInformation {
2
+ version?: string;
3
+ hash?: string;
4
+ branch?: string;
5
+ tag?: string;
6
+ timeBuiltISO?: string;
7
+ notes?: string;
8
+ }
@@ -0,0 +1,19 @@
1
+ import { BuildInformation } from './build-information.js';
2
+
3
+ export class RatchetInfo {
4
+ // Prevent instantiation
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
6
+ private constructor() {}
7
+
8
+ public static buildInformation(): BuildInformation {
9
+ const val: BuildInformation = {
10
+ version: 'LOCAL-SNAPSHOT',
11
+ hash: 'LOCAL-HASH',
12
+ branch: 'LOCAL-BRANCH',
13
+ tag: 'LOCAL-TAG',
14
+ timeBuiltISO: 'LOCAL-TIME-ISO',
15
+ notes: 'LOCAL-NOTES',
16
+ };
17
+ return val;
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ export interface HistogramEntry<T> {
2
+ item: T;
3
+ count: number;
4
+ }
@@ -0,0 +1,25 @@
1
+ import { Histogram } from './histogram.js';
2
+ import { describe, expect, test } from 'vitest';
3
+
4
+ describe('#histogram', function () {
5
+ test('should count the values correctly', function () {
6
+ const histogram: Histogram<string> = new Histogram<string>();
7
+
8
+ histogram.update('a');
9
+ histogram.update('a');
10
+ histogram.update('b');
11
+ histogram.update('c');
12
+
13
+ expect(histogram.getTotalCount()).toEqual(4);
14
+
15
+ expect(histogram.countForValue('a')).toEqual(2);
16
+ expect(histogram.countForValue('b')).toEqual(1);
17
+ expect(histogram.countForValue('c')).toEqual(1);
18
+ expect(histogram.countForValue('d')).toEqual(0);
19
+
20
+ expect(histogram.percentForValue('a')).toEqual(0.5);
21
+ expect(histogram.percentForValue('b')).toEqual(0.25);
22
+ expect(histogram.percentForValue('c')).toEqual(0.25);
23
+ expect(histogram.percentForValue('d')).toEqual(0);
24
+ });
25
+ });
@@ -0,0 +1,61 @@
1
+ import { HistogramEntry } from './histogram-entry.js';
2
+
3
+ /**
4
+ * Implements a simple histogram (for each object, how many times does it appear)
5
+ */
6
+ export class Histogram<T> {
7
+ private entries: HistogramEntry<T>[] = [];
8
+
9
+ public update(val: T, addValue = 1): void {
10
+ if (val) {
11
+ const entry: HistogramEntry<T> = this.entries.find((e) => e.item === val);
12
+ if (entry) {
13
+ entry.count += addValue;
14
+ } else {
15
+ this.entries.push({
16
+ item: val,
17
+ count: addValue,
18
+ } as HistogramEntry<T>);
19
+ }
20
+ }
21
+ }
22
+
23
+ public sort(): void {
24
+ this.entries.sort((a, b) => {
25
+ let rval: number = b.count - a.count;
26
+ if (rval === 0) {
27
+ rval = String(b.item).localeCompare(String(a.item));
28
+ }
29
+ return rval;
30
+ });
31
+ }
32
+
33
+ // No need for second level sort, since the same key can only be in there once
34
+ public sortKeys(): void {
35
+ this.entries.sort((a, b) => String(b.item).localeCompare(String(a.item)));
36
+ }
37
+
38
+ public reverse(): void {
39
+ this.entries.reverse();
40
+ }
41
+
42
+ public getEntries(): HistogramEntry<T>[] {
43
+ return this.entries;
44
+ }
45
+
46
+ public getTotalCount(): number {
47
+ let rval = 0;
48
+ this.entries.forEach((h) => (rval += h.count));
49
+ return rval;
50
+ }
51
+
52
+ public countForValue(val: T): number {
53
+ const entry: HistogramEntry<T> = this.entries.find((test) => test.item === val);
54
+ return entry ? entry.count : 0;
55
+ }
56
+
57
+ public percentForValue(val: T): number {
58
+ const total: number = this.getTotalCount();
59
+ return total === 0 ? 0 : this.countForValue(val) / total;
60
+ }
61
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This is meant to be an object that wraps up the standard JWT fields along with some other helpful fields
3
+ * in a type-safe way. This is used in my Epsilon library, although it can be easily used elsewhere.
4
+ *
5
+ * In the case of a sudo situation, use the proxy field. For example, if I am alice, but I am running as
6
+ * bob, bob should be in the user field and alice should be in the proxy field. While this may seem somewhat
7
+ * backwards, it allows the majority of code to proceed as if bob is logged in, and only code that cares that a
8
+ * proxy is going on (e.g., audit trail code) needs to even check the proxy field.
9
+ *
10
+ * Note: other interfaces can extend this token to gain more functionality
11
+ */
12
+ import { JwtTokenBase } from './jwt-token-base.js';
13
+
14
+ export interface CommonJwtToken<T> extends JwtTokenBase {
15
+ user: T; // Data for the authenticated user
16
+ proxy: T; // Data for the proxy user (if any)
17
+ }
@@ -0,0 +1,5 @@
1
+ export const enum ExpiredJwtHandling {
2
+ RETURN_NULL,
3
+ THROW_EXCEPTION,
4
+ ADD_FLAG,
5
+ }
@@ -0,0 +1,26 @@
1
+ import { jwtDecode } from 'jwt-decode';
2
+ import { ExpiredJwtHandling } from './expired-jwt-handling.js';
3
+ import { JwtTokenBase } from './jwt-token-base.js';
4
+ import { JwtPayloadExpirationRatchet } from './jwt-payload-expiration-ratchet.js';
5
+
6
+ /**
7
+ * This is a stepped-down version of the original JwtRatchet which is
8
+ * now relocated to ratchet-node-only, as it depends on crypto and
9
+ * stream libraries that aren't available in the browser (and common
10
+ * is meant to work on both the browser and node).
11
+ *
12
+ * This can decode a token, and verify expiration, but it can't check
13
+ * the signature since it lacks the libraries (and keys!) for that.
14
+ */
15
+ export class JwtDecodeOnlyRatchet {
16
+ public static decodeTokenNoVerify<T extends JwtTokenBase>(
17
+ token: string,
18
+ expiredHandling: ExpiredJwtHandling = ExpiredJwtHandling.RETURN_NULL,
19
+ inDecodeFuntion?: (val: string) => T,
20
+ ): T {
21
+ const decodeFunction: (val: string) => T = inDecodeFuntion ?? jwtDecode;
22
+ let decoded: T = decodeFunction(token);
23
+ decoded = JwtPayloadExpirationRatchet.processPayloadExpiration<T>(decoded, expiredHandling);
24
+ return decoded;
25
+ }
26
+ }
@@ -0,0 +1,45 @@
1
+ import { ExpiredJwtHandling } from './expired-jwt-handling.js';
2
+ import { JwtTokenBase } from './jwt-token-base.js';
3
+ import { Logger } from '../logger/logger.js';
4
+ import { DurationRatchet } from '../lang/duration-ratchet.js';
5
+
6
+ export class JwtPayloadExpirationRatchet {
7
+ private static readonly EXPIRED_FLAG_NAME: string = '__jwtServiceExpiredFlag';
8
+
9
+ public static processPayloadExpiration<T extends JwtTokenBase>(payload: T, expiredHandling: ExpiredJwtHandling): T {
10
+ if (payload) {
11
+ const nowSeconds: number = Math.floor(Date.now() / 1000);
12
+ // A backwards compatibility hack since some of my old code used to incorrectly write the exp field in milliseconds
13
+ const expSeconds: number = payload?.exp && payload.exp > nowSeconds * 100 ? Math.floor(payload.exp / 1000) : payload?.exp;
14
+ const nbfSeconds: number = payload?.nbf && payload.nbf > nowSeconds * 100 ? Math.floor(payload.nbf / 1000) : payload?.nbf;
15
+
16
+ if ((expSeconds && nowSeconds >= expSeconds) || (nbfSeconds && nowSeconds <= nbfSeconds)) {
17
+ // Only do this if expiration is defined
18
+ const age: number = nowSeconds - expSeconds;
19
+ Logger.debug('JWT token expired or before NBF : on %d, %s ago', payload.exp, DurationRatchet.formatMsDuration(age * 1000));
20
+ switch (expiredHandling) {
21
+ case ExpiredJwtHandling.THROW_EXCEPTION:
22
+ throw new Error('JWT Token was expired');
23
+ case ExpiredJwtHandling.ADD_FLAG:
24
+ payload[JwtPayloadExpirationRatchet.EXPIRED_FLAG_NAME] = true;
25
+ break;
26
+ default:
27
+ payload = null;
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ return payload;
33
+ }
34
+
35
+ public static hasExpiredFlag(ob: any): boolean {
36
+ return ob && ob[JwtPayloadExpirationRatchet.EXPIRED_FLAG_NAME] === true;
37
+ }
38
+
39
+ public static removeExpiredFlag(ob: any) {
40
+ if (ob) {
41
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
42
+ delete ob[JwtPayloadExpirationRatchet.EXPIRED_FLAG_NAME];
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Defines the common fields expected in a JWT token according to the RFC
3
+ * https://www.rfc-editor.org/rfc/rfc7519.html
4
+ */
5
+
6
+ export interface JwtTokenBase {
7
+ iss?: string; // Issuer
8
+ sub?: string; // Subject
9
+ aud?: string; // Audience
10
+ exp?: number; // Expiration time
11
+ nbf?: number; // Not Before
12
+ iat?: number; // Issued at (time of creation)
13
+ jti?: string; // Unique ID for the token
14
+ }
@@ -0,0 +1,79 @@
1
+ import { ArrayRatchet, MatchReport } from './array-ratchet.js';
2
+ import { describe, expect, test } from 'vitest';
3
+
4
+ const sortedArr: any[] = [
5
+ {
6
+ n: 'test1',
7
+ v: 1,
8
+ },
9
+ {
10
+ n: 'test1',
11
+ v: 2,
12
+ },
13
+ {
14
+ n: 'test1',
15
+ v: 4,
16
+ },
17
+ {
18
+ n: 'test1',
19
+ v: 4,
20
+ },
21
+ {
22
+ n: 'test1',
23
+ v: 7,
24
+ },
25
+ {
26
+ n: 'test1',
27
+ v: 12,
28
+ },
29
+ {
30
+ n: 'test1',
31
+ v: 25,
32
+ },
33
+ {
34
+ n: 'test1',
35
+ v: 30,
36
+ },
37
+ ];
38
+
39
+ describe('#extractSubarrayFromSortedByNumberField', function () {
40
+ test('should extract the subarray', function () {
41
+ const subArr1: any[] = ArrayRatchet.extractSubarrayFromSortedByNumberField(sortedArr, 'v', 5, 14);
42
+ expect(subArr1.length).toEqual(2);
43
+
44
+ const subArr2: any[] = ArrayRatchet.extractSubarrayFromSortedByNumberField(sortedArr, 'v', null, 4);
45
+ expect(subArr2.length).toEqual(4);
46
+
47
+ const subArr3: any[] = ArrayRatchet.extractSubarrayFromSortedByNumberField(sortedArr, 'v', 7, null);
48
+ expect(subArr3.length).toEqual(4);
49
+ });
50
+ });
51
+
52
+ describe('#compareTwoArrays', function () {
53
+ test('should create a match report', function () {
54
+ const arr1: string[] = ['a', 'b', 'c'];
55
+ const arr2: string[] = ['a', 'e', 'i', 'o', 'u'];
56
+
57
+ const report: MatchReport<string> = ArrayRatchet.compareTwoArrays(arr1, arr2, (a, b) => a.localeCompare(b));
58
+
59
+ expect(report).toBeTruthy();
60
+ expect(report.setOneOnly.length).toEqual(2);
61
+ expect(report.setTwoOnly.length).toEqual(4);
62
+ expect(report.matching.length).toEqual(1);
63
+ });
64
+ });
65
+
66
+ describe('#findSplit', function () {
67
+ test('should find the split', function () {
68
+ // All values past the split point should be larger than the target
69
+ const split10: number = ArrayRatchet.findSplit(sortedArr, 'v', 10);
70
+ const split4: number = ArrayRatchet.findSplit(sortedArr, 'v', 4);
71
+ const split0: number = ArrayRatchet.findSplit(sortedArr, 'v', 0);
72
+ const split32: number = ArrayRatchet.findSplit(sortedArr, 'v', 32);
73
+
74
+ expect(split10).toEqual(4);
75
+ expect(split4).toEqual(3);
76
+ expect(split0).toEqual(null);
77
+ expect(split32).toEqual(7);
78
+ });
79
+ });
@@ -0,0 +1,141 @@
1
+ /*
2
+ Functions for working with arrays
3
+ */
4
+
5
+ import { RequireRatchet } from './require-ratchet.js';
6
+ import { MapRatchet } from './map-ratchet.js';
7
+
8
+ export class ArrayRatchet {
9
+ public static wrapElementsInArray(input: any[]): any[][] {
10
+ return input.map((i) => [i]);
11
+ }
12
+
13
+ public static compareTwoArrays<T>(ar1: T[], ar2: T[], fn: ComparisonFunction<T>): MatchReport<T> {
14
+ ar1.sort(fn);
15
+ ar2.sort(fn);
16
+
17
+ let id1 = 0;
18
+ let id2 = 0;
19
+ const rval: MatchReport<T> = {
20
+ matching: [],
21
+ setOneOnly: [],
22
+ setTwoOnly: [],
23
+ };
24
+
25
+ while (id1 < ar1.length && id2 < ar2.length) {
26
+ const aVal: T = ar1[id1];
27
+ const pVal: T = ar2[id2];
28
+ const comp: number = fn(aVal, pVal);
29
+
30
+ if (comp === 0) {
31
+ rval.matching.push(aVal);
32
+ id1++;
33
+ id2++;
34
+ } else if (comp < 0) {
35
+ rval.setOneOnly.push(aVal);
36
+ id1++;
37
+ } else {
38
+ rval.setTwoOnly.push(pVal);
39
+ id2++;
40
+ }
41
+ }
42
+
43
+ if (id1 < ar1.length - 1) {
44
+ rval.setOneOnly = rval.setOneOnly.concat(ar1.slice(id1));
45
+ }
46
+ if (id2 < ar2.length - 1) {
47
+ rval.setTwoOnly = rval.setTwoOnly.concat(ar2.slice(id2));
48
+ }
49
+
50
+ return rval;
51
+ }
52
+
53
+ /**
54
+ * Given a sorted array of type T with a field named fieldName of type R, perform
55
+ * binary search to find the top and bottom bounds and extract the result
56
+ * @param input Array to select from
57
+ * @param fieldDotPath Path of the field
58
+ * @param minInclusive min value of field to include
59
+ * @param maxExclusive max value of field to include
60
+ */
61
+ public static extractSubarrayFromSortedByNumberField<T>(
62
+ input: T[],
63
+ fieldDotPath: string,
64
+ minInclusive: number,
65
+ maxExclusive: number,
66
+ ): T[] {
67
+ if (!input || input.length === 0) {
68
+ return input;
69
+ }
70
+
71
+ let bottomIdx: number = minInclusive === null ? 0 : ArrayRatchet.findSplit(input, fieldDotPath, minInclusive) || 0;
72
+ const topIdx: number = maxExclusive === null ? input.length : ArrayRatchet.findSplit(input, fieldDotPath, maxExclusive);
73
+
74
+ const bottomValue: number = MapRatchet.findValueDotPath(input[bottomIdx], fieldDotPath);
75
+ if (bottomIdx === input.length - 1 && bottomValue < minInclusive) {
76
+ // Highest value is still larger than min
77
+ return [];
78
+ }
79
+
80
+ // For min inclusive, have to handle this case
81
+ // Since it has to be able to handle the value===min case
82
+ if (bottomIdx < input.length && bottomIdx < topIdx && bottomValue < minInclusive) {
83
+ bottomIdx++;
84
+ }
85
+
86
+ return input.slice(bottomIdx, topIdx + 1);
87
+ }
88
+
89
+ /**
90
+ * Given a sorted array, find the location in the array where all the entries
91
+ * above that index have a value larger that the target (the index and under
92
+ * are less than or equal to the target)
93
+ * @param input Array to select from
94
+ * @param fieldName Name of the field
95
+ * @param target the value to search for
96
+ */
97
+ public static findSplit(input: any[], fieldDotPath: string, target: number): number {
98
+ RequireRatchet.notNullOrUndefined(input);
99
+ RequireRatchet.notNullOrUndefined(fieldDotPath);
100
+ RequireRatchet.notNullOrUndefined(target);
101
+
102
+ if (input.length === 0 || MapRatchet.findValueDotPath(input[0], fieldDotPath) > target) {
103
+ return null;
104
+ }
105
+
106
+ let min = 0;
107
+ let max: number = input.length;
108
+ let rval: number = null;
109
+
110
+ while (rval === null) {
111
+ const curIdx: number = Math.floor((min + max) / 2);
112
+ const curVal: number = MapRatchet.findValueDotPath(input[curIdx], fieldDotPath);
113
+ if (min === max || min === max - 1) {
114
+ rval = min;
115
+ } else if (curVal <= target) {
116
+ min = curIdx;
117
+ } else {
118
+ max = curIdx;
119
+ }
120
+ }
121
+ return rval;
122
+ }
123
+
124
+ public static shuffleInPlace(array: any[]): void {
125
+ if (array?.length) {
126
+ // Ignore nulls and non-arrays
127
+ for (let i = array.length - 1; i >= 0; i--) {
128
+ const j = Math.floor(Math.random() * (i + 1));
129
+ [array[i], array[j]] = [array[j], array[i]];
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ export type ComparisonFunction<T> = (t1: T, t2: T) => number;
136
+
137
+ export interface MatchReport<T> {
138
+ matching: T[];
139
+ setOneOnly: T[];
140
+ setTwoOnly: T[];
141
+ }
@@ -0,0 +1,48 @@
1
+ import { Base64Ratchet } from './base64-ratchet.js';
2
+ import { describe, expect, test } from 'vitest';
3
+
4
+ describe('#base64', function () {
5
+ test('should parse a Uint8Array from base64', function () {
6
+ const result: Uint8Array = Base64Ratchet.base64StringToUint8Array('dGVzdHVzZXI6dGVzdHBhc3M=');
7
+ expect(result).toBeTruthy();
8
+ expect(result.length).toEqual(17);
9
+ });
10
+
11
+ test('should parse a string from base64', function () {
12
+ const result: string = Base64Ratchet.base64StringToString('dGVzdHVzZXI6dGVzdHBhc3M=');
13
+ expect(result).toBeTruthy();
14
+ expect(result.length).toEqual(17);
15
+ });
16
+
17
+ test('should round-trip a string', function () {
18
+ const testString = 'teststring';
19
+ const enc: string = Base64Ratchet.generateBase64VersionOfString(testString);
20
+ const result: string = Base64Ratchet.base64StringToString(enc);
21
+ expect(result).toBeTruthy();
22
+ expect(result).toEqual(testString);
23
+ });
24
+
25
+ test('should round-trip an object', function () {
26
+ const testOb: any = { a: 'teststring', b: 27 };
27
+ const enc: string = Base64Ratchet.safeObjectToBase64JSON(testOb);
28
+ const result: any = Base64Ratchet.safeBase64JSONParse(enc);
29
+ expect(result).toBeTruthy();
30
+ expect(result.a).toEqual(testOb.a);
31
+ expect(result.b).toEqual(testOb.b);
32
+ });
33
+
34
+ test('should work on special characters', function () {
35
+ const src = '✓ à la mode';
36
+ const b64: string = Base64Ratchet.generateBase64VersionOfString(src);
37
+ const back: string = Base64Ratchet.base64StringToString(b64);
38
+ expect(b64).toEqual('4pyTIMOgIGxhIG1vZGU=');
39
+ expect(back).toEqual(src);
40
+ });
41
+
42
+ test('should work on arbitrary data', function () {
43
+ const srcB64 = '7zo_JDdubAcOMnLtkoth_rLRc6Zj5RKRpNGv_nTVYY4';
44
+ const back: Uint8Array = Base64Ratchet.base64UrlStringToBytes(srcB64);
45
+ expect(back).toBeTruthy();
46
+ expect(back.length).toEqual(32);
47
+ });
48
+ });