import { z } from "zod"; /** * Offline sync envelope (ARCHITEKTUR §4.6). The per-op payload schemas live in * src/lib/sync/ops.ts (owned by lane field); this file only fixes the wire format. */ export const SYNC_OP_TYPES = [ "session.start", "session.pause", "session.resume", "session.end", "work_order.transition", "note.create", "checklist.toggle", "material.upsert", "photo.attach", "voice.attach", "report.save_draft", "report.submit", "signature.capture", "emergency.create", // L12 Zeiterfassung (additive, conflict-free) "session.stop_day", "session.segment", "time.add_manual", "time.propose_correction", // L14 Abrechnungsübersicht (additive, conflict-free; registered in services/sync/external-ops.ts) "milestone.reach", ] as const; export type SyncOpType = (typeof SYNC_OP_TYPES)[number]; /** Ops that compare WorkOrder.version and may produce a conflict. All others are additive. */ export const CONFLICTING_OPS: readonly SyncOpType[] = ["work_order.transition", "report.submit"]; export const syncOperationSchema = z.object({ clientOpId: z.string().uuid(), opType: z.enum(SYNC_OP_TYPES), entityType: z.string().max(40).optional(), entityId: z.string().max(64).optional(), baseVersion: z.number().int().positive().optional(), payload: z.record(z.string(), z.unknown()), clientCreatedAt: z.string().datetime(), }); export type SyncOperationInput = z.infer; export const syncRequestSchema = z.object({ deviceId: z.string().max(64), operations: z.array(syncOperationSchema).min(1).max(100), }); export type SyncOpResult = { clientOpId: string; status: "applied" | "duplicate" | "conflict" | "rejected"; /** server ids created by the op, keyed by the client id they replace */ idMap?: Record; entityVersion?: number; errorCode?: "not_found" | "forbidden" | "invalid" | "conflict" | "blocked" | "internal"; message?: string; }; export type SyncResponse = { results: SyncOpResult[]; serverTime: string };