All files / src/lib decision-batch-schema.ts

100% Statements 5/5
100% Branches 0/0
100% Functions 0/0
100% Lines 5/5

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54            55x         55x         55x                                                 55x                     55x  
// Shared client/server batch schema (spec §2.1) — no server-only imports. The
// client pre-validates with this exact module, so the two sides can never
// drift. Regexes are the spec's, verbatim — lowercase Crockford base32 ULIDs,
// items carrying the repository's `item_` prefix.
import { z } from "zod";
 
export const ClientKey = z
  .string()
  .length(26)
  .regex(/^[0-9a-hjkmnp-tv-z]{26}$/);
 
export const ItemId = z
  .string()
  .max(64)
  .regex(/^item_[0-9a-hjkmnp-tv-z]{26}$/);
 
export const BatchEntrySchema = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("triage"),
    decision: z.object({
      itemId: ItemId,
      result: z.enum(["top", "high", "medium", "low"]),
    }),
    clientKey: ClientKey,
  }),
  z.object({
    type: z.literal("pairwise_compare"),
    decision: z.object({
      itemAId: ItemId,
      itemBId: ItemId,
      result: z.enum(["item_a", "item_b", "tie", "skip"]),
    }),
    clientKey: ClientKey,
  }),
  z.object({
    type: z.literal("usage"),
    decision: z.object({ itemId: ItemId, result: z.enum(["uses", "not_uses"]) }),
    clientKey: ClientKey,
  }),
]);
 
export const BatchRequestSchema = z.object({
  decisions: z.array(BatchEntrySchema).min(1).max(50),
});
 
export type BatchEntry = z.infer<typeof BatchEntrySchema>;
export type BatchResultStatus = "created" | "duplicate" | "rejected";
export interface BatchResult {
  clientKey: string;
  status: BatchResultStatus;
  reason?: "unknown_item";
}
export const MAX_BATCH_BYTES = 65_536;