ts-fortress 1.0.0 → 2.0.1
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/README.md +430 -34
- package/dist/array/array-of-length.d.mts.map +1 -1
- package/dist/array/array-of-length.mjs +20 -8
- package/dist/array/array-of-length.mjs.map +1 -1
- package/dist/array/array.d.mts.map +1 -1
- package/dist/array/array.mjs +13 -7
- package/dist/array/array.mjs.map +1 -1
- package/dist/array/non-empty-array.d.mts.map +1 -1
- package/dist/array/non-empty-array.mjs +20 -8
- package/dist/array/non-empty-array.mjs.map +1 -1
- package/dist/array/tuple.d.mts.map +1 -1
- package/dist/array/tuple.mjs +20 -8
- package/dist/array/tuple.mjs.map +1 -1
- package/dist/branded/brand.d.mts.map +1 -1
- package/dist/branded/brand.mjs +7 -2
- package/dist/branded/brand.mjs.map +1 -1
- package/dist/compose/intersection.d.mts.map +1 -1
- package/dist/compose/intersection.mjs +17 -8
- package/dist/compose/intersection.mjs.map +1 -1
- package/dist/compose/merge-records.d.mts.map +1 -1
- package/dist/compose/merge-records.mjs +17 -8
- package/dist/compose/merge-records.mjs.map +1 -1
- package/dist/compose/union.d.mts.map +1 -1
- package/dist/compose/union.mjs +9 -4
- package/dist/compose/union.mjs.map +1 -1
- package/dist/enum/enum.d.mts.map +1 -1
- package/dist/enum/enum.mjs +17 -9
- package/dist/enum/enum.mjs.map +1 -1
- package/dist/enum/uint-range.d.mts.map +1 -1
- package/dist/enum/uint-range.mjs +10 -10
- package/dist/enum/uint-range.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/record/key-value-record.d.mts.map +1 -1
- package/dist/record/key-value-record.mjs +39 -15
- package/dist/record/key-value-record.mjs.map +1 -1
- package/dist/record/keyof.d.mts +1 -1
- package/dist/record/keyof.d.mts.map +1 -1
- package/dist/record/omit.d.mts.map +1 -1
- package/dist/record/omit.mjs +6 -2
- package/dist/record/omit.mjs.map +1 -1
- package/dist/record/partial.d.mts.map +1 -1
- package/dist/record/partial.mjs +6 -2
- package/dist/record/partial.mjs.map +1 -1
- package/dist/record/pick.d.mts.map +1 -1
- package/dist/record/pick.mjs +6 -2
- package/dist/record/pick.mjs.map +1 -1
- package/dist/record/record.d.mts.map +1 -1
- package/dist/record/record.mjs +22 -7
- package/dist/record/record.mjs.map +1 -1
- package/dist/type.d.mts +3 -2
- package/dist/type.d.mts.map +1 -1
- package/dist/utils/create-assert-fn.d.mts.map +1 -1
- package/dist/utils/create-assert-fn.mjs +2 -1
- package/dist/utils/create-assert-fn.mjs.map +1 -1
- package/dist/utils/create-cast-fn.d.mts.map +1 -1
- package/dist/utils/create-cast-fn.mjs +2 -1
- package/dist/utils/create-cast-fn.mjs.map +1 -1
- package/dist/utils/create-primitive-type.d.mts.map +1 -1
- package/dist/utils/create-primitive-type.mjs +6 -2
- package/dist/utils/create-primitive-type.mjs.map +1 -1
- package/dist/utils/index.d.mts +0 -1
- package/dist/utils/index.d.mts.map +1 -1
- package/dist/utils/index.mjs +0 -1
- package/dist/utils/index.mjs.map +1 -1
- package/dist/validation-error.d.mts +44 -0
- package/dist/validation-error.d.mts.map +1 -0
- package/dist/validation-error.mjs +40 -0
- package/dist/validation-error.mjs.map +1 -0
- package/package.json +3 -3
- package/src/array/array-of-length.mts +24 -17
- package/src/array/array-of-length.test.mts +46 -8
- package/src/array/array.mts +16 -16
- package/src/array/array.test.mts +32 -5
- package/src/array/non-empty-array.mts +25 -17
- package/src/array/non-empty-array.test.mts +46 -8
- package/src/array/tuple.mts +28 -17
- package/src/array/tuple.test.mts +68 -7
- package/src/branded/brand.mts +13 -6
- package/src/branded/number/finite-number.test.mts +164 -0
- package/src/branded/number/int.test.mts +156 -0
- package/src/branded/number/int16.test.mts +115 -0
- package/src/branded/number/int32.test.mts +109 -0
- package/src/branded/number/non-negative-finite-number.test.mts +126 -0
- package/src/branded/number/non-zero-finite-number.test.mts +120 -0
- package/src/branded/number/non-zero-int.test.mts +113 -0
- package/src/branded/number/non-zero-safe-int.test.mts +120 -0
- package/src/branded/number/positive-finite-number.test.mts +122 -0
- package/src/branded/number/positive-int.test.mts +174 -0
- package/src/branded/number/positive-safe-int.test.mts +126 -0
- package/src/branded/number/safe-int.test.mts +113 -0
- package/src/branded/number/safe-uint.test.mts +121 -0
- package/src/branded/number/uint.test.mts +169 -0
- package/src/branded/number/uint16.test.mts +121 -0
- package/src/branded/number/uint32.test.mts +121 -0
- package/src/branded/simple-branded-number.test.mts +160 -0
- package/src/branded/simple-branded-string.test.mts +178 -0
- package/src/combination.test.mts +268 -11
- package/src/compose/intersection.mts +23 -17
- package/src/compose/intersection.test.mts +125 -13
- package/src/compose/merge-records.mts +23 -17
- package/src/compose/merge-records.test.mts +38 -5
- package/src/compose/union.mts +9 -10
- package/src/compose/union.test.mts +20 -4
- package/src/enum/enum.mts +22 -19
- package/src/enum/enum.test.mts +20 -4
- package/src/enum/uint-range.mts +11 -16
- package/src/enum/uint-range.test.mts +19 -4
- package/src/index.mts +1 -0
- package/src/primitives/bigint.test.mts +247 -0
- package/src/primitives/boolean.test.mts +323 -0
- package/src/primitives/symbol.test.mts +124 -0
- package/src/record/key-value-record.mts +48 -28
- package/src/record/key-value-record.test.mts +43 -5
- package/src/record/keyof.mts +6 -1
- package/src/record/keyof.test.mts +20 -4
- package/src/record/omit.mts +7 -7
- package/src/record/omit.test.mts +19 -5
- package/src/record/optional.test.mts +46 -0
- package/src/record/partial.mts +7 -7
- package/src/record/partial.test.mts +69 -13
- package/src/record/pick.mts +7 -7
- package/src/record/pick.test.mts +19 -5
- package/src/record/record.mts +40 -24
- package/src/record/record.test.mts +52 -9
- package/src/type.mts +3 -2
- package/src/utils/create-assert-fn.mts +6 -2
- package/src/utils/create-cast-fn.mts +6 -2
- package/src/utils/create-primitive-type.mts +6 -5
- package/src/utils/index.mts +0 -1
- package/src/validation-error.mts +81 -0
- package/src/validation-error.test.mts +173 -0
- package/dist/utils/validation-error-message.d.mts +0 -2
- package/dist/utils/validation-error-message.d.mts.map +0 -1
- package/dist/utils/validation-error-message.mjs +0 -13
- package/dist/utils/validation-error-message.mjs.map +0 -1
- package/src/utils/validation-error-message.mts +0 -17
package/README.md
CHANGED
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript-first schema validation library with static type inference.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/ts-fortress)
|
|
6
|
+
[](https://www.npmjs.com/package/ts-fortress)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://codecov.io/gh/noshiro-pf/ts-fortress)
|
|
9
|
+
|
|
10
|
+
**ts-fortress** is a runtime validation library similar to [io-ts](https://github.com/gcanti/io-ts) and [zod](https://github.com/colinhacks/zod), designed to provide type-safe schema validation with excellent TypeScript integration and static type inference.
|
|
6
11
|
|
|
7
12
|
## Features
|
|
8
13
|
|
|
9
14
|
- 🔒 **Type-safe validation** - Full TypeScript support with static type inference
|
|
10
15
|
- 🏗️ **Composable schemas** - Build complex validation schemas from simple building blocks
|
|
11
16
|
- 🎯 **Zero-runtime overhead** - Type information is inferred at compile time
|
|
12
|
-
- 🔄 **Result-based error handling** - Structured error reporting with `Result<T,
|
|
17
|
+
- 🔄 **Result-based error handling** - Structured error reporting with `Result<T, readonly ValidationError[]>`
|
|
13
18
|
- 🏷️ **Branded types** - Rich collection of branded number types (Int, SafeInt, PositiveInt, etc.)
|
|
14
19
|
- ⚡ **Performance focused** - Minimal runtime overhead and optimized validation
|
|
15
20
|
- 🧩 **Functional composition** - Union, intersection, and merge operations for schema composition
|
|
21
|
+
- 🛠️ **Required default values** - All schemas require explicit default values, enabling automatic data filling via `fill()` function
|
|
16
22
|
|
|
17
23
|
## Installation
|
|
18
24
|
|
|
@@ -69,6 +75,387 @@ if (result.isOk()) {
|
|
|
69
75
|
}
|
|
70
76
|
```
|
|
71
77
|
|
|
78
|
+
## Default Values and Data Filling
|
|
79
|
+
|
|
80
|
+
One of ts-fortress's key design decisions is **requiring explicit default values** for all schema types. This enables powerful data filling capabilities:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import * as t from 'ts-fortress';
|
|
84
|
+
|
|
85
|
+
// Every type requires a default value
|
|
86
|
+
const UserProfile = t.record({
|
|
87
|
+
name: t.string('Anonymous'), // Default: 'Anonymous'
|
|
88
|
+
age: t.number(0), // Default: 0
|
|
89
|
+
email: t.optional(t.string('')), // Optional field with default ''
|
|
90
|
+
preferences: t.record({
|
|
91
|
+
theme: t.string('light'), // Default: 'light'
|
|
92
|
+
notifications: t.boolean(true), // Default: true
|
|
93
|
+
}),
|
|
94
|
+
tags: t.array(t.string('')), // Default: empty array []
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// The fill() function automatically provides missing values
|
|
98
|
+
const partialData = {
|
|
99
|
+
name: 'John Doe',
|
|
100
|
+
preferences: {
|
|
101
|
+
theme: 'dark',
|
|
102
|
+
// notifications missing - will be filled with default
|
|
103
|
+
},
|
|
104
|
+
// age, email, tags missing - will be filled with defaults
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const filledData = UserProfile.fill(partialData);
|
|
108
|
+
console.log(filledData);
|
|
109
|
+
// Output: {
|
|
110
|
+
// name: 'John Doe',
|
|
111
|
+
// age: 0, // ← Filled with default
|
|
112
|
+
// email: '', // ← Filled with default
|
|
113
|
+
// preferences: {
|
|
114
|
+
// theme: 'dark',
|
|
115
|
+
// notifications: true, // ← Filled with default
|
|
116
|
+
// },
|
|
117
|
+
// tags: [], // ← Filled with default
|
|
118
|
+
// }
|
|
119
|
+
|
|
120
|
+
// fill() is type-safe and always returns a complete object
|
|
121
|
+
type UserProfile = t.TypeOf<typeof UserProfile>;
|
|
122
|
+
const completeUser: UserProfile = UserProfile.fill(anyPartialData);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Benefits of Required Default Values
|
|
126
|
+
|
|
127
|
+
- **Consolidated definitions**: Type definitions and default values are defined in one place, eliminating the need to maintain separate default objects
|
|
128
|
+
- **Data integrity**: Never worry about missing required fields
|
|
129
|
+
- **API resilience**: Handle incomplete data gracefully from external APIs
|
|
130
|
+
- **Form handling**: Easily initialize forms with default values
|
|
131
|
+
- **Configuration**: Provide sensible defaults for optional configuration
|
|
132
|
+
- **Testing**: Generate complete test data from partial fixtures
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Real-world example: API response handling
|
|
136
|
+
const ApiResponse = t.record({
|
|
137
|
+
data: t.array(t.string('')),
|
|
138
|
+
pagination: t.record({
|
|
139
|
+
page: t.number(1),
|
|
140
|
+
limit: t.number(10),
|
|
141
|
+
total: t.number(0),
|
|
142
|
+
}),
|
|
143
|
+
meta: t.record({
|
|
144
|
+
timestamp: t.number(Date.now()),
|
|
145
|
+
version: t.string('1.0.0'),
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Handle incomplete API responses gracefully
|
|
150
|
+
const incompleteResponse = { data: ['item1', 'item2'] };
|
|
151
|
+
const completeResponse = ApiResponse.fill(incompleteResponse);
|
|
152
|
+
// All missing fields are automatically filled with their defaults
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Why ts-fortress over Zod and io-ts?
|
|
156
|
+
|
|
157
|
+
While ts-fortress, [Zod](https://github.com/colinhacks/zod), and [io-ts](https://github.com/gcanti/io-ts) are all excellent TypeScript validation libraries, ts-fortress provides enhanced type safety during validator construction and addresses critical runtime consistency issues found in io-ts.
|
|
158
|
+
|
|
159
|
+
### Type Safety in Schema Definition
|
|
160
|
+
|
|
161
|
+
**Problem with Zod**: The following code compiles without errors but creates an invalid schema:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import * as z from 'zod';
|
|
165
|
+
|
|
166
|
+
// ❌ This compiles but is incorrect!
|
|
167
|
+
export const SomeObject = z.object({
|
|
168
|
+
key1: 1, // Should be z.literal(1)
|
|
169
|
+
key2: 'string', // Should be z.string()
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export type SomeObject = z.infer<typeof SomeObject>;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The above Zod schema will fail at runtime because raw values (`1`, `'string'`) are not valid Zod validators.
|
|
176
|
+
|
|
177
|
+
**Correct Zod usage** requires remembering to wrap all values:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// ✅ Correct Zod usage
|
|
181
|
+
export const SomeObject = z.object({
|
|
182
|
+
key1: z.literal(1),
|
|
183
|
+
key2: z.string(),
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**ts-fortress prevents this error at compile time**:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import * as t from 'ts-fortress';
|
|
191
|
+
|
|
192
|
+
// ❌ TypeScript error - this won't compile!
|
|
193
|
+
export const SomeObject = t.record({
|
|
194
|
+
key1: 1, // Error: number is not assignable to Type<unknown>
|
|
195
|
+
key2: 'string', // Error: string is not assignable to Type<unknown>
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ✅ Correct ts-fortress usage - enforced by TypeScript
|
|
199
|
+
export const SomeObject = t.record({
|
|
200
|
+
key1: t.numberLiteral(1), // or t.number(1) with default
|
|
201
|
+
key2: t.string(''),
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Benefits
|
|
206
|
+
|
|
207
|
+
- **Compile-time safety**: TypeScript catches invalid schema definitions immediately
|
|
208
|
+
- **IDE support**: Better autocomplete and error highlighting during development
|
|
209
|
+
- **Reduced runtime errors**: Impossible to create invalid schemas that fail at runtime
|
|
210
|
+
- **Self-documenting**: The type system guides you toward correct usage
|
|
211
|
+
|
|
212
|
+
### Deep Readonly Types by Default
|
|
213
|
+
|
|
214
|
+
**ts-fortress generates deeply readonly types**, promoting immutability and preventing accidental mutations:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import * as t from 'ts-fortress';
|
|
218
|
+
|
|
219
|
+
const UserType = t.record({
|
|
220
|
+
name: t.string(''),
|
|
221
|
+
address: t.record({
|
|
222
|
+
street: t.string(''),
|
|
223
|
+
city: t.string(''),
|
|
224
|
+
}),
|
|
225
|
+
tags: t.array(t.string('')),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
type User = t.TypeOf<typeof UserType>;
|
|
229
|
+
// ↑ Readonly<{
|
|
230
|
+
// name: string;
|
|
231
|
+
// address: Readonly<{
|
|
232
|
+
// street: string;
|
|
233
|
+
// city: string;
|
|
234
|
+
// }>;
|
|
235
|
+
// tags: readonly string[];
|
|
236
|
+
// }>
|
|
237
|
+
|
|
238
|
+
const user: User = UserType.cast(someData);
|
|
239
|
+
|
|
240
|
+
// ❌ All of these produce TypeScript errors:
|
|
241
|
+
user.name = 'new name'; // Cannot assign to 'name' because it is read-only
|
|
242
|
+
user.address.street = 'new street'; // Cannot assign to 'street' because it is read-only
|
|
243
|
+
user.tags.push('new tag'); // Property 'push' does not exist on readonly array
|
|
244
|
+
user.tags[0] = 'modified'; // Index signature in type 'readonly string[]' only permits reading
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**In contrast, Zod types are mutable by default**:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import * as z from 'zod';
|
|
251
|
+
|
|
252
|
+
const UserSchema = z.object({
|
|
253
|
+
name: z.string(),
|
|
254
|
+
address: z.object({
|
|
255
|
+
street: z.string(),
|
|
256
|
+
city: z.string(),
|
|
257
|
+
}),
|
|
258
|
+
tags: z.array(z.string()),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
type User = z.infer<typeof UserSchema>;
|
|
262
|
+
// ↑ {
|
|
263
|
+
// name: string;
|
|
264
|
+
// address: {
|
|
265
|
+
// street: string;
|
|
266
|
+
// city: string;
|
|
267
|
+
// };
|
|
268
|
+
// tags: string[];
|
|
269
|
+
// }
|
|
270
|
+
|
|
271
|
+
const user: User = UserSchema.parse(someData);
|
|
272
|
+
|
|
273
|
+
// ✅ These all work in Zod (but may not be desirable):
|
|
274
|
+
user.name = 'new name'; // No error
|
|
275
|
+
user.address.street = 'new street'; // No error
|
|
276
|
+
user.tags.push('new tag'); // No error
|
|
277
|
+
user.tags[0] = 'modified'; // No error
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Benefits of Deep Readonly
|
|
281
|
+
|
|
282
|
+
- **Immutability by default**: Prevents accidental mutations that can lead to bugs
|
|
283
|
+
- **Functional programming support**: Encourages functional programming patterns
|
|
284
|
+
- **Predictable data flow**: Ensures data integrity throughout your application
|
|
285
|
+
- **Better optimization**: Enables better compiler optimizations and reasoning about code
|
|
286
|
+
- **Thread safety**: Immutable data is inherently safe to share between contexts
|
|
287
|
+
|
|
288
|
+
### Runtime-Type Consistency Issues in io-ts
|
|
289
|
+
|
|
290
|
+
**io-ts has several long-standing bugs** where runtime behavior doesn't match TypeScript types, which have remained unfixed for years:
|
|
291
|
+
|
|
292
|
+
#### 1. Keyof Type Mismatch ([Issue #697](https://github.com/gcanti/io-ts/issues/697))
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import * as t from 'io-ts';
|
|
296
|
+
|
|
297
|
+
const T = t.keyof({
|
|
298
|
+
0: undefined,
|
|
299
|
+
1: undefined,
|
|
300
|
+
2: undefined,
|
|
301
|
+
3: undefined,
|
|
302
|
+
4: undefined,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ❌ Runtime behavior is inconsistent with TypeScript types!
|
|
306
|
+
console.log(T.decode(0)); // Left (fails) - number 0 is rejected
|
|
307
|
+
console.log(T.decode('0')); // Right (succeeds) - string "0" is accepted
|
|
308
|
+
|
|
309
|
+
type T = t.TypeOf<typeof T>;
|
|
310
|
+
// ↑ TypeScript infers: 0 | 1 | 2 | 3 | 4 (number literals)
|
|
311
|
+
// But should be: "0" | "1" | "2" | "3" | "4" (string literals)
|
|
312
|
+
|
|
313
|
+
// The runtime validator only accepts strings, but TypeScript thinks it accepts numbers!
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### 2. Union + Undefined Decode Issues ([Issue #677](https://github.com/gcanti/io-ts/issues/677))
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import * as t from 'io-ts';
|
|
320
|
+
import { isRight } from 'fp-ts/Either';
|
|
321
|
+
|
|
322
|
+
const A = t.type({
|
|
323
|
+
A: t.union([t.number, t.undefined, t.null]),
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const B = t.type({
|
|
327
|
+
B: t.union([t.number, t.undefined, t.null]),
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const C = t.partial({
|
|
331
|
+
C: t.union([t.number, t.null]),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// ❌ Case 1: Union decode adds unexpected fields
|
|
335
|
+
{
|
|
336
|
+
const UnionBA = t.union([B, A]);
|
|
337
|
+
const res = UnionBA.decode({ A: 1 });
|
|
338
|
+
|
|
339
|
+
if (isRight(res)) {
|
|
340
|
+
console.log(res.right); // { A: 1, B: undefined } <- NG (expected: { A: 1 })
|
|
341
|
+
console.log(A.is(res.right)); // true <- ok
|
|
342
|
+
console.log(B.is(res.right)); // true <- NG (expected: false)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ❌ Case 2: Union decode produces inconsistent results
|
|
347
|
+
{
|
|
348
|
+
const UnionCA = t.union([C, A]);
|
|
349
|
+
const res = UnionCA.decode({ A: 1 });
|
|
350
|
+
|
|
351
|
+
if (isRight(res)) {
|
|
352
|
+
console.log(res.right); // { A: 1 } <- NG (expected: {})
|
|
353
|
+
console.log(A.is(res.right)); // true <- ok
|
|
354
|
+
console.log(C.is(res.right)); // true <- ok
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**ts-fortress eliminates these problems** by ensuring strict runtime-type consistency:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import * as t from 'ts-fortress';
|
|
363
|
+
|
|
364
|
+
// ✅ ts-fortress: Runtime and types always match
|
|
365
|
+
const T = t.keyof(
|
|
366
|
+
t.record({
|
|
367
|
+
0: t.undefinedType,
|
|
368
|
+
1: t.undefinedType,
|
|
369
|
+
2: t.undefinedType,
|
|
370
|
+
3: t.undefinedType,
|
|
371
|
+
4: t.undefinedType,
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
type T = t.TypeOf<typeof T>;
|
|
376
|
+
// ↑ TypeScript correctly infers: "0" | "1" | "2" | "3" | "4" (string literals)
|
|
377
|
+
|
|
378
|
+
// ✅ Runtime behavior matches TypeScript types exactly
|
|
379
|
+
console.log(T.validate(0)); // ❌ Fails correctly - number 0 is rejected
|
|
380
|
+
console.log(T.validate('0')); // ✅ Success - string "0" is accepted
|
|
381
|
+
|
|
382
|
+
// ✅ Complex union types work reliably without unexpected behavior
|
|
383
|
+
const A = t.record({
|
|
384
|
+
A: t.union([t.number(0), t.undefinedType, t.nullType]),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const B = t.record({
|
|
388
|
+
B: t.union([t.number(0), t.undefinedType, t.nullType]),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const C = t.partial(
|
|
392
|
+
t.record({
|
|
393
|
+
C: t.union([t.number(0), t.nullType]),
|
|
394
|
+
}),
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// ✅ Case 1: Union validation is predictable and correct
|
|
398
|
+
{
|
|
399
|
+
const UnionBA = t.union([B, A]);
|
|
400
|
+
const result = UnionBA.validate({ A: 1 });
|
|
401
|
+
|
|
402
|
+
if (result.isOk()) {
|
|
403
|
+
console.log(result.value); // { A: 1 } <- ✅ Correct! No unexpected fields
|
|
404
|
+
console.log(A.is(result.value)); // true <- ✅ Correct
|
|
405
|
+
console.log(B.is(result.value)); // false <- ✅ Correct! B requires field B
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ✅ Case 2: Consistent validation behavior
|
|
410
|
+
{
|
|
411
|
+
const UnionCA = t.union([C, A]);
|
|
412
|
+
const result = UnionCA.validate({ A: 1 });
|
|
413
|
+
|
|
414
|
+
if (result.isOk()) {
|
|
415
|
+
console.log(result.value); // { A: 1 } <- ✅ Correct and consistent
|
|
416
|
+
console.log(A.is(result.value)); // true <- ✅ Correct
|
|
417
|
+
console.log(C.is(result.value)); // true <- ✅ Consistent! ts-fortress partial types allow extra fields
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
This makes ts-fortress especially valuable in large codebases where schema correctness is critical and runtime failures need to be minimized.
|
|
423
|
+
|
|
424
|
+
## Migration from io-ts
|
|
425
|
+
|
|
426
|
+
If you're coming from io-ts, here's how common patterns translate:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// io-ts style
|
|
430
|
+
import * as t from 'io-ts';
|
|
431
|
+
|
|
432
|
+
const UserCodec = t.type({
|
|
433
|
+
id: t.string,
|
|
434
|
+
name: t.string,
|
|
435
|
+
age: t.number,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
type User = t.TypeOf<typeof UserCodec>;
|
|
439
|
+
|
|
440
|
+
// ts-fortress style
|
|
441
|
+
import * as t from 'ts-fortress';
|
|
442
|
+
|
|
443
|
+
const UserType = t.record({
|
|
444
|
+
id: t.string(''),
|
|
445
|
+
name: t.string(''),
|
|
446
|
+
age: t.number(0),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
type User = t.TypeOf<typeof UserType>;
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Key differences:
|
|
453
|
+
|
|
454
|
+
- **Default values**: ts-fortress requires explicit default values for better type safety
|
|
455
|
+
- **Naming**: `record` instead of `type`, more explicit function names
|
|
456
|
+
- **Error handling**: Built-in `Result` type instead of `Either`
|
|
457
|
+
- **Branded types**: Rich collection of pre-built branded number types
|
|
458
|
+
|
|
72
459
|
## Core Concepts
|
|
73
460
|
|
|
74
461
|
### Type Interface
|
|
@@ -76,15 +463,15 @@ if (result.isOk()) {
|
|
|
76
463
|
Every validator in ts-fortress implements the `Type<A>` interface:
|
|
77
464
|
|
|
78
465
|
```typescript
|
|
79
|
-
|
|
466
|
+
type Type<A> = Readonly<{
|
|
80
467
|
typeName: string; // Human-readable type name
|
|
81
468
|
defaultValue: A; // Default value for this type
|
|
82
469
|
is: (a: unknown) => a is A; // Type guard function
|
|
83
470
|
assertIs: (a: unknown) => asserts a is A; // Type assertion
|
|
84
471
|
cast: (a: unknown) => A; // Cast with fallback to default
|
|
85
472
|
fill: (a: unknown) => A; // Fill missing values with defaults
|
|
86
|
-
validate: (a: unknown) => Result<A,
|
|
87
|
-
}
|
|
473
|
+
validate: (a: unknown) => Result<A, readonly ValidationError[]>; // Detailed validation
|
|
474
|
+
}>;
|
|
88
475
|
```
|
|
89
476
|
|
|
90
477
|
### Primitive Types
|
|
@@ -212,7 +599,7 @@ type DiceRoll = t.TypeOf<typeof DiceRoll>; // 1 | 2 | 3 | 4 | 5 | 6
|
|
|
212
599
|
|
|
213
600
|
## Error Handling
|
|
214
601
|
|
|
215
|
-
ts-fortress uses `Result<T,
|
|
602
|
+
ts-fortress uses `Result<T, readonly ValidationError[]>` for structured error handling with detailed error information:
|
|
216
603
|
|
|
217
604
|
```typescript
|
|
218
605
|
const UserType = t.record({
|
|
@@ -223,9 +610,18 @@ const UserType = t.record({
|
|
|
223
610
|
const invalidData = { name: 123, age: 'not a number' };
|
|
224
611
|
|
|
225
612
|
const result = UserType.validate(invalidData);
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
613
|
+
if (Result.isErr(result)) {
|
|
614
|
+
// result.value is an array of ValidationError objects
|
|
615
|
+
result.value.forEach((error) => {
|
|
616
|
+
console.log('Path:', error.path); // ['name'] or ['age']
|
|
617
|
+
console.log('Expected:', error.expectedType); // 'string' or 'number'
|
|
618
|
+
console.log('Actual:', error.actualValue); // 123 or 'not a number'
|
|
619
|
+
console.log('Type:', error.typeName); // type being validated
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Convert to legacy string messages for backward compatibility
|
|
623
|
+
const messages = t.validationErrorsToMessages(result.value);
|
|
624
|
+
console.log(messages);
|
|
229
625
|
// ['Expected string at name, got number', 'Expected number at age, got string']
|
|
230
626
|
}
|
|
231
627
|
|
|
@@ -237,40 +633,40 @@ try {
|
|
|
237
633
|
}
|
|
238
634
|
```
|
|
239
635
|
|
|
240
|
-
|
|
636
|
+
### ValidationError Structure
|
|
241
637
|
|
|
242
|
-
|
|
638
|
+
Each validation error provides detailed information:
|
|
243
639
|
|
|
244
640
|
```typescript
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
641
|
+
interface ValidationError {
|
|
642
|
+
path: readonly string[]; // Path to the field that failed
|
|
643
|
+
actualValue: unknown; // The actual value that failed validation
|
|
644
|
+
expectedType: string; // The expected type or constraint
|
|
645
|
+
message?: string; // Optional custom error message
|
|
646
|
+
typeName: string; // Name of the type being validated
|
|
647
|
+
}
|
|
648
|
+
```
|
|
253
649
|
|
|
254
|
-
|
|
650
|
+
### Error Utilities
|
|
255
651
|
|
|
256
|
-
|
|
652
|
+
```typescript
|
|
257
653
|
import * as t from 'ts-fortress';
|
|
258
654
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
name: t.string(''),
|
|
262
|
-
age: t.number(0),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
type User = t.TypeOf<typeof UserType>;
|
|
266
|
-
```
|
|
655
|
+
// Convert ValidationError objects to string messages
|
|
656
|
+
const messages = t.validationErrorsToMessages(errors);
|
|
267
657
|
|
|
268
|
-
|
|
658
|
+
// Convert single ValidationError to message
|
|
659
|
+
const message = t.validationErrorToMessage(error);
|
|
269
660
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
661
|
+
// Create custom validation errors
|
|
662
|
+
const customError = t.createValidationError({
|
|
663
|
+
path: ['user', 'email'],
|
|
664
|
+
actualValue: 'invalid-email',
|
|
665
|
+
expectedType: 'valid email address',
|
|
666
|
+
typeName: 'EmailAddress',
|
|
667
|
+
message: 'Please provide a valid email address',
|
|
668
|
+
});
|
|
669
|
+
```
|
|
274
670
|
|
|
275
671
|
## API Reference
|
|
276
672
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array-of-length.d.mts","sourceRoot":"","sources":["../../src/array/array-of-length.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,SAAS,SAAS,EAClD,MAAM,CAAC,EACP,aAAa,IAAI,CAAC,CAAC,CAAC,EACpB,UAAU,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACpC,CAAC,KACD,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"array-of-length.d.mts","sourceRoot":"","sources":["../../src/array/array-of-length.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,CAAC,SAAS,SAAS,EAClD,MAAM,CAAC,EACP,aAAa,IAAI,CAAC,CAAC,CAAC,EACpB,UAAU,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACpC,CAAC,KACD,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAkE1B,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { Arr, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
3
3
|
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
4
4
|
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { createPrimitiveValidationError, prependIndexToValidationErrors } from '../validation-error.mjs';
|
|
6
6
|
|
|
7
7
|
const arrayOfLength = (size, elementType, options) => {
|
|
8
8
|
const { typeName,
|
|
@@ -12,20 +12,32 @@ const arrayOfLength = (size, elementType, options) => {
|
|
|
12
12
|
const validate = (a) => {
|
|
13
13
|
if (!Arr.isArray(a)) {
|
|
14
14
|
return Result.err([
|
|
15
|
-
|
|
15
|
+
createPrimitiveValidationError({
|
|
16
|
+
actualValue: a,
|
|
17
|
+
expectedType: 'array',
|
|
18
|
+
typeName: typeNameFilled,
|
|
19
|
+
}),
|
|
16
20
|
]);
|
|
17
21
|
}
|
|
18
22
|
if (a.length !== size) {
|
|
19
23
|
return Result.err([
|
|
20
|
-
|
|
24
|
+
{
|
|
25
|
+
path: [],
|
|
26
|
+
actualValue: a,
|
|
27
|
+
expectedType: typeNameFilled,
|
|
28
|
+
message: `Expected array of length ${size}, got length ${a.length}`,
|
|
29
|
+
typeName: typeNameFilled,
|
|
30
|
+
},
|
|
21
31
|
]);
|
|
22
32
|
}
|
|
23
|
-
|
|
33
|
+
const errors = a.flatMap((el, index) => {
|
|
24
34
|
const res = elementType.validate(el);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
return Result.isErr(res)
|
|
36
|
+
? prependIndexToValidationErrors(res.value, index)
|
|
37
|
+
: [];
|
|
38
|
+
});
|
|
39
|
+
if (errors.length > 0) {
|
|
40
|
+
return Result.err(errors);
|
|
29
41
|
}
|
|
30
42
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
31
43
|
return Result.ok(a);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array-of-length.mjs","sources":["../../src/array/array-of-length.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AASO,MAAM,aAAa,GAAG,CAC3B,IAAO,EACP,WAAoB,EACpB,OAGE,KAC2B;AAG7B,IAAA,MAAM,EACJ,QAAQ;;AAER,IAAA,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,YAAY,CAAM,GAC/D,GAAG,OAAO,IAAI,EAAE;IAEjB,MAAM,cAAc,GAClB,QAAQ,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAA,CAAG;AAEtE,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;QAC1C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,
|
|
1
|
+
{"version":3,"file":"array-of-length.mjs","sources":["../../src/array/array-of-length.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AASO,MAAM,aAAa,GAAG,CAC3B,IAAO,EACP,WAAoB,EACpB,OAGE,KAC2B;AAG7B,IAAA,MAAM,EACJ,QAAQ;;AAER,IAAA,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,YAAY,CAAM,GAC/D,GAAG,OAAO,IAAI,EAAE;IAEjB,MAAM,cAAc,GAClB,QAAQ,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAA,CAAG;AAEtE,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;QAC1C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,8BAA8B,CAAC;AAC7B,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,OAAO;AACrB,oBAAA,QAAQ,EAAE,cAAc;iBACzB,CAAC;AACH,aAAA,CAAC;QACJ;AAEA,QAAA,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,EAAE;YACrB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA;AACE,oBAAA,IAAI,EAAE,EAAE;AACR,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,cAAc;AAC5B,oBAAA,OAAO,EAAE,CAAA,yBAAA,EAA4B,IAAI,gBAAgB,CAAC,CAAC,MAAM,CAAA,CAAE;AACnE,oBAAA,QAAQ,EAAE,cAAc;AACY,iBAAA;AACvC,aAAA,CAAC;QACJ;QAEA,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,KAAI;YACrC,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;AACpC,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG;kBACnB,8BAA8B,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK;kBAC/C,EAAE;AACR,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;AACrB,YAAA,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3B;;AAGA,QAAA,OAAO,MAAM,CAAC,EAAE,CAAC,CAAiB,CAAC;AACrC,IAAA,CAAC;AAED,IAAA,MAAM,IAAI,GAAoB,CAAC,CAAC,KAC9B,GAAG,CAAC,OAAO,CAAC,CAAC;AACX;;YAEG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAa;UACjE,YAAY;IAElB,OAAO;AACL,QAAA,QAAQ,EAAE,cAAc;QACxB,YAAY;QACZ,IAAI;QACJ,QAAQ;AACR,QAAA,EAAE,EAAE,UAAU,CAAC,QAAQ,CAAC;AACxB,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;AAC5B,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;KACnC;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array.d.mts","sourceRoot":"","sources":["../../src/array/array.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"array.d.mts","sourceRoot":"","sources":["../../src/array/array.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAOxC,eAAO,MAAM,KAAK,GAAI,CAAC,EACrB,aAAa,IAAI,CAAC,CAAC,CAAC,EACpB,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,SAAS,CAAC,EAAE,CAAC;CAC5B,CAAC,CACH,KACA,IAAI,CAAC,SAAS,CAAC,EAAE,CA6CnB,CAAC"}
|
package/dist/array/array.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { Arr, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { createAssertFn } from '../utils/create-assert-fn.mjs';
|
|
3
3
|
import { createCastFn } from '../utils/create-cast-fn.mjs';
|
|
4
4
|
import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import { createPrimitiveValidationError, prependIndexToValidationErrors } from '../validation-error.mjs';
|
|
6
6
|
|
|
7
7
|
const array = (elementType, options) => {
|
|
8
8
|
const { typeName, defaultValue = [] } = options ?? {};
|
|
@@ -10,15 +10,21 @@ const array = (elementType, options) => {
|
|
|
10
10
|
const validate = (a) => {
|
|
11
11
|
if (!Arr.isArray(a)) {
|
|
12
12
|
return Result.err([
|
|
13
|
-
|
|
13
|
+
createPrimitiveValidationError({
|
|
14
|
+
actualValue: a,
|
|
15
|
+
expectedType: 'array',
|
|
16
|
+
typeName: typeNameFilled,
|
|
17
|
+
}),
|
|
14
18
|
]);
|
|
15
19
|
}
|
|
16
|
-
|
|
20
|
+
const errors = a.flatMap((el, index) => {
|
|
17
21
|
const res = elementType.validate(el);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
return Result.isErr(res)
|
|
23
|
+
? prependIndexToValidationErrors(res.value, index)
|
|
24
|
+
: [];
|
|
25
|
+
});
|
|
26
|
+
if (errors.length > 0) {
|
|
27
|
+
return Result.err(errors);
|
|
22
28
|
}
|
|
23
29
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
24
30
|
return Result.ok(a);
|
package/dist/array/array.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"array.mjs","sources":["../../src/array/array.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"array.mjs","sources":["../../src/array/array.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAQa,KAAK,GAAG,CACnB,WAAoB,EACpB,OAKC,KACqB;IAGtB,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,OAAO,IAAI,EAAE;IAErD,MAAM,cAAc,GAAW,QAAQ,IAAI,IAAI,WAAW,CAAC,QAAQ,CAAA,GAAA,CAAK;AAExE,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAI;QAC1C,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACnB,OAAO,MAAM,CAAC,GAAG,CAAC;AAChB,gBAAA,8BAA8B,CAAC;AAC7B,oBAAA,WAAW,EAAE,CAAC;AACd,oBAAA,YAAY,EAAE,OAAO;AACrB,oBAAA,QAAQ,EAAE,cAAc;iBACzB,CAAC;AACH,aAAA,CAAC;QACJ;QAEA,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,KAAI;YACrC,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;AACpC,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG;kBACnB,8BAA8B,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK;kBAC/C,EAAE;AACR,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE;AACrB,YAAA,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;QAC3B;;AAGA,QAAA,OAAO,MAAM,CAAC,EAAE,CAAC,CAAM,CAAC;AAC1B,IAAA,CAAC;AAED,IAAA,MAAM,IAAI,GAAoB,CAAC,CAAC,KAC9B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY;IAEnE,OAAO;AACL,QAAA,QAAQ,EAAE,cAAc;QACxB,YAAY;QACZ,IAAI;QACJ,QAAQ;AACR,QAAA,EAAE,EAAE,UAAU,CAAC,QAAQ,CAAC;AACxB,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;AAClC,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;KAC7B;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"non-empty-array.d.mts","sourceRoot":"","sources":["../../src/array/non-empty-array.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"non-empty-array.d.mts","sourceRoot":"","sources":["../../src/array/non-empty-array.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AASxC,eAAO,MAAM,aAAa,GAAI,CAAC,EAC7B,aAAa,IAAI,CAAC,CAAC,CAAC,EACpB,UAAU,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;CACjC,CAAC,KACD,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CA6DvB,CAAC"}
|