@dungarees/core 0.11.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/application.d.ts +66 -0
- package/application.js +80 -0
- package/fake.d.ts +17 -0
- package/fake.js +29 -0
- package/fake.test.d.ts +1 -0
- package/fake.test.js +53 -0
- package/marbles-vitest.d.ts +55 -0
- package/marbles-vitest.js +127 -0
- package/marbles-vitest.test.d.ts +1 -0
- package/marbles-vitest.test.js +104 -0
- package/marbles.d.ts +27 -0
- package/marbles.js +53 -0
- package/marbles.test.d.ts +1 -0
- package/marbles.test.js +36 -0
- package/package.json +481 -0
- package/type-util.d.ts +50 -0
- package/type-util.js +1 -0
- package/type-util.test.d.ts +1 -0
- package/type-util.test.js +135 -0
- package/url.d.ts +13 -0
- package/url.js +24 -0
- package/url.test.d.ts +1 -0
- package/url.test.js +48 -0
- package/util.d.ts +53 -0
- package/util.js +236 -0
- package/util.test.d.ts +1 -0
- package/util.test.js +202 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { assert } from 'tsafe';
|
|
2
|
+
import { test } from 'vitest';
|
|
3
|
+
test('Split<PATH, DELIMITER>', () => {
|
|
4
|
+
assert();
|
|
5
|
+
assert();
|
|
6
|
+
assert();
|
|
7
|
+
assert();
|
|
8
|
+
assert();
|
|
9
|
+
});
|
|
10
|
+
test('SplitObjectPath<PATH>', () => {
|
|
11
|
+
assert();
|
|
12
|
+
assert();
|
|
13
|
+
assert();
|
|
14
|
+
assert();
|
|
15
|
+
});
|
|
16
|
+
test('SplitFilePath<PATH>', () => {
|
|
17
|
+
assert();
|
|
18
|
+
assert();
|
|
19
|
+
assert();
|
|
20
|
+
assert();
|
|
21
|
+
});
|
|
22
|
+
test('StringLiteral<LITERAL>', () => {
|
|
23
|
+
const literal = 'asd';
|
|
24
|
+
assert();
|
|
25
|
+
// @ts-expect-error it has to be a literal
|
|
26
|
+
const nonLiteral = 'asd';
|
|
27
|
+
assert();
|
|
28
|
+
});
|
|
29
|
+
test('ObjectWithStringLiteralKey<KEY, VALUE>', () => {
|
|
30
|
+
const obj = {
|
|
31
|
+
a: 1,
|
|
32
|
+
};
|
|
33
|
+
assert();
|
|
34
|
+
// @ts-expect-error it has to be a literal
|
|
35
|
+
const nonLiteral = {
|
|
36
|
+
a: 1,
|
|
37
|
+
};
|
|
38
|
+
assert();
|
|
39
|
+
});
|
|
40
|
+
test('JsonType', () => {
|
|
41
|
+
const json = {
|
|
42
|
+
a: 1,
|
|
43
|
+
b: '2',
|
|
44
|
+
c: true,
|
|
45
|
+
d: null,
|
|
46
|
+
e: [1, '2', true, null, [1, '2', true, null]],
|
|
47
|
+
};
|
|
48
|
+
assert();
|
|
49
|
+
// @ts-expect-error it cannot be a function
|
|
50
|
+
const nonJson = {
|
|
51
|
+
f: () => { },
|
|
52
|
+
};
|
|
53
|
+
assert();
|
|
54
|
+
});
|
|
55
|
+
test('TokenNonEmptyString<TOKEN>', () => {
|
|
56
|
+
const token = 'a';
|
|
57
|
+
assert();
|
|
58
|
+
// @ts-expect-error it cannot be empty
|
|
59
|
+
const nonToken = '';
|
|
60
|
+
assert();
|
|
61
|
+
});
|
|
62
|
+
test('Guard<TYPE>', () => {
|
|
63
|
+
const guard = (arg) => arg === 1;
|
|
64
|
+
assert();
|
|
65
|
+
// @ts-expect-error it has to be a guard
|
|
66
|
+
const nonGuard = (arg) => arg === 1;
|
|
67
|
+
assert();
|
|
68
|
+
});
|
|
69
|
+
test('GetGuarded<GUARD>', () => {
|
|
70
|
+
const guard = (arg) => arg === 1;
|
|
71
|
+
assert();
|
|
72
|
+
});
|
|
73
|
+
test('EntryTuples', () => {
|
|
74
|
+
assert();
|
|
75
|
+
assert();
|
|
76
|
+
assert();
|
|
77
|
+
assert();
|
|
78
|
+
});
|
|
79
|
+
test('FilterRecord<RECORD, TYPE>', () => {
|
|
80
|
+
// TODO remove the keys as well
|
|
81
|
+
assert();
|
|
82
|
+
});
|
|
83
|
+
test('GetAllPaths<OBJECT>', () => {
|
|
84
|
+
assert();
|
|
85
|
+
});
|
|
86
|
+
test('GetValuesByPath<OBJECT, PATH>', () => {
|
|
87
|
+
assert();
|
|
88
|
+
});
|
|
89
|
+
test('JoinArray<STRINGS, DELIMITER>', () => {
|
|
90
|
+
assert();
|
|
91
|
+
assert();
|
|
92
|
+
});
|
|
93
|
+
test('FromCamelCase<STRING>', () => {
|
|
94
|
+
assert();
|
|
95
|
+
assert();
|
|
96
|
+
assert();
|
|
97
|
+
});
|
|
98
|
+
test('ToCamelCase<STRING[]>', () => {
|
|
99
|
+
assert();
|
|
100
|
+
assert();
|
|
101
|
+
});
|
|
102
|
+
test('FromKebabCase<STRING>', () => {
|
|
103
|
+
assert();
|
|
104
|
+
assert();
|
|
105
|
+
assert();
|
|
106
|
+
});
|
|
107
|
+
test('ToKebabCase<STRING[]>', () => {
|
|
108
|
+
assert();
|
|
109
|
+
assert();
|
|
110
|
+
});
|
|
111
|
+
test('FromSnakeCase<STRING>', () => {
|
|
112
|
+
assert();
|
|
113
|
+
assert();
|
|
114
|
+
assert();
|
|
115
|
+
});
|
|
116
|
+
test('ToSnakeCase<STRING[]>', () => {
|
|
117
|
+
assert();
|
|
118
|
+
assert();
|
|
119
|
+
});
|
|
120
|
+
test('Mutable<TYPE>', () => {
|
|
121
|
+
assert();
|
|
122
|
+
assert();
|
|
123
|
+
});
|
|
124
|
+
test('PartialBesides<OBJECT, KEYS>', () => {
|
|
125
|
+
assert();
|
|
126
|
+
});
|
|
127
|
+
test('DeepPartial<T> - single level', () => {
|
|
128
|
+
assert();
|
|
129
|
+
});
|
|
130
|
+
test('DeepPartial<T> - nested props', () => {
|
|
131
|
+
assert();
|
|
132
|
+
});
|
|
133
|
+
test('SyncFunctionToAsync<FUNC>', () => {
|
|
134
|
+
assert();
|
|
135
|
+
});
|
package/url.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type UrlConfig = {
|
|
2
|
+
protocol: string;
|
|
3
|
+
hostname: string;
|
|
4
|
+
pathname?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
search?: UrlSearchConfig | undefined;
|
|
7
|
+
hash?: string;
|
|
8
|
+
};
|
|
9
|
+
export type UrlSearchConfig = Record<string, number | string | boolean>;
|
|
10
|
+
export declare const stringifyUrl: (config: UrlConfig) => string;
|
|
11
|
+
export declare const parseUrl: (url: `${string}://${string}`) => UrlConfig;
|
|
12
|
+
export type Split<STRING, CHARACTER extends string> = STRING extends `${infer S1}${CHARACTER}${infer S2}` ? [S1, S2] : never;
|
|
13
|
+
export type Url = string;
|
package/url.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const DEFAULT_PORT = 80;
|
|
2
|
+
export const stringifyUrl = (config) => {
|
|
3
|
+
const { protocol, hostname, search = {}, ...rest } = config;
|
|
4
|
+
const url = new URL(`${protocol}://${hostname}`);
|
|
5
|
+
let key;
|
|
6
|
+
for (key in rest) {
|
|
7
|
+
url[key] = String(rest[key]);
|
|
8
|
+
}
|
|
9
|
+
for (const [k, v] of Object.entries(search)) {
|
|
10
|
+
url.searchParams.append(k, String(v));
|
|
11
|
+
}
|
|
12
|
+
return url.toString();
|
|
13
|
+
};
|
|
14
|
+
export const parseUrl = (url) => {
|
|
15
|
+
const { protocol, hostname, pathname, port, searchParams, hash } = new URL(url);
|
|
16
|
+
return {
|
|
17
|
+
protocol: protocol.at(-1) === ':' ? protocol.slice(0, -1) : protocol,
|
|
18
|
+
hostname,
|
|
19
|
+
pathname,
|
|
20
|
+
port: port?.length > 0 ? Number(port) : DEFAULT_PORT,
|
|
21
|
+
hash: hash[0] === '#' ? hash.slice(1) : hash,
|
|
22
|
+
search: Object.fromEntries(searchParams.entries()),
|
|
23
|
+
};
|
|
24
|
+
};
|
package/url.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/url.test.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseUrl, stringifyUrl } from './url.js';
|
|
2
|
+
import { expect, test } from 'vitest';
|
|
3
|
+
test('stringify full url', () => {
|
|
4
|
+
expect(stringifyUrl({
|
|
5
|
+
protocol: 'https',
|
|
6
|
+
hostname: 'localhost',
|
|
7
|
+
pathname: '/path',
|
|
8
|
+
port: 8080,
|
|
9
|
+
search: {
|
|
10
|
+
a: '1',
|
|
11
|
+
b: '2',
|
|
12
|
+
},
|
|
13
|
+
hash: 'hash',
|
|
14
|
+
})).toBe('https://localhost:8080/path?a=1&b=2#hash');
|
|
15
|
+
});
|
|
16
|
+
test('stringify partial url', () => {
|
|
17
|
+
expect(stringifyUrl({
|
|
18
|
+
protocol: 'https',
|
|
19
|
+
hostname: 'localhost',
|
|
20
|
+
})).toBe('https://localhost/');
|
|
21
|
+
});
|
|
22
|
+
test('parse full url', () => {
|
|
23
|
+
expect(parseUrl('https://localhost:8080/path?a=1&b=2#hash')).toEqual({
|
|
24
|
+
protocol: 'https',
|
|
25
|
+
hostname: 'localhost',
|
|
26
|
+
pathname: '/path',
|
|
27
|
+
port: 8080,
|
|
28
|
+
search: {
|
|
29
|
+
a: '1',
|
|
30
|
+
b: '2',
|
|
31
|
+
},
|
|
32
|
+
hash: 'hash',
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
test('parse full url', () => {
|
|
36
|
+
expect(parseUrl('https://localhost/')).toEqual({
|
|
37
|
+
protocol: 'https',
|
|
38
|
+
hostname: 'localhost',
|
|
39
|
+
pathname: '/',
|
|
40
|
+
port: 80,
|
|
41
|
+
search: {},
|
|
42
|
+
hash: '',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
test('Url type', () => {
|
|
46
|
+
const url = 'https://host:8080/path/subpath/?a=1&b=2#hash';
|
|
47
|
+
expect(url).toBe(url);
|
|
48
|
+
});
|
package/util.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Guard, JsonType } from './type-util.ts';
|
|
2
|
+
import type { Call, Fn } from 'hotscript';
|
|
3
|
+
export declare const typeKey: unique symbol;
|
|
4
|
+
export declare const makeObjectFromStringLiteral: <KEY, VALUE>(key: StringLiteral<KEY>, value: VALUE) => ObjectWithStringLiteralKey<KEY, VALUE>;
|
|
5
|
+
export declare const split: <const STRING extends string, const DELIMITER extends string>(str: STRING, delimiter: DELIMITER) => Split<STRING, DELIMITER>;
|
|
6
|
+
export declare const join: <const KEYS extends string[], const DELIMITER extends string>(keys: KEYS, delimiter: DELIMITER) => JoinArray<KEYS, DELIMITER>;
|
|
7
|
+
export declare const capitalize: <const STRING extends string>(str: STRING) => Capitalize<STRING>;
|
|
8
|
+
export declare const pluralize: (str: string, value: number, prefix?: boolean) => string;
|
|
9
|
+
export declare const toCamelCase: <const SEGMENTS extends readonly string[]>(segments: SEGMENTS) => ToCamelCase<SEGMENTS>;
|
|
10
|
+
export declare const kebabCase2camelCase: <const KEBAB_CASE extends string>(kebabCase: KEBAB_CASE) => ToCamelCase<FromKebabCase<KEBAB_CASE>>;
|
|
11
|
+
export declare const camelCase2kebabCase: <const CAMEL_CASE extends string>(camelCase: CAMEL_CASE) => ToKebabCase<FromCamelCase<CAMEL_CASE>>;
|
|
12
|
+
export declare const isDeepEqual: (a: any, b: any) => boolean;
|
|
13
|
+
export declare const deepEqualPartial: (actual: any, expected: any) => boolean;
|
|
14
|
+
export declare const findByPattern: <MATCH extends JsonType, const PATTERNS extends readonly FindByPatterns[]>(patterns: PATTERNS, itemToMatch: MATCH) => GetValueFromPatternList<PATTERNS> | undefined;
|
|
15
|
+
export declare const optionalPatternToList: <const VALUE>(value: VALUE) => OptionalPatternToList<VALUE>;
|
|
16
|
+
export type FindByPatterns = FindByPattern | FindByPartialPattern | FindByDefault;
|
|
17
|
+
export type FindByPattern<VALUE = any, PATTERN extends JsonType = JsonType> = {
|
|
18
|
+
readonly value: VALUE;
|
|
19
|
+
readonly pattern: PATTERN;
|
|
20
|
+
};
|
|
21
|
+
export type FindByPartialPattern<VALUE = any, PATTERN extends JsonType = JsonType> = {
|
|
22
|
+
readonly value: VALUE;
|
|
23
|
+
readonly patternPartial: Partial<PATTERN>;
|
|
24
|
+
};
|
|
25
|
+
export type FindByDefault<VALUE = any> = {
|
|
26
|
+
readonly value: VALUE;
|
|
27
|
+
};
|
|
28
|
+
export type OptionalPatternList<VALUE, PATTERN extends JsonType> = VALUE | Array<FindByPattern<VALUE, PATTERN> | FindByPartialPattern<VALUE, PATTERN> | FindByDefault<VALUE>>;
|
|
29
|
+
export type GetValueFromPatternList<PATTERNS extends readonly FindByPatterns[]> = PATTERNS[number]['value'];
|
|
30
|
+
export type OptionalPatternToList<VALUE> = VALUE extends readonly FindByPatterns[] ? VALUE : readonly [FindByDefault<VALUE>];
|
|
31
|
+
export declare const isDefined: <T>(value: T) => value is NonNullable<T>;
|
|
32
|
+
export declare const assertDefined: <T>(value: T, messageOption?: ErrorMessage<T>) => NonNullable<T>;
|
|
33
|
+
export declare const assertTypeByGuard: <T, V>({ value, guard, message, }: AssertTypeByGuardArg<T, V>) => V & T;
|
|
34
|
+
type AssertPredicateArg<T> = {
|
|
35
|
+
value: T;
|
|
36
|
+
predicate: (value: T) => boolean;
|
|
37
|
+
message: ErrorMessage<T>;
|
|
38
|
+
};
|
|
39
|
+
export declare const assertPredicate: <T>({ value, predicate, message, }: AssertPredicateArg<T>) => T;
|
|
40
|
+
export declare const assertImpossible: (message: string) => never;
|
|
41
|
+
type ErrorMessage<T> = string | ((value: T) => string);
|
|
42
|
+
type AssertTypeByGuardArg<T, V> = {
|
|
43
|
+
value: V;
|
|
44
|
+
guard: Guard<T>;
|
|
45
|
+
message: ErrorMessage<V>;
|
|
46
|
+
};
|
|
47
|
+
export declare const unPrototypeProperties: <const T extends Record<string, any>, const KEYS extends keyof T>(obj: T, keys: KEYS[]) => Pick<T, KEYS>;
|
|
48
|
+
type MapConstResult<ARRAY extends readonly any[], F extends Fn> = {
|
|
49
|
+
readonly [K in keyof ARRAY]: Call<F, ARRAY[K]>;
|
|
50
|
+
};
|
|
51
|
+
export declare const mapConst: <const ARRAY extends readonly any[]>(array: ARRAY) => <F extends Fn>(transformer: (value: ARRAY[number]) => unknown) => MapConstResult<ARRAY, F>;
|
|
52
|
+
export declare const objectFromConstEntries: <const ENTRIES extends readonly (readonly [string, any])[]>(entries: ENTRIES) => { [K in ENTRIES[number] as K[0]]: K[1]; };
|
|
53
|
+
export {};
|
package/util.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
export const typeKey = Symbol('type');
|
|
2
|
+
export const makeObjectFromStringLiteral = (key, value) => {
|
|
3
|
+
// We have to cast the type because [key] is always considered as string
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
5
|
+
return { [key]: value };
|
|
6
|
+
};
|
|
7
|
+
export const split = (str, delimiter) => str.split(delimiter);
|
|
8
|
+
export const join = (keys, delimiter) => keys.join(delimiter);
|
|
9
|
+
export const capitalize = (str) => {
|
|
10
|
+
return `${str[0]?.toUpperCase()}${str.slice(1)}`;
|
|
11
|
+
};
|
|
12
|
+
export const pluralize = (str, value, prefix = false) => {
|
|
13
|
+
return `${prefix ? value + ' ' : ''}${str}${Math.abs(value) === 1 ? '' : 's'}`;
|
|
14
|
+
};
|
|
15
|
+
const fromKebabCase = (kebabCase) => {
|
|
16
|
+
return split(kebabCase, '-');
|
|
17
|
+
};
|
|
18
|
+
const toKebabCase = (segments) => {
|
|
19
|
+
const mutableSegments = segments;
|
|
20
|
+
return join(mutableSegments, '-');
|
|
21
|
+
};
|
|
22
|
+
const fromCamelCase = (input) => {
|
|
23
|
+
const segments = input.match(/[A-Z]?[a-z]+|[0-9]+/g) ?? [];
|
|
24
|
+
return segments.map((s) => s.toLowerCase());
|
|
25
|
+
};
|
|
26
|
+
export const toCamelCase = (segments) => {
|
|
27
|
+
return segments.reduce((acc, segment, index) => {
|
|
28
|
+
if (index === 0) {
|
|
29
|
+
return segment;
|
|
30
|
+
}
|
|
31
|
+
if (segment.length === 0) {
|
|
32
|
+
return acc;
|
|
33
|
+
}
|
|
34
|
+
return `${acc}${capitalize(segment)}`;
|
|
35
|
+
}, '');
|
|
36
|
+
};
|
|
37
|
+
export const kebabCase2camelCase = (kebabCase) => toCamelCase(fromKebabCase(kebabCase));
|
|
38
|
+
export const camelCase2kebabCase = (camelCase) => toKebabCase(fromCamelCase(camelCase));
|
|
39
|
+
export const isDeepEqual = (a, b) => {
|
|
40
|
+
// in order to support circular references we have to keep track of visited objects.
|
|
41
|
+
// for that reason we have to create new function for each invocation.
|
|
42
|
+
const visited = new WeakMap();
|
|
43
|
+
// eslint-disable-next-line complexity
|
|
44
|
+
const inner = (a, b) => {
|
|
45
|
+
// in case strict equality - there is nothing to check anymore.
|
|
46
|
+
if (a === b) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
// in case any of values is not an object, there is nothing to do, except to check strict equality.
|
|
50
|
+
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
|
|
51
|
+
// looks weird, but it is most efficient way to test NaN.
|
|
52
|
+
// otherwise we have to involve Number.isNaN, which causes context switch and therefore is slower.
|
|
53
|
+
// eslint-disable-next-line no-self-compare
|
|
54
|
+
return a !== a && b !== b;
|
|
55
|
+
}
|
|
56
|
+
// if constructors are different, objects are definitely not equal.
|
|
57
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const { constructor } = a;
|
|
61
|
+
if (constructor === Date) {
|
|
62
|
+
return a.getTime() === b.getTime();
|
|
63
|
+
}
|
|
64
|
+
if (constructor === RegExp) {
|
|
65
|
+
return a.source === b.source && a.flags === b.flags;
|
|
66
|
+
}
|
|
67
|
+
if (constructor === Set) {
|
|
68
|
+
if (a.size !== b.size) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
for (const value of a) {
|
|
72
|
+
if (!b.has(value)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (constructor === ArrayBuffer) {
|
|
79
|
+
a = new DataView(a);
|
|
80
|
+
b = new DataView(b);
|
|
81
|
+
}
|
|
82
|
+
if (constructor === DataView || ArrayBuffer.isView(a)) {
|
|
83
|
+
// this is a TypedArray.
|
|
84
|
+
if (constructor !== DataView) {
|
|
85
|
+
a = new DataView(a.buffer);
|
|
86
|
+
b = new DataView(b.buffer);
|
|
87
|
+
}
|
|
88
|
+
if (a.byteLength !== b.byteLength)
|
|
89
|
+
return false;
|
|
90
|
+
for (let i = a.byteLength; i-- !== 0;) {
|
|
91
|
+
if (a.getUint8(i) !== b.getUint8(i)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
// Check circular references
|
|
98
|
+
if (visited.has(a) && visited.get(a) === b) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
visited.set(a, b);
|
|
102
|
+
if (constructor === Array) {
|
|
103
|
+
if (a.length !== b.length) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
for (let i = a.length; i-- !== 0;) {
|
|
107
|
+
if (!inner(a[i], b[i])) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (constructor === Map) {
|
|
114
|
+
if (a.size !== b.size) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
for (const entry of a) {
|
|
118
|
+
if (!b.has(entry[0]) || !inner(entry[1], b.get(entry[0]))) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// at this point, we've handled all possible data containers and we can compare objects as plain.
|
|
125
|
+
if (a.valueOf !== Object.prototype.valueOf && typeof a.valueOf === 'function' && typeof b.valueOf === 'function') {
|
|
126
|
+
return a.valueOf() === b.valueOf();
|
|
127
|
+
}
|
|
128
|
+
if (a.toString !== Object.prototype.toString && typeof a.toString === 'function' && typeof b.toString === 'function') {
|
|
129
|
+
return a.toString() === b.toString();
|
|
130
|
+
}
|
|
131
|
+
const aKeys = Object.keys(a);
|
|
132
|
+
let key;
|
|
133
|
+
for (let l = aKeys.length; l-- !== 0;) {
|
|
134
|
+
key = aKeys[l];
|
|
135
|
+
if (!Object.hasOwn(b, key) || !inner(a[key], b[key])) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return Object.keys(b).length === aKeys.length;
|
|
140
|
+
};
|
|
141
|
+
return inner(a, b);
|
|
142
|
+
};
|
|
143
|
+
export const deepEqualPartial = (actual, expected) => {
|
|
144
|
+
if (actual === undefined || actual === null) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const isObject = (input) => typeof input === 'object';
|
|
148
|
+
if (!isObject(expected)) {
|
|
149
|
+
return expected === actual;
|
|
150
|
+
}
|
|
151
|
+
if (!isObject(actual)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(actual).every((key) => {
|
|
155
|
+
const val = actual[key];
|
|
156
|
+
if (val instanceof Object) {
|
|
157
|
+
return deepEqualPartial(expected[key], val);
|
|
158
|
+
}
|
|
159
|
+
return actual[key] === expected[key];
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
export const findByPattern = (patterns, itemToMatch) => patterns
|
|
163
|
+
.map(toPatternResolver)
|
|
164
|
+
.sort((a, b) => a.priority - b.priority)
|
|
165
|
+
.find(({ match }) => match(itemToMatch))?.value;
|
|
166
|
+
export const optionalPatternToList = (value) => {
|
|
167
|
+
if (Array.isArray(value)) {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
return [{ value }];
|
|
171
|
+
};
|
|
172
|
+
const createPatternResolver = ({ pattern, value }) => ({
|
|
173
|
+
value,
|
|
174
|
+
match: (itemToMatch) => isDeepEqual(pattern, itemToMatch),
|
|
175
|
+
priority: 0,
|
|
176
|
+
});
|
|
177
|
+
createPatternResolver.is = (pattern) => 'pattern' in pattern;
|
|
178
|
+
const createPatternPartialResolver = ({ patternPartial, value, }) => ({
|
|
179
|
+
value,
|
|
180
|
+
match: (itemToMatch) => deepEqualPartial(patternPartial, itemToMatch),
|
|
181
|
+
priority: 1,
|
|
182
|
+
});
|
|
183
|
+
createPatternPartialResolver.is = (pattern) => 'patternPartial' in pattern;
|
|
184
|
+
const createDefaultResolver = ({ value }) => ({
|
|
185
|
+
value,
|
|
186
|
+
match: () => true,
|
|
187
|
+
priority: 2,
|
|
188
|
+
});
|
|
189
|
+
const toPatternResolver = (pattern) => {
|
|
190
|
+
if (createPatternResolver.is(pattern)) {
|
|
191
|
+
return createPatternResolver(pattern);
|
|
192
|
+
}
|
|
193
|
+
if (createPatternPartialResolver.is(pattern)) {
|
|
194
|
+
return createPatternPartialResolver(pattern);
|
|
195
|
+
}
|
|
196
|
+
return createDefaultResolver(pattern);
|
|
197
|
+
};
|
|
198
|
+
export const isDefined = (value) => {
|
|
199
|
+
return value !== null && value !== undefined;
|
|
200
|
+
};
|
|
201
|
+
export const assertDefined = (value, messageOption = 'Undefined value') => {
|
|
202
|
+
if (value === null || value === undefined) {
|
|
203
|
+
throw new Error(getErrorMessage(messageOption, value));
|
|
204
|
+
}
|
|
205
|
+
return value;
|
|
206
|
+
};
|
|
207
|
+
export const assertTypeByGuard = ({ value, guard, message, }) => {
|
|
208
|
+
if (!guard(value)) {
|
|
209
|
+
throw new Error(getErrorMessage(message, value));
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
};
|
|
213
|
+
export const assertPredicate = ({ value, predicate, message, }) => {
|
|
214
|
+
if (!predicate(value)) {
|
|
215
|
+
throw new Error(getErrorMessage(message, value));
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
};
|
|
219
|
+
export const assertImpossible = (message) => {
|
|
220
|
+
throw new Error(message);
|
|
221
|
+
};
|
|
222
|
+
const getErrorMessage = (message, value) => typeof message === 'function' ? message(value) : message;
|
|
223
|
+
export const unPrototypeProperties = (obj, keys) => {
|
|
224
|
+
const propertyEntries = keys.map((key) => {
|
|
225
|
+
const value = isFunction(obj[key]) ? (...args) => obj[key](...args) : obj[key];
|
|
226
|
+
return [key, value];
|
|
227
|
+
});
|
|
228
|
+
return Object.fromEntries(propertyEntries);
|
|
229
|
+
};
|
|
230
|
+
const isFunction = (value) => value instanceof Function;
|
|
231
|
+
export const mapConst = (array) => (transformer) => {
|
|
232
|
+
return array.map((item) => transformer(item));
|
|
233
|
+
};
|
|
234
|
+
export const objectFromConstEntries = (entries) => {
|
|
235
|
+
return Object.fromEntries(entries);
|
|
236
|
+
};
|
package/util.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|