# Changelog

## [1.0.1] - 2026-09-27

Fixes from the review of 1.0.0. No API changes.

### 🐛 Fixed

- `isRedirecting` no longer stays `true` after a server-side `redirect()` that only changes the search params, no longer turns `true` again when the user comes back to the page, and is cleared when a new call starts.
- `useFormAction` returns the same `actionState` object until the result changes, so it can sit in effect dependencies.
- A `.formAction()` passed directly to `<form action>` no longer fails to type-check.
- FormData decoding keeps rows after a gap in array indexes (rows removed from the middle of a list) instead of silently dropping them. A schema accepts the gaps with optional elements: `z.array(row.optional())`.

### 🔒 Security

- The empty array slots that FormData indexes may open are limited to 10,000 for the whole form. The previous limit applied to each index only, so a small request with nested indexes (`items[i].tags[199]`) could make the schema validate entries × index slots.

## [1.0.0] - 2026-09-26

A security and correctness release. See [MIGRATION.md](./MIGRATION.md) for the upgrade steps; the audit behind it is in `docs/AUDIT_PLAN.md`.

### ⚠️ Breaking changes

- **Requirements:** Next.js 15 or 16, React 19, Zod 4 as a peer dependency; react-hook-form ≥ 7.55 and @hookform/resolvers ≥ 5.1 are optional peers for `next-action-forge/hooks/form`; sonner is no longer required; TypeScript ≥ 5.4.
- **Toasts** go through `<ActionForgeProvider toast={toast}>` or a `toast` hook option. `ToastRestorer` is removed.
- **`useFormAction`** moves to `next-action-forge/hooks/form` and sends the form values as an object to a regular `.action()`; `transformData` returns the action input; `actionState` starts `undefined`.
- **`.formAction()`** decodes FormData by structure only (`a.b`, `a[0]`, `a[]`) and no longer converts `"true"`, `"null"`, dates or JSON text; schemas declare conversions (`z.stringbool()`, `z.coerce.number()`, `emptyToNull`).
- **`ActionResult.status`** (`"success" | "error" | "navigation"`) is required.
- **Validation errors** use dotted `fields` keys, report root-level issues in `formErrors`, and omit empty `fields`.
- **Hook parameters:** `input` is `undefined` until validation succeeds and the new `rawInput` holds the raw input; inputs and outputs are redacted; `.onError()` receives `{ parsedInput, rawInput }`.
- **Redirect targets** must be same-origin paths starting with `/`.
- **Error details** are revealed only when `NODE_ENV === "development"`.

### 🔒 Security

- Output-schema failures answer `OUTPUT_VALIDATION_ERROR` without field names instead of leaking the output schema (and bypassing `.onError()`).
- A `ZodError` thrown by the handler is an internal error, not a user-facing validation error.
- Unhandled errors are logged in every environment through a pluggable `logger` (they were silent in production).
- Redirects computed from action results can no longer send users to external sites.
- Lifecycle hooks and `.onError()` receive redacted copies of inputs and outputs (`redact` option, `DEFAULT_REDACT_KEYS`).
- `.useBeforeValidation()` lets authentication run before validation, so anonymous callers learn nothing about schemas.
- Middleware returning an invalid result fails closed.
- In development, results containing sensitive-looking keys without an `outputSchema` trigger a warning.
- FormData decoding ignores prototype-polluting names and oversized array indexes.

### 🚀 Added

- `.useBeforeValidation()`, `{ redirect }` option on `.action()`/`.formAction()`, async redirect callbacks.
- `createTypedMiddleware<Output, Input, RequiredContext>` and inferred middleware context for inline middleware.
- `createActionClient({ logger, authRedirect, redact })`.
- `decodeFormData`, `emptyToNull`, `emptyToUndefined`, `isSafeRedirectPath`, `zodErrorToServerActionError`, `OutputValidationError`, `InvalidFormDataError`, `AnyServerActionClient`.
- Zod Mini schemas are accepted (the library imports only `zod/v4/core`).
- `useOptimisticAction` returns `isRedirecting`; both void and input actions are typed.

### 🐛 Fixed

- Async refinements and transforms in schemas (they always failed with `INTERNAL_ERROR`).
- A form action passed directly to `<form action>` never reached its handler.
- Next.js control flow (legacy `NEXT_NOT_FOUND`, errors wrapped as `cause`, dynamic-rendering bailouts) is rethrown instead of becoming `INTERNAL_ERROR`, including for actions called from Server Components.
- A throwing `toServerActionError()` no longer rejects the action; plain objects with the method are honoured.
- The authentication redirect no longer overwrites `redirectTo` or mutates the consumer's error object.
- `useServerAction`: void results count as success; `redirect()`/`notFound()` from actions no longer become `fetchError`; a throwing `onSuccess` keeps the success; stable `execute`; correct `isExecuting` with concurrent calls; `isRedirecting` resets after navigation, including to the same URL; unsafe redirects are ignored.
- `useFormAction`: void results run `onSuccess`, toasts, reset and redirect; nested field errors land on their inputs; root errors are shown; authentication redirects are followed; Zod 4 client validation works; failed submissions are reported.
- `useOptimisticAction`: the optimistic update and the call share one transition.
- Middleware typing: README examples compile, typed helpers chain, builder methods return `this`.
- Packaging: `next-action-forge/hooks` no longer needs sonner or react-hook-form; separate ESM/CJS types for every entry.

## [0.6.0] - 2026-04-22

### 🐛 Bug Fixes

- **Empty FormData strings are no longer coerced to `null`.** ⚠️ **BREAKING
  SEMANTICS for consumers.** Previously `tryParseJSON` returned `null` for
  any empty/whitespace-only string, which silently broke schemas like
  `z.string()` (non-nullable) on empty form fields — a common and visible
  regression. Empty strings now pass through unchanged.

  **Impact is broader than just dates.** Any consumer schema relying on
  `"" → null` coercion needs an explicit preprocess. Known impact patterns:

  | Consumer schema | Before (v0.5.x) | After (v0.6.0) | Action required |
  |---|---|---|---|
  | `z.string()` (required) | `null` → fail | `""` → pass | — (fix) |
  | `z.string().nullable()` | `null` → DB NULL | `""` → DB `""` | wrap |
  | `z.date().nullable()` | `null` → pass | `""` → fail | wrap |
  | `z.coerce.number().nullable()` | `null` → pass | `""` → `0` (silent!) | wrap |
  | `z.enum(...).nullable()` | `null` → pass | `""` → fail | wrap |

  The silent cases (string and number nullable) are the most dangerous —
  validation passes but the DB row gets `""` or `0` where it should have
  been `NULL`, breaking `WHERE col IS NULL` queries and corrupting
  semantics. Audit every `.nullable()` schema used with `.formAction()`.

  Recommended pattern (generic, works for any nullable field type):

  ```ts
  export const emptyToNull = <T extends z.ZodTypeAny>(schema: T) =>
    z.preprocess(
      (v) => (v === undefined) || (typeof v === "string" && v.trim() === "")
        ? null : v,
      schema,
    );

  // Usage
  const schema = z.object({
    descrizione: emptyToNull(z.string().nullable()),
    anno:        emptyToNull(z.coerce.number().int().positive().nullable().optional()),
    data:        emptyToNull(z.date().nullable().optional()),
    stato:       emptyToNull(z.enum([...]).nullable().optional()),
  });
  ```

### 🛠️ Type Refinements

- **`onStart` context is now typed as `Record<string, never>`**, reflecting
  the runtime reality that the middleware chain has not yet populated it.
- **`onFailure` and `onComplete` context is now `Partial<Context>`**,
  because failures can occur before the middleware chain finishes (input
  validation, an early middleware rejection). Consumers should null-check
  fields rather than assume the full `Context` shape.
- **`onSuccess` context remains the full `Context`** — success only fires
  after all middleware and the handler have completed.

### 🚀 New Features

- **Lifecycle hooks on `ServerActionClient`**
  - `.onStart(hook)` — fires after input validation, before the middleware chain.
  - `.onSuccess(hook)` — fires after the handler resolves and output validation passes. Receives the validated `output` and total `duration` in ms.
  - `.onFailure(hook)` — fires when the action fails (validation error, middleware-returned error, or thrown exception). Receives `error` and `duration`. Never fires for Next.js navigation errors (`redirect`, `notFound`).
  - `.onComplete(hook)` — fires after the action resolves, success or failure, with the final `result` object. Not fired for Next.js navigation errors.
  - All hooks are **side-effect only**: return values are ignored, the flow cannot be mutated.
  - Errors thrown inside hooks are caught and logged to `console.warn`; they never surface to the caller.
  - Multiple hooks of the same kind are allowed and run in registration order.
  - Optional `{ fireAndForget: true }` schedules a hook without awaiting it.

- **`.name(actionName)` builder method**
  - Attaches a semantic name to the action (e.g. `"auth.login"`).
  - The name is propagated to every lifecycle hook via `params.name` and is intended for structured logging and audit trails.

- **New exported types**: `HookOptions`, `HookParamsBase`, `OnStartHook`, `OnSuccessHook`, `OnFailureHook`, `OnCompleteHook`.

### 🧪 Tooling

- Added `vitest` as a devDependency and an `npm test` / `npm run test:watch` script.
- Added a full test suite for lifecycle hooks (17 tests) in `src/core/__tests__/lifecycle-hooks.test.ts`.

### ✨ Notes

- Fully backward-compatible: existing consumers continue to work unchanged.
- Hooks are implemented in both `.action()` and `.formAction()` runtimes.

## [0.5.2] - 2025-01-08

### 🐛 Critical Bug Fixes

- **Fixed Date objects not working with FormData**
  - Date objects are now properly serialized to ISO strings on the client
  - Server correctly deserializes ISO date strings back to Date objects
  - Zod `z.date()`, `z.date().nullable()`, `z.date().optional()` schemas now work correctly

- **Fixed memory leak in useFormAction hook**
  - Added missing dependency array to useEffect that updates callback refs
  - Prevents unnecessary re-renders and potential memory accumulation

- **Fixed localStorage crash risks in useServerAction hook**
  - Added proper error handling for JSON.parse failures (corrupted data)
  - Added data validation to ensure parsed value is an array
  - Added quota management to prevent QuotaExceededError crashes
  - Auto-trims old toasts when storage exceeds limits

### 🚀 New Features

- **Added support for special JavaScript types in FormData**
  - `BigInt`: Now properly serialized and deserialized
  - `Map`: Converted to/from array of entries format
  - `Set`: Converted to/from array format
  - `Symbol`: Throws descriptive error (cannot be serialized)
  - Circular references: Now caught with descriptive error message

### 📝 Technical Details

- Added `serializeValue` helper function for type-safe serialization
- Added `restoreSpecialTypes` function for Map/Set deserialization
- Added `BIGINT_REGEX` pattern for BigInt string detection
- Improved `tryParseJSON` to handle all new special types

## [0.5.1] - 2025-12-29
- Fix useFormAction/formaction serializing object

## [0.5.0] - 2025-10-06

### ✨ New Features

- **Added `createTypedMiddleware` helper for better TypeScript inference**
  - Solves context type inference issue through middleware chains
  - Enables autocomplete and type safety for context in action functions
  - No more manual type assertions needed (`context as { userId: bigint }`)
  - Factory functions now correctly propagate context types

### 🚀 Improvements

- **Better Developer Experience**
  - Context properties now have full IntelliSense support
  - Compile-time type safety for middleware context
  - Cleaner, more maintainable middleware code
  - Perfect type inference through `.use()` chain

### 📝 Technical Details

- New `createTypedMiddleware<TOutputContext>()` helper in `next-action-forge/core`
- Explicitly declares middleware output context type
- Works with factory pattern for composable middleware
- Zero breaking changes - fully backward compatible

### 📚 Usage Example

```typescript
import { createTypedMiddleware } from 'next-action-forge';

interface AuthContext {
  userId: string;
  role: string;
}

const createAuthMiddleware = () =>
  createTypedMiddleware<AuthContext>(async ({ context, input }) => {
    const session = await getSession();
    return {
      context: {
        userId: session.userId,
        role: session.role,
      },
    };
  });

const authClient = baseClient.use(createAuthMiddleware());

// ✅ Context is now fully typed!
authClient.action(async (context) => {
  const userId = context.userId; // IntelliSense works!
});
```

## [0.4.2] - 2025-09-24

### 🐛 Critical Bug Fix

- **Fixed isExecuting timing issue in useServerAction hook**
  - `setIsExecuting(true)` is now called immediately to prevent spam-clicking
  - Buttons disable instantly on click, preventing multiple simultaneous requests
  - Fixed race condition where `startTransition` delayed state updates
  - Maintains all React 18 concurrent rendering benefits for async operations

### 🧹 Code Quality Improvements

- **Cleaned up hook architecture**
  - Removed dead code and redundant execute functions
  - Simplified internal architecture while preserving functionality
  - Better code maintainability with single execution path
  - Zero breaking changes - complete backward compatibility

### 📝 Technical Details

- Moved `setIsExecuting(true)` outside `startTransition` for immediate UI response
- Preserved `startTransition` for the actual async operation
- Eliminated architectural confusion between multiple execute functions
- Maintained all existing functionality: success/error handling, redirects, toasts

## [0.4.1] - 2025-09-08

### 🐛 Critical Bug Fix

- **Fixed boolean parsing for checkbox and form fields**
  - Server now correctly parses "true"/"false" strings to JavaScript booleans
  - Resolves Zod validation failures for boolean fields (e.g., `isVerified`)
  - Maintains compatibility with React Hook Form checkbox handling
  - Also parses "null" string to JavaScript null value

### 🚀 Improvements

- **Better FormData convention support**
  - Aligns with HTML form limitations (can't send real booleans, only strings)
  - Follows common checkbox patterns used by React Hook Form
  - Number strings like "123" still remain as strings to avoid ambiguity
  - Case-sensitive parsing (only lowercase "true"/"false" are converted)

### 📝 Technical Details

- Updated `tryParseJSON` to handle boolean conventions in FormData context
- Safe approach: only exact matches "true"/"false"/"null" are parsed
- Preserves all other string values including "True", "FALSE", "123", etc.

## [0.4.0] - 2025-09-08

### 🐛 Critical Bug Fix

- **Fixed FormData asymmetry between client and server**
  - Server now correctly deserializes JSON-stringified objects from FormData
  - Added `tryParseJSON` helper that only parses objects/arrays, not primitives
  - Maintains perfect symmetry with client-side `objectToFormData`
  - Resolves Zod validation failures for nested objects in forms

### 🚀 Improvements

- **Better handling of complex form data**
  - Nested objects now work correctly in form submissions
  - Arrays with nested objects are properly deserialized
  - Primitive strings like "true", "123", "null" remain as strings (no ambiguous parsing)
  - Files and Blobs are preserved correctly

### 🔧 Technical Details

- Added safe JSON parsing that only attempts to parse strings starting with `{` or `[`
- Graceful fallback for malformed JSON (returns original string)
- Zero breaking changes for existing forms with simple data types

## [0.3.5] - 2025-08-04

- update readme

## [0.3.4] - 2025-08-04

### 🚀 New Features

- **Automatic TypeScript inference for middleware input**
  - Middleware now receives fully typed input when used after `.inputSchema()`
  - No more `unknown` type - get full IntelliSense for your validated data
  - **Important**: Call `.inputSchema()` before `.use()` to enable type inference

## [0.3.2] - 2025-08-04

### 🚀 New Features

- Added `isRedirecting` state to both `useServerAction` and `useFormAction` hooks
  - Tracks when a redirect is in progress after successful action completion
  - Allows for better UX by showing appropriate loading states during navigation
  - Prevents user confusion when form appears available but redirect is pending

### 🔧 Improvements

- Enhanced user experience during redirect transitions
- Form buttons can now show different states: submitting vs redirecting
- Automatic reset of `isRedirecting` state when processing new actions

### 📝 Usage Examples

#### isRedirecting State

```typescript
const { form, onSubmit, isSubmitting, isRedirecting } = useFormAction({
  action: loginAction,
  // ...
});

<button disabled={isSubmitting || isRedirecting}>
  {isRedirecting ? "Redirecting..." : 
   isSubmitting ? "Logging in..." : 
   "Login"}
</button>
```

#### Typed Middleware Input

```typescript
// ✅ Correct: inputSchema BEFORE use
export const loginAction = actionClient
  .inputSchema(loginSchema)  // Define schema first
  .use(async ({ context, input }) => {
    // input is fully typed as { email: string, password: string }
    if (input.email === "blocked@example.com") {
      return { error: { code: "BLOCKED", message: "Email blocked" } };
    }
    return { context };
  })
  .action(async (input) => { /* ... */ });

// ❌ Wrong: use before inputSchema results in input: unknown
export const wrongAction = actionClient
  .use(async ({ context, input }) => {
    // input is 'unknown' - no type safety!
  })
  .inputSchema(schema)  // Too late!
  .action(async (input) => { /* ... */ });
```

## [0.3.1] - 2025-08-03

### 🚀 FIX

- Fix useFormAction hook in next-action-forge library:
  - Reduce useEffect dependencies to prevent infinite loops
  - Use refs for callbacks to avoid re-renders
  - Add proper cleanup for redirect timeouts
  - Add default delay for state update completion


## [0.3.0] - 2025-08-01

### 🚀 Major Features

- **Server-Driven Redirects**: Complete implementation of redirect system across all hooks
- Added `.redirect()` method to ServerActionClient for declarative redirect configuration
- Full support for conditional redirects based on action results
- Consistent redirect handling in both `useServerAction` and `useFormAction` hooks

### 🐛 Bug Fixes

- Fixed redirect not working in `useFormAction` hook
- Fixed success response handling when action returns no data
- Improved NEXT_REDIRECT error detection using proper digest checking

### 📚 Documentation

- Comprehensive examples for redirect patterns in README
- Added conditional redirect examples with TypeScript
- Updated API reference with redirect method documentation

## [0.2.8] - 2025-08-01

### 📚 Documentation

- Updated README with comprehensive documentation for the new `.redirect()` feature
- Added examples for simple redirects, advanced configuration, and conditional redirects
- Updated API reference to include the redirect method

## [0.2.7] - 2025-08-01

### 🚀 New Features

- Added `.redirect()` method to ServerActionClient for server-driven redirects
- Server actions can now define redirects that execute after successful completion
- Redirects support string URLs or configuration objects with replace/delay options
- Added automatic redirect handling in useServerAction hook with configurable options
- Support for conditional redirects using function callbacks

## [0.2.6] - 2025-08-01

### 🐛 Bug Fixes

- Fixed client-side useServerAction hook to properly handle NEXT_REDIRECT errors
- Navigation errors are now re-thrown in the hook's catch block instead of being displayed as toasts
- This completes the fix for redirect handling (server-side was fixed in 0.2.5)

## [0.2.5] - 2025-08-01

### 🐛 Bug Fixes

- Fixed NEXT_REDIRECT errors being shown as toast messages (both server and client side)
- Server actions now correctly preserve Next.js redirects using robust error detection
- `redirect()` calls in server actions now work as expected without showing error toasts
- Added support for all Next.js navigation errors (redirect, notFound, forbidden, unauthorized)

### 🚀 New Features

- Added `isRedirectError()` utility function for detecting redirect errors
- Added `isNotFoundError()` utility function for detecting notFound errors  
- Added `isHTTPAccessFallbackError()` for detecting HTTP navigation errors
- Added `isNextNavigationError()` helper to check all navigation errors at once

### 🔧 Technical Changes

- Adopted next-safe-action's robust error detection approach
- Created `/next/errors` module with proper TypeScript types
- Improved compatibility with different Next.js versions

## [0.2.4] - 2025-08-01

### 🚀 New Features

- Added function overloads to `useServerAction` hook for better void action support

### 🐛 Bug Fixes

- Fixed TypeScript type inference for void actions by explicitly checking for `undefined` InputSchema
- Improved TypeScript type inference using function overloads in `useServerAction`
- `useServerAction` now correctly handles both void and input actions with a single API
- Actions without input parameters work seamlessly with proper type inference
- Added proper type guards for void input detection in `createServerAction`
- Fixed runtime type checking with `__hasInputSchema` metadata
- Enhanced middleware execution to properly handle undefined inputs for void actions

### 🔧 Technical Changes

- Added explicit `InputSchema extends undefined` check in type conditionals
- Updated `ServerActionClient.action()` method to accept `(context: Context) => Promise<Data>` when no input schema is defined
- Modified `createServerAction` to conditionally pass parameters based on input schema presence
- Added `hasInputSchema` variable for clearer conditional logic
- Improved type safety for void input actions

### 📝 Usage

The `useServerAction` hook now works correctly for both void and input actions:

```typescript
// Void action - no parameters needed
const logoutAction = actionClient.action(async () => {
  return { message: "Logged out" };
});

const { execute } = useServerAction(logoutAction);
execute(); // Works without parameters!

// Input action - parameters required
const loginAction = actionClient
  .inputSchema(z.object({ email: z.string() }))
  .action(async (input) => {
    return { token: "..." };
  });

const { execute } = useServerAction(loginAction);
execute({ email: "test@test.com" }); // Requires input
```



## [0.2.0] - 2025-01-31

### 🚀 New Features

#### Persistent Toast Messages on Redirect
- **Toast messages now survive page redirects!** Perfect for authentication errors and other redirect scenarios
- Automatically saves error toasts to localStorage before redirect
- Includes `ToastRestorer` component to restore toasts after navigation
- Future-proof: Compatible with upcoming Sonner persistent toast feature
- Zero configuration required - works automatically

### 🔧 Improvements

- Enhanced `useServerAction` hook to handle toast persistence during redirects
- Added automatic cleanup of old toasts (30 seconds expiry)
- Graceful fallback if localStorage is unavailable

### 📦 New Exports

```typescript
import { ToastRestorer } from 'next-action-forge/hooks';
```

## [0.1.0] - 2025-07-31

### 🎉 Initial Release

Next Action Forge is a powerful, type-safe toolkit for Next.js server actions with Zod validation and class-based API design.

### Features

- **Type-safe server actions** with full TypeScript support
- **Zod validation** built-in with perfect type inference
- **Class-based API** with intuitive method chaining via `createActionClient()`
- **React hooks** for seamless client-side integration:
  - `useServerAction` - Execute actions with loading state and callbacks
  - `useOptimisticAction` - Optimistic UI updates
  - `useFormAction` - React Hook Form integration
- **Middleware system** with context propagation
- **Zero config** - works out of the box
- **Custom error handling** with flexible error transformation
- **Duck-typed errors** - Any error with `toServerActionError()` method is automatically handled
- **Smart FormData parsing** - Handles arrays, checkboxes, and files correctly
- **Form actions** with automatic FormData parsing via `.formAction()`
- **Output validation** support via `.outputSchema()`

### Requirements

- Next.js 14.0.0 or higher
- React 18.0.0 or higher
- Zod 4.0.0 or higher