wellcrafted 0.29.0 → 0.31.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
@@ -46,8 +46,8 @@ function divide(a: number, b: number): Result<number, string> {
46
46
  ### 🏷️ Brand Types
47
47
  Create distinct types from primitives
48
48
  ```typescript
49
- type UserId = Brand<string, "UserId">;
50
- type OrderId = Brand<string, "OrderId">;
49
+ type UserId = string & Brand<"UserId">;
50
+ type OrderId = string & Brand<"OrderId">;
51
51
 
52
52
  // TypeScript prevents mixing them up!
53
53
  function getUser(id: UserId) { /* ... */ }
@@ -84,11 +84,13 @@ const userQuery = defineQuery({
84
84
  });
85
85
 
86
86
  // Use reactively in components with automatic state management
87
- const query = createQuery(userQuery.options());
87
+ // Svelte 5 requires accessor function; React uses options directly
88
+ const query = createQuery(() => userQuery.options); // Svelte
88
89
  // query.data, query.error, query.isPending all managed automatically
89
90
 
90
91
  // Or use imperatively for direct execution (perfect for event handlers)
91
92
  const { data, error } = await userQuery.fetch();
93
+ // Or shorthand: await userQuery() (same as .ensure())
92
94
  if (error) {
93
95
  showErrorToast(error.message);
94
96
  return;
@@ -336,12 +338,13 @@ export const recorder = {
336
338
  };
337
339
 
338
340
  // 3. Component Usage - Choose reactive or imperative based on needs
339
- // Reactive: Automatic state management
340
- const recorderState = createQuery(recorder.getRecorderState.options());
341
+ // Reactive: Automatic state management (Svelte 5 requires accessor function)
342
+ const recorderState = createQuery(() => recorder.getRecorderState.options);
341
343
 
342
344
  // Imperative: Direct execution for event handlers
343
345
  async function handleStartRecording() {
344
346
  const { error } = await recorder.startRecording.execute();
347
+ // Or shorthand: await recorder.startRecording()
345
348
  if (error) {
346
349
  showToast(error.title, { description: error.description });
347
350
  }
@@ -623,8 +626,8 @@ For comprehensive examples, service layer patterns, framework integrations, and
623
626
 
624
627
  ### Query Functions
625
628
  - **`createQueryFactories(client)`** - Create query/mutation factories for TanStack Query
626
- - **`defineQuery(options)`** - Define a query with dual interface (`.options()` + `.fetch()`)
627
- - **`defineMutation(options)`** - Define a mutation with dual interface (`.options()` + `.execute()`)
629
+ - **`defineQuery(options)`** - Define a query with dual interface (`.options` + callable/`.ensure()`/`.fetch()`)
630
+ - **`defineMutation(options)`** - Define a mutation with dual interface (`.options` + callable/`.execute()`)
628
631
 
629
632
  ### Error Functions
630
633
  - **`createTaggedError(name)`** - Creates error factory functions with fluent API
@@ -646,6 +649,30 @@ For comprehensive examples, service layer patterns, framework integrations, and
646
649
  - **`UnwrapOk<R>`** - Extract success value type from Result
647
650
  - **`UnwrapErr<R>`** - Extract error value type from Result
648
651
 
652
+ ## Development Setup
653
+
654
+ ### Peer Directory Requirement
655
+
656
+ Wellcrafted shares AI agent skills (`.agents/skills/`, `.claude/skills/`) with the [Epicenter](https://github.com/EpicenterHQ/epicenter) repo via relative symlinks. Epicenter is the source of truth for skill definitions — they're authored and maintained there, and wellcrafted consumes them to stay in sync.
657
+
658
+ **Both repos must be sibling directories under the same parent:**
659
+
660
+ ```
661
+ Code/
662
+ ├── epicenter/ # Source of truth for skills
663
+ │ └── .agents/skills/
664
+ └── wellcrafted/ # Symlinks to epicenter
665
+ ├── .agents/skills/<name> → ../../../epicenter/.agents/skills/<name>
666
+ └── .claude/skills/<name> → ../../.agents/skills/<name>
667
+ ```
668
+
669
+ If symlinks appear broken after cloning, ensure epicenter is cloned alongside wellcrafted:
670
+
671
+ ```bash
672
+ cd "$(git rev-parse --show-toplevel)/.."
673
+ git clone https://github.com/EpicenterHQ/epicenter.git
674
+ ```
675
+
649
676
  ## License
650
677
 
651
678
  MIT
package/dist/brand.d.ts CHANGED
@@ -6,35 +6,103 @@ declare const brand: unique symbol;
6
6
  /**
7
7
  * Creates a brand type for nominal typing in TypeScript.
8
8
  *
9
- * Branded types help create distinct types from primitive types, preventing
9
+ * Branded types create distinct types from primitive types, preventing
10
10
  * accidental mixing of values that should be semantically different.
11
11
  *
12
+ * ## Why wellcrafted Brand?
13
+ *
14
+ * **Composable** — Brands stack to create hierarchical type relationships.
15
+ * Child types are assignable to parent types, but not vice versa. Multiple
16
+ * inheritance works via intersection. This is possible because of the nested
17
+ * object structure `{ [brand]: { [K in T]: true } }` — when brands intersect,
18
+ * their properties merge instead of conflicting.
19
+ *
20
+ * **Framework-agnostic** — Unlike Zod's `.brand()`, ArkType's `.brand()`, or
21
+ * Valibot's `v.brand()` — which each produce library-specific branded types —
22
+ * wellcrafted's `Brand<T>` is a pure type utility with zero runtime footprint.
23
+ * Define your branded type once, then plug it into any runtime validator.
24
+ * Switch validation libraries without touching your type definitions.
25
+ *
26
+ * **Dual-declaration friendly** — TypeScript has two parallel namespaces: types
27
+ * and values. You can use the same PascalCase name for both the branded type and
28
+ * its runtime validator. JSDoc written on the type shows up everywhere the name
29
+ * appears — function signatures, schema definitions, and imports — giving you a
30
+ * single hover experience across your entire codebase.
31
+ *
12
32
  * @template T - A string literal type that serves as the brand identifier
13
33
  *
14
- * @example
34
+ * @example Single brand — preventing ID mix-ups
15
35
  * ```typescript
16
- * // Create a branded ID type
17
- * type ID = string & Brand<"ID">;
36
+ * type UserId = string & Brand<"UserId">;
37
+ * type OrderId = string & Brand<"OrderId">;
18
38
  *
19
- * // Create functions that work with branded types
20
- * function createID(value: string): ID {
21
- * return value as ID;
22
- * }
39
+ * const userId: UserId = "user-123" as UserId;
40
+ * const orderId: OrderId = userId; // ❌ Type error
41
+ * ```
23
42
  *
24
- * function processID(id: ID): void {
25
- * console.log(`Processing ID: ${id}`);
26
- * }
43
+ * @example Hierarchical brands child assignable to parent
44
+ * ```typescript
45
+ * type AbsolutePath = string & Brand<"AbsolutePath">;
46
+ * type ConfigPath = AbsolutePath & Brand<"ConfigPath">;
27
47
  *
28
- * // Usage
29
- * const userID = createID("user-123");
30
- * const productID = createID("product-456");
48
+ * declare const configPath: ConfigPath;
49
+ * const abs: AbsolutePath = configPath; // ✅ Child assignable to parent
50
+ * const cfg: ConfigPath = abs; // ❌ Parent not assignable to child
51
+ * ```
31
52
  *
32
- * processID(userID); // ✅ Works
33
- * processID("raw-string"); // ❌ TypeScript error - string is not assignable to ID
53
+ * @example Multiple inheritance
54
+ * ```typescript
55
+ * type Serializable = unknown & Brand<"Serializable">;
56
+ * type Validated = unknown & Brand<"Validated">;
57
+ * type SafeData = Serializable & Validated & Brand<"SafeData">;
58
+ *
59
+ * // SafeData is assignable to both Serializable and Validated
34
60
  * ```
61
+ *
62
+ * @example Dual-declaration — same name for type and runtime validator
63
+ *
64
+ * TypeScript resolves the name from context: type position = branded type,
65
+ * value position = runtime validator. One name, zero ambiguity.
66
+ *
67
+ * ```typescript
68
+ * // Type-only brand (no runtime validation needed)
69
+ * type Guid = string & Brand<"Guid">;
70
+ *
71
+ * // Dual-declaration: type + runtime validator share the same name.
72
+ * // Hover over FileId anywhere — IDE shows the same JSDoc whether
73
+ * // it appears as a type annotation or a runtime schema.
74
+ * type FileId = Guid & Brand<"FileId">;
75
+ * const FileId = type("string").pipe((s): FileId => s as FileId);
76
+ * ```
77
+ *
78
+ * @example Framework-agnostic — same Brand, any validator
79
+ *
80
+ * Define the type once with `Brand<T>`, then create runtime validators
81
+ * with whichever library you prefer. Your branded types are never locked
82
+ * to a specific validation library.
83
+ *
84
+ * ```typescript
85
+ * import { type } from "arktype";
86
+ * import { z } from "zod";
87
+ * import * as v from "valibot";
88
+ *
89
+ * // Define the type ONCE — it's just a type, no library dependency
90
+ * type FileId = string & Brand<"FileId">;
91
+ *
92
+ * // ArkType
93
+ * const FileId = type("string").pipe((s): FileId => s as FileId);
94
+ *
95
+ * // Zod
96
+ * const FileId = z.string().transform((s): FileId => s as FileId);
97
+ *
98
+ * // Valibot
99
+ * const FileId = v.pipe(v.string(), v.transform((s): FileId => s as FileId));
100
+ * ```
101
+ *
102
+ * @see {@link https://wellcrafted.dev/integrations/validation-libraries | Using Brand with Validation Libraries}
35
103
  */
36
104
  type Brand<T extends string> = {
37
- [brand]: T;
105
+ [brand]: { [K in T]: true };
38
106
  };
39
107
  //#endregion
40
108
  export { Brand };
@@ -1 +1 @@
1
- {"version":3,"file":"brand.d.ts","names":[],"sources":["../src/brand.ts"],"sourcesContent":[],"mappings":";;;AAmCA;cAhCc,KAgCG,EAAA,OAAA,MAAA;;;AAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAAlC;GAA6B,KAAA,GAAQ"}
1
+ {"version":3,"file":"brand.d.ts","names":[],"sources":["../src/brand.ts"],"sourcesContent":[],"mappings":";;;AAGkC;cAApB,KAoGJ,EAAA,OAAA,MAAA;;;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KADF;GACH,KAAA,WAAgB"}
@@ -1,6 +1,18 @@
1
1
  import { Err } from "../result-DolxQXIZ.js";
2
2
 
3
3
  //#region src/error/types.d.ts
4
+
5
+ /**
6
+ * JSON-serializable value types for error context.
7
+ * Ensures all error data can be safely serialized via JSON.stringify.
8
+ */
9
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
10
+ [key: string]: JsonValue;
11
+ };
12
+ /**
13
+ * JSON-serializable object type for error context.
14
+ */
15
+ type JsonObject = Record<string, JsonValue>;
4
16
  /**
5
17
  * Base type for any tagged error, used as a constraint for cause parameters.
6
18
  */
@@ -13,10 +25,8 @@ type AnyTaggedError = {
13
25
  * - When TContext is undefined (default): NO context property (explicit opt-in)
14
26
  * - When TContext includes undefined (e.g., `{ foo: string } | undefined`): context is OPTIONAL but typed
15
27
  * - When TContext is a specific type without undefined: context is REQUIRED with that exact type
16
- *
17
- * This follows Rust's explicit error philosophy: context must be explicitly added via .withContext<T>().
18
28
  */
19
- type WithContext<TContext> = [TContext] extends [undefined] ? {} : [undefined] extends [TContext] ? {
29
+ type WithContext<TContext> = [TContext] extends [undefined] ? Record<never, never> : [undefined] extends [TContext] ? {
20
30
  context?: Exclude<TContext, undefined>;
21
31
  } : {
22
32
  context: TContext;
@@ -26,80 +36,21 @@ type WithContext<TContext> = [TContext] extends [undefined] ? {} : [undefined] e
26
36
  * - When TCause is undefined (default): NO cause property (explicit opt-in)
27
37
  * - When TCause includes undefined (e.g., `NetworkError | undefined`): cause is OPTIONAL, constrained
28
38
  * - When TCause is a specific type without undefined: cause is REQUIRED
29
- *
30
- * This follows Rust's explicit error philosophy: cause must be explicitly added via .withCause<T>().
31
- * Using brackets to prevent distributive conditional behavior with union types.
32
39
  */
33
- type WithCause<TCause> = [TCause] extends [undefined] ? {} : [undefined] extends [TCause] ? {
40
+ type WithCause<TCause> = [TCause] extends [undefined] ? Record<never, never> : [undefined] extends [TCause] ? {
34
41
  cause?: Exclude<TCause, undefined>;
35
42
  } : {
36
43
  cause: TCause;
37
44
  };
38
45
  /**
39
- * Creates a tagged error type for type-safe error handling.
46
+ * A tagged error type for type-safe error handling.
40
47
  * Uses the `name` property as a discriminator for tagged unions.
41
48
  *
42
- * **Explicit Opt-In Philosophy (Rust-inspired):**
43
- * By default, errors only have `name` and `message`. Context and cause must be
44
- * explicitly added via type parameters. This follows Rust's thiserror pattern
45
- * where error properties are intentional architectural decisions.
46
- *
47
- * **Type Parameter Behavior:**
48
- * - When `TContext` is `undefined` (default): NO context property
49
- * - When `TContext` is `{ ... } | undefined`: `context` is OPTIONAL but typed
50
- * - When `TContext` is specified without undefined: `context` is REQUIRED
51
- * - When `TCause` is `undefined` (default): NO cause property
52
- * - When `TCause` is `{ ... } | undefined`: `cause` is OPTIONAL but typed
53
- * - When `TCause` is specified without undefined: `cause` is REQUIRED
54
- *
55
49
  * @template TName - The error name (discriminator for tagged unions)
56
50
  * @template TContext - Additional context data for the error (default: undefined = no context)
57
51
  * @template TCause - The type of error that caused this error (default: undefined = no cause)
58
- *
59
- * @example
60
- * ```ts
61
- * // Minimal error (no context, no cause)
62
- * type ValidationError = TaggedError<"ValidationError">;
63
- * const validationError: ValidationError = {
64
- * name: "ValidationError",
65
- * message: "Input is required"
66
- * };
67
- * // validationError only has name and message
68
- *
69
- * // Error with required context
70
- * type NetworkError = TaggedError<"NetworkError", { host: string; port: number }>;
71
- * const networkError: NetworkError = {
72
- * name: "NetworkError",
73
- * message: "Socket timeout",
74
- * context: { host: "db.example.com", port: 5432 } // Required!
75
- * };
76
- *
77
- * // Error with OPTIONAL but TYPED context (union with undefined)
78
- * type LogError = TaggedError<"LogError", { file: string; line: number } | undefined>;
79
- * const logError1: LogError = { name: "LogError", message: "Parse failed" }; // OK
80
- * const logError2: LogError = { name: "LogError", message: "Parse failed", context: { file: "app.ts", line: 42 } }; // OK
81
- *
82
- * // Error with required context and optional cause
83
- * type DatabaseError = TaggedError<"DatabaseError", { operation: string }, NetworkError | undefined>;
84
- * const dbError: DatabaseError = {
85
- * name: "DatabaseError",
86
- * message: "Failed to connect to database",
87
- * context: { operation: "connect" }, // Required!
88
- * cause: networkError // Optional, but must be NetworkError if provided
89
- * };
90
- *
91
- * // Discriminated unions still work
92
- * function handleError(error: ValidationError | NetworkError) {
93
- * switch (error.name) {
94
- * case "ValidationError": // TypeScript knows this is ValidationError
95
- * break;
96
- * case "NetworkError": // TypeScript knows this is NetworkError
97
- * break;
98
- * }
99
- * }
100
- * ```
101
52
  */
102
- type TaggedError<TName extends string = string, TContext extends Record<string, unknown> | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = Readonly<{
53
+ type TaggedError<TName extends string = string, TContext extends JsonObject | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = Readonly<{
103
54
  name: TName;
104
55
  message: string;
105
56
  } & WithContext<TContext> & WithCause<TCause>>;
@@ -108,222 +59,102 @@ type TaggedError<TName extends string = string, TContext extends Record<string,
108
59
  /**
109
60
  * Extracts a readable error message from an unknown error value
110
61
  *
111
- * This utility is commonly used in mapErr functions when converting
112
- * unknown errors to typed error objects in the Result system.
113
- *
114
62
  * @param error - The unknown error to extract a message from
115
63
  * @returns A string representation of the error
116
- *
117
- * @example
118
- * ```ts
119
- * // With native Error
120
- * const error = new Error("Something went wrong");
121
- * const message = extractErrorMessage(error); // "Something went wrong"
122
- *
123
- * // With string error
124
- * const stringError = "String error";
125
- * const message2 = extractErrorMessage(stringError); // "String error"
126
- *
127
- * // With object error
128
- * const unknownError = { code: 500, details: "Server error" };
129
- * const message3 = extractErrorMessage(unknownError); // '{"code":500,"details":"Server error"}'
130
- *
131
- * // Used in mapErr function
132
- * const result = await tryAsync({
133
- * try: () => riskyOperation(),
134
- * mapErr: (error) => Err({
135
- * name: "NetworkError",
136
- * message: extractErrorMessage(error),
137
- * context: { operation: "riskyOperation" },
138
- * cause: error,
139
- * }),
140
- * });
141
- * ```
142
64
  */
143
65
  declare function extractErrorMessage(error: unknown): string;
144
66
  /**
145
67
  * Replaces the "Error" suffix with "Err" suffix in error type names.
146
- *
147
- * @template T - An error type name that must end with "Error"
148
- * @returns The type name with "Error" replaced by "Err"
149
- *
150
- * @example
151
- * ```ts
152
- * type NetworkErr = ReplaceErrorWithErr<"NetworkError">; // "NetworkErr"
153
- * type ValidationErr = ReplaceErrorWithErr<"ValidationError">; // "ValidationErr"
154
- * ```
155
68
  */
156
69
  type ReplaceErrorWithErr<T extends `${string}Error`> = T extends `${infer TBase}Error` ? `${TBase}Err` : never;
70
+ /**
71
+ * Input provided to the message template function.
72
+ * Contains everything the error will have except `message` (since that's what it computes).
73
+ */
74
+ type MessageInput<TName extends string, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = {
75
+ name: TName;
76
+ } & ([TContext] extends [undefined] ? Record<never, never> : {
77
+ context: Exclude<TContext, undefined>;
78
+ }) & ([TCause] extends [undefined] ? Record<never, never> : {
79
+ cause: Exclude<TCause, undefined>;
80
+ });
81
+ /**
82
+ * Message template function type.
83
+ */
84
+ type MessageFn<TName extends string, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = (input: MessageInput<TName, TContext, TCause>) => string;
157
85
  /**
158
86
  * Helper type that determines optionality based on whether T includes undefined.
159
- * - If T includes undefined → property is optional
160
- * - If T does not include undefined → property is required
161
87
  */
162
88
  type OptionalIfUndefined<T, TKey extends string> = undefined extends T ? { [K in TKey]?: Exclude<T, undefined> } : { [K in TKey]: T };
163
89
  /**
164
- * Input type for error constructors with fluent API context/cause handling.
165
- *
166
- * Follows explicit opt-in philosophy:
167
- * - When TContext/TCause is undefined: property doesn't exist
168
- * - When TContext/TCause includes undefined: property is optional but typed
169
- * - When TContext/TCause is a specific type: property is required
90
+ * Input type for error factory functions.
91
+ * `message` is optional (computed from template when omitted).
92
+ * `context` and `cause` follow the same optionality rules as before.
170
93
  */
171
- type ErrorInput<TContext extends Record<string, unknown> | undefined, TCause extends AnyTaggedError | undefined> = {
172
- message: string;
173
- } & (TContext extends undefined ? {} : OptionalIfUndefined<TContext, "context">) & (TCause extends undefined ? {} : OptionalIfUndefined<TCause, "cause">);
94
+ type ErrorCallInput<TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = {
95
+ message?: string;
96
+ } & (TContext extends undefined ? Record<never, never> : OptionalIfUndefined<TContext, "context">) & (TCause extends undefined ? Record<never, never> : OptionalIfUndefined<TCause, "cause">);
174
97
  /**
175
- * The factories object returned by createTaggedError and its builder methods.
98
+ * The final factories object returned by `.withMessage()`.
99
+ * Has factory functions, NO chain methods.
176
100
  */
177
- type ErrorFactories<TName extends `${string}Error`, TContext extends Record<string, unknown> | undefined, TCause extends AnyTaggedError | undefined> = { [K in TName]: (input: ErrorInput<TContext, TCause>) => TaggedError<TName, TContext, TCause> } & { [K in ReplaceErrorWithErr<TName>]: (input: ErrorInput<TContext, TCause>) => Err<TaggedError<TName, TContext, TCause>> };
101
+ type FinalFactories<TName extends `${string}Error`, TContext extends JsonObject | undefined, TCause extends AnyTaggedError | undefined> = { [K in TName]: (input: ErrorCallInput<TContext, TCause>) => TaggedError<TName, TContext, TCause> } & { [K in ReplaceErrorWithErr<TName>]: (input: ErrorCallInput<TContext, TCause>) => Err<TaggedError<TName, TContext, TCause>> };
178
102
  /**
179
103
  * Builder interface for the fluent createTaggedError API.
180
- * Provides chaining methods and the error factories.
104
+ * Has chain methods only, NO factory functions.
105
+ * Must call `.withMessage(fn)` to get factories.
181
106
  */
182
- type ErrorBuilder<TName extends `${string}Error`, TContext extends Record<string, unknown> | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = ErrorFactories<TName, TContext, TCause> & {
107
+ type ErrorBuilder<TName extends `${string}Error`, TContext extends JsonObject | undefined = undefined, TCause extends AnyTaggedError | undefined = undefined> = {
183
108
  /**
184
109
  * Constrains the context type for this error.
185
- *
186
- * Optionality is determined by whether the type includes `undefined`:
187
- * - `withContext<T>()` where T doesn't include undefined → context is **required**
188
- * - `withContext<T | undefined>()` → context is **optional** but typed when provided
189
- *
190
- * @typeParam T - The shape of the context object. Include `| undefined` to make optional.
191
- *
192
- * @example Required context
193
- * ```ts
194
- * const { FileError } = createTaggedError('FileError')
195
- * .withContext<{ path: string }>()
196
- *
197
- * FileError({ message: 'Not found', context: { path: '/etc/config' } }) // OK
198
- * FileError({ message: 'Not found' }) // Type error: context required
199
- * ```
200
- *
201
- * @example Optional but typed context
202
- * ```ts
203
- * const { LogError } = createTaggedError('LogError')
204
- * .withContext<{ file: string; line: number } | undefined>()
205
- *
206
- * LogError({ message: 'Parse error' }) // OK
207
- * LogError({ message: 'Parse error', context: { file: 'app.ts', line: 42 } }) // OK
208
- * ```
209
- *
210
- * @example Default (no generic): permissive optional context
211
- * ```ts
212
- * const { FlexError } = createTaggedError('FlexError')
213
- * .withContext() // Defaults to Record<string, unknown> | undefined
214
- *
215
- * FlexError({ message: 'Error' }) // OK - context is optional
216
- * FlexError({ message: 'Error', context: { anything: 'works' } }) // OK
217
- * ```
110
+ * `.withContext<T>()` where T doesn't include undefined → context is **required**
111
+ * `.withContext<T | undefined>()` context is **optional** but typed when provided
218
112
  */
219
- withContext<T extends Record<string, unknown> | undefined = Record<string, unknown> | undefined>(): ErrorBuilder<TName, T, TCause>;
113
+ withContext<T extends JsonObject | undefined = JsonObject | undefined>(): ErrorBuilder<TName, T, TCause>;
220
114
  /**
221
115
  * Constrains the cause type for this error.
222
- *
223
- * Optionality is determined by whether the type includes `undefined`:
224
- * - `withCause<T>()` where T doesn't include undefined → cause is **required**
225
- * - `withCause<T | undefined>()` → cause is **optional** but typed when provided
226
- *
227
- * Since cause is typically optional, include `| undefined` in most cases.
228
- *
229
- * @typeParam T - The allowed cause type(s). Include `| undefined` to make optional.
230
- *
231
- * @example Optional typed cause (common)
232
- * ```ts
233
- * const { ServiceError } = createTaggedError('ServiceError')
234
- * .withCause<DbError | CacheError | undefined>()
235
- *
236
- * ServiceError({ message: 'Failed' }) // OK
237
- * ServiceError({ message: 'Failed', cause: dbError }) // OK
238
- * ```
239
- *
240
- * @example Required cause (for wrapper errors)
241
- * ```ts
242
- * const { UnhandledError } = createTaggedError('UnhandledError')
243
- * .withCause<AnyTaggedError>()
244
- *
245
- * UnhandledError({ message: 'Unexpected', cause: originalError }) // OK
246
- * UnhandledError({ message: 'Unexpected' }) // Type error: cause required
247
- * ```
248
- *
249
- * @example Default (no generic): permissive optional cause
250
- * ```ts
251
- * const { FlexError } = createTaggedError('FlexError')
252
- * .withCause() // Defaults to AnyTaggedError | undefined
253
- *
254
- * FlexError({ message: 'Error' }) // OK - cause is optional
255
- * FlexError({ message: 'Error', cause: anyTaggedError }) // OK
256
- * ```
116
+ * `.withCause<T>()` where T doesn't include undefined → cause is **required**
117
+ * `.withCause<T | undefined>()` cause is **optional** but typed when provided
257
118
  */
258
119
  withCause<T extends AnyTaggedError | undefined = AnyTaggedError | undefined>(): ErrorBuilder<TName, TContext, T>;
120
+ /**
121
+ * Terminal method that defines how the error message is computed from its data.
122
+ * Returns the factory functions — this is the only way to get them.
123
+ *
124
+ * @param fn - Template function that receives `{ name, context?, cause? }` and returns a message string
125
+ */
126
+ withMessage(fn: MessageFn<TName, TContext, TCause>): FinalFactories<TName, TContext, TCause>;
259
127
  };
260
128
  /**
261
129
  * Creates a new tagged error type with a fluent builder API.
262
130
  *
263
- * Returns an object containing:
264
- * - `{Name}Error`: Factory function that creates plain TaggedError objects
265
- * - `{Name}Err`: Factory function that creates Err-wrapped TaggedError objects
266
- * - `withContext<T>()`: Chain method to add context type
267
- * - `withCause<T>()`: Chain method to add cause type
268
- *
269
- * **Explicit Opt-In (Rust-inspired):**
270
- * By default, errors only have `{ name, message }`. Context and cause must be
271
- * explicitly added via `.withContext<T>()` and `.withCause<T>()`. This follows
272
- * Rust's thiserror pattern where error properties are intentional decisions.
131
+ * The builder provides `.withContext<T>()`, `.withCause<T>()`, and `.withMessage(fn)`.
132
+ * `.withMessage(fn)` is **required** and **terminal** it returns the factory functions.
273
133
  *
274
- * **Optionality via type unions:**
275
- * Both `withContext` and `withCause` determine optionality based on whether
276
- * the type includes `undefined`:
277
- * - `T` without undefined → property is required
278
- * - `T | undefined` → property is optional but typed when provided
279
- *
280
- * @template TName - The name of the error type (must end with "Error")
281
- * @param name - The name of the error type
282
- *
283
- * @example Minimal error (no context, no cause)
134
+ * @example Simple error
284
135
  * ```ts
285
- * const { NetworkError, NetworkErr } = createTaggedError('NetworkError')
286
- *
287
- * NetworkError({ message: 'Connection failed' })
288
- * // Error only has { name: 'NetworkError', message: 'Connection failed' }
136
+ * const { RecorderBusyError, RecorderBusyErr } = createTaggedError('RecorderBusyError')
137
+ * .withMessage(() => 'A recording is already in progress');
289
138
  * ```
290
139
  *
291
- * @example Required context
140
+ * @example Error with context
292
141
  * ```ts
293
- * const { ApiError, ApiErr } = createTaggedError('ApiError')
294
- * .withContext<{ endpoint: string; status: number }>()
295
- *
296
- * ApiError({ message: 'Failed', context: { endpoint: '/users', status: 500 } })
297
- * // ApiError({ message: 'Failed' }) // Type error: context required
298
- * ```
299
- *
300
- * @example Optional typed cause
301
- * ```ts
302
- * const { ServiceError } = createTaggedError('ServiceError')
303
- * .withCause<DbError | CacheError | undefined>()
304
- *
305
- * ServiceError({ message: 'Failed' }) // OK
306
- * ServiceError({ message: 'Failed', cause: dbError }) // OK, typed
307
- * ```
308
- *
309
- * @example Full example with both
310
- * ```ts
311
- * const { UserServiceError } = createTaggedError('UserServiceError')
312
- * .withContext<{ userId: string }>()
313
- * .withCause<RepoError | undefined>()
314
- *
315
- * // Type extraction works
316
- * type UserServiceError = ReturnType<typeof UserServiceError>
142
+ * const { DbNotFoundError, DbNotFoundErr } = createTaggedError('DbNotFoundError')
143
+ * .withContext<{ table: string; id: string }>()
144
+ * .withMessage(({ context }) => `${context.table} '${context.id}' not found`);
317
145
  * ```
318
146
  *
319
- * @example Permissive mode (if you want the old behavior)
147
+ * @example Error with context and cause
320
148
  * ```ts
321
- * const { FlexibleError } = createTaggedError('FlexibleError')
322
- * .withContext<Record<string, unknown> | undefined>()
323
- * .withCause<AnyTaggedError | undefined>()
149
+ * const { ServiceError, ServiceErr } = createTaggedError('ServiceError')
150
+ * .withContext<{ operation: string }>()
151
+ * .withCause<DbServiceError>()
152
+ * .withMessage(({ context, cause }) =>
153
+ * `Operation '${context.operation}' failed: ${cause.message}`
154
+ * );
324
155
  * ```
325
156
  */
326
157
  declare function createTaggedError<TName extends `${string}Error`>(name: TName): ErrorBuilder<TName>;
327
158
  //#endregion
328
- export { AnyTaggedError, TaggedError, createTaggedError, extractErrorMessage };
159
+ export { AnyTaggedError, JsonObject, JsonValue, TaggedError, createTaggedError, extractErrorMessage };
329
160
  //# 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/utils.ts"],"sourcesContent":[],"mappings":"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"}