Skip to content

Latest commit

 

History

History
1222 lines (788 loc) · 40.3 KB

File metadata and controls

1222 lines (788 loc) · 40.3 KB

wellcrafted

0.44.0

Minor Changes

  • b56ade1: Rename the hook-local Result adapters: queryOptionsresultQueryOptions and mutationOptionsresultMutationOptions.

    Breaking change. Update imports and call sites:

    -import { queryOptions, mutationOptions } from "wellcrafted/query";
    +import { resultQueryOptions, resultMutationOptions } from "wellcrafted/query";

    createQueryFactories, defineQuery, and defineMutation are unchanged — they still compose through the renamed adapters, so there is still exactly one place that unwraps Result into TanStack's throwing contract.

    Why: the old names collided with TanStack Query's own queryOptions / mutationOptions identity helpers. The result* prefix removes that collision and names what the adapters do — adapt a Result-returning queryFn / mutationFn — so both can coexist in one file without aliasing.

    This clarifies the two-family model:

    • resultQueryOptions / resultMutationOptions — hook-local, reactive, or local-QueryClient operations passed straight to a framework hook.
    • createQueryFactories(...).defineQuery / defineMutation — reusable, QueryClient-bound handles with imperative helpers (.fetch, .ensure, callable mutations).

    Cache operations (invalidateQueries, setQueryData, ensureQueryData, …) stay on the QueryClient and are intentionally not wrapped.

0.43.0

Minor Changes

  • f7f627b: Add once at the new wellcrafted/function subpath. once(fn) runs the wrapped function at most once: the first call invokes it and caches the result, and every later call returns that cached result while ignoring any arguments passed after the first. It is a dependency-free combinator for idempotent disposal and lazy one-time initialization.

0.42.0

Minor Changes

  • 4575de2: Simplify the imperative factory API so query reads choose a cache policy explicitly and mutations use the callable form as their only imperative trigger.

    Queries now expose:

    • .fetch(): run through TanStack Query's freshness-aware fetchQuery path.
    • .ensure(): return cached data if it exists, fetching only when the cache has no data.

    This makes the read policy visible at the call site:

    const freshEnough = await userQuery.fetch();
    const cacheFirst = await userQuery.ensure();

    Mutations no longer expose a duplicate .execute() helper:

    const saved = await saveUser(input);

    The richer TanStack mutation observer surface still lives behind .options for hooks.

0.41.0

Minor Changes

  • fc1c87e: Add queryOptions and mutationOptions as the canonical Result-to-TanStack adapters.

    wellcrafted/query now exposes two lower-level helpers alongside defineQuery / defineMutation:

    • queryOptions(input) accepts a queryKey plus a Result-returning queryFn and returns standard QueryObserverOptions whose queryFn resolves Ok(data) and throws Err(error).
    • mutationOptions(input) accepts a mutationKey plus a Result-returning mutationFn and returns standard MutationOptions with the same Result unwrapping behavior.

    These helpers are platform-agnostic: no QueryClient required. They compose cleanly inside framework hooks:

    import { queryOptions, mutationOptions } from "wellcrafted/query";
    
    const user = createQuery(() =>
      queryOptions({
        queryKey: ["user", userId],
        queryFn: () => services.getUser(userId),
      })
    );
    
    const save = createMutation(() =>
      mutationOptions({
        mutationKey: ["saveUser"],
        mutationFn: (input: SaveUserInput) => services.saveUser(input),
      })
    );

    defineQuery and defineMutation now compose through these helpers, so there is exactly one canonical conversion path from Result<TData, TError> to TanStack's throwing contract. The .options produced by defineQuery and defineMutation is the same shape returned by queryOptions and mutationOptions.

    Use queryOptions / mutationOptions when options are local to a hook call site. Use defineQuery / defineMutation when you also want imperative helpers (.fetch, .ensure, .execute, callable form) bound to a specific QueryClient.

    The new helpers share names with TanStack Query's framework-adapter identity helpers (@tanstack/react-query, @tanstack/svelte-query). This is intentional: Wellcrafted's versions are the Result-aware equivalents. If you need both in one file, alias one on import.

0.40.0

Minor Changes

  • 92a38df: Add defineKeys and infer literal mutationKey tuples in defineMutation without as const.

    defineMutation now uses TypeScript 5.0's const type parameter modifier on TMutationKey, mirroring the recent change to defineQuery. mutationKey: ['users', 'create'] infers as readonly ['users', 'create'] instead of widening to string[].

    // Before
    defineMutation({
      mutationKey: ['users', 'create'] as const,
      mutationFn: ...
    });
    
    // After
    defineMutation({
      mutationKey: ['users', 'create'],
      mutationFn: ...
    });

    defineKeys is a new identity helper for declaring TanStack Query key maps. It uses the const modifier to preserve tuple types on static entries, and a strict tuple constraint to narrow factory return shapes without as const. Factory bodies still need as const for literal narrowing of static positions.

    import { defineKeys } from "wellcrafted/query";
    
    const userKeys = defineKeys({
      all: ["users"], // readonly ['users']
      active: ["users", "active"], // readonly ['users', 'active']
      detail: (id: string) => ["users", id], // [string, string]
      page: (n: number) => ["users", n] as const, // readonly ['users', number]
    });

    Without defineKeys, the same map requires as const on every line. The const modifier on a generic does not reach into function-body return-type inference, which is why factory entries with literal positions like 'users' still need as const inside the body to preserve the literal.

0.39.0

Minor Changes

  • b75fffd: Infer literal queryKey tuples in defineQuery without requiring as const. The TQueryKey type parameter now uses TypeScript 5.0's const modifier, so queryKey: ['users', userId] is inferred as readonly ['users', string] instead of being widened to string[]. This preserves the narrow key type on .options.queryKey and inside queryFn's context, matching how TanStack Query types keys when you write them inline.

    Before:

    const userQuery = defineQuery({
      queryKey: ["users", userId] as const, // `as const` required to keep the tuple
      queryFn: ({ queryKey }) => services.getUser(queryKey[1]),
    });

    After:

    const userQuery = defineQuery({
      queryKey: ["users", userId], // inferred as readonly ['users', string]
      queryFn: ({ queryKey }) => services.getUser(queryKey[1]),
    });

0.38.0

Minor Changes

  • 2b47280: Remove defineHttpErrors and its type companions.

    Bumped as minor (pre-1.0 convention: minor is breaking in 0.x) rather than major. wellcrafted@1.0.0 was published and unpublished on 2025-07-17 and npm permanently blocks reusing that version number, so a major bump from 0.37.0 -> 1.0.0 cannot publish.

    defineHttpErrors coupled error definition to one specific transport (HTTP). The right primitive is defineErrors, with HTTP status (or any other per-variant metadata) carried in a sibling map keyed by variant name. This deletion shrinks the public surface and keeps wellcrafted/error transport-agnostic, so the same errors can flow through HTTP, queues, RPC, CLIs, or anywhere else without per-transport variants of the primitive.

    Removed exports:

    • defineHttpErrors
    • HttpErrorFactory
    • HttpErrorsConfig
    • ValidatedHttpConfig
    • DefineHttpErrorsReturn
    • InferHttpError
    • InferHttpErrors

    Migration

    Replace the tuple-configured factory with defineErrors plus a satisfies-checked side-map:

    // before
    import { defineHttpErrors, type InferHttpErrors } from "wellcrafted/error";
    
    const AiChatError = defineHttpErrors({
      Unauthorized: [401, () => ({ message: "Unauthorized" })],
      ProviderNotConfigured: [
        503,
        ({ provider }: { provider: string }) => ({
          message: `${provider} not configured`,
          provider,
        }),
      ],
    });
    type AiChatError = InferHttpErrors<typeof AiChatError>;
    
    return c.json(
      AiChatError.ProviderNotConfigured({ provider }),
      AiChatError.ProviderNotConfigured.status
    );
    // after
    import { defineErrors, type InferErrors } from "wellcrafted/error";
    
    const AiChatError = defineErrors({
      Unauthorized: () => ({ message: "Unauthorized" }),
      ProviderNotConfigured: ({ provider }: { provider: string }) => ({
        message: `${provider} not configured`,
        provider,
      }),
    });
    type AiChatError = InferErrors<typeof AiChatError>;
    
    const AiChatErrorStatus = {
      Unauthorized: 401,
      ProviderNotConfigured: 503,
    } as const satisfies Record<AiChatError["name"], number>;
    
    return c.json(
      AiChatError.ProviderNotConfigured({ provider }),
      AiChatErrorStatus.ProviderNotConfigured
    );

    The satisfies Record<E["name"], number> clause enforces exhaustiveness at compile time: adding a variant without a status (or a stale key after a rename) is a type error.

    The same shape generalizes to any other per-variant metadata you want to attach without polluting the error body or forking the primitive: icons, log levels, retry classifications, gRPC codes, problem-type URIs, etc. Each lives in its own as const satisfies Record<E["name"], T> table next to the consumer that cares.

0.37.0

Minor Changes

  • 30f0711: Add defineHttpErrors: typed HTTP error factories with a static .status property.

    Like defineErrors, but each variant is paired with its HTTP status code via a [status, factory] tuple. The status is attached to the factory function as a literal type (e.g. AssetError.MissingFile.status is 400), never serialized into the error body.

    const AssetError = defineHttpErrors({
      MissingFile: [400, () => ({ message: "Missing file" })],
      FileTooLarge: [
        413,
        ({ size }: { size: number }) => ({
          message: `File too large: ${size}`,
          size,
        }),
      ],
      StorageLimitExceeded: [402, () => ({ message: "Storage limit exceeded" })],
    });
    
    // Hono route handler:
    return c.json(AssetError.MissingFile(), AssetError.MissingFile.status);
    // body:   { error: { name: 'MissingFile', message: 'Missing file' }, data: null }
    // status: 400

    Also exports InferHttpError, InferHttpErrors, HttpErrorFactory, DefineHttpErrorsReturn, HttpErrorsConfig, and ValidatedHttpConfig.

0.36.0

Minor Changes

  • b9b0dce: Add parseJson and JsonParseError to wellcrafted/json.

    parseJson(text) parses a JSON string into a Result<JsonValue, JsonParseError> instead of throwing. It is the Result-returning counterpart to JSON.parse:

    • the success value is typed as JsonValue rather than any, so you must narrow or validate it before treating it as a known shape
    • malformed input is reported as a tagged JsonParseError (built with defineErrors) carrying the underlying SyntaxError as cause, instead of throwing
    import { parseJson } from "wellcrafted/json";
    
    const { data, error } = parseJson(raw);
    if (error) return Err(error); // JsonParseError
    data; // JsonValue

    No reviver argument is accepted: a reviver can return arbitrary values, which would make the JsonValue success type unsound.

  • 5c4dab6: Add the wellcrafted/testing entry point with expectOk and expectErr.

    These are test-only assertion helpers for Result values. Each unwraps the expected branch and returns it narrowed, throwing if the Result is the other variant:

    import { expectOk, expectErr } from "wellcrafted/testing";
    
    const value = expectOk(parseConfig(raw)); // success value, narrowed
    const error = expectErr(parseConfig("x")); // error value, narrowed
    expect(error.name).toBe("ConfigParseError");

    They intentionally throw: a failed expectation should abort the test, which every runner reports as a failure. Throwing is fenced into this separate wellcrafted/testing entry point so wellcrafted/result stays throw-free. The helpers throw a plain Error, so they work under bun, vitest, jest, or node:test without depending on a test framework.

0.35.0

Minor Changes

  • ea6597e: Add wellcrafted/logger — a structured, level-keyed logger for libraries that use defineErrors.

    • Five levels (trace / debug / info / warn / error), no fatal. Mirrors Rust's tracing.
    • log.warn(err) / log.error(err) take a typed error unary (from defineErrors), accepting both the raw variant and the Err<> wrapper — level is a call-site decision, never a property of the variant.
    • log.info / log.debug / log.trace are free-form (message + optional data).
    • DI-only: no global registry, no default logger singleton. Every consumer takes a log?: Logger option.
    • Ships pure-JS sinks (consoleSink, memorySink, composeSinks) and the tapErr Result-flow combinator (mirrors Rust's .inspect_err). tapErr is re-exported from both wellcrafted/logger (canonical use) and wellcrafted/result (where it structurally belongs).

    Runtime-agnostic. Environment-specific sinks (e.g. a Bun JSONL file sink) belong in downstream packages.

    The LoggableError = AnyTaggedError | Err<AnyTaggedError> union is safe to discriminate via "name" in err: name is already stamped (and reserved) by defineErrors on every tagged error, and Err<E> has no top-level name. No new field reservation was needed.

    import { createLogger, consoleSink, tapErr } from "wellcrafted/logger";
    
    const log = createLogger("my-source"); // defaults to consoleSink
    
    const result = await tryAsync({
      try: () => writeTable(path),
      catch: (cause) => MyError.WriteFailed({ path, cause }),
    }).then(tapErr(log.warn));

Patch Changes

  • 687485e: Fix trySync and tryAsync type inference for catch handlers returning union Err types

    When a catch handler returned multiple Err variants (e.g., Err<A> | Err<B>), TypeScript could not infer the union, requiring explicit return type annotations. Replaced three overloads per function with a single generic signature that infers the full catch return type.

0.34.0

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.

0.33.0

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

0.32.0

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.

0.31.0

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 sealed by template: When .withMessage() is used, the message field is not in the factory input type — it is derived entirely from the template function. Without .withMessage(), message is required at the call site.
    • 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

0.30.0

Minor Changes

  • f2b3ebd: Add Standard Schema v1 compliant Result wrappers for schema validation libraries

    New wellcrafted/standard-schema module provides:

    • OkSchema(schema) - wraps any Standard Schema into { data: T, error: null }
    • ErrSchema(schema) - wraps any Standard Schema into { data: null, error: E }
    • ResultSchema(dataSchema, errorSchema) - creates discriminated union of Ok | Err

    Works with Zod, Valibot, ArkType, and any Standard Schema v1 compliant library. Preserves schema capabilities (validate, jsonSchema) based on input schema features.

0.29.1

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

0.29.0

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),
    });

0.28.0

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

0.27.0

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

0.26.0

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).

0.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, 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>.

0.25.0

Minor Changes

  • 0143960: Align TaggedError type parameter order with createTaggedError

    Changed TaggedError<TName, TCause, TContext> to TaggedError<TName, TContext, TCause> so both APIs use consistent ordering. This simplifies context-only error definitions:

    Before:

    type NetworkError = TaggedError<"NetworkError", never, { host: string }>;

    After:

    type NetworkError = TaggedError<"NetworkError", { host: string }>;

    Single-generic usage (TaggedError<"Name">) is unaffected.

0.24.0

Minor Changes

  • e724405: Add function overloads to createTaggedError for opt-in type strictness

    The createTaggedError function now supports three overload signatures:

    1. Fully flexible (default): No generics required, both context and cause are optional
    2. Context fixed: Specify TContext generic to require context, cause remains flexible
    3. Both fixed: Specify both TContext and TCause generics to require both

    This allows callers to opt-in to stricter type safety when needed while maintaining backward compatibility with the flexible default behavior. When a type is explicitly specified at factory creation time, it becomes required at every call site.

    Updated type definitions:

    • FlexibleTaggedErrorFactories, ContextFixedTaggedErrorFactories, BothFixedTaggedErrorFactories
    • FlexibleTaggedErrorConstructorFn, ContextFixedTaggedErrorConstructorFn, BothFixedTaggedErrorConstructorFn
    • And corresponding Err constructor function variants
  • 60e3439: feat(error): add typed context and cause support to createTaggedError

    Introduces three usage modes for createTaggedError:

    1. Flexible mode: Context and cause are optional with any shape
    2. Fixed context mode: Context is required with an exact type
    3. Both fixed mode: Context required, cause constrained to specific type

    The TaggedError type now uses conditional types so that context and cause properties only exist when specified, making the types more precise.

    // Mode 1: Flexible
    const { NetworkError } = createTaggedError("NetworkError");
    NetworkError({ message: "Timeout" });
    NetworkError({ message: "Timeout", context: { url: "..." } });
    
    // Mode 2: Fixed context (context required)
    type BlobContext = { filename: string; code: "INVALID" | "TOO_LARGE" };
    const { BlobError } = createTaggedError<"BlobError", BlobContext>(
      "BlobError"
    );
    BlobError({
      message: "Invalid",
      context: { filename: "x", code: "INVALID" },
    });
    
    // Mode 3: Both fixed
    const { ApiError } = createTaggedError<"ApiError", ApiContext, NetworkError>(
      "ApiError"
    );

Patch Changes

  • a95c8f8: Improve type inference for tagged error factories

    Refactored createTaggedError to automatically infer cause and context types from input, eliminating the need for explicit generic parameters. Added TContext generic parameter to TaggedError type for better type safety. Reorganized internal type structure with separate TaggedErrorConstructorFn and TaggedErrConstructorFn types for improved modularity.

0.23.1

Patch Changes

  • 0e5f291: Improve extractErrorMessage with comprehensive primitive handling and extended error property checking

0.23.0

Minor Changes

  • ae5792f: feat: simplify error system with unified TaggedError type

    • Combined BaseError and TaggedError into a single unified type
    • Made cause property optional and strongly typed as TaggedError for error chaining
    • Created JSON-serializable call stacks through error chaining
    • Each error in the chain maintains full TypeScript typing and context
    • Updated documentation with comprehensive examples of error chaining patterns

    BREAKING CHANGE: BaseError type has been removed. Use TaggedError or TaggedError<string> instead.

  • 1c0d30f: feat: add typed cause generic to TaggedError for type-safe error chaining

    • Add second generic parameter TCause to TaggedError type
    • Enable compile-time validation of error cause relationships
    • Use TaggedError<string, any> constraint to avoid circular references
    • Add comprehensive JSDoc with JSON serialization examples
    • Update all documentation to show typed cause
    • Maintain full backward compatibility with existing single-generic usage

    Example:

    type NetworkError = TaggedError<"NetworkError">;
    type DatabaseError = TaggedError<"DatabaseError", NetworkError>;
    
    const dbError: DatabaseError = {
      name: "DatabaseError",
      message: "Connection failed",
      cause: networkError, // TypeScript enforces NetworkError type
    };

Patch Changes

  • cb062a9: chore: upgrade Biome to v2.2.6 and migrate configuration

0.22.0

Minor Changes

  • 8b752cd: Rename mapErr to catch and add smart return type narrowing to trySync/tryAsync

    Breaking Change: The mapErr parameter in trySync and tryAsync functions has been renamed to catch for better semantic clarity.

    New Feature: Added function overloads that provide smart return type narrowing:

    • When catch always returns Ok<T>, the function returns Ok<T> (guaranteed success)
    • When catch can return Err<E>, the function returns Result<T, E> (may succeed or fail)

    This eliminates unnecessary error checking when you know your error handler always recovers, while still requiring proper error handling when failures are possible.

    Migration: Replace mapErr with catch in all trySync and tryAsync calls:

    // Before
    trySync({
      try: () => operation(),
      mapErr: (error) => Err(new Error("Failed")),
    });
    
    // After
    trySync({
      try: () => operation(),
      catch: (error) => Err(new Error("Failed")),
    });

    Improved JSDoc documentation with clearer examples and better hover experience.

0.21.4

Patch Changes

  • e07df18: Fixes QueryClient cache-access functions to return TQueryData instead of TData

0.21.3

Patch Changes

  • 2a966f8: Fixes isResult() to allow { data: null, error: null } as valid Ok(null) result

0.21.2

Patch Changes

  • 851983e: Updates types to include TData generics for improved type accuracy

0.21.1

Patch Changes

  • 48048a2: Adds default generics for defineMutation. Because we set a default generic void for TVariables, we can call mutation.mutate() instead of having to always put in mutation.mutate({}).

0.21.0

Minor Changes

  • 5c73e8e: Renames mapError parameter to mapErr in trySync and tryAsync functions.

    The mapErr parameter now returns Err<E> directly instead of just the error value E. This change provides more explicit control over error wrapping.

    Migration required:

    // Before
    trySync({
      try: () => operation(),
      mapError: (error) => ({
        name: "MyError",
        message: "Operation failed",
        cause: error,
      }),
    });
    
    // After
    trySync({
      try: () => operation(),
      mapErr: (error) =>
        Err({
          name: "MyError",
          message: "Operation failed",
          cause: error,
        }),
    });

0.20.0

Minor Changes

  • af03f84: Rename fetchCached method to fetch in query definitions

    BREAKING CHANGE: The fetchCached() method on query definitions has been renamed to fetch() to better reflect its actual behavior. This method intelligently fetches data from cache OR network based on staleness.

    Migration:

    • Replace all .fetchCached() calls with .fetch()
    • No functional changes - behavior remains identical
    • See MIGRATION_FETCHCACHED_TO_FETCH.md for detailed migration guide

    Before:

    const { data, error } = await userQuery.fetchCached();

    After:

    const { data, error } = await userQuery.fetch();

0.19.1

Patch Changes

  • a4efb1b: Replaces QueryOptions with QueryObserverOptions

0.19.0

Minor Changes

  • 83e90fe: Add TanStack Query integration utilities

    • New createQueryFactories function for creating type-safe query and mutation definitions
    • Automatic Result type handling for TanStack Query
    • Dual interface pattern: reactive (for UI) and imperative (for actions)
    • Full TypeScript support with proper type inference
    • Comprehensive documentation and examples

0.18.0

Minor Changes

  • 5257a07: Add createTaggedError factory function for generating tagged error constructors

    Introduces a new utility function that creates factory functions for tagged errors:

    • createTaggedError(name) returns two factory functions:
      • One that creates plain TaggedError objects (named with "Error" suffix)
      • One that creates Err-wrapped TaggedError objects (named with "Err" suffix)
    • Automatic naming convention: "Error" suffix becomes "Err" suffix for Result-wrapped version
    • Fully typed with TypeScript generics for type safety
    • Comprehensive JSDoc documentation with examples

    This simplifies creating tagged error constructors and provides a consistent API for both plain errors and Result-wrapped errors.

    Example usage:

    const { NetworkError, NetworkErr } = createTaggedError("NetworkError");
    
    // Creates plain error object
    const error = NetworkError({
      message: "Connection failed",
      context: { url },
      cause: undefined,
    });
    
    // Creates Err-wrapped error for Result types
    return NetworkErr({
      message: "Connection failed",
      context: { url },
      cause: undefined,
    });

0.17.0

Minor Changes

  • deb7bff: Make context property optional in TaggedError type

    The context property in BaseError and TaggedError is now optional, allowing developers to omit it when no contextual information is needed. This reduces boilerplate code and eliminates the need for empty context objects.

    Before:

    return Err({
      name: "ValidationError",
      message: "Invalid input",
      context: {}, // Required but meaningless
      cause: null,
    });

    After:

    return Err({
      name: "ValidationError",
      message: "Invalid input",
      cause: null,
      // context can be omitted
    });

    This is a backwards-compatible change - all existing code continues to work unchanged.

0.16.1

Patch Changes

  • 062ac62: Adds dts to tsdown config, fixes missing types

0.16.0

Minor Changes

  • b450bd1: Adds Brand type for nominal typing in Typescript
  • 662cd35: Rebrands repo to "wellcrafted" with modular architecture

0.15.0

Minor Changes

  • 95f7704: Renames unwrapIfResult function to resolve

0.14.0

Minor Changes

  • c5a8d2a: Adds unwrap function to extract success value from Result<T, E> and throw error for Err<E> variant

0.13.1

Patch Changes

  • 27c88a4: Fixes partitionResults function to have default empty oks and errs arrays

0.13.0

Minor Changes

  • 0416841: Renames mapErr to mapError, enhance docs, introduce naming conventions for error suffix
  • a708959: Renames mapErr to mapError, enhances result and error handling documentation, and introduces naming conventions for error types

0.12.0

Minor Changes

  • efbe590: Adds BaseError, TaggedError, extractErrorMessage, and comprehensive error handling guide

Patch Changes

  • 3d0539e: Add declarationMap: true to tsconfig

0.11.0

Minor Changes

  • 05a04b8: Adds unwrapIfResult function

0.10.0

Minor Changes

  • 422f86c: Adds isResult type guard

0.9.5

Patch Changes

  • d5f209a: Migrates to tsdown

0.9.4

Patch Changes

  • 0937db9: Adds files field to package.json

0.9.3

Patch Changes

  • f5ac7c1: Adds main, module, and types for improved module resolution to package.json

0.9.2

Patch Changes

  • 0f9bf08: Updates Typescript module and lib to NodeNext and ESNext

0.9.1

Patch Changes

  • 208a06d: Adds output support for CommonJS

0.9.0

Minor Changes

  • c750a55: Adds partitionResults utility function to separate Result objects into success and error arrays
  • 55cfbff: Enhances Result documentation and add extraction utilities

Patch Changes

  • d6d6b6e: Updates TypeScript configuration and package.json exports

0.8.0

Minor Changes

  • ff97dbb: Adds partitionResults utility function to separate Result objects into success and error arrays

0.7.2

Patch Changes

  • b85bd17: Adds "require" to exports for more compatibility

0.7.1

Patch Changes

  • 5cfccdd: Fixes tsup config and entry points

0.7.0

Minor Changes

  • 0b32ddd: Updates mapErr handling in trySync and tryAsync functions, now automatically wrapping with Err<>
  • 50498bf: Removes discriminated union implementation of result in favor of exclusive (data/error) pattern

Patch Changes

  • 1c4ef64: Make second arg of mutate optional, use Partial Typescript helper

0.6.0

Minor Changes

  • 5e6133a: Added new onSuccessLocal, onErrorLocal, onSettledLocal

0.5.2

Patch Changes

  • d1d5257: Restore old ok and err behavior

0.5.1

Patch Changes

  • fe51ba1: Updated generics of trySync and tryAsync again

0.5.0

Minor Changes

  • 344f3d5: Update generics in trySync and tryAsync functions; remove services.ts

Patch Changes

  • 7f49d16: Add OnMutateError generic to createMutation function

0.4.2

Patch Changes

  • 57a88a6: Simplify Result type definition by consolidating Ok and Err types

0.4.1

Patch Changes

  • 35dbcdd: Update mapError to take in Err and spit out Err

0.4.0

Minor Changes

  • 75df94c: Rename catch parameter to mapErr in trySync and tryAsync functions for improved clarity

0.3.2

Patch Changes

  • 9b4bf3e: createMutation function returns function directly

0.3.1

Patch Changes

  • 0cf54d2: Update first parameter of createMutation for improved clarity
  • 043d624: Add InferOk and InferErr utility types for Result type inference
  • b4a0bc4: Add ServiceFn type and update exports in index.ts

0.3.0

Minor Changes

  • 9598e2a: Add createMutation function

0.2.6

Patch Changes

  • 53e8cf2: Make MutationFn callbacks optional

0.2.5

Patch Changes

  • 2bbf651: Make callbacks optional

0.2.4

Patch Changes

  • 69be638: Fix callback type definitions in MutationFn for improved clarity and consistency

0.2.3

Patch Changes

  • 5739f07: Correct type definition for tryAsync function to ensure it always returns a Promise
  • 732009a: Rename and update ServiceResultFactoryFns to ServiceErrorFns for improved clarity and consistency in error handling
  • 6c1b688: Update type definition for tryAsync function to enforce promise return type

0.2.2

Patch Changes

  • c39ff0f: Export service-related functions from index

0.2.1

Patch Changes

  • fe27e9b: Update ServiceResultFactoryFns type definitions for improved clarity and consistency

0.2.0

Minor Changes

  • 7b3a558: Create service result factory functions

Patch Changes

  • 5aec8f9: Simplify logic of createServiceResultFactoryFns
  • 42b2163: Extracted /index to /result and reexported

0.1.1

Patch Changes

  • dd4ce91: Simplify Ok and Err functions

0.1.0

Minor Changes

  • 4485244: Initial features scaffolded (ok, err, result, trySync, tryAsync)

Patch Changes

  • 6ef1580: Added keywords to package metadata to improve npm discoverability