@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SortBy } from './array.library.js';
|
|
2
|
+
import type { Property } from './type.library.js';
|
|
3
|
+
/**
|
|
4
|
+
* extend an Object's prototype to include new method, if no clash
|
|
5
|
+
*/
|
|
6
|
+
export declare const patch: <T extends Record<"prototype" | "name", any>>(proto: T, property: string, method: Function) => void;
|
|
7
|
+
declare global {
|
|
8
|
+
interface String {
|
|
9
|
+
/** remove redundant spaces to a new string */ trimAll(pat?: RegExp): string;
|
|
10
|
+
/** upper-case first letter of a word */ toProperCase(): string;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
type GroupFn<T extends Property<any>> = (value: T, index?: number) => PropertyKey;
|
|
14
|
+
type SortFn<T> = (left: T, right: T) => -1 | 0 | 1;
|
|
15
|
+
declare global {
|
|
16
|
+
interface Array<T> {
|
|
17
|
+
/** reduce Array to a keyed Object[] */ keyedBy(...keys: (keyof T)[]): Record<PropertyKey, T[]>;
|
|
18
|
+
/** reduce Array to a keyed Object[], mapped */ keyedBy<S extends Property<T>>(grpfn: GroupFn<S>): Record<PropertyKey, T[]>;
|
|
19
|
+
/** reduce Array to a keyed single-Object */ lookupBy(...keys: (keyof T)[]): Record<PropertyKey, T>;
|
|
20
|
+
/** reduce Array to a keyed single-Object, mapped */ lookupBy<S extends Property<T>>(grpfn: GroupFn<S>): Record<PropertyKey, T>;
|
|
21
|
+
/** return ordered Array */ orderBy(...keys: (PropertyKey | SortBy)[]): T[];
|
|
22
|
+
/** return ordered Array, mapped */ orderBy<V extends keyof T>(mapfn: SortFn<V>): V[];
|
|
23
|
+
/** return sorted Array */ sortBy(...keys: (PropertyKey | SortBy)[]): T[];
|
|
24
|
+
/** return ordered Array, mapped */ sortBy<V extends keyof T>(mapfn: SortFn<V>): V[];
|
|
25
|
+
/** return new Array with no repeated elements */ distinct(): T[];
|
|
26
|
+
/** return mapped Array with no repeated elements */ distinct<S>(mapfn: (value: T, index: number, array: T[]) => S, thisArg?: any): S[];
|
|
27
|
+
/** clear down an Array */ clear(): T[];
|
|
28
|
+
/** return cartesian-product of Array of Arrays */ cartesian(): T;
|
|
29
|
+
/** return cartesian-product of Array of Arrays */ cartesian(...args: T[][]): T[];
|
|
30
|
+
/** tap into an Array */ tap(tapfn: (value: T[]) => void, thisArg?: any): T[];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { trimAll, toProperCase } from './string.library.js';
|
|
2
|
+
import { asArray, byKey, byLkp, sortKey } from './array.library.js';
|
|
3
|
+
// Prototype extensions
|
|
4
|
+
// Remember to define any imports as a Function Declaration (not a Function Expression)
|
|
5
|
+
// so that they are 'hoisted' prior to extending a prototype
|
|
6
|
+
/**
|
|
7
|
+
* extend an Object's prototype to include new method, if no clash
|
|
8
|
+
*/
|
|
9
|
+
export const patch = (proto, property, method) => {
|
|
10
|
+
if (proto.prototype.hasOwnProperty(property)) { // if already defined,
|
|
11
|
+
if (trimAll(method.toString()) !== trimAll(proto.prototype?.[property]?.toString()))
|
|
12
|
+
console.warn(`${proto.name}.${property} already defined`); // show warning if different method definition
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
Object.defineProperty(proto.prototype, property, {
|
|
16
|
+
configurable: false,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
writable: false,
|
|
19
|
+
value: method,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
patch(String, 'trimAll', function (pat) { return trimAll(this, pat); });
|
|
24
|
+
patch(String, 'toProperCase', function () { return toProperCase(this); });
|
|
25
|
+
function sorted(...keys) { return sortKey(this, ...keys); }
|
|
26
|
+
patch(Array, 'orderBy', sorted); // order array by named keys
|
|
27
|
+
patch(Array, 'sortBy', sorted); // sort array by named keys
|
|
28
|
+
function keyed(...keys) { return byKey(this, ...keys); }
|
|
29
|
+
function lookup(...keys) { return byLkp(this, ...keys); }
|
|
30
|
+
patch(Array, 'keyedBy', keyed); // reduce array by named keys
|
|
31
|
+
patch(Array, 'lookupBy', lookup); // reduce array by named keys, only one entry per key
|
|
32
|
+
patch(Array, 'tap', function (fn) {
|
|
33
|
+
fn(this); // run an arbitrary function
|
|
34
|
+
return this; // then return the original array
|
|
35
|
+
});
|
|
36
|
+
patch(Array, 'clear', function () {
|
|
37
|
+
this.fill(null).length = 0; // wipe the contents, then set the 'length' to zero
|
|
38
|
+
return this;
|
|
39
|
+
});
|
|
40
|
+
patch(Array, 'distinct', function (mapfn) {
|
|
41
|
+
return mapfn
|
|
42
|
+
? this.map(mapfn).distinct() // run the mapping selector, then recurse
|
|
43
|
+
: Array.from(new Set(this)); // eliminate duplicates
|
|
44
|
+
});
|
|
45
|
+
patch(Array, 'cartesian', function (...args) {
|
|
46
|
+
const [a, b = [], ...c] = args.length === 0 ? this : args;
|
|
47
|
+
const cartFn = (a, b) => asArray([]).concat(...a.map(d => b.map(e => asArray([]).concat(d, e))));
|
|
48
|
+
return b.length
|
|
49
|
+
? this.cartesian(cartFn(a, b), ...c) // run the cartFn function, then recurse
|
|
50
|
+
: asArray(a || []); // return the collated result
|
|
51
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Obj, KeyOf, ValueOf, EntryOf, Primitives } from './type.library.js';
|
|
2
|
+
/** mutate Object | Array by excluding values with specified primitive 'types' */
|
|
3
|
+
export declare function exclude<T extends Obj>(obj: T, ...types: (Primitives | Lowercase<Primitives>)[]): T;
|
|
4
|
+
/** mutate Object | Array reference with properties removed */
|
|
5
|
+
export declare function omit<T extends Obj>(obj: T): T;
|
|
6
|
+
export declare function omit<T extends Obj>(obj: T, ...keys: PropertyKey[]): T;
|
|
7
|
+
/** remove all ownKeys from an Object | Array */
|
|
8
|
+
export declare function purge<T extends Obj>(obj: T): T;
|
|
9
|
+
/** reset Object */
|
|
10
|
+
export declare function reset<T extends Obj>(orig: T, obj?: T): T & {
|
|
11
|
+
[x: number]: any;
|
|
12
|
+
length?: any;
|
|
13
|
+
toString?: (() => string) | (() => string);
|
|
14
|
+
toLocaleString?: {
|
|
15
|
+
(): string;
|
|
16
|
+
(locales: string | string[], options?: Intl.NumberFormatOptions & Intl.DateTimeFormatOptions): string;
|
|
17
|
+
} | (() => string);
|
|
18
|
+
pop?: any;
|
|
19
|
+
push?: any;
|
|
20
|
+
concat?: any;
|
|
21
|
+
join?: any;
|
|
22
|
+
reverse?: any;
|
|
23
|
+
shift?: any;
|
|
24
|
+
slice?: any;
|
|
25
|
+
sort?: any;
|
|
26
|
+
splice?: any;
|
|
27
|
+
unshift?: any;
|
|
28
|
+
indexOf?: any;
|
|
29
|
+
lastIndexOf?: any;
|
|
30
|
+
every?: any;
|
|
31
|
+
some?: any;
|
|
32
|
+
forEach?: any;
|
|
33
|
+
map?: any;
|
|
34
|
+
filter?: any;
|
|
35
|
+
reduce?: any;
|
|
36
|
+
reduceRight?: any;
|
|
37
|
+
find?: any;
|
|
38
|
+
findIndex?: any;
|
|
39
|
+
fill?: any;
|
|
40
|
+
copyWithin?: any;
|
|
41
|
+
entries?: any;
|
|
42
|
+
keys?: any;
|
|
43
|
+
values?: any;
|
|
44
|
+
includes?: any;
|
|
45
|
+
flatMap?: any;
|
|
46
|
+
flat?: any;
|
|
47
|
+
at?: any;
|
|
48
|
+
findLast?: any;
|
|
49
|
+
findLastIndex?: any;
|
|
50
|
+
toReversed?: any;
|
|
51
|
+
toSorted?: any;
|
|
52
|
+
toSpliced?: any;
|
|
53
|
+
with?: any;
|
|
54
|
+
keyedBy?: any;
|
|
55
|
+
lookupBy?: any;
|
|
56
|
+
orderBy?: any;
|
|
57
|
+
sortBy?: any;
|
|
58
|
+
distinct?: any;
|
|
59
|
+
clear?: any;
|
|
60
|
+
cartesian?: any;
|
|
61
|
+
tap?: any;
|
|
62
|
+
};
|
|
63
|
+
/** array of all enumerable PropertyKeys */
|
|
64
|
+
export declare function ownKeys<T extends Obj>(json: T): KeyOf<T>[];
|
|
65
|
+
/** array of all enumerable object values */
|
|
66
|
+
export declare function ownValues<T extends Obj>(json: T): ValueOf<T>[];
|
|
67
|
+
/** tuple of enumerable entries with string | symbol keys */
|
|
68
|
+
export declare function ownEntries<T extends Obj>(json: T, all?: boolean): EntryOf<T>[];
|
|
69
|
+
/** get a string-array of 'getter' names for an object */
|
|
70
|
+
export declare const getAccessors: (obj?: any) => (string | number | symbol)[];
|
|
71
|
+
/** get a string-array of 'setter' names for an object */
|
|
72
|
+
export declare const setAccessors: (obj?: any) => (string | number | symbol)[];
|
|
73
|
+
/** copy all Own properties (including getters / setters) to a new object */
|
|
74
|
+
export declare const copyObject: <T extends Obj>(target: T, source: T) => T;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { asType, getType, isEmpty, isFunction, isPrimitive } from './type.library.js';
|
|
2
|
+
/** mutate Object | Array by excluding values with specified primitive 'types' */
|
|
3
|
+
export function exclude(obj, ...types) {
|
|
4
|
+
const exclusions = types
|
|
5
|
+
.map(item => item.toLowerCase()) // cast Primitives as Lowercase<Primitives> to aid in matching
|
|
6
|
+
.distinct();
|
|
7
|
+
if (obj && typeof obj === 'object') { // only works on Objects and Arrays
|
|
8
|
+
const keys = [];
|
|
9
|
+
ownEntries(obj)
|
|
10
|
+
.forEach(([key, value]) => {
|
|
11
|
+
const type = getType(value);
|
|
12
|
+
if (['Object', 'Array'].includes(type)) // recurse into object
|
|
13
|
+
exclude(value, ...exclusions);
|
|
14
|
+
if (isPrimitive(value) && exclusions.includes(type.toLowerCase()))
|
|
15
|
+
keys.push(key);
|
|
16
|
+
});
|
|
17
|
+
if (!isEmpty(keys)) // if any values to be excluded
|
|
18
|
+
omit(obj, ...keys);
|
|
19
|
+
}
|
|
20
|
+
return obj; // return Object reference, even though Object has been mutated
|
|
21
|
+
}
|
|
22
|
+
export function omit(obj, ...keys) {
|
|
23
|
+
const { type, value } = asType(obj);
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'Array':
|
|
26
|
+
if (isEmpty(keys)) {
|
|
27
|
+
value.clear(); // clear entire Array
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
keys
|
|
31
|
+
.sort()
|
|
32
|
+
.reverse() // remove from end-to-start to preserve indexes
|
|
33
|
+
.forEach(key => value.splice(Number(key), 1)); // remove Array index
|
|
34
|
+
break;
|
|
35
|
+
case 'Object':
|
|
36
|
+
(isEmpty(keys) ? ownKeys(value) : keys) // if no {keys}, assume all ownKeys
|
|
37
|
+
.forEach(key => Reflect.deleteProperty(value, key));
|
|
38
|
+
}
|
|
39
|
+
return value; // return Object reference, even though Object has been mutated
|
|
40
|
+
}
|
|
41
|
+
/** remove all ownKeys from an Object | Array */
|
|
42
|
+
export function purge(obj) {
|
|
43
|
+
return omit(obj);
|
|
44
|
+
}
|
|
45
|
+
/** reset Object */
|
|
46
|
+
export function reset(orig, obj) {
|
|
47
|
+
return Object.assign(purge(orig), { ...obj });
|
|
48
|
+
}
|
|
49
|
+
// These functions are to preserve the typescript 'type' of an object's keys & values
|
|
50
|
+
// and will include both string and symbol keys
|
|
51
|
+
/** array of all enumerable PropertyKeys */
|
|
52
|
+
export function ownKeys(json) {
|
|
53
|
+
return ownEntries(json).map(([key]) => key);
|
|
54
|
+
}
|
|
55
|
+
/** array of all enumerable object values */
|
|
56
|
+
export function ownValues(json) {
|
|
57
|
+
return ownEntries(json).map(([_, value]) => value);
|
|
58
|
+
}
|
|
59
|
+
/** tuple of enumerable entries with string | symbol keys */
|
|
60
|
+
export function ownEntries(json, all = false) {
|
|
61
|
+
if (!json || typeof json !== 'object')
|
|
62
|
+
return [];
|
|
63
|
+
const entries = new Map();
|
|
64
|
+
const limit = all ? 10 : 1; // limit depth of 10 down the prototype chain to prevent infinite loops
|
|
65
|
+
let depth = 0;
|
|
66
|
+
let proto = json;
|
|
67
|
+
do {
|
|
68
|
+
Reflect
|
|
69
|
+
.ownKeys(proto)
|
|
70
|
+
.filter(key => !entries.has(key)) // filter out if already tracking this key
|
|
71
|
+
.forEach(key => {
|
|
72
|
+
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
|
73
|
+
if (desc?.enumerable) // only track enumerable properties
|
|
74
|
+
entries.set(key, desc.value);
|
|
75
|
+
});
|
|
76
|
+
proto = Object.getPrototypeOf(proto);
|
|
77
|
+
} while (proto && proto !== Object.prototype && ++depth < limit);
|
|
78
|
+
return [...entries.entries()];
|
|
79
|
+
}
|
|
80
|
+
/** get a string-array of 'getter' names for an object */
|
|
81
|
+
export const getAccessors = (obj = {}) => {
|
|
82
|
+
return ownAccessors(obj, 'get');
|
|
83
|
+
};
|
|
84
|
+
/** get a string-array of 'setter' names for an object */
|
|
85
|
+
export const setAccessors = (obj = {}) => {
|
|
86
|
+
return ownAccessors(obj, 'set');
|
|
87
|
+
};
|
|
88
|
+
const ownAccessors = (obj = {}, type) => {
|
|
89
|
+
const accessors = Object.getOwnPropertyDescriptors(obj.prototype || Object.getPrototypeOf(obj));
|
|
90
|
+
return ownEntries(accessors)
|
|
91
|
+
.filter(([_, descriptor]) => isFunction(descriptor[type]))
|
|
92
|
+
.map(([key, _]) => key);
|
|
93
|
+
};
|
|
94
|
+
/** copy all Own properties (including getters / setters) to a new object */
|
|
95
|
+
export const copyObject = (target, source) => {
|
|
96
|
+
return Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
97
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** registry of registered classes */
|
|
2
|
+
export declare const Registry: Map<string, Function>;
|
|
3
|
+
/** make a deep-copy, using standard browser or JSON functions */
|
|
4
|
+
export declare function clone<T>(obj: T, opts?: {
|
|
5
|
+
transfer: any[];
|
|
6
|
+
}): T;
|
|
7
|
+
/** return a copy. remove unsupported values (e.g. \<undefined>, function) */
|
|
8
|
+
export declare function cleanify<T>(obj: T): T;
|
|
9
|
+
/** deep-copy an Object, and optionally replace \<undefined> fields with a Sentinel function call */
|
|
10
|
+
export declare function cloneify<T>(obj: T, sentinel?: Function): T;
|
|
11
|
+
/**
|
|
12
|
+
* For items which are not currently serializable via standard JSON.stringify (Undefined, BigInt, Set, Map, Symbol, etc.)
|
|
13
|
+
* this creates a stringified, single key:value Object to represent the value; for example '{ "$BigInt": 123 }'
|
|
14
|
+
*
|
|
15
|
+
* Drawbacks:
|
|
16
|
+
* no support Function / WeakMap / WeakSet / WeakRef
|
|
17
|
+
* limited support for user-defined Classes (must be specifically registered with @Serialize() decorator)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* serialize Objects for string-safe stashing in WebStorage, Cache, etc
|
|
21
|
+
* uses JSON.stringify where available, else returns stringified single key:value Object '{[$type]: value}'
|
|
22
|
+
*/
|
|
23
|
+
export declare function stringify<T>(obj: T): string;
|
|
24
|
+
/** rebuild an Object from its stringified representation */
|
|
25
|
+
export declare function objectify<T>(str: any, sentinel?: Function): T;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { curry } from './function.library.js';
|
|
2
|
+
import { ownKeys, ownValues, ownEntries } from './reflection.library.js';
|
|
3
|
+
import { isType, asType, isEmpty, isDefined, isUndefined, isNullish, isString, isObject, isArray, isFunction, isSymbolFor, isSymbol } from './type.library.js';
|
|
4
|
+
/** registry of registered classes */
|
|
5
|
+
// DO NOT EDIT THIS VALUE: used by decorator.library.ts
|
|
6
|
+
export const Registry = new Map();
|
|
7
|
+
// be aware that 'structuredClone' preserves \<undefined> values...
|
|
8
|
+
// and JSON.stringify() does not
|
|
9
|
+
/** make a deep-copy, using standard browser or JSON functions */
|
|
10
|
+
export function clone(obj, opts) {
|
|
11
|
+
try {
|
|
12
|
+
return globalThis.structuredClone(obj, opts);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return cleanify(obj); // fallback to JSON functions
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** return a copy. remove unsupported values (e.g. \<undefined>, function) */
|
|
19
|
+
export function cleanify(obj) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(JSON.stringify(obj)); // run any toString() methods
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn('Could not clean object: ', obj);
|
|
25
|
+
return { ...obj };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** deep-copy an Object, and optionally replace \<undefined> fields with a Sentinel function call */
|
|
29
|
+
export function cloneify(obj, sentinel) {
|
|
30
|
+
try {
|
|
31
|
+
return objectify(stringify(obj), sentinel);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.warn('Could not cloneify object: ', obj);
|
|
35
|
+
console.warn('stack: ', error.stack);
|
|
36
|
+
return obj;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function replacer(key, obj) { return isEmpty(key) ? obj : stringize(obj); }
|
|
40
|
+
function reviver(_key, val) { return decode(val); }
|
|
41
|
+
// safe-characters [sp " ; < > [ ] ^ { | }]
|
|
42
|
+
const safeList = ['20', '22', '3B', '3C', '3E', '5B', '5D', '5E', '7B', '7C', '7D'];
|
|
43
|
+
/** encode control characters, then replace a safe-subset back to text-string */
|
|
44
|
+
function encode(val) {
|
|
45
|
+
let enc = encodeURI(val);
|
|
46
|
+
if (enc.includes('%')) { // if an encoded URI might be in string
|
|
47
|
+
safeList.forEach(code => {
|
|
48
|
+
const uri = '%' + code;
|
|
49
|
+
const reg = new RegExp(uri, 'g');
|
|
50
|
+
enc = enc.replace(reg, decodeURI(uri));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return enc;
|
|
54
|
+
}
|
|
55
|
+
/** decode control characters */
|
|
56
|
+
function decode(val) {
|
|
57
|
+
if (isString(val)) {
|
|
58
|
+
try {
|
|
59
|
+
return decodeURI(val); // might fail if badly encoded '%'
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// console.warn(`decodeURI: ${(error as Error).message} -> ${val}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return val; // return original value
|
|
66
|
+
}
|
|
67
|
+
/** check type can be stringify'd */
|
|
68
|
+
function isStringable(val) {
|
|
69
|
+
return !isType(val, 'Function', 'AsyncFunction', 'WeakMap', 'WeakSet', 'WeakRef');
|
|
70
|
+
}
|
|
71
|
+
/** string representation of a single key:value Object */
|
|
72
|
+
function oneKey(type, value) {
|
|
73
|
+
return `{"$${type}":${value}}`;
|
|
74
|
+
}
|
|
75
|
+
/** Symbols in an Object-key will need special treatment */
|
|
76
|
+
function fromSymbol(key) {
|
|
77
|
+
return stringize(isSymbol(key) // @@(name) for global, @(name) for local symbols
|
|
78
|
+
? `${isSymbolFor(key) ? '@' : ''}@(${key.description ?? ''})`
|
|
79
|
+
: key);
|
|
80
|
+
}
|
|
81
|
+
const symKey = /^@(@)?\(([^\)]*)\)$/; // pattern to match a stringify'd Symbol
|
|
82
|
+
/** reconstruct a Symbol from a string-representation of a key */
|
|
83
|
+
function toSymbol(value) {
|
|
84
|
+
const [pat, keyFor, desc] = value.toString().match(symKey) || [null, void 0, void 0];
|
|
85
|
+
switch (true) {
|
|
86
|
+
case isSymbol(value): // already a Symbol
|
|
87
|
+
case isNullish(pat): // incorrectly encoded Symbol
|
|
88
|
+
case isDefined(keyFor) && isUndefined(desc): // incorrectly encoded global Symbol
|
|
89
|
+
return value;
|
|
90
|
+
case isDefined(keyFor): // global Symbol
|
|
91
|
+
return Symbol.for(desc);
|
|
92
|
+
case isUndefined(keyFor): // local Symbol
|
|
93
|
+
default:
|
|
94
|
+
return Symbol(desc);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* For items which are not currently serializable via standard JSON.stringify (Undefined, BigInt, Set, Map, Symbol, etc.)
|
|
99
|
+
* this creates a stringified, single key:value Object to represent the value; for example '{ "$BigInt": 123 }'
|
|
100
|
+
*
|
|
101
|
+
* Drawbacks:
|
|
102
|
+
* no support Function / WeakMap / WeakSet / WeakRef
|
|
103
|
+
* limited support for user-defined Classes (must be specifically registered with @Serialize() decorator)
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* serialize Objects for string-safe stashing in WebStorage, Cache, etc
|
|
107
|
+
* uses JSON.stringify where available, else returns stringified single key:value Object '{[$type]: value}'
|
|
108
|
+
*/
|
|
109
|
+
export function stringify(obj) {
|
|
110
|
+
return stringize(obj, false);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* internal function to process stringify-requests (and hide second parameter)
|
|
114
|
+
* where first argument is the object to stringify, and
|
|
115
|
+
* the second argument is a boolean to indicate if function is being called recursively
|
|
116
|
+
*/
|
|
117
|
+
function stringize(obj, recurse = true) {
|
|
118
|
+
const arg = asType(obj);
|
|
119
|
+
const one = curry(oneKey)(arg.type); // curry the oneKey() function
|
|
120
|
+
switch (arg.type) {
|
|
121
|
+
case 'String':
|
|
122
|
+
if (!recurse) { // if a top-level string (e.g. 'true' or '1234')
|
|
123
|
+
recurse = arg.value === 'true' // ensure true|false|null|1234 are quoted by JSON.stringify
|
|
124
|
+
|| arg.value === 'false' // so they will be correctly identified during objectify()
|
|
125
|
+
|| arg.value === 'null'
|
|
126
|
+
|| parseFloat(arg.value).toString() === arg.value;
|
|
127
|
+
}
|
|
128
|
+
return recurse
|
|
129
|
+
? JSON.stringify(encode(arg.value)) // encode string for safe-storage
|
|
130
|
+
: encode(arg.value); // dont JSON.stringify a top-level string
|
|
131
|
+
case 'Boolean':
|
|
132
|
+
case 'Null':
|
|
133
|
+
case 'Number':
|
|
134
|
+
return JSON.stringify(arg.value); // JSON.stringify will correctly handle these
|
|
135
|
+
case 'Void':
|
|
136
|
+
case 'Undefined':
|
|
137
|
+
return one(JSON.stringify('void')); // preserve 'undefined' values
|
|
138
|
+
case 'BigInt':
|
|
139
|
+
return one(arg.value.toString()); // even though BigInt has a toString method, it is not supported in JSON.stringify
|
|
140
|
+
case 'Object':
|
|
141
|
+
const obj = ownEntries(arg.value)
|
|
142
|
+
.filter(([, val]) => isStringable(val))
|
|
143
|
+
.map(([key, val]) => `${fromSymbol(key)}: ${stringize(val)}`)
|
|
144
|
+
.join(',');
|
|
145
|
+
return `{${obj}}`;
|
|
146
|
+
case 'Array':
|
|
147
|
+
const arr = arg.value
|
|
148
|
+
.filter(val => isStringable(val))
|
|
149
|
+
.map(val => stringize(val))
|
|
150
|
+
.join(',');
|
|
151
|
+
return `[${arr}]`;
|
|
152
|
+
case 'Map':
|
|
153
|
+
const map = Array.from(arg.value.entries())
|
|
154
|
+
.filter(([, val]) => isStringable(val))
|
|
155
|
+
.map(([key, val]) => `[${stringize(key)}, ${stringize(val)}]`)
|
|
156
|
+
.join(',');
|
|
157
|
+
return one(`[${map}]`);
|
|
158
|
+
case 'Set':
|
|
159
|
+
const set = Array.from(arg.value.values())
|
|
160
|
+
.filter(val => isStringable(val))
|
|
161
|
+
.map(val => stringize(val))
|
|
162
|
+
.join(',');
|
|
163
|
+
return one(`[${set}]`);
|
|
164
|
+
case 'Symbol':
|
|
165
|
+
return one(fromSymbol(arg.value));
|
|
166
|
+
case 'RegExp':
|
|
167
|
+
return one(stringize({ source: arg.value.source, flags: arg.value.flags }));
|
|
168
|
+
case 'Class':
|
|
169
|
+
default:
|
|
170
|
+
const value = arg.value;
|
|
171
|
+
switch (true) {
|
|
172
|
+
case !isStringable(value): // Object is not stringify-able
|
|
173
|
+
return void 0;
|
|
174
|
+
case isFunction(value.toString): // Object has its own toString method
|
|
175
|
+
const str = value.toString();
|
|
176
|
+
return one(str.includes('"') // TODO: improve detection of JSON vs non-JSON strings
|
|
177
|
+
? str
|
|
178
|
+
: JSON.stringify(str));
|
|
179
|
+
case isFunction(value.valueOf): // Object has its own valueOf method
|
|
180
|
+
return one(JSON.stringify(value.valueOf()));
|
|
181
|
+
case isFunction(value.toJSON): // Object has its own toJSON method
|
|
182
|
+
return one(stringize(value.toJSON()));
|
|
183
|
+
default: // else standard stringify
|
|
184
|
+
return one(JSON.stringify(value, replacer));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** rebuild an Object from its stringified representation */
|
|
189
|
+
export function objectify(str, sentinel) {
|
|
190
|
+
if (!isString(str))
|
|
191
|
+
return str; // skip parsing
|
|
192
|
+
let parse;
|
|
193
|
+
try {
|
|
194
|
+
parse = JSON.parse(str, reviver); // catch if cannot parse
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (str.startsWith('"') && str.endsWith('"')) {
|
|
198
|
+
console.warn(`objectify.parse: -> ${str}, ${error.message}`);
|
|
199
|
+
return str; // bail-out
|
|
200
|
+
}
|
|
201
|
+
else
|
|
202
|
+
return objectify(`"${str}"`, sentinel); // have another try, quoted
|
|
203
|
+
}
|
|
204
|
+
switch (true) {
|
|
205
|
+
case str.startsWith('{') && str.endsWith('}'): // looks like Object
|
|
206
|
+
case str.startsWith('[') && str.endsWith(']'): // looks like Array
|
|
207
|
+
return traverse(parse, sentinel); // recurse into object
|
|
208
|
+
default:
|
|
209
|
+
return parse;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** recurse into Object / Array, looking for special single key:value Objects */
|
|
213
|
+
function traverse(obj, sentinel) {
|
|
214
|
+
if (isObject(obj)) {
|
|
215
|
+
return typeify(ownEntries(obj)
|
|
216
|
+
.reduce((acc, [key, val]) => Object.assign(acc, { [toSymbol(key)]: typeify(traverse(val, sentinel)) }), {}), sentinel);
|
|
217
|
+
}
|
|
218
|
+
if (isArray(obj)) {
|
|
219
|
+
return ownValues(obj)
|
|
220
|
+
.map(val => typeify(traverse(val, sentinel)));
|
|
221
|
+
}
|
|
222
|
+
return obj;
|
|
223
|
+
}
|
|
224
|
+
/** rebuild an Object from its single key:value representation */
|
|
225
|
+
function typeify(json, sentinel) {
|
|
226
|
+
if (!isObject(json) || ownKeys(json).length !== 1)
|
|
227
|
+
return json; // only JSON Objects, with a single key:value pair
|
|
228
|
+
const [$type, value] = ownEntries(json)[0];
|
|
229
|
+
if (!String($type).startsWith('$'))
|
|
230
|
+
return json; // not a serialized single key:value Object
|
|
231
|
+
const type = $type.substring(1); // remove '$' prefix
|
|
232
|
+
switch (type) {
|
|
233
|
+
case 'String':
|
|
234
|
+
case 'Boolean':
|
|
235
|
+
case 'Object':
|
|
236
|
+
case 'Array':
|
|
237
|
+
return value; // these types are already handled by traverse()
|
|
238
|
+
case 'Number':
|
|
239
|
+
return Number(value);
|
|
240
|
+
case 'BigInt':
|
|
241
|
+
return BigInt(value);
|
|
242
|
+
case 'Null':
|
|
243
|
+
return null;
|
|
244
|
+
case 'Undefined':
|
|
245
|
+
case 'Empty':
|
|
246
|
+
case 'Void':
|
|
247
|
+
return sentinel?.(); // run Sentinel function to handle undefined values
|
|
248
|
+
case 'Date':
|
|
249
|
+
return new Date(value);
|
|
250
|
+
case 'RegExp':
|
|
251
|
+
return new RegExp(value.source, value.flags);
|
|
252
|
+
case 'Symbol':
|
|
253
|
+
return toSymbol(value);
|
|
254
|
+
case 'Map':
|
|
255
|
+
return new Map(value);
|
|
256
|
+
case 'Set':
|
|
257
|
+
return new Set(value);
|
|
258
|
+
default:
|
|
259
|
+
const cls = Registry.get($type); // lookup registered Class
|
|
260
|
+
if (!cls) {
|
|
261
|
+
console.warn(`objectify: dont know how to deserialize '${type}'`);
|
|
262
|
+
return json; // return original JSON object
|
|
263
|
+
}
|
|
264
|
+
return Reflect.construct(cls, [value]); // create new Class instance
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** select local | session storage */
|
|
2
|
+
export declare function selStorage(store?: 'local' | 'session'): Storage;
|
|
3
|
+
/** get storage */
|
|
4
|
+
export declare function getStorage<T>(): T;
|
|
5
|
+
export declare function getStorage<T>(key: string): T | undefined;
|
|
6
|
+
export declare function getStorage<T>(key: string | undefined, dflt?: T): T;
|
|
7
|
+
/** set / delete storage */
|
|
8
|
+
export declare function setStorage<T>(key: string, val?: T): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { objectify, stringify } from './serialize.library.js';
|
|
2
|
+
import { CONTEXT, getContext } from './utility.library.js';
|
|
3
|
+
import { isDefined, isUndefined, isString } from './type.library.js';
|
|
4
|
+
const context = getContext();
|
|
5
|
+
let storage = context.type === CONTEXT.Browser
|
|
6
|
+
? context.global?.localStorage //as globalThis.Storage // default to localStorage in a browser
|
|
7
|
+
: void 0;
|
|
8
|
+
/** select local | session storage */
|
|
9
|
+
export function selStorage(store = 'local') {
|
|
10
|
+
const name = (store + 'Storage');
|
|
11
|
+
return storage = globalThis[name]; // return whichever was selected.
|
|
12
|
+
}
|
|
13
|
+
export function getStorage(key, dflt) {
|
|
14
|
+
let store;
|
|
15
|
+
if (isUndefined(key))
|
|
16
|
+
return dflt ?? {};
|
|
17
|
+
switch (context.type) {
|
|
18
|
+
case CONTEXT.Browser:
|
|
19
|
+
store = storage.getItem(key);
|
|
20
|
+
break;
|
|
21
|
+
case CONTEXT.NodeJS:
|
|
22
|
+
store = context.global.process.env[key];
|
|
23
|
+
break;
|
|
24
|
+
case CONTEXT.GoogleAppsScript:
|
|
25
|
+
store = context.global.PropertiesService?.getUserProperties().getProperty(key);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Cannot determine Javascript context: ${context.type}`);
|
|
29
|
+
}
|
|
30
|
+
return isString(store)
|
|
31
|
+
? objectify(store) // rebuild object from its stringified representation
|
|
32
|
+
: dflt;
|
|
33
|
+
}
|
|
34
|
+
/** set / delete storage */
|
|
35
|
+
export function setStorage(key, val) {
|
|
36
|
+
const stash = isDefined(val) ? stringify(val) : void 0;
|
|
37
|
+
const set = isDefined(stash);
|
|
38
|
+
switch (context.type) {
|
|
39
|
+
case CONTEXT.Browser:
|
|
40
|
+
set
|
|
41
|
+
? storage.setItem(key, stash)
|
|
42
|
+
: storage.removeItem(key);
|
|
43
|
+
break;
|
|
44
|
+
case CONTEXT.NodeJS:
|
|
45
|
+
set
|
|
46
|
+
? (context.global.process.env[key] = stash)
|
|
47
|
+
: (delete context.global.process.env[key]);
|
|
48
|
+
break;
|
|
49
|
+
case CONTEXT.GoogleAppsScript:
|
|
50
|
+
set
|
|
51
|
+
? context.global.PropertiesService?.getUserProperties().setProperty(key, stash)
|
|
52
|
+
: context.global.PropertiesService?.getUserProperties().deleteProperty(key);
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Cannot determine Javascript context: ${context.type}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clean a string to remove some standard control-characters (tab, line-feed, carriage-return) and trim redundant spaces.
|
|
3
|
+
* allow for optional RegExp to specify additional match
|
|
4
|
+
*/
|
|
5
|
+
export declare function trimAll(str: string | number, pat?: RegExp): string;
|
|
6
|
+
/** every word has its first letter capitalized */
|
|
7
|
+
export declare function toProperCase<T extends string>(...str: T[]): T;
|
|
8
|
+
export declare const toCamelCase: <T extends string>(sentence: T) => T;
|
|
9
|
+
export declare const randomString: (len?: number) => string;
|
|
10
|
+
/** use sprintf-style formatting on a string */
|
|
11
|
+
export declare function sprintf(fmt: string, ...msg: any[]): string;
|
|
12
|
+
export declare function sprintf(...msg: any[]): string;
|
|
13
|
+
/** apply a plural suffix, if greater than '1' */
|
|
14
|
+
export declare const plural: (val: string | number | Record<string, string>, word: string, plural?: string) => string | ((num: string, word: string) => string);
|
|
15
|
+
/** strip a plural suffix, if endsWith 's' */
|
|
16
|
+
export declare const singular: (val: string) => string;
|
|
17
|
+
/** make an Object's values into a Template Literals, and evaluate */
|
|
18
|
+
export declare const makeTemplate: (templateString: Object) => (templateData: Object) => any;
|
|
19
|
+
/** stringify if not nullish */
|
|
20
|
+
export { asString } from './coercion.library.js';
|
|
21
|
+
export declare const toLower: <T>(str: T) => string | T;
|
|
22
|
+
export declare const toUpper: <T>(str: T) => string | T;
|
|
23
|
+
/** assert string is within bounds */
|
|
24
|
+
type StrLen<Min, Max = Min> = string & {
|
|
25
|
+
__value__: never;
|
|
26
|
+
};
|
|
27
|
+
export declare const strlen: <Min extends number, Max extends number>(str: unknown, min: Min, max?: Max) => StrLen<Min, Max>;
|
|
28
|
+
/**
|
|
29
|
+
* pad a string with leading character
|
|
30
|
+
* @param nbr input value to pad
|
|
31
|
+
* @param len fill-length (default: 2)
|
|
32
|
+
* @param fill character (default \<space> for string and \<zero> for number)
|
|
33
|
+
* @returns fixed-length string padded on the left with fill-character
|
|
34
|
+
*/
|
|
35
|
+
export declare const pad: (nbr?: string | number | bigint, len?: number, fill?: string | number) => string;
|
|
36
|
+
/** pad a string with non-blocking spaces, to help right-align a display */
|
|
37
|
+
export declare const padString: (str: string | number | bigint, pad?: number) => string;
|