ts-data-forge 7.1.1 → 9.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.
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/expect-type.d.mts.map +1 -1
- package/dist/expect-type.mjs.map +1 -1
- package/dist/functional/ternary-result/ternary-result.d.mts.map +1 -1
- package/dist/guard/index.d.mts +1 -0
- package/dist/guard/index.d.mts.map +1 -1
- package/dist/guard/index.mjs +1 -0
- package/dist/guard/index.mjs.map +1 -1
- package/dist/guard/is-length-bounded-string.d.mts +164 -0
- package/dist/guard/is-length-bounded-string.d.mts.map +1 -0
- package/dist/guard/is-length-bounded-string.mjs +164 -0
- package/dist/guard/is-length-bounded-string.mjs.map +1 -0
- package/dist/guard/is-non-null-object.d.mts +3 -3
- package/dist/guard/is-non-null-object.mjs +3 -3
- package/dist/guard/is-record.d.mts +47 -29
- package/dist/guard/is-record.d.mts.map +1 -1
- package/dist/guard/is-record.mjs +48 -30
- package/dist/guard/is-record.mjs.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/number/branded-types/int.d.mts +1 -1
- package/dist/number/branded-types/int16.d.mts +1 -1
- package/dist/number/branded-types/int32.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
- package/dist/number/branded-types/positive-int.d.mts +1 -1
- package/dist/number/branded-types/positive-int16.d.mts +1 -1
- package/dist/number/branded-types/positive-int32.d.mts +1 -1
- package/dist/number/branded-types/positive-safe-int.d.mts +1 -1
- package/dist/number/branded-types/positive-uint16.d.mts +1 -1
- package/dist/number/branded-types/positive-uint32.d.mts +1 -1
- package/dist/number/branded-types/safe-int.d.mts +1 -1
- package/dist/number/branded-types/safe-uint.d.mts +1 -1
- package/dist/number/branded-types/uint.d.mts +1 -1
- package/dist/number/branded-types/uint16.d.mts +1 -1
- package/dist/number/branded-types/uint32.d.mts +1 -1
- package/dist/number/refined-number-utils.d.mts.map +1 -1
- package/dist/number/refined-number-utils.mjs.map +1 -1
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +7 -5
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/package.json +12 -12
- package/src/array/impl/array-utils-transformation.mts +1 -2
- package/src/array/impl/array-utils-transformation.test.mts +3 -36
- package/src/array/impl/array-utils-validation.test.mts +1 -3
- package/src/expect-type.mts +4 -5
- package/src/functional/ternary-result/ternary-result.mts +2 -6
- package/src/guard/index.mts +1 -0
- package/src/guard/is-length-bounded-string.mts +199 -0
- package/src/guard/is-length-bounded-string.test.mts +170 -0
- package/src/guard/is-non-null-object.mts +3 -3
- package/src/guard/is-record.mts +49 -30
- package/src/guard/is-record.test.mts +133 -61
- package/src/number/refined-number-utils.mts +1 -9
- package/src/object/object.mts +25 -23
- package/src/others/fast-deep-equal.mts +10 -8
|
@@ -1,123 +1,195 @@
|
|
|
1
1
|
import { type UnknownRecord } from 'ts-type-forge';
|
|
2
2
|
import { expectType } from '../expect-type.mjs';
|
|
3
|
+
import { hasKey } from './has-key.mjs';
|
|
3
4
|
import { isRecord } from './is-record.mjs';
|
|
4
5
|
|
|
5
6
|
describe(isRecord, () => {
|
|
6
|
-
describe('
|
|
7
|
-
test('
|
|
8
|
-
const
|
|
7
|
+
describe('plain objects are records', () => {
|
|
8
|
+
test('{ x: 1 } is a record', () => {
|
|
9
|
+
const unk: unknown = { x: 1 } as const;
|
|
10
|
+
|
|
11
|
+
if (isRecord(unk)) {
|
|
12
|
+
expectType<typeof unk, UnknownRecord>('=');
|
|
13
|
+
} else {
|
|
14
|
+
assert.fail();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
test('{} is a record', () => {
|
|
19
|
+
const unk: unknown = {} as const;
|
|
11
20
|
|
|
12
21
|
assert.isTrue(isRecord(unk));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Object.create(null) is a record', () => {
|
|
25
|
+
const unk: unknown = Object.create(null);
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
assert.isTrue(isRecord(unk));
|
|
15
28
|
});
|
|
29
|
+
});
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
describe('arrays are not records', () => {
|
|
32
|
+
test('type-level: arrays are not assignable to UnknownRecord', () => {
|
|
33
|
+
// Arrays have a number index signature but no string index signature,
|
|
34
|
+
// so they are assignable to `Record<number, unknown>` but not to
|
|
35
|
+
// `UnknownRecord` (= `ReadonlyRecord<string, unknown>`).
|
|
36
|
+
expectType<readonly number[], Record<number, unknown>>('<=');
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
expectType<readonly number[], UnknownRecord>('!<=');
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
expectType<readonly string[], Record<number, unknown>>('<=');
|
|
41
|
+
|
|
42
|
+
expectType<readonly string[], UnknownRecord>('!<=');
|
|
23
43
|
|
|
24
|
-
expectType<
|
|
44
|
+
expectType<readonly [], UnknownRecord>('!<=');
|
|
25
45
|
});
|
|
26
46
|
|
|
27
|
-
test('[] is a record', () => {
|
|
28
|
-
const
|
|
47
|
+
test('[1, 2, 3] is not a record', () => {
|
|
48
|
+
const unk: unknown = [1, 2, 3] as const;
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
assert.isFalse(isRecord(unk));
|
|
51
|
+
});
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
test('["1", "2", "3"] is not a record', () => {
|
|
54
|
+
const unk: unknown = ['1', '2', '3'] as const;
|
|
33
55
|
|
|
34
|
-
|
|
56
|
+
assert.isFalse(isRecord(unk));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('[] is not a record', () => {
|
|
60
|
+
const unk: unknown = [] as const;
|
|
61
|
+
|
|
62
|
+
assert.isFalse(isRecord(unk));
|
|
35
63
|
});
|
|
36
64
|
});
|
|
37
65
|
|
|
38
|
-
describe('non-null objects are
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
describe('other non-null objects are records', () => {
|
|
67
|
+
// These are not statically assignable to `UnknownRecord` either, but the
|
|
68
|
+
// guard intentionally admits them: every property read through
|
|
69
|
+
// `UnknownRecord` is typed `unknown`, so the narrowing is type-safe, and
|
|
70
|
+
// it keeps `isRecord(u) && hasKey(u, k)` usable for probing instances
|
|
71
|
+
// such as caught `Error` objects.
|
|
72
|
+
test('Date is a record', () => {
|
|
73
|
+
expectType<Date, UnknownRecord>('!<=');
|
|
74
|
+
|
|
75
|
+
const unk: unknown = new Date('2020-01-01');
|
|
76
|
+
|
|
77
|
+
assert.isTrue(isRecord(unk));
|
|
78
|
+
});
|
|
41
79
|
|
|
42
|
-
|
|
80
|
+
test('RegExp is a record', () => {
|
|
81
|
+
const unk: unknown = /test/u;
|
|
43
82
|
|
|
44
83
|
assert.isTrue(isRecord(unk));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('Map is a record (but its entries are not own properties)', () => {
|
|
87
|
+
expectType<ReadonlyMap<string, number>, UnknownRecord>('!<=');
|
|
88
|
+
|
|
89
|
+
const unk: unknown = new Map([['a', 1]]);
|
|
45
90
|
|
|
46
|
-
|
|
91
|
+
if (isRecord(unk)) {
|
|
92
|
+
// Map entries are not own properties, so hasKey correctly rejects them.
|
|
93
|
+
assert.isFalse(hasKey(unk, 'a'));
|
|
94
|
+
} else {
|
|
95
|
+
assert.fail();
|
|
96
|
+
}
|
|
47
97
|
});
|
|
48
98
|
|
|
49
|
-
test('
|
|
50
|
-
|
|
99
|
+
test('Set is a record', () => {
|
|
100
|
+
expectType<ReadonlySet<number>, UnknownRecord>('!<=');
|
|
101
|
+
|
|
102
|
+
const unk: unknown = new Set([1, 2, 3]);
|
|
103
|
+
|
|
104
|
+
assert.isTrue(isRecord(unk));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('Error is a record and its own properties can be probed', () => {
|
|
108
|
+
const unk: unknown = Object.assign(new Error('failure'), {
|
|
109
|
+
code: 'ENOENT',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (isRecord(unk) && hasKey(unk, 'code')) {
|
|
113
|
+
expectType<(typeof unk)['code'], unknown>('=');
|
|
51
114
|
|
|
52
|
-
|
|
115
|
+
assert.strictEqual(unk.code, 'ENOENT');
|
|
116
|
+
} else {
|
|
117
|
+
assert.fail();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('class instance is a record', () => {
|
|
122
|
+
class Foo {
|
|
123
|
+
readonly x = 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const unk: unknown = new Foo();
|
|
53
127
|
|
|
54
128
|
assert.isTrue(isRecord(unk));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('functions are not records', () => {
|
|
133
|
+
test('arrow function is not a record', () => {
|
|
134
|
+
const unk: unknown = () => 0;
|
|
55
135
|
|
|
56
|
-
|
|
136
|
+
assert.isFalse(isRecord(unk));
|
|
57
137
|
});
|
|
58
138
|
});
|
|
59
139
|
|
|
60
|
-
describe('primitives are not
|
|
140
|
+
describe('primitives are not records', () => {
|
|
61
141
|
test('null is not a record', () => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const unk: unknown = obj;
|
|
142
|
+
const unk: unknown = null;
|
|
65
143
|
|
|
66
144
|
assert.isFalse(isRecord(unk));
|
|
67
|
-
|
|
68
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
69
145
|
});
|
|
70
146
|
|
|
71
147
|
test('undefined is not a record', () => {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const unk: unknown = prm;
|
|
148
|
+
const unk: unknown = undefined;
|
|
75
149
|
|
|
76
150
|
assert.isFalse(isRecord(unk));
|
|
77
|
-
|
|
78
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
79
151
|
});
|
|
80
152
|
|
|
81
153
|
test('3 is not a record', () => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const unk: unknown = prm;
|
|
154
|
+
const unk: unknown = 3;
|
|
85
155
|
|
|
86
156
|
assert.isFalse(isRecord(unk));
|
|
87
|
-
|
|
88
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
89
157
|
});
|
|
90
158
|
|
|
91
159
|
test('"str" is not a record', () => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const unk: unknown = prm;
|
|
160
|
+
const unk: unknown = 'str';
|
|
95
161
|
|
|
96
162
|
assert.isFalse(isRecord(unk));
|
|
163
|
+
});
|
|
97
164
|
|
|
98
|
-
|
|
165
|
+
test('true is not a record', () => {
|
|
166
|
+
const unk: unknown = true;
|
|
167
|
+
|
|
168
|
+
assert.isFalse(isRecord(unk));
|
|
99
169
|
});
|
|
100
|
-
});
|
|
101
170
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// const unk: unknown = obj;
|
|
105
|
-
// const res = isRecord(unk);
|
|
171
|
+
test('symbol is not a record', () => {
|
|
172
|
+
const unk: unknown = Symbol('sym');
|
|
106
173
|
|
|
107
|
-
|
|
108
|
-
|
|
174
|
+
assert.isFalse(isRecord(unk));
|
|
175
|
+
});
|
|
109
176
|
|
|
110
|
-
|
|
111
|
-
|
|
177
|
+
test('bigint is not a record', () => {
|
|
178
|
+
const unk: unknown = 1n;
|
|
112
179
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
180
|
+
assert.isFalse(isRecord(unk));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('combined with hasKey', () => {
|
|
185
|
+
const unk: unknown = { 'some-key': 42 } as const;
|
|
117
186
|
|
|
118
|
-
|
|
119
|
-
|
|
187
|
+
if (isRecord(unk) && hasKey(unk, 'some-key')) {
|
|
188
|
+
expectType<(typeof unk)['some-key'], unknown>('=');
|
|
120
189
|
|
|
121
|
-
|
|
122
|
-
|
|
190
|
+
assert.strictEqual(unk['some-key'], 42);
|
|
191
|
+
} else {
|
|
192
|
+
assert.fail();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
123
195
|
});
|
|
@@ -185,15 +185,7 @@ export namespace TsDataForgeInternals {
|
|
|
185
185
|
}>;
|
|
186
186
|
|
|
187
187
|
type BaseKeys =
|
|
188
|
-
| '
|
|
189
|
-
| 'div'
|
|
190
|
-
| 'is'
|
|
191
|
-
| 'max'
|
|
192
|
-
| 'min'
|
|
193
|
-
| 'mul'
|
|
194
|
-
| 'pow'
|
|
195
|
-
| 'random'
|
|
196
|
-
| 'sub';
|
|
188
|
+
'add' | 'div' | 'is' | 'max' | 'min' | 'mul' | 'pow' | 'random' | 'sub';
|
|
197
189
|
|
|
198
190
|
type FloatMethods = 'ceil' | 'floor' | 'round';
|
|
199
191
|
|
package/src/object/object.mts
CHANGED
|
@@ -224,11 +224,9 @@ export namespace Obj {
|
|
|
224
224
|
const Keys extends readonly (keyof R)[],
|
|
225
225
|
>(
|
|
226
226
|
...args:
|
|
227
|
-
|
|
228
|
-
| readonly [keys: readonly PropertyKey[]]
|
|
227
|
+
readonly [record: R, keys: Keys] | readonly [keys: readonly PropertyKey[]]
|
|
229
228
|
):
|
|
230
|
-
| Omit<R, ArrayElement<Keys>>
|
|
231
|
-
| ((record: R) => Omit<R, ArrayElement<Keys>>) {
|
|
229
|
+
Omit<R, ArrayElement<Keys>> | ((record: R) => Omit<R, ArrayElement<Keys>>) {
|
|
232
230
|
switch (args.length) {
|
|
233
231
|
case 2: {
|
|
234
232
|
const [record, keys] = args;
|
|
@@ -644,32 +642,36 @@ declare namespace TsDataForgeInternals {
|
|
|
644
642
|
readonly [K in keyof B as {} extends Pick<B, K> ? never : K]: B[K];
|
|
645
643
|
} & {
|
|
646
644
|
// 2. Optional properties from B that are NOT in A
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
?
|
|
651
|
-
|
|
652
|
-
|
|
645
|
+
readonly [
|
|
646
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
647
|
+
K in keyof B as {} extends Pick<B, K>
|
|
648
|
+
? K extends keyof A
|
|
649
|
+
? never
|
|
650
|
+
: K
|
|
651
|
+
: never
|
|
652
|
+
]?: B[K];
|
|
653
653
|
} & {
|
|
654
654
|
// 3. Optional properties from B that ARE in A (create union)
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
? K
|
|
655
|
+
readonly [
|
|
656
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
657
|
+
K in keyof B as {} extends Pick<B, K>
|
|
658
|
+
? K extends keyof A
|
|
659
|
+
? K
|
|
660
|
+
: never
|
|
659
661
|
: never
|
|
660
|
-
|
|
662
|
+
]?: K extends keyof A ? A[K] | B[K] : never;
|
|
661
663
|
} & {
|
|
662
664
|
// 4. Required properties only in A (not overridden by B)
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
? never
|
|
666
|
-
|
|
665
|
+
readonly [
|
|
666
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
667
|
+
K in Exclude<keyof A, keyof B> as {} extends Pick<A, K> ? never : K
|
|
668
|
+
]: A[K];
|
|
667
669
|
} & {
|
|
668
670
|
// 5. Optional properties only in A (not overridden by B)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
? K
|
|
672
|
-
|
|
671
|
+
readonly [
|
|
672
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
673
|
+
K in Exclude<keyof A, keyof B> as {} extends Pick<A, K> ? K : never
|
|
674
|
+
]?: A[K];
|
|
673
675
|
} extends infer O
|
|
674
676
|
? Readonly<
|
|
675
677
|
{
|
|
@@ -16,6 +16,16 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Arrays are handled before `isRecord`, which excludes them.
|
|
20
|
+
if (Array.isArray(a)) {
|
|
21
|
+
return (
|
|
22
|
+
Array.isArray(b) &&
|
|
23
|
+
a.constructor === b.constructor &&
|
|
24
|
+
a.length === b.length &&
|
|
25
|
+
a.every((ai, index) => fastDeepEqual(ai, b[index]))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
if (isRecord(a)) {
|
|
20
30
|
if (!isRecord(b)) {
|
|
21
31
|
return false;
|
|
@@ -25,14 +35,6 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
25
35
|
return false;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
if (Array.isArray(a)) {
|
|
29
|
-
return (
|
|
30
|
-
Array.isArray(b) &&
|
|
31
|
-
a.length === b.length &&
|
|
32
|
-
a.every((ai, index) => fastDeepEqual(ai, b[index]))
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
38
|
if (isMap(a)) {
|
|
37
39
|
return (
|
|
38
40
|
isMap(b) &&
|