Skip to content

Releases: wellcrafted-dev/wellcrafted

v0.34.0

03 Mar 19:54
3a2830c

Choose a tag to compare

Minor Changes

  • 7e1da94: Move JsonValue and JsonObject types to wellcrafted/json export path.

    BREAKING: JsonValue and JsonObject are no longer exported from wellcrafted/error. Import them from wellcrafted/json instead.

v0.33.0

03 Mar 03:29
1b50a39

Choose a tag to compare

Minor Changes

  • 1c6e597: BREAKING: defineErrors v2 — Rust-style namespaced errors with Err-by-default

    • Factories now return Err<...> directly (no dual FooError/FooErr factories)
    • Keys are short variant names (Connection, Parse) instead of ConnectionError
    • InferError<T> takes a single factory (typeof HttpError.Connection)
    • InferErrors<T> replaces InferErrorUnion<T> for union extraction
    • ValidatedConfig provides descriptive error when reserved name key is used

v0.32.0

02 Mar 17:45
f32d21c

Choose a tag to compare

Minor Changes

  • 4539eb2: Redesign createTaggedError builder: flat .withFields() API replaces nested .withContext()/.withCause(), .withMessage() is optional and seals the message (not in factory input type), message required at call site when .withMessage() is absent. Removes context nesting, cause as first-class field, and reason convention.

v0.31.0

26 Feb 05:52
e969fd2

Choose a tag to compare

Minor Changes

  • 69bf59e: Breaking: createTaggedError now 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 message field is no longer a required input at the factory call site — it is derived from the template function passed to .withMessage(). You can still pass message: as an optional override.
    • Context type tightened: context constraint changed from Record<string, unknown> to JsonObject (values must be JSON-serializable).
    • Strict builder/factory separation: ErrorBuilder only has chain methods (.withContext(), .withCause(), .withMessage()). FinalFactories only 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

26 Dec 06:32
2d07dd6

Choose a tag to compare

Patch Changes

  • 7df7aee: Fix Brand type to support hierarchical/stacked brands

    Previously, stacking brands resulted in never because intersecting { [brand]: 'A' } with { [brand]: 'B' } collapsed the property to never.

    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

09 Dec 02:10
2e20e4b

Choose a tag to compare

Minor Changes

  • 1237ce4: BREAKING: Rename resultQueryFn to queryFn and resultMutationFn to mutationFn

    The result prefix 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

07 Dec 22:51
450f504

Choose a tag to compare

Minor Changes

  • 7c41baf: feat(error): explicit opt-in for context and cause properties

    Following Rust's thiserror pattern, createTaggedError now uses explicit opt-in for context and cause properties. By default, errors only have { name, message }.

    Breaking Change: Previously, all errors had optional context and cause properties 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

07 Dec 21:27
6899028

Choose a tag to compare

Minor Changes

  • b917060: Replace defineError with fluent createTaggedError API

    The createTaggedError function 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:

    • defineError has been removed (use createTaggedError instead)
    • The old createTaggedError generic overloads are removed in favor of the fluent API

v0.26.0

06 Dec 23:40
18499be

Choose a tag to compare

Minor Changes

  • 5f0e7af: Make query and mutation definitions directly callable

    Query and mutation definitions from defineQuery and defineMutation are 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: .options is now a property instead of a function. Update createQuery(query.options()) to createQuery(query.options).

v0.25.1

01 Dec 22:20
ab7a85a

Choose a tag to compare

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, createTaggedError used function overloads to provide precise call-site type inference. While this worked well for constructing errors, it broke ReturnType<typeof MyError> because TypeScript picks the last overload (the most constrained signature).

    This change simplifies to single signatures per mode:

    1. Flexible mode (no type params): context and cause are optional with loose typing
    2. Fixed context mode (TContext specified): context is required with exact type
    3. Both fixed mode (TContext + TCause): context required, cause optional but constrained

    ReturnType now works correctly:

    const { NetworkError } = createTaggedError("NetworkError");
    type NetworkError = ReturnType<typeof NetworkError>;
    // = TaggedError<'NetworkError'> with optional context/cause

    BREAKING CHANGE: In flexible mode, context is now typed as Record<string, unknown> | undefined instead 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 as Result<T, E>.