chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)
* 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>
This commit is contained in:
co-authored by
Claude Fable 5.1
Jakob Wennberg
parent
18cbc4c30a
commit
f266c386f3
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* JSON body reader for v1 routes.
|
||||
*
|
||||
* Every v1 write route parses its body the same way: read JSON, and on a
|
||||
* malformed body answer with the VALIDATION_ERROR envelope pointing at
|
||||
* `body`. This helper is that block, so routes do:
|
||||
*
|
||||
* const raw = await readV1JsonBody(request, ctx)
|
||||
* if (!raw.ok) return raw.response
|
||||
* const parsed = Schema.safeParse(raw.body)
|
||||
*/
|
||||
|
||||
import type { NextResponse } from 'next/server'
|
||||
import { v1ErrorResponseFromCode, type V1ValidationContext } from './errors'
|
||||
|
||||
export type V1JsonBodyResult =
|
||||
| { ok: true; body: unknown }
|
||||
| { ok: false; response: NextResponse }
|
||||
|
||||
export async function readV1JsonBody(
|
||||
request: Request,
|
||||
ctx: V1ValidationContext,
|
||||
): Promise<V1JsonBodyResult> {
|
||||
try {
|
||||
return { ok: true, body: await request.json() }
|
||||
} catch {
|
||||
return {
|
||||
ok: false,
|
||||
response: await v1ErrorResponseFromCode('VALIDATION_ERROR', ctx.log, {
|
||||
requestId: ctx.requestId,
|
||||
details: { field: 'body', message: 'Body is not valid JSON.' },
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,32 +47,6 @@ export interface DryRunPreviewBase<T> {
|
||||
preview: T
|
||||
}
|
||||
|
||||
export interface DryRunPreviewStaged<T> extends DryRunPreviewBase<T> {
|
||||
/** `pending_operations.id`. Use with POST /v1/operations/{id}:commit. */
|
||||
staged_operation_id: string
|
||||
/**
|
||||
* Journal lines this write WOULD produce on commit. Absent for
|
||||
* non-financial writes. Each item: `{ account, debit, credit, description? }`.
|
||||
*/
|
||||
journal_lines?: Array<{
|
||||
account: string
|
||||
debit: number
|
||||
credit: number
|
||||
description?: string
|
||||
}>
|
||||
/**
|
||||
* The voucher number that WOULD be assigned on commit. Present only
|
||||
* when the write produces a posted journal entry. Voucher numbers are
|
||||
* sequential, so this is a *projection*: the actual number could differ
|
||||
* by one or two if another committer beat the agent to the next number.
|
||||
*/
|
||||
voucher_number_assigned_on_commit?: string
|
||||
/** Effect on account balances. Absent for non-financial writes. */
|
||||
account_deltas?: Array<{ account: string; delta: number }>
|
||||
}
|
||||
|
||||
export type DryRunPreview<T> = DryRunPreviewBase<T> | DryRunPreviewStaged<T>
|
||||
|
||||
interface DryRunResponseOptions {
|
||||
requestId: string
|
||||
log: Logger
|
||||
@@ -90,37 +64,3 @@ export function dryRunPreview<T>(preview: T, opts: DryRunResponseOptions): NextR
|
||||
opts.log.info('dry-run preview returned', { stage: 'validation-only' })
|
||||
return ok(body, { requestId: opts.requestId, dryRun: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 200 OK dry-run response for a staged preview (financial writes).
|
||||
*
|
||||
* Phase 2 PR-B-1 does not yet exercise this path; the helper is in place so
|
||||
* Phase 2 PR-B-2 (invoice writes) and later phases (journal entries,
|
||||
* year-end, etc.) reuse it without redefining the shape.
|
||||
*/
|
||||
export function dryRunStaged<T>(
|
||||
data: {
|
||||
preview: T
|
||||
stagedOperationId: string
|
||||
journalLines?: DryRunPreviewStaged<T>['journal_lines']
|
||||
voucherNumberAssignedOnCommit?: string
|
||||
accountDeltas?: DryRunPreviewStaged<T>['account_deltas']
|
||||
},
|
||||
opts: DryRunResponseOptions,
|
||||
): NextResponse {
|
||||
const body: DryRunPreviewStaged<T> = {
|
||||
dry_run: true,
|
||||
preview: data.preview,
|
||||
staged_operation_id: data.stagedOperationId,
|
||||
...(data.journalLines ? { journal_lines: data.journalLines } : {}),
|
||||
...(data.voucherNumberAssignedOnCommit
|
||||
? { voucher_number_assigned_on_commit: data.voucherNumberAssignedOnCommit }
|
||||
: {}),
|
||||
...(data.accountDeltas ? { account_deltas: data.accountDeltas } : {}),
|
||||
}
|
||||
opts.log.info('dry-run preview returned', {
|
||||
stage: 'staged',
|
||||
stagedOperationId: data.stagedOperationId,
|
||||
})
|
||||
return ok(body, { requestId: opts.requestId, dryRun: true })
|
||||
}
|
||||
|
||||
+36
-5
@@ -28,7 +28,6 @@ import {
|
||||
errorResponse as legacyErrorResponse,
|
||||
errorResponseFromCode as legacyErrorResponseFromCode,
|
||||
} from '@/lib/errors/get-structured-error'
|
||||
import { getErrorEntry } from '@/lib/errors/structured-errors'
|
||||
import type { Logger } from '@/lib/logger'
|
||||
import { API_V1_VERSION, API_V1_VERSION_HEADER } from './version'
|
||||
|
||||
@@ -177,10 +176,42 @@ export async function v1ErrorResponseFromCode(
|
||||
return rewriteEnvelope(legacy, ctx)
|
||||
}
|
||||
|
||||
/** Minimal slice of the v1 route context the validation helpers need. */
|
||||
export interface V1ValidationContext {
|
||||
requestId: string
|
||||
log: Logger
|
||||
}
|
||||
|
||||
/** Structural view of a ZodError: enough to render the v1 issues list. */
|
||||
export interface V1ZodErrorLike {
|
||||
issues: ReadonlyArray<{ path: ReadonlyArray<PropertyKey>; message: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick check: does this code map to a registered entry? Used by callers that
|
||||
* want to validate a code before throwing it (e.g. registry-driven dispatch).
|
||||
* Map Zod issues into the `{ field, message }` list the v1 envelope carries
|
||||
* under `details.issues`.
|
||||
*/
|
||||
export function isRegisteredV1Code(code: string): boolean {
|
||||
return getErrorEntry(code) !== undefined
|
||||
export function v1ZodIssues(err: V1ZodErrorLike): Array<{ field: string; message: string }> {
|
||||
return err.issues.map((i) => ({
|
||||
field: i.path.join('.'),
|
||||
message: i.message,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* VALIDATION_ERROR envelope for a failed Zod parse:
|
||||
*
|
||||
* const parsed = Schema.safeParse(rawBody)
|
||||
* if (!parsed.success) return v1ValidationError(ctx, parsed.error)
|
||||
*/
|
||||
export async function v1ValidationError(
|
||||
ctx: V1ValidationContext,
|
||||
err: V1ZodErrorLike,
|
||||
): Promise<NextResponse> {
|
||||
return v1ErrorResponseFromCode('VALIDATION_ERROR', ctx.log, {
|
||||
requestId: ctx.requestId,
|
||||
details: {
|
||||
issues: v1ZodIssues(err),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* pagination hints, not security tokens.
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import { UUID_RE as UUID } from '@/lib/invariants/uuid'
|
||||
|
||||
export const DEFAULT_LIMIT = 50
|
||||
export const MAX_LIMIT = 100
|
||||
@@ -47,14 +47,6 @@ export function parsePaginationParams(url: URL): PaginationParams {
|
||||
return { limit, cursor: cursor && cursor.length > 0 ? cursor : null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for query-param validation when a route already uses Zod for inputs.
|
||||
*/
|
||||
export const PaginationQuerySchema = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(MAX_LIMIT).optional(),
|
||||
cursor: z.string().min(1).optional(),
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Default (created_at, id) keyset cursor
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
@@ -82,7 +74,6 @@ export function encodeDefaultCursor(row: { created_at: string; id: string } | nu
|
||||
// PostgREST would likely reject these, but validating here keeps the failure
|
||||
// mode predictable (stale cursor → "start over") rather than 400-ing.
|
||||
const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/
|
||||
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
|
||||
/**
|
||||
* Decode a cursor produced by encodeDefaultCursor. Returns null when the
|
||||
|
||||
@@ -205,9 +205,6 @@ export function listEndpoints(): EndpointDefinition[] {
|
||||
return Array.from(ENDPOINTS.values())
|
||||
}
|
||||
|
||||
export function getEndpoint(method: HttpMethod, path: string): EndpointDefinition | undefined {
|
||||
return ENDPOINTS.get(`${method} ${path}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the registered endpoint for a CONCRETE request path (e.g.
|
||||
@@ -510,10 +507,3 @@ export function generateOpenApiSpec(serverUrl: string): OpenApiSpec {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test-only escape hatch. Clears the registry: used in unit tests so a test
|
||||
* that registers a fake endpoint doesn't leak into the next test.
|
||||
*/
|
||||
export function _resetRegistryForTests(): void {
|
||||
ENDPOINTS.clear()
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import type { Logger } from '@/lib/logger'
|
||||
import { parseReportDateRange, type DateRange } from '@/lib/reports/date-range'
|
||||
import { v1ErrorResponse, v1ErrorResponseFromCode } from './errors'
|
||||
|
||||
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/
|
||||
import { UUID_RE } from '@/lib/invariants/uuid'
|
||||
|
||||
export interface FiscalPeriodRow {
|
||||
id: string
|
||||
|
||||
Reference in New Issue
Block a user