@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
@@ -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;