@magmacomputing/tempo 1.0.0

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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/array.library.d.ts +25 -0
  4. package/dist/array.library.js +78 -0
  5. package/dist/buffer.library.d.ts +6 -0
  6. package/dist/buffer.library.js +192 -0
  7. package/dist/cipher.class.d.ts +18 -0
  8. package/dist/cipher.class.js +65 -0
  9. package/dist/class.library.d.ts +10 -0
  10. package/dist/class.library.js +57 -0
  11. package/dist/coercion.library.d.ts +14 -0
  12. package/dist/coercion.library.js +71 -0
  13. package/dist/enumerate.library.d.ts +64 -0
  14. package/dist/enumerate.library.js +54 -0
  15. package/dist/function.library.d.ts +9 -0
  16. package/dist/function.library.js +49 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +3 -0
  19. package/dist/logify.class.d.ts +33 -0
  20. package/dist/logify.class.js +53 -0
  21. package/dist/number.library.d.ts +16 -0
  22. package/dist/number.library.js +36 -0
  23. package/dist/object.library.d.ts +37 -0
  24. package/dist/object.library.js +94 -0
  25. package/dist/pledge.class.d.ts +54 -0
  26. package/dist/pledge.class.js +131 -0
  27. package/dist/prototype.library.d.ts +33 -0
  28. package/dist/prototype.library.js +51 -0
  29. package/dist/reflection.library.d.ts +74 -0
  30. package/dist/reflection.library.js +97 -0
  31. package/dist/serialize.library.d.ts +25 -0
  32. package/dist/serialize.library.js +266 -0
  33. package/dist/storage.library.d.ts +8 -0
  34. package/dist/storage.library.js +57 -0
  35. package/dist/string.library.d.ts +37 -0
  36. package/dist/string.library.js +93 -0
  37. package/dist/tempo.class.d.ts +556 -0
  38. package/dist/tempo.class.js +1412 -0
  39. package/dist/tempo.config/plugins/term.import.d.ts +42 -0
  40. package/dist/tempo.config/plugins/term.import.js +44 -0
  41. package/dist/tempo.config/plugins/term.quarter.d.ts +7 -0
  42. package/dist/tempo.config/plugins/term.quarter.js +28 -0
  43. package/dist/tempo.config/plugins/term.season.d.ts +7 -0
  44. package/dist/tempo.config/plugins/term.season.js +36 -0
  45. package/dist/tempo.config/plugins/term.timeline.d.ts +7 -0
  46. package/dist/tempo.config/plugins/term.timeline.js +19 -0
  47. package/dist/tempo.config/plugins/term.utils.d.ts +17 -0
  48. package/dist/tempo.config/plugins/term.utils.js +38 -0
  49. package/dist/tempo.config/plugins/term.zodiac.d.ts +7 -0
  50. package/dist/tempo.config/plugins/term.zodiac.js +62 -0
  51. package/dist/tempo.config/tempo.default.d.ts +169 -0
  52. package/dist/tempo.config/tempo.default.js +158 -0
  53. package/dist/tempo.config/tempo.enum.d.ts +99 -0
  54. package/dist/tempo.config/tempo.enum.js +78 -0
  55. package/dist/temporal.polyfill.d.ts +9 -0
  56. package/dist/temporal.polyfill.js +18 -0
  57. package/dist/type.library.d.ts +296 -0
  58. package/dist/type.library.js +80 -0
  59. package/dist/utility.library.d.ts +32 -0
  60. package/dist/utility.library.js +54 -0
  61. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Magma Computing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # <img src="./img/hourglass-svgrepo-com.svg" width="100px"> <span style="font-size:4em;">Tempo</span>
2
+
3
+ **Tempo** is a premium, high-performance wrapper around the JavaScript `Temporal` API, providing a fluent and intuitive interface for date-time manipulation and flexible parsing.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Temporal](https://img.shields.io/badge/Temporal-Stage%203-blue)](https://tc39.es/proposal-temporal/)
7
+
8
+ ## 🚀 Overview
9
+
10
+ Working with dates in JavaScript has historically been painful. The new `Temporal` proposal (Stage 3) fixes this, but it can be verbose and strict when parsing strings.
11
+
12
+ **Tempo** bridges that gap by providing:
13
+ - **Flexible Parsing**: Interprets almost any date string, including relative ones like "next Friday".
14
+ - **Fluent API**: Chainable methods for adding, subtracting, and setting date-times.
15
+ - **Built-in Plugins**: Access complex date ranges (Quarters, Seasons, Fiscal Years) easily.
16
+ - **Immutable**: Every operation returns a new `Tempo` instance, ensuring thread safety and predictability.
17
+
18
+ ## 📦 Installation
19
+
20
+ ```bash
21
+ npm install @magmacomputing/tempo
22
+ ```
23
+
24
+ ## 🛠️ Quick Start
25
+
26
+ ```javascript
27
+ import { Tempo } from '@magmacomputing/tempo';
28
+
29
+ // Instantiate
30
+ const now = new Tempo();
31
+ const birthday = new Tempo('20-May-1990');
32
+ const nextWeek = new Tempo('next Monday');
33
+
34
+ // Manipulate
35
+ const later = now.add({ days: 3, hours: 2 });
36
+ const startOfMonth = now.set({ start: 'month' });
37
+
38
+ // Format
39
+ console.log(now.format('{dd} {mmm} {yyyy}')); // using custom format with tokens: "24 Jan 2026"
40
+ console.log(now.fmt.date); // using pre-built formats: "2026-01-24"
41
+ ```
42
+
43
+ ## 📚 Documentation
44
+
45
+ For detailed technical guides, please refer to:
46
+ - [Tempo Class Documentation](./doc/Tempo.md)
47
+ - [Parsing Engine](./doc/Tempo.md#parsing)
48
+ - [Formatting Specs](./doc/Tempo.md#formatting)
49
+ - [Plugin System (Terms)](./doc/Tempo.md#plugins-terms)
50
+
51
+ ## 💬 Contact & Support
52
+
53
+ If you have a question, find a bug, or want to suggest a new feature:
54
+
55
+ 1. **Bug Reports & Features**: Please open an [Issue](https://github.com/magmacomputing/tempo/issues).
56
+ 2. **Questions & Ideas**: Start a thread in [Discussions](https://github.com/magmacomputing/tempo/discussions).
57
+ 3. **Direct Contact**: You can reach me at `hello@magmacomputing.com`.
58
+
59
+ ## ⚖️ License
60
+
61
+ Distributed under the MIT License. See `LICENSE` for more information.
@@ -0,0 +1,25 @@
1
+ import type { Property } from './type.library.js';
2
+ /** Coerce {value} into {value[]} ( if not already ), with optional {fill} Object */
3
+ export { asArray } from './coercion.library.js';
4
+ /** insert a value into an Array by its sorted position */
5
+ export declare const sortInsert: <T, K extends keyof T>(arr: T[] | undefined, val: T, key?: K) => T[];
6
+ /** sort Array-of-Objects by multiple keys */
7
+ export interface SortBy {
8
+ field: string;
9
+ dir?: 'asc' | 'desc';
10
+ index?: number | '*';
11
+ default?: any;
12
+ }
13
+ /** provide a sort-function to order a set of keys */
14
+ export declare function sortBy<T extends Property<T>>(...keys: (PropertyKey | SortBy)[]): (left: T, right: T) => 0 | 1 | -1;
15
+ /** return an array sorted-by a series of keys */
16
+ export declare function sortKey<T extends Property<any>>(array: T[], ...keys: (PropertyKey | SortBy)[]): T[];
17
+ type GroupFn<T extends Property<T>> = (value: T, index?: number) => PropertyKey;
18
+ /** group array of objects by the return value of the passed callback. */
19
+ export declare function byKey<T extends Property<any>>(arr: T[], grpFn: GroupFn<T>): Record<PropertyKey, T[]>;
20
+ /** group array of objects according to a list of key fields. */
21
+ export declare function byKey<T extends Property<any>>(arr: T[], ...keys: (keyof T)[]): Record<PropertyKey, T[]>;
22
+ /** group array of objects by the return value of the passed callback, but only the 'last' entry */
23
+ export declare function byLkp<T extends Property<any>>(arr: T[], grpFn: GroupFn<T>): Record<PropertyKey, T>;
24
+ /** group array of objects according to a list of key fields, but only the 'last' entry */
25
+ export declare function byLkp<T extends Property<any>>(arr: T[], ...keys: (keyof T)[]): Record<keyof T, T>;
@@ -0,0 +1,78 @@
1
+ import { asString } from './coercion.library.js';
2
+ import { extract } from './object.library.js';
3
+ import { ownEntries } from './reflection.library.js';
4
+ import { stringify } from './serialize.library.js';
5
+ import { isNumber, isDate, isTempo, isObject, isDefined, isUndefined, isFunction, nullToValue } from './type.library.js';
6
+ /** Coerce {value} into {value[]} ( if not already ), with optional {fill} Object */
7
+ export { asArray } from './coercion.library.js';
8
+ // adapted from https://jsbin.com/insert/4/edit?js,output
9
+ /** insert a value into an Array by its sorted position */
10
+ export const sortInsert = (arr = [], val, key) => {
11
+ const obj = isObject(val) && isDefined(key); // array of Objects
12
+ let low = 0, high = arr.length;
13
+ while (low < high) {
14
+ const mid = (low + high) >>> 1; // divide by 2
15
+ const source = obj
16
+ ? arr[mid][key] // array of Object values
17
+ : arr[mid]; // assume Primitive values
18
+ const target = obj
19
+ ? val[key]
20
+ : val;
21
+ if (source < target)
22
+ low = mid + 1;
23
+ else
24
+ high = mid;
25
+ }
26
+ arr.splice(low, 0, val); // mutate original Array
27
+ return arr;
28
+ };
29
+ /** provide a sort-function to order a set of keys */
30
+ export function sortBy(...keys) {
31
+ const sortOptions = keys // coerce string => SortBy
32
+ .flat() // flatten Array-of-Array
33
+ .map(key => isObject(key) ? key : { field: stringify(key) }); // build Array of sort-options
34
+ return (left, right) => {
35
+ let result = 0; // 0 = same, -1 = left<right, +1 = left>right
36
+ sortOptions.forEach(key => {
37
+ if (result === 0) { // no need to look further if result !== 0
38
+ const dir = key.dir === 'desc' ? -1 : 1;
39
+ const field = key.field + (key.index ? `[${key.index}]` : '');
40
+ const valueA = extract(left, field, nullToValue(key.default, 0));
41
+ const valueB = extract(right, field, nullToValue(key.default, 0));
42
+ switch (true) {
43
+ case isNumber(valueA) && isNumber(valueB):
44
+ case isDate(valueA) && isDate(valueB):
45
+ case isTempo(valueA) && isTempo(valueB):
46
+ result = dir * (valueA - valueB);
47
+ break;
48
+ default:
49
+ result = dir * asString(valueA)?.localeCompare(asString(valueB));
50
+ break;
51
+ }
52
+ }
53
+ });
54
+ return result;
55
+ };
56
+ }
57
+ /** return an array sorted-by a series of keys */
58
+ export function sortKey(array, ...keys) {
59
+ return array.sort(sortBy(...keys));
60
+ }
61
+ export function byKey(arr, fnKey, ...keys) {
62
+ if (isFunction(fnKey))
63
+ return Object.groupBy(arr, fnKey);
64
+ const keyed = [fnKey] // mapFn is a keyof T
65
+ .concat(keys) // append any trailing keyof T[]
66
+ .flat(); // flatten Array-of-Array
67
+ return Object.groupBy(arr, itm => // group an array into an object with named keys
68
+ keyed
69
+ .map(key => isUndefined(itm[key]) ? '' : stringify(itm[key]))
70
+ .join('.'));
71
+ }
72
+ export function byLkp(arr, fnKey, ...keys) {
73
+ const group = isFunction(fnKey)
74
+ ? byKey(arr, fnKey) // group by the callback function
75
+ : byKey(arr, fnKey, ...keys); // group by the list of keys
76
+ return ownEntries(group)
77
+ .reduce((acc, [key, grp]) => Object.assign(acc, { [key]: grp?.pop() }), {});
78
+ }
@@ -0,0 +1,6 @@
1
+ export declare function b64ToUint6(nChr: number): number;
2
+ export declare function base64DecToArr(sBase64: string, nBlocksSize?: number): Uint8Array<ArrayBuffer>;
3
+ export declare function uint6ToB64(nUint6: number): number;
4
+ export declare function base64EncArr(aBytes: Uint8Array): string;
5
+ export declare function UTF8ArrToStr(aBytes: Uint8Array): string;
6
+ export declare function strToUTF8Arr(sDOMStr: string): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ // https://developer.mozilla.org/en-US/docs/Glossary/Base64
3
+ // Array of bytes to Base64 string decoding
4
+ export function b64ToUint6(nChr) {
5
+ return nChr > 64 && nChr < 91
6
+ ? nChr - 65
7
+ : nChr > 96 && nChr < 123
8
+ ? nChr - 71
9
+ : nChr > 47 && nChr < 58
10
+ ? nChr + 4
11
+ : nChr === 43
12
+ ? 62
13
+ : nChr === 47
14
+ ? 63
15
+ : 0;
16
+ }
17
+ export function base64DecToArr(sBase64, nBlocksSize) {
18
+ const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
19
+ const nInLen = sB64Enc.length;
20
+ const nOutLen = nBlocksSize
21
+ ? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
22
+ : (nInLen * 3 + 1) >> 2;
23
+ const taBytes = new Uint8Array(nOutLen);
24
+ let nMod3;
25
+ let nMod4;
26
+ let nUint24 = 0;
27
+ let nOutIdx = 0;
28
+ for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
29
+ nMod4 = nInIdx & 3;
30
+ nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
31
+ if (nMod4 === 3 || nInLen - nInIdx === 1) {
32
+ nMod3 = 0;
33
+ while (nMod3 < 3 && nOutIdx < nOutLen) {
34
+ taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
35
+ nMod3++;
36
+ nOutIdx++;
37
+ }
38
+ nUint24 = 0;
39
+ }
40
+ }
41
+ return taBytes;
42
+ }
43
+ /* Base64 string to array encoding */
44
+ export function uint6ToB64(nUint6) {
45
+ return nUint6 < 26
46
+ ? nUint6 + 65
47
+ : nUint6 < 52
48
+ ? nUint6 + 71
49
+ : nUint6 < 62
50
+ ? nUint6 - 4
51
+ : nUint6 === 62
52
+ ? 43
53
+ : nUint6 === 63
54
+ ? 47
55
+ : 65;
56
+ }
57
+ export function base64EncArr(aBytes) {
58
+ let nMod3 = 2;
59
+ let sB64Enc = "";
60
+ const nLen = aBytes.length;
61
+ let nUint24 = 0;
62
+ for (let nIdx = 0; nIdx < nLen; nIdx++) {
63
+ nMod3 = nIdx % 3;
64
+ if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
65
+ sB64Enc += "\r\n";
66
+ }
67
+ nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
68
+ if (nMod3 === 2 || aBytes.length - nIdx === 1) {
69
+ sB64Enc += String.fromCodePoint(uint6ToB64((nUint24 >>> 18) & 63), uint6ToB64((nUint24 >>> 12) & 63), uint6ToB64((nUint24 >>> 6) & 63), uint6ToB64(nUint24 & 63));
70
+ nUint24 = 0;
71
+ }
72
+ }
73
+ return (sB64Enc.substring(0, sB64Enc.length - 2 + nMod3) +
74
+ (nMod3 === 2 ? "" : nMod3 === 1 ? "=" : "=="));
75
+ }
76
+ /* UTF-8 array to JS string and vice versa */
77
+ export function UTF8ArrToStr(aBytes) {
78
+ let sView = "";
79
+ let nPart;
80
+ const nLen = aBytes.length;
81
+ for (let nIdx = 0; nIdx < nLen; nIdx++) {
82
+ nPart = aBytes[nIdx];
83
+ sView += String.fromCodePoint(nPart > 251 && nPart < 254 && nIdx + 5 < nLen /* six bytes */
84
+ ? /* (nPart - 252 << 30) may be not so safe in ECMAScript! So…: */
85
+ (nPart - 252) * 1073741824 +
86
+ ((aBytes[++nIdx] - 128) << 24) +
87
+ ((aBytes[++nIdx] - 128) << 18) +
88
+ ((aBytes[++nIdx] - 128) << 12) +
89
+ ((aBytes[++nIdx] - 128) << 6) +
90
+ aBytes[++nIdx] -
91
+ 128
92
+ : nPart > 247 && nPart < 252 && nIdx + 4 < nLen /* five bytes */
93
+ ? ((nPart - 248) << 24) +
94
+ ((aBytes[++nIdx] - 128) << 18) +
95
+ ((aBytes[++nIdx] - 128) << 12) +
96
+ ((aBytes[++nIdx] - 128) << 6) +
97
+ aBytes[++nIdx] -
98
+ 128
99
+ : nPart > 239 && nPart < 248 && nIdx + 3 < nLen /* four bytes */
100
+ ? ((nPart - 240) << 18) +
101
+ ((aBytes[++nIdx] - 128) << 12) +
102
+ ((aBytes[++nIdx] - 128) << 6) +
103
+ aBytes[++nIdx] -
104
+ 128
105
+ : nPart > 223 && nPart < 240 && nIdx + 2 < nLen /* three bytes */
106
+ ? ((nPart - 224) << 12) +
107
+ ((aBytes[++nIdx] - 128) << 6) +
108
+ aBytes[++nIdx] -
109
+ 128
110
+ : nPart > 191 && nPart < 224 && nIdx + 1 < nLen /* two bytes */
111
+ ? ((nPart - 192) << 6) + aBytes[++nIdx] - 128
112
+ : /* nPart < 127 ? */ /* one byte */
113
+ nPart);
114
+ }
115
+ return sView;
116
+ }
117
+ export function strToUTF8Arr(sDOMStr) {
118
+ let aBytes;
119
+ let nChr;
120
+ const nStrLen = sDOMStr.length;
121
+ let nArrLen = 0;
122
+ /* mapping… */
123
+ for (let nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) {
124
+ nChr = sDOMStr.codePointAt(nMapIdx);
125
+ if (nChr > 65536) {
126
+ nMapIdx++;
127
+ }
128
+ nArrLen +=
129
+ nChr < 0x80
130
+ ? 1
131
+ : nChr < 0x800
132
+ ? 2
133
+ : nChr < 0x10000
134
+ ? 3
135
+ : nChr < 0x200000
136
+ ? 4
137
+ : nChr < 0x4000000
138
+ ? 5
139
+ : 6;
140
+ }
141
+ aBytes = new Uint8Array(nArrLen);
142
+ /* transcription… */
143
+ let nIdx = 0;
144
+ let nChrIdx = 0;
145
+ while (nIdx < nArrLen) {
146
+ nChr = sDOMStr.codePointAt(nChrIdx);
147
+ if (nChr < 128) {
148
+ /* one byte */
149
+ aBytes[nIdx++] = nChr;
150
+ }
151
+ else if (nChr < 0x800) {
152
+ /* two bytes */
153
+ aBytes[nIdx++] = 192 + (nChr >>> 6);
154
+ aBytes[nIdx++] = 128 + (nChr & 63);
155
+ }
156
+ else if (nChr < 0x10000) {
157
+ /* three bytes */
158
+ aBytes[nIdx++] = 224 + (nChr >>> 12);
159
+ aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
160
+ aBytes[nIdx++] = 128 + (nChr & 63);
161
+ }
162
+ else if (nChr < 0x200000) {
163
+ /* four bytes */
164
+ aBytes[nIdx++] = 240 + (nChr >>> 18);
165
+ aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
166
+ aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
167
+ aBytes[nIdx++] = 128 + (nChr & 63);
168
+ nChrIdx++;
169
+ }
170
+ else if (nChr < 0x4000000) {
171
+ /* five bytes */
172
+ aBytes[nIdx++] = 248 + (nChr >>> 24);
173
+ aBytes[nIdx++] = 128 + ((nChr >>> 18) & 63);
174
+ aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
175
+ aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
176
+ aBytes[nIdx++] = 128 + (nChr & 63);
177
+ nChrIdx++;
178
+ } /* if (nChr <= 0x7fffffff) */
179
+ else {
180
+ /* six bytes */
181
+ aBytes[nIdx++] = 252 + (nChr >>> 30);
182
+ aBytes[nIdx++] = 128 + ((nChr >>> 24) & 63);
183
+ aBytes[nIdx++] = 128 + ((nChr >>> 18) & 63);
184
+ aBytes[nIdx++] = 128 + ((nChr >>> 12) & 63);
185
+ aBytes[nIdx++] = 128 + ((nChr >>> 6) & 63);
186
+ aBytes[nIdx++] = 128 + (nChr & 63);
187
+ nChrIdx++;
188
+ }
189
+ nChrIdx++;
190
+ }
191
+ return aBytes;
192
+ }
@@ -0,0 +1,18 @@
1
+ /** Static-only cryptographic methods */
2
+ export declare class Cipher {
3
+ #private;
4
+ /** random UUID */
5
+ static randomKey: () => string;
6
+ /** decode base64 back into object */
7
+ static decodeBase64: <T>(buf?: string) => T;
8
+ /** encode object into base64 */
9
+ static encodeBase64: (buf: unknown) => string;
10
+ static hmac: (source: string | Object, secret: string, alg?: string, len?: number) => Promise<string>;
11
+ static hash: (source: string | Object, len?: number, alg?: string) => Promise<string>;
12
+ static encodeBuffer: (str: string) => Uint16Array<ArrayBuffer>;
13
+ static decodeBuffer: (buf: Uint16Array) => string;
14
+ static encrypt: (data: any) => Promise<string>;
15
+ static decrypt: (secret: Promise<ArrayBuffer>) => Promise<string>;
16
+ static sign: (doc: any) => Promise<string>;
17
+ static verify: (signature: Promise<ArrayBuffer>, doc: any) => Promise<boolean>;
18
+ }
@@ -0,0 +1,65 @@
1
+ import { toHex } from './number.library.js';
2
+ import { asString } from './string.library.js';
3
+ import { Immutable, Static } from './class.library.js';
4
+ import { stringify, objectify } from './serialize.library.js';
5
+ import { base64DecToArr, base64EncArr, strToUTF8Arr, UTF8ArrToStr } from './buffer.library.js';
6
+ const crypto = globalThis.crypto;
7
+ const subtle = crypto.subtle;
8
+ const keys = {
9
+ Algorithm: 'SHA-256',
10
+ Encoding: 'utf-8',
11
+ SignKey: 'RSASSA-PKCS1-v1_5',
12
+ TypeKey: 'AES-GCM',
13
+ };
14
+ /** Static-only cryptographic methods */
15
+ @Immutable
16
+ @Static // prevent instantiation
17
+ export class Cipher {
18
+ static #cryptoKey = subtle.generateKey({ name: keys.TypeKey, length: 128 }, false, ['encrypt', 'decrypt']);
19
+ static #vector = crypto.getRandomValues(new Uint8Array(16));
20
+ static #asymmetricKey = subtle.generateKey({
21
+ name: keys.SignKey,
22
+ modulusLength: 2048,
23
+ publicExponent: new Uint8Array([1, 0, 1]),
24
+ hash: { name: keys.Algorithm },
25
+ }, false, ['sign', 'verify']);
26
+ /** random UUID */
27
+ static randomKey = () => crypto.randomUUID().split('-')[0];
28
+ /** decode base64 back into object */
29
+ static decodeBase64 = (buf = '') => {
30
+ const uint8 = base64DecToArr(buf); // first, convert to UInt8Array
31
+ const str = UTF8ArrToStr(uint8); // convert to string
32
+ return objectify(str); // rebuild the original object
33
+ };
34
+ /** encode object into base64 */
35
+ static encodeBase64 = (buf) => {
36
+ const str = stringify(buf); // first, stringify the incoming buffer
37
+ const uint8 = strToUTF8Arr(str); // convert to Uint8Array
38
+ return base64EncArr(uint8); // convert to string
39
+ };
40
+ static hmac = async (source, secret, alg = 'SHA-512', len) => {
41
+ const encoder = new TextEncoder();
42
+ const keyData = encoder.encode(secret);
43
+ const messageData = encoder.encode(asString(source));
44
+ const key = await subtle.importKey('raw', keyData, { name: 'HMAC', hash: { name: alg } }, false, ['sign']);
45
+ const signature = await subtle.sign('HMAC', key, messageData);
46
+ return toHex(Array.from(new Uint8Array(signature)), len);
47
+ };
48
+ static hash = async (source, len, alg = 'SHA-256') => {
49
+ const buffer = Cipher.encodeBuffer(asString(source));
50
+ const hash = await subtle.digest(alg, buffer);
51
+ return toHex(Array.from(new Uint8Array(hash)), len);
52
+ };
53
+ static encodeBuffer = (str) => new Uint16Array(new TextEncoder().encode(str));
54
+ static decodeBuffer = (buf) => new TextDecoder(keys.Encoding).decode(buf);
55
+ static encrypt = async (data) => subtle.encrypt({ name: keys.TypeKey, iv: Cipher.#vector }, await Cipher.#cryptoKey, Cipher.encodeBuffer(data))
56
+ .then(result => new Uint16Array(result))
57
+ .then(Cipher.decodeBuffer);
58
+ static decrypt = async (secret) => subtle.decrypt({ name: keys.TypeKey, iv: Cipher.#vector }, await Cipher.#cryptoKey, await secret)
59
+ .then(result => new Uint16Array(result))
60
+ .then(Cipher.decodeBuffer);
61
+ static sign = async (doc) => subtle.sign(keys.SignKey, (await Cipher.#asymmetricKey).privateKey, Cipher.encodeBuffer(doc))
62
+ .then(result => new Uint16Array(result))
63
+ .then(Cipher.decodeBuffer);
64
+ static verify = async (signature, doc) => subtle.verify(keys.SignKey, (await Cipher.#asymmetricKey).publicKey, await signature, Cipher.encodeBuffer(doc));
65
+ }
@@ -0,0 +1,10 @@
1
+ import type { Constructor } from './type.library.js';
2
+ /**
3
+ * Some interesting Class Decorators
4
+ */
5
+ /** decorator to freeze a Class to prevent modification */
6
+ export declare function Immutable<T extends Constructor>(value: T, { kind, name, addInitializer }: ClassDecoratorContext<T>): T | void;
7
+ /** register a Class for serialization */
8
+ export declare function Serializable<T extends Constructor>(value: T, { kind, name, addInitializer }: ClassDecoratorContext<T>): T | void;
9
+ /** make a Class not instantiable */
10
+ export declare function Static<T extends Constructor>(value: T, { kind, name }: ClassDecoratorContext<T>): T | void;
@@ -0,0 +1,57 @@
1
+ import { Registry } from './serialize.library.js';
2
+ /**
3
+ * Some interesting Class Decorators
4
+ */
5
+ /** decorator to freeze a Class to prevent modification */
6
+ export function Immutable(value, { kind, name, addInitializer }) {
7
+ name = String(name);
8
+ switch (kind) {
9
+ case 'class':
10
+ const wrapper = {
11
+ [name]: class extends value {
12
+ constructor(...args) {
13
+ super(...args);
14
+ Object.freeze(this); // freeze the instance
15
+ }
16
+ }
17
+ }[name];
18
+ addInitializer(() => {
19
+ Object.freeze(value); // freeze the static properties
20
+ Object.freeze(value.prototype); // freeze the prototype methods
21
+ Object.freeze(wrapper); // freeze the wrapper static properties
22
+ Object.freeze(wrapper.prototype); // freeze the wrapper prototype methods
23
+ });
24
+ return wrapper;
25
+ default:
26
+ throw new Error(`@Immutable decorating unknown 'kind': ${kind} (${name})`);
27
+ }
28
+ }
29
+ /** register a Class for serialization */
30
+ export function Serializable(value, { kind, name, addInitializer }) {
31
+ name = String(name); // cast as String
32
+ switch (kind) {
33
+ case 'class':
34
+ addInitializer(() => Registry.set(`$${name}`, value)); // register the class for serialization, via its toString() method
35
+ return value;
36
+ default:
37
+ throw new Error(`@Serializable decorating unknown 'kind': ${kind} (${name})`);
38
+ }
39
+ }
40
+ /** make a Class not instantiable */
41
+ export function Static(value, { kind, name }) {
42
+ name = String(name);
43
+ switch (kind) {
44
+ case 'class':
45
+ const wrapper = {
46
+ [name]: class extends value {
47
+ constructor(...args) {
48
+ super(...args);
49
+ throw new TypeError(`${name} is not a constructor`);
50
+ }
51
+ }
52
+ }[name];
53
+ return wrapper;
54
+ default:
55
+ throw new Error(`@Static decorating unknown 'kind': ${kind} (${name})`);
56
+ }
57
+ }
@@ -0,0 +1,14 @@
1
+ /** Coerce {value} into {value[]} ( if not already ), with optional {fill} Object */
2
+ export declare function asArray<T>(arr: Exclude<ArrayLike<T>, string> | undefined): T[];
3
+ export declare function asArray<T>(arr: T | Exclude<Iterable<T> | undefined, string>): NonNullable<T>[];
4
+ export declare function asArray<T, K>(arr: Iterable<T> | ArrayLike<T>, fill: K): K[];
5
+ /** stringify if not nullish */
6
+ export declare function asString<T>(str?: T): string;
7
+ /** convert String | Number | BigInt to Number */
8
+ export declare function asNumber(str?: string | number | bigint): number;
9
+ /** convert String | Number to BigInt */
10
+ export declare function asInteger<T extends string | number | bigint>(str?: T): bigint;
11
+ /** test if can convert String to Numeric */
12
+ export declare function isNumeric(str?: string | number | bigint): boolean;
13
+ /** return as Number if possible, else original String */
14
+ export declare const ifNumeric: (str: string | number | bigint, stripZero?: boolean) => string | number;
@@ -0,0 +1,71 @@
1
+ import { clone, stringify } from './serialize.library.js';
2
+ import { isIntegerLike, isArrayLike, isDefined, isInteger, isIterable, isNullish, isString, isUndefined, asType, isNumber } from './type.library.js';
3
+ export function asArray(arr = [], fill) {
4
+ switch (true) {
5
+ case isArrayLike(arr): // allow for {length:nn} objects
6
+ case isIterable(arr) && !isString(arr): // dont iterate Strings
7
+ return Array.from(arr, val => {
8
+ return isUndefined(fill) || isDefined(val)
9
+ ? val // if no {fill}, then use {val}
10
+ : clone(fill); // clone {fill} to create new Objects
11
+ });
12
+ default:
13
+ return Array.of(arr);
14
+ }
15
+ }
16
+ /** stringify if not nullish */
17
+ export function asString(str) {
18
+ return isNullish(str)
19
+ ? ''
20
+ : isInteger(str)
21
+ ? str.toString() + 'n'
22
+ : stringify(str);
23
+ }
24
+ /** convert String | Number | BigInt to Number */
25
+ export function asNumber(str) {
26
+ return parseFloat(str?.toString() ?? 'NaN');
27
+ }
28
+ /** convert String | Number to BigInt */
29
+ export function asInteger(str) {
30
+ const arg = asType(str);
31
+ switch (arg.type) {
32
+ case 'BigInt':
33
+ return arg.value; // already a BigInt
34
+ case 'Number':
35
+ return BigInt(Math.trunc(arg.value)); // cast as BigInt
36
+ case 'String':
37
+ return (isIntegerLike(arg.value)) // String representation of a BigInt
38
+ ? BigInt(arg.value.slice(0, -1)) // get rid of trailing 'n'
39
+ : BigInt(arg.value);
40
+ default:
41
+ return str;
42
+ }
43
+ }
44
+ /** test if can convert String to Numeric */
45
+ export function isNumeric(str) {
46
+ const arg = asType(str);
47
+ switch (arg.type) {
48
+ case 'Number':
49
+ case 'BigInt':
50
+ return true;
51
+ case 'String':
52
+ return isIntegerLike(arg.value)
53
+ ? true // is Number | BigInt
54
+ : !isNaN(asNumber(str)) && isFinite(str); // test if Number
55
+ default:
56
+ return false;
57
+ }
58
+ }
59
+ /** return as Number if possible, else original String */
60
+ export const ifNumeric = (str, stripZero = false) => {
61
+ switch (true) {
62
+ case isInteger(str): // BigInt → Number
63
+ return Number(str);
64
+ case isNumber(str): // Number → as-is
65
+ return str;
66
+ case isNumeric(str) && (!str?.toString().startsWith('0') || stripZero):
67
+ return asNumber(str); // numeric String → Number
68
+ default:
69
+ return str; // non-numeric String → as-is
70
+ }
71
+ };