/** * cli:scaffold-api-client — validate.ts */ import { normalizeOffline } from '../../../../../lib/pwa-meta.js' import { ScaffoldApiClientInputSchema, type ScaffoldApiClientInput, type ValidationResult } from './types.js' /** * Offline-write coherence — BLOCKING. A 'write' entity without `versioned` * ships silent last-write-wins (no 409s); nested/screen-mode entities have no * outbox URL-match convention, so refusing beats emitting a spec that never * matches (queued mutations would replay against the wrong path). */ function checkOfflineWrite(spec: ScaffoldApiClientInput): string[] { const errors: string[] = [] const isScreenMode = spec.useScreens || spec.routeMode === 'screens' for (const entity of spec.entities) { if (normalizeOffline(entity.pwa?.offline) !== 'write') continue if (!entity.versioned) { errors.push( `[entities.${entity.name}] offline: 'write' requires versioned: true — without the IVersionedEntity ` + `rowversion the backend cannot 409 a stale queued edit (silent last-write-wins). Scaffold the entity ` + `with versioned: true (scaffold-entity + scaffold-business) and set it here.`, ) } if (entity.parentPath) { errors.push( `[entities.${entity.name}] offline: 'write' is incompatible with parentPath — the outbox match ` + `convention only covers flat integration URLs in v1. Drop one of the two.`, ) } if (isScreenMode) { errors.push( `[entities.${entity.name}] offline: 'write' is incompatible with the screens route mode — outbox ` + `specs match the integration stratum URLs (/api/{module}/{section}). Use routeMode: 'integration'.`, ) } } return errors } export function validate(raw: unknown): ValidationResult { const result = ScaffoldApiClientInputSchema.safeParse(raw) if (!result.success) { return { valid: false, errors: result.error.issues.map(i => `[${i.path.join('.')}] ${i.message}`), warnings: [] } } const errors = checkOfflineWrite(result.data) if (errors.length > 0) return { valid: false, errors, warnings: [] } return { valid: true, errors: [], warnings: [] } }