@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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/array.library.d.ts +25 -0
- package/dist/array.library.js +78 -0
- package/dist/buffer.library.d.ts +6 -0
- package/dist/buffer.library.js +192 -0
- package/dist/cipher.class.d.ts +18 -0
- package/dist/cipher.class.js +65 -0
- package/dist/class.library.d.ts +10 -0
- package/dist/class.library.js +57 -0
- package/dist/coercion.library.d.ts +14 -0
- package/dist/coercion.library.js +71 -0
- package/dist/enumerate.library.d.ts +64 -0
- package/dist/enumerate.library.js +54 -0
- package/dist/function.library.d.ts +9 -0
- package/dist/function.library.js +49 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/logify.class.d.ts +33 -0
- package/dist/logify.class.js +53 -0
- package/dist/number.library.d.ts +16 -0
- package/dist/number.library.js +36 -0
- package/dist/object.library.d.ts +37 -0
- package/dist/object.library.js +94 -0
- package/dist/pledge.class.d.ts +54 -0
- package/dist/pledge.class.js +131 -0
- package/dist/prototype.library.d.ts +33 -0
- package/dist/prototype.library.js +51 -0
- package/dist/reflection.library.d.ts +74 -0
- package/dist/reflection.library.js +97 -0
- package/dist/serialize.library.d.ts +25 -0
- package/dist/serialize.library.js +266 -0
- package/dist/storage.library.d.ts +8 -0
- package/dist/storage.library.js +57 -0
- package/dist/string.library.d.ts +37 -0
- package/dist/string.library.js +93 -0
- package/dist/tempo.class.d.ts +556 -0
- package/dist/tempo.class.js +1412 -0
- package/dist/tempo.config/plugins/term.import.d.ts +42 -0
- package/dist/tempo.config/plugins/term.import.js +44 -0
- package/dist/tempo.config/plugins/term.quarter.d.ts +7 -0
- package/dist/tempo.config/plugins/term.quarter.js +28 -0
- package/dist/tempo.config/plugins/term.season.d.ts +7 -0
- package/dist/tempo.config/plugins/term.season.js +36 -0
- package/dist/tempo.config/plugins/term.timeline.d.ts +7 -0
- package/dist/tempo.config/plugins/term.timeline.js +19 -0
- package/dist/tempo.config/plugins/term.utils.d.ts +17 -0
- package/dist/tempo.config/plugins/term.utils.js +38 -0
- package/dist/tempo.config/plugins/term.zodiac.d.ts +7 -0
- package/dist/tempo.config/plugins/term.zodiac.js +62 -0
- package/dist/tempo.config/tempo.default.d.ts +169 -0
- package/dist/tempo.config/tempo.default.js +158 -0
- package/dist/tempo.config/tempo.enum.d.ts +99 -0
- package/dist/tempo.config/tempo.enum.js +78 -0
- package/dist/temporal.polyfill.d.ts +9 -0
- package/dist/temporal.polyfill.js +18 -0
- package/dist/type.library.d.ts +296 -0
- package/dist/type.library.js +80 -0
- package/dist/utility.library.d.ts +32 -0
- package/dist/utility.library.js +54 -0
- 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
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](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
|
+
};
|