@synap-core/sdk 1.1.0 → 1.2.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.
- package/README.md +58 -6
- package/dist/client-C5sF20f1.d.cts +253 -0
- package/dist/client-C5sF20f1.d.ts +253 -0
- package/dist/index.cjs +48 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -7
- package/dist/index.d.ts +118 -7
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/public.cjs +627 -0
- package/dist/public.cjs.map +1 -0
- package/dist/public.d.cts +228 -0
- package/dist/public.d.ts +228 -0
- package/dist/public.js +578 -0
- package/dist/public.js.map +1 -0
- package/dist/testing.cjs +386 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +110 -0
- package/dist/testing.d.ts +110 -0
- package/dist/testing.js +352 -0
- package/dist/testing.js.map +1 -0
- package/package.json +6 -1
- package/src/client.ts +24 -7
- package/src/index.ts +13 -0
- package/src/public/client.ts +579 -0
- package/src/public/form-model.ts +210 -0
- package/src/public/index.ts +12 -0
- package/src/public/safe-markdown.ts +114 -0
- package/src/public/share-model.ts +47 -0
- package/src/public/site-model.ts +217 -0
- package/src/templates.ts +122 -0
- package/src/testing/fixtures.ts +132 -0
- package/src/testing/index.ts +354 -0
- package/src/public.ts +0 -262
|
@@ -0,0 +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":";AA8IO,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AACvB,IAAM,qBAAqB;AAE3B,IAAM,yBAAyB;AAwJtC,IAAM,eAAe;AAErB,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AAEvB,SAAS,OAAO,MAAuB;AACrC,QAAM,IAAK,MAA2C;AACtD,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,KAAK,IAAI,IACtD,KAAK,IAAI,GAAG,kBAAkB,IAC9B;AACN;AAGA,SAAS,SAAS,MAA8D;AAC9E,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,IAAI;AACV,MAAI,EAAE,UAAU,KAAM,QAAO,EAAE,OAAO,KAAK;AAC3C,MAAI,CAAC,MAAM,QAAQ,EAAE,OAAO,EAAG,QAAO;AACtC,SAAO;AAAA,IACL,QAAQ,EAAE,QACP,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,UAAU,cAAc,EAC9E,MAAM,GAAG,gBAAgB;AAAA,EAC9B;AACF;AAGA,IAAM,sBAAsB;AAE5B,SAAS,oBAAoB,MAAwB;AACnD,SACE,OAAO,SAAS,YAChB,SAAS,QACR,KAA4B,SAAS;AAE1C;AAGA,SAAS,SAAS,QAAgB,MAAoD;AACpF,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,OAAO,oBAAoB,IAAI,EAAG,QAAO;AACxD,SAAO;AACT;AAEA,SAAS,aAAa,QAAgB,MAAgC;AACpE,MAAI,WAAW,IAAK,QAAO;AAC3B,MAAI,WAAW,OAAO,WAAW,OAAO,WAAW,IAAK,QAAO;AAC/D,SAAO,SAAS,QAAQ,IAAI;AAC9B;AAGA,IAAM,sBAAsB;AAG5B,SAAS,cAAc,KAAmC;AACxD,QAAM,MAAM,IAAI,QAAQ,IAAI,aAAa,GAAG,KAAK;AACjD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACJ,MAAI,QAAQ,KAAK,GAAG,GAAG;AACrB,UAAM,OAAO,GAAG;AAAA,EAClB,OAAO;AACL,UAAM,KAAK,KAAK,MAAM,GAAG;AACzB,QAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,UAAM,KAAK,MAAM,KAAK,KAAK,IAAI,KAAK,GAAI;AAAA,EAC1C;AACA,SAAO,KAAK,IAAI,KAAK,IAAI,GAAG,GAAG,GAAG,mBAAmB;AACvD;AAGA,SAAS,KAAuB,OAAU,KAAwC;AAChF,QAAM,MAAM,UAAU,kBAAkB,MAAM,cAAc,GAAG,IAAI;AACnE,SAAQ,QAAQ,SAAY,EAAE,IAAI,OAAO,MAAM,IAAI,EAAE,IAAI,OAAO,OAAO,eAAe,IAAI;AAC5F;AAEA,eAAe,SAAS,KAAiC;AACvD,MAAI;AACF,WAAO,MAAM,IAAI,KAAK;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,SAAS,QAAQ,KAA4B;AAC3C,MAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,QAAQ,OAAO,QAAQ,KAAM,QAAO;AACjF,SAAO,mBAAmB,GAAG;AAC/B;AAGA,IAAM,YAAY;AAOlB,SAAS,iBAAiB,GAA+C;AACvE,QAAM,SAAmB,CAAC;AAC1B,MAAI,GAAG,WAAW,QAAW;AAC3B,QAAI,CAAC,OAAO,UAAU,EAAE,MAAM,KAAK,EAAE,SAAS,KAAK,EAAE,SAAS,gBAAiB,QAAO;AACtF,WAAO,KAAK,UAAU,EAAE,MAAM,EAAE;AAAA,EAClC;AACA,MAAI,GAAG,UAAU,QAAW;AAC1B,QAAI,CAAC,OAAO,UAAU,EAAE,KAAK,KAAK,EAAE,QAAQ,EAAG,QAAO;AACtD,WAAO,KAAK,SAAS,KAAK,IAAI,gBAAgB,KAAK,IAAI,GAAG,EAAE,KAAK,CAAC,CAAC,EAAE;AAAA,EACvE;AACA,MAAI,GAAG,SAAS,QAAW;AACzB,UAAM,QAAQ,OAAO,EAAE,SAAS,WAAW,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI;AACzE,QAAI,MAAM,SAAS,mBAAoB,QAAO;AAC9C,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,QAAQ,OAAO;AACxB,YAAM,IAAI,UAAU,KAAK,IAAI;AAC7B,UAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC,CAAC,EAAG,QAAO;AACjC,WAAK,IAAI,EAAE,CAAC,CAAC;AAAA,IACf;AACA,QAAI,MAAM,SAAS,EAAG,QAAO,KAAK,QAAQ,MAAM,KAAK,GAAG,CAAC,EAAE;AAAA,EAC7D;AACA,SAAO,OAAO,SAAS,IAAI,IAAI,OAAO,KAAK,GAAG,CAAC,KAAK;AACtD;AAEO,SAAS,mBAAmB,MAAyC;AAC1E,QAAM,OAAO,GAAG,KAAK,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAC/C,QAAM,UAAU,MAAoB;AAClC,UAAM,IAAI,KAAK,SAAS,WAAW;AACnC,QAAI,CAAC,EAAG,OAAM,IAAI,MAAM,wDAAwD;AAChF,WAAO;AAAA,EACT;AACA,QAAM,MAAM,CAAC,UAA8B,UACzC,GAAG,IAAI,IAAI,QAAQ,IAAI,mBAAmB,KAAK,CAAC;AAElD,iBAAe,KACb,QACA,MAC0B;AAC1B,QAAI;AACF,aAAO,MAAM,QAAQ,EAAE,QAAQ;AAAA,QAC7B,QAAQ,KAAK;AAAA,QACb,aAAa;AAAA,QACb,SACE,KAAK,WAAW,SACZ,EAAE,gBAAgB,mBAAmB,IACrC,KAAK,cACH,EAAE,QAAQ,oBAAoB,iBAAiB,KAAK,YAAY,IAChE,EAAE,QAAQ,mBAAmB;AAAA,QACrC,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACvD,CAAC;AAAA,IACH,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,iBAAe,QACb,UACA,OAC0B;AAC1B,UAAM,MAAM,MAAM,KAAK,IAAI,UAAU,KAAK,GAAG,EAAE,QAAQ,MAAM,CAAC;AAC9D,QAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,OAAO,UAAU;AAC/C,QAAI,IAAI,WAAW,IAAK,QAAO,KAAK,SAAS,IAAI,QAAQ,MAAM,SAAS,GAAG,CAAC,GAAG,GAAG;AAClF,QAAI;AACF,aAAO,EAAE,IAAI,MAAM,MAAO,MAAM,IAAI,KAAK,EAAQ;AAAA,IACnD,QAAQ;AACN,aAAO,EAAE,IAAI,OAAO,OAAO,SAAS;AAAA,IACtC;AAAA,EACF;AAGA,iBAAe,SACb,MACA,aACkD;AAClD,QAAI,SAAS,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,YAAY;AAC1D,UAAM,aAAa,OAAO,gBAAgB,YAAY,gBAAgB,KAAK,cAAc;AACzF,UAAM,MAAM,MAAM,KAAK,GAAG,IAAI,UAAU,IAAI,IAAI,EAAE,QAAQ,OAAO,aAAa,WAAW,CAAC;AAC1F,QAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,OAAO,kBAAkB;AACvD,UAAM,OAAO,IAAI,QAAQ,IAAI,MAAM;AACnC,QAAI,IAAI,WAAW,OAAO,eAAe,QAAW;AAClD,aAAO,EAAE,IAAI,MAAM,aAAa,MAAM,MAAM,QAAQ,WAAW;AAAA,IACjE;AACA,QAAI,IAAI,WAAW,IAAK,QAAO,KAAK,aAAa,IAAI,QAAQ,MAAM,SAAS,GAAG,CAAC,GAAG,GAAG;AACtF,QAAI;AACF,aAAO,EAAE,IAAI,MAAM,MAAO,MAAM,IAAI,KAAK,GAAS,KAAK;AAAA,IACzD,QAAQ;AACN,aAAO,EAAE,IAAI,OAAO,OAAO,SAAS;AAAA,IACtC;AAAA,EACF;AAEA,WAAS,QAAQ,WAAmBA,OAA4B;AAC9D,WAAO,SAAqB,QAAQ,SAAS,GAAGA,OAAM,WAAW;AAAA,EACnE;AAEA,iBAAe,eACb,WACA,KACA,OACqE;AACrE,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,IAAI,QAAQ,GAAG;AACrB,QAAI,UAAU,QAAQ,MAAM,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,YAAY;AACzE,UAAM,SAAS,iBAAiB,KAAK;AACrC,QAAI,WAAW,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,cAAc;AAC9D,WAAO,SAAS,GAAG,KAAK,gBAAgB,CAAC,GAAG,MAAM,IAAI,OAAO,WAAW;AAAA,EAC1E;AAEA,WAAS,UACP,WACA,KACA,gBACAA,OACA;AACA,UAAM,QAAQ,CAAC,QAAQ,SAAS,GAAG,QAAQ,GAAG,GAAG,QAAQ,cAAc,CAAC;AACxE,UAAM,OAAO,MAAM,MAAM,CAAC,MAAM,MAAM,IAAI,IACtC,GAAG,MAAM,CAAC,CAAC,gBAAgB,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAC/C;AACJ,WAAO,SAAuB,MAAMA,OAAM,WAAW;AAAA,EACvD;AAEA,QAAM,QACJ,KAAK,UAAU,CAAC,OAAe,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAEvF,iBAAe,SACb,OACA,OACA,QACmE;AACnE,UAAM,OAAgC;AAAA,MACpC,QAAQ,MAAM;AAAA,MACd,CAAC,YAAY,GAAG;AAAA,IAClB;AACA,QAAI,WAAW,OAAW,MAAK,SAAS;AACxC,QAAI,MAAM,iBAAiB,OAAW,MAAK,eAAe,MAAM;AAChE,QAAI,MAAM,mBAAmB,OAAW,MAAK,iBAAiB,MAAM;AACpE,UAAM,MAAM,MAAM,KAAK,IAAI,SAAS,KAAK,GAAG;AAAA,MAC1C,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,UAAM,OAAO,CAAC,YAA0B,EAAE,OAAO,OAAgB,OAAO;AACxE,QAAI,CAAC,IAAK,QAAO,KAAK,EAAE,IAAI,OAAO,OAAO,UAAU,CAAC;AACrD,QAAI,IAAI,WAAW,IAAK,QAAO,KAAK,EAAE,IAAI,MAAM,UAAU,KAAK,CAAC;AAChE,QAAI,IAAI,WAAW,KAAK;AACtB,UAAI,SAAsC;AAC1C,UAAI;AACF,iBAAS,SAAS,MAAM,IAAI,KAAK,CAAC;AAAA,MACpC,QAAQ;AACN,iBAAS;AAAA,MACX;AACA,UAAI,CAAC,OAAQ,QAAO,KAAK,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC;AACvD,UAAI,WAAW,OAAQ,QAAO,EAAE,OAAO,KAAK;AAC5C,aAAO,KAAK,EAAE,IAAI,OAAO,OAAO,WAAW,QAAQ,OAAO,OAAO,CAAC;AAAA,IACpE;AACA,QAAI,IAAI,WAAW,IAAK,QAAO,KAAK,EAAE,IAAI,OAAO,OAAO,cAAc,CAAC;AACvE,QAAI,IAAI,WAAW,IAAK,QAAO,KAAK,KAAK,gBAAgB,GAAG,CAAC;AAC7D,QAAI,IAAI,WAAW,OAAO,oBAAoB,MAAM,SAAS,GAAG,CAAC,GAAG;AAClE,aAAO,KAAK,EAAE,IAAI,OAAO,OAAO,iBAAiB,CAAC;AAAA,IACpD;AACA,WAAO,KAAK,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC;AAAA,EAC5C;AAEA,SAAO;AAAA,IACL,WAAW,CAAC,UAAU,QAAqB,UAAU,KAAK;AAAA,IAC1D,SAAS,CAAC,UAAU,QAAoB,SAAS,KAAK;AAAA,IACtD,MAAM,WAAW,OAAO,OAAO;AAC7B,YAAM,QAAQ,MAAM,SAAS,OAAO,OAAO,MAAM,MAAM;AACvD,UAAI,CAAC,MAAM,MAAO,QAAO,MAAM;AAG/B,YAAM,QAAQ,MAAM,QAAoB,SAAS,KAAK;AACtD,UAAI,CAAC,MAAM,GAAI,QAAO,EAAE,IAAI,OAAO,OAAO,QAAQ;AAClD,YAAM,MAAM,OAAO,MAAM,IAAI,CAAC;AAC9B,YAAM,SAAS,MAAM,SAAS,OAAO,OAAO,MAAM,KAAK,MAAM;AAC7D,aAAO,OAAO,QAAQ,EAAE,IAAI,OAAO,OAAO,QAAQ,IAAI,OAAO;AAAA,IAC/D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AChiBA,IAAM,aAAuE;AAAA,EAC3E,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,WAAW,MAA6C;AAC/D,MAAI,SAAS,WAAY,QAAO;AAChC,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,UAAW,QAAO;AAC/B,SAAO;AACT;AAEO,SAAS,cAAc,MAAgC;AAC5D,SAAO,KAAK,OAAO,IAAI,CAAC,OAAO;AAAA,IAC7B,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,MAAM,EAAE;AAAA,IACR,UAAU,EAAE,aAAa;AAAA,IACzB,SAAS,WAAW,EAAE,IAAI;AAAA,IAC1B,WAAW,WAAW,EAAE,IAAI;AAAA,IAC5B,SAAS,EAAE,aAAa,QAAQ,CAAC;AAAA,IACjC,WAAW,EAAE,aAAa;AAAA,IAC1B,KAAK,EAAE,aAAa;AAAA,IACpB,KAAK,EAAE,aAAa;AAAA,EACtB,EAAE;AACJ;AAEO,SAAS,cAAc,QAAkC;AAC9D,SAAO,OAAO,YAAY,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,YAAY,aAAa,QAAQ,EAAE,CAAC,CAAC;AAC7F;AAGO,SAAS,SAAS,QAAsB,QAA4C;AACzF,QAAM,SAAiC,CAAC;AACxC,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,OAAO,EAAE,GAAG;AACtB,QAAI,EAAE,YAAY,YAAY;AAC5B,UAAI,EAAE,YAAY,MAAM,KAAM,QAAO,EAAE,GAAG,IAAI;AAC9C;AAAA,IACF;AACA,UAAM,IAAI,OAAO,MAAM,WAAW,EAAE,KAAK,IAAI;AAC7C,QAAI,MAAM,IAAI;AACZ,UAAI,EAAE,SAAU,QAAO,EAAE,GAAG,IAAI;AAChC;AAAA,IACF;AACA,QAAI,EAAE,cAAc,UAAa,EAAE,SAAS,EAAE,WAAW;AACvD,aAAO,EAAE,GAAG,IAAI,WAAW,EAAE,SAAS;AAAA,IACxC,WAAW,EAAE,YAAY,YAAY,EAAE,QAAQ,SAAS,KAAK,CAAC,EAAE,QAAQ,SAAS,CAAC,GAAG;AACnF,aAAO,EAAE,GAAG,IAAI;AAAA,IAClB,WAAW,EAAE,cAAc,WAAW,CAAC,6BAA6B,KAAK,CAAC,GAAG;AAC3E,aAAO,EAAE,GAAG,IAAI;AAAA,IAClB,WAAW,EAAE,cAAc,UAAU;AACnC,YAAM,IAAI,OAAO,CAAC;AAClB,UAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO,EAAE,GAAG,IAAI;AAAA,eAChC,EAAE,QAAQ,UAAa,IAAI,EAAE,IAAK,QAAO,EAAE,GAAG,IAAI,YAAY,EAAE,GAAG;AAAA,eACnE,EAAE,QAAQ,UAAa,IAAI,EAAE,IAAK,QAAO,EAAE,GAAG,IAAI,WAAW,EAAE,GAAG;AAAA,IAC7E;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,aAAa,QAAsB,QAA6C;AAC9F,QAAM,MAA+B,CAAC;AACtC,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAI,OAAO,EAAE,GAAG;AACtB,QAAI,EAAE,YAAY,YAAY;AAC5B,UAAI,EAAE,GAAG,IAAI,MAAM;AACnB;AAAA,IACF;AACA,UAAM,IAAI,OAAO,MAAM,WAAW,EAAE,KAAK,IAAI;AAC7C,QAAI,MAAM,GAAI;AACd,QAAI,EAAE,GAAG,IAAI,EAAE,cAAc,WAAW,OAAO,CAAC,IAAI;AAAA,EACtD;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,MAAkB,cAAsC;AACvF,SAAO,CAAC,KAAK,QAAQ,YAAa,iBAAiB,QAAQ,iBAAiB;AAC9E;AAEO,SAAS,iBAAiB,OAAgC;AAC/D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EACX;AACF;AAoBO,SAAS,cAAc,KAAmB,QAAqC;AACpF,QAAM,OAAO,EAAE,aAAa,CAAC,GAAG,WAAW,OAAO,aAAa,MAAM;AACrE,UAAQ,IAAI,OAAO;AAAA,IACjB,KAAK,WAAW;AACd,YAAM,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAC9C,YAAM,cAAc,OAAO;AAAA,QACzB,IAAI,OAAO,OAAO,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,oBAAoB,CAAC;AAAA,MAC7E;AACA,YAAM,IAAI,OAAO,KAAK,WAAW,EAAE;AACnC,aAAO;AAAA,QACL,GAAG;AAAA,QACH;AAAA,QACA,SACE,IAAI,IACA,+DACA;AAAA,MACR;AAAA,IACF;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW;AAAA,QACX,aAAa;AAAA,QACb,SAAS;AAAA,MACX;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW;AAAA,QACX,SAAS;AAAA,MACX;AAAA,IACF,KAAK;AACH,aAAO,EAAE,GAAG,MAAM,SAAS,4CAA4C;AAAA,IACzE,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS;AAAA,MACX;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,WAAW;AAAA,QACX,SAAS;AAAA,MACX;AAAA,IACF,KAAK;AACH,aAAO,EAAE,GAAG,MAAM,SAAS,mDAAmD;AAAA,EAClF;AACF;AAGO,SAAS,oBAAoB,aAAqB,UAAkB,KAAqB;AAC9F,QAAM,OAAO,OAAO,SAAS,WAAW,KAAK,cAAc,IAAI,cAAc;AAC7E,SAAO,KAAK,IAAI,GAAG,WAAW,OAAO,GAAG;AAC1C;;;ACxLA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,SAAS,UAAU,SAAS,CAAC;AAGtD,SAAS,SAAS,KAA4B;AACnD,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,KAAK,CAAC;AAAA,EAC1B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,eAAe,IAAI,IAAI,QAAQ,IAAI,IAAI,OAAO;AACvD;AAGA,IAAM,SAAS;AAER,SAAS,aAAa,MAAwB;AACnD,QAAM,MAAgB,CAAC;AACvB,MAAI,OAAO;AACX,aAAW,KAAK,KAAK,SAAS,MAAM,GAAG;AACrC,UAAM,KAAK,EAAE,SAAS;AACtB,QAAI,KAAK,KAAM,KAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,MAAM,MAAM,EAAE,EAAE,CAAC;AACpE,QAAI,EAAE,CAAC,MAAM,QAAW;AACtB,UAAI,KAAK,EAAE,MAAM,UAAU,MAAM,EAAE,CAAC,EAAE,CAAC;AAAA,IACzC,OAAO;AACL,YAAM,OAAO,SAAS,EAAE,CAAC,CAAC;AAC1B,UAAI,KAAK,OAAO,EAAE,MAAM,QAAQ,MAAM,EAAE,CAAC,GAAG,KAAK,IAAI,EAAE,MAAM,QAAQ,MAAM,EAAE,CAAC,EAAE,CAAC;AAAA,IACnF;AACA,WAAO,KAAK,EAAE,CAAC,EAAE;AAAA,EACnB;AACA,MAAI,OAAO,KAAK,OAAQ,KAAI,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC;AACzE,SAAO;AACT;AAEA,IAAM,UAAU;AAChB,IAAM,SAAS;AACf,IAAM,UAAU;AAChB,IAAM,QAAQ;AAEP,SAAS,kBAAkB,QAAyB;AACzD,QAAM,QAAQ,OAAO,QAAQ,UAAU,IAAI,EAAE,MAAM,IAAI;AACvD,QAAM,SAAkB,CAAC;AACzB,MAAI,OAAiB,CAAC;AACtB,MAAI,OAAuD;AAE3D,QAAM,QAAQ,MAAM;AAClB,QAAI,KAAK,OAAQ,QAAO,KAAK,EAAE,MAAM,aAAa,SAAS,aAAa,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;AACzF,WAAO,CAAC;AACR,QAAI,KAAM,QAAO,KAAK,EAAE,MAAM,QAAQ,GAAG,KAAK,CAAC;AAC/C,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,QAAI,OAAO;AACT,YAAM;AACN,YAAM,OAAiB,CAAC;AACxB;AACA,aAAO,IAAI,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,WAAW,MAAM,CAAC,CAAC,EAAG,MAAK,KAAK,MAAM,GAAG,CAAC;AAC3F,aAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,KAAK,IAAI,EAAE,CAAC;AACnD;AAAA,IACF;AACA,QAAI,KAAK,KAAK,MAAM,IAAI;AACtB,YAAM;AACN;AAAA,IACF;AACA,UAAM,UAAU,KAAK,MAAM,OAAO;AAClC,QAAI,SAAS;AACX,YAAM;AACN,YAAM,QAAQ,QAAQ,CAAC,EAAE;AACzB,aAAO,KAAK,EAAE,MAAM,WAAW,OAAO,SAAS,aAAa,QAAQ,CAAC,CAAC,EAAE,CAAC;AACzE;AAAA,IACF;AACA,UAAM,SAAS,KAAK,MAAM,MAAM;AAChC,UAAM,UAAU,SAAS,OAAO,KAAK,MAAM,OAAO;AAClD,QAAI,UAAU,SAAS;AACrB,YAAM,YAAY,YAAY;AAC9B,UAAI,KAAK,UAAW,QAAQ,KAAK,YAAY,UAAY,OAAM;AAC/D,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,WAAW,OAAO,CAAC,EAAE;AAClD,WAAK,MAAM,KAAK,cAAc,UAAU,SAAU,CAAC,CAAC,CAAC;AACrD;AAAA,IACF;AACA,QAAI,KAAM,OAAM;AAChB,SAAK,KAAK,KAAK,KAAK,CAAC;AAAA,EACvB;AACA,QAAM;AACN,SAAO;AACT;;;ACjFA,IAAM,WAAW,EAAE,aAAa,OAAO;AAEvC,IAAM,WAAW;AACjB,IAAM,gBAAgB;AAGtB,SAAS,cAAc,GAAoB;AACzC,QAAM,IAAI,SAAS,KAAK,CAAC;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,IAAI,KAAK,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AACzE,SAAO,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,MAAM;AAC1C;AAQO,SAAS,kBAAkB,OAAoB,OAAsB,CAAC,GAAW;AACtF,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,OAAO,UAAU,UAAW,QAAO,QAAS,KAAK,QAAQ,OAAO,QAAU,KAAK,QAAQ,MAAM;AACjG,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,IAAI,KAAK,aAAa,QAAQ,QAAQ,EAAE,OAAO,KAAK,IAAI,OAAO,KAAK;AAAA,EACtG;AACA,MAAI,cAAc,KAAK,GAAG;AACxB,WAAO,IAAI,KAAK,eAAe,QAAQ,EAAE,WAAW,UAAU,UAAU,MAAM,CAAC,EAAE;AAAA,MAC/E,oBAAI,KAAK,GAAG,KAAK,YAAY;AAAA,IAC/B;AAAA,EACF;AACA,MAAI,cAAc,KAAK,KAAK,GAAG;AAC7B,UAAM,KAAK,IAAI,KAAK,KAAK;AACzB,QAAI,CAAC,OAAO,MAAM,GAAG,QAAQ,CAAC,GAAG;AAC/B,aAAO,IAAI,KAAK,eAAe,QAAQ;AAAA,QACrC,WAAW;AAAA,QACX,WAAW;AAAA,QACX,UAAU,KAAK,YAAY;AAAA,MAC7B,CAAC,EAAE,OAAO,EAAE;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAKA,IAAM,iBAAiB;AAMhB,SAAS,eAAe,KAA0D;AACvF,MAAI,QAAQ,UAAa,QAAQ,QAAQ,QAAQ,GAAI,QAAO;AAC5D,MAAI,MAAM,QAAQ,GAAG,EAAG,QAAO;AAC/B,MAAI,CAAC,aAAa,KAAK,GAAG,EAAG,QAAO;AACpC,QAAM,IAAI,OAAO,GAAG;AACpB,SAAO,KAAK,iBAAiB,IAAI;AACnC;AAOO,SAAS,UAAU,MAAc,UAA4D;AAClG,MAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,EAAG,QAAO;AAChD,QAAM,QAAQ,KAAK,IAAI,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC7E,QAAM,UAAU,OAAO,KAAK;AAC5B,SAAO,UAAU,kBAAkB,EAAE,QAAQ,MAAM,IAAI;AACzD;AAyCO,SAAS,sBACd,MACA,MACqB;AACrB,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,KAAK;AACvC,QAAM,UAAU,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,QAAQ,IAAI;AAClE,QAAM,YAAY,KAAK,MAAM,kBAAkB,QAAQ,IAAI;AAC3D,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,SAAS,CAAC;AACnF,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,MAAM,KAAK,SAAS,SAAS,GAAG,IAAI,MAAM;AAChD,QAAM,OAAO,CAAC,OAAyB;AAAA,IACrC,MAAM;AAAA,IACN,MAAM,MAAM,IAAI,KAAK,WAAW,GAAG,KAAK,QAAQ,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC;AAAA,IACnE,SAAS,MAAM;AAAA,EACjB;AAEA,QAAM,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,UAAU,CAAC,CAAC;AACvD,QAAM,QAAQ,oBAAI,IAAY,CAAC,GAAG,SAAS,CAAC;AAC5C,WAAS,IAAI,UAAU,QAAQ,KAAK,UAAU,QAAQ,KAAK;AACzD,QAAI,KAAK,KAAK,KAAK,UAAW,OAAM,IAAI,CAAC;AAAA,EAC3C;AACA,QAAM,QAAoB,CAAC;AAC3B,MAAI,OAAO;AACX,aAAW,KAAK,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG;AAChD,QAAI,IAAI,SAAS,EAAG,OAAM,KAAK,EAAE,MAAM,QAAQ,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,aACtD,IAAI,OAAO,EAAG,OAAM,KAAK,EAAE,MAAM,OAAO,KAAK,OAAO,IAAI,GAAG,CAAC;AACrE,UAAM,KAAK,EAAE,MAAM,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,KAAK,MAAM;AACzB,QAAM,OAAO,QAAQ,IAAI,KAAK,SAAS,IAAI;AAC3C,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,OAAO,KAAK;AAAA,IACZ,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI,QAAQ,IAAI,KAAK,SAAS,QAAQ;AAAA,IACtC,OAAO,KAAK,UAAU;AAAA,IACtB,YAAY,KAAK,QAAQ,KAAK,UAAU;AAAA,IACxC,MAAM,UAAU,IAAI,KAAK,KAAK,IAAI,UAAU,GAAG,SAAS,CAAC,IAAI;AAAA,IAC7D,MAAM,UAAU,YAAY,KAAK,UAAU,CAAC,IAAI;AAAA,IAChD;AAAA,EACF;AACF;AAeO,SAAS,kBAAkB,QAAsB,OAAsB,CAAC,GAAoB;AACjG,SAAO;AAAA,IACL,OAAO,OAAO,SAAS;AAAA,IACvB,UAAU,OAAO;AAAA,IACjB,MAAM,OAAO;AAAA,IACb,YAAY,OAAO,QAAQ,OAAO,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,MACzE;AAAA,MACA,OAAO,kBAAkB,OAAO,IAAI;AAAA,IACtC,EAAE;AAAA,IACF,QAAQ,OAAO,OAAO,kBAAkB,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,IAChE,aAAa,OAAO;AAAA,EACtB;AACF;;;AC3MO,IAAM,oBAAoB;AAajC,SAAS,YAAY,OAAiC;AACpD,SAAO,UAAU,aAAa,UAAU,kBAAkB,UAAU;AACtE;AAEO,SAAS,iBACd,QACA,OAAsB,CAAC,GACP;AAChB,MAAI,CAAC,OAAO,GAAI,QAAO,EAAE,MAAM,eAAe,WAAW,YAAY,OAAO,KAAK,EAAE;AACnF,QAAM,QAAQ,OAAO;AACrB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,MAAM,SAAS;AAAA,IACtB,YAAY,OAAO,QAAQ,MAAM,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,MACxE;AAAA,MACA,OAAO,kBAAkB,OAAO,IAAI;AAAA,IACtC,EAAE;AAAA,IACF,QAAQ,MAAM,OAAO,kBAAkB,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA,IAC9D,aAAa,MAAM;AAAA,EACrB;AACF;","names":["opts"]}
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/testing/index.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
MOCK_FAILURE_TOKENS: () => MOCK_FAILURE_TOKENS,
|
|
24
|
+
MOCK_FORM: () => MOCK_FORM,
|
|
25
|
+
MOCK_POD_URL: () => MOCK_POD_URL,
|
|
26
|
+
MOCK_RETRY_AFTER_SEC: () => MOCK_RETRY_AFTER_SEC,
|
|
27
|
+
MOCK_SHARE: () => MOCK_SHARE,
|
|
28
|
+
MOCK_SITE: () => MOCK_SITE,
|
|
29
|
+
MOCK_TOKENS: () => MOCK_TOKENS,
|
|
30
|
+
createMockPod: () => createMockPod
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(testing_exports);
|
|
33
|
+
|
|
34
|
+
// src/testing/fixtures.ts
|
|
35
|
+
var MOCK_FORM = {
|
|
36
|
+
title: "Get in touch",
|
|
37
|
+
fields: [
|
|
38
|
+
{ key: "name", label: "Name", type: "text", required: true, constraints: { maxLength: 120 } },
|
|
39
|
+
{ key: "email", label: "Email", type: "email", required: true },
|
|
40
|
+
{
|
|
41
|
+
key: "topic",
|
|
42
|
+
label: "Topic",
|
|
43
|
+
type: "enum",
|
|
44
|
+
constraints: { enum: ["Sales", "Support", "Partnership"] }
|
|
45
|
+
},
|
|
46
|
+
{ key: "message", label: "Message", type: "richtext", help: "A few lines is plenty." }
|
|
47
|
+
],
|
|
48
|
+
successMessage: "Thanks \u2014 we received your message.",
|
|
49
|
+
honeypotField: "hp",
|
|
50
|
+
ticket: "mock-ticket",
|
|
51
|
+
minSubmitMs: 0,
|
|
52
|
+
captcha: { required: false }
|
|
53
|
+
};
|
|
54
|
+
var MOCK_SHARE = {
|
|
55
|
+
resourceType: "entity",
|
|
56
|
+
title: "Launch notes",
|
|
57
|
+
properties: { status: "open", owner: "Ada", featured: true },
|
|
58
|
+
body: {
|
|
59
|
+
format: "markdown",
|
|
60
|
+
content: [
|
|
61
|
+
"# Launch notes",
|
|
62
|
+
"",
|
|
63
|
+
"This page is a **published snapshot** from a Synap pod.",
|
|
64
|
+
"",
|
|
65
|
+
"- Read the [Synap docs](https://synap.live)",
|
|
66
|
+
"- Links other than http, https and mailto render as plain text: [bad](javascript:alert(1))",
|
|
67
|
+
"",
|
|
68
|
+
"<script>raw HTML is shown as text, never run</script>"
|
|
69
|
+
].join("\n")
|
|
70
|
+
},
|
|
71
|
+
publishedOn: "2026-09-27"
|
|
72
|
+
};
|
|
73
|
+
function publicId(prefix, n) {
|
|
74
|
+
return `p_${prefix}${String(n).padStart(16 - prefix.length, "0")}`;
|
|
75
|
+
}
|
|
76
|
+
function slugify(s) {
|
|
77
|
+
return s.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
78
|
+
}
|
|
79
|
+
var PAPER_TITLES = [
|
|
80
|
+
"Sparse graphs at scale",
|
|
81
|
+
"Learning to rank, revisited",
|
|
82
|
+
"A note on causal tracing",
|
|
83
|
+
"Small models, long contexts",
|
|
84
|
+
"Evaluating retrieval honestly",
|
|
85
|
+
"Robust tokenisation",
|
|
86
|
+
"Graphs that forget",
|
|
87
|
+
"Calibration under shift",
|
|
88
|
+
"Latent space cartography",
|
|
89
|
+
"Proofs as programs, again",
|
|
90
|
+
"Cheap uncertainty",
|
|
91
|
+
"R\xE9sum\xE9 of a decade"
|
|
92
|
+
];
|
|
93
|
+
var VENUES = ["NeurIPS", "ICML", "ACL", "ICLR"];
|
|
94
|
+
var PAPERS = PAPER_TITLES.map((title, i) => ({
|
|
95
|
+
collection: "paper",
|
|
96
|
+
publicId: publicId("paper", i + 1),
|
|
97
|
+
slug: slugify(title),
|
|
98
|
+
title,
|
|
99
|
+
properties: {
|
|
100
|
+
year: 2026 - i % 5,
|
|
101
|
+
venue: VENUES[i % VENUES.length],
|
|
102
|
+
open: i % 3 !== 0
|
|
103
|
+
},
|
|
104
|
+
publishedOn: `2026-09-${String(28 - i).padStart(2, "0")}`,
|
|
105
|
+
body: i % 4 === 3 ? null : { format: "markdown", content: `# ${title}
|
|
106
|
+
|
|
107
|
+
Abstract of **${title}**.` }
|
|
108
|
+
}));
|
|
109
|
+
var PEOPLE = ["Ada Lovelace", "Alan Turing", "Grace Hopper"].map((name, i) => ({
|
|
110
|
+
collection: "person",
|
|
111
|
+
publicId: publicId("person", i + 1),
|
|
112
|
+
slug: null,
|
|
113
|
+
title: name,
|
|
114
|
+
properties: { name, role: i === 0 ? "Lead" : "Researcher" },
|
|
115
|
+
publishedOn: "2026-09-01",
|
|
116
|
+
body: null
|
|
117
|
+
}));
|
|
118
|
+
var MOCK_SITE = {
|
|
119
|
+
title: "Demo lab",
|
|
120
|
+
description: "What the lab publishes",
|
|
121
|
+
publishedOn: "2026-09-01",
|
|
122
|
+
collections: {
|
|
123
|
+
paper: { sort: ["-year"], pageSize: 5, addressing: "slug", records: PAPERS },
|
|
124
|
+
person: { sort: ["name"], pageSize: 20, addressing: "publicId", records: PEOPLE },
|
|
125
|
+
event: { sort: [], pageSize: 10, addressing: "slug", records: [] }
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/testing/index.ts
|
|
130
|
+
var MOCK_POD_URL = "https://mock.synap.invalid";
|
|
131
|
+
var MOCK_TOKENS = { form: "demo-form", share: "demo", site: "demo-site" };
|
|
132
|
+
var MOCK_RETRY_AFTER_SEC = 30;
|
|
133
|
+
var MOCK_FAILURE_TOKENS = {
|
|
134
|
+
/** 429 with no `Retry-After` (what the pod's public doors send today). */
|
|
135
|
+
rateLimited: "mock-rate-limited",
|
|
136
|
+
/** 429 with `Retry-After: 30`. */
|
|
137
|
+
rateLimitedRetryAfter: "mock-rate-limited-retry-after",
|
|
138
|
+
/** 403 `public_doors_disabled`: the owner switched public pages off. */
|
|
139
|
+
doorsDisabled: "mock-doors-disabled",
|
|
140
|
+
/** 500. */
|
|
141
|
+
serverError: "mock-server-error",
|
|
142
|
+
/** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */
|
|
143
|
+
unavailable: "mock-unavailable",
|
|
144
|
+
/** No answer: the fetch rejects (`network` / `pod_unreachable`). */
|
|
145
|
+
unreachable: "mock-unreachable"
|
|
146
|
+
};
|
|
147
|
+
var NOT_FOUND = { error: "Not found" };
|
|
148
|
+
var DOORS_DISABLED = {
|
|
149
|
+
error: "The owner has not turned on public pages and forms for this workspace.",
|
|
150
|
+
code: "public_doors_disabled"
|
|
151
|
+
};
|
|
152
|
+
var SAFELISTED = /* @__PURE__ */ new Set(["accept", "accept-language", "content-language", "content-type"]);
|
|
153
|
+
var PREFLIGHT_ALLOWED = /* @__PURE__ */ new Set(["content-type"]);
|
|
154
|
+
var PUBLIC_ID_RE = /^p_[A-Za-z0-9]{16}$/;
|
|
155
|
+
var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
156
|
+
var SLUG_MAX_LENGTH = 96;
|
|
157
|
+
var DIGITS = /^\d{1,9}$/;
|
|
158
|
+
var SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;
|
|
159
|
+
var MAX_OFFSET = 1e4;
|
|
160
|
+
var MAX_LIMIT = 100;
|
|
161
|
+
var MAX_SORT_KEYS = 3;
|
|
162
|
+
var PUBLISHED_AT = "publishedAt";
|
|
163
|
+
function json(status, body, headers = {}) {
|
|
164
|
+
return new Response(JSON.stringify(body), {
|
|
165
|
+
status,
|
|
166
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", ...headers }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function weakEtag(value) {
|
|
170
|
+
let h = 2166136261;
|
|
171
|
+
for (const ch of JSON.stringify(value)) {
|
|
172
|
+
h ^= ch.charCodeAt(0);
|
|
173
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
174
|
+
}
|
|
175
|
+
return `W/"${h.toString(16).padStart(8, "0")}"`;
|
|
176
|
+
}
|
|
177
|
+
function etagMatches(header, etag) {
|
|
178
|
+
if (!header) return false;
|
|
179
|
+
if (header.trim() === "*") return true;
|
|
180
|
+
const strip = (t) => t.trim().replace(/^W\//, "");
|
|
181
|
+
return header.split(",").some((t) => strip(t) === strip(etag));
|
|
182
|
+
}
|
|
183
|
+
function failure(token) {
|
|
184
|
+
switch (token) {
|
|
185
|
+
case MOCK_FAILURE_TOKENS.rateLimited:
|
|
186
|
+
return json(429, { error: "Too many requests", retryAfter: "5 minutes" });
|
|
187
|
+
case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:
|
|
188
|
+
return json(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
|
|
189
|
+
case MOCK_FAILURE_TOKENS.doorsDisabled:
|
|
190
|
+
return json(403, DOORS_DISABLED);
|
|
191
|
+
case MOCK_FAILURE_TOKENS.serverError:
|
|
192
|
+
return json(500, { error: "Internal error" });
|
|
193
|
+
case MOCK_FAILURE_TOKENS.unavailable:
|
|
194
|
+
return json(503, { received: false });
|
|
195
|
+
case MOCK_FAILURE_TOKENS.unreachable:
|
|
196
|
+
return "reject";
|
|
197
|
+
default:
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function summary(record) {
|
|
202
|
+
const { body: _body, ...rest } = record;
|
|
203
|
+
return rest;
|
|
204
|
+
}
|
|
205
|
+
function manifest(site) {
|
|
206
|
+
return {
|
|
207
|
+
...site.title !== void 0 ? { title: site.title } : {},
|
|
208
|
+
...site.description !== void 0 ? { description: site.description } : {},
|
|
209
|
+
publishedOn: site.publishedOn,
|
|
210
|
+
collections: Object.keys(site.collections).sort().map((key) => {
|
|
211
|
+
const c = site.collections[key];
|
|
212
|
+
return { key, count: c.records.length, sort: [...c.sort], pageSize: c.pageSize, addressing: c.addressing };
|
|
213
|
+
})
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function parseQuery(params, c) {
|
|
217
|
+
for (const name of params.keys()) {
|
|
218
|
+
if (name === "embed") return "embed is not available";
|
|
219
|
+
if (name === "ref" || name.startsWith("ref[")) return "ref filter is not available";
|
|
220
|
+
}
|
|
221
|
+
const one = (name) => {
|
|
222
|
+
const all = params.getAll(name);
|
|
223
|
+
return all.length === 0 ? void 0 : all.length === 1 ? all[0] : null;
|
|
224
|
+
};
|
|
225
|
+
const rawOffset = one("offset");
|
|
226
|
+
if (rawOffset === null || rawOffset !== void 0 && !DIGITS.test(rawOffset)) return "invalid offset";
|
|
227
|
+
const offset = rawOffset === void 0 ? 0 : Number(rawOffset);
|
|
228
|
+
if (offset > MAX_OFFSET) return "offset too large";
|
|
229
|
+
const rawLimit = one("limit");
|
|
230
|
+
if (rawLimit === null || rawLimit !== void 0 && !DIGITS.test(rawLimit)) return "invalid limit";
|
|
231
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, rawLimit === void 0 ? c.pageSize : Number(rawLimit)));
|
|
232
|
+
const rawSort = one("sort");
|
|
233
|
+
if (rawSort === null) return "invalid sort";
|
|
234
|
+
const terms = rawSort !== void 0 ? rawSort.split(",") : c.sort.length > 0 ? [...c.sort] : [`-${PUBLISHED_AT}`];
|
|
235
|
+
if (terms.length > MAX_SORT_KEYS) return "invalid sort";
|
|
236
|
+
const allowed = /* @__PURE__ */ new Set([PUBLISHED_AT, ...c.sort.map((s) => s.replace(/^-/, ""))]);
|
|
237
|
+
const sort = [];
|
|
238
|
+
for (const term of terms) {
|
|
239
|
+
const m = SORT_TERM.exec(term);
|
|
240
|
+
if (!m || !allowed.has(m[2]) || sort.some((s) => s.key === m[2])) return "invalid sort";
|
|
241
|
+
sort.push({ key: m[2], desc: m[1] === "-" });
|
|
242
|
+
}
|
|
243
|
+
return { offset, limit, sort };
|
|
244
|
+
}
|
|
245
|
+
function compareRecords(sort) {
|
|
246
|
+
const rank = (v) => typeof v === "string" ? 0 : typeof v === "number" ? 1 : 2;
|
|
247
|
+
const valueOf = (r, key) => key === PUBLISHED_AT ? r.publishedOn : r.properties[key];
|
|
248
|
+
return (a, b) => {
|
|
249
|
+
for (const { key, desc } of sort) {
|
|
250
|
+
const va = valueOf(a, key);
|
|
251
|
+
const vb = valueOf(b, key);
|
|
252
|
+
if (va === void 0 || vb === void 0) {
|
|
253
|
+
if (va !== vb) return va === void 0 ? 1 : -1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
let d = rank(va) - rank(vb);
|
|
257
|
+
if (d === 0) d = va < vb ? -1 : va > vb ? 1 : 0;
|
|
258
|
+
if (d !== 0) return desc ? -d : d;
|
|
259
|
+
}
|
|
260
|
+
if (a.publishedOn !== b.publishedOn) return a.publishedOn < b.publishedOn ? 1 : -1;
|
|
261
|
+
return a.publicId < b.publicId ? -1 : a.publicId > b.publicId ? 1 : 0;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function cached(view, ifNoneMatch) {
|
|
265
|
+
const etag = weakEtag(view);
|
|
266
|
+
if (etagMatches(ifNoneMatch, etag)) {
|
|
267
|
+
return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
|
|
268
|
+
}
|
|
269
|
+
return json(200, view, { ETag: etag });
|
|
270
|
+
}
|
|
271
|
+
function siteDoor(site, rest, params, ifNoneMatch) {
|
|
272
|
+
if (rest.length === 0) return cached(manifest(site), ifNoneMatch);
|
|
273
|
+
if (rest[0] !== "collections" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {
|
|
274
|
+
return json(404, NOT_FOUND);
|
|
275
|
+
}
|
|
276
|
+
const c = site.collections[rest[1]];
|
|
277
|
+
if (rest.length === 2) {
|
|
278
|
+
const q = parseQuery(params, c);
|
|
279
|
+
if (typeof q === "string") return json(400, { error: q });
|
|
280
|
+
const sorted = [...c.records].sort(compareRecords(q.sort));
|
|
281
|
+
const page = {
|
|
282
|
+
items: sorted.slice(q.offset, q.offset + q.limit).map(summary),
|
|
283
|
+
total: sorted.length,
|
|
284
|
+
offset: q.offset,
|
|
285
|
+
limit: q.limit
|
|
286
|
+
};
|
|
287
|
+
return cached(page, ifNoneMatch);
|
|
288
|
+
}
|
|
289
|
+
const address = rest[2];
|
|
290
|
+
const byPublicId = PUBLIC_ID_RE.test(address);
|
|
291
|
+
if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);
|
|
292
|
+
const record = c.records.find((r) => byPublicId ? r.publicId === address : r.slug === address);
|
|
293
|
+
if (!record) return json(404, NOT_FOUND);
|
|
294
|
+
if (params.has("embed")) return json(400, { error: "embed is not available" });
|
|
295
|
+
return cached(record, ifNoneMatch);
|
|
296
|
+
}
|
|
297
|
+
function invalidKeys(form, body) {
|
|
298
|
+
const fields = body?.fields;
|
|
299
|
+
if (typeof fields !== "object" || fields === null) return [];
|
|
300
|
+
const answers = fields;
|
|
301
|
+
return form.fields.filter((f) => {
|
|
302
|
+
const v = answers[f.key];
|
|
303
|
+
if (v === void 0 || v === "" || v === null) return f.required === true;
|
|
304
|
+
if (f.type === "boolean") return typeof v !== "boolean" || f.required === true && v !== true;
|
|
305
|
+
const allowed = f.constraints?.enum;
|
|
306
|
+
return allowed !== void 0 && !allowed.includes(String(v));
|
|
307
|
+
}).map((f) => f.key);
|
|
308
|
+
}
|
|
309
|
+
function createMockPod(options = {}) {
|
|
310
|
+
const forms = options.forms ?? { [MOCK_TOKENS.form]: MOCK_FORM };
|
|
311
|
+
const shares = options.shares ?? { [MOCK_TOKENS.share]: MOCK_SHARE };
|
|
312
|
+
const sites = options.sites ?? { [MOCK_TOKENS.site]: MOCK_SITE };
|
|
313
|
+
const requests = [];
|
|
314
|
+
const submissions = [];
|
|
315
|
+
const mockFetch = async (input, init) => {
|
|
316
|
+
const request = typeof input === "object" && "method" in input ? input : null;
|
|
317
|
+
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
318
|
+
const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
|
|
319
|
+
const headers = {};
|
|
320
|
+
new Headers(init?.headers ?? request?.headers).forEach((value, name) => {
|
|
321
|
+
headers[name.toLowerCase()] = value;
|
|
322
|
+
});
|
|
323
|
+
const credentials = init?.credentials ?? request?.credentials;
|
|
324
|
+
requests.push({ method, url: url.href, headers, credentials });
|
|
325
|
+
if (credentials === "include") throw new TypeError("Failed to fetch: credentialed request to a public door");
|
|
326
|
+
if (options.browser) {
|
|
327
|
+
const preflighted = Object.keys(headers).filter((h) => !SAFELISTED.has(h) && !PREFLIGHT_ALLOWED.has(h));
|
|
328
|
+
if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(", ")}`);
|
|
329
|
+
}
|
|
330
|
+
const m = url.pathname.match(/^\/api\/hub\/public\/(forms|shares|sites)\/(.+)$/);
|
|
331
|
+
if (!m) return json(404, NOT_FOUND);
|
|
332
|
+
let segments;
|
|
333
|
+
try {
|
|
334
|
+
segments = m[2].split("/").map(decodeURIComponent);
|
|
335
|
+
} catch {
|
|
336
|
+
return json(404, NOT_FOUND);
|
|
337
|
+
}
|
|
338
|
+
const [token, ...rest] = segments;
|
|
339
|
+
const failed = failure(token);
|
|
340
|
+
if (failed === "reject") throw new TypeError("Failed to fetch: mock pod unreachable");
|
|
341
|
+
if (failed) return failed;
|
|
342
|
+
const door = m[1];
|
|
343
|
+
if (door === "forms" && method === "POST" && rest.length === 0) {
|
|
344
|
+
let body = null;
|
|
345
|
+
try {
|
|
346
|
+
body = JSON.parse(typeof init?.body === "string" ? init.body : "null");
|
|
347
|
+
} catch {
|
|
348
|
+
body = null;
|
|
349
|
+
}
|
|
350
|
+
const form = Object.hasOwn(forms, token) ? forms[token] : void 0;
|
|
351
|
+
const invalid = form ? invalidKeys(form, body) : [];
|
|
352
|
+
const status = invalid.length > 0 ? 422 : 202;
|
|
353
|
+
submissions.push({ token, body, status });
|
|
354
|
+
return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });
|
|
355
|
+
}
|
|
356
|
+
if (method !== "GET") return json(404, NOT_FOUND);
|
|
357
|
+
if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);
|
|
358
|
+
if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);
|
|
359
|
+
if (door === "sites" && Object.hasOwn(sites, token)) {
|
|
360
|
+
return siteDoor(sites[token], rest, url.searchParams, headers["if-none-match"] ?? null);
|
|
361
|
+
}
|
|
362
|
+
return json(404, NOT_FOUND);
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
podUrl: MOCK_POD_URL,
|
|
366
|
+
fetch: mockFetch,
|
|
367
|
+
requests,
|
|
368
|
+
submissions,
|
|
369
|
+
reset() {
|
|
370
|
+
requests.length = 0;
|
|
371
|
+
submissions.length = 0;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
376
|
+
0 && (module.exports = {
|
|
377
|
+
MOCK_FAILURE_TOKENS,
|
|
378
|
+
MOCK_FORM,
|
|
379
|
+
MOCK_POD_URL,
|
|
380
|
+
MOCK_RETRY_AFTER_SEC,
|
|
381
|
+
MOCK_SHARE,
|
|
382
|
+
MOCK_SITE,
|
|
383
|
+
MOCK_TOKENS,
|
|
384
|
+
createMockPod
|
|
385
|
+
});
|
|
386
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing/index.ts","../src/testing/fixtures.ts"],"sourcesContent":["/**\n * @synap-core/sdk/testing — a mock pod: a fake `fetch` that answers the pod's\n * public doors (forms, shares, sites) from fixtures, with no network. Inject\n * it into the REAL client (`createPublicClient({ podUrl, fetch: pod.fetch })`)\n * so everything above the wire — status handling, typed errors, the models —\n * is the production path. For unit tests, a site's mock mode and a kit's\n * states gallery.\n *\n * Wire behaviour mirrors the backend doors: the uniform 404 for every miss,\n * the constant 202 on a submit, the site doors' query bounds (400), weak\n * ETags with If-None-Match → 304. Like a browser against the real doors, a\n * `credentials: \"include\"` request is refused (it rejects, as a CORS failure\n * does); with `{ browser: true }` a non-safelisted request header is too.\n *\n * tokens MOCK_TOKENS.form / .share / .site the demo form, share and site\n * tokens MOCK_FAILURE_TOKENS.* one failure on every door\n * anything else the uniform 404\n */\nimport type {\n PublicCollectionPage,\n PublicForm,\n PublicRecord,\n PublicRecordSummary,\n PublicShare,\n PublicSite,\n} from \"../public/client\";\nimport { MOCK_FORM, MOCK_SHARE, MOCK_SITE, type MockSite } from \"./fixtures\";\n\nexport { MOCK_FORM, MOCK_SHARE, MOCK_SITE };\nexport type { MockCollection, MockSite } from \"./fixtures\";\n\n/** A placeholder origin; never contacted. */\nexport const MOCK_POD_URL = \"https://mock.synap.invalid\";\n\nexport const MOCK_TOKENS = { form: \"demo-form\", share: \"demo\", site: \"demo-site\" } as const;\n\n/** The `Retry-After` the `rateLimitedRetryAfter` token sends. */\nexport const MOCK_RETRY_AFTER_SEC = 30;\n\n/** Tokens that make every door answer one failure, so each UI state can be rendered. */\nexport const MOCK_FAILURE_TOKENS = {\n /** 429 with no `Retry-After` (what the pod's public doors send today). */\n rateLimited: \"mock-rate-limited\",\n /** 429 with `Retry-After: 30`. */\n rateLimitedRetryAfter: \"mock-rate-limited-retry-after\",\n /** 403 `public_doors_disabled`: the owner switched public pages off. */\n doorsDisabled: \"mock-doors-disabled\",\n /** 500. */\n serverError: \"mock-server-error\",\n /** 503 (a read: `pod_unreachable` / `server`; a submit: `unavailable`). */\n unavailable: \"mock-unavailable\",\n /** No answer: the fetch rejects (`network` / `pod_unreachable`). */\n unreachable: \"mock-unreachable\",\n} as const;\n\nexport interface MockPodOptions {\n /** Forms by token (default: the demo form under `MOCK_TOKENS.form`). */\n forms?: Record<string, PublicForm>;\n /** Shares by token (default: the demo share under `MOCK_TOKENS.share`). */\n shares?: Record<string, PublicShare>;\n /** Sites by token (default: the demo site under `MOCK_TOKENS.site`). */\n sites?: Record<string, MockSite>;\n /** Refuse non-safelisted request headers, as a browser's CORS preflight would. */\n browser?: boolean;\n}\n\nexport interface MockRequest {\n method: string;\n url: string;\n /** Lower-cased header names. */\n headers: Record<string, string>;\n credentials: RequestCredentials | undefined;\n}\n\nexport interface MockSubmission {\n token: string;\n /** The parsed envelope (`fields`, `ticket`, `hp`, …). */\n body: unknown;\n /** The answer the mock gave. */\n status: 202 | 422;\n}\n\nexport interface MockPod {\n podUrl: string;\n fetch: typeof fetch;\n /** Every request seen, in order (assert credentials and headers on it). */\n requests: MockRequest[];\n /** Every submit seen, in order. */\n submissions: MockSubmission[];\n /** Forget `requests` and `submissions`. */\n reset(): void;\n}\n\nconst NOT_FOUND = { error: \"Not found\" };\nconst DOORS_DISABLED = {\n error: \"The owner has not turned on public pages and forms for this workspace.\",\n code: \"public_doors_disabled\",\n};\n/** Request headers a browser sends cross-origin without a preflight. */\nconst SAFELISTED = new Set([\"accept\", \"accept-language\", \"content-language\", \"content-type\"]);\n/** The only request header the real doors allow on a preflight. */\nconst PREFLIGHT_ALLOWED = new Set([\"content-type\"]);\n\nconst PUBLIC_ID_RE = /^p_[A-Za-z0-9]{16}$/;\nconst SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst SLUG_MAX_LENGTH = 96;\nconst DIGITS = /^\\d{1,9}$/;\nconst SORT_TERM = /^(-?)([A-Za-z][A-Za-z0-9_]{0,63})$/;\nconst MAX_OFFSET = 10_000;\nconst MAX_LIMIT = 100;\nconst MAX_SORT_KEYS = 3;\nconst PUBLISHED_AT = \"publishedAt\";\n\nfunction json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-cache\", ...headers },\n });\n}\n\n/** FNV-1a, 32 bits: a stable weak validator, no crypto needed. */\nfunction weakEtag(value: unknown): string {\n let h = 0x811c9dc5;\n for (const ch of JSON.stringify(value)) {\n h ^= ch.charCodeAt(0);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return `W/\"${h.toString(16).padStart(8, \"0\")}\"`;\n}\n\nfunction etagMatches(header: string | null, etag: string): boolean {\n if (!header) return false;\n if (header.trim() === \"*\") return true;\n const strip = (t: string) => t.trim().replace(/^W\\//, \"\");\n return header.split(\",\").some((t) => strip(t) === strip(etag));\n}\n\nfunction failure(token: string): Response | \"reject\" | null {\n switch (token) {\n case MOCK_FAILURE_TOKENS.rateLimited:\n return json(429, { error: \"Too many requests\", retryAfter: \"5 minutes\" });\n case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:\n return json(429, { error: \"Too many requests\" }, { \"Retry-After\": String(MOCK_RETRY_AFTER_SEC) });\n case MOCK_FAILURE_TOKENS.doorsDisabled:\n return json(403, DOORS_DISABLED);\n case MOCK_FAILURE_TOKENS.serverError:\n return json(500, { error: \"Internal error\" });\n case MOCK_FAILURE_TOKENS.unavailable:\n return json(503, { received: false });\n case MOCK_FAILURE_TOKENS.unreachable:\n return \"reject\";\n default:\n return null;\n }\n}\n\nfunction summary(record: PublicRecord): PublicRecordSummary {\n const { body: _body, ...rest } = record;\n return rest;\n}\n\nfunction manifest(site: MockSite): PublicSite {\n return {\n ...(site.title !== undefined ? { title: site.title } : {}),\n ...(site.description !== undefined ? { description: site.description } : {}),\n publishedOn: site.publishedOn,\n collections: Object.keys(site.collections)\n .sort()\n .map((key) => {\n const c = site.collections[key];\n return { key, count: c.records.length, sort: [...c.sort], pageSize: c.pageSize, addressing: c.addressing };\n }),\n };\n}\n\ntype Sort = Array<{ key: string; desc: boolean }>;\n\n/** The door's `parseCollectionQuery`: the same bounds, the same 400s. */\nfunction parseQuery(params: URLSearchParams, c: { sort: string[]; pageSize: number }): { offset: number; limit: number; sort: Sort } | string {\n for (const name of params.keys()) {\n if (name === \"embed\") return \"embed is not available\";\n if (name === \"ref\" || name.startsWith(\"ref[\")) return \"ref filter is not available\";\n }\n const one = (name: string): string | undefined | null => {\n const all = params.getAll(name);\n return all.length === 0 ? undefined : all.length === 1 ? all[0] : null;\n };\n const rawOffset = one(\"offset\");\n if (rawOffset === null || (rawOffset !== undefined && !DIGITS.test(rawOffset))) return \"invalid offset\";\n const offset = rawOffset === undefined ? 0 : Number(rawOffset);\n if (offset > MAX_OFFSET) return \"offset too large\";\n const rawLimit = one(\"limit\");\n if (rawLimit === null || (rawLimit !== undefined && !DIGITS.test(rawLimit))) return \"invalid limit\";\n const limit = Math.min(MAX_LIMIT, Math.max(1, rawLimit === undefined ? c.pageSize : Number(rawLimit)));\n const rawSort = one(\"sort\");\n if (rawSort === null) return \"invalid sort\";\n const terms =\n rawSort !== undefined ? rawSort.split(\",\") : c.sort.length > 0 ? [...c.sort] : [`-${PUBLISHED_AT}`];\n if (terms.length > MAX_SORT_KEYS) return \"invalid sort\";\n const allowed = new Set([PUBLISHED_AT, ...c.sort.map((s) => s.replace(/^-/, \"\"))]);\n const sort: Sort = [];\n for (const term of terms) {\n const m = SORT_TERM.exec(term);\n if (!m || !allowed.has(m[2]) || sort.some((s) => s.key === m[2])) return \"invalid sort\";\n sort.push({ key: m[2], desc: m[1] === \"-\" });\n }\n return { offset, limit, sort };\n}\n\n/** jsonb-like order within a type (strings < numbers < booleans); a missing value sorts last. */\nfunction compareRecords(sort: Sort) {\n const rank = (v: unknown) => (typeof v === \"string\" ? 0 : typeof v === \"number\" ? 1 : 2);\n const valueOf = (r: PublicRecord, key: string) => (key === PUBLISHED_AT ? r.publishedOn : r.properties[key]);\n return (a: PublicRecord, b: PublicRecord): number => {\n for (const { key, desc } of sort) {\n const va = valueOf(a, key);\n const vb = valueOf(b, key);\n if (va === undefined || vb === undefined) {\n if (va !== vb) return va === undefined ? 1 : -1;\n continue;\n }\n let d = rank(va) - rank(vb);\n if (d === 0) d = va < vb ? -1 : va > vb ? 1 : 0;\n if (d !== 0) return desc ? -d : d;\n }\n if (a.publishedOn !== b.publishedOn) return a.publishedOn < b.publishedOn ? 1 : -1;\n return a.publicId < b.publicId ? -1 : a.publicId > b.publicId ? 1 : 0;\n };\n}\n\n/** 200 with a weak ETag, or 304 when If-None-Match already holds it. */\nfunction cached(view: unknown, ifNoneMatch: string | null): Response {\n const etag = weakEtag(view);\n if (etagMatches(ifNoneMatch, etag)) {\n return new Response(null, { status: 304, headers: { ETag: etag, \"Cache-Control\": \"no-cache\" } });\n }\n return json(200, view, { ETag: etag });\n}\n\nfunction siteDoor(site: MockSite, rest: string[], params: URLSearchParams, ifNoneMatch: string | null): Response {\n if (rest.length === 0) return cached(manifest(site), ifNoneMatch);\n if (rest[0] !== \"collections\" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {\n return json(404, NOT_FOUND);\n }\n const c = site.collections[rest[1]];\n if (rest.length === 2) {\n const q = parseQuery(params, c);\n if (typeof q === \"string\") return json(400, { error: q });\n const sorted = [...c.records].sort(compareRecords(q.sort));\n const page: PublicCollectionPage = {\n items: sorted.slice(q.offset, q.offset + q.limit).map(summary),\n total: sorted.length,\n offset: q.offset,\n limit: q.limit,\n };\n return cached(page, ifNoneMatch);\n }\n const address = rest[2];\n const byPublicId = PUBLIC_ID_RE.test(address);\n if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);\n const record = c.records.find((r) => (byPublicId ? r.publicId === address : r.slug === address));\n if (!record) return json(404, NOT_FOUND);\n if (params.has(\"embed\")) return json(400, { error: \"embed is not available\" });\n return cached(record, ifNoneMatch);\n}\n\n/** The door's 422 for answers that do not satisfy the form (required, enum). */\nfunction invalidKeys(form: PublicForm, body: unknown): string[] {\n const fields = (body as { fields?: unknown } | null)?.fields;\n if (typeof fields !== \"object\" || fields === null) return [];\n const answers = fields as Record<string, unknown>;\n return form.fields\n .filter((f) => {\n const v = answers[f.key];\n if (v === undefined || v === \"\" || v === null) return f.required === true;\n if (f.type === \"boolean\") return typeof v !== \"boolean\" || (f.required === true && v !== true);\n const allowed = f.constraints?.enum;\n return allowed !== undefined && !allowed.includes(String(v));\n })\n .map((f) => f.key);\n}\n\nexport function createMockPod(options: MockPodOptions = {}): MockPod {\n const forms = options.forms ?? { [MOCK_TOKENS.form]: MOCK_FORM };\n const shares = options.shares ?? { [MOCK_TOKENS.share]: MOCK_SHARE };\n const sites = options.sites ?? { [MOCK_TOKENS.site]: MOCK_SITE };\n const requests: MockRequest[] = [];\n const submissions: MockSubmission[] = [];\n\n const mockFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const request = typeof input === \"object\" && \"method\" in input ? input : null;\n const url = new URL(typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url);\n const method = (init?.method ?? request?.method ?? \"GET\").toUpperCase();\n const headers: Record<string, string> = {};\n new Headers(init?.headers ?? request?.headers).forEach((value, name) => {\n headers[name.toLowerCase()] = value;\n });\n const credentials = init?.credentials ?? request?.credentials;\n requests.push({ method, url: url.href, headers, credentials });\n\n // What a browser does against the real doors (`Access-Control-Allow-Origin: *`).\n if (credentials === \"include\") throw new TypeError(\"Failed to fetch: credentialed request to a public door\");\n if (options.browser) {\n const preflighted = Object.keys(headers).filter((h) => !SAFELISTED.has(h) && !PREFLIGHT_ALLOWED.has(h));\n if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(\", \")}`);\n }\n\n const m = url.pathname.match(/^\\/api\\/hub\\/public\\/(forms|shares|sites)\\/(.+)$/);\n if (!m) return json(404, NOT_FOUND);\n let segments: string[];\n try {\n segments = m[2].split(\"/\").map(decodeURIComponent);\n } catch {\n return json(404, NOT_FOUND);\n }\n const [token, ...rest] = segments;\n const failed = failure(token);\n if (failed === \"reject\") throw new TypeError(\"Failed to fetch: mock pod unreachable\");\n if (failed) return failed;\n\n const door = m[1];\n if (door === \"forms\" && method === \"POST\" && rest.length === 0) {\n let body: unknown = null;\n try {\n body = JSON.parse(typeof init?.body === \"string\" ? init.body : \"null\");\n } catch {\n body = null;\n }\n const form = Object.hasOwn(forms, token) ? forms[token] : undefined;\n const invalid = form ? invalidKeys(form, body) : [];\n const status = invalid.length > 0 ? 422 : 202;\n submissions.push({ token, body, status });\n return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });\n }\n if (method !== \"GET\") return json(404, NOT_FOUND);\n if (door === \"forms\" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);\n if (door === \"shares\" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);\n if (door === \"sites\" && Object.hasOwn(sites, token)) {\n return siteDoor(sites[token], rest, url.searchParams, headers[\"if-none-match\"] ?? null);\n }\n return json(404, NOT_FOUND);\n };\n\n return {\n podUrl: MOCK_POD_URL,\n fetch: mockFetch as typeof fetch,\n requests,\n submissions,\n reset() {\n requests.length = 0;\n submissions.length = 0;\n },\n };\n}\n","/**\n * Demo data for the mock pod. Shapes are the `@synap-core/sdk/public` wire\n * types, so a fixture that drifts from the contract fails to compile.\n */\nimport type { PublicForm, PublicRecord, PublicShare } from \"../public/client\";\n\nexport const MOCK_FORM: PublicForm = {\n title: \"Get in touch\",\n fields: [\n { key: \"name\", label: \"Name\", type: \"text\", required: true, constraints: { maxLength: 120 } },\n { key: \"email\", label: \"Email\", type: \"email\", required: true },\n {\n key: \"topic\",\n label: \"Topic\",\n type: \"enum\",\n constraints: { enum: [\"Sales\", \"Support\", \"Partnership\"] },\n },\n { key: \"message\", label: \"Message\", type: \"richtext\", help: \"A few lines is plenty.\" },\n ],\n successMessage: \"Thanks — we received your message.\",\n honeypotField: \"hp\",\n ticket: \"mock-ticket\",\n minSubmitMs: 0,\n captcha: { required: false },\n};\n\nexport const MOCK_SHARE: PublicShare = {\n resourceType: \"entity\",\n title: \"Launch notes\",\n properties: { status: \"open\", owner: \"Ada\", featured: true },\n body: {\n format: \"markdown\",\n content: [\n \"# Launch notes\",\n \"\",\n \"This page is a **published snapshot** from a Synap pod.\",\n \"\",\n \"- Read the [Synap docs](https://synap.live)\",\n \"- Links other than http, https and mailto render as plain text: [bad](javascript:alert(1))\",\n \"\",\n \"<script>raw HTML is shown as text, never run</script>\",\n ].join(\"\\n\"),\n },\n publishedOn: \"2026-09-27\",\n};\n\n/** One collection of a mock site: its door config and its live records. */\nexport interface MockCollection {\n /** Default sort keys (`-` = descending); also the sort keys the door accepts. */\n sort: string[];\n pageSize: number;\n addressing: \"slug\" | \"publicId\";\n records: PublicRecord[];\n}\n\n/** A mock published site. The manifest is derived from it (counts included). */\nexport interface MockSite {\n title?: string;\n description?: string;\n publishedOn: string;\n collections: Record<string, MockCollection>;\n}\n\n/** A public id of the door's shape (`p_` + 16 base62), deterministic. */\nfunction publicId(prefix: string, n: number): string {\n return `p_${prefix}${String(n).padStart(16 - prefix.length, \"0\")}`;\n}\n\nfunction slugify(s: string): string {\n return s\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n}\n\nconst PAPER_TITLES = [\n \"Sparse graphs at scale\",\n \"Learning to rank, revisited\",\n \"A note on causal tracing\",\n \"Small models, long contexts\",\n \"Evaluating retrieval honestly\",\n \"Robust tokenisation\",\n \"Graphs that forget\",\n \"Calibration under shift\",\n \"Latent space cartography\",\n \"Proofs as programs, again\",\n \"Cheap uncertainty\",\n \"Résumé of a decade\",\n];\nconst VENUES = [\"NeurIPS\", \"ICML\", \"ACL\", \"ICLR\"];\n\nconst PAPERS: PublicRecord[] = PAPER_TITLES.map((title, i) => ({\n collection: \"paper\",\n publicId: publicId(\"paper\", i + 1),\n slug: slugify(title),\n title,\n properties: {\n year: 2026 - (i % 5),\n venue: VENUES[i % VENUES.length],\n open: i % 3 !== 0,\n },\n publishedOn: `2026-09-${String(28 - i).padStart(2, \"0\")}`,\n body: i % 4 === 3 ? null : { format: \"markdown\", content: `# ${title}\\n\\nAbstract of **${title}**.` },\n}));\n\nconst PEOPLE: PublicRecord[] = [\"Ada Lovelace\", \"Alan Turing\", \"Grace Hopper\"].map((name, i) => ({\n collection: \"person\",\n publicId: publicId(\"person\", i + 1),\n slug: null,\n title: name,\n properties: { name, role: i === 0 ? \"Lead\" : \"Researcher\" },\n publishedOn: \"2026-09-01\",\n body: null,\n}));\n\n/**\n * The demo site: `paper` (12 records, 5 per page, by slug: pagination),\n * `person` (by public id), `event` (nothing published: the empty state).\n */\nexport const MOCK_SITE: MockSite = {\n title: \"Demo lab\",\n description: \"What the lab publishes\",\n publishedOn: \"2026-09-01\",\n collections: {\n paper: { sort: [\"-year\"], pageSize: 5, addressing: \"slug\", records: PAPERS },\n person: { sort: [\"name\"], pageSize: 20, addressing: \"publicId\", records: PEOPLE },\n event: { sort: [], pageSize: 10, addressing: \"slug\", records: [] },\n },\n};\n"],"mappings":"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","names":[]}
|