import { z } from "zod"; import { BLOCK_TYPES } from "../../constants.js"; /** * DTOs for the internal block endpoints (TASK-0092 slice 2). * * The realtime gateway hydrates a cold room and persists (flushes) a CRDT * doc against these endpoints. They are the **internal twin** of the public * block endpoints: same tables, service-token auth, no permission re-check * (the room's join matrix already gated the edits). * * Block shape = the codec/DTO convention (PRODUCT-SPEC §4): lowercase * snake_case `type`, fractional `position`, JSON `content`. The DB enum is * SCREAMING_SNAKE; the translation happens in `internal.service.ts` (the * one boundary where the codec doc meets Postgres — same resolution as the * realtime `PrismaDocStore` seam, recorded in the phase-07 task file). */ /** The codec block types (lowercase) — the DB `BLOCK_TYPES` case-folded. */ const CODEC_BLOCK_TYPES = BLOCK_TYPES.map((t) => t.toLowerCase()) as [string, ...string[]]; export const InternalBlockDto = z.object({ id: z.string().min(1), parentId: z.string().min(1).nullable().optional(), type: z.enum(CODEC_BLOCK_TYPES), position: z.string().min(1), content: z.record(z.unknown()).default({}), properties: z.record(z.unknown()).optional() }); export type InternalBlock = z.infer; /** * `GET /internal/blocks/:pageId` query. Cursor = the `position` of the last * block of the previous page (exclusive lower bound) — position-ordered * paging for the 100k-block hydration (doc §4.2, streamed per page). */ export const InternalReadQuery = z.object({ cursor: z.string().min(1).optional(), pageSize: z.coerce.number().int().min(1).max(500).optional() }); export type InternalReadQueryInput = z.infer; /** * `POST /internal/blocks/flush` body. The full block set of the page (the * CRDT doc re-encoded), persisted under the `pages.version` CAS: * `baseVersion` must equal the current version or the flush is a no-op and * the response carries `conflict.externalBlocks` (the concurrent writer's * state) for the room to rebase on (doc §4.3, the "flush wins" rule). */ export const InternalFlushDto = z.object({ pageId: z.string().min(1), baseVersion: z.number().int().min(1), blocks: z.array(InternalBlockDto).max(5000) }); export type InternalFlushInput = z.infer;