zilla-util 1.2.27

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 (80) hide show
  1. package/LICENSE.txt +202 -0
  2. package/lib/esm/array.d.ts +30 -0
  3. package/lib/esm/array.js +111 -0
  4. package/lib/esm/array.js.map +1 -0
  5. package/lib/esm/base64.d.ts +2 -0
  6. package/lib/esm/base64.js +51 -0
  7. package/lib/esm/base64.js.map +1 -0
  8. package/lib/esm/crypto.d.ts +16 -0
  9. package/lib/esm/crypto.js +182 -0
  10. package/lib/esm/crypto.js.map +1 -0
  11. package/lib/esm/date.d.ts +2 -0
  12. package/lib/esm/date.js +47 -0
  13. package/lib/esm/date.js.map +1 -0
  14. package/lib/esm/deep.d.ts +24 -0
  15. package/lib/esm/deep.js +318 -0
  16. package/lib/esm/deep.js.map +1 -0
  17. package/lib/esm/error.d.ts +1 -0
  18. package/lib/esm/error.js +9 -0
  19. package/lib/esm/error.js.map +1 -0
  20. package/lib/esm/hash.d.ts +4 -0
  21. package/lib/esm/hash.js +18 -0
  22. package/lib/esm/hash.js.map +1 -0
  23. package/lib/esm/index.d.ts +17 -0
  24. package/lib/esm/index.js +18 -0
  25. package/lib/esm/index.js.map +1 -0
  26. package/lib/esm/logger.d.ts +18 -0
  27. package/lib/esm/logger.js +18 -0
  28. package/lib/esm/logger.js.map +1 -0
  29. package/lib/esm/lru.d.ts +25 -0
  30. package/lib/esm/lru.js +137 -0
  31. package/lib/esm/lru.js.map +1 -0
  32. package/lib/esm/msg.d.ts +29 -0
  33. package/lib/esm/msg.js +7 -0
  34. package/lib/esm/msg.js.map +1 -0
  35. package/lib/esm/object.d.ts +8 -0
  36. package/lib/esm/object.js +31 -0
  37. package/lib/esm/object.js.map +1 -0
  38. package/lib/esm/package.d.ts +2 -0
  39. package/lib/esm/package.js +38 -0
  40. package/lib/esm/package.js.map +1 -0
  41. package/lib/esm/regex.d.ts +1 -0
  42. package/lib/esm/regex.js +22 -0
  43. package/lib/esm/regex.js.map +1 -0
  44. package/lib/esm/retry.d.ts +13 -0
  45. package/lib/esm/retry.js +57 -0
  46. package/lib/esm/retry.js.map +1 -0
  47. package/lib/esm/sha256.d.ts +50 -0
  48. package/lib/esm/sha256.js +181 -0
  49. package/lib/esm/sha256.js.map +1 -0
  50. package/lib/esm/string.d.ts +106 -0
  51. package/lib/esm/string.js +257 -0
  52. package/lib/esm/string.js.map +1 -0
  53. package/lib/esm/subst.d.ts +6 -0
  54. package/lib/esm/subst.js +37 -0
  55. package/lib/esm/subst.js.map +1 -0
  56. package/lib/esm/time.d.ts +27 -0
  57. package/lib/esm/time.js +84 -0
  58. package/lib/esm/time.js.map +1 -0
  59. package/lib/esm/url.d.ts +9 -0
  60. package/lib/esm/url.js +80 -0
  61. package/lib/esm/url.js.map +1 -0
  62. package/package.json +72 -0
  63. package/src/array.ts +134 -0
  64. package/src/base64.ts +53 -0
  65. package/src/crypto.ts +281 -0
  66. package/src/date.ts +48 -0
  67. package/src/deep.ts +348 -0
  68. package/src/error.ts +7 -0
  69. package/src/hash.ts +20 -0
  70. package/src/index.ts +17 -0
  71. package/src/logger.ts +35 -0
  72. package/src/lru.ts +187 -0
  73. package/src/msg.ts +33 -0
  74. package/src/object.ts +43 -0
  75. package/src/package.ts +39 -0
  76. package/src/retry.ts +71 -0
  77. package/src/string.ts +338 -0
  78. package/src/subst.ts +41 -0
  79. package/src/time.ts +105 -0
  80. package/src/url.ts +92 -0
package/src/deep.ts ADDED
@@ -0,0 +1,348 @@
1
+ export type ObjectNav = {
2
+ append?: boolean;
3
+ remove?: number;
4
+ next?: string | number;
5
+ };
6
+
7
+ export const parseDeep = (fieldPath: string): ObjectNav[] => {
8
+ const parts = fieldPath.split(".");
9
+ const operations: ObjectNav[] = [];
10
+ for (let i = 0; i < parts.length; i++) {
11
+ const part = parts[i];
12
+ let squareBracketStart = part.indexOf("[");
13
+ if (squareBracketStart !== -1) {
14
+ const field = part.substring(0, squareBracketStart);
15
+ operations.push({ next: field });
16
+ while (squareBracketStart !== -1) {
17
+ const squareBracketEnd = part.indexOf("]", squareBracketStart);
18
+ if (squareBracketEnd === -1) {
19
+ throw new Error(`parseDeep: invalid fieldPath (missing closing square bracket): ${fieldPath}`);
20
+ }
21
+ const index = part.substring(squareBracketStart + 1, squareBracketEnd);
22
+ if (index.length === 0) {
23
+ operations.push({ append: true });
24
+ } else if (index.startsWith("-")) {
25
+ operations.push({ remove: parseInt(index.substring(1)) });
26
+ } else {
27
+ operations.push({ next: parseInt(index) });
28
+ }
29
+ squareBracketStart = part.indexOf("[", squareBracketEnd);
30
+ }
31
+ } else {
32
+ operations.push({ next: part });
33
+ }
34
+ }
35
+ // coalesce append operations into their preceding next
36
+ const ops = [];
37
+ let opsIndex = 0;
38
+ for (let i = 0; i < operations.length; i++) {
39
+ if (operations[i].append && opsIndex > 0) {
40
+ ops[opsIndex - 1].append = true;
41
+ } else {
42
+ ops[opsIndex] = operations[i];
43
+ opsIndex++;
44
+ }
45
+ }
46
+ return ops;
47
+ };
48
+
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ export const deepGet = <T>(obj: any, fieldPath: string): T | undefined => {
51
+ const operations = parseDeep(fieldPath);
52
+
53
+ let thing = obj;
54
+ for (let i = 0; i < operations.length; i++) {
55
+ const op = operations[i];
56
+ if (op.append) {
57
+ throw new Error(`deepGet: invalid fieldPath (cannot append): ${fieldPath}`);
58
+ }
59
+ if (op.remove) {
60
+ throw new Error(`deepGet: invalid fieldPath (cannot remove): ${fieldPath}`);
61
+ }
62
+ const next = op.next;
63
+ if (typeof next === "string" || typeof next === "number") {
64
+ if (typeof thing === "undefined" || thing == null) return undefined;
65
+ thing = thing[next];
66
+ }
67
+ }
68
+ return thing;
69
+ };
70
+
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ export const deepUpdate = (obj: any, fieldPath: string, value: any) => {
73
+ const operations = parseDeep(fieldPath);
74
+
75
+ let thing = obj;
76
+ for (let i = 0; i < operations.length - 1; i++) {
77
+ const op = operations[i];
78
+ const next = op.next;
79
+ if (op.append && next) {
80
+ if (typeof thing[next] === "undefined" || thing[next] == null) {
81
+ const nextThing = {};
82
+ thing[next] = [nextThing];
83
+ thing = nextThing;
84
+ } else if (Array.isArray(thing[next])) {
85
+ thing[next].push({});
86
+ thing = thing[next][thing[next].length - 1];
87
+ } else {
88
+ throw new Error(`deepUpdate: invalid fieldPath (cannot append to non-array): ${fieldPath}`);
89
+ }
90
+ } else if (typeof next === "string" || typeof next === "number") {
91
+ if (!(next in thing)) {
92
+ thing[next] = {};
93
+ }
94
+ thing = thing[next];
95
+ }
96
+ }
97
+
98
+ const lastOp = operations[operations.length - 1];
99
+ if (lastOp.append) {
100
+ if (typeof lastOp.next === "string" || typeof lastOp.next === "number") {
101
+ if (Array.isArray(thing[lastOp.next])) {
102
+ thing[lastOp.next].push(value);
103
+ } else {
104
+ thing[lastOp.next] = [value];
105
+ }
106
+ } else if (lastOp.next && Array.isArray(thing[lastOp.next])) {
107
+ thing[lastOp.next].push(value);
108
+ } else if (typeof lastOp.next === "undefined" || lastOp.next == null) {
109
+ thing.push(value);
110
+ } else {
111
+ throw new Error(`deepUpdate: invalid lastOp (${JSON.stringify(lastOp)}), cannot append`);
112
+ }
113
+ } else if (typeof lastOp.remove === "number") {
114
+ thing.splice(lastOp.remove, 1);
115
+ } else {
116
+ thing[lastOp.next as string | number] = value;
117
+ }
118
+ };
119
+
120
+ export const deepEquals = <T>(object1: T, object2: T): boolean => {
121
+ if (object1 === object2) return true;
122
+
123
+ const keys1 = Object.keys(object1 as Record<keyof T, unknown>) as (keyof T)[];
124
+ const keys2 = Object.keys(object2 as Record<keyof T, unknown>) as (keyof T)[];
125
+
126
+ if (keys1.length === 0 || keys1.length !== keys2.length) {
127
+ return false;
128
+ }
129
+
130
+ return keys1.every((key) => {
131
+ const val1 = object1[key];
132
+ const val2 = object2[key];
133
+ const areObjects = isObject(val1) && isObject(val2);
134
+
135
+ if ((areObjects && !deepEquals(val1, val2)) || (!areObjects && val1 !== val2)) {
136
+ return false;
137
+ }
138
+
139
+ return true;
140
+ });
141
+ };
142
+
143
+ export const deepAtLeastEquals = <T>(subset: Partial<T>, superset: Partial<T>, ignore?: (keyof T)[]): boolean => {
144
+ if (subset === superset) return true;
145
+
146
+ const keysSubset = Object.keys(subset as Record<keyof T, unknown>) as (keyof T)[];
147
+
148
+ return keysSubset
149
+ .filter((k) => !ignore || !ignore.includes(k))
150
+ .every((key) => {
151
+ if (!(key in superset)) {
152
+ return false;
153
+ }
154
+
155
+ const val1 = subset[key];
156
+ const val2 = superset[key];
157
+
158
+ if (val2 === undefined) {
159
+ // Ensure superset contains the property
160
+ return false;
161
+ }
162
+
163
+ const areObjects = isObject(val1) && isObject(val2);
164
+
165
+ if (areObjects) {
166
+ return deepAtLeastEquals(val1 as Record<string, unknown>, val2 as Partial<Record<string, unknown>>);
167
+ }
168
+
169
+ return val1 === val2;
170
+ });
171
+ };
172
+
173
+ export const isObject = <T>(object: T): boolean => object != null && typeof object === "object";
174
+
175
+ export const stripNonAlphaNumericKeys = <T>(obj: T, exclude?: RegExp | RegExp[]): T => {
176
+ const skip: RegExp[] = [];
177
+ if (Array.isArray(exclude)) {
178
+ skip.push(...exclude);
179
+ } else if (exclude) {
180
+ skip.push(exclude);
181
+ }
182
+ for (const key in obj) {
183
+ // Check if key contains any non-alphanumeric characters
184
+ if (/[^a-z0-9_]/i.test(key) && !skip.find((s) => s.test(key))) {
185
+ delete obj[key];
186
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
187
+ // If the value is a nested object or array, recurse into it
188
+ stripNonAlphaNumericKeys(obj[key], exclude);
189
+ }
190
+ }
191
+ return obj;
192
+ };
193
+
194
+ export const hasDuplicateProperty = (things: Record<string, unknown>[], prop: string): boolean => {
195
+ const found = new Set();
196
+ return things.some((entry) => {
197
+ if (found.has(entry[prop])) {
198
+ return true; // Found a duplicate
199
+ } else {
200
+ found.add(entry[prop]);
201
+ return false;
202
+ }
203
+ });
204
+ };
205
+
206
+ export const hasUniqueProperty = (things: Record<string, unknown>[], prop: string): boolean =>
207
+ !hasDuplicateProperty(things, prop);
208
+
209
+ export const filterObject = <T>(obj: Record<string, T>, keys: string[]): Record<string, T> =>
210
+ keys.reduce(
211
+ (acc: Record<string, T>, key: string) =>
212
+ // eslint-disable-next-line no-prototype-builtins
213
+ obj.hasOwnProperty(key) ? { ...acc, [key]: obj[key] } : acc,
214
+ {}
215
+ );
216
+
217
+ export const isEmpty = (obj?: unknown | null | undefined): boolean => {
218
+ if (typeof obj === "undefined" || obj == null || obj === "" || (Array.isArray(obj) && obj.length === 0)) {
219
+ return true;
220
+ } else if (typeof obj === "object") {
221
+ const fields = Object.keys(obj);
222
+ if (fields.length === 0) return true;
223
+ let allEmpty = true;
224
+ for (const fieldName of Object.keys(obj)) {
225
+ if (!isEmpty((obj as Record<string, unknown>)[fieldName])) {
226
+ allEmpty = false;
227
+ break;
228
+ }
229
+ }
230
+ if (allEmpty) return true;
231
+ }
232
+ return false;
233
+ };
234
+
235
+ export const isNotEmpty = (obj?: unknown | null | undefined): boolean => !isEmpty(obj);
236
+
237
+ export const filterProperties = (obj: Record<string, unknown>, propNames: string[]): Record<string, unknown> => {
238
+ return propNames.reduce((acc, prop) => {
239
+ if (Object.prototype.hasOwnProperty.call(obj, prop)) {
240
+ acc[prop] = obj[prop];
241
+ }
242
+ return acc;
243
+ }, {} as Record<string, unknown>);
244
+ };
245
+
246
+ export const deepEqualsForFields = (
247
+ o1: Record<string, unknown>,
248
+ o2: Record<string, unknown>,
249
+ propNames: string[]
250
+ ): boolean => {
251
+ const t1 = filterProperties(o1, propNames);
252
+ const t2 = filterProperties(o2, propNames);
253
+ return deepEquals(t1, t2);
254
+ };
255
+
256
+ export const FN_ALWAYS_TRUE = () => true;
257
+ export const FN_ALWAYS_FALSE = () => false;
258
+
259
+ const proxyCache = new WeakMap<object, unknown>();
260
+ const arrayMutators = new Set([
261
+ "push",
262
+ "pop",
263
+ "shift",
264
+ "unshift",
265
+ "splice",
266
+ "sort",
267
+ "reverse",
268
+ "fill",
269
+ "copyWithin",
270
+ "append",
271
+ ]);
272
+
273
+ export const immutify = <T>(obj: T): T => {
274
+ if (obj === null || typeof obj !== "object") return obj;
275
+ if (proxyCache.has(obj)) return proxyCache.get(obj) as T;
276
+
277
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
+ const handler: ProxyHandler<any> = Array.isArray(obj)
279
+ ? {
280
+ get(target, prop, receiver) {
281
+ if (typeof prop === "string" && arrayMutators.has(prop)) return () => undefined;
282
+ const value = Reflect.get(target, prop, receiver);
283
+ return value && typeof value === "object" ? immutify(value) : value;
284
+ },
285
+ set: FN_ALWAYS_TRUE,
286
+ deleteProperty: FN_ALWAYS_TRUE,
287
+ defineProperty: FN_ALWAYS_TRUE,
288
+ setPrototypeOf: FN_ALWAYS_TRUE,
289
+ }
290
+ : {
291
+ get(target, prop, receiver) {
292
+ const value = Reflect.get(target, prop, receiver);
293
+ return value && typeof value === "object" ? immutify(value) : value;
294
+ },
295
+ set: FN_ALWAYS_TRUE,
296
+ deleteProperty: FN_ALWAYS_TRUE,
297
+ defineProperty: FN_ALWAYS_TRUE,
298
+ setPrototypeOf: FN_ALWAYS_TRUE,
299
+ };
300
+
301
+ const proxy = new Proxy(obj, handler);
302
+ proxyCache.set(obj, proxy);
303
+ return proxy;
304
+ };
305
+
306
+ export const copyWithRegExp = <T>(obj: T): T => {
307
+ if (obj instanceof RegExp) {
308
+ return new RegExp(obj.source, obj.flags) as T;
309
+ }
310
+ if (Array.isArray(obj)) {
311
+ const arr: unknown[] = [];
312
+ for (const item of obj) {
313
+ arr.push(copyWithRegExp(item));
314
+ }
315
+ return arr as T;
316
+ }
317
+ if (obj && typeof obj === "object") {
318
+ const newObj: Record<string, unknown> = {};
319
+ for (const [key, val] of Object.entries(obj)) {
320
+ newObj[key] = copyWithRegExp(val);
321
+ }
322
+ return newObj as T;
323
+ }
324
+ return obj;
325
+ };
326
+
327
+ export const deepTransform = (
328
+ input: unknown,
329
+ predicate: (value: unknown) => boolean,
330
+ transformer: (value: unknown) => unknown
331
+ ): unknown => {
332
+ if (predicate(input)) return transformer(input);
333
+
334
+ if (Array.isArray(input)) {
335
+ return input.map((item) => deepTransform(item, predicate, transformer));
336
+ }
337
+
338
+ if (input !== null && typeof input === "object") {
339
+ const result: Record<string | number | symbol, unknown> = {};
340
+ for (const key of Reflect.ownKeys(input)) {
341
+ const value = (input as Record<string | number | symbol, unknown>)[key];
342
+ result[key] = deepTransform(value, predicate, transformer);
343
+ }
344
+ return result;
345
+ }
346
+
347
+ return input;
348
+ };
package/src/error.ts ADDED
@@ -0,0 +1,7 @@
1
+ export const wrapError = (context: string, err: unknown): Error => {
2
+ if (err instanceof Error) {
3
+ return new Error(context, { cause: err });
4
+ } else {
5
+ return new Error(`${context}: ${String(err)}`);
6
+ }
7
+ };
package/src/hash.ts ADDED
@@ -0,0 +1,20 @@
1
+ import Sha256 from "./sha256.js";
2
+ export const sha256 = (s: unknown) => Sha256.hash(`${s}`);
3
+
4
+ export const insertAtIndex = (str: string, insert: string, index: number): string => {
5
+ return str.slice(0, index) + insert + str.slice(index);
6
+ };
7
+
8
+ export const shaLevels = (val: string | number | boolean, levels: number): string => {
9
+ return dirLevels(sha256(`${val}`), levels);
10
+ };
11
+
12
+ export const dirLevels = (val: string | number | boolean, levels: number): string => {
13
+ let s = `${val}`;
14
+ if (levels > 0) {
15
+ for (let i = 0; i < levels; i++) {
16
+ s = insertAtIndex(s, "/", 2 + 3 * i);
17
+ }
18
+ }
19
+ return s;
20
+ };
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from "./array.js";
2
+ export * from "./base64.js";
3
+ export * from "./crypto.js";
4
+ export * from "./date.js";
5
+ export * from "./deep.js";
6
+ export * from "./error.js";
7
+ export * from "./hash.js";
8
+ export * from "./logger.js";
9
+ export * from "./lru.js";
10
+ export * from "./msg.js";
11
+ export * from "./object.js";
12
+ export * from "./retry.js";
13
+ export * from "./package.js";
14
+ export * from "./string.js";
15
+ export * from "./subst.js";
16
+ export * from "./time.js";
17
+ export * from "./url.js";
package/src/logger.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type GenericLogger = {
2
+ isSilent: () => boolean;
3
+ isAnyEnabled: () => boolean;
4
+ isTraceEnabled: () => boolean;
5
+ isVerboseEnabled: () => boolean;
6
+ isDebugEnabled: () => boolean;
7
+ isInfoEnabled: () => boolean;
8
+ isWarningEnabled: () => boolean;
9
+ isErrorEnabled: () => boolean;
10
+ trace: (message: string, ...args: unknown[]) => void;
11
+ verbose: (message: string, ...args: unknown[]) => void;
12
+ debug: (message: string, ...args: unknown[]) => void;
13
+ log: (message: string, ...args: unknown[]) => void;
14
+ info: (message: string, ...args: unknown[]) => void;
15
+ warn: (message: string, ...args: unknown[]) => void;
16
+ error: (message: string, ...args: unknown[]) => void;
17
+ };
18
+
19
+ export const DEFAULT_LOGGER: GenericLogger = {
20
+ isSilent: () => false,
21
+ isAnyEnabled: () => true,
22
+ isTraceEnabled: () => true,
23
+ isVerboseEnabled: () => true,
24
+ isDebugEnabled: () => true,
25
+ isInfoEnabled: () => true,
26
+ isWarningEnabled: () => true,
27
+ isErrorEnabled: () => true,
28
+ trace: (message: string, ...args: unknown[]) => console.trace(message, ...args),
29
+ verbose: (message: string, ...args: unknown[]) => console.trace(message, ...args),
30
+ debug: (message: string, ...args: unknown[]) => console.debug(message, ...args),
31
+ log: (message: string, ...args: unknown[]) => console.log(message, ...args),
32
+ info: (message: string, ...args: unknown[]) => console.info(message, ...args),
33
+ warn: (message: string, ...args: unknown[]) => console.warn(message, ...args),
34
+ error: (message: string, ...args: unknown[]) => console.error(message, ...args),
35
+ }
package/src/lru.ts ADDED
@@ -0,0 +1,187 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ZillaClock, DEFAULT_CLOCK } from "zilla-util";
3
+ import { GenericLogger } from "./logger.js";
4
+ import {safeStringify} from "./string.js";
5
+
6
+ export interface LRUCacheConfig {
7
+ maxSize?: number;
8
+ maxAge?: number;
9
+ clock?: ZillaClock;
10
+ touchOnGet?: boolean;
11
+ logger?: GenericLogger;
12
+ }
13
+
14
+ export class LRUCache<K, V> {
15
+ private readonly maxSize: number;
16
+ private readonly maxAge?: number;
17
+ private cache: Map<K, { value: V; timestamp: number }>;
18
+ private clock: ZillaClock;
19
+ private touchOnGet?: boolean;
20
+ private logger?: GenericLogger;
21
+
22
+ constructor({ maxSize = 100, maxAge, clock = DEFAULT_CLOCK, touchOnGet = true, logger }: LRUCacheConfig = {}) {
23
+ if (maxSize <= 0) throw new Error("maxSize must be positive");
24
+ this.maxSize = maxSize;
25
+ this.maxAge = maxAge;
26
+ this.cache = new Map();
27
+ this.clock = clock;
28
+ this.touchOnGet = touchOnGet;
29
+ this.logger = logger;
30
+ if (this.logger && this.logger.isTraceEnabled()) {
31
+ if (clock.constructor) {
32
+ this.logger.trace(`LRUCache.constructor now=${this.clock.now()} using clock=${clock.constructor}`);
33
+ } else {
34
+ this.logger.trace(
35
+ `LRUCache.constructor now=${this.clock.now()} using clock=${
36
+ clock === DEFAULT_CLOCK ? "DEFAULT_CLOCK" : "custom-clock"
37
+ }`
38
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ get(key: K): V | undefined {
44
+ if (this.logger && this.logger.isTraceEnabled()) {
45
+ this.logger.trace(`LRUCache.get(${key}) now=${this.clock.now()} starting`);
46
+ }
47
+ const entry = this.cache.get(key);
48
+ if (this.logger && this.logger.isTraceEnabled()) {
49
+ this.logger.trace(
50
+ `LRUCache.get(${key}) now=${this.clock.now()} entry=${entry ? safeStringify(entry) : "undefined"}`
51
+ );
52
+ }
53
+ if (!entry) {
54
+ return undefined;
55
+ }
56
+
57
+ if (this.maxAge !== undefined && this.clock.now() - entry.timestamp > this.maxAge) {
58
+ if (this.logger && this.logger.isTraceEnabled()) {
59
+ this.logger.trace(
60
+ `LRUCache.get(${key}) now=${this.clock.now()} EXPIRED entry=${
61
+ entry ? safeStringify(entry) : "undefined"
62
+ }`
63
+ );
64
+ }
65
+ this.cache.delete(key);
66
+ return undefined;
67
+ }
68
+
69
+ if (this.touchOnGet) {
70
+ if (this.logger && this.logger.isTraceEnabled()) {
71
+ this.logger.trace(
72
+ `LRUCache.get(${key}) now=${this.clock.now()} TOUCH-ON-GET entry=${
73
+ entry ? safeStringify(entry) : "undefined"
74
+ }`
75
+ );
76
+ }
77
+ this.cache.delete(key);
78
+ this.cache.set(key, { ...entry, timestamp: this.clock.now() });
79
+ }
80
+ if (this.logger && this.logger.isTraceEnabled()) {
81
+ this.logger.trace(`LRUCache.get(${key}) now=${this.clock.now()} RETURNING entry.value=${entry.value}`);
82
+ }
83
+ return entry.value;
84
+ }
85
+
86
+ set(key: K, value: V): void {
87
+ if (this.logger && this.logger.isTraceEnabled()) {
88
+ this.logger.trace(`LRUCache.set(${key}) now=${this.clock.now()} starting`);
89
+ }
90
+ if (this.cache.has(key)) {
91
+ if (this.logger && this.logger.isTraceEnabled()) {
92
+ this.logger.trace(`LRUCache.set(${key}) now=${this.clock.now()} KEY FOUND DELETING`);
93
+ }
94
+ this.cache.delete(key);
95
+ } else if (this.cache.size >= this.maxSize) {
96
+ if (this.logger && this.logger.isTraceEnabled()) {
97
+ this.logger.trace(
98
+ `LRUCache.set(${key}) now=${this.clock.now()} this.cache.size=${this.cache.size} >= this.maxSize=${
99
+ this.maxSize
100
+ } EVICTING`
101
+ );
102
+ }
103
+ this.evict();
104
+ }
105
+ if (this.logger && this.logger.isTraceEnabled()) {
106
+ this.logger.trace(`LRUCache.set(${key}) now=${this.clock.now()} SETTING value=${safeStringify(value)}`);
107
+ }
108
+ this.cache.set(key, { value, timestamp: this.clock.now() });
109
+ }
110
+
111
+ private evict(): void {
112
+ if (this.logger && this.logger.isTraceEnabled()) {
113
+ this.logger.trace(`LRUCache.evict now=${this.clock.now()} starting`);
114
+ }
115
+ const oldestKey = this.cache.keys().next().value;
116
+ if (oldestKey !== undefined) {
117
+ if (this.logger && this.logger.isTraceEnabled()) {
118
+ this.logger.trace(`LRUCache.evict now=${this.clock.now()} DELETING oldestKey=${oldestKey}`);
119
+ }
120
+ this.cache.delete(oldestKey);
121
+ } else {
122
+ if (this.logger && this.logger.isTraceEnabled()) {
123
+ this.logger.trace(`LRUCache.evict now=${this.clock.now()} NOTHING TO DELETE oldestKey=undefined`);
124
+ }
125
+ }
126
+ }
127
+
128
+ delete(key: K): void {
129
+ if (this.logger && this.logger.isTraceEnabled()) {
130
+ this.logger.trace(`LRUCache.delete(${key}) now=${this.clock.now()} starting`);
131
+ }
132
+ this.cache.delete(key);
133
+ if (this.logger && this.logger.isTraceEnabled()) {
134
+ this.logger.trace(`LRUCache.delete(${key}) now=${this.clock.now()} finished`);
135
+ }
136
+ }
137
+
138
+ clear(): void {
139
+ if (this.logger && this.logger.isTraceEnabled()) {
140
+ this.logger.trace(`LRUCache.clear now=${this.clock.now()} starting`);
141
+ }
142
+ this.cache.clear();
143
+ if (this.logger && this.logger.isTraceEnabled()) {
144
+ this.logger.trace(`LRUCache.clear now=${this.clock.now()} finished`);
145
+ }
146
+ }
147
+ }
148
+
149
+ export function withLRUCache<V>(
150
+ fn: (...args: any[]) => Promise<V>,
151
+ cacheOrConfig?: LRUCache<string, V> | LRUCacheConfig,
152
+ keyFn?: (...args: any[]) => string
153
+ ): (...args: any[]) => Promise<V>;
154
+
155
+ export function withLRUCache<V>(
156
+ fn: (...args: any[]) => V,
157
+ cacheOrConfig?: LRUCache<string, V> | LRUCacheConfig,
158
+ keyFn?: (...args: any[]) => string
159
+ ): (...args: any[]) => V;
160
+
161
+ export function withLRUCache<V>(
162
+ fn: (...args: any[]) => V | Promise<V>,
163
+ cacheOrConfig?: LRUCache<string, V> | LRUCacheConfig,
164
+ keyFn?: (...args: any[]) => string
165
+ ): (...args: any[]) => V | Promise<V> {
166
+ const cache = cacheOrConfig instanceof LRUCache ? cacheOrConfig : new LRUCache<string, V>(cacheOrConfig);
167
+
168
+ const defaultKeyFn = (...args: any[]): string =>
169
+ JSON.stringify(args, (key, value) => (typeof value === "function" ? value.toString() : value));
170
+
171
+ return (...args: any[]): V | Promise<V> => {
172
+ const key = (keyFn ?? defaultKeyFn)(...args);
173
+ const cachedValue = cache.get(key);
174
+ if (cachedValue !== undefined) return cachedValue;
175
+
176
+ const result = fn(...args);
177
+ if (result instanceof Promise) {
178
+ return result.then((resolvedResult) => {
179
+ cache.set(key, resolvedResult);
180
+ return resolvedResult;
181
+ });
182
+ } else {
183
+ cache.set(key, result);
184
+ return result;
185
+ }
186
+ };
187
+ }
package/src/msg.ts ADDED
@@ -0,0 +1,33 @@
1
+ export enum ZillaMsgTransportType {
2
+ email = "email",
3
+ sms = "sms",
4
+ }
5
+
6
+ export const MESSAGE_TYPE_VALUES: ZillaMsgTransportType[] = Object.values(ZillaMsgTransportType);
7
+
8
+ export type ZillaMsgRecipient = {
9
+ name?: string;
10
+ destination: string;
11
+ };
12
+
13
+ export type ZillaMessage = {
14
+ transport: string;
15
+ type: ZillaMsgTransportType;
16
+ via: string;
17
+ template: string;
18
+ locale: string;
19
+ from: string;
20
+ to: string;
21
+ messages: Record<string, string>;
22
+ ctime: number;
23
+ };
24
+
25
+ export type ZillaMsgTransport = {
26
+ name: string;
27
+ type: ZillaMsgTransportType;
28
+ sender: string;
29
+ via: () => string;
30
+ templates: string[];
31
+ formatTo: (recipient: ZillaMsgRecipient) => string;
32
+ send: (recipient: ZillaMsgRecipient, rendered: Record<string, string>) => Promise<unknown>;
33
+ };
package/src/object.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { SortedSet } from "./array.js";
2
+
3
+ export type EmptyObjectType = Record<string, never>;
4
+
5
+ export const keysByValue = <K extends string, V>(obj: Record<K, V>, val: V): K[] => {
6
+ return (Object.keys(obj) as K[]).filter((k) => obj[k] === val);
7
+ };
8
+
9
+ export const firstKeyByValue = <K extends string, V>(obj: Record<K, V>, val: V): K | undefined => {
10
+ return (Object.keys(obj) as K[]).find((k) => obj[k] === val);
11
+ };
12
+
13
+ export const enumRecord = <E extends string | number | symbol, V>(
14
+ e: Record<string, E>,
15
+ init: V | ((e?: E) => V)
16
+ ): Record<E, V> => {
17
+ const o = {} as Record<E, V>;
18
+ for (const k of Object.values(e) as E[]) {
19
+ o[k] = typeof init === "function" ? (init as (e?: E) => V)(k) : init;
20
+ }
21
+ return o;
22
+ };
23
+
24
+ export const setsToArrays = <K extends string, V>(input: Record<K, SortedSet<V>>): Record<K, V[]> =>
25
+ Object.fromEntries(Object.entries(input).map(([k, set]) => [k, (set as SortedSet<V>).toArray()])) as Record<K, V[]>;
26
+
27
+ export const findDuplicates = <T>(items: T[], field: keyof T): string[] => {
28
+ const counts = new Map<string, number>();
29
+ for (const item of items) {
30
+ const key = String(item[field]);
31
+ counts.set(key, (counts.get(key) ?? 0) + 1);
32
+ }
33
+ return [...counts].filter(([, n]) => n > 1).map(([k]) => k);
34
+ };
35
+
36
+ export const reverseEnum = <E extends Record<string, string>>(enumObj: E): Record<string, string> => {
37
+ const rev: Record<string, string> = {};
38
+ (Object.keys(enumObj) as Array<keyof E>).forEach((key: keyof E): void => {
39
+ const value: string = enumObj[key];
40
+ rev[value] = key as string;
41
+ });
42
+ return rev;
43
+ };