wellcrafted 0.32.0 → 0.33.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 CHANGED
@@ -54,19 +54,22 @@ function getUser(id: UserId) { /* ... */ }
54
54
  ```
55
55
 
56
56
  ### 📋 Tagged Errors
57
- Structured, serializable errors with a fluent API
57
+ Structured, serializable errors with a declarative API
58
58
  ```typescript
59
- import { createTaggedError } from "wellcrafted/error";
59
+ import { defineErrors, type InferError } from "wellcrafted/error";
60
60
 
61
- // Static error — no fields needed
62
- const { ValidationError } = createTaggedError("ValidationError")
63
- .withMessage(() => "Email is required");
64
- ValidationError();
65
-
66
- // Structured error — fields are spread flat on the error object
67
- const { ApiError } = createTaggedError("ApiError")
68
- .withFields<{ endpoint: string }>()
69
- .withMessage(({ endpoint }) => `Request to ${endpoint} failed`);
61
+ const errors = defineErrors({
62
+ // Static error no fields needed
63
+ ValidationError: () => ({
64
+ message: "Email is required",
65
+ }),
66
+ // Structured error — fields are spread flat on the error object
67
+ ApiError: (fields: { endpoint: string }) => ({
68
+ ...fields,
69
+ message: `Request to ${fields.endpoint} failed`,
70
+ }),
71
+ });
72
+ const { ValidationError, ApiError } = errors;
70
73
  ```
71
74
 
72
75
  ### 🔄 Query Integration
@@ -109,13 +112,17 @@ npm install wellcrafted
109
112
 
110
113
  ```typescript
111
114
  import { tryAsync } from "wellcrafted/result";
112
- import { createTaggedError } from "wellcrafted/error";
115
+ import { defineErrors, type InferError } from "wellcrafted/error";
113
116
 
114
- // Define your error with factory function
115
- const { ApiError, ApiErr } = createTaggedError("ApiError")
116
- .withFields<{ endpoint: string }>()
117
- .withMessage(({ endpoint }) => `Failed to fetch ${endpoint}`);
118
- type ApiError = ReturnType<typeof ApiError>;
117
+ // Define your errors declaratively
118
+ const errors = defineErrors({
119
+ ApiError: (fields: { endpoint: string }) => ({
120
+ ...fields,
121
+ message: `Failed to fetch ${fields.endpoint}`,
122
+ }),
123
+ });
124
+ const { ApiError, ApiErr } = errors;
125
+ type ApiError = InferError<typeof errors, "ApiError">;
119
126
 
120
127
  // Wrap any throwing operation
121
128
  const { data, error } = await tryAsync({
@@ -213,14 +220,20 @@ if (error) {
213
220
  ### Wrap Unsafe Operations
214
221
 
215
222
  ```typescript
216
- // Define errors with fields and message template
217
- const { ParseError, ParseErr } = createTaggedError("ParseError")
218
- .withFields<{ input: string }>()
219
- .withMessage(({ input }) => `Invalid JSON: ${input.slice(0, 50)}`);
223
+ import { defineErrors, type InferError } from "wellcrafted/error";
220
224
 
221
- const { NetworkError, NetworkErr } = createTaggedError("NetworkError")
222
- .withFields<{ url: string }>()
223
- .withMessage(({ url }) => `Request to ${url} failed`);
225
+ // Define errors declaratively
226
+ const errors = defineErrors({
227
+ ParseError: (fields: { input: string }) => ({
228
+ ...fields,
229
+ message: `Invalid JSON: ${fields.input.slice(0, 50)}`,
230
+ }),
231
+ NetworkError: (fields: { url: string }) => ({
232
+ ...fields,
233
+ message: `Request to ${fields.url} failed`,
234
+ }),
235
+ });
236
+ const { ParseErr, NetworkErr } = errors;
224
237
 
225
238
  // Synchronous
226
239
  const result = trySync({
@@ -239,16 +252,19 @@ const result = await tryAsync({
239
252
 
240
253
  ```typescript
241
254
  // 1. Service Layer - Pure business logic
242
- import { createTaggedError } from "wellcrafted/error";
255
+ import { defineErrors, type InferError } from "wellcrafted/error";
243
256
  import { tryAsync, Result, Ok } from "wellcrafted/result";
244
257
 
245
- const { RecorderServiceError, RecorderServiceErr } = createTaggedError("RecorderServiceError")
246
- .withFields<{ currentState?: string; permissions?: string }>()
247
- .withMessage(({ permissions, currentState }) => {
248
- if (permissions) return `Missing ${permissions} permission`;
249
- return `Invalid recorder state: ${currentState}`;
250
- });
251
- type RecorderServiceError = ReturnType<typeof RecorderServiceError>;
258
+ const recorderErrors = defineErrors({
259
+ RecorderServiceError: (fields: { currentState?: string; permissions?: string }) => ({
260
+ ...fields,
261
+ message: fields.permissions
262
+ ? `Missing ${fields.permissions} permission`
263
+ : `Invalid recorder state: ${fields.currentState}`,
264
+ }),
265
+ });
266
+ const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
267
+ type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;
252
268
 
253
269
  export function createRecorderService() {
254
270
  let isRecording = false;
@@ -367,8 +383,12 @@ const { data: parsed } = trySync({
367
383
 
368
384
  ### Propagation Pattern (May Fail)
369
385
  ```typescript
370
- const { ParseError, ParseErr } = createTaggedError("ParseError")
371
- .withMessage(() => "Invalid JSON");
386
+ const parseErrors = defineErrors({
387
+ ParseError: () => ({
388
+ message: "Invalid JSON",
389
+ }),
390
+ });
391
+ const { ParseErr } = parseErrors;
372
392
 
373
393
  // When catch can return Err<E>, function returns Result<T, E>
374
394
  const mayFail = trySync({
@@ -421,16 +441,18 @@ Based on real-world usage, here's the recommended pattern for creating services
421
441
  ### Factory Function Pattern
422
442
 
423
443
  ```typescript
424
- import { createTaggedError } from "wellcrafted/error";
444
+ import { defineErrors, type InferError } from "wellcrafted/error";
425
445
  import { Result, Ok } from "wellcrafted/result";
426
446
 
427
447
  // 1. Define service-specific errors with typed fields and message
428
- const { RecorderServiceError, RecorderServiceErr } = createTaggedError("RecorderServiceError")
429
- .withFields<{ isRecording: boolean }>()
430
- .withMessage(({ isRecording }) =>
431
- isRecording ? "Already recording" : "Not currently recording"
432
- );
433
- type RecorderServiceError = ReturnType<typeof RecorderServiceError>;
448
+ const recorderErrors = defineErrors({
449
+ RecorderServiceError: (fields: { isRecording: boolean }) => ({
450
+ ...fields,
451
+ message: fields.isRecording ? "Already recording" : "Not currently recording",
452
+ }),
453
+ });
454
+ const { RecorderServiceError, RecorderServiceErr } = recorderErrors;
455
+ type RecorderServiceError = InferError<typeof recorderErrors, "RecorderServiceError">;
434
456
 
435
457
  // 2. Create service with factory function
436
458
  export function createRecorderService() {
@@ -534,12 +556,16 @@ export async function GET(request: Request) {
534
556
  <summary><b>Form Validation</b></summary>
535
557
 
536
558
  ```typescript
537
- const { FormError, FormErr } = createTaggedError("FormError")
538
- .withFields<{ fields: Record<string, string[]> }>()
539
- .withMessage(({ fields }) => {
540
- const fieldNames = Object.keys(fields).join(", ");
541
- return `Validation failed for: ${fieldNames}`;
542
- });
559
+ import { defineErrors, type InferError } from "wellcrafted/error";
560
+
561
+ const formErrors = defineErrors({
562
+ FormError: (fields: { fields: Record<string, string[]> }) => ({
563
+ ...fields,
564
+ message: `Validation failed for: ${Object.keys(fields.fields).join(", ")}`,
565
+ }),
566
+ });
567
+ const { FormErr } = formErrors;
568
+ type FormError = InferError<typeof formErrors, "FormError">;
543
569
 
544
570
  function validateLoginForm(data: unknown): Result<LoginData, FormError> {
545
571
  const errors: Record<string, string[]> = {};
@@ -617,20 +643,19 @@ For comprehensive examples, service layer patterns, framework integrations, and
617
643
  - **`defineMutation(options)`** - Define a mutation with dual interface (`.options` + callable/`.execute()`)
618
644
 
619
645
  ### Error Functions
620
- - **`createTaggedError(name)`** - Creates error factory functions with fluent API
621
- - Returns `{ErrorName}` (plain error) and `{ErrorName}Err` (Err-wrapped)
622
- - Chain `.withFields<T>()` to add typed fields (spread flat on the error object)
623
- - Chain `.withMessage(fn)` *(optional)* to seal the message with a template `message` is not in the factory input type when present
624
- - Without `.withMessage()`, `message` is required at the call site
625
- - `name` is a reserved key — prevented at compile time by `NoReservedKeys`
626
- - Factory input is flat (e.g., `{ endpoint: '/api' }`), not nested
646
+ - **`defineErrors(definitions)`** - Define multiple error factories in a single declaration
647
+ - Each key becomes an error name; the value is a factory function returning fields + `message`
648
+ - Returns `{ErrorName}` (plain error) and `{ErrorName}Err` (Err-wrapped) for each key
649
+ - Factory functions receive typed fields and return `{ ...fields, message }`
650
+ - No-field errors use `() => ({ message: '...' })`
651
+ - `name` is a reserved key — prevented at compile time
627
652
  - **`extractErrorMessage(error)`** - Extract readable message from unknown error
628
653
 
629
654
  ### Types
630
655
  - **`Result<T, E>`** - Union of Ok<T> | Err<E>
631
656
  - **`Ok<T>`** - Success result type
632
657
  - **`Err<E>`** - Error result type
633
- - **`TaggedError<T>`** - Structured error type
658
+ - **`InferError<TErrors, TName>`** - Extract error type from `defineErrors` result
634
659
  - **`Brand<T, B>`** - Branded type wrapper
635
660
  - **`ExtractOkFromResult<R>`** - Extract Ok variant from Result union
636
661
  - **`ExtractErrFromResult<R>`** - Extract Err variant from Result union
@@ -21,111 +21,76 @@ type AnyTaggedError = {
21
21
  message: string;
22
22
  };
23
23
  /**
24
- * A tagged error type for type-safe error handling.
25
- * Uses the `name` property as a discriminator for tagged unions.
26
- * Additional fields are spread flat on the error object.
27
- *
28
- * @template TName - The error name (discriminator for tagged unions)
29
- * @template TFields - Additional fields spread flat on the error (default: none)
24
+ * Constructor return must include `message: string`.
25
+ * JSON serializability is a convention, not enforced at the type level
26
+ * (optional fields produce `T | undefined` which breaks `JsonObject`).
30
27
  */
31
- type TaggedError<TName extends string = string, TFields extends JsonObject = Record<never, never>> = Readonly<{
32
- name: TName;
28
+ type ErrorBody = {
33
29
  message: string;
34
- } & TFields>;
35
- //# sourceMappingURL=types.d.ts.map
36
- //#endregion
37
- //#region src/error/utils.d.ts
38
- /**
39
- * Extracts a readable error message from an unknown error value
40
- *
41
- * @param error - The unknown error to extract a message from
42
- * @returns A string representation of the error
43
- */
44
- declare function extractErrorMessage(error: unknown): string;
45
- /**
46
- * Constraint that prevents the reserved key `name` from appearing in fields.
47
- * `message` is not reserved — when `.withMessage()` seals it, `message` is
48
- * simply absent from the input type. When `.withMessage()` is not used,
49
- * `message` is a built-in input handled separately from TFields.
50
- */
51
- type NoReservedKeys = {
52
- name?: never;
53
30
  };
54
31
  /**
55
- * Replaces the "Error" suffix with "Err" suffix in error type names.
32
+ * Per-key validation: tells the user exactly what `name` will be stamped as.
33
+ * If a user provides `name` in the return object, they see a descriptive error.
56
34
  */
57
- type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
58
- /**
59
- * Message template function type.
60
- * Receives the fields (without name/message) and returns a message string.
61
- */
62
- type MessageFn<TFields extends JsonObject> = (input: TFields) => string;
63
- /** Factory input when message is required (no `.withMessage()`). */
64
- type RequiredMessageInput<TFields extends JsonObject> = {
35
+ type ValidateErrorBody<K extends string> = {
65
36
  message: string;
66
- } & TFields;
67
- /**
68
- * Whether the entire input parameter can be omitted (called with no args).
69
- * True when TFields is empty or all-optional (and message is sealed via withMessage).
70
- */
71
- type IsInputOptional<TFields extends JsonObject> = Partial<TFields> extends TFields ? true : false;
72
- /**
73
- * Factories returned by `.withMessage()` — message is sealed by the template.
74
- * `message` is NOT in the input type. Only has factory functions, no chain methods.
75
- */
76
- type SealedFactories<TName extends `${string}Error`, TFields extends JsonObject> = { [K in TName]: IsInputOptional<TFields> extends true ? (input?: TFields) => TaggedError<TName, TFields> : (input: TFields) => TaggedError<TName, TFields> } & { [K in ReplaceErrorWithErr<TName>]: IsInputOptional<TFields> extends true ? (input?: TFields) => Err<TaggedError<TName, TFields>> : (input: TFields) => Err<TaggedError<TName, TFields>> };
77
- /**
78
- * Builder object returned by createTaggedError (and .withFields()).
79
- * Has factory functions (message required) AND chain methods.
80
- */
81
- type ErrorBuilder<TName extends `${string}Error`, TFields extends JsonObject = Record<never, never>> = { [K in TName]: (input: RequiredMessageInput<TFields>) => TaggedError<TName, TFields> } & { [K in ReplaceErrorWithErr<TName>]: (input: RequiredMessageInput<TFields>) => Err<TaggedError<TName, TFields>> } & {
82
- /** Defines additional fields spread flat on the error object. */
83
- withFields<T extends JsonObject & NoReservedKeys>(): ErrorBuilder<TName, T>;
84
- /**
85
- * Seals the message — the template owns it entirely.
86
- * `message` is NOT in the returned factory's input type.
87
- */
88
- withMessage(fn: MessageFn<TFields>): SealedFactories<TName, TFields>;
37
+ name?: `The 'name' key is reserved as '${K}'. Remove it.`;
89
38
  };
39
+ /** The config: each key is a variant name, each value is a constructor function. */
40
+ type ErrorsConfig = Record<string, (...args: any[]) => ErrorBody>;
41
+ /** Validates each config entry, injecting the key-specific `name` reservation message. */
42
+ type ValidatedConfig<T extends ErrorsConfig> = { [K in keyof T & string]: T[K] extends ((...args: infer A) => infer R) ? (...args: A) => R & ValidateErrorBody<K> : T[K] };
43
+ /** Single factory: takes constructor args, returns Err-wrapped error. */
44
+ type ErrorFactory<TName extends string, TFn extends (...args: any[]) => ErrorBody> = { [K in TName]: (...args: Parameters<TFn>) => Err<Readonly<{
45
+ name: TName;
46
+ } & ReturnType<TFn>>> };
47
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
48
+ /** Return type of `defineErrors`. Maps each config key to its factory. */
49
+ type DefineErrorsReturn<TConfig extends ErrorsConfig> = UnionToIntersection<{ [K in keyof TConfig & string]: ErrorFactory<K, TConfig[K]> }[keyof TConfig & string]>;
50
+ /** Extract the error type from a single factory. */
51
+ type InferError<T> = T extends ((...args: any[]) => Err<infer R>) ? R : never;
52
+ /** Extract union of ALL error types from a defineErrors return. */
53
+ type InferErrors<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => Err<infer R>) ? R : never }[keyof T];
54
+ //#endregion
55
+ //#region src/error/defineErrors.d.ts
90
56
  /**
91
- * Creates a new tagged error type with a fluent builder API.
57
+ * Defines a set of typed error factories using Rust-style namespaced variants.
92
58
  *
93
- * Returns an object with factory functions immediately available (message required),
94
- * plus `.withFields<T>()` and `.withMessage(fn)` for further configuration.
59
+ * Each key is a short variant name (the namespace provides context). Every
60
+ * factory returns `Err<...>` directly ready for `trySync`/`tryAsync` catch
61
+ * handlers. The variant name is stamped as `name` on the error object.
95
62
  *
96
- * Two mutually exclusive modes:
97
- * 1. **No `.withMessage()`**: `message` is required at the call site
98
- * 2. **With `.withMessage()`**: message is sealed — NOT in the input type
99
- *
100
- * @example No fields, message required at call site
63
+ * @example
101
64
  * ```ts
102
- * const { SimpleError, SimpleErr } = createTaggedError('SimpleError');
103
- * SimpleErr({ message: 'Something went wrong' });
104
- * ```
65
+ * const HttpError = defineErrors({
66
+ * Connection: ({ cause }: { cause: string }) => ({
67
+ * message: `Failed to connect: ${cause}`,
68
+ * cause,
69
+ * }),
70
+ * Parse: ({ cause }: { cause: string }) => ({
71
+ * message: `Failed to parse: ${cause}`,
72
+ * cause,
73
+ * }),
74
+ * });
105
75
  *
106
- * @example No fields, with sealed message
107
- * ```ts
108
- * const { RecorderBusyError } = createTaggedError('RecorderBusyError')
109
- * .withMessage(() => 'A recording is already in progress');
110
- * RecorderBusyError(); // message always: 'A recording is already in progress'
111
- * ```
76
+ * type HttpError = InferErrors<typeof HttpError>;
112
77
  *
113
- * @example With fields, message required
114
- * ```ts
115
- * const { FsReadError } = createTaggedError('FsReadError')
116
- * .withFields<{ path: string }>();
117
- * FsReadError({ message: 'Failed to read', path: '/etc/config' });
78
+ * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
118
79
  * ```
80
+ */
81
+ declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
82
+ //# sourceMappingURL=defineErrors.d.ts.map
83
+ //#endregion
84
+ //#region src/error/extractErrorMessage.d.ts
85
+ /**
86
+ * Extracts a readable error message from an unknown error value
119
87
  *
120
- * @example With fields + sealed message (template computes from fields)
121
- * ```ts
122
- * const { ResponseError } = createTaggedError('ResponseError')
123
- * .withFields<{ status: number }>()
124
- * .withMessage(({ status }) => `HTTP ${status}`);
125
- * ResponseError({ status: 404 }); // message: "HTTP 404"
126
- * ```
88
+ * @param error - The unknown error to extract a message from
89
+ * @returns A string representation of the error
127
90
  */
128
- declare function createTaggedError<TName extends `${string}Error`>(name: TName): ErrorBuilder<TName>;
91
+ declare function extractErrorMessage(error: unknown): string;
92
+ //# sourceMappingURL=extractErrorMessage.d.ts.map
93
+
129
94
  //#endregion
130
- export { AnyTaggedError, JsonObject, JsonValue, TaggedError, createTaggedError, extractErrorMessage };
95
+ export { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, JsonObject, JsonValue, ValidatedConfig, defineErrors, extractErrorMessage };
131
96
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/utils.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/error/types.ts","../../src/error/defineErrors.ts","../../src/error/extractErrorMessage.ts"],"sourcesContent":[],"mappings":"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"}
@@ -1,6 +1,45 @@
1
1
  import { Err } from "../result-DnOm5ds5.js";
2
2
 
3
- //#region src/error/utils.ts
3
+ //#region src/error/defineErrors.ts
4
+ /**
5
+ * Defines a set of typed error factories using Rust-style namespaced variants.
6
+ *
7
+ * Each key is a short variant name (the namespace provides context). Every
8
+ * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
9
+ * handlers. The variant name is stamped as `name` on the error object.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const HttpError = defineErrors({
14
+ * Connection: ({ cause }: { cause: string }) => ({
15
+ * message: `Failed to connect: ${cause}`,
16
+ * cause,
17
+ * }),
18
+ * Parse: ({ cause }: { cause: string }) => ({
19
+ * message: `Failed to parse: ${cause}`,
20
+ * cause,
21
+ * }),
22
+ * });
23
+ *
24
+ * type HttpError = InferErrors<typeof HttpError>;
25
+ *
26
+ * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
27
+ * ```
28
+ */
29
+ function defineErrors(config) {
30
+ const result = {};
31
+ for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
32
+ const body = ctor(...args);
33
+ return Err(Object.freeze({
34
+ ...body,
35
+ name
36
+ }));
37
+ };
38
+ return result;
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/error/extractErrorMessage.ts
4
43
  /**
5
44
  * Extracts a readable error message from an unknown error value
6
45
  *
@@ -34,83 +73,7 @@ function extractErrorMessage(error) {
34
73
  }
35
74
  return String(error);
36
75
  }
37
- /**
38
- * Creates a new tagged error type with a fluent builder API.
39
- *
40
- * Returns an object with factory functions immediately available (message required),
41
- * plus `.withFields<T>()` and `.withMessage(fn)` for further configuration.
42
- *
43
- * Two mutually exclusive modes:
44
- * 1. **No `.withMessage()`**: `message` is required at the call site
45
- * 2. **With `.withMessage()`**: message is sealed — NOT in the input type
46
- *
47
- * @example No fields, message required at call site
48
- * ```ts
49
- * const { SimpleError, SimpleErr } = createTaggedError('SimpleError');
50
- * SimpleErr({ message: 'Something went wrong' });
51
- * ```
52
- *
53
- * @example No fields, with sealed message
54
- * ```ts
55
- * const { RecorderBusyError } = createTaggedError('RecorderBusyError')
56
- * .withMessage(() => 'A recording is already in progress');
57
- * RecorderBusyError(); // message always: 'A recording is already in progress'
58
- * ```
59
- *
60
- * @example With fields, message required
61
- * ```ts
62
- * const { FsReadError } = createTaggedError('FsReadError')
63
- * .withFields<{ path: string }>();
64
- * FsReadError({ message: 'Failed to read', path: '/etc/config' });
65
- * ```
66
- *
67
- * @example With fields + sealed message (template computes from fields)
68
- * ```ts
69
- * const { ResponseError } = createTaggedError('ResponseError')
70
- * .withFields<{ status: number }>()
71
- * .withMessage(({ status }) => `HTTP ${status}`);
72
- * ResponseError({ status: 404 }); // message: "HTTP 404"
73
- * ```
74
- */
75
- function createTaggedError(name) {
76
- const errName = name.replace(/Error$/, "Err");
77
- const createBuilder = () => {
78
- const errorConstructor = (input) => {
79
- const { message,...fields } = input;
80
- return {
81
- name,
82
- message,
83
- ...fields
84
- };
85
- };
86
- const errConstructor = (input) => Err(errorConstructor(input));
87
- return {
88
- [name]: errorConstructor,
89
- [errName]: errConstructor,
90
- withFields() {
91
- return createBuilder();
92
- },
93
- withMessage(fn) {
94
- const sealedErrorConstructor = (input) => {
95
- const fields = input ?? {};
96
- const message = fn(fields);
97
- return {
98
- name,
99
- message,
100
- ...fields
101
- };
102
- };
103
- const sealedErrConstructor = (input) => Err(sealedErrorConstructor(input));
104
- return {
105
- [name]: sealedErrorConstructor,
106
- [errName]: sealedErrConstructor
107
- };
108
- }
109
- };
110
- };
111
- return createBuilder();
112
- }
113
76
 
114
77
  //#endregion
115
- export { createTaggedError, extractErrorMessage };
78
+ export { defineErrors, extractErrorMessage };
116
79
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["error: unknown","name: TName","input: { message: string } & TFields","fn: MessageFn<TFields>","input?: TFields"],"sources":["../../src/error/utils.ts"],"sourcesContent":["import type { TaggedError, JsonObject } from \"./types.js\";\nimport { Err } from \"../result/result.js\";\n\n/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n\n/**\n * Constraint that prevents the reserved key `name` from appearing in fields.\n * `message` is not reserved — when `.withMessage()` seals it, `message` is\n * simply absent from the input type. When `.withMessage()` is not used,\n * `message` is a built-in input handled separately from TFields.\n */\ntype NoReservedKeys = { name?: never };\n\n/**\n * Replaces the \"Error\" suffix with \"Err\" suffix in error type names.\n */\ntype ReplaceErrorWithErr<T extends `${string}Error`> =\n\tT extends `${infer TBase}Error` ? `${TBase}Err` : never;\n\n/**\n * Message template function type.\n * Receives the fields (without name/message) and returns a message string.\n */\ntype MessageFn<TFields extends JsonObject> = (input: TFields) => string;\n\n// =============================================================================\n// Factory Input Types\n// =============================================================================\n\n/** Factory input when message is required (no `.withMessage()`). */\ntype RequiredMessageInput<TFields extends JsonObject> = { message: string } &\n\tTFields;\n\n/**\n * Whether the entire input parameter can be omitted (called with no args).\n * True when TFields is empty or all-optional (and message is sealed via withMessage).\n */\ntype IsInputOptional<TFields extends JsonObject> =\n\tPartial<TFields> extends TFields ? true : false;\n\n// =============================================================================\n// Builder & Factory Types\n// =============================================================================\n\n/**\n * Factories returned by `.withMessage()` — message is sealed by the template.\n * `message` is NOT in the input type. Only has factory functions, no chain methods.\n */\ntype SealedFactories<\n\tTName extends `${string}Error`,\n\tTFields extends JsonObject,\n> = {\n\t[K in TName]: IsInputOptional<TFields> extends true\n\t\t? (input?: TFields) => TaggedError<TName, TFields>\n\t\t: (input: TFields) => TaggedError<TName, TFields>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: IsInputOptional<TFields> extends true\n\t\t? (input?: TFields) => Err<TaggedError<TName, TFields>>\n\t\t: (input: TFields) => Err<TaggedError<TName, TFields>>;\n};\n\n/**\n * Builder object returned by createTaggedError (and .withFields()).\n * Has factory functions (message required) AND chain methods.\n */\ntype ErrorBuilder<\n\tTName extends `${string}Error`,\n\tTFields extends JsonObject = Record<never, never>,\n> = {\n\t[K in TName]: (\n\t\tinput: RequiredMessageInput<TFields>,\n\t) => TaggedError<TName, TFields>;\n} & {\n\t[K in ReplaceErrorWithErr<TName>]: (\n\t\tinput: RequiredMessageInput<TFields>,\n\t) => Err<TaggedError<TName, TFields>>;\n} & {\n\t/** Defines additional fields spread flat on the error object. */\n\twithFields<T extends JsonObject & NoReservedKeys>(): ErrorBuilder<TName, T>;\n\n\t/**\n\t * Seals the message — the template owns it entirely.\n\t * `message` is NOT in the returned factory's input type.\n\t */\n\twithMessage(fn: MessageFn<TFields>): SealedFactories<TName, TFields>;\n};\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Creates a new tagged error type with a fluent builder API.\n *\n * Returns an object with factory functions immediately available (message required),\n * plus `.withFields<T>()` and `.withMessage(fn)` for further configuration.\n *\n * Two mutually exclusive modes:\n * 1. **No `.withMessage()`**: `message` is required at the call site\n * 2. **With `.withMessage()`**: message is sealed — NOT in the input type\n *\n * @example No fields, message required at call site\n * ```ts\n * const { SimpleError, SimpleErr } = createTaggedError('SimpleError');\n * SimpleErr({ message: 'Something went wrong' });\n * ```\n *\n * @example No fields, with sealed message\n * ```ts\n * const { RecorderBusyError } = createTaggedError('RecorderBusyError')\n * .withMessage(() => 'A recording is already in progress');\n * RecorderBusyError(); // message always: 'A recording is already in progress'\n * ```\n *\n * @example With fields, message required\n * ```ts\n * const { FsReadError } = createTaggedError('FsReadError')\n * .withFields<{ path: string }>();\n * FsReadError({ message: 'Failed to read', path: '/etc/config' });\n * ```\n *\n * @example With fields + sealed message (template computes from fields)\n * ```ts\n * const { ResponseError } = createTaggedError('ResponseError')\n * .withFields<{ status: number }>()\n * .withMessage(({ status }) => `HTTP ${status}`);\n * ResponseError({ status: 404 }); // message: \"HTTP 404\"\n * ```\n */\nexport function createTaggedError<TName extends `${string}Error`>(\n\tname: TName,\n): ErrorBuilder<TName> {\n\tconst errName = name.replace(\n\t\t/Error$/,\n\t\t\"Err\",\n\t) as ReplaceErrorWithErr<TName>;\n\n\tconst createBuilder = <\n\t\tTFields extends JsonObject = Record<never, never>,\n\t>(): ErrorBuilder<TName, TFields> => {\n\t\tconst errorConstructor = (\n\t\t\tinput: { message: string } & TFields,\n\t\t) => {\n\t\t\tconst { message, ...fields } = input;\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\tmessage,\n\t\t\t\t...fields,\n\t\t\t} as unknown as TaggedError<TName, TFields>;\n\t\t};\n\n\t\tconst errConstructor = (\n\t\t\tinput: { message: string } & TFields,\n\t\t) => Err(errorConstructor(input));\n\n\t\treturn {\n\t\t\t[name]: errorConstructor,\n\t\t\t[errName]: errConstructor,\n\n\t\t\twithFields<T extends JsonObject & NoReservedKeys>() {\n\t\t\t\treturn createBuilder<T>();\n\t\t\t},\n\n\t\t\twithMessage(\n\t\t\t\tfn: MessageFn<TFields>,\n\t\t\t): SealedFactories<TName, TFields> {\n\t\t\t\tconst sealedErrorConstructor = (input?: TFields) => {\n\t\t\t\t\tconst fields = (input ?? {}) as TFields;\n\t\t\t\t\tconst message = fn(fields);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t...fields,\n\t\t\t\t\t} as unknown as TaggedError<TName, TFields>;\n\t\t\t\t};\n\n\t\t\t\tconst sealedErrConstructor = (input?: TFields) =>\n\t\t\t\t\tErr(sealedErrorConstructor(input));\n\n\t\t\t\treturn {\n\t\t\t\t\t[name]: sealedErrorConstructor,\n\t\t\t\t\t[errName]: sealedErrConstructor,\n\t\t\t\t} as SealedFactories<TName, TFields>;\n\t\t\t},\n\t\t} as ErrorBuilder<TName, TFields>;\n\t};\n\n\treturn createBuilder();\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,oBAAoBA,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8HD,SAAgB,kBACfC,MACsB;CACtB,MAAM,UAAU,KAAK,QACpB,UACA,MACA;CAED,MAAM,gBAAgB,MAEe;EACpC,MAAM,mBAAmB,CACxBC,UACI;GACJ,MAAM,EAAE,QAAS,GAAG,QAAQ,GAAG;AAC/B,UAAO;IACN;IACA;IACA,GAAG;GACH;EACD;EAED,MAAM,iBAAiB,CACtBA,UACI,IAAI,iBAAiB,MAAM,CAAC;AAEjC,SAAO;IACL,OAAO;IACP,UAAU;GAEX,aAAoD;AACnD,WAAO,eAAkB;GACzB;GAED,YACCC,IACkC;IAClC,MAAM,yBAAyB,CAACC,UAAoB;KACnD,MAAM,SAAU,SAAS,CAAE;KAC3B,MAAM,UAAU,GAAG,OAAO;AAC1B,YAAO;MACN;MACA;MACA,GAAG;KACH;IACD;IAED,MAAM,uBAAuB,CAACA,UAC7B,IAAI,uBAAuB,MAAM,CAAC;AAEnC,WAAO;MACL,OAAO;MACP,UAAU;IACX;GACD;EACD;CACD;AAED,QAAO,eAAe;AACtB"}
1
+ {"version":3,"file":"index.js","names":["config: TConfig & ValidatedConfig<TConfig>","result: Record<string, unknown>","error: unknown"],"sources":["../../src/error/defineErrors.ts","../../src/error/extractErrorMessage.ts"],"sourcesContent":["import { Err } from \"../result/result.js\";\nimport type {\n\tDefineErrorsReturn,\n\tErrorsConfig,\n\tValidatedConfig,\n} from \"./types.js\";\n\n/**\n * Defines a set of typed error factories using Rust-style namespaced variants.\n *\n * Each key is a short variant name (the namespace provides context). Every\n * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch\n * handlers. The variant name is stamped as `name` on the error object.\n *\n * @example\n * ```ts\n * const HttpError = defineErrors({\n * Connection: ({ cause }: { cause: string }) => ({\n * message: `Failed to connect: ${cause}`,\n * cause,\n * }),\n * Parse: ({ cause }: { cause: string }) => ({\n * message: `Failed to parse: ${cause}`,\n * cause,\n * }),\n * });\n *\n * type HttpError = InferErrors<typeof HttpError>;\n *\n * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>\n * ```\n */\nexport function defineErrors<const TConfig extends ErrorsConfig>(\n\tconfig: TConfig & ValidatedConfig<TConfig>,\n): DefineErrorsReturn<TConfig> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [name, ctor] of Object.entries(config)) {\n\t\tresult[name] = (...args: unknown[]) => {\n\t\t\tconst body = (ctor as (...a: unknown[]) => Record<string, unknown>)(\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn Err(Object.freeze({ ...body, name }));\n\t\t};\n\t}\n\n\treturn result as DefineErrorsReturn<TConfig>;\n}\n","/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,aACfA,QAC8B;CAC9B,MAAMC,SAAkC,CAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,KAAK,IAAI,OAAO,QAAQ,OAAO,CAChD,QAAO,QAAQ,CAAC,GAAG,SAAoB;EACtC,MAAM,OAAO,AAAC,KACb,GAAG,KACH;AACD,SAAO,IAAI,OAAO,OAAO;GAAE,GAAG;GAAM;EAAM,EAAC,CAAC;CAC5C;AAGF,QAAO;AACP;;;;;;;;;;ACzCD,SAAgB,oBAAoBC,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wellcrafted",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Delightful TypeScript patterns for elegant, type-safe applications",
5
5
  "type": "module",
6
6
  "files": [