f266c386f3
* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n namespaces and 4 unused dependencies; fold byte-identical helper copies into one canonical home each (lib/utils chunk/sleep/utcDateStamp, lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format, lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body + v1ValidationError rolled out to ~55 v1 routes, booking-template schemas). No behaviour change: v1 bodies and status codes, MCP tool schemas, DB writes and money math are untouched. Naive ore rounding was deliberately not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list of things left alone on purpose. tsc, lint, 19588 unit tests and check:guards green; antipattern baseline ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(transactions): import RawTransaction from @/types after the ingest re-export removal CI's type ratchet (check:types, full tsconfig) caught the one test file that still imported the type through lib/transactions/ingest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
67 lines
3.0 KiB
TypeScript
67 lines
3.0 KiB
TypeScript
/**
|
|
* Dry-run response helpers for v1 write endpoints.
|
|
*
|
|
* Architectural contract (per the v1 plan):
|
|
*
|
|
* 1. Every POST / PATCH / DELETE accepts `?dry_run=true` or `X-Dry-Run: true`.
|
|
* 2. A dry-run response returns 200 OK with `{ data: { dry_run: true, preview, ... } }`
|
|
* and the `X-Dry-Run: true` response header: NEVER the resource's
|
|
* normal success status (201, 204, etc.). A caller that sees `200`
|
|
* with `X-Dry-Run` knows the write was NOT committed.
|
|
* 3. Commit by re-issuing the same request without `dry_run=true`, passing
|
|
* the same `Idempotency-Key` to guarantee at-most-once semantics. The
|
|
* wrapper keeps the two apart: a dry-run response is never written to the
|
|
* idempotency cache, and the dry-run flag is folded into the request hash,
|
|
* so the commit executes for real instead of replaying the preview.
|
|
* Order matters. Once a key has committed for real, re-issuing it WITH
|
|
* `dry_run=true` is rejected as key reuse (409 IDEMPOTENCY_KEY_REUSE)
|
|
* rather than answered with the committed result dressed up as a preview.
|
|
*
|
|
* Two preview shapes are supported:
|
|
*
|
|
* - **Validation-only** (non-financial resources like customers): the
|
|
* preview is the would-be record. No staging, no `pending_operations`
|
|
* row, no journal lines. Useful for validating inputs and discovering
|
|
* conflicts (duplicate org_number, validation errors) before committing.
|
|
*
|
|
* - **Staged** (financial resources: invoices, journal entries, period
|
|
* ops, salary; later phases): the preview is the record PLUS a
|
|
* `staged_operation_id` from `pending_operations`, the `journal_lines`
|
|
* that would be posted, and the `voucher_number_assigned_on_commit`.
|
|
* Committing happens either by re-POSTing or via
|
|
* `POST /v1/operations/{staged_operation_id}:commit`.
|
|
*
|
|
* This file ships the helpers for both modes. Phase 2 PR-B-1 only uses the
|
|
* validation-only path (customers); the staged path is wired but not
|
|
* exercised until invoice writes land in PR-B-2.
|
|
*/
|
|
|
|
import { NextResponse } from 'next/server'
|
|
import type { Logger } from '@/lib/logger'
|
|
import { ok } from './response'
|
|
|
|
export interface DryRunPreviewBase<T> {
|
|
/** Always `true` so agents can dispatch on this without parsing headers. */
|
|
dry_run: true
|
|
/** The would-be resource. Same shape as the success response. */
|
|
preview: T
|
|
}
|
|
|
|
interface DryRunResponseOptions {
|
|
requestId: string
|
|
log: Logger
|
|
}
|
|
|
|
/**
|
|
* Return a 200 OK dry-run response for a validation-only preview.
|
|
*
|
|
* Use for non-financial writes (customers, suppliers metadata, employee
|
|
* profiles, settings) where there's nothing to stage: the agent just wants
|
|
* to know what would be written and whether validation passes.
|
|
*/
|
|
export function dryRunPreview<T>(preview: T, opts: DryRunResponseOptions): NextResponse {
|
|
const body: DryRunPreviewBase<T> = { dry_run: true, preview }
|
|
opts.log.info('dry-run preview returned', { stage: 'validation-only' })
|
|
return ok(body, { requestId: opts.requestId, dryRun: true })
|
|
}
|