mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-13 19:43:27 +02:00
- export core teams subpath from package.json so team imports resolve - invalidate team caches on realtime team events - share team list query key; filter active teams in select - optimistic update/archive team mutations with rollback - seed create-project default team once, stop fighting deselection - validate team mutation responses against TeamSchema - add malformed-response tests for team schemas - constrain issue/autopilot team pickers to the selected project's teams - fix create-project / quick-create test mocks for the team picker (queryOptions export, quick-create store lastTeamId/setLastTeamId) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
import type { ZodType } from "zod";
|
|
import { type Logger, noopLogger } from "../logger";
|
|
|
|
// Module-level logger for schema warnings. Defaults to no-op so test
|
|
// runs don't spam stderr; the platform layer wires a real logger via
|
|
// `setSchemaLogger` at app boot.
|
|
let schemaLogger: Logger = noopLogger;
|
|
|
|
export function setSchemaLogger(logger: Logger): void {
|
|
schemaLogger = logger;
|
|
}
|
|
|
|
export interface ParseOptions {
|
|
/** Endpoint identifier used in the warning log so we can grep for which
|
|
* contract drifted in production telemetry. */
|
|
endpoint: string;
|
|
}
|
|
|
|
/**
|
|
* Validate a JSON value parsed from an API response against a zod schema,
|
|
* returning the parsed value on success or `fallback` on failure.
|
|
*
|
|
* On failure we log a warning with the endpoint and zod's structured error,
|
|
* but never throw — the UI layer must keep rendering. This is the boundary
|
|
* defense that turns "API contract drifted" from a white-screen incident
|
|
* into a degraded-but-rendering page.
|
|
*
|
|
* The return type is anchored to `T` (inferred from `fallback`), not to the
|
|
* schema's `z.infer` type. Schemas are intentionally **lenient** — string
|
|
* enums kept as `z.string()` so an unknown enum value still parses, etc. —
|
|
* so the parsed runtime value can be wider than the strict TS type at the
|
|
* call site. The caller asserts compatibility by typing the fallback to the
|
|
* expected `T`; downstream code is already responsible for handling unknown
|
|
* enum values via `default`-bearing switches and optional chaining.
|
|
*
|
|
* See CLAUDE.md "API Response Compatibility" for when to reach for this.
|
|
*/
|
|
export function parseWithFallback<T>(
|
|
data: unknown,
|
|
schema: ZodType,
|
|
fallback: T,
|
|
opts: ParseOptions,
|
|
): T {
|
|
const result = schema.safeParse(data);
|
|
if (result.success) return result.data as T;
|
|
schemaLogger.warn(
|
|
`API response failed schema validation: ${opts.endpoint}`,
|
|
{
|
|
endpoint: opts.endpoint,
|
|
issues: result.error.issues,
|
|
received: data,
|
|
},
|
|
);
|
|
return fallback;
|
|
}
|
|
|
|
/**
|
|
* Validate a mutation response against a zod schema, warning (not throwing) on
|
|
* drift. Unlike `parseWithFallback`, there is no synthetic fallback: a write
|
|
* response has no safe empty stand-in — an `id: ""` placeholder would corrupt
|
|
* the caches these responses feed. On drift we log via the same channel as
|
|
* `parseWithFallback` and return the raw value cast to `T`; the caller's
|
|
* optimistic cache patch plus `onSettled` invalidation remain the authoritative
|
|
* safety net.
|
|
*
|
|
* See CLAUDE.md "API Compatibility" for when to reach for this over
|
|
* `parseWithFallback`.
|
|
*/
|
|
export function parseOrWarn<T>(
|
|
data: unknown,
|
|
schema: ZodType,
|
|
opts: ParseOptions,
|
|
): T {
|
|
const result = schema.safeParse(data);
|
|
if (result.success) return result.data as T;
|
|
schemaLogger.warn(
|
|
`API response failed schema validation: ${opts.endpoint}`,
|
|
{
|
|
endpoint: opts.endpoint,
|
|
issues: result.error.issues,
|
|
received: data,
|
|
},
|
|
);
|
|
return data as T;
|
|
}
|