@wener/utils 1.1.3 → 1.1.5

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 (49) hide show
  1. package/README.md +9 -0
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/esm/index.js +1 -1
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/system/index.js +1 -1
  7. package/dist/system/index.js.map +1 -1
  8. package/lib/index.js +8 -1
  9. package/lib/index.js.map +1 -1
  10. package/lib/io/isBuffer.js.map +1 -1
  11. package/lib/logging/createChildLogger.js +18 -0
  12. package/lib/logging/createChildLogger.js.map +1 -0
  13. package/lib/logging/createNoopLogger.js +14 -0
  14. package/lib/logging/createNoopLogger.js.map +1 -0
  15. package/lib/logging/createWriteLogger.js +13 -0
  16. package/lib/logging/createWriteLogger.js.map +1 -0
  17. package/lib/modules/isModule.js +6 -0
  18. package/lib/modules/isModule.js.map +1 -0
  19. package/lib/modules/parseModuleId.js.map +1 -1
  20. package/lib/objects/get.js +14 -0
  21. package/lib/objects/get.js.map +1 -0
  22. package/lib/objects/parseObjectPath.js +27 -0
  23. package/lib/objects/parseObjectPath.js.map +1 -0
  24. package/lib/objects/set.js +36 -0
  25. package/lib/objects/set.js.map +1 -0
  26. package/lib/strings/renderTemplate.js +23 -0
  27. package/lib/strings/renderTemplate.js.map +1 -0
  28. package/package.json +13 -6
  29. package/src/index.ts +13 -1
  30. package/src/io/isBuffer.ts +5 -1
  31. package/src/logging/Logger.ts +19 -0
  32. package/src/logging/createChildLogger.ts +16 -0
  33. package/src/logging/createNoopLogger.ts +13 -0
  34. package/src/logging/createWriteLogger.ts +15 -0
  35. package/src/logging/logger.test.ts +34 -0
  36. package/src/modules/isModule.ts +10 -0
  37. package/src/modules/parseModuleId.ts +5 -0
  38. package/src/objects/get.test.ts +116 -0
  39. package/src/objects/get.ts +49 -0
  40. package/src/objects/parseObjectPath.test.ts +18 -0
  41. package/src/objects/parseObjectPath.ts +40 -0
  42. package/src/objects/set.test.ts +405 -0
  43. package/src/objects/set.ts +48 -0
  44. package/src/strings/renderTemplate.test.ts +25 -0
  45. package/src/strings/renderTemplate.ts +31 -0
  46. package/tsconfig.json +0 -4
  47. package/lib/strings/templates.js +0 -8
  48. package/lib/strings/templates.js.map +0 -1
  49. package/src/strings/templates.ts +0 -13
package/src/index.ts CHANGED
@@ -7,6 +7,11 @@ export {
7
7
  type MaybeArray,
8
8
  } from './arrays/MaybeArray';
9
9
 
10
+ // object
11
+ export { get } from './objects/get';
12
+ export { set } from './objects/set';
13
+ export { parseObjectPath } from './objects/parseObjectPath';
14
+
10
15
  // async
11
16
  export { createLazyPromise, type LazyPromise } from './asyncs/LazyPromise';
12
17
  export { setAsyncInterval, clearAsyncInterval } from './asyncs/AsyncInterval';
@@ -26,10 +31,17 @@ export { dequal } from './validations/dequal';
26
31
 
27
32
  // modules
28
33
  export { parseModuleId, type ParsedModuleId } from './modules/parseModuleId';
34
+ export { isModule } from './modules/isModule';
35
+
36
+ // logging
37
+ export { type Logger, type LogLevel } from './logging/Logger';
38
+ export { createWriteLogger } from './logging/createWriteLogger';
39
+ export { createNoopLogger } from './logging/createNoopLogger';
40
+ export { createChildLogger } from './logging/createChildLogger';
29
41
 
30
42
  // strings
31
43
  export { pascalCase, camelCase } from './strings/camelCase';
32
- export { templateString } from './strings/templates';
44
+ export { renderTemplate } from './strings/renderTemplate';
33
45
 
34
46
  export { createRandom } from './maths/random';
35
47
  export { isBuffer } from './io/isBuffer';
@@ -1,4 +1,8 @@
1
- // https://github.com/feross/is-buffer/blob/master/index.js
1
+ /**
2
+ * check {@code obj} is Buffer
3
+ *
4
+ * {@link https://github.com/feross/is-buffer feross/is-buffer}
5
+ */
2
6
  export function isBuffer(obj: any): obj is Buffer {
3
7
  return (
4
8
  obj != null &&
@@ -0,0 +1,19 @@
1
+ export interface Logger {
2
+ trace(...data: any[]): void;
3
+
4
+ debug(...data: any[]): void;
5
+
6
+ info(...data: any[]): void;
7
+
8
+ warn(...data: any[]): void;
9
+
10
+ error(...data: any[]): void;
11
+
12
+ child?: (o: object) => Logger;
13
+ }
14
+
15
+ export interface LoggerWithChild extends Logger {
16
+ child: (o: object) => LoggerWithChild;
17
+ }
18
+
19
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
@@ -0,0 +1,16 @@
1
+ import { Logger, LoggerWithChild } from './Logger';
2
+ import { createWriteLogger } from './createWriteLogger';
3
+
4
+ export function createChildLogger(l: Logger, ctx: object): LoggerWithChild {
5
+ if (l.child) {
6
+ return l.child(ctx) as LoggerWithChild;
7
+ }
8
+ return createWriteLogger((o) => {
9
+ const { level, values, ...c } = o;
10
+ if (Object.keys(c).length) {
11
+ l[level](c, ...values);
12
+ } else {
13
+ l[level](...values);
14
+ }
15
+ }, ctx);
16
+ }
@@ -0,0 +1,13 @@
1
+ import { LoggerWithChild } from './Logger';
2
+
3
+ export function createNoopLogger(): LoggerWithChild {
4
+ const noop = (..._: any[]) => void 0;
5
+ return {
6
+ trace: noop,
7
+ debug: noop,
8
+ info: noop,
9
+ warn: noop,
10
+ error: noop,
11
+ child: () => createNoopLogger(),
12
+ };
13
+ }
@@ -0,0 +1,15 @@
1
+ import { LoggerWithChild, LogLevel } from './Logger';
2
+
3
+ export function createWriteLogger(
4
+ write: (o: { level: LogLevel; values: any[] } & Record<string | symbol, any>) => void,
5
+ context: object = {},
6
+ ): LoggerWithChild {
7
+ return {
8
+ trace: (...values) => write({ ...context, level: 'trace', values }),
9
+ debug: (...values) => write({ ...context, level: 'debug', values }),
10
+ info: (...values) => write({ ...context, level: 'info', values }),
11
+ warn: (...values) => write({ ...context, level: 'warn', values }),
12
+ error: (...values) => write({ ...context, level: 'error', values }),
13
+ child: (ctx) => createWriteLogger(write, { ...context, ...ctx }),
14
+ };
15
+ }
@@ -0,0 +1,34 @@
1
+ import test from 'ava';
2
+ import { createChildLogger } from './createChildLogger';
3
+ import { createWriteLogger } from './createWriteLogger';
4
+
5
+ test('logger', (t) => {
6
+ {
7
+ let logs: any[] = [];
8
+ const base = createWriteLogger((o) => logs.push(o));
9
+ let l = createChildLogger(base, { c: 'test' });
10
+ l.info('hello');
11
+ t.deepEqual(logs.shift(), { level: 'info', values: ['hello'], c: 'test' });
12
+ l.child({ m: 1 }).trace('trace');
13
+ t.deepEqual(logs.shift(), { level: 'trace', values: ['trace'], c: 'test', m: 1 });
14
+ }
15
+ createChildLogger(console, { c: 'test' }).info('hello');
16
+ {
17
+ let pass = 0;
18
+ let l = createWriteLogger(
19
+ (o) => {
20
+ pass++;
21
+ t.log(`${o.level}: [${[o.m, o.c].filter(Boolean).join('.') || 'default'}]`, ...o.values);
22
+ },
23
+ {
24
+ m: 'Root',
25
+ },
26
+ );
27
+ l.info('nice');
28
+ t.is(pass, 1);
29
+ l.child({}).info('nice 2');
30
+ t.is(pass, 2);
31
+ createChildLogger(l, { m: 'Child' }).info('nice 3');
32
+ t.is(pass, 3);
33
+ }
34
+ });
@@ -0,0 +1,10 @@
1
+ export function isModule(o: any): o is Module {
2
+ return o && o[Symbol.toStringTag] === 'Module';
3
+ }
4
+
5
+ export interface Module {
6
+ [Symbol.toStringTag]: 'Module';
7
+ default?: any;
8
+
9
+ [k: string | symbol]: any;
10
+ }
@@ -17,6 +17,11 @@ export type ParsedModuleId = {
17
17
  }
18
18
  );
19
19
 
20
+ /**
21
+ * Parse NPM module id
22
+ *
23
+ * parseModuleId('@wener/reaction@latest/index.js') // => { id: '@wener/reaction@latest', name: '@wener/reaction', version: 'latest', range: 'latest', pkg: 'reaction', path: '/index.js', scoped: true, org: 'wener' }
24
+ */
20
25
  export function parseModuleId(s: string): ParsedModuleId | undefined {
21
26
  const groups = s.match(regModuleId)?.groups;
22
27
  if (!groups) {
@@ -0,0 +1,116 @@
1
+ import test from 'ava';
2
+ import { expectType } from 'tsd';
3
+ import { get } from './get';
4
+ import { parseObjectPath } from './parseObjectPath';
5
+
6
+ test('get typed', (t) => {
7
+ interface TestClass {
8
+ normal: string;
9
+ nested: {
10
+ a: number;
11
+ b: {
12
+ c: boolean;
13
+ };
14
+ };
15
+ arr: number[];
16
+ nestedArr: {
17
+ sum: number;
18
+ other: null;
19
+ }[];
20
+ deep: {
21
+ arr: string[];
22
+ };
23
+ deeplvl1: {
24
+ deeplvl2: {
25
+ deeplvl3: {
26
+ deeplvl4: {
27
+ value: RegExp;
28
+ };
29
+ }[];
30
+ };
31
+ }[];
32
+ }
33
+
34
+ const obj = {} as TestClass;
35
+
36
+ expectType<number>(get(obj, 'nested.a', null));
37
+ expectType<string>(get(obj, 'normal', null));
38
+ expectType<number>(get(obj, 'nested.a'));
39
+ expectType<boolean>(get(obj, 'nested.b.c'));
40
+ expectType<number[]>(get(obj, 'arr'));
41
+ expectType<number>(get(obj, 'arr[13]'));
42
+ expectType<number>(get(obj, 'arr.13'));
43
+ expectType<null>(get(obj, 'nestedArr[3].other'));
44
+ expectType<string[]>(get(obj, 'deep.deep'));
45
+ expectType<string>(get(obj, 'deep.arr[333]'));
46
+ expectType<number>(get(obj, 'deep.arr[333].length'));
47
+ expectType<boolean>(get(obj, 'nested["b"]["c"]'));
48
+ expectType<never>(get(obj, ''));
49
+ expectType<number>(get(obj, '', 3));
50
+ expectType<never>(get(obj, 'nested.asdfasdf'));
51
+ expectType<RegExp>(get(obj, 'deeplvl1[1].deeplvl2.deeplvl3[88].deeplvl4.value'));
52
+ expectType<never>(get(obj, 'deeplvl1[1].deeplvl2.deeplvl1[88].deeplvl4.value'));
53
+ expectType<TestClass>(get(obj, 'deeplvl1[1].deeplvl2.deeplvl1[88].deeplvl4.value', obj));
54
+ expectType<string>(get(obj, 'nested["dd"]', ''));
55
+ t.pass();
56
+ });
57
+
58
+ test('get', (t) => {
59
+ let obj = {
60
+ undef: undefined,
61
+ zero: 0,
62
+ one: 1,
63
+ n: null,
64
+ f: false,
65
+ a: {
66
+ two: 2,
67
+ b: {
68
+ three: 3,
69
+ c: {
70
+ four: 4,
71
+ },
72
+ },
73
+ },
74
+ arr: [5, [6, { v: 7 }]],
75
+ } as const;
76
+
77
+ function check(path: string, value: any, def?: any) {
78
+ const out = get(obj, path, def);
79
+ t.is(out, value, 'get(obj, "' + path + '") should be ' + value + ', got ' + out);
80
+ if (path) {
81
+ const arr = parseObjectPath(path);
82
+ t.is(get(obj, arr, def), value, `get(obj,${JSON.stringify(arr)}, ${def})`);
83
+ }
84
+ }
85
+
86
+ check('', undefined);
87
+ check('one', obj.one);
88
+ check('one.two', undefined);
89
+ check('a', obj.a);
90
+ check('a.two', obj.a.two);
91
+ check('a.b', obj.a.b);
92
+ check('a.b.three', obj.a.b.three);
93
+ check('a.b.c', obj.a.b.c);
94
+ check('a.b.c.four', obj.a.b.c.four);
95
+ check('n', obj.n);
96
+ check('n.badkey', undefined);
97
+ check('f', false);
98
+ check('f.badkey', undefined);
99
+
100
+ check('', 'foo', 'foo');
101
+ check('undef', 'foo', 'foo');
102
+ check('n', null, 'foo');
103
+ check('n.badkey', 'foo', 'foo');
104
+ check('zero', 0, 'foo');
105
+ check('a.badkey', 'foo', 'foo');
106
+ check('a.badkey.anotherbadkey', 'foo', 'foo');
107
+ check('f', false, 'foo');
108
+ check('f.badkey', 'foo', 'foo');
109
+
110
+ check('arr.0', 5);
111
+ check('arr.1.0', 6);
112
+ check('arr.1.1.v', 7);
113
+ check('arr[0]', 5);
114
+ check('arr[1][0]', 6);
115
+ check('arr[1][1][v]', 7);
116
+ });
@@ -0,0 +1,49 @@
1
+ import { ObjectKey, parseObjectPath } from './parseObjectPath';
2
+
3
+ /**
4
+ * get by path
5
+ *
6
+ * {@link https://github.com/developit/dlv dlv}
7
+ */
8
+ export function get<O extends object, P extends ObjectKey, OrElse extends unknown>(
9
+ obj: O,
10
+ key: P | P[],
11
+ def?: OrElse,
12
+ ): ResolveObjectPathType<O, P, OrElse> {
13
+ const undef = undefined;
14
+ const path = parseObjectPath(key);
15
+ let out: any = obj;
16
+ for (const i of path) {
17
+ out = out ? out[i] : undef;
18
+ }
19
+ return out === undef ? def : out;
20
+ }
21
+
22
+ /**
23
+ * It tries to resolve the path of the given object, otherwise it will return OrElse
24
+ *
25
+ * {@link https://github.com/Pouja/typescript-deep-path-safe typescript-deep-path-safe}
26
+ */
27
+ export type ResolveObjectPathType<
28
+ ObjectType,
29
+ Path extends string | symbol | number,
30
+ OrElse,
31
+ > = Path extends keyof ObjectType
32
+ ? ObjectType[Path]
33
+ : Path extends `${infer LeftSide}.${infer RightSide}`
34
+ ? LeftSide extends keyof ObjectType
35
+ ? ResolveObjectPathType<ObjectType[LeftSide], RightSide, OrElse>
36
+ : Path extends `${infer LeftSide}[${number}].${infer RightSide}`
37
+ ? LeftSide extends keyof ObjectType
38
+ ? ObjectType[LeftSide] extends Array<infer U>
39
+ ? ResolveObjectPathType<U, RightSide, OrElse>
40
+ : OrElse
41
+ : OrElse
42
+ : OrElse
43
+ : Path extends `${infer LeftSide}[${number}]`
44
+ ? LeftSide extends keyof ObjectType
45
+ ? ObjectType[LeftSide] extends Array<infer U>
46
+ ? U
47
+ : OrElse
48
+ : OrElse
49
+ : OrElse;
@@ -0,0 +1,18 @@
1
+ import test from 'ava';
2
+ import { parseObjectPath } from './parseObjectPath';
3
+
4
+ test('parseObjectPath', (t) => {
5
+ for (const [k, v] of [
6
+ ['a.b.c', ['a', 'b', 'c']],
7
+ ['a[1].c', ['a', '1', 'c']],
8
+ ['a.1.c', ['a', '1', 'c']],
9
+ ['a[b].c', ['a', 'b', 'c']],
10
+ ['arr[1][0]', ['arr', '1', '0']],
11
+ ['arr.2', ['arr', '2']],
12
+ ['arr[2]', ['arr', '2']],
13
+ [Symbol.toStringTag, [Symbol.toStringTag]],
14
+ [[Symbol.toStringTag], [Symbol.toStringTag]],
15
+ ]) {
16
+ t.deepEqual(parseObjectPath(k), v);
17
+ }
18
+ });
@@ -0,0 +1,40 @@
1
+ export type ObjectKey = string | symbol | number;
2
+ export type ObjectPath = Array<ObjectKey>;
3
+ export type ObjectPathLike = ObjectKey | ObjectPath;
4
+
5
+ /**
6
+ * Parse object path
7
+ *
8
+ * parseObjectPath('a.b.c') // => ['a', 'b', 'c']
9
+ * parseObjectPath('a[0].b') // => ['a', 0, 'b']
10
+ * parseObjectPath('a[0][1]') // => ['a', 0, 1]
11
+ *
12
+ */
13
+ export function parseObjectPath(s: ObjectPathLike): ObjectPath {
14
+ if (typeof s !== 'string') {
15
+ return Array.isArray(s) ? s : [s];
16
+ }
17
+ const parts = s.split('.');
18
+ // fast path
19
+ if (!s.includes('[')) {
20
+ return parts;
21
+ }
22
+
23
+ const result = [];
24
+ for (const part of parts) {
25
+ if (!part.endsWith(']')) {
26
+ result.push(part);
27
+ } else {
28
+ // a[0][1]
29
+ // try parseInt can extend to support a[-1] to use .at access
30
+ const s = part.split('[');
31
+ for (let sub of s) {
32
+ if (sub.endsWith(']')) {
33
+ sub = sub.slice(0, -1);
34
+ }
35
+ result.push(sub);
36
+ }
37
+ }
38
+ }
39
+ return result;
40
+ }