@naturalcycles/js-lib 15.87.0 → 15.89.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/dist/enum.util.d.ts +77 -1
- package/dist/enum.util.js +65 -0
- package/dist/is.util.d.ts +5 -1
- package/dist/is.util.js +9 -0
- package/dist/log/commonLogger.d.ts +10 -2
- package/dist/log/commonLogger.js +41 -9
- package/dist/types.d.ts +18 -0
- package/package.json +1 -1
- package/src/enum.util.ts +124 -1
- package/src/is.util.ts +10 -1
- package/src/log/commonLogger.ts +56 -7
- package/src/types.ts +20 -0
package/dist/enum.util.d.ts
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
|
-
import type { AnyObject, NumberEnum, StringEnum } from './types.js';
|
|
1
|
+
import type { AnyObject, Enum, NumberEnum, StringEnum } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
4
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
5
|
+
*
|
|
6
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
10
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
11
|
+
*
|
|
12
|
+
* Color.RED // 'red'
|
|
13
|
+
* Color.keys // ['RED', 'GREEN']
|
|
14
|
+
* Color.values // ['red', 'green']
|
|
15
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
16
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
17
|
+
* Color.keyOf('red') // 'RED'
|
|
18
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
19
|
+
*
|
|
20
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
21
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
22
|
+
*
|
|
23
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
24
|
+
*
|
|
25
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
26
|
+
*
|
|
27
|
+
* @experimental
|
|
28
|
+
*/
|
|
29
|
+
export declare function _enum<const T extends Record<string, string> | Record<string, number>>(obj: T): EnumObject<T>;
|
|
2
30
|
export declare function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined;
|
|
3
31
|
/**
|
|
4
32
|
* Returns all String keys of a number-enum.
|
|
@@ -91,3 +119,51 @@ export declare function _numberEnumKeyOrUndefined<T extends NumberEnum>(en: T, v
|
|
|
91
119
|
export declare function _numberEnumKey<T extends NumberEnum>(en: T, v: T[keyof T] | undefined | null): keyof T;
|
|
92
120
|
export declare function _stringEnumKeyOrUndefined<T extends StringEnum>(en: T, v: string | undefined | null): keyof T | undefined;
|
|
93
121
|
export declare function _stringEnumKey<T extends StringEnum>(en: T, v: string | undefined | null): keyof T;
|
|
122
|
+
/**
|
|
123
|
+
* @experimental
|
|
124
|
+
*/
|
|
125
|
+
export type EnumObject<T> = Readonly<T> & EnumHelpers<T>;
|
|
126
|
+
/**
|
|
127
|
+
* Helpers that `_enum()` hangs off the returned object.
|
|
128
|
+
* Defined as non-enumerable, so that `Object.keys/values/entries`, `JSON.stringify`
|
|
129
|
+
* and `for..in` still only see the actual Enum members.
|
|
130
|
+
*
|
|
131
|
+
* `keys`/`values`/`entries` are precomputed, and keep the literal types
|
|
132
|
+
* that the `Object.*` equivalents would widen to `string`.
|
|
133
|
+
*
|
|
134
|
+
* @experimental
|
|
135
|
+
*/
|
|
136
|
+
interface EnumHelpers<T> {
|
|
137
|
+
/**
|
|
138
|
+
* Type-only carrier for the union of all values, so that the Enum's type can be declared
|
|
139
|
+
* without a second import: `export type Color = typeof Color.type`.
|
|
140
|
+
*
|
|
141
|
+
* It has no runtime counterpart, reading it as a value returns undefined.
|
|
142
|
+
*/
|
|
143
|
+
readonly type: Enum<T>;
|
|
144
|
+
readonly keys: readonly (keyof T)[];
|
|
145
|
+
readonly values: readonly Enum<T>[];
|
|
146
|
+
readonly entries: EnumEntries<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Type-guard. O(1), backed by a Map.
|
|
149
|
+
*/
|
|
150
|
+
readonly is: (v: unknown) => v is Enum<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Reverse lookup, the counterpart of `Color.RED`. O(1). Throws if the value is not a member.
|
|
153
|
+
*/
|
|
154
|
+
readonly keyOf: (v: Enum<T>) => keyof T;
|
|
155
|
+
/**
|
|
156
|
+
* Like `keyOf`, but takes untrusted input and returns undefined instead of throwing.
|
|
157
|
+
*/
|
|
158
|
+
readonly keyOfOrUndefined: (v: unknown) => keyof T | undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The mapped type (rather than `readonly [keyof T, Enum<T>][]`) keeps each key paired
|
|
162
|
+
* with its own value, instead of allowing any key/value combination.
|
|
163
|
+
*
|
|
164
|
+
* @experimental
|
|
165
|
+
*/
|
|
166
|
+
type EnumEntries<T> = readonly {
|
|
167
|
+
[K in keyof T]: readonly [k: K, v: T[K]];
|
|
168
|
+
}[keyof T][];
|
|
169
|
+
export {};
|
package/dist/enum.util.js
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
3
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
4
|
+
*
|
|
5
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
9
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
10
|
+
*
|
|
11
|
+
* Color.RED // 'red'
|
|
12
|
+
* Color.keys // ['RED', 'GREEN']
|
|
13
|
+
* Color.values // ['red', 'green']
|
|
14
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
15
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
16
|
+
* Color.keyOf('red') // 'RED'
|
|
17
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
18
|
+
*
|
|
19
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
20
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
21
|
+
*
|
|
22
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
23
|
+
*
|
|
24
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
export function _enum(obj) {
|
|
29
|
+
if (RESERVED_KEYS.some(k => Object.hasOwn(obj, k))) {
|
|
30
|
+
throw new Error(`_enum keys must not be named ${RESERVED_KEYS.join(', ')}, as they collide with the helpers`);
|
|
31
|
+
}
|
|
32
|
+
const rawEntries = Object.entries(obj);
|
|
33
|
+
const keys = Object.keys(obj);
|
|
34
|
+
const values = Object.values(obj);
|
|
35
|
+
const entries = rawEntries;
|
|
36
|
+
// Backs both `is` and `keyOf`. On duplicate values the last key wins, like a native number enum.
|
|
37
|
+
const keyByValue = new Map(rawEntries.map(([k, v]) => [v, k]));
|
|
38
|
+
return Object.defineProperties({ ...obj }, {
|
|
39
|
+
keys: { value: keys },
|
|
40
|
+
values: { value: values },
|
|
41
|
+
entries: { value: entries },
|
|
42
|
+
is: { value: (v) => keyByValue.has(v) },
|
|
43
|
+
keyOf: {
|
|
44
|
+
value: (v) => {
|
|
45
|
+
const key = keyByValue.get(v);
|
|
46
|
+
if (key === undefined)
|
|
47
|
+
throw new Error(`_enum keyOf not found for: ${String(v)}`);
|
|
48
|
+
return key;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
keyOfOrUndefined: { value: (v) => keyByValue.get(v) },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keys that `_enum()` reserves for its helpers, and therefore rejects as Enum member names.
|
|
56
|
+
*/
|
|
57
|
+
const RESERVED_KEYS = [
|
|
58
|
+
'type',
|
|
59
|
+
'keys',
|
|
60
|
+
'values',
|
|
61
|
+
'entries',
|
|
62
|
+
'is',
|
|
63
|
+
'keyOf',
|
|
64
|
+
'keyOfOrUndefined',
|
|
65
|
+
];
|
|
1
66
|
export function getEnumType(en) {
|
|
2
67
|
/*
|
|
3
68
|
* enum Foo { A = 1, B = 2 }
|
package/dist/is.util.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyObject, FalsyValue, NullishValue, Primitive } from './types.js';
|
|
1
|
+
import type { AnyObject, FalsyValue, NullishValue, PlainObject, Primitive } from './types.js';
|
|
2
2
|
type Nullish<T> = T extends NullishValue ? T : never;
|
|
3
3
|
type Truthy<T> = T extends FalsyValue ? never : T;
|
|
4
4
|
type Falsy<T> = T extends FalsyValue ? T : never;
|
|
@@ -25,6 +25,10 @@ export declare const _isFalsy: <T>(v: T) => v is Falsy<T>;
|
|
|
25
25
|
* Currently treats RegEx as Object too, e.g _isObject(/some/) === true
|
|
26
26
|
*/
|
|
27
27
|
export declare function _isObject(obj: any): obj is AnyObject;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if item is an object literal: not an Array, Error, RegExp or class instance.
|
|
30
|
+
*/
|
|
31
|
+
export declare function _isPlainObject(v: any): v is PlainObject;
|
|
28
32
|
export declare function _isPrimitive(v: any): v is Primitive;
|
|
29
33
|
export declare function _isEmptyObject(obj: AnyObject): boolean;
|
|
30
34
|
export declare function _isNotEmptyObject(obj: AnyObject): boolean;
|
package/dist/is.util.js
CHANGED
|
@@ -23,6 +23,15 @@ export const _isFalsy = (v) => !v;
|
|
|
23
23
|
export function _isObject(obj) {
|
|
24
24
|
return (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) || false;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns true if item is an object literal: not an Array, Error, RegExp or class instance.
|
|
28
|
+
*/
|
|
29
|
+
export function _isPlainObject(v) {
|
|
30
|
+
if (typeof v !== 'object' || v === null)
|
|
31
|
+
return false;
|
|
32
|
+
const proto = Object.getPrototypeOf(v);
|
|
33
|
+
return proto === Object.prototype || proto === null;
|
|
34
|
+
}
|
|
26
35
|
export function _isPrimitive(v) {
|
|
27
36
|
return (v === null ||
|
|
28
37
|
v === undefined ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MutateOptions } from '../types.js';
|
|
1
|
+
import type { AnyObject, MutateOptions } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* These levels follow console.* naming,
|
|
4
4
|
* so you can use console[level] safely.
|
|
@@ -51,7 +51,15 @@ export declare function commonLoggerPipe(loggers: CommonLogger[]): CommonLogger;
|
|
|
51
51
|
* Creates a "child" CommonLogger with prefix (one or multiple).
|
|
52
52
|
*/
|
|
53
53
|
export declare function commonLoggerPrefix(logger: CommonLogger, ...prefixes: any[]): CommonLogger;
|
|
54
|
+
export type CommonLogSink = CommonLogger | CommonLogWithLevelFunction;
|
|
55
|
+
export interface CommonLoggerWithContext extends CommonLogger {
|
|
56
|
+
child: (context: AnyObject) => CommonLoggerWithContext;
|
|
57
|
+
}
|
|
54
58
|
/**
|
|
55
59
|
* Creates a CommonLogger from a single function that takes `level` and `args`.
|
|
60
|
+
*
|
|
61
|
+
* With a context, every call emits one object: plain-object args merged in, first Error as `err`,
|
|
62
|
+
* the rest joined into `msg`.
|
|
56
63
|
*/
|
|
57
|
-
export declare function commonLoggerCreate(
|
|
64
|
+
export declare function commonLoggerCreate(sink: CommonLogSink): CommonLogger;
|
|
65
|
+
export declare function commonLoggerCreate(sink: CommonLogSink, context: AnyObject): CommonLoggerWithContext;
|
package/dist/log/commonLogger.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _isPlainObject } from '../is.util.js';
|
|
1
2
|
// copy-pasted to avoid weird circular dependency
|
|
2
3
|
const _noop = (..._args) => undefined;
|
|
3
4
|
export const commonLogLevelNumber = {
|
|
@@ -72,14 +73,45 @@ export function commonLoggerPrefix(logger, ...prefixes) {
|
|
|
72
73
|
error: (...args) => logger.error(...prefixes, ...args),
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
export function commonLoggerCreate(sink, context) {
|
|
77
|
+
const fn = typeof sink === 'function' ? sink : (level, args) => sink[level](...args);
|
|
78
|
+
if (!context) {
|
|
79
|
+
return {
|
|
80
|
+
debug: (...args) => fn('debug', args),
|
|
81
|
+
log: (...args) => fn('log', args),
|
|
82
|
+
warn: (...args) => fn('warn', args),
|
|
83
|
+
error: (...args) => fn('error', args),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const ctx = { ...context };
|
|
87
|
+
const logger = {
|
|
88
|
+
debug: (...args) => fn('debug', [toLogEntry(ctx, args)]),
|
|
89
|
+
log: (...args) => fn('log', [toLogEntry(ctx, args)]),
|
|
90
|
+
warn: (...args) => fn('warn', [toLogEntry(ctx, args)]),
|
|
91
|
+
error: (...args) => fn('error', [toLogEntry(ctx, args)]),
|
|
92
|
+
child: c => commonLoggerCreate(fn, { ...ctx, ...c }),
|
|
84
93
|
};
|
|
94
|
+
return logger;
|
|
95
|
+
}
|
|
96
|
+
function toLogEntry(context, args) {
|
|
97
|
+
const entry = { ...context };
|
|
98
|
+
let err;
|
|
99
|
+
let msg;
|
|
100
|
+
for (const arg of args) {
|
|
101
|
+
if (_isPlainObject(arg)) {
|
|
102
|
+
Object.assign(entry, arg);
|
|
103
|
+
}
|
|
104
|
+
else if (!err && arg instanceof Error) {
|
|
105
|
+
err = arg;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const s = String(arg);
|
|
109
|
+
msg = msg === undefined ? s : `${msg} ${s}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (err)
|
|
113
|
+
entry['err'] = err;
|
|
114
|
+
if (msg !== undefined)
|
|
115
|
+
entry['msg'] = msg;
|
|
116
|
+
return entry;
|
|
85
117
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,9 +25,27 @@ export interface StringMap<T = string> {
|
|
|
25
25
|
* Because `object` type is not safe/recommended to be used (e.g discouraged by eslint-typescript due to: https://github.com/microsoft/TypeScript/issues/21732)
|
|
26
26
|
*/
|
|
27
27
|
export type AnyObject = Record<string, any>;
|
|
28
|
+
/**
|
|
29
|
+
* Object literal, with a prototype of `Object.prototype` or `null`. What `_isPlainObject` narrows to.
|
|
30
|
+
*/
|
|
31
|
+
export type PlainObject = Branded<AnyObject, 'PlainObject'>;
|
|
28
32
|
export type AnyEnum = NumberEnum;
|
|
29
33
|
export type NumberEnum = Record<string, number | string>;
|
|
30
34
|
export type StringEnum = Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* Union of all values of an Enum-like object: both native TypeScript enums and `_enum()` objects.
|
|
37
|
+
*
|
|
38
|
+
* For an `_enum()` object prefer `typeof Color.type`, which needs no import.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* enum Color { RED = 'red', GREEN = 'green' }
|
|
42
|
+
* type ColorValue = Enum<typeof Color> // 'red' | 'green'
|
|
43
|
+
*
|
|
44
|
+
* @experimental
|
|
45
|
+
*/
|
|
46
|
+
export type Enum<T> = T extends {
|
|
47
|
+
values: readonly (infer V)[];
|
|
48
|
+
} ? V : T[keyof T];
|
|
31
49
|
export type CreatedUpdated = {
|
|
32
50
|
created: UnixTimestamp;
|
|
33
51
|
updated: UnixTimestamp;
|
package/package.json
CHANGED
package/src/enum.util.ts
CHANGED
|
@@ -1,4 +1,79 @@
|
|
|
1
|
-
import type { AnyObject, NumberEnum, StringEnum } from './types.js'
|
|
1
|
+
import type { AnyObject, Enum, NumberEnum, StringEnum } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a runtime Enum object. A replacement for a native TypeScript enum,
|
|
5
|
+
* which is not supported by type-stripping runtimes (e.g `node --experimental-strip-types`).
|
|
6
|
+
*
|
|
7
|
+
* Declare the type with the same name as the const, to mimic how native enums merge value and type.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* export const Color = _enum({ RED: 'red', GREEN: 'green' })
|
|
11
|
+
* export type Color = typeof Color.type // 'red' | 'green'
|
|
12
|
+
*
|
|
13
|
+
* Color.RED // 'red'
|
|
14
|
+
* Color.keys // ['RED', 'GREEN']
|
|
15
|
+
* Color.values // ['red', 'green']
|
|
16
|
+
* Color.entries // [['RED', 'red'], ['GREEN', 'green']]
|
|
17
|
+
* Color.is(x) // type-guard for 'red' | 'green'
|
|
18
|
+
* Color.keyOf('red') // 'RED'
|
|
19
|
+
* JSON.stringify(Color) // '{"RED":"red","GREEN":"green"}', no reverse-mapping, no helper leakage
|
|
20
|
+
*
|
|
21
|
+
* The result doesn't satisfy `StringEnum`/`NumberEnum`, so the `_stringEnum*`/`_numberEnum*`
|
|
22
|
+
* functions don't accept it. Use the helpers on the returned object instead.
|
|
23
|
+
*
|
|
24
|
+
* Nothing is frozen, immutability is expressed through `readonly` types only.
|
|
25
|
+
*
|
|
26
|
+
* Throws if the input contains a key that collides with one of the helper names.
|
|
27
|
+
*
|
|
28
|
+
* @experimental
|
|
29
|
+
*/
|
|
30
|
+
export function _enum<const T extends Record<string, string> | Record<string, number>>(
|
|
31
|
+
obj: T,
|
|
32
|
+
): EnumObject<T> {
|
|
33
|
+
if (RESERVED_KEYS.some(k => Object.hasOwn(obj, k))) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`_enum keys must not be named ${RESERVED_KEYS.join(', ')}, as they collide with the helpers`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const rawEntries = Object.entries(obj)
|
|
40
|
+
const keys = Object.keys(obj) as (keyof T)[]
|
|
41
|
+
const values = Object.values(obj) as Enum<T>[]
|
|
42
|
+
const entries = rawEntries as unknown as EnumEntries<T>
|
|
43
|
+
// Backs both `is` and `keyOf`. On duplicate values the last key wins, like a native number enum.
|
|
44
|
+
const keyByValue = new Map<unknown, keyof T>(rawEntries.map(([k, v]) => [v, k]))
|
|
45
|
+
|
|
46
|
+
return Object.defineProperties(
|
|
47
|
+
{ ...obj },
|
|
48
|
+
{
|
|
49
|
+
keys: { value: keys },
|
|
50
|
+
values: { value: values },
|
|
51
|
+
entries: { value: entries },
|
|
52
|
+
is: { value: (v: unknown): v is Enum<T> => keyByValue.has(v) },
|
|
53
|
+
keyOf: {
|
|
54
|
+
value: (v: Enum<T>): keyof T => {
|
|
55
|
+
const key = keyByValue.get(v)
|
|
56
|
+
if (key === undefined) throw new Error(`_enum keyOf not found for: ${String(v)}`)
|
|
57
|
+
return key
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
keyOfOrUndefined: { value: (v: unknown): keyof T | undefined => keyByValue.get(v) },
|
|
61
|
+
},
|
|
62
|
+
) as EnumObject<T>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Keys that `_enum()` reserves for its helpers, and therefore rejects as Enum member names.
|
|
67
|
+
*/
|
|
68
|
+
const RESERVED_KEYS = [
|
|
69
|
+
'type',
|
|
70
|
+
'keys',
|
|
71
|
+
'values',
|
|
72
|
+
'entries',
|
|
73
|
+
'is',
|
|
74
|
+
'keyOf',
|
|
75
|
+
'keyOfOrUndefined',
|
|
76
|
+
] as const
|
|
2
77
|
|
|
3
78
|
export function getEnumType(en: AnyObject): 'StringEnum' | 'NumberEnum' | undefined {
|
|
4
79
|
/*
|
|
@@ -226,3 +301,51 @@ export function _stringEnumKey<T extends StringEnum>(en: T, v: string | undefine
|
|
|
226
301
|
if (!r) throw new Error(`_stringEnumKey not found for: ${v}`)
|
|
227
302
|
return r
|
|
228
303
|
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @experimental
|
|
307
|
+
*/
|
|
308
|
+
export type EnumObject<T> = Readonly<T> & EnumHelpers<T>
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Helpers that `_enum()` hangs off the returned object.
|
|
312
|
+
* Defined as non-enumerable, so that `Object.keys/values/entries`, `JSON.stringify`
|
|
313
|
+
* and `for..in` still only see the actual Enum members.
|
|
314
|
+
*
|
|
315
|
+
* `keys`/`values`/`entries` are precomputed, and keep the literal types
|
|
316
|
+
* that the `Object.*` equivalents would widen to `string`.
|
|
317
|
+
*
|
|
318
|
+
* @experimental
|
|
319
|
+
*/
|
|
320
|
+
interface EnumHelpers<T> {
|
|
321
|
+
/**
|
|
322
|
+
* Type-only carrier for the union of all values, so that the Enum's type can be declared
|
|
323
|
+
* without a second import: `export type Color = typeof Color.type`.
|
|
324
|
+
*
|
|
325
|
+
* It has no runtime counterpart, reading it as a value returns undefined.
|
|
326
|
+
*/
|
|
327
|
+
readonly type: Enum<T>
|
|
328
|
+
readonly keys: readonly (keyof T)[]
|
|
329
|
+
readonly values: readonly Enum<T>[]
|
|
330
|
+
readonly entries: EnumEntries<T>
|
|
331
|
+
/**
|
|
332
|
+
* Type-guard. O(1), backed by a Map.
|
|
333
|
+
*/
|
|
334
|
+
readonly is: (v: unknown) => v is Enum<T>
|
|
335
|
+
/**
|
|
336
|
+
* Reverse lookup, the counterpart of `Color.RED`. O(1). Throws if the value is not a member.
|
|
337
|
+
*/
|
|
338
|
+
readonly keyOf: (v: Enum<T>) => keyof T
|
|
339
|
+
/**
|
|
340
|
+
* Like `keyOf`, but takes untrusted input and returns undefined instead of throwing.
|
|
341
|
+
*/
|
|
342
|
+
readonly keyOfOrUndefined: (v: unknown) => keyof T | undefined
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The mapped type (rather than `readonly [keyof T, Enum<T>][]`) keeps each key paired
|
|
347
|
+
* with its own value, instead of allowing any key/value combination.
|
|
348
|
+
*
|
|
349
|
+
* @experimental
|
|
350
|
+
*/
|
|
351
|
+
type EnumEntries<T> = readonly { [K in keyof T]: readonly [k: K, v: T[K]] }[keyof T][]
|
package/src/is.util.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyObject, FalsyValue, NullishValue, Primitive } from './types.js'
|
|
1
|
+
import type { AnyObject, FalsyValue, NullishValue, PlainObject, Primitive } from './types.js'
|
|
2
2
|
|
|
3
3
|
type Nullish<T> = T extends NullishValue ? T : never
|
|
4
4
|
type Truthy<T> = T extends FalsyValue ? never : T
|
|
@@ -32,6 +32,15 @@ export function _isObject(obj: any): obj is AnyObject {
|
|
|
32
32
|
return (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) || false
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if item is an object literal: not an Array, Error, RegExp or class instance.
|
|
37
|
+
*/
|
|
38
|
+
export function _isPlainObject(v: any): v is PlainObject {
|
|
39
|
+
if (typeof v !== 'object' || v === null) return false
|
|
40
|
+
const proto = Object.getPrototypeOf(v)
|
|
41
|
+
return proto === Object.prototype || proto === null
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
export function _isPrimitive(v: any): v is Primitive {
|
|
36
45
|
return (
|
|
37
46
|
v === null ||
|
package/src/log/commonLogger.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { _isPlainObject } from '../is.util.js'
|
|
2
|
+
import type { AnyObject, MutateOptions } from '../types.js'
|
|
2
3
|
|
|
3
4
|
// copy-pasted to avoid weird circular dependency
|
|
4
5
|
const _noop = (..._args: any[]): undefined => undefined
|
|
@@ -125,14 +126,62 @@ export function commonLoggerPrefix(logger: CommonLogger, ...prefixes: any[]): Co
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
export type CommonLogSink = CommonLogger | CommonLogWithLevelFunction
|
|
130
|
+
|
|
131
|
+
export interface CommonLoggerWithContext extends CommonLogger {
|
|
132
|
+
child: (context: AnyObject) => CommonLoggerWithContext
|
|
133
|
+
}
|
|
134
|
+
|
|
128
135
|
/**
|
|
129
136
|
* Creates a CommonLogger from a single function that takes `level` and `args`.
|
|
137
|
+
*
|
|
138
|
+
* With a context, every call emits one object: plain-object args merged in, first Error as `err`,
|
|
139
|
+
* the rest joined into `msg`.
|
|
130
140
|
*/
|
|
131
|
-
export function commonLoggerCreate(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
export function commonLoggerCreate(sink: CommonLogSink): CommonLogger
|
|
142
|
+
export function commonLoggerCreate(sink: CommonLogSink, context: AnyObject): CommonLoggerWithContext
|
|
143
|
+
export function commonLoggerCreate(sink: CommonLogSink, context?: AnyObject): CommonLogger {
|
|
144
|
+
const fn: CommonLogWithLevelFunction =
|
|
145
|
+
typeof sink === 'function' ? sink : (level, args) => sink[level](...args)
|
|
146
|
+
|
|
147
|
+
if (!context) {
|
|
148
|
+
return {
|
|
149
|
+
debug: (...args) => fn('debug', args),
|
|
150
|
+
log: (...args) => fn('log', args),
|
|
151
|
+
warn: (...args) => fn('warn', args),
|
|
152
|
+
error: (...args) => fn('error', args),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const ctx: AnyObject = { ...context }
|
|
157
|
+
|
|
158
|
+
const logger: CommonLoggerWithContext = {
|
|
159
|
+
debug: (...args) => fn('debug', [toLogEntry(ctx, args)]),
|
|
160
|
+
log: (...args) => fn('log', [toLogEntry(ctx, args)]),
|
|
161
|
+
warn: (...args) => fn('warn', [toLogEntry(ctx, args)]),
|
|
162
|
+
error: (...args) => fn('error', [toLogEntry(ctx, args)]),
|
|
163
|
+
child: c => commonLoggerCreate(fn, { ...ctx, ...c }),
|
|
164
|
+
}
|
|
165
|
+
return logger
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function toLogEntry(context: AnyObject, args: any[]): AnyObject {
|
|
169
|
+
const entry: AnyObject = { ...context }
|
|
170
|
+
let err: Error | undefined
|
|
171
|
+
let msg: string | undefined
|
|
172
|
+
|
|
173
|
+
for (const arg of args) {
|
|
174
|
+
if (_isPlainObject(arg)) {
|
|
175
|
+
Object.assign(entry, arg)
|
|
176
|
+
} else if (!err && arg instanceof Error) {
|
|
177
|
+
err = arg
|
|
178
|
+
} else {
|
|
179
|
+
const s = String(arg)
|
|
180
|
+
msg = msg === undefined ? s : `${msg} ${s}`
|
|
181
|
+
}
|
|
137
182
|
}
|
|
183
|
+
|
|
184
|
+
if (err) entry['err'] = err
|
|
185
|
+
if (msg !== undefined) entry['msg'] = msg
|
|
186
|
+
return entry
|
|
138
187
|
}
|
package/src/types.ts
CHANGED
|
@@ -30,10 +30,30 @@ export interface StringMap<T = string> {
|
|
|
30
30
|
*/
|
|
31
31
|
export type AnyObject = Record<string, any>
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Object literal, with a prototype of `Object.prototype` or `null`. What `_isPlainObject` narrows to.
|
|
35
|
+
*/
|
|
36
|
+
export type PlainObject = Branded<AnyObject, 'PlainObject'>
|
|
37
|
+
|
|
33
38
|
export type AnyEnum = NumberEnum
|
|
34
39
|
export type NumberEnum = Record<string, number | string>
|
|
35
40
|
export type StringEnum = Record<string, string>
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Union of all values of an Enum-like object: both native TypeScript enums and `_enum()` objects.
|
|
44
|
+
*
|
|
45
|
+
* For an `_enum()` object prefer `typeof Color.type`, which needs no import.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* enum Color { RED = 'red', GREEN = 'green' }
|
|
49
|
+
* type ColorValue = Enum<typeof Color> // 'red' | 'green'
|
|
50
|
+
*
|
|
51
|
+
* @experimental
|
|
52
|
+
*/
|
|
53
|
+
// The first branch is for `_enum()` objects, whose helpers `T[keyof T]` would otherwise pull into
|
|
54
|
+
// the union. Native enums never match it, since their values are `string | number`, never an array.
|
|
55
|
+
export type Enum<T> = T extends { values: readonly (infer V)[] } ? V : T[keyof T]
|
|
56
|
+
|
|
37
57
|
// oxlint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
38
58
|
export type CreatedUpdated = {
|
|
39
59
|
created: UnixTimestamp
|