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.
Files changed (61) hide show
  1. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  2. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  3. package/dist/entry-point.mjs +1 -0
  4. package/dist/entry-point.mjs.map +1 -1
  5. package/dist/expect-type.d.mts.map +1 -1
  6. package/dist/expect-type.mjs.map +1 -1
  7. package/dist/functional/ternary-result/ternary-result.d.mts.map +1 -1
  8. package/dist/guard/index.d.mts +1 -0
  9. package/dist/guard/index.d.mts.map +1 -1
  10. package/dist/guard/index.mjs +1 -0
  11. package/dist/guard/index.mjs.map +1 -1
  12. package/dist/guard/is-length-bounded-string.d.mts +164 -0
  13. package/dist/guard/is-length-bounded-string.d.mts.map +1 -0
  14. package/dist/guard/is-length-bounded-string.mjs +164 -0
  15. package/dist/guard/is-length-bounded-string.mjs.map +1 -0
  16. package/dist/guard/is-non-null-object.d.mts +3 -3
  17. package/dist/guard/is-non-null-object.mjs +3 -3
  18. package/dist/guard/is-record.d.mts +47 -29
  19. package/dist/guard/is-record.d.mts.map +1 -1
  20. package/dist/guard/is-record.mjs +48 -30
  21. package/dist/guard/is-record.mjs.map +1 -1
  22. package/dist/index.mjs +1 -0
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/number/branded-types/int.d.mts +1 -1
  25. package/dist/number/branded-types/int16.d.mts +1 -1
  26. package/dist/number/branded-types/int32.d.mts +1 -1
  27. package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
  28. package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
  29. package/dist/number/branded-types/positive-int.d.mts +1 -1
  30. package/dist/number/branded-types/positive-int16.d.mts +1 -1
  31. package/dist/number/branded-types/positive-int32.d.mts +1 -1
  32. package/dist/number/branded-types/positive-safe-int.d.mts +1 -1
  33. package/dist/number/branded-types/positive-uint16.d.mts +1 -1
  34. package/dist/number/branded-types/positive-uint32.d.mts +1 -1
  35. package/dist/number/branded-types/safe-int.d.mts +1 -1
  36. package/dist/number/branded-types/safe-uint.d.mts +1 -1
  37. package/dist/number/branded-types/uint.d.mts +1 -1
  38. package/dist/number/branded-types/uint16.d.mts +1 -1
  39. package/dist/number/branded-types/uint32.d.mts +1 -1
  40. package/dist/number/refined-number-utils.d.mts.map +1 -1
  41. package/dist/number/refined-number-utils.mjs.map +1 -1
  42. package/dist/object/object.d.mts.map +1 -1
  43. package/dist/object/object.mjs.map +1 -1
  44. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  45. package/dist/others/fast-deep-equal.mjs +7 -5
  46. package/dist/others/fast-deep-equal.mjs.map +1 -1
  47. package/package.json +12 -12
  48. package/src/array/impl/array-utils-transformation.mts +1 -2
  49. package/src/array/impl/array-utils-transformation.test.mts +3 -36
  50. package/src/array/impl/array-utils-validation.test.mts +1 -3
  51. package/src/expect-type.mts +4 -5
  52. package/src/functional/ternary-result/ternary-result.mts +2 -6
  53. package/src/guard/index.mts +1 -0
  54. package/src/guard/is-length-bounded-string.mts +199 -0
  55. package/src/guard/is-length-bounded-string.test.mts +170 -0
  56. package/src/guard/is-non-null-object.mts +3 -3
  57. package/src/guard/is-record.mts +49 -30
  58. package/src/guard/is-record.test.mts +133 -61
  59. package/src/number/refined-number-utils.mts +1 -9
  60. package/src/object/object.mts +25 -23
  61. 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('arrays are Records', () => {
7
- test('number[] is assignable to Record<number, unknown>', () => {
8
- const arr: Record<number, unknown> = [1, 2, 3] as 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
- const unk: unknown = arr;
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
- expectType<typeof unk, UnknownRecord>('<=');
27
+ assert.isTrue(isRecord(unk));
15
28
  });
29
+ });
16
30
 
17
- test('string[] is assignable to Record<number, unknown>', () => {
18
- const arr: Record<number, unknown> = ['1', '2', '3'] as const;
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
- const unk: unknown = arr;
38
+ expectType<readonly number[], UnknownRecord>('!<=');
21
39
 
22
- assert.isTrue(isRecord(unk));
40
+ expectType<readonly string[], Record<number, unknown>>('<=');
41
+
42
+ expectType<readonly string[], UnknownRecord>('!<=');
23
43
 
24
- expectType<typeof unk, UnknownRecord>('<=');
44
+ expectType<readonly [], UnknownRecord>('!<=');
25
45
  });
26
46
 
27
- test('[] is a record', () => {
28
- const arr: unknown = [] as const;
47
+ test('[1, 2, 3] is not a record', () => {
48
+ const unk: unknown = [1, 2, 3] as const;
29
49
 
30
- const unk: unknown = arr;
50
+ assert.isFalse(isRecord(unk));
51
+ });
31
52
 
32
- assert.isTrue(isRecord(unk));
53
+ test('["1", "2", "3"] is not a record', () => {
54
+ const unk: unknown = ['1', '2', '3'] as const;
33
55
 
34
- expectType<typeof unk, UnknownRecord>('<=');
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 Records', () => {
39
- test('{ x: 1 } is a record', () => {
40
- const obj = { x: 1 } as const;
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
- const unk: unknown = obj;
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
- expectType<typeof unk, UnknownRecord>('<=');
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('{} is a record', () => {
50
- const obj = {} as const;
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
- const unk: unknown = obj;
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
- expectType<typeof unk, UnknownRecord>('<=');
136
+ assert.isFalse(isRecord(unk));
57
137
  });
58
138
  });
59
139
 
60
- describe('primitives are not Records', () => {
140
+ describe('primitives are not records', () => {
61
141
  test('null is not a record', () => {
62
- const obj = null;
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 prm = undefined;
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 prm = 3;
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 prm = 'str';
93
-
94
- const unk: unknown = prm;
160
+ const unk: unknown = 'str';
95
161
 
96
162
  assert.isFalse(isRecord(unk));
163
+ });
97
164
 
98
- expectType<typeof unk, UnknownRecord>('!<=');
165
+ test('true is not a record', () => {
166
+ const unk: unknown = true;
167
+
168
+ assert.isFalse(isRecord(unk));
99
169
  });
100
- });
101
170
 
102
- // test('Map is not a record', () => {
103
- // const obj = new MutableMap();
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
- // assertNotType<typeof obj, UnknownRecord>("<=");
108
- // expectType<typeof res, boolean>("=");
174
+ assert.isFalse(isRecord(unk));
175
+ });
109
176
 
110
- // expect(res).toBe(false);
111
- // });
177
+ test('bigint is not a record', () => {
178
+ const unk: unknown = 1n;
112
179
 
113
- // test('Set is not a record', () => {
114
- // const obj = new MutableSet();
115
- // const unk: unknown = obj;
116
- // const res = isRecord(unk);
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
- // assertNotType<typeof obj, UnknownRecord>("<=");
119
- // expectType<typeof res, boolean>("=");
187
+ if (isRecord(unk) && hasKey(unk, 'some-key')) {
188
+ expectType<(typeof unk)['some-key'], unknown>('=');
120
189
 
121
- // expect(res).toBe(false);
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
- | 'add'
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
 
@@ -224,11 +224,9 @@ export namespace Obj {
224
224
  const Keys extends readonly (keyof R)[],
225
225
  >(
226
226
  ...args:
227
- | readonly [record: R, keys: Keys]
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
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
648
- readonly [K in keyof B as {} extends Pick<B, K>
649
- ? K extends keyof A
650
- ? never
651
- : K
652
- : never]?: B[K];
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
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
656
- readonly [K in keyof B as {} extends Pick<B, K>
657
- ? K extends keyof A
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
- : never]?: K extends keyof A ? A[K] | B[K] : never;
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
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
664
- readonly [K in Exclude<keyof A, keyof B> as {} extends Pick<A, K>
665
- ? never
666
- : K]: A[K];
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
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
670
- readonly [K in Exclude<keyof A, keyof B> as {} extends Pick<A, K>
671
- ? K
672
- : never]?: A[K];
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) &&