@synap-core/sdk 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +212 -12
  3. package/dist/{chunk-J7XHAAFJ.js → chunk-UK2U6SMA.js} +14 -2
  4. package/dist/chunk-UK2U6SMA.js.map +1 -0
  5. package/dist/{client-CVUT1Mcf.d.cts → client-B50KC2aJ.d.cts} +14 -9
  6. package/dist/{client-CVUT1Mcf.d.ts → client-B50KC2aJ.d.ts} +14 -9
  7. package/dist/connect.cjs +790 -161
  8. package/dist/connect.cjs.map +1 -1
  9. package/dist/connect.d.cts +610 -37
  10. package/dist/connect.d.ts +610 -37
  11. package/dist/connect.js +777 -162
  12. package/dist/connect.js.map +1 -1
  13. package/dist/public.cjs +9 -2
  14. package/dist/public.cjs.map +1 -1
  15. package/dist/public.d.cts +19 -4
  16. package/dist/public.d.ts +19 -4
  17. package/dist/public.js +8 -2
  18. package/dist/public.js.map +1 -1
  19. package/dist/testing.cjs +366 -17
  20. package/dist/testing.cjs.map +1 -1
  21. package/dist/testing.d.cts +190 -7
  22. package/dist/testing.d.ts +190 -7
  23. package/dist/testing.js +358 -17
  24. package/dist/testing.js.map +1 -1
  25. package/dist/{types-DpqPNaaa.d.cts → types-DZYNBCq7.d.cts} +47 -5
  26. package/dist/{types-DpqPNaaa.d.ts → types-DZYNBCq7.d.ts} +47 -5
  27. package/package.json +36 -12
  28. package/src/connect/auth.ts +314 -55
  29. package/src/connect/client.ts +472 -26
  30. package/src/connect/cloud.ts +210 -0
  31. package/src/connect/discovery.ts +79 -44
  32. package/src/connect/encoding.ts +1 -1
  33. package/src/connect/errors.ts +8 -1
  34. package/src/connect/flow.ts +80 -12
  35. package/src/connect/http.ts +42 -0
  36. package/src/connect/hub-types.ts +254 -0
  37. package/src/connect/index.ts +69 -23
  38. package/src/connect/rules.ts +18 -0
  39. package/src/connect/seal.ts +1 -1
  40. package/src/connect/store.ts +50 -8
  41. package/src/connect/types.ts +48 -4
  42. package/src/public/client.ts +14 -2
  43. package/src/public/safe-markdown.ts +13 -0
  44. package/src/public/share-model.ts +6 -2
  45. package/src/public/site-model.ts +5 -3
  46. package/src/testing/auth-server.ts +377 -20
  47. package/src/testing/cloud.ts +83 -0
  48. package/src/testing/fixtures.ts +26 -0
  49. package/src/testing/index.ts +10 -6
  50. package/src/testing/seed.ts +120 -0
  51. package/dist/chunk-J7XHAAFJ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/public/client.ts","../src/public/form-model.ts","../src/public/safe-markdown.ts","../src/public/site-model.ts","../src/public/share-model.ts"],"sourcesContent":["/**\n * @synap-core/sdk/public — a thin, CREDENTIALLESS fetch client for the pod's\n * public doors (Sites W3/W4, collection door C1a/C1b):\n *\n * GET <pod>/api/hub/public/shares/:token read a published share\n * GET <pod>/api/hub/public/forms/:token a form's public definition + ticket\n * POST <pod>/api/hub/public/forms/:token a guest submission (202 / 422 / 503)\n * GET <pod>/api/hub/public/sites/:token a site manifest\n * GET <pod>/api/hub/public/sites/:token/collections/:key a page of records\n * GET <pod>/api/hub/public/sites/:token/collections/:key/:slugOrPublicId a record\n *\n * Invariants (pinned by src/__tests__/public.guard.test.ts):\n * - every request is `credentials: 'omit'`; no Authorization / Cookie / session\n * header is ever set (only `Accept`, `If-None-Match` on a site revalidation,\n * and `Content-Type` on POST);\n * - imports NOTHING (no tRPC, superjson, zod, authenticated client);\n * - the uniform 404 is one typed `not_found` (revoked / expired / unknown are\n * indistinguishable by design); the constant 202 is `{ received: true }`;\n * - a submit has six typed non-success outcomes (`invalid` with the field keys,\n * `retry`, `unavailable`, `rate_limited`, `network`, `server`); a 422 `retry`\n * (ticket missing / too early / stale) is retried ONCE automatically: the\n * form is fetched again, `minSubmitMs` is waited out (capped) and the same\n * submission (same idempotency key) is resent;\n * - server detail (bodies, messages) never reaches the caller.\n *\n * DRIFT RISK: the response types below are HAND-WRITTEN from the backend wire\n * contracts (synap-backend: `_codecs/public-shares.ts` PublicShareResponseSchema,\n * `services/forms/form-definition.ts` publicFormView + the public-forms route,\n * `SubmissionEnvelopeSchema`, `_codecs/public-sites.ts` + the bounds in\n * `services/sharing/public-site-read.ts` parseCollectionQuery).\n * `src/__tests__/fixtures.ts` copies those shapes; when the backend changes,\n * update both.\n */\n\n// ── Wire types (hand-written; see DRIFT RISK) ────────────────────────────────\n\nexport interface PublicShare {\n resourceType: \"entity\";\n /** Present only when the publisher allowlisted `title`. */\n title?: string;\n properties: Record<string, string | number | boolean>;\n body: { format: \"markdown\"; content: string } | null;\n /** Publication day, YYYY-MM-DD. */\n publishedOn: string;\n}\n\nexport type PublicFormFieldType =\n | \"text\"\n | \"richtext\"\n | \"email\"\n | \"phone\"\n | \"url\"\n | \"number\"\n | \"boolean\"\n | \"date\"\n | \"enum\";\n\nexport interface PublicFormField {\n key: string;\n label: string;\n type: PublicFormFieldType;\n required?: true;\n help?: string;\n constraints?: {\n enum?: string[];\n min?: number;\n max?: number;\n maxLength?: number;\n };\n}\n\nexport interface PublicForm {\n title: string;\n fields: PublicFormField[];\n successMessage: string;\n /** The envelope key of the honeypot (always \"hp\" today). */\n honeypotField: string;\n /** Time-to-submit ticket, minted by this GET; pass it back to `submitForm`. */\n ticket: string;\n /**\n * The POST is refused with `retry` until this many ms have passed since the\n * ticket was minted. Hold the submit button until then. 0 = no wait.\n */\n minSubmitMs: number;\n captcha: { required: true; siteKey: string | null } | { required: false };\n}\n\n/** A published value: a publish-time snapshot, scalars only (C1b). */\nexport type PublicValue = string | number | boolean;\n\nexport interface PublicSiteCollection {\n /** The collection key (a profile slug); pass it to `listCollection`. */\n key: string;\n /** Live published records in it. */\n count: number;\n /** The default sort keys (`-` = descending). */\n sort: string[];\n /** The default page size. */\n pageSize: number;\n /** How a record of this collection is addressed in URLs. */\n addressing: \"slug\" | \"publicId\";\n}\n\n/** A published site's manifest. */\nexport interface PublicSite {\n /** Present only when the owner's policy publishes `title`. */\n title?: string;\n /** Present only when the owner's policy publishes `description`. */\n description?: string;\n /** Day the site went live, YYYY-MM-DD. */\n publishedOn: string;\n collections: PublicSiteCollection[];\n}\n\n/** One published record as a list item (no body). */\nexport interface PublicRecordSummary {\n collection: string;\n /** The stable public address: `p_` + 16 random base62 chars. */\n publicId: string;\n /** The record's slug in its collection, or null. */\n slug: string | null;\n /** Present only when the owner's policy publishes `title`. */\n title?: string;\n properties: Record<string, PublicValue>;\n /** Publication day, YYYY-MM-DD. */\n publishedOn: string;\n}\n\nexport interface PublicCollectionPage {\n items: PublicRecordSummary[];\n /** Live published records in the collection. */\n total: number;\n offset: number;\n limit: number;\n}\n\n/** One published record, with its pinned body. */\nexport interface PublicRecord extends PublicRecordSummary {\n body: { format: \"markdown\"; content: string } | null;\n}\n\n/** The collection door's query bounds (backend `parseCollectionQuery`). */\nexport const SITE_MAX_OFFSET = 10_000;\nexport const SITE_MAX_LIMIT = 100;\nexport const SITE_MAX_SORT_KEYS = 3;\n/** The sort key every collection accepts, besides its configured ones. */\nexport const SITE_PUBLISHED_AT_SORT = \"publishedAt\";\n\n// ── Results ──────────────────────────────────────────────────────────────────\n\n/** `not_found` is the uniform 404 (never says revoked vs expired vs unknown). */\nexport type PublicReadError =\n | \"not_found\"\n | \"rate_limited\"\n /** The owner has not turned public pages/forms on for this workspace. */\n | \"doors_disabled\"\n | \"network\"\n | \"server\";\n/** A submit never yields `not_found`: the door answers 202 to everything. */\nexport type PublicSubmitError =\n | \"invalid\"\n /** The ticket was refused, even after the one automatic retry. */\n | \"retry\"\n /** 503: the pod failed to file it; resubmitting with the same key is safe. */\n | \"unavailable\"\n | \"rate_limited\"\n /** The owner has not turned public pages/forms on for this workspace. */\n | \"doors_disabled\"\n | \"network\"\n | \"server\";\n\n/**\n * A site read's failure. `not_found` is the uniform 404 (an unknown, revoked\n * or unpublished site, collection or record are indistinguishable).\n */\nexport type PublicSiteError =\n | \"not_found\"\n | \"rate_limited\"\n /** The site is live but the owner switched its space's public pages off. */\n | \"doors_disabled\"\n /** 400: the query is out of bounds or names a sort key the collection does not offer. */\n | \"bad_request\"\n /** No answer (network, DNS, CORS) or a gateway 502/503/504. */\n | \"pod_unreachable\"\n | \"server\";\n\n/**\n * A failure. `rate_limited` carries `retryAfterSec` only when the response\n * had a `Retry-After` header the caller can read (see README: the pod's public\n * doors do not send one today, and a browser cannot read it cross-origin).\n */\nexport type PublicFailure<E extends string> = E extends \"rate_limited\"\n ? { ok: false; error: E; retryAfterSec?: number }\n : { ok: false; error: E };\n\nexport type PublicResult<T, E extends string = PublicReadError> =\n | { ok: true; data: T }\n | PublicFailure<E>;\n\n/** A site read. `etag` is the door's weak validator, for `ifNoneMatch`. */\nexport type PublicSiteResult<T> =\n | { ok: true; data: T; etag: string | null }\n | PublicFailure<PublicSiteError>;\n\n/** 304: what the caller holds for `ifNoneMatch` is still current. */\nexport interface PublicNotModified {\n ok: true;\n notModified: true;\n etag: string;\n}\n\n/**\n * Revalidate with the ETag of an earlier answer. SERVER-SIDE ONLY: the public\n * doors allow no request header but `Content-Type` cross-origin, so a browser\n * fails the preflight (→ `pod_unreachable`). Browsers revalidate on their own\n * through their HTTP cache (`Cache-Control: no-cache` + ETag).\n */\nexport interface Revalidate {\n ifNoneMatch: string;\n}\n\nexport interface CollectionQuery {\n /** 0..10 000 (default 0). Anything else is `bad_request`, without a request. */\n offset?: number;\n /** An integer, clamped to 1..100 (default: the collection's page size). */\n limit?: number;\n /**\n * ≤ 3 keys, `-` = descending, out of the collection's `sort` keys and\n * `publishedAt` (default: the collection's sort). A string is comma-split.\n */\n sort?: string | readonly string[];\n}\n\nexport type SubmitResult =\n | { ok: true; received: true }\n /** 422: the answers do not satisfy the form. `fields` are field keys; empty = not a submission. */\n | { ok: false; error: \"invalid\"; fields: string[] }\n | PublicFailure<Exclude<PublicSubmitError, \"invalid\">>;\n\nexport interface SubmitFormInput {\n /** Answers keyed by the form's field `key`s. */\n fields: Record<string, unknown>;\n /** The `ticket` from `getForm` (waiting out the form's min time is the caller's job). */\n ticket?: string;\n /** Turnstile-style token, when `captcha.required`. */\n captchaToken?: string;\n /** Optional replay key; a repeated key collapses onto one submission. */\n idempotencyKey?: string;\n}\n\nexport interface PublicClientOptions {\n /** Pod origin, e.g. `https://you.synap.live` (a trailing slash is ignored). */\n podUrl: string;\n /** Injectable fetch; defaults to the global one. */\n fetch?: typeof fetch;\n /** Injectable wait (tests); defaults to `setTimeout`. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface PublicClient {\n readShare(token: string): Promise<PublicResult<PublicShare>>;\n getForm(token: string): Promise<PublicResult<PublicForm>>;\n submitForm(token: string, input: SubmitFormInput): Promise<SubmitResult>;\n\n /** A published site's manifest: its metadata and configured collections. */\n getSite(siteToken: string): Promise<PublicSiteResult<PublicSite>>;\n getSite(\n siteToken: string,\n opts: Revalidate\n ): Promise<PublicSiteResult<PublicSite> | PublicNotModified>;\n\n /** One page of a collection's live published records (no bodies). */\n listCollection(\n siteToken: string,\n key: string,\n query?: CollectionQuery\n ): Promise<PublicSiteResult<PublicCollectionPage>>;\n listCollection(\n siteToken: string,\n key: string,\n query: CollectionQuery & Revalidate\n ): Promise<PublicSiteResult<PublicCollectionPage> | PublicNotModified>;\n\n /** One record by its public id (`p_…`) or its slug, with its pinned body. */\n getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string\n ): Promise<PublicSiteResult<PublicRecord>>;\n getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string,\n opts: Revalidate\n ): Promise<PublicSiteResult<PublicRecord> | PublicNotModified>;\n}\n\n/** The honeypot envelope key the door reads. Always sent empty. */\nconst HONEYPOT_KEY = \"hp\";\n/** Longest wait the automatic ticket retry will sit out, whatever the pod says. */\nconst MAX_TICKET_WAIT_MS = 10_000;\nconst MAX_INVALID_KEYS = 100;\nconst MAX_KEY_LENGTH = 200;\n\nfunction waitMs(form: unknown): number {\n const n = (form as { minSubmitMs?: unknown } | null)?.minSubmitMs;\n return typeof n === \"number\" && Number.isFinite(n) && n > 0\n ? Math.min(n, MAX_TICKET_WAIT_MS)\n : 0;\n}\n\n/** A 422 body -> outcome. Only field keys survive; anything else is dropped. */\nfunction parse422(body: unknown): { retry: true } | { fields: string[] } | null {\n if (typeof body !== \"object\" || body === null) return null;\n const b = body as { retry?: unknown; invalid?: unknown };\n if (b.retry === true) return { retry: true };\n if (!Array.isArray(b.invalid)) return null;\n return {\n fields: b.invalid\n .filter((k): k is string => typeof k === \"string\" && k.length <= MAX_KEY_LENGTH)\n .slice(0, MAX_INVALID_KEYS),\n };\n}\n\n/** The one refusal body's machine code (see `PUBLIC_DOORS_DISABLED_BODY` on the pod). */\nconst DOORS_DISABLED_CODE = \"public_doors_disabled\";\n\nfunction isDoorsDisabledBody(body: unknown): boolean {\n return (\n typeof body === \"object\" &&\n body !== null &&\n (body as { code?: unknown }).code === DOORS_DISABLED_CODE\n );\n}\n\n/** A non-200 answer of a read door (no answer at all is the caller's `network`). */\nfunction classify(status: number, body: unknown): Exclude<PublicReadError, \"network\"> {\n if (status === 404) return \"not_found\";\n if (status === 429) return \"rate_limited\";\n if (status === 403 && isDoorsDisabledBody(body)) return \"doors_disabled\";\n return \"server\";\n}\n\nfunction classifySite(status: number, body: unknown): PublicSiteError {\n if (status === 400) return \"bad_request\";\n if (status === 502 || status === 503 || status === 504) return \"pod_unreachable\";\n return classify(status, body);\n}\n\n/** Longest `Retry-After` passed on, whatever the response says. */\nconst MAX_RETRY_AFTER_SEC = 86_400;\n\n/** `Retry-After` (delta-seconds or an HTTP date) in whole seconds, else undefined. */\nfunction retryAfterSec(res: Response): number | undefined {\n const raw = res.headers.get(\"retry-after\")?.trim();\n if (!raw) return undefined;\n let sec: number;\n if (/^\\d+$/.test(raw)) {\n sec = Number(raw);\n } else {\n const at = Date.parse(raw);\n if (Number.isNaN(at)) return undefined;\n sec = Math.ceil((at - Date.now()) / 1000);\n }\n return Math.min(Math.max(0, sec), MAX_RETRY_AFTER_SEC);\n}\n\n/** A failure; `rate_limited` picks up the response's `Retry-After`, if any. */\nfunction fail<E extends string>(error: E, res: Response | null): PublicFailure<E> {\n const sec = error === \"rate_limited\" && res ? retryAfterSec(res) : undefined;\n return (sec === undefined ? { ok: false, error } : { ok: false, error, retryAfterSec: sec }) as PublicFailure<E>;\n}\n\nasync function readBody(res: Response): Promise<unknown> {\n try {\n return await res.json();\n } catch {\n return null;\n }\n}\n\n/**\n * One URL path segment, or null when it could not stay one: empty, `.` and\n * `..` would be resolved away by the URL parser and reach ANOTHER door (e.g.\n * collection `..` is the manifest), typed as the wrong shape.\n */\nfunction segment(raw: string): string | null {\n if (typeof raw !== \"string\" || raw === \"\" || raw === \".\" || raw === \"..\") return null;\n return encodeURIComponent(raw);\n}\n\n/** A sort term as the door parses it (`-` = descending). */\nconst SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;\n\n/**\n * The collection query string, checked against the door's static bounds so a\n * request that can only be a 400 is never sent. null = `bad_request`. Whether\n * a sort key is one the collection offers is the door's call.\n */\nfunction collectionSearch(q: CollectionQuery | undefined): string | null {\n const params: string[] = [];\n if (q?.offset !== undefined) {\n if (!Number.isInteger(q.offset) || q.offset < 0 || q.offset > SITE_MAX_OFFSET) return null;\n params.push(`offset=${q.offset}`);\n }\n if (q?.limit !== undefined) {\n if (!Number.isInteger(q.limit) || q.limit < 0) return null;\n params.push(`limit=${Math.min(SITE_MAX_LIMIT, Math.max(1, q.limit))}`);\n }\n if (q?.sort !== undefined) {\n const terms = typeof q.sort === \"string\" ? q.sort.split(\",\") : [...q.sort];\n if (terms.length > SITE_MAX_SORT_KEYS) return null;\n const keys = new Set<string>();\n for (const term of terms) {\n const m = SORT_TERM.exec(term);\n if (!m || keys.has(m[2])) return null;\n keys.add(m[2]);\n }\n if (terms.length > 0) params.push(`sort=${terms.join(\",\")}`);\n }\n return params.length > 0 ? `?${params.join(\"&\")}` : \"\";\n}\n\nexport function createPublicClient(opts: PublicClientOptions): PublicClient {\n const base = `${opts.podUrl.replace(/\\/+$/, \"\")}/api/hub/public`;\n const doFetch = (): typeof fetch => {\n const f = opts.fetch ?? globalThis.fetch;\n if (!f) throw new Error(\"No fetch available: pass `fetch` to createPublicClient\");\n return f;\n };\n const url = (resource: \"shares\" | \"forms\", token: string) =>\n `${base}/${resource}/${encodeURIComponent(token)}`;\n\n async function send(\n target: string,\n init: { method: \"GET\" | \"POST\"; body?: string; ifNoneMatch?: string }\n ): Promise<Response | null> {\n try {\n return await doFetch()(target, {\n method: init.method,\n credentials: \"omit\",\n headers:\n init.method === \"POST\"\n ? { \"Content-Type\": \"application/json\" }\n : init.ifNoneMatch\n ? { Accept: \"application/json\", \"If-None-Match\": init.ifNoneMatch }\n : { Accept: \"application/json\" },\n ...(init.body !== undefined ? { body: init.body } : {}),\n });\n } catch {\n return null;\n }\n }\n\n async function getJson<T>(\n resource: \"shares\" | \"forms\",\n token: string\n ): Promise<PublicResult<T>> {\n const res = await send(url(resource, token), { method: \"GET\" });\n if (!res) return { ok: false, error: \"network\" };\n if (res.status !== 200) return fail(classify(res.status, await readBody(res)), res);\n try {\n return { ok: true, data: (await res.json()) as T };\n } catch {\n return { ok: false, error: \"server\" };\n }\n }\n\n /** A site door read. `path` null = a segment that cannot be addressed: the uniform miss. */\n async function siteRead<T>(\n path: string | null,\n ifNoneMatch: string | undefined\n ): Promise<PublicSiteResult<T> | PublicNotModified> {\n if (path === null) return { ok: false, error: \"not_found\" };\n const revalidate = typeof ifNoneMatch === \"string\" && ifNoneMatch !== \"\" ? ifNoneMatch : undefined;\n const res = await send(`${base}/sites/${path}`, { method: \"GET\", ifNoneMatch: revalidate });\n if (!res) return { ok: false, error: \"pod_unreachable\" };\n const etag = res.headers.get(\"etag\");\n if (res.status === 304 && revalidate !== undefined) {\n return { ok: true, notModified: true, etag: etag ?? revalidate };\n }\n if (res.status !== 200) return fail(classifySite(res.status, await readBody(res)), res);\n try {\n return { ok: true, data: (await res.json()) as T, etag };\n } catch {\n return { ok: false, error: \"server\" };\n }\n }\n\n function getSite(siteToken: string, opts?: Partial<Revalidate>) {\n return siteRead<PublicSite>(segment(siteToken), opts?.ifNoneMatch);\n }\n\n async function listCollection(\n siteToken: string,\n key: string,\n query?: CollectionQuery & Partial<Revalidate>\n ): Promise<PublicSiteResult<PublicCollectionPage> | PublicNotModified> {\n const token = segment(siteToken);\n const k = segment(key);\n if (token === null || k === null) return { ok: false, error: \"not_found\" };\n const search = collectionSearch(query);\n if (search === null) return { ok: false, error: \"bad_request\" };\n return siteRead(`${token}/collections/${k}${search}`, query?.ifNoneMatch);\n }\n\n function getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string,\n opts?: Partial<Revalidate>\n ) {\n const parts = [segment(siteToken), segment(key), segment(slugOrPublicId)];\n const path = parts.every((p) => p !== null)\n ? `${parts[0]}/collections/${parts[1]}/${parts[2]}`\n : null;\n return siteRead<PublicRecord>(path, opts?.ifNoneMatch);\n }\n\n const sleep =\n opts.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));\n\n async function postOnce(\n token: string,\n input: SubmitFormInput,\n ticket: string | undefined\n ): Promise<{ retry: true } | { retry: false; result: SubmitResult }> {\n const body: Record<string, unknown> = {\n fields: input.fields,\n [HONEYPOT_KEY]: \"\",\n };\n if (ticket !== undefined) body.ticket = ticket;\n if (input.captchaToken !== undefined) body.captchaToken = input.captchaToken;\n if (input.idempotencyKey !== undefined) body.idempotencyKey = input.idempotencyKey;\n const res = await send(url(\"forms\", token), {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n const done = (result: SubmitResult) => ({ retry: false as const, result });\n if (!res) return done({ ok: false, error: \"network\" });\n if (res.status === 202) return done({ ok: true, received: true });\n if (res.status === 422) {\n let parsed: ReturnType<typeof parse422> = null;\n try {\n parsed = parse422(await res.json());\n } catch {\n parsed = null;\n }\n if (!parsed) return done({ ok: false, error: \"server\" });\n if (\"retry\" in parsed) return { retry: true };\n return done({ ok: false, error: \"invalid\", fields: parsed.fields });\n }\n if (res.status === 503) return done({ ok: false, error: \"unavailable\" });\n if (res.status === 429) return done(fail(\"rate_limited\", res));\n if (res.status === 403 && isDoorsDisabledBody(await readBody(res))) {\n return done({ ok: false, error: \"doors_disabled\" });\n }\n return done({ ok: false, error: \"server\" });\n }\n\n return {\n readShare: (token) => getJson<PublicShare>(\"shares\", token),\n getForm: (token) => getJson<PublicForm>(\"forms\", token),\n async submitForm(token, input) {\n const first = await postOnce(token, input, input.ticket);\n if (!first.retry) return first.result;\n // Ticket refused: fetch the form again (a fresh ticket + the wait), hold\n // for it, resubmit once with the same idempotency key.\n const fresh = await getJson<PublicForm>(\"forms\", token);\n if (!fresh.ok) return { ok: false, error: \"retry\" };\n await sleep(waitMs(fresh.data));\n const second = await postOnce(token, input, fresh.data.ticket);\n return second.retry ? { ok: false, error: \"retry\" } : second.result;\n },\n getSite: getSite as PublicClient[\"getSite\"],\n listCollection: listCollection as PublicClient[\"listCollection\"],\n getRecord: getRecord as PublicClient[\"getRecord\"],\n };\n}\n","/**\n * Lead form — pure model. A form component only renders what these\n * functions decide, so the decisions are testable without a DOM.\n *\n * Privacy rule: the pod answers every accepted submission with the same 202,\n * so the site can only ever say \"received\". Never add copy that implies\n * whether an email address is already known.\n */\nimport type {\n PublicForm,\n PublicFormField,\n PublicReadError,\n SubmitResult,\n} from \"./client\";\n\nexport type FieldControl = \"input\" | \"textarea\" | \"select\" | \"checkbox\";\n\nexport interface FieldModel {\n key: string;\n label: string;\n help?: string;\n required: boolean;\n control: FieldControl;\n /** HTML input type for `control: \"input\"`. */\n inputType: \"text\" | \"email\" | \"tel\" | \"url\" | \"number\" | \"date\";\n options: string[];\n maxLength?: number;\n min?: number;\n max?: number;\n}\n\nexport type FieldValue = string | boolean;\nexport type FormValues = Record<string, FieldValue>;\n\nconst INPUT_TYPE: Record<PublicFormField[\"type\"], FieldModel[\"inputType\"]> = {\n text: \"text\",\n richtext: \"text\",\n email: \"email\",\n phone: \"tel\",\n url: \"url\",\n number: \"number\",\n boolean: \"text\",\n date: \"date\",\n enum: \"text\",\n};\n\nfunction controlFor(type: PublicFormField[\"type\"]): FieldControl {\n if (type === \"richtext\") return \"textarea\";\n if (type === \"enum\") return \"select\";\n if (type === \"boolean\") return \"checkbox\";\n return \"input\";\n}\n\nexport function toFieldModels(form: PublicForm): FieldModel[] {\n return form.fields.map((f) => ({\n key: f.key,\n label: f.label,\n help: f.help,\n required: f.required === true,\n control: controlFor(f.type),\n inputType: INPUT_TYPE[f.type],\n options: f.constraints?.enum ?? [],\n maxLength: f.constraints?.maxLength,\n min: f.constraints?.min,\n max: f.constraints?.max,\n }));\n}\n\nexport function initialValues(fields: FieldModel[]): FormValues {\n return Object.fromEntries(fields.map((f) => [f.key, f.control === \"checkbox\" ? false : \"\"]));\n}\n\n/** Client-side checks only; the pod re-validates everything. */\nexport function validate(fields: FieldModel[], values: FormValues): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const f of fields) {\n const v = values[f.key];\n if (f.control === \"checkbox\") {\n if (f.required && v !== true) errors[f.key] = \"Required\";\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n if (f.required) errors[f.key] = \"Required\";\n continue;\n }\n if (f.maxLength !== undefined && s.length > f.maxLength) {\n errors[f.key] = `At most ${f.maxLength} characters`;\n } else if (f.control === \"select\" && f.options.length > 0 && !f.options.includes(s)) {\n errors[f.key] = \"Pick one of the options\";\n } else if (f.inputType === \"email\" && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(s)) {\n errors[f.key] = \"Enter a valid email\";\n } else if (f.inputType === \"number\") {\n const n = Number(s);\n if (!Number.isFinite(n)) errors[f.key] = \"Enter a number\";\n else if (f.min !== undefined && n < f.min) errors[f.key] = `At least ${f.min}`;\n else if (f.max !== undefined && n > f.max) errors[f.key] = `At most ${f.max}`;\n }\n }\n return errors;\n}\n\n/** The `fields` payload for `submitForm`: trimmed, empties dropped, numbers typed. */\nexport function toSubmission(fields: FieldModel[], values: FormValues): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of fields) {\n const v = values[f.key];\n if (f.control === \"checkbox\") {\n out[f.key] = v === true;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") continue;\n out[f.key] = f.inputType === \"number\" ? Number(s) : s;\n }\n return out;\n}\n\n/** Whether the submit button may fire (captcha passthrough). */\nexport function captchaSatisfied(form: PublicForm, captchaToken: string | null): boolean {\n return !form.captcha.required || (captchaToken !== null && captchaToken !== \"\");\n}\n\nexport function loadErrorMessage(error: PublicReadError): string {\n switch (error) {\n case \"not_found\":\n return \"This form isn't available.\";\n case \"rate_limited\":\n return \"Too many requests. Try again in a minute.\";\n case \"doors_disabled\":\n return \"This form isn't accepting responses right now.\";\n case \"network\":\n return \"We couldn't reach the server. Check your connection.\";\n case \"server\":\n return \"Something went wrong loading this form.\";\n }\n}\n\n/** A failed submit, as the form renders it. */\nexport interface SubmitFailure {\n /** One plain sentence for the form; never server detail. */\n message: string;\n /** Sentences by field key, only for keys that are on this form. */\n fieldErrors: Record<string, string>;\n /** Offer \"Try again\": nothing was filed and resending is safe. */\n retryable: boolean;\n /** The ticket is stale: fetch the form again before the next send. */\n refreshForm: boolean;\n}\n\nexport type FailedSubmit = Extract<SubmitResult, { ok: false }>;\n\n/**\n * What to show for a submit that was NOT received. Success is only ever\n * `{ ok: true, received: true }`; every branch here says \"not sent\".\n */\nexport function submitFailure(res: FailedSubmit, fields: FieldModel[]): SubmitFailure {\n const none = { fieldErrors: {}, retryable: false, refreshForm: false };\n switch (res.error) {\n case \"invalid\": {\n const known = new Set(fields.map((f) => f.key));\n const fieldErrors = Object.fromEntries(\n res.fields.filter((k) => known.has(k)).map((k) => [k, \"Check this answer.\"])\n );\n const n = Object.keys(fieldErrors).length;\n return {\n ...none,\n fieldErrors,\n message:\n n > 0\n ? \"Some answers need another look. Your message was not sent.\"\n : \"We couldn't read that as a message. Reload the page and try again.\",\n };\n }\n case \"retry\":\n return {\n ...none,\n retryable: true,\n refreshForm: true,\n message: \"That was sent a little too fast. Your message was not sent; try again.\",\n };\n case \"unavailable\":\n return {\n ...none,\n retryable: true,\n message: \"We couldn't save your message just now. It was not sent; try again.\",\n };\n case \"rate_limited\":\n return { ...none, message: \"Too many attempts. Try again in a minute.\" };\n case \"doors_disabled\":\n return {\n ...none,\n message: \"This form isn't accepting responses right now. Your message was not sent.\",\n };\n case \"network\":\n return {\n ...none,\n retryable: true,\n message: \"We couldn't reach the server. Your message was not sent.\",\n };\n case \"server\":\n return { ...none, message: \"Something went wrong. Your message was not sent.\" };\n }\n}\n\n/** Milliseconds until the pod will accept a submit for a ticket minted at `loadedAt`. */\nexport function submitWaitRemaining(minSubmitMs: number, loadedAt: number, now: number): number {\n const wait = Number.isFinite(minSubmitMs) && minSubmitMs > 0 ? minSubmitMs : 0;\n return Math.max(0, loadedAt + wait - now);\n}\n","/**\n * A deliberately tiny, SAFE markdown reader for published bodies (shares,\n * site records).\n *\n * It produces plain data (blocks of text runs) that React renders as text\n * nodes. It never produces HTML, so there is nothing to sanitize: raw HTML in\n * the source (`<script>`, `<img onerror>`) is shown verbatim as text.\n *\n * Supported: `#`–`######` headings, paragraphs, `-`/`*`/`1.` list items,\n * fenced code blocks, `**bold**`, and `[text](url)` links whose URL is\n * http, https or mailto. Any other link renders as its text only.\n * Everything else is plain text. Extend here — never by rendering HTML.\n */\n\nexport type Inline =\n | { type: \"text\"; text: string }\n | { type: \"strong\"; text: string }\n | { type: \"link\"; text: string; href: string };\n\nexport type Block =\n | { type: \"heading\"; level: 1 | 2 | 3 | 4 | 5 | 6; inlines: Inline[] }\n | { type: \"paragraph\"; inlines: Inline[] }\n | { type: \"list\"; ordered: boolean; items: Inline[][] }\n | { type: \"code\"; text: string };\n\nconst SAFE_PROTOCOLS = new Set([\"http:\", \"https:\", \"mailto:\"]);\n\n/** The href if it is an absolute http/https/mailto URL, else null. */\nexport function safeHref(raw: string): string | null {\n let url: URL;\n try {\n url = new URL(raw.trim());\n } catch {\n return null;\n }\n return SAFE_PROTOCOLS.has(url.protocol) ? url.href : null;\n}\n\n/** A link URL may hold one level of balanced parens (`/wiki/Foo_(bar)`). */\nconst INLINE = /\\*\\*([^*]+)\\*\\*|\\[([^\\]]+)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)\\)/g;\n\nexport function parseInlines(text: string): Inline[] {\n const out: Inline[] = [];\n let last = 0;\n for (const m of text.matchAll(INLINE)) {\n const at = m.index ?? 0;\n if (at > last) out.push({ type: \"text\", text: text.slice(last, at) });\n if (m[1] !== undefined) {\n out.push({ type: \"strong\", text: m[1] });\n } else {\n const href = safeHref(m[3]);\n out.push(href ? { type: \"link\", text: m[2], href } : { type: \"text\", text: m[2] });\n }\n last = at + m[0].length;\n }\n if (last < text.length) out.push({ type: \"text\", text: text.slice(last) });\n return out;\n}\n\nconst HEADING = /^(#{1,6})\\s+(.*)$/;\nconst BULLET = /^\\s*[-*]\\s+(.*)$/;\nconst ORDERED = /^\\s*\\d+[.)]\\s+(.*)$/;\nconst FENCE = /^\\s*(```|~~~)/;\n\nexport function parseSafeMarkdown(source: string): Block[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const blocks: Block[] = [];\n let para: string[] = [];\n let list: { ordered: boolean; items: Inline[][] } | null = null;\n\n const flush = () => {\n if (para.length) blocks.push({ type: \"paragraph\", inlines: parseInlines(para.join(\" \")) });\n para = [];\n if (list) blocks.push({ type: \"list\", ...list });\n list = null;\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const fence = line.match(FENCE);\n if (fence) {\n flush();\n const code: string[] = [];\n i++;\n while (i < lines.length && !lines[i].trimStart().startsWith(fence[1])) code.push(lines[i++]);\n blocks.push({ type: \"code\", text: code.join(\"\\n\") });\n continue;\n }\n if (line.trim() === \"\") {\n flush();\n continue;\n }\n const heading = line.match(HEADING);\n if (heading) {\n flush();\n const level = heading[1].length as 1 | 2 | 3 | 4 | 5 | 6;\n blocks.push({ type: \"heading\", level, inlines: parseInlines(heading[2]) });\n continue;\n }\n const bullet = line.match(BULLET);\n const ordered = bullet ? null : line.match(ORDERED);\n if (bullet || ordered) {\n const isOrdered = ordered !== null;\n if (para.length || (list && list.ordered !== isOrdered)) flush();\n if (!list) list = { ordered: isOrdered, items: [] };\n list.items.push(parseInlines((bullet ?? ordered)![1]));\n continue;\n }\n if (list) flush();\n para.push(line.trim());\n }\n flush();\n return blocks;\n}\n","/**\n * Published site — pure models for collection and record pages: one value\n * formatter, link-based pagination (`?page=n`, SEO-friendly) and the record\n * view. A page component only renders what these decide.\n *\n * Formatting is deterministic: the locale and time zone default to `en` and\n * `UTC`, never the runtime's, so a server render and a client render agree.\n */\nimport {\n SITE_MAX_LIMIT,\n SITE_MAX_OFFSET,\n type PublicCollectionPage,\n type PublicRecord,\n type PublicRecordSummary,\n type PublicValue,\n} from \"./client\";\nimport { parseSafeMarkdown, type Block } from \"./safe-markdown\";\n\nexport interface FormatOptions {\n /** BCP 47 locale (default `en`). */\n locale?: string;\n /** IANA zone for date-times (default `UTC`). Plain dates are never shifted. */\n timeZone?: string;\n /** Words for booleans (default Yes / No). */\n labels?: { yes: string; no: string };\n}\n\n/**\n * Group digits only from 5 digits up, so a year stays `2026` and 12345 reads\n * `12,345` (`useGrouping: \"min2\"`, ES2023 Intl; this package's lib is ES2022,\n * hence the cast). An engine without it falls back to always grouping.\n */\nconst GROUPING = { useGrouping: \"min2\" } as unknown as Intl.NumberFormatOptions;\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst ISO_DATE_TIME = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\n/** A real calendar day (`2026-02-30` is not one). */\nfunction isCalendarDay(s: string): boolean {\n const m = ISO_DATE.exec(s);\n if (!m) return false;\n const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));\n return d.toISOString().slice(0, 10) === s;\n}\n\n/**\n * One published value as display text, the same on every site: booleans as\n * words, numbers grouped for the locale, ISO dates and date-times as medium\n * dates (date-times with a short time). Any other string is returned as is.\n * Plain text only: render it as a text node.\n */\nexport function formatPublicValue(value: PublicValue, opts: FormatOptions = {}): string {\n const locale = opts.locale ?? \"en\";\n if (typeof value === \"boolean\") return value ? (opts.labels?.yes ?? \"Yes\") : (opts.labels?.no ?? \"No\");\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? new Intl.NumberFormat(locale, GROUPING).format(value) : String(value);\n }\n if (isCalendarDay(value)) {\n return new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeZone: \"UTC\" }).format(\n new Date(`${value}T00:00:00Z`)\n );\n }\n if (ISO_DATE_TIME.test(value)) {\n const at = new Date(value);\n if (!Number.isNaN(at.getTime())) {\n return new Intl.DateTimeFormat(locale, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone: opts.timeZone ?? \"UTC\",\n }).format(at);\n }\n }\n return value;\n}\n\n// ── Pagination ──────────────────────────────────────────────────────────────\n\n/** Highest page number `parsePageParam` accepts. */\nconst MAX_PAGE_PARAM = 1_000_000;\n\n/**\n * A `?page=` value → a page number. Missing or empty → 1; a positive integer\n * → itself; anything else → null (render the not-found state).\n */\nexport function parsePageParam(raw: string | string[] | null | undefined): number | null {\n if (raw === undefined || raw === null || raw === \"\") return 1;\n if (Array.isArray(raw)) return null;\n if (!/^[1-9]\\d*$/.test(raw)) return null;\n const n = Number(raw);\n return n <= MAX_PAGE_PARAM ? n : null;\n}\n\n/**\n * The `listCollection` query for page `page` (1-based) of `pageSize` items.\n * null when the page lies past the door's offset ceiling (10 000): render\n * the not-found state, never a request that can only be refused.\n */\nexport function pageQuery(page: number, pageSize: number): { offset: number; limit: number } | null {\n if (!Number.isInteger(page) || page < 1) return null;\n const limit = Math.min(SITE_MAX_LIMIT, Math.max(1, Math.floor(pageSize) || 1));\n const offset = (page - 1) * limit;\n return offset <= SITE_MAX_OFFSET ? { offset, limit } : null;\n}\n\nexport interface PageLink {\n page: number;\n /** `basePath` for page 1 (the canonical URL), else `basePath?page=n`. */\n href: string;\n current: boolean;\n}\n\nexport type PageItem = ({ kind: \"page\" } & PageLink) | { kind: \"gap\"; key: string };\n\nexport interface CollectionPageModel {\n items: PublicRecordSummary[];\n total: number;\n /** Current page, 1-based. */\n page: number;\n /** Reachable pages (≥ 1); the door serves no offset past 10 000. */\n pageCount: number;\n pageSize: number;\n /** 1-based positions of the first and last item shown; 0 and 0 when none. */\n from: number;\n to: number;\n /** Nothing is published in this collection. */\n empty: boolean;\n /** Records exist but not on this page (a stale or hand-typed `?page=`). */\n outOfRange: boolean;\n prev: PageLink | null;\n next: PageLink | null;\n /** First, last, and `window` pages either side of the current one, with gaps. */\n pages: PageItem[];\n}\n\nexport interface PageModelOptions {\n /** The collection page's path, e.g. `/papers` (may carry its own query). */\n basePath: string;\n /** Pages shown either side of the current one (default 1). */\n window?: number;\n /** Query parameter name (default `page`). */\n param?: string;\n}\n\nexport function toCollectionPageModel(\n page: PublicCollectionPage,\n opts: PageModelOptions\n): CollectionPageModel {\n const pageSize = Math.max(1, page.limit);\n const current = Math.floor(Math.max(0, page.offset) / pageSize) + 1;\n const reachable = Math.floor(SITE_MAX_OFFSET / pageSize) + 1;\n const pageCount = Math.max(1, Math.min(Math.ceil(page.total / pageSize), reachable));\n const param = opts.param ?? \"page\";\n const sep = opts.basePath.includes(\"?\") ? \"&\" : \"?\";\n const link = (n: number): PageLink => ({\n page: n,\n href: n === 1 ? opts.basePath : `${opts.basePath}${sep}${param}=${n}`,\n current: n === current,\n });\n\n const window = Math.max(0, Math.floor(opts.window ?? 1));\n const shown = new Set<number>([1, pageCount]);\n for (let n = current - window; n <= current + window; n++) {\n if (n >= 1 && n <= pageCount) shown.add(n);\n }\n const pages: PageItem[] = [];\n let last = 0;\n for (const n of [...shown].sort((a, b) => a - b)) {\n if (n - last === 2) pages.push({ kind: \"page\", ...link(n - 1) });\n else if (n - last > 2) pages.push({ kind: \"gap\", key: `gap-${last}` });\n pages.push({ kind: \"page\", ...link(n) });\n last = n;\n }\n\n const count = page.items.length;\n const from = count > 0 ? page.offset + 1 : 0;\n return {\n items: page.items,\n total: page.total,\n page: current,\n pageCount,\n pageSize,\n from,\n to: count > 0 ? page.offset + count : 0,\n empty: page.total === 0,\n outOfRange: page.total > 0 && count === 0,\n prev: current > 1 ? link(Math.min(current - 1, pageCount)) : null,\n next: current < pageCount ? link(current + 1) : null,\n pages,\n };\n}\n\n// ── Record ──────────────────────────────────────────────────────────────────\n\nexport interface RecordViewModel {\n title: string | null;\n publicId: string;\n slug: string | null;\n /** Rows exactly as the publisher allowlisted them, formatted. */\n properties: Array<{ key: string; value: string }>;\n /** The body as safe blocks (never HTML). */\n blocks: Block[];\n publishedOn: string;\n}\n\nexport function toRecordViewModel(record: PublicRecord, opts: FormatOptions = {}): RecordViewModel {\n return {\n title: record.title ?? null,\n publicId: record.publicId,\n slug: record.slug,\n properties: Object.entries(record.properties ?? {}).map(([key, value]) => ({\n key,\n value: formatPublicValue(value, opts),\n })),\n blocks: record.body ? parseSafeMarkdown(record.body.content) : [],\n publishedOn: record.publishedOn,\n };\n}\n","/**\n * Shared-document page — pure model for a `/d/[token]` page.\n *\n * Every failure to read collapses into ONE \"unavailable\" state. `not_found`\n * is already uniform at the pod (revoked, expired and unknown are\n * indistinguishable by design); the page keeps that promise and does not add\n * a distinction of its own. `retryable` only says whether \"try again later\"\n * is honest (a network blip) — it never says anything about the token.\n */\nimport type { PublicReadError, PublicResult, PublicShare } from \"./client\";\nimport { parseSafeMarkdown, type Block } from \"./safe-markdown\";\nimport { formatPublicValue, type FormatOptions } from \"./site-model\";\n\nexport const UNAVAILABLE_TITLE = \"This link isn't available\";\n\nexport type SharePageModel =\n | { kind: \"unavailable\"; retryable: boolean }\n | {\n kind: \"share\";\n title: string | null;\n /** Property rows exactly as the publisher allowlisted them. */\n properties: Array<{ key: string; value: string }>;\n blocks: Block[];\n publishedOn: string;\n };\n\nfunction isRetryable(error: PublicReadError): boolean {\n return error === \"network\" || error === \"rate_limited\" || error === \"server\";\n}\n\nexport function toSharePageModel(\n result: PublicResult<PublicShare>,\n opts: FormatOptions = {}\n): SharePageModel {\n if (!result.ok) return { kind: \"unavailable\", retryable: isRetryable(result.error) };\n const share = result.data;\n return {\n kind: \"share\",\n title: share.title ?? null,\n properties: Object.entries(share.properties ?? {}).map(([key, value]) => ({\n key,\n value: formatPublicValue(value, opts),\n })),\n blocks: share.body ? parseSafeMarkdown(share.body.content) : [],\n publishedOn: share.publishedOn,\n };\n}\n"],"mappings":"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","names":["opts"]}
1
+ {"version":3,"sources":["../src/public/client.ts","../src/public/form-model.ts","../src/public/safe-markdown.ts","../src/public/site-model.ts","../src/public/share-model.ts"],"sourcesContent":["/**\n * @synap-core/sdk/public — a thin, CREDENTIALLESS fetch client for the pod's\n * public doors (Sites W3/W4, collection door C1a/C1b):\n *\n * GET <pod>/api/hub/public/shares/:token read a published share\n * GET <pod>/api/hub/public/forms/:token a form's public definition + ticket\n * POST <pod>/api/hub/public/forms/:token a guest submission (202 / 422 / 503)\n * GET <pod>/api/hub/public/sites/:token a site manifest\n * GET <pod>/api/hub/public/sites/:token/collections/:key a page of records\n * GET <pod>/api/hub/public/sites/:token/collections/:key/:slugOrPublicId a record\n *\n * Invariants (pinned by src/__tests__/public.guard.test.ts):\n * - every request is `credentials: 'omit'`; no Authorization / Cookie / session\n * header is ever set (only `Accept`, `If-None-Match` on a site revalidation,\n * and `Content-Type` on POST);\n * - imports NOTHING (no tRPC, superjson, zod, authenticated client);\n * - the uniform 404 is one typed `not_found` (revoked / expired / unknown are\n * indistinguishable by design); the constant 202 is `{ received: true }`;\n * - a submit has six typed non-success outcomes (`invalid` with the field keys,\n * `retry`, `unavailable`, `rate_limited`, `network`, `server`); a 422 `retry`\n * (ticket missing / too early / stale) is retried ONCE automatically: the\n * form is fetched again, `minSubmitMs` is waited out (capped) and the same\n * submission (same idempotency key) is resent;\n * - server detail (bodies, messages) never reaches the caller.\n *\n * DRIFT RISK: the response types below are HAND-WRITTEN from the backend wire\n * contracts (synap-backend: `_codecs/public-shares.ts` PublicShareResponseSchema,\n * `services/forms/form-definition.ts` publicFormView + the public-forms route,\n * `SubmissionEnvelopeSchema`, `_codecs/public-sites.ts` + the bounds in\n * `services/sharing/public-site-read.ts` parseCollectionQuery).\n * `src/__tests__/fixtures.ts` copies those shapes; when the backend changes,\n * update both.\n */\n\n// ── Wire types (hand-written; see DRIFT RISK) ────────────────────────────────\n\n/**\n * A published body, in the format the pod serves it (backend\n * `PublicBodyFormatSchema`): `markdown` is text to parse with\n * `parseSafeMarkdown`; `html` is a whole HTML document (an estimate, a report)\n * that a page renders ONLY inside `<iframe sandbox=\"\" srcdoc>`, never in its\n * own DOM. `null`: nothing pinned, or a body that is not text (pdf, docx).\n */\nexport interface PublicBody {\n format: \"markdown\" | \"html\";\n content: string;\n}\n\nexport interface PublicShare {\n resourceType: \"entity\";\n /** Present only when the publisher allowlisted `title`. */\n title?: string;\n properties: Record<string, string | number | boolean>;\n body: PublicBody | null;\n /** Publication day, YYYY-MM-DD. */\n publishedOn: string;\n}\n\nexport type PublicFormFieldType =\n | \"text\"\n | \"richtext\"\n | \"email\"\n | \"phone\"\n | \"url\"\n | \"number\"\n | \"boolean\"\n | \"date\"\n | \"enum\";\n\nexport interface PublicFormField {\n key: string;\n label: string;\n type: PublicFormFieldType;\n required?: true;\n help?: string;\n constraints?: {\n enum?: string[];\n min?: number;\n max?: number;\n maxLength?: number;\n };\n}\n\nexport interface PublicForm {\n title: string;\n fields: PublicFormField[];\n successMessage: string;\n /** The envelope key of the honeypot (always \"hp\" today). */\n honeypotField: string;\n /** Time-to-submit ticket, minted by this GET; pass it back to `submitForm`. */\n ticket: string;\n /**\n * The POST is refused with `retry` until this many ms have passed since the\n * ticket was minted. Hold the submit button until then. 0 = no wait.\n */\n minSubmitMs: number;\n captcha: { required: true; siteKey: string | null } | { required: false };\n}\n\n/** A published value: a publish-time snapshot, scalars only (C1b). */\nexport type PublicValue = string | number | boolean;\n\nexport interface PublicSiteCollection {\n /** The collection key (a profile slug); pass it to `listCollection`. */\n key: string;\n /** Live published records in it. */\n count: number;\n /** The default sort keys (`-` = descending). */\n sort: string[];\n /** The default page size. */\n pageSize: number;\n /** How a record of this collection is addressed in URLs. */\n addressing: \"slug\" | \"publicId\";\n}\n\n/** A published site's manifest. */\nexport interface PublicSite {\n /** Present only when the owner's policy publishes `title`. */\n title?: string;\n /** Present only when the owner's policy publishes `description`. */\n description?: string;\n /** Day the site went live, YYYY-MM-DD. */\n publishedOn: string;\n collections: PublicSiteCollection[];\n}\n\n/** One published record as a list item (no body). */\nexport interface PublicRecordSummary {\n collection: string;\n /** The stable public address: `p_` + 16 random base62 chars. */\n publicId: string;\n /** The record's slug in its collection, or null. */\n slug: string | null;\n /** Present only when the owner's policy publishes `title`. */\n title?: string;\n properties: Record<string, PublicValue>;\n /** Publication day, YYYY-MM-DD. */\n publishedOn: string;\n}\n\nexport interface PublicCollectionPage {\n items: PublicRecordSummary[];\n /** Live published records in the collection. */\n total: number;\n offset: number;\n limit: number;\n}\n\n/** One published record, with its pinned body. */\nexport interface PublicRecord extends PublicRecordSummary {\n body: PublicBody | null;\n}\n\n/** The collection door's query bounds (backend `parseCollectionQuery`). */\nexport const SITE_MAX_OFFSET = 10_000;\nexport const SITE_MAX_LIMIT = 100;\nexport const SITE_MAX_SORT_KEYS = 3;\n/** The sort key every collection accepts, besides its configured ones. */\nexport const SITE_PUBLISHED_AT_SORT = \"publishedAt\";\n\n// ── Results ──────────────────────────────────────────────────────────────────\n\n/** `not_found` is the uniform 404 (never says revoked vs expired vs unknown). */\nexport type PublicReadError =\n | \"not_found\"\n | \"rate_limited\"\n /** The owner has not turned public pages/forms on for this workspace. */\n | \"doors_disabled\"\n | \"network\"\n | \"server\";\n/** A submit never yields `not_found`: the door answers 202 to everything. */\nexport type PublicSubmitError =\n | \"invalid\"\n /** The ticket was refused, even after the one automatic retry. */\n | \"retry\"\n /** 503: the pod failed to file it; resubmitting with the same key is safe. */\n | \"unavailable\"\n | \"rate_limited\"\n /** The owner has not turned public pages/forms on for this workspace. */\n | \"doors_disabled\"\n | \"network\"\n | \"server\";\n\n/**\n * A site read's failure. `not_found` is the uniform 404 (an unknown, revoked\n * or unpublished site, collection or record are indistinguishable).\n */\nexport type PublicSiteError =\n | \"not_found\"\n | \"rate_limited\"\n /** The site is live but the owner switched its space's public pages off. */\n | \"doors_disabled\"\n /** 400: the query is out of bounds or names a sort key the collection does not offer. */\n | \"bad_request\"\n /** No answer (network, DNS, CORS) or a gateway 502/503/504. */\n | \"pod_unreachable\"\n | \"server\";\n\n/**\n * A failure. `rate_limited` carries `retryAfterSec` only when the response\n * had a `Retry-After` header the caller can read (see README: the pod's public\n * doors do not send one today, and a browser cannot read it cross-origin).\n */\nexport type PublicFailure<E extends string> = E extends \"rate_limited\"\n ? { ok: false; error: E; retryAfterSec?: number }\n : { ok: false; error: E };\n\nexport type PublicResult<T, E extends string = PublicReadError> =\n | { ok: true; data: T }\n | PublicFailure<E>;\n\n/** A site read. `etag` is the door's weak validator, for `ifNoneMatch`. */\nexport type PublicSiteResult<T> =\n | { ok: true; data: T; etag: string | null }\n | PublicFailure<PublicSiteError>;\n\n/** 304: what the caller holds for `ifNoneMatch` is still current. */\nexport interface PublicNotModified {\n ok: true;\n notModified: true;\n etag: string;\n}\n\n/**\n * Revalidate with the ETag of an earlier answer. SERVER-SIDE ONLY: the public\n * doors allow no request header but `Content-Type` cross-origin, so a browser\n * fails the preflight (→ `pod_unreachable`). Browsers revalidate on their own\n * through their HTTP cache (`Cache-Control: no-cache` + ETag).\n */\nexport interface Revalidate {\n ifNoneMatch: string;\n}\n\nexport interface CollectionQuery {\n /** 0..10 000 (default 0). Anything else is `bad_request`, without a request. */\n offset?: number;\n /** An integer, clamped to 1..100 (default: the collection's page size). */\n limit?: number;\n /**\n * ≤ 3 keys, `-` = descending, out of the collection's `sort` keys and\n * `publishedAt` (default: the collection's sort). A string is comma-split.\n */\n sort?: string | readonly string[];\n}\n\nexport type SubmitResult =\n | { ok: true; received: true }\n /** 422: the answers do not satisfy the form. `fields` are field keys; empty = not a submission. */\n | { ok: false; error: \"invalid\"; fields: string[] }\n | PublicFailure<Exclude<PublicSubmitError, \"invalid\">>;\n\nexport interface SubmitFormInput {\n /** Answers keyed by the form's field `key`s. */\n fields: Record<string, unknown>;\n /** The `ticket` from `getForm` (waiting out the form's min time is the caller's job). */\n ticket?: string;\n /** Turnstile-style token, when `captcha.required`. */\n captchaToken?: string;\n /** Optional replay key; a repeated key collapses onto one submission. */\n idempotencyKey?: string;\n}\n\nexport interface PublicClientOptions {\n /** Pod origin, e.g. `https://you.synap.live` (a trailing slash is ignored). */\n podUrl: string;\n /** Injectable fetch; defaults to the global one. */\n fetch?: typeof fetch;\n /** Injectable wait (tests); defaults to `setTimeout`. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface PublicClient {\n readShare(token: string): Promise<PublicResult<PublicShare>>;\n getForm(token: string): Promise<PublicResult<PublicForm>>;\n submitForm(token: string, input: SubmitFormInput): Promise<SubmitResult>;\n\n /** A published site's manifest: its metadata and configured collections. */\n getSite(siteToken: string): Promise<PublicSiteResult<PublicSite>>;\n getSite(\n siteToken: string,\n opts: Revalidate\n ): Promise<PublicSiteResult<PublicSite> | PublicNotModified>;\n\n /** One page of a collection's live published records (no bodies). */\n listCollection(\n siteToken: string,\n key: string,\n query?: CollectionQuery\n ): Promise<PublicSiteResult<PublicCollectionPage>>;\n listCollection(\n siteToken: string,\n key: string,\n query: CollectionQuery & Revalidate\n ): Promise<PublicSiteResult<PublicCollectionPage> | PublicNotModified>;\n\n /** One record by its public id (`p_…`) or its slug, with its pinned body. */\n getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string\n ): Promise<PublicSiteResult<PublicRecord>>;\n getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string,\n opts: Revalidate\n ): Promise<PublicSiteResult<PublicRecord> | PublicNotModified>;\n}\n\n/** The honeypot envelope key the door reads. Always sent empty. */\nconst HONEYPOT_KEY = \"hp\";\n/** Longest wait the automatic ticket retry will sit out, whatever the pod says. */\nconst MAX_TICKET_WAIT_MS = 10_000;\nconst MAX_INVALID_KEYS = 100;\nconst MAX_KEY_LENGTH = 200;\n\nfunction waitMs(form: unknown): number {\n const n = (form as { minSubmitMs?: unknown } | null)?.minSubmitMs;\n return typeof n === \"number\" && Number.isFinite(n) && n > 0\n ? Math.min(n, MAX_TICKET_WAIT_MS)\n : 0;\n}\n\n/** A 422 body -> outcome. Only field keys survive; anything else is dropped. */\nfunction parse422(body: unknown): { retry: true } | { fields: string[] } | null {\n if (typeof body !== \"object\" || body === null) return null;\n const b = body as { retry?: unknown; invalid?: unknown };\n if (b.retry === true) return { retry: true };\n if (!Array.isArray(b.invalid)) return null;\n return {\n fields: b.invalid\n .filter((k): k is string => typeof k === \"string\" && k.length <= MAX_KEY_LENGTH)\n .slice(0, MAX_INVALID_KEYS),\n };\n}\n\n/** The one refusal body's machine code (see `PUBLIC_DOORS_DISABLED_BODY` on the pod). */\nconst DOORS_DISABLED_CODE = \"public_doors_disabled\";\n\nfunction isDoorsDisabledBody(body: unknown): boolean {\n return (\n typeof body === \"object\" &&\n body !== null &&\n (body as { code?: unknown }).code === DOORS_DISABLED_CODE\n );\n}\n\n/** A non-200 answer of a read door (no answer at all is the caller's `network`). */\nfunction classify(status: number, body: unknown): Exclude<PublicReadError, \"network\"> {\n if (status === 404) return \"not_found\";\n if (status === 429) return \"rate_limited\";\n if (status === 403 && isDoorsDisabledBody(body)) return \"doors_disabled\";\n return \"server\";\n}\n\nfunction classifySite(status: number, body: unknown): PublicSiteError {\n if (status === 400) return \"bad_request\";\n if (status === 502 || status === 503 || status === 504) return \"pod_unreachable\";\n return classify(status, body);\n}\n\n/** Longest `Retry-After` passed on, whatever the response says. */\nconst MAX_RETRY_AFTER_SEC = 86_400;\n\n/** `Retry-After` (delta-seconds or an HTTP date) in whole seconds, else undefined. */\nfunction retryAfterSec(res: Response): number | undefined {\n const raw = res.headers.get(\"retry-after\")?.trim();\n if (!raw) return undefined;\n let sec: number;\n if (/^\\d+$/.test(raw)) {\n sec = Number(raw);\n } else {\n const at = Date.parse(raw);\n if (Number.isNaN(at)) return undefined;\n sec = Math.ceil((at - Date.now()) / 1000);\n }\n return Math.min(Math.max(0, sec), MAX_RETRY_AFTER_SEC);\n}\n\n/** A failure; `rate_limited` picks up the response's `Retry-After`, if any. */\nfunction fail<E extends string>(error: E, res: Response | null): PublicFailure<E> {\n const sec = error === \"rate_limited\" && res ? retryAfterSec(res) : undefined;\n return (sec === undefined ? { ok: false, error } : { ok: false, error, retryAfterSec: sec }) as PublicFailure<E>;\n}\n\nasync function readBody(res: Response): Promise<unknown> {\n try {\n return await res.json();\n } catch {\n return null;\n }\n}\n\n/**\n * One URL path segment, or null when it could not stay one: empty, `.` and\n * `..` would be resolved away by the URL parser and reach ANOTHER door (e.g.\n * collection `..` is the manifest), typed as the wrong shape.\n */\nfunction segment(raw: string): string | null {\n if (typeof raw !== \"string\" || raw === \"\" || raw === \".\" || raw === \"..\") return null;\n return encodeURIComponent(raw);\n}\n\n/** A sort term as the door parses it (`-` = descending). */\nconst SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;\n\n/**\n * The collection query string, checked against the door's static bounds so a\n * request that can only be a 400 is never sent. null = `bad_request`. Whether\n * a sort key is one the collection offers is the door's call.\n */\nfunction collectionSearch(q: CollectionQuery | undefined): string | null {\n const params: string[] = [];\n if (q?.offset !== undefined) {\n if (!Number.isInteger(q.offset) || q.offset < 0 || q.offset > SITE_MAX_OFFSET) return null;\n params.push(`offset=${q.offset}`);\n }\n if (q?.limit !== undefined) {\n if (!Number.isInteger(q.limit) || q.limit < 0) return null;\n params.push(`limit=${Math.min(SITE_MAX_LIMIT, Math.max(1, q.limit))}`);\n }\n if (q?.sort !== undefined) {\n const terms = typeof q.sort === \"string\" ? q.sort.split(\",\") : [...q.sort];\n if (terms.length > SITE_MAX_SORT_KEYS) return null;\n const keys = new Set<string>();\n for (const term of terms) {\n const m = SORT_TERM.exec(term);\n if (!m || keys.has(m[2])) return null;\n keys.add(m[2]);\n }\n if (terms.length > 0) params.push(`sort=${terms.join(\",\")}`);\n }\n return params.length > 0 ? `?${params.join(\"&\")}` : \"\";\n}\n\nexport function createPublicClient(opts: PublicClientOptions): PublicClient {\n const base = `${opts.podUrl.replace(/\\/+$/, \"\")}/api/hub/public`;\n const doFetch = (): typeof fetch => {\n const f = opts.fetch ?? globalThis.fetch;\n if (!f) throw new Error(\"No fetch available: pass `fetch` to createPublicClient\");\n return f;\n };\n const url = (resource: \"shares\" | \"forms\", token: string) =>\n `${base}/${resource}/${encodeURIComponent(token)}`;\n\n async function send(\n target: string,\n init: { method: \"GET\" | \"POST\"; body?: string; ifNoneMatch?: string }\n ): Promise<Response | null> {\n try {\n return await doFetch()(target, {\n method: init.method,\n credentials: \"omit\",\n headers:\n init.method === \"POST\"\n ? { \"Content-Type\": \"application/json\" }\n : init.ifNoneMatch\n ? { Accept: \"application/json\", \"If-None-Match\": init.ifNoneMatch }\n : { Accept: \"application/json\" },\n ...(init.body !== undefined ? { body: init.body } : {}),\n });\n } catch {\n return null;\n }\n }\n\n async function getJson<T>(\n resource: \"shares\" | \"forms\",\n token: string\n ): Promise<PublicResult<T>> {\n const res = await send(url(resource, token), { method: \"GET\" });\n if (!res) return { ok: false, error: \"network\" };\n if (res.status !== 200) return fail(classify(res.status, await readBody(res)), res);\n try {\n return { ok: true, data: (await res.json()) as T };\n } catch {\n return { ok: false, error: \"server\" };\n }\n }\n\n /** A site door read. `path` null = a segment that cannot be addressed: the uniform miss. */\n async function siteRead<T>(\n path: string | null,\n ifNoneMatch: string | undefined\n ): Promise<PublicSiteResult<T> | PublicNotModified> {\n if (path === null) return { ok: false, error: \"not_found\" };\n const revalidate = typeof ifNoneMatch === \"string\" && ifNoneMatch !== \"\" ? ifNoneMatch : undefined;\n const res = await send(`${base}/sites/${path}`, { method: \"GET\", ifNoneMatch: revalidate });\n if (!res) return { ok: false, error: \"pod_unreachable\" };\n const etag = res.headers.get(\"etag\");\n if (res.status === 304 && revalidate !== undefined) {\n return { ok: true, notModified: true, etag: etag ?? revalidate };\n }\n if (res.status !== 200) return fail(classifySite(res.status, await readBody(res)), res);\n try {\n return { ok: true, data: (await res.json()) as T, etag };\n } catch {\n return { ok: false, error: \"server\" };\n }\n }\n\n function getSite(siteToken: string, opts?: Partial<Revalidate>) {\n return siteRead<PublicSite>(segment(siteToken), opts?.ifNoneMatch);\n }\n\n async function listCollection(\n siteToken: string,\n key: string,\n query?: CollectionQuery & Partial<Revalidate>\n ): Promise<PublicSiteResult<PublicCollectionPage> | PublicNotModified> {\n const token = segment(siteToken);\n const k = segment(key);\n if (token === null || k === null) return { ok: false, error: \"not_found\" };\n const search = collectionSearch(query);\n if (search === null) return { ok: false, error: \"bad_request\" };\n return siteRead(`${token}/collections/${k}${search}`, query?.ifNoneMatch);\n }\n\n function getRecord(\n siteToken: string,\n key: string,\n slugOrPublicId: string,\n opts?: Partial<Revalidate>\n ) {\n const parts = [segment(siteToken), segment(key), segment(slugOrPublicId)];\n const path = parts.every((p) => p !== null)\n ? `${parts[0]}/collections/${parts[1]}/${parts[2]}`\n : null;\n return siteRead<PublicRecord>(path, opts?.ifNoneMatch);\n }\n\n const sleep =\n opts.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));\n\n async function postOnce(\n token: string,\n input: SubmitFormInput,\n ticket: string | undefined\n ): Promise<{ retry: true } | { retry: false; result: SubmitResult }> {\n const body: Record<string, unknown> = {\n fields: input.fields,\n [HONEYPOT_KEY]: \"\",\n };\n if (ticket !== undefined) body.ticket = ticket;\n if (input.captchaToken !== undefined) body.captchaToken = input.captchaToken;\n if (input.idempotencyKey !== undefined) body.idempotencyKey = input.idempotencyKey;\n const res = await send(url(\"forms\", token), {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n const done = (result: SubmitResult) => ({ retry: false as const, result });\n if (!res) return done({ ok: false, error: \"network\" });\n if (res.status === 202) return done({ ok: true, received: true });\n if (res.status === 422) {\n let parsed: ReturnType<typeof parse422> = null;\n try {\n parsed = parse422(await res.json());\n } catch {\n parsed = null;\n }\n if (!parsed) return done({ ok: false, error: \"server\" });\n if (\"retry\" in parsed) return { retry: true };\n return done({ ok: false, error: \"invalid\", fields: parsed.fields });\n }\n if (res.status === 503) return done({ ok: false, error: \"unavailable\" });\n if (res.status === 429) return done(fail(\"rate_limited\", res));\n if (res.status === 403 && isDoorsDisabledBody(await readBody(res))) {\n return done({ ok: false, error: \"doors_disabled\" });\n }\n return done({ ok: false, error: \"server\" });\n }\n\n return {\n readShare: (token) => getJson<PublicShare>(\"shares\", token),\n getForm: (token) => getJson<PublicForm>(\"forms\", token),\n async submitForm(token, input) {\n const first = await postOnce(token, input, input.ticket);\n if (!first.retry) return first.result;\n // Ticket refused: fetch the form again (a fresh ticket + the wait), hold\n // for it, resubmit once with the same idempotency key.\n const fresh = await getJson<PublicForm>(\"forms\", token);\n if (!fresh.ok) return { ok: false, error: \"retry\" };\n await sleep(waitMs(fresh.data));\n const second = await postOnce(token, input, fresh.data.ticket);\n return second.retry ? { ok: false, error: \"retry\" } : second.result;\n },\n getSite: getSite as PublicClient[\"getSite\"],\n listCollection: listCollection as PublicClient[\"listCollection\"],\n getRecord: getRecord as PublicClient[\"getRecord\"],\n };\n}\n","/**\n * Lead form — pure model. A form component only renders what these\n * functions decide, so the decisions are testable without a DOM.\n *\n * Privacy rule: the pod answers every accepted submission with the same 202,\n * so the site can only ever say \"received\". Never add copy that implies\n * whether an email address is already known.\n */\nimport type {\n PublicForm,\n PublicFormField,\n PublicReadError,\n SubmitResult,\n} from \"./client\";\n\nexport type FieldControl = \"input\" | \"textarea\" | \"select\" | \"checkbox\";\n\nexport interface FieldModel {\n key: string;\n label: string;\n help?: string;\n required: boolean;\n control: FieldControl;\n /** HTML input type for `control: \"input\"`. */\n inputType: \"text\" | \"email\" | \"tel\" | \"url\" | \"number\" | \"date\";\n options: string[];\n maxLength?: number;\n min?: number;\n max?: number;\n}\n\nexport type FieldValue = string | boolean;\nexport type FormValues = Record<string, FieldValue>;\n\nconst INPUT_TYPE: Record<PublicFormField[\"type\"], FieldModel[\"inputType\"]> = {\n text: \"text\",\n richtext: \"text\",\n email: \"email\",\n phone: \"tel\",\n url: \"url\",\n number: \"number\",\n boolean: \"text\",\n date: \"date\",\n enum: \"text\",\n};\n\nfunction controlFor(type: PublicFormField[\"type\"]): FieldControl {\n if (type === \"richtext\") return \"textarea\";\n if (type === \"enum\") return \"select\";\n if (type === \"boolean\") return \"checkbox\";\n return \"input\";\n}\n\nexport function toFieldModels(form: PublicForm): FieldModel[] {\n return form.fields.map((f) => ({\n key: f.key,\n label: f.label,\n help: f.help,\n required: f.required === true,\n control: controlFor(f.type),\n inputType: INPUT_TYPE[f.type],\n options: f.constraints?.enum ?? [],\n maxLength: f.constraints?.maxLength,\n min: f.constraints?.min,\n max: f.constraints?.max,\n }));\n}\n\nexport function initialValues(fields: FieldModel[]): FormValues {\n return Object.fromEntries(fields.map((f) => [f.key, f.control === \"checkbox\" ? false : \"\"]));\n}\n\n/** Client-side checks only; the pod re-validates everything. */\nexport function validate(fields: FieldModel[], values: FormValues): Record<string, string> {\n const errors: Record<string, string> = {};\n for (const f of fields) {\n const v = values[f.key];\n if (f.control === \"checkbox\") {\n if (f.required && v !== true) errors[f.key] = \"Required\";\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") {\n if (f.required) errors[f.key] = \"Required\";\n continue;\n }\n if (f.maxLength !== undefined && s.length > f.maxLength) {\n errors[f.key] = `At most ${f.maxLength} characters`;\n } else if (f.control === \"select\" && f.options.length > 0 && !f.options.includes(s)) {\n errors[f.key] = \"Pick one of the options\";\n } else if (f.inputType === \"email\" && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(s)) {\n errors[f.key] = \"Enter a valid email\";\n } else if (f.inputType === \"number\") {\n const n = Number(s);\n if (!Number.isFinite(n)) errors[f.key] = \"Enter a number\";\n else if (f.min !== undefined && n < f.min) errors[f.key] = `At least ${f.min}`;\n else if (f.max !== undefined && n > f.max) errors[f.key] = `At most ${f.max}`;\n }\n }\n return errors;\n}\n\n/** The `fields` payload for `submitForm`: trimmed, empties dropped, numbers typed. */\nexport function toSubmission(fields: FieldModel[], values: FormValues): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of fields) {\n const v = values[f.key];\n if (f.control === \"checkbox\") {\n out[f.key] = v === true;\n continue;\n }\n const s = typeof v === \"string\" ? v.trim() : \"\";\n if (s === \"\") continue;\n out[f.key] = f.inputType === \"number\" ? Number(s) : s;\n }\n return out;\n}\n\n/** Whether the submit button may fire (captcha passthrough). */\nexport function captchaSatisfied(form: PublicForm, captchaToken: string | null): boolean {\n return !form.captcha.required || (captchaToken !== null && captchaToken !== \"\");\n}\n\nexport function loadErrorMessage(error: PublicReadError): string {\n switch (error) {\n case \"not_found\":\n return \"This form isn't available.\";\n case \"rate_limited\":\n return \"Too many requests. Try again in a minute.\";\n case \"doors_disabled\":\n return \"This form isn't accepting responses right now.\";\n case \"network\":\n return \"We couldn't reach the server. Check your connection.\";\n case \"server\":\n return \"Something went wrong loading this form.\";\n }\n}\n\n/** A failed submit, as the form renders it. */\nexport interface SubmitFailure {\n /** One plain sentence for the form; never server detail. */\n message: string;\n /** Sentences by field key, only for keys that are on this form. */\n fieldErrors: Record<string, string>;\n /** Offer \"Try again\": nothing was filed and resending is safe. */\n retryable: boolean;\n /** The ticket is stale: fetch the form again before the next send. */\n refreshForm: boolean;\n}\n\nexport type FailedSubmit = Extract<SubmitResult, { ok: false }>;\n\n/**\n * What to show for a submit that was NOT received. Success is only ever\n * `{ ok: true, received: true }`; every branch here says \"not sent\".\n */\nexport function submitFailure(res: FailedSubmit, fields: FieldModel[]): SubmitFailure {\n const none = { fieldErrors: {}, retryable: false, refreshForm: false };\n switch (res.error) {\n case \"invalid\": {\n const known = new Set(fields.map((f) => f.key));\n const fieldErrors = Object.fromEntries(\n res.fields.filter((k) => known.has(k)).map((k) => [k, \"Check this answer.\"])\n );\n const n = Object.keys(fieldErrors).length;\n return {\n ...none,\n fieldErrors,\n message:\n n > 0\n ? \"Some answers need another look. Your message was not sent.\"\n : \"We couldn't read that as a message. Reload the page and try again.\",\n };\n }\n case \"retry\":\n return {\n ...none,\n retryable: true,\n refreshForm: true,\n message: \"That was sent a little too fast. Your message was not sent; try again.\",\n };\n case \"unavailable\":\n return {\n ...none,\n retryable: true,\n message: \"We couldn't save your message just now. It was not sent; try again.\",\n };\n case \"rate_limited\":\n return { ...none, message: \"Too many attempts. Try again in a minute.\" };\n case \"doors_disabled\":\n return {\n ...none,\n message: \"This form isn't accepting responses right now. Your message was not sent.\",\n };\n case \"network\":\n return {\n ...none,\n retryable: true,\n message: \"We couldn't reach the server. Your message was not sent.\",\n };\n case \"server\":\n return { ...none, message: \"Something went wrong. Your message was not sent.\" };\n }\n}\n\n/** Milliseconds until the pod will accept a submit for a ticket minted at `loadedAt`. */\nexport function submitWaitRemaining(minSubmitMs: number, loadedAt: number, now: number): number {\n const wait = Number.isFinite(minSubmitMs) && minSubmitMs > 0 ? minSubmitMs : 0;\n return Math.max(0, loadedAt + wait - now);\n}\n","/**\n * A deliberately tiny, SAFE markdown reader for published bodies (shares,\n * site records).\n *\n * It produces plain data (blocks of text runs) that React renders as text\n * nodes. It never produces HTML, so there is nothing to sanitize: raw HTML in\n * the source (`<script>`, `<img onerror>`) is shown verbatim as text.\n *\n * Supported: `#`–`######` headings, paragraphs, `-`/`*`/`1.` list items,\n * fenced code blocks, `**bold**`, and `[text](url)` links whose URL is\n * http, https or mailto. Any other link renders as its text only.\n * Everything else is plain text. Extend here — never by rendering HTML.\n */\n\nimport type { PublicBody } from \"./client\";\n\nexport type Inline =\n | { type: \"text\"; text: string }\n | { type: \"strong\"; text: string }\n | { type: \"link\"; text: string; href: string };\n\nexport type Block =\n | { type: \"heading\"; level: 1 | 2 | 3 | 4 | 5 | 6; inlines: Inline[] }\n | { type: \"paragraph\"; inlines: Inline[] }\n | { type: \"list\"; ordered: boolean; items: Inline[][] }\n | { type: \"code\"; text: string };\n\nconst SAFE_PROTOCOLS = new Set([\"http:\", \"https:\", \"mailto:\"]);\n\n/** The href if it is an absolute http/https/mailto URL, else null. */\nexport function safeHref(raw: string): string | null {\n let url: URL;\n try {\n url = new URL(raw.trim());\n } catch {\n return null;\n }\n return SAFE_PROTOCOLS.has(url.protocol) ? url.href : null;\n}\n\n/** A link URL may hold one level of balanced parens (`/wiki/Foo_(bar)`). */\nconst INLINE = /\\*\\*([^*]+)\\*\\*|\\[([^\\]]+)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)\\)/g;\n\nexport function parseInlines(text: string): Inline[] {\n const out: Inline[] = [];\n let last = 0;\n for (const m of text.matchAll(INLINE)) {\n const at = m.index ?? 0;\n if (at > last) out.push({ type: \"text\", text: text.slice(last, at) });\n if (m[1] !== undefined) {\n out.push({ type: \"strong\", text: m[1] });\n } else {\n const href = safeHref(m[3]);\n out.push(href ? { type: \"link\", text: m[2], href } : { type: \"text\", text: m[2] });\n }\n last = at + m[0].length;\n }\n if (last < text.length) out.push({ type: \"text\", text: text.slice(last) });\n return out;\n}\n\nconst HEADING = /^(#{1,6})\\s+(.*)$/;\nconst BULLET = /^\\s*[-*]\\s+(.*)$/;\nconst ORDERED = /^\\s*\\d+[.)]\\s+(.*)$/;\nconst FENCE = /^\\s*(```|~~~)/;\n\nexport function parseSafeMarkdown(source: string): Block[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const blocks: Block[] = [];\n let para: string[] = [];\n let list: { ordered: boolean; items: Inline[][] } | null = null;\n\n const flush = () => {\n if (para.length) blocks.push({ type: \"paragraph\", inlines: parseInlines(para.join(\" \")) });\n para = [];\n if (list) blocks.push({ type: \"list\", ...list });\n list = null;\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const fence = line.match(FENCE);\n if (fence) {\n flush();\n const code: string[] = [];\n i++;\n while (i < lines.length && !lines[i].trimStart().startsWith(fence[1])) code.push(lines[i++]);\n blocks.push({ type: \"code\", text: code.join(\"\\n\") });\n continue;\n }\n if (line.trim() === \"\") {\n flush();\n continue;\n }\n const heading = line.match(HEADING);\n if (heading) {\n flush();\n const level = heading[1].length as 1 | 2 | 3 | 4 | 5 | 6;\n blocks.push({ type: \"heading\", level, inlines: parseInlines(heading[2]) });\n continue;\n }\n const bullet = line.match(BULLET);\n const ordered = bullet ? null : line.match(ORDERED);\n if (bullet || ordered) {\n const isOrdered = ordered !== null;\n if (para.length || (list && list.ordered !== isOrdered)) flush();\n if (!list) list = { ordered: isOrdered, items: [] };\n list.items.push(parseInlines((bullet ?? ordered)![1]));\n continue;\n }\n if (list) flush();\n para.push(line.trim());\n }\n flush();\n return blocks;\n}\n\n/**\n * A published body as a page renders it: markdown -> safe blocks; html -> the\n * document for a sandboxed frame, never parsed as markdown (its tags would show\n * as text). One rule for the share page and the site record page.\n */\nexport function bodyOf(body: PublicBody | null | undefined): { blocks: Block[]; html: string | null } {\n if (!body) return { blocks: [], html: null };\n if (body.format === \"html\") return { blocks: [], html: body.content };\n return { blocks: parseSafeMarkdown(body.content), html: null };\n}\n","/**\n * Published site — pure models for collection and record pages: one value\n * formatter, link-based pagination (`?page=n`, SEO-friendly) and the record\n * view. A page component only renders what these decide.\n *\n * Formatting is deterministic: the locale and time zone default to `en` and\n * `UTC`, never the runtime's, so a server render and a client render agree.\n */\nimport {\n SITE_MAX_LIMIT,\n SITE_MAX_OFFSET,\n type PublicCollectionPage,\n type PublicRecord,\n type PublicRecordSummary,\n type PublicValue,\n} from \"./client\";\nimport { bodyOf, type Block } from \"./safe-markdown\";\n\nexport interface FormatOptions {\n /** BCP 47 locale (default `en`). */\n locale?: string;\n /** IANA zone for date-times (default `UTC`). Plain dates are never shifted. */\n timeZone?: string;\n /** Words for booleans (default Yes / No). */\n labels?: { yes: string; no: string };\n}\n\n/**\n * Group digits only from 5 digits up, so a year stays `2026` and 12345 reads\n * `12,345` (`useGrouping: \"min2\"`, ES2023 Intl; this package's lib is ES2022,\n * hence the cast). An engine without it falls back to always grouping.\n */\nconst GROUPING = { useGrouping: \"min2\" } as unknown as Intl.NumberFormatOptions;\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst ISO_DATE_TIME = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?(Z|[+-]\\d{2}:\\d{2})$/;\n\n/** A real calendar day (`2026-02-30` is not one). */\nfunction isCalendarDay(s: string): boolean {\n const m = ISO_DATE.exec(s);\n if (!m) return false;\n const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));\n return d.toISOString().slice(0, 10) === s;\n}\n\n/**\n * One published value as display text, the same on every site: booleans as\n * words, numbers grouped for the locale, ISO dates and date-times as medium\n * dates (date-times with a short time). Any other string is returned as is.\n * Plain text only: render it as a text node.\n */\nexport function formatPublicValue(value: PublicValue, opts: FormatOptions = {}): string {\n const locale = opts.locale ?? \"en\";\n if (typeof value === \"boolean\") return value ? (opts.labels?.yes ?? \"Yes\") : (opts.labels?.no ?? \"No\");\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? new Intl.NumberFormat(locale, GROUPING).format(value) : String(value);\n }\n if (isCalendarDay(value)) {\n return new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeZone: \"UTC\" }).format(\n new Date(`${value}T00:00:00Z`)\n );\n }\n if (ISO_DATE_TIME.test(value)) {\n const at = new Date(value);\n if (!Number.isNaN(at.getTime())) {\n return new Intl.DateTimeFormat(locale, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone: opts.timeZone ?? \"UTC\",\n }).format(at);\n }\n }\n return value;\n}\n\n// ── Pagination ──────────────────────────────────────────────────────────────\n\n/** Highest page number `parsePageParam` accepts. */\nconst MAX_PAGE_PARAM = 1_000_000;\n\n/**\n * A `?page=` value → a page number. Missing or empty → 1; a positive integer\n * → itself; anything else → null (render the not-found state).\n */\nexport function parsePageParam(raw: string | string[] | null | undefined): number | null {\n if (raw === undefined || raw === null || raw === \"\") return 1;\n if (Array.isArray(raw)) return null;\n if (!/^[1-9]\\d*$/.test(raw)) return null;\n const n = Number(raw);\n return n <= MAX_PAGE_PARAM ? n : null;\n}\n\n/**\n * The `listCollection` query for page `page` (1-based) of `pageSize` items.\n * null when the page lies past the door's offset ceiling (10 000): render\n * the not-found state, never a request that can only be refused.\n */\nexport function pageQuery(page: number, pageSize: number): { offset: number; limit: number } | null {\n if (!Number.isInteger(page) || page < 1) return null;\n const limit = Math.min(SITE_MAX_LIMIT, Math.max(1, Math.floor(pageSize) || 1));\n const offset = (page - 1) * limit;\n return offset <= SITE_MAX_OFFSET ? { offset, limit } : null;\n}\n\nexport interface PageLink {\n page: number;\n /** `basePath` for page 1 (the canonical URL), else `basePath?page=n`. */\n href: string;\n current: boolean;\n}\n\nexport type PageItem = ({ kind: \"page\" } & PageLink) | { kind: \"gap\"; key: string };\n\nexport interface CollectionPageModel {\n items: PublicRecordSummary[];\n total: number;\n /** Current page, 1-based. */\n page: number;\n /** Reachable pages (≥ 1); the door serves no offset past 10 000. */\n pageCount: number;\n pageSize: number;\n /** 1-based positions of the first and last item shown; 0 and 0 when none. */\n from: number;\n to: number;\n /** Nothing is published in this collection. */\n empty: boolean;\n /** Records exist but not on this page (a stale or hand-typed `?page=`). */\n outOfRange: boolean;\n prev: PageLink | null;\n next: PageLink | null;\n /** First, last, and `window` pages either side of the current one, with gaps. */\n pages: PageItem[];\n}\n\nexport interface PageModelOptions {\n /** The collection page's path, e.g. `/papers` (may carry its own query). */\n basePath: string;\n /** Pages shown either side of the current one (default 1). */\n window?: number;\n /** Query parameter name (default `page`). */\n param?: string;\n}\n\nexport function toCollectionPageModel(\n page: PublicCollectionPage,\n opts: PageModelOptions\n): CollectionPageModel {\n const pageSize = Math.max(1, page.limit);\n const current = Math.floor(Math.max(0, page.offset) / pageSize) + 1;\n const reachable = Math.floor(SITE_MAX_OFFSET / pageSize) + 1;\n const pageCount = Math.max(1, Math.min(Math.ceil(page.total / pageSize), reachable));\n const param = opts.param ?? \"page\";\n const sep = opts.basePath.includes(\"?\") ? \"&\" : \"?\";\n const link = (n: number): PageLink => ({\n page: n,\n href: n === 1 ? opts.basePath : `${opts.basePath}${sep}${param}=${n}`,\n current: n === current,\n });\n\n const window = Math.max(0, Math.floor(opts.window ?? 1));\n const shown = new Set<number>([1, pageCount]);\n for (let n = current - window; n <= current + window; n++) {\n if (n >= 1 && n <= pageCount) shown.add(n);\n }\n const pages: PageItem[] = [];\n let last = 0;\n for (const n of [...shown].sort((a, b) => a - b)) {\n if (n - last === 2) pages.push({ kind: \"page\", ...link(n - 1) });\n else if (n - last > 2) pages.push({ kind: \"gap\", key: `gap-${last}` });\n pages.push({ kind: \"page\", ...link(n) });\n last = n;\n }\n\n const count = page.items.length;\n const from = count > 0 ? page.offset + 1 : 0;\n return {\n items: page.items,\n total: page.total,\n page: current,\n pageCount,\n pageSize,\n from,\n to: count > 0 ? page.offset + count : 0,\n empty: page.total === 0,\n outOfRange: page.total > 0 && count === 0,\n prev: current > 1 ? link(Math.min(current - 1, pageCount)) : null,\n next: current < pageCount ? link(current + 1) : null,\n pages,\n };\n}\n\n// ── Record ──────────────────────────────────────────────────────────────────\n\nexport interface RecordViewModel {\n title: string | null;\n publicId: string;\n slug: string | null;\n /** Rows exactly as the publisher allowlisted them, formatted. */\n properties: Array<{ key: string; value: string }>;\n /** A markdown body as safe blocks (`[]` for an html body or none). */\n blocks: Block[];\n /** An html body: render ONLY as `<iframe sandbox=\"\" srcdoc>`. `null` otherwise. */\n html: string | null;\n publishedOn: string;\n}\n\nexport function toRecordViewModel(record: PublicRecord, opts: FormatOptions = {}): RecordViewModel {\n return {\n title: record.title ?? null,\n publicId: record.publicId,\n slug: record.slug,\n properties: Object.entries(record.properties ?? {}).map(([key, value]) => ({\n key,\n value: formatPublicValue(value, opts),\n })),\n ...bodyOf(record.body),\n publishedOn: record.publishedOn,\n };\n}\n","/**\n * Shared-document page — pure model for a `/d/[token]` page.\n *\n * Every failure to read collapses into ONE \"unavailable\" state. `not_found`\n * is already uniform at the pod (revoked, expired and unknown are\n * indistinguishable by design); the page keeps that promise and does not add\n * a distinction of its own. `retryable` only says whether \"try again later\"\n * is honest (a network blip) — it never says anything about the token.\n */\nimport type { PublicReadError, PublicResult, PublicShare } from \"./client\";\nimport { bodyOf, type Block } from \"./safe-markdown\";\nimport { formatPublicValue, type FormatOptions } from \"./site-model\";\n\nexport const UNAVAILABLE_TITLE = \"This link isn't available\";\n\nexport type SharePageModel =\n | { kind: \"unavailable\"; retryable: boolean }\n | {\n kind: \"share\";\n title: string | null;\n /** Property rows exactly as the publisher allowlisted them. */\n properties: Array<{ key: string; value: string }>;\n /** A markdown body as safe blocks (`[]` for an html body or none). */\n blocks: Block[];\n /** An html body: render ONLY as `<iframe sandbox=\"\" srcdoc>`. `null` otherwise. */\n html: string | null;\n publishedOn: string;\n };\n\nfunction isRetryable(error: PublicReadError): boolean {\n return error === \"network\" || error === \"rate_limited\" || error === \"server\";\n}\n\nexport function toSharePageModel(\n result: PublicResult<PublicShare>,\n opts: FormatOptions = {}\n): SharePageModel {\n if (!result.ok) return { kind: \"unavailable\", retryable: isRetryable(result.error) };\n const share = result.data;\n return {\n kind: \"share\",\n title: share.title ?? null,\n properties: Object.entries(share.properties ?? {}).map(([key, value]) => ({\n key,\n value: formatPublicValue(value, opts),\n })),\n ...bodyOf(share.body),\n publishedOn: share.publishedOn,\n };\n}\n\n"],"mappings":"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","names":["opts"]}