@jfdevelops/create-error 0.1.1 → 0.2.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/README.md +353 -294
- package/dist/create-error.cjs +60 -13
- package/dist/create-error.cjs.map +1 -1
- package/dist/create-error.d.cts +21 -2
- package/dist/create-error.d.cts.map +1 -1
- package/dist/create-error.d.mts +21 -2
- package/dist/create-error.d.mts.map +1 -1
- package/dist/create-error.mjs +60 -14
- package/dist/create-error.mjs.map +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/types.d.cts +67 -10
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +67 -10
- package/dist/types.d.mts.map +1 -1
- package/package.json +69 -70
package/README.md
CHANGED
|
@@ -1,294 +1,353 @@
|
|
|
1
|
-
# @jfdevelops/create-error
|
|
2
|
-
|
|
3
|
-
Create strongly typed, configurable error families from any synchronous
|
|
4
|
-
[Standard Schema](https://standardschema.dev/schema) object.
|
|
5
|
-
|
|
6
|
-
## Install
|
|
7
|
-
|
|
8
|
-
```sh
|
|
9
|
-
pnpm add @jfdevelops/create-error
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
Bring your own Standard Schema-compatible validation library, such as Zod,
|
|
13
|
-
Valibot, or ArkType. `create-error` does not bundle one.
|
|
14
|
-
|
|
15
|
-
## Quick start
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { createError } from '@jfdevelops/create-error';
|
|
19
|
-
import { z } from 'zod';
|
|
20
|
-
|
|
21
|
-
const createFormError = createError({
|
|
22
|
-
definition: z.object({
|
|
23
|
-
code: z.string(),
|
|
24
|
-
scope: z.string(),
|
|
25
|
-
}),
|
|
26
|
-
data: {
|
|
27
|
-
property: 'context',
|
|
28
|
-
resolve({ definition, input }) {
|
|
29
|
-
return { ...input, scope: definition.scope };
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
message({ definition, data, implementation }) {
|
|
33
|
-
return implementation({
|
|
34
|
-
context: data,
|
|
35
|
-
scope: definition.scope,
|
|
36
|
-
});
|
|
37
|
-
},
|
|
38
|
-
properties({ definition, data, implementation }) {
|
|
39
|
-
return {
|
|
40
|
-
code: definition.code,
|
|
41
|
-
scope: definition.scope,
|
|
42
|
-
context: data,
|
|
43
|
-
renderMessage(renderer = implementation) {
|
|
44
|
-
return renderer({
|
|
45
|
-
context: data,
|
|
46
|
-
scope: definition.scope,
|
|
47
|
-
});
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
},
|
|
51
|
-
toJSON(error) {
|
|
52
|
-
return {
|
|
53
|
-
name: error.name,
|
|
54
|
-
code: error.code,
|
|
55
|
-
scope: error.scope,
|
|
56
|
-
message: error.message,
|
|
57
|
-
context: error.context,
|
|
58
|
-
};
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export const FormError = createFormError.Error;
|
|
63
|
-
|
|
64
|
-
export class InvalidFieldError extends createFormError({
|
|
65
|
-
code: 'invalidField',
|
|
66
|
-
scope: 'field',
|
|
67
|
-
})
|
|
68
|
-
.defineContext(
|
|
69
|
-
z.object({
|
|
70
|
-
field: z.string(),
|
|
71
|
-
scope: z.literal('field'),
|
|
72
|
-
}),
|
|
73
|
-
)
|
|
74
|
-
.implement(
|
|
75
|
-
({ code, context }) => `${code}: ${context.field} is invalid`,
|
|
76
|
-
) {}
|
|
77
|
-
|
|
78
|
-
const error = new InvalidFieldError({ field: 'email' });
|
|
79
|
-
|
|
80
|
-
error.code; // "invalidField"
|
|
81
|
-
error.scope; // "field"
|
|
82
|
-
error.context; // { field: string; scope: "field" }
|
|
83
|
-
error instanceof Error; // true
|
|
84
|
-
error instanceof FormError; // true
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
No explicit generic arguments or `as const` assertions are needed. Definition
|
|
88
|
-
inputs are checked by the schema, and definition-derived fields use its parsed
|
|
89
|
-
output type so transformations remain type-safe.
|
|
90
|
-
|
|
91
|
-
For reusable or separately declared configurations, the package exports a
|
|
92
|
-
common `CreateErrorConfig` type:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import { createError, type CreateErrorConfig } from '@jfdevelops/create-error';
|
|
96
|
-
|
|
97
|
-
const definition = z.object({ code: z.string() });
|
|
98
|
-
|
|
99
|
-
const config = {
|
|
100
|
-
definition,
|
|
101
|
-
data: {
|
|
102
|
-
property: 'context',
|
|
103
|
-
resolve: ({ input }) => input,
|
|
104
|
-
},
|
|
105
|
-
message: ({ data, implementation }) => implementation(data),
|
|
106
|
-
} satisfies CreateErrorConfig<typeof definition>;
|
|
107
|
-
|
|
108
|
-
const createDomainError = createError(config);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## How the factory works
|
|
112
|
-
|
|
113
|
-
The API has four stages:
|
|
114
|
-
|
|
115
|
-
1. `createError(blueprint)` configures a family and returns a factory.
|
|
116
|
-
2. `factory(definition)` validates and captures one definition.
|
|
117
|
-
3. The returned builder optionally selects a context schema with
|
|
118
|
-
`defineContext(schema)`.
|
|
119
|
-
4. `implement(callback)` captures the implementation and produces an
|
|
120
|
-
extendable concrete error class.
|
|
121
|
-
|
|
122
|
-
The definition builder exposes both `defineContext` and `implement`.
|
|
123
|
-
`defineContext` returns a narrower builder that exposes only `implement`, so a
|
|
124
|
-
context schema can be selected only once. Calling `implement` directly keeps
|
|
125
|
-
context schemas optional. Every implementation receives exactly one argument;
|
|
126
|
-
the blueprint decides its shape when it calls `implementation(argument)`. When
|
|
127
|
-
that argument is a constructed object, the concrete definition fields are added
|
|
128
|
-
automatically, so implementations can read values such as `code` without the
|
|
129
|
-
blueprint repeating them. Forwarding opaque data directly preserves its original
|
|
130
|
-
scalar, tuple, object, or class-instance shape.
|
|
131
|
-
|
|
132
|
-
The `definition` option must implement Standard Schema V1 and must produce an
|
|
133
|
-
object. Validation runs when `factory(definition)` is called. Because class
|
|
134
|
-
creation is synchronous, a schema whose `validate` method returns a promise
|
|
135
|
-
rejected with a `TypeError`.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
`data`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
`
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
},
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
1
|
+
# @jfdevelops/create-error
|
|
2
|
+
|
|
3
|
+
Create strongly typed, configurable error families from any synchronous
|
|
4
|
+
[Standard Schema](https://standardschema.dev/schema) object.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @jfdevelops/create-error
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Bring your own Standard Schema-compatible validation library, such as Zod,
|
|
13
|
+
Valibot, or ArkType. `create-error` does not bundle one.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createError } from '@jfdevelops/create-error';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
|
|
21
|
+
const createFormError = createError({
|
|
22
|
+
definition: z.object({
|
|
23
|
+
code: z.string(),
|
|
24
|
+
scope: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
data: {
|
|
27
|
+
property: 'context',
|
|
28
|
+
resolve({ definition, input }) {
|
|
29
|
+
return { ...input, scope: definition.scope };
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
message({ definition, data, implementation }) {
|
|
33
|
+
return implementation({
|
|
34
|
+
context: data,
|
|
35
|
+
scope: definition.scope,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
properties({ definition, data, implementation }) {
|
|
39
|
+
return {
|
|
40
|
+
code: definition.code,
|
|
41
|
+
scope: definition.scope,
|
|
42
|
+
context: data,
|
|
43
|
+
renderMessage(renderer = implementation) {
|
|
44
|
+
return renderer({
|
|
45
|
+
context: data,
|
|
46
|
+
scope: definition.scope,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
toJSON(error) {
|
|
52
|
+
return {
|
|
53
|
+
name: error.name,
|
|
54
|
+
code: error.code,
|
|
55
|
+
scope: error.scope,
|
|
56
|
+
message: error.message,
|
|
57
|
+
context: error.context,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const FormError = createFormError.Error;
|
|
63
|
+
|
|
64
|
+
export class InvalidFieldError extends createFormError({
|
|
65
|
+
code: 'invalidField',
|
|
66
|
+
scope: 'field',
|
|
67
|
+
})
|
|
68
|
+
.defineContext(
|
|
69
|
+
z.object({
|
|
70
|
+
field: z.string(),
|
|
71
|
+
scope: z.literal('field'),
|
|
72
|
+
}),
|
|
73
|
+
)
|
|
74
|
+
.implement(
|
|
75
|
+
({ code, context }) => `${code}: ${context.field} is invalid`,
|
|
76
|
+
) {}
|
|
77
|
+
|
|
78
|
+
const error = new InvalidFieldError({ field: 'email' });
|
|
79
|
+
|
|
80
|
+
error.code; // "invalidField"
|
|
81
|
+
error.scope; // "field"
|
|
82
|
+
error.context; // { field: string; scope: "field" }
|
|
83
|
+
error instanceof Error; // true
|
|
84
|
+
error instanceof FormError; // true
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
No explicit generic arguments or `as const` assertions are needed. Definition
|
|
88
|
+
inputs are checked by the schema, and definition-derived fields use its parsed
|
|
89
|
+
output type so transformations remain type-safe.
|
|
90
|
+
|
|
91
|
+
For reusable or separately declared configurations, the package exports a
|
|
92
|
+
common `CreateErrorConfig` type:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { createError, type CreateErrorConfig } from '@jfdevelops/create-error';
|
|
96
|
+
|
|
97
|
+
const definition = z.object({ code: z.string() });
|
|
98
|
+
|
|
99
|
+
const config = {
|
|
100
|
+
definition,
|
|
101
|
+
data: {
|
|
102
|
+
property: 'context',
|
|
103
|
+
resolve: ({ input }) => input,
|
|
104
|
+
},
|
|
105
|
+
message: ({ data, implementation }) => implementation(data),
|
|
106
|
+
} satisfies CreateErrorConfig<typeof definition>;
|
|
107
|
+
|
|
108
|
+
const createDomainError = createError(config);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## How the factory works
|
|
112
|
+
|
|
113
|
+
The API has four stages:
|
|
114
|
+
|
|
115
|
+
1. `createError(blueprint)` configures a family and returns a factory.
|
|
116
|
+
2. `factory(definition)` validates and captures one definition.
|
|
117
|
+
3. The returned builder optionally selects a context schema with
|
|
118
|
+
`defineContext(schema)`.
|
|
119
|
+
4. `implement(callback)` captures the implementation and produces an
|
|
120
|
+
extendable concrete error class.
|
|
121
|
+
|
|
122
|
+
The definition builder exposes both `defineContext` and `implement`.
|
|
123
|
+
`defineContext` returns a narrower builder that exposes only `implement`, so a
|
|
124
|
+
context schema can be selected only once. Calling `implement` directly keeps
|
|
125
|
+
context schemas optional. Every implementation receives exactly one argument;
|
|
126
|
+
the blueprint decides its shape when it calls `implementation(argument)`. When
|
|
127
|
+
that argument is a constructed object, the concrete definition fields are added
|
|
128
|
+
automatically, so implementations can read values such as `code` without the
|
|
129
|
+
blueprint repeating them. Forwarding opaque data directly preserves its original
|
|
130
|
+
scalar, tuple, object, or class-instance shape.
|
|
131
|
+
|
|
132
|
+
The `definition` option must implement Standard Schema V1 and must produce an
|
|
133
|
+
object. Validation runs when `factory(definition)` is called. Because class
|
|
134
|
+
creation is synchronous, a schema whose `validate` method returns a promise or
|
|
135
|
+
other thenable is rejected with a `TypeError`. `createError` also checks that
|
|
136
|
+
`data.resolve`, `message`, and any `properties` or `toJSON` options are
|
|
137
|
+
functions.
|
|
138
|
+
|
|
139
|
+
## What `data` and `properties` do
|
|
140
|
+
|
|
141
|
+
`data` defines the constructor-input lifecycle. Its `resolve` callback receives
|
|
142
|
+
the concrete definition and the value passed to `new ErrorClass(input)`. The
|
|
143
|
+
callback can preserve that input, normalize it, or combine it with definition
|
|
144
|
+
values. After optional `defineContext` validation, the final value is stored on
|
|
145
|
+
the instance using `data.property`.
|
|
146
|
+
|
|
147
|
+
`properties` defines the rest of the instance API. It can expose definition
|
|
148
|
+
values such as a stable error code, pass the resolved data through, or add
|
|
149
|
+
methods and getters. It does not participate in input validation or
|
|
150
|
+
transformation. Because each concrete error types its data separately, the
|
|
151
|
+
blueprint can store `data` but cannot read its fields; derive values from
|
|
152
|
+
`definition` or call `implementation(data)` instead.
|
|
153
|
+
|
|
154
|
+
For a small error family, the constructor input can pass straight through and
|
|
155
|
+
`properties` can be omitted entirely:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const createLookupError = createError({
|
|
159
|
+
definition: z.object({ code: z.string() }),
|
|
160
|
+
data: {
|
|
161
|
+
property: 'resourceId',
|
|
162
|
+
resolve: ({ input }) => input,
|
|
163
|
+
},
|
|
164
|
+
message: ({ data, implementation }) => implementation(data),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
class MissingUserError extends createLookupError({ code: 'missingUser' })
|
|
168
|
+
.defineContext(z.string())
|
|
169
|
+
.implement((resourceId) => `User ${resourceId} was not found`) {}
|
|
170
|
+
|
|
171
|
+
const error = new MissingUserError('user_123');
|
|
172
|
+
error.resourceId; // string
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Use both options when constructor input needs normalization and the error should
|
|
176
|
+
present a richer public API:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const createValidationError = createError({
|
|
180
|
+
definition: z.object({
|
|
181
|
+
code: z.string(),
|
|
182
|
+
section: z.string(),
|
|
183
|
+
}),
|
|
184
|
+
data: {
|
|
185
|
+
property: 'context',
|
|
186
|
+
resolve: ({ definition, input }) => ({
|
|
187
|
+
...input,
|
|
188
|
+
section: definition.section,
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
message: ({ data, implementation }) => implementation(data),
|
|
192
|
+
properties: ({ definition, data }) => ({
|
|
193
|
+
code: definition.code,
|
|
194
|
+
section: definition.section,
|
|
195
|
+
describe: () => `${definition.code} in ${definition.section}`,
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
class InvalidEmailError extends createValidationError({
|
|
200
|
+
code: 'invalidEmail',
|
|
201
|
+
section: 'profile',
|
|
202
|
+
})
|
|
203
|
+
.defineContext(
|
|
204
|
+
z.object({
|
|
205
|
+
field: z.string(),
|
|
206
|
+
section: z.literal('profile'),
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
.implement(({ field }) => `${field} is invalid`) {}
|
|
210
|
+
|
|
211
|
+
const error = new InvalidEmailError({ field: 'email' });
|
|
212
|
+
error.context; // { field: string; section: "profile" }
|
|
213
|
+
error.code; // "invalidEmail"
|
|
214
|
+
error.describe(); // "invalidEmail in profile"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Use schema-native restrictions when definitions have a closed set of values:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const createHttpError = createError({
|
|
221
|
+
definition: z.object({
|
|
222
|
+
code: z.enum(['notFound', 'unauthorized']),
|
|
223
|
+
scope: z.literal('request'),
|
|
224
|
+
}),
|
|
225
|
+
data: {
|
|
226
|
+
property: 'details',
|
|
227
|
+
resolve: ({ input }) => input,
|
|
228
|
+
},
|
|
229
|
+
message: ({ implementation, data }) => implementation(data),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const NotFoundError = createHttpError({
|
|
233
|
+
code: 'notFound',
|
|
234
|
+
scope: 'request',
|
|
235
|
+
})
|
|
236
|
+
.defineContext(z.object({ resource: z.string() }))
|
|
237
|
+
.implement((details) => `${details.resource} was not found`, {
|
|
238
|
+
name: 'NotFoundError',
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Naming error classes
|
|
243
|
+
|
|
244
|
+
A class declared with `class NotFoundError extends ... {}` uses its own name for
|
|
245
|
+
`error.name`, stack traces, and serialized output. A class assigned to a
|
|
246
|
+
variable has no name of its own, so pass `name` as the second argument to
|
|
247
|
+
`implement`. Without either, instances keep the native `"Error"` name.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const NotFoundError = createHttpError({ code: 'notFound', scope: 'request' })
|
|
251
|
+
.implement((details: { resource: string }) => details.resource, {
|
|
252
|
+
name: 'NotFoundError',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
new NotFoundError({ resource: 'user' }).name; // "NotFoundError"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Data and behavior
|
|
259
|
+
|
|
260
|
+
`data.resolve` can return any value: objects, scalars, tuples, unions, class
|
|
261
|
+
instances, `null`, and `undefined` are all supported. When `defineContext` is
|
|
262
|
+
used, its Standard Schema validates the resolved data, its input type determines
|
|
263
|
+
the concrete error constructor input, and its output type is passed to the
|
|
264
|
+
implementation and stored on the error. Without a context schema, the last
|
|
265
|
+
parameter of the consumer implementation determines those types.
|
|
266
|
+
|
|
267
|
+
For each new error instance, callbacks run in this order:
|
|
268
|
+
|
|
269
|
+
1. `data.resolve`
|
|
270
|
+
2. Context validation and transformation, when configured
|
|
271
|
+
3. `message`
|
|
272
|
+
4. `properties`, when configured
|
|
273
|
+
|
|
274
|
+
Exceptions thrown by a schema or callback are not wrapped. Native `Error`
|
|
275
|
+
behavior is preserved, including stack traces, subclass names, prototypes, and
|
|
276
|
+
the optional `cause` passed as the second constructor argument.
|
|
277
|
+
|
|
278
|
+
Only plain objects passed to `implementation(argument)` receive the definition
|
|
279
|
+
fields. Class instances, such as a `Date` or `Map`, are passed through
|
|
280
|
+
unchanged. The types recognize common built-in classes, but they cannot tell
|
|
281
|
+
your own class instances from plain objects, so wrap those in an object, as in
|
|
282
|
+
`implementation({ user })`, when the implementation needs definition fields.
|
|
283
|
+
|
|
284
|
+
Every family exposes its shared base as `factory.Error`, and `factory.is(value)`
|
|
285
|
+
checks whether a value came from any class in the family. Both narrow to the
|
|
286
|
+
fields every family error shares: definition-derived properties use the
|
|
287
|
+
schema's output type, and the data property is `unknown`.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
try {
|
|
291
|
+
await saveForm();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (createFormError.is(error)) {
|
|
294
|
+
error.code; // string
|
|
295
|
+
error.context; // unknown
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`is` is an `instanceof` check, so errors created by another copy of the
|
|
301
|
+
factory, such as one loaded in a different realm, do not match. To name the
|
|
302
|
+
shared instance type, use `InstanceType<typeof createFormError.Error>`.
|
|
303
|
+
|
|
304
|
+
Concrete classes and named subclasses also inherit a lazy invariant helper:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
InvalidFieldError.invariant(
|
|
308
|
+
formIsValid,
|
|
309
|
+
() => ({ field: 'email' }),
|
|
310
|
+
{ cause },
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The input function runs only when the condition is falsy. TypeScript only
|
|
315
|
+
allows assertion methods on explicitly typed names, so the static method works
|
|
316
|
+
on `class` declarations. For a class stored in a `const`, use the standalone
|
|
317
|
+
`invariant` export, which narrows the same way:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import { invariant } from '@jfdevelops/create-error';
|
|
321
|
+
|
|
322
|
+
invariant(NotFoundError, user, () => ({ resource: 'user' }));
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
When an error's constructor input is itself a function, pass it wrapped, as in
|
|
326
|
+
`() => handler`, so it is not mistaken for lazy input. The types enforce this.
|
|
327
|
+
|
|
328
|
+
When `toJSON` is configured, it is installed once on the family prototype and
|
|
329
|
+
used by `JSON.stringify`. Its callback can read native error fields, configured
|
|
330
|
+
properties, and the resolved data property; each concrete error's `toJSON()`
|
|
331
|
+
return type preserves parsed definition output and context-schema output. When
|
|
332
|
+
omitted, the family does not add a `toJSON` method.
|
|
333
|
+
|
|
334
|
+
## Property safety
|
|
335
|
+
|
|
336
|
+
`properties` may add fields, methods, getters, symbols, and other own property
|
|
337
|
+
descriptors. It cannot replace `name`, `message`, `stack`, `cause`,
|
|
338
|
+
`constructor`, or `prototype`. The configured data property may only be
|
|
339
|
+
repeated with the exact resolved value. Collisions throw a `TypeError`.
|
|
340
|
+
|
|
341
|
+
The library always creates the family base class. Customize a family through
|
|
342
|
+
the blueprint instead of supplying a separate `BaseError`.
|
|
343
|
+
|
|
344
|
+
## Releasing
|
|
345
|
+
|
|
346
|
+
This project uses Changesets for versioning and changelogs. Add a changeset
|
|
347
|
+
with `pnpm changeset` whenever a package change should be released. Maintainers
|
|
348
|
+
can consume pending changesets with `pnpm version-packages`, review the updated
|
|
349
|
+
version and changelog, then publish with `pnpm release`.
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT
|