Releases: wellcrafted-dev/wellcrafted
v0.34.0
v0.33.0
Minor Changes
-
1c6e597: BREAKING:
defineErrorsv2 — Rust-style namespaced errors with Err-by-default- Factories now return
Err<...>directly (no dualFooError/FooErrfactories) - Keys are short variant names (
Connection,Parse) instead ofConnectionError InferError<T>takes a single factory (typeof HttpError.Connection)InferErrors<T>replacesInferErrorUnion<T>for union extractionValidatedConfigprovides descriptive error when reservednamekey is used
- Factories now return
v0.32.0
Minor Changes
- 4539eb2: Redesign
createTaggedErrorbuilder: flat.withFields()API replaces nested.withContext()/.withCause(),.withMessage()is optional and seals the message (not in factory input type),messagerequired at call site when.withMessage()is absent. Removescontextnesting,causeas first-class field, andreasonconvention.
v0.31.0
Minor Changes
-
69bf59e: Breaking:
createTaggedErrornow requires.withMessage(fn)as a mandatory terminal builder step before factories are available.What Changed
.withMessage(fn)is now required: Factories (XxxError,XxxErr) are only returned after calling.withMessage(). Previously you could destructure factories after just.withContext()or.withCause().- Message is auto-computed: The
messagefield is no longer a required input at the factory call site — it is derived from the template function passed to.withMessage(). You can still passmessage:as an optional override. - Context type tightened:
contextconstraint changed fromRecord<string, unknown>toJsonObject(values must be JSON-serializable). - Strict builder/factory separation:
ErrorBuilderonly has chain methods (.withContext(),.withCause(),.withMessage()).FinalFactoriesonly has factory functions — no more mixing.
Migration
Before:
const { UserError, UserErr } = createTaggedError("UserError").withContext<{ userId: string; }>(); // ❌ Error: factories not available without .withMessage()
After:
const { UserError, UserErr } = createTaggedError("UserError") .withContext<{ userId: string }>() .withMessage(({ context }) => `User ${context.userId} failed`); // ✅ Factories now available
v0.29.1
Patch Changes
-
7df7aee: Fix Brand type to support hierarchical/stacked brands
Previously, stacking brands resulted in
neverbecause intersecting{ [brand]: 'A' }with{ [brand]: 'B' }collapsed the property tonever.Now brands use a nested object structure
{ [brand]: { [K in T]: true } }(following Effect-TS pattern), allowing brand stacking to work correctly:type AbsolutePath = string & Brand<"AbsolutePath">; type ProjectDir = AbsolutePath & Brand<"ProjectDir">; const projectDir = "/home/project" as ProjectDir; const abs: AbsolutePath = projectDir; // Works - child assignable to parent
v0.29.0
Minor Changes
-
1237ce4: BREAKING: Rename
resultQueryFntoqueryFnandresultMutationFntomutationFnThe
resultprefix was redundant since the TypeScript signature already encodes that these functions return Result types. This removes unnecessary Hungarian notation from the API.Migration:
// Before defineQuery({ queryKey: ["users"], resultQueryFn: () => getUsers(), }); defineMutation({ mutationKey: ["users", "create"], resultMutationFn: (input) => createUser(input), }); // After defineQuery({ queryKey: ["users"], queryFn: () => getUsers(), }); defineMutation({ mutationKey: ["users", "create"], mutationFn: (input) => createUser(input), });
v0.28.0
Minor Changes
-
7c41baf: feat(error): explicit opt-in for context and cause properties
Following Rust's thiserror pattern,
createTaggedErrornow uses explicit opt-in forcontextandcauseproperties. By default, errors only have{ name, message }.Breaking Change: Previously, all errors had optional
contextandcauseproperties by default. Now you must explicitly chain.withContext<T>()and/or.withCause<T>()to add these properties.Before (old behavior):
const { ApiError } = createTaggedError("ApiError"); // ApiError had: { name, message, context?: Record<string, unknown>, cause?: AnyTaggedError }
After (new behavior):
// Minimal error - only name and message const { ApiError } = createTaggedError("ApiError"); // ApiError has: { name, message } // With required context const { ApiError } = createTaggedError("ApiError").withContext<{ endpoint: string; }>(); // ApiError has: { name, message, context: { endpoint: string } } // With optional typed cause const { ApiError } = createTaggedError("ApiError").withCause< NetworkError | undefined >(); // ApiError has: { name, message, cause?: NetworkError }
Migration: To replicate the old permissive behavior, either specify the types explicitly:
const { FlexibleError } = createTaggedError("FlexibleError") .withContext<Record<string, unknown> | undefined>() .withCause<AnyTaggedError | undefined>();
Or use the new defaults by calling without generics:
const { FlexibleError } = createTaggedError("FlexibleError") .withContext() // Defaults to Record<string, unknown> | undefined .withCause(); // Defaults to AnyTaggedError | undefined
v0.27.0
Minor Changes
-
b917060: Replace
defineErrorwith fluentcreateTaggedErrorAPIThe
createTaggedErrorfunction now uses a fluent builder pattern for type constraints:// Simple usage (flexible mode) const { NetworkError, NetworkErr } = createTaggedError("NetworkError"); // Required context const { ApiError } = createTaggedError("ApiError").withContext<{ endpoint: string; status: number; }>(); // Optional typed context const { LogError } = createTaggedError("LogError").withContext< { file: string; line: number } | undefined >(); // Chaining both context and cause const { RepoError } = createTaggedError("RepoError") .withContext<{ entity: string }>() .withCause<DbError | undefined>();
Breaking changes:
defineErrorhas been removed (usecreateTaggedErrorinstead)- The old
createTaggedErrorgeneric overloads are removed in favor of the fluent API
v0.26.0
Minor Changes
-
5f0e7af: Make query and mutation definitions directly callable
Query and mutation definitions from
defineQueryanddefineMutationare now directly callable functions:// Queries - callable defaults to ensure() behavior const { data, error } = await userQuery(); // same as userQuery.ensure() // Mutations - callable defaults to execute() behavior const { data, error } = await createUser({ name: "John" }); // same as createUser.execute()
The explicit methods (
.ensure(),.fetch(),.execute()) remain available for when you need different behavior or prefer explicit code.Breaking change:
.optionsis now a property instead of a function. UpdatecreateQuery(query.options())tocreateQuery(query.options).
v0.25.1
Patch Changes
-
5d72453: feat(error): support optional typed context via union with undefined
You can now specify context that is optional but still type-checked when provided by using a union with
undefined:type LogContext = { file: string; line: number } | undefined; const { LogError } = createTaggedError<"LogError", LogContext>("LogError"); // Context is optional LogError({ message: "Parse failed" }); // But when provided, it's typed LogError({ message: "Parse failed", context: { file: "app.ts", line: 42 } });
This gives you the best of both worlds: optional context like flexible mode, but with type enforcement like fixed context mode.
The same pattern works for
cause:type NetworkError = TaggedError<"NetworkError">; type CauseType = NetworkError | undefined; const { ApiError } = createTaggedError< "ApiError", { endpoint: string }, CauseType >("ApiError");
-
5d72453: fix(error): simplify TaggedError types for better ReturnType inference
Previously,
createTaggedErrorused function overloads to provide precise call-site type inference. While this worked well for constructing errors, it brokeReturnType<typeof MyError>because TypeScript picks the last overload (the most constrained signature).This change simplifies to single signatures per mode:
- Flexible mode (no type params): context and cause are optional with loose typing
- Fixed context mode (TContext specified): context is required with exact type
- Both fixed mode (TContext + TCause): context required, cause optional but constrained
ReturnTypenow works correctly:const { NetworkError } = createTaggedError("NetworkError"); type NetworkError = ReturnType<typeof NetworkError>; // = TaggedError<'NetworkError'> with optional context/cause
BREAKING CHANGE: In flexible mode,
contextis now typed asRecord<string, unknown> | undefinedinstead of precisely inferred at call sites. Users who need typed context should use fixed context mode:// Before (flexible with inference - no longer works) const { ApiError } = createTaggedError("ApiError"); const err = ApiError({ message: "x", context: { endpoint: "/users" } }); // After (fixed context mode) const { ApiError } = createTaggedError<"ApiError", { endpoint: string }>( "ApiError" ); const err = ApiError({ message: "x", context: { endpoint: "/users" } });
-
2388e95: fix(result): add overload for trySync/tryAsync when catch returns Ok | Err union
Previously, trySync and tryAsync only had overloads for catch handlers that returned exclusively Ok or exclusively Err. This caused type errors when a catch handler could return either based on runtime conditions (conditional recovery pattern).
Added a third overload to both functions that accepts catch handlers returning
Ok<T> | Err<E>, properly typing the return asResult<T, E>.