feat: warm-up deps — ts-reset, web-vitals, RHF devtool, query-broadcast
Four low-risk adds before the Zod 4 / drizzle-zod headliner: - @total-typescript/ts-reset: tightens TS stdlib types globally (JSON.parse → unknown, fetch().json() → unknown, .filter(Boolean) narrows, Set literals respect typed Set targets). Caught 179 latent type errors; fixed all production sites (8 files) and added `any` cast escape hatch in test files (ESLint exemption scoped to tests/). - web-vitals + /api/v1/internal/vitals endpoint + WebVitalsReporter client component: establishes Core Web Vitals baseline (LCP/INP/CLS/ FCP/TTFB) via navigator.sendBeacon. Required before optimisation work. - @hookform/devtools + FormDevtool wrapper: dev-only RHF state inspector, lazy-loaded via next/dynamic so the chunk is excluded from prod bundles entirely. - @tanstack/query-broadcast-client-experimental: cross-tab cache sync via BroadcastChannel — wired in query-provider.tsx, 1-liner. Audit doc updated with sections 35 + 36 (PDF stack overhaul + comprehensive second-pass package sweep) covering ~20 package adoption candidates and 4-5 deprecation candidates. Verified: tsc clean, vitest 1293/1293 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,7 +60,7 @@ describe('Error response security — AppError subclasses', () => {
|
||||
const error = new AppError(400, 'Bad request', 'BAD_REQUEST');
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(400);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Bad request');
|
||||
expect(body.code).toBe('BAD_REQUEST');
|
||||
// Stack trace must never appear in the response body
|
||||
@@ -72,7 +72,7 @@ describe('Error response security — AppError subclasses', () => {
|
||||
const error = new NotFoundError('Client');
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(404);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
// Message is now plain-text user-facing (no jargon, lowercased entity).
|
||||
expect(body.error).toBe("We couldn't find that client. It may have been removed.");
|
||||
expect(body.code).toBe('NOT_FOUND');
|
||||
@@ -83,7 +83,7 @@ describe('Error response security — AppError subclasses', () => {
|
||||
const error = new ForbiddenError();
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(403);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.code).toBe('FORBIDDEN');
|
||||
});
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('Error response security — AppError subclasses', () => {
|
||||
const error = new RateLimitError(60);
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(429);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.retryAfter).toBe(60);
|
||||
expect(JSON.stringify(body)).not.toMatch(/stack|node_modules/i);
|
||||
});
|
||||
@@ -102,7 +102,7 @@ describe('Error response security — AppError subclasses', () => {
|
||||
]);
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(400);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.details).toHaveLength(1);
|
||||
expect(body.details[0].field).toBe('email');
|
||||
expect(JSON.stringify(body)).not.toContain('src/');
|
||||
@@ -115,7 +115,7 @@ describe('Error response security — unknown / native errors', () => {
|
||||
const error = new Error('SELECT * FROM users WHERE id = 1; DROP TABLE users;--');
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
expect(JSON.stringify(body)).not.toContain('SELECT');
|
||||
expect(JSON.stringify(body)).not.toContain('DROP TABLE');
|
||||
@@ -127,7 +127,7 @@ describe('Error response security — unknown / native errors', () => {
|
||||
);
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
expect(JSON.stringify(body)).not.toContain('G:\\');
|
||||
expect(JSON.stringify(body)).not.toContain('src\\lib');
|
||||
@@ -137,7 +137,7 @@ describe('Error response security — unknown / native errors', () => {
|
||||
const error = new Error('ENOENT: no such file at /app/node_modules/pg/lib/connection.js');
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
expect(JSON.stringify(body)).not.toContain('node_modules');
|
||||
expect(JSON.stringify(body)).not.toContain('ENOENT');
|
||||
@@ -147,7 +147,7 @@ describe('Error response security — unknown / native errors', () => {
|
||||
const error = new Error('relation "users" does not exist');
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
expect(JSON.stringify(body)).not.toContain('relation');
|
||||
expect(JSON.stringify(body)).not.toContain('"users"');
|
||||
@@ -156,14 +156,14 @@ describe('Error response security — unknown / native errors', () => {
|
||||
it('null thrown value returns generic 500', async () => {
|
||||
const response = errorResponse(null);
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
});
|
||||
|
||||
it('string thrown returns generic 500', async () => {
|
||||
const response = errorResponse('something went wrong internally');
|
||||
expect(response.status).toBe(500);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.error).toBe('Internal server error');
|
||||
// The raw string must not appear in the response
|
||||
expect(JSON.stringify(body)).not.toContain('something went wrong internally');
|
||||
@@ -184,7 +184,7 @@ describe('Error response security — ZodError', () => {
|
||||
]);
|
||||
const response = errorResponse(error);
|
||||
expect(response.status).toBe(400);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
expect(body.code).toBe('VALIDATION_ERROR');
|
||||
expect(body.details).toBeDefined();
|
||||
expect(Array.isArray(body.details)).toBe(true);
|
||||
@@ -203,7 +203,7 @@ describe('Error response security — ZodError', () => {
|
||||
},
|
||||
]);
|
||||
const response = errorResponse(error);
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
const bodyStr = JSON.stringify(body);
|
||||
expect(bodyStr).not.toContain('src/');
|
||||
expect(bodyStr).not.toContain('node_modules');
|
||||
@@ -222,7 +222,7 @@ describe('Error response security — response shape invariants', () => {
|
||||
new RateLimitError(30),
|
||||
];
|
||||
for (const err of errors) {
|
||||
const body = await errorResponse(err).json();
|
||||
const body = (await errorResponse(err).json()) as any;
|
||||
expect(typeof body.error).toBe('string');
|
||||
expect(body.error.length).toBeGreaterThan(0);
|
||||
// Stack must never appear
|
||||
@@ -232,7 +232,7 @@ describe('Error response security — response shape invariants', () => {
|
||||
|
||||
it('500 response body carries error + code (and requestId when in-flight)', async () => {
|
||||
const response = errorResponse(new Error('db connection refused'));
|
||||
const body = await response.json();
|
||||
const body = (await response.json()) as any;
|
||||
// Allowed keys for a 500 response. `code` is always present; `requestId`
|
||||
// and `message` only appear when an active request context is in scope.
|
||||
const allowed = new Set(['error', 'code', 'requestId', 'message']);
|
||||
|
||||
Reference in New Issue
Block a user