payload-live-preview 2.2.1 → 2.3.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 +21 -18
- package/dist/adapters/astro/PreviewBoundary.astro +17 -4
- package/dist/adapters/astro/RichText.astro +13 -2
- package/dist/adapters/astro/index.d.ts +7 -7
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +3 -3
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.d.ts +2 -2
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.d.ts +4 -4
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/nuxt/module.d.ts +1 -1
- package/dist/adapters/react/index.d.ts +2 -2
- package/dist/adapters/react/index.js +1 -1
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.d.ts +5 -5
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/adapters/vue/index.d.ts +2 -2
- package/dist/adapters/vue/index.js +1 -1
- package/dist/adapters/vue/index.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +15 -3
- package/dist/client.d.ts +15 -3
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/codegen-astro.js +1 -1
- package/dist/codegen-astro.js.map +1 -1
- package/dist/codegen-cli.js +1 -1
- package/dist/codegen-cli.js.map +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.js +1 -1
- package/dist/codegen.js.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +141 -6
- package/dist/core.d.ts +141 -6
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/doctor-cli.d.ts +1 -1
- package/dist/doctor-cli.js +1 -1
- package/dist/doctor-cli.js.map +1 -1
- package/dist/doctor.d.ts +2 -2
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/{document-session-Bkd181PV.d.ts → document-session-BuYuO3ae.d.ts} +27 -2
- package/dist/{fragment-endpoint-DGGLIijM.d.ts → fragment-endpoint-CQE_7MKi.d.ts} +5 -3
- package/dist/fragment.cjs +1 -1
- package/dist/fragment.cjs.map +1 -1
- package/dist/fragment.d.cts +6 -2
- package/dist/fragment.d.ts +6 -2
- package/dist/fragment.js +1 -1
- package/dist/fragment.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -16
- package/dist/index.d.ts +79 -16
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lean.cjs +1 -1
- package/dist/lean.cjs.map +1 -1
- package/dist/lean.js +1 -1
- package/dist/lean.js.map +1 -1
- package/dist/lexical.cjs +1 -1
- package/dist/lexical.cjs.map +1 -1
- package/dist/lexical.d.cts +16 -4
- package/dist/lexical.d.ts +16 -4
- package/dist/lexical.js +1 -1
- package/dist/lexical.js.map +1 -1
- package/dist/{locals-zz2gi9dj.d.ts → locals-CyzDKfKU.d.ts} +7 -1
- package/dist/{middleware-CI-UTf6R.d.ts → middleware-Dnt6u4nF.d.ts} +2 -2
- package/dist/{options-CnYybG51.d.ts → options-DTzhFVJ4.d.ts} +13 -1
- package/dist/plugin.cjs +1 -1
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +15 -9
- package/dist/plugin.d.ts +15 -9
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +8 -2
- package/dist/plugins.d.ts +8 -2
- package/dist/plugins.js +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/{probe-D-IHg9dF.d.ts → probe-afwdYebU.d.ts} +6 -2
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +23 -3
- package/dist/server.d.ts +23 -3
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/structural.cjs +2 -2
- package/dist/structural.cjs.map +1 -1
- package/dist/structural.d.cts +2 -0
- package/dist/structural.d.ts +2 -0
- package/dist/structural.js +1 -1
- package/dist/structural.js.map +1 -1
- package/package.json +7 -4
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":["AUTHORIZED_PREVIEW_BRAND_KEY","BRAND","Symbol","for","isAuthorizedPreviewContext","value","Object","isFrozen","createAuthorizedPreviewContext","fields","freeze","scope","payload","document","payloadHeaders","PreviewConfigurationError","Error","name","refused","outcome","authorized","context","DEFAULT_USERS_SLUG","COOKIE_NAME_PATTERN","COOKIE_VALUE_PATTERN","extractCookie","header","maxLength","length","prefix","values","part","split","trimmed","trim","startsWith","push","slice","test","httpOriginFor","option","url","URL","protocol","origin","warnOnce","key","message","env","globalThis","process","isDevelopmentProcess","holder","warned","Set","has","add","console","warn","cachedEncoder","DEFAULT_PURPOSE","TOKEN_VERSION","encoder","TextEncoder","secretBytes","secret","bytes","encode","byteLength","String","cryptoFor","configured","candidate","crypto","subtle","async","hmacKey","importKey","hash","toBase64Url","binary","byte","fromCharCode","btoa","replace","fromBase64Url","text","padded","repeat","atob","Uint8Array","index","charCodeAt","pathSegment","isPreviewDocumentID","Number","isSafeInteger","payloadAPIBase","serverURL","apiRoute","includes","username","password","search","rawPath","route","every","pathname","isPayloadScopeCurrent","expiresAt","isFinite","Date","now","matchesPreviewDocument","expected","actual","kind","slug","id","DEFAULT_QUERY_PARAMS","bind","field","options","buildAttributes","bindByPath","picker","path","proxy","Proxy","get","_target","prop","recordPath","join","attrs","attribute","type","richtext","html","locale","alt","href","arrayTemplate","REGISTRY_ID","SUPPRESSED","createPreviewBindings","authorization","owner","bindOptions","boundary","boundaryOptions","attributes","RangeError","dependsOn","boundaryAttributes","covers","paths","coverAttribute","PreviewFetchError","reason","status","constructor","cause","super","this","readScopeAllows","config","target","depth","maxDepth","refuseScope","attempt","settle","ok","draft","draftFor","params","query","URLSearchParams","set","read","timeoutMs","allowed","fetchImpl","fetch","errorMode","startedAt","timeout","AbortSignal","signal","any","response","body","headers","Headers","accept","entries","fromEntries","cache","redirect","failureReason","json","Array","isArray","data","hasOwn","matchesPreviewDocumentData","result","durationMs","onDiagnostic","external","aborted","isWhereTree","queryName","map","segment","appendWhere","node","nextPath","some","forEach","branch","request","strategy","cookieName","cookieNameFor","sentOrigin","maxCookieLength","Math","max","usersSlug","encodeURIComponent","forwarded","cookie","controller","AbortController","abort","isAborted","timer","addEventListener","once","setTimeout","DOMException","user","clearTimeout","removeEventListener","collection","readUser","exp","readExpiry","subject","authorizedAt","authorizeSession","token","transport","searchParams","getAll","param","readToken","parts","payloadText","payloadBytes","signature","verified","verify","claims","parsed","JSON","parse","TextDecoder","decode","c","optionalString","parseClaims","iat","aud","audience","pur","purpose","pth","loc","replay","first","store","consume","isUsed","markUsed","consumeTokenId","jti","sub","authorizeToken","isValidPayloadScope","authorizeVerifier","normalizeServerURL","isInteger","resolved","runtimeOptions","mergeDepth","base","settings","fetchDocument","direct","where","toString","docs","fetchGlobal","global","signals","queryParams","at","alias","adminOrigins","allowedOrigins","referer","refererOrigin","adminOrigin","ttl","min","ttlMs","getRandomValues","wire","v","stringify","signed","sign","locals"],"sources":["../src/types/authorized-preview.ts","../src/security/preview-verdict.ts","../src/security/preview-session.ts","../src/types/dev-warning.ts","../src/security/preview-token.ts","../src/security/preview-scope.ts","../src/adapters/shared/preview-request.ts","../src/dsl/bind.ts","../src/dsl/preview-bindings.ts","../src/server/preview.ts","../src/security/preview-authorization.ts","../src/adapters/shared/locals.ts"],"sourcesContent":["/**\n * The authorized preview context's shape and brand, in the leaf `types` domain\n * so producer and consumers recognise the same context. See ADR 0006.\n */\n\n/**\n * The brand key, in the type and on the object.\n *\n * This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per\n * *declaration*, and the published entries each carry their own declaration\n * file — so `AuthorizedPreviewContext` from the root barrel was a different\n * type from the one in `./server` or in an adapter, and the documented flow\n * (authorize once, hand the context to the adapter hook and to the draft read)\n * did not type-check in any combination a consumer can write.\n *\n * That is the same trap the runtime brand below already avoids by using\n * `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the\n * type. A shared string literal is identical in every bundle for the same\n * reason a registry symbol is.\n *\n * It is not what makes a context trustworthy. `isAuthorizedPreviewContext`\n * decides that, on the registry symbol and on `Object.isFrozen`, and every\n * consumption point calls it — the adapter policy, the draft read and the\n * binding emitter. Writing this key by hand buys nothing.\n */\nexport const AUTHORIZED_PREVIEW_BRAND_KEY = 'payload-live-preview.authorized-preview-context';\n\nexport type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';\n\n/** One server-authorized Payload document, not a query selected by the browser. */\nexport type AuthorizedPreviewDocument =\n | { readonly kind: 'collection'; readonly slug: string; readonly id: string | number }\n | { readonly kind: 'global'; readonly slug: string };\n\n/** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */\nexport interface AuthorizedPreviewPayloadScope {\n /** Payload base URL, distinct from the preview site's audience. */\n readonly serverURL: string;\n /** REST prefix, default `/api`. */\n readonly apiRoute?: string;\n readonly document: AuthorizedPreviewDocument;\n /** Maximum requested population depth, not a field-level ACL. */\n readonly maxDepth: number;\n}\n\n/** What a context is bound to; every field is optional because every strategy binds differently. */\nexport interface AuthorizedPreviewScope {\n /** The site origin the credential was issued for. */\n readonly audience?: string;\n /** The request pathname the credential is valid for. */\n readonly path?: string;\n readonly locale?: string;\n /** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */\n readonly payload?: AuthorizedPreviewPayloadScope;\n}\n\n/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */\nexport interface AuthorizedPreviewContext {\n readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;\n readonly strategy: PreviewAuthorizationStrategyName;\n /** The user id, token subject, or whatever the verifier named. */\n readonly subject: string | undefined;\n /** Unix milliseconds. */\n readonly authorizedAt: number;\n /** Unix milliseconds, or `undefined` when the strategy does not know. */\n readonly expiresAt: number | undefined;\n readonly scope: AuthorizedPreviewScope;\n /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */\n readonly payloadHeaders: Readonly<Record<string, string>>;\n}\n\n// A registry symbol: entries are separate bundles, and a per-bundle `Symbol()`\n// would refuse the other's contexts in the package while unit tests passed.\nconst BRAND = Symbol.for(AUTHORIZED_PREVIEW_BRAND_KEY);\n\n/**\n * Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an\n * unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the\n * brand is a runtime check, not a cryptographic one (ADR 0006).\n */\nexport function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<PropertyKey, unknown>)[BRAND] === true &&\n Object.isFrozen(value)\n );\n}\n\n/** Internal: the one place a context is made. */\nexport function createAuthorizedPreviewContext(\n fields: Omit<AuthorizedPreviewContext, typeof AUTHORIZED_PREVIEW_BRAND_KEY>,\n): AuthorizedPreviewContext {\n const context = Object.freeze({\n ...fields,\n scope: Object.freeze({\n ...fields.scope,\n ...(fields.scope.payload === undefined\n ? {}\n : {\n payload: Object.freeze({\n ...fields.scope.payload,\n document: Object.freeze({ ...fields.scope.payload.document }),\n }),\n }),\n }),\n payloadHeaders: Object.freeze({ ...fields.payloadHeaders }),\n [BRAND]: true,\n [AUTHORIZED_PREVIEW_BRAND_KEY]: true,\n });\n // No cast: the frozen object carries the brand key the interface declares.\n return context;\n}\n","/**\n * The verdict shape every authorization strategy produces, and the one error\n * class that is allowed to escape it. Kept apart from the strategies so the\n * adapters can import the error without pulling in HMAC or session code.\n */\n\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\n\n/** Why a request was refused. `'authorized'` is the one non-refusal. */\nexport type PreviewAuthorizationOutcome =\n | 'authorized'\n | 'missing-credential'\n | 'invalid'\n | 'expired'\n | 'wrong-audience'\n | 'wrong-path'\n | 'wrong-locale'\n | 'wrong-purpose'\n | 'replayed'\n | 'unavailable';\n\n/** The result of `authorizePreviewRequest()`: a verdict, never an exception. */\nexport type PreviewAuthorization =\n | {\n readonly authorized: true;\n readonly outcome: 'authorized';\n readonly context: AuthorizedPreviewContext;\n }\n | {\n readonly authorized: false;\n readonly outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>;\n readonly context: null;\n };\n\n/** The request shape every strategy reads: a URL and a header getter. */\nexport interface PreviewAuthorizationRequest {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n /** Request lifetime, when supplied by the caller. Session fetches follow it. */\n readonly signal?: AbortSignal;\n}\n\n/**\n * A strategy was misconfigured — raised on first use and re-thrown by the\n * adapters rather than reported as `'unavailable'`, so it fails loudly.\n */\nexport class PreviewConfigurationError extends Error {\n override readonly name = 'PreviewConfigurationError';\n}\n\n/** Recognises the error across bundles, where `instanceof` sees a different class copy. */\nexport function isPreviewConfigurationError(error: unknown): error is PreviewConfigurationError {\n return (\n error instanceof PreviewConfigurationError ||\n (error instanceof Error && error.name === 'PreviewConfigurationError')\n );\n}\n\nexport function refused(\n outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>,\n): PreviewAuthorization {\n return { authorized: false, outcome, context: null };\n}\n","/**\n * The `payload-session` strategy: forward exactly one named cookie to\n * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.\n * A cookie-only request preserves working Payload 2 sessions. When `/me`\n * reports no user, one retry adds an `Origin` for Payload 3's CSRF check.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** A `fetch`-compatible function, injectable for tests. */\nexport type FetchLike = (\n input: string,\n init: { readonly headers: Record<string, string>; readonly signal: AbortSignal },\n) => Promise<{ readonly ok: boolean; readonly status: number; json(): Promise<unknown> }>;\n\nexport interface PayloadSessionStrategy {\n readonly type: 'payload-session';\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** Auth collection slug; `users` unless the project renamed it. */\n readonly usersSlug?: string;\n /** Cookie name; Payload's default is `payload-token`. */\n readonly cookieName?: string;\n /**\n * Send this listed origin with the cookie from the first request. Without\n * it, try the existing cookie-only request, then the origin of `serverURL`\n * once when `/me` reports a null user. Draft reads keep the accepted headers.\n */\n readonly origin?: string;\n /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */\n readonly timeoutMs?: number;\n /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */\n readonly maxCookieLength?: number;\n readonly fetch?: FetchLike;\n /** Clock, Unix milliseconds. Injectable for tests. */\n readonly now?: () => number;\n}\n\nconst DEFAULT_USERS_SLUG = 'users';\nconst DEFAULT_COOKIE_NAME = 'payload-token';\nconst DEFAULT_TIMEOUT_MS = 3_000;\nconst MIN_TIMEOUT_MS = 250;\nconst DEFAULT_MAX_COOKIE_LENGTH = 4_096;\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[A-Za-z0-9._~-]+$/;\n/** Below this an `exp` is Unix seconds; a millisecond timestamp passed it in 1973. */\nconst SECONDS_CEILING = 1e11;\n\n/**\n * Exactly one value for `name`, or `null` when absent, empty, over `maxLength`\n * or repeated — a repeated cookie is ambiguous and is refused, not guessed.\n */\nexport function extractCookie(\n header: string | null,\n name: string,\n maxLength: number = DEFAULT_MAX_COOKIE_LENGTH,\n): string | null {\n if (header === null || header.length === 0) return null;\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const part of header.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) values.push(trimmed.slice(prefix.length));\n }\n if (values.length !== 1) return null;\n const value = values[0] ?? '';\n // A JWT is `[A-Za-z0-9._~-]`; anything else must not be forwarded in a header.\n if (value.length === 0 || value.length > maxLength || !COOKIE_VALUE_PATTERN.test(value)) {\n return null;\n }\n return value;\n}\n\nfunction cookieNameFor(configured: string | undefined): string {\n const name = configured ?? DEFAULT_COOKIE_NAME;\n if (!COOKIE_NAME_PATTERN.test(name)) {\n throw new PreviewConfigurationError(\n `payload-live-preview: cookie name \"${name}\" is not a valid cookie-name token`,\n );\n }\n return name;\n}\n\n/** The origin of an absolute http(s) URL; `option` names it in the error. */\nfunction httpOriginFor(value: string, option: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PreviewConfigurationError(\n `payload-live-preview: ${option} \"${value}\" is not an absolute URL`,\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PreviewConfigurationError(\n `payload-live-preview: ${option} must be http(s), got \"${url.protocol}\"`,\n );\n }\n return url.origin;\n}\n\nexport async function authorizeSession(\n request: PreviewAuthorizationRequest,\n strategy: PayloadSessionStrategy,\n): Promise<PreviewAuthorization> {\n const cookieName = cookieNameFor(strategy.cookieName);\n const origin = httpOriginFor(strategy.serverURL, 'serverURL');\n const sentOrigin =\n strategy.origin === undefined ? undefined : httpOriginFor(strategy.origin, 'origin');\n const value = extractCookie(\n request.headers.get('cookie'),\n cookieName,\n strategy.maxCookieLength ?? DEFAULT_MAX_COOKIE_LENGTH,\n );\n if (value === null) return refused('missing-credential');\n const now = strategy.now ?? Date.now;\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, strategy.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n // Retain AbortSignal.timeout's refusal of non-integral or non-finite values.\n if (!Number.isSafeInteger(timeoutMs)) return refused('unavailable');\n const fetchImpl: FetchLike = strategy.fetch ?? globalThis.fetch;\n const usersSlug = encodeURIComponent(strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n const forwarded = `${cookieName}=${value}`;\n let payloadHeaders: Record<string, string> = { cookie: forwarded };\n if (sentOrigin !== undefined) payloadHeaders['origin'] = sentOrigin;\n const controller = new AbortController();\n const abort = (): void => {\n controller.abort(request.signal?.reason);\n };\n const isAborted = (): boolean => controller.signal.aborted;\n request.signal?.addEventListener('abort', abort, { once: true });\n if (request.signal?.aborted === true) abort();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let body: unknown;\n try {\n if (isAborted()) return refused('unavailable');\n timer = setTimeout(() => {\n controller.abort(new DOMException('The operation timed out', 'TimeoutError'));\n }, timeoutMs);\n for (let attempt = 0; attempt < 2; attempt++) {\n // `depth=0`: the verdict needs the user's id, not its populated relations.\n const response = await fetchImpl(`${origin}/api/${usersSlug}/me?depth=0`, {\n headers: { ...payloadHeaders, accept: 'application/json' },\n signal: controller.signal,\n });\n if (isAborted()) return refused('unavailable');\n if (!response.ok) return refused(response.status === 401 ? 'invalid' : 'unavailable');\n body = await response.json();\n if (isAborted()) return refused('unavailable');\n // Payload 2 accepts an absent Origin; adding one unconditionally can\n // reject its valid session when only the frontend is in the CSRF list.\n if (\n attempt !== 0 ||\n sentOrigin !== undefined ||\n typeof body !== 'object' ||\n body === null ||\n (body as { user?: unknown }).user !== null\n ) {\n break;\n }\n payloadHeaders = { cookie: forwarded, origin };\n }\n } catch {\n return refused('unavailable');\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n request.signal?.removeEventListener('abort', abort);\n }\n const user = readUser(body, strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n if (user === null) return refused('invalid');\n const expiresAt = readExpiry(body);\n if (expiresAt !== undefined && expiresAt <= now()) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'payload-session',\n subject: user,\n authorizedAt: now(),\n expiresAt,\n scope: {},\n payloadHeaders,\n }),\n };\n}\n\n/** The user id from a `/me` body, or `null` when absent or from another auth collection — Payload answers `/me` for every one of them. */\nfunction readUser(body: unknown, usersSlug: string): string | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const user = (body as { user?: unknown }).user;\n if (typeof user !== 'object' || user === null || Array.isArray(user)) return null;\n const collection =\n (body as { collection?: unknown }).collection ?? (user as { collection?: unknown }).collection;\n if (collection !== undefined && collection !== usersSlug) return null;\n const id = (user as { id?: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n if (typeof id === 'number' && Number.isFinite(id)) return String(id);\n return null;\n}\n\n/** `/me` reports `exp` in Unix seconds; units are decided by magnitude so an expired millisecond value stays expired. */\nfunction readExpiry(body: unknown): number | undefined {\n const exp = (body as { exp?: unknown }).exp;\n if (typeof exp !== 'number' || !Number.isFinite(exp) || exp <= 0) return undefined;\n return exp < SECONDS_CEILING ? exp * 1000 : exp;\n}\n","/**\n * One development warning per process. The set of issued keys lives on\n * `globalThis` so two package copies in one process still warn once.\n */\n\nconst WARNED_KEY = '__payloadLivePreviewDeprecationsWarned';\n\n/** `true` outside a production build; `false` when `NODE_ENV` is production or unknown. */\nexport function isDevelopmentProcess(): boolean {\n try {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n ?.env;\n if (env === undefined) return false;\n return env['NODE_ENV'] !== 'production';\n } catch {\n return false;\n }\n}\n\n/** Warn once per process for `key`, outside production only. */\nexport function warnOnce(key: string, message: string): void {\n if (!isDevelopmentProcess()) return;\n const holder = globalThis as unknown as Record<string, Set<string> | undefined>;\n const warned = (holder[WARNED_KEY] ??= new Set<string>());\n if (warned.has(key)) return;\n warned.add(key);\n try {\n console.warn(`[payload-live-preview] ${message}`);\n } catch {\n // A console that throws must not break a request.\n }\n}\n\n/**\n * The deprecated options the adapters and the inline generator both accept;\n * an adapter hands them on to the generator, and the shared keys keep that to\n * one warning each (ADR 0026).\n */\nexport function warnDeprecatedOptions(options: {\n readonly defaults?: string | undefined;\n readonly onUnboundChange?: string | undefined;\n}): void {\n if (options.defaults === 'v1') {\n warnOnce(\n 'defaults-v1',\n \"`defaults: 'v1'` is removed in 3.0: `pll migrate` writes the 1.x rows it stands for \" +\n 'into your options, where they keep working (ADR 0026).',\n );\n }\n if (options.onUnboundChange !== undefined) {\n warnOnce(\n 'on-unbound-change',\n \"`onUnboundChange` is removed in 3.0: name it `onUnfaithfulPatch`, where 'route' is \" +\n \"'escalate'; `pll migrate` renames it (ADR 0026).\",\n );\n }\n}\n\n/** Test hook: forget every warning issued so far. */\nexport function resetDevWarnings(): void {\n const holder = globalThis as unknown as Record<string, Set<string> | undefined>;\n holder[WARNED_KEY] = undefined;\n}\n","/**\n * The `signed-token` strategy: a short-lived HMAC-SHA256 token minted by the\n * Payload side and verified by the site. Format and bindings: ADR 0006 §3.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport { warnOnce } from '@/types/dev-warning';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** The subset of Web Crypto the token strategy needs, injectable for environments that expose it elsewhere. */\nexport interface SubtleCryptoLike {\n readonly subtle: {\n importKey(\n format: 'raw',\n keyData: Uint8Array,\n algorithm: { readonly name: 'HMAC'; readonly hash: 'SHA-256' },\n extractable: false,\n keyUsages: readonly ('sign' | 'verify')[],\n ): Promise<CryptoKey>;\n sign(algorithm: 'HMAC', key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer>;\n verify(\n algorithm: 'HMAC',\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array,\n ): Promise<boolean>;\n };\n getRandomValues<T extends Uint8Array>(array: T): T;\n}\n\n/** Where a signed token is read from on the request. */\nexport type PreviewTokenTransport =\n | { readonly kind: 'query'; readonly param?: string }\n | { readonly kind: 'header'; readonly name?: string };\n\n/**\n * Optional replay store. Not shipped: a per-process memory is useless behind\n * a load balancer, so the store is a deployment decision.\n */\nexport interface PreviewTokenReplayStore {\n /**\n * Record `id` as used until `expiresAt` and say whether this call was the\n * first: `true` admits the token, anything else refuses it as `replayed`, a\n * throw refuses it as `unavailable`. Check and record in one step (Redis\n * `SET NX PX`, a unique insert): a read followed by a write lets two requests\n * that carry the same token and arrive together both pass.\n */\n consume(id: string, expiresAt: number): Promise<boolean> | boolean;\n}\n\n/**\n * @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one\n * step, so two requests with the same token that arrive together both pass\n * `isUsed` before either reaches `markUsed`. Implement\n * {@link PreviewTokenReplayStore.consume} instead.\n */\nexport interface PreviewTokenReplayChecks {\n isUsed(id: string): Promise<boolean> | boolean;\n markUsed(id: string, expiresAt: number): Promise<void> | void;\n}\n\nexport interface SignedTokenStrategy {\n readonly type: 'signed-token';\n /** Shared secret, at least 32 bytes; shorter ones are refused on first use. */\n readonly secret: string | Uint8Array;\n /** The site origin tokens must name, e.g. `https://www.example.com`. */\n readonly audience: string;\n /** Purpose string tokens must carry. Default `live-preview`. */\n readonly purpose?: string;\n /** Default: query parameter `previewToken`. */\n readonly transport?: PreviewTokenTransport;\n /** Resolve the request's locale for the `loc` binding. Unset: tokens with `loc` are refused as `wrong-locale`. */\n readonly locale?: (request: PreviewAuthorizationRequest) => string | undefined;\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape is accepted until 3.0\n readonly replay?: PreviewTokenReplayStore | PreviewTokenReplayChecks;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\n/** What `issuePreviewToken()` binds a token to. */\nexport interface PreviewTokenClaims {\n /** The site origin the token is for. */\n readonly audience: string;\n /** Request pathname the token opens. Recommended; omit only for a single-page preview. */\n readonly path?: string;\n readonly locale?: string;\n readonly subject?: string;\n readonly purpose?: string;\n /** Lifetime in milliseconds. Default ten minutes; capped at one hour. */\n readonly ttlMs?: number;\n}\n\nexport interface IssuePreviewTokenOptions {\n readonly secret: string | Uint8Array;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\ninterface WireClaims {\n readonly v: 1;\n readonly aud: string;\n readonly pth?: string;\n readonly loc?: string;\n readonly sub?: string;\n readonly pur: string;\n readonly iat: number;\n readonly exp: number;\n readonly jti: string;\n}\n\nconst DEFAULT_PURPOSE = 'live-preview';\nconst DEFAULT_QUERY_PARAM = 'previewToken';\nconst DEFAULT_HEADER_NAME = 'x-preview-token';\nconst DEFAULT_TTL_MS = 10 * 60_000;\nconst MAX_TTL_MS = 60 * 60_000;\nconst MIN_SECRET_BYTES = 32;\nconst TOKEN_VERSION = 'v1';\n/** A real token is a few hundred bytes. */\nconst MAX_TOKEN_LENGTH = 4_096;\n\n// Lazy: a module-scope `new TextEncoder()` is a side effect a bundler must keep.\nlet cachedEncoder: TextEncoder | undefined;\nfunction encoder(): TextEncoder {\n cachedEncoder ??= new TextEncoder();\n return cachedEncoder;\n}\n\nfunction secretBytes(secret: string | Uint8Array): Uint8Array {\n const bytes = typeof secret === 'string' ? encoder().encode(secret) : secret;\n if (bytes.byteLength < MIN_SECRET_BYTES) {\n throw new PreviewConfigurationError(\n `payload-live-preview: the preview token secret must be at least ${String(MIN_SECRET_BYTES)} bytes`,\n );\n }\n return bytes;\n}\n\nfunction cryptoFor(configured: SubtleCryptoLike | undefined): SubtleCryptoLike | undefined {\n if (configured !== undefined) return configured;\n const candidate = (globalThis as { crypto?: unknown }).crypto;\n if (typeof (candidate as { subtle?: unknown } | undefined)?.subtle === 'object') {\n return candidate as SubtleCryptoLike;\n }\n return undefined;\n}\n\nasync function hmacKey(crypto: SubtleCryptoLike, secret: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey('raw', secret, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n 'verify',\n ]);\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(text: string): Uint8Array | undefined {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return undefined;\n const padded =\n text.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (text.length % 4)) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n } catch {\n return undefined;\n }\n}\n\n/** Mint a preview token on the Payload side; pair with `buildLivePreviewUrl`. */\nexport async function issuePreviewToken(\n claims: PreviewTokenClaims,\n options: IssuePreviewTokenOptions,\n): Promise<string> {\n const crypto = cryptoFor(options.crypto);\n if (crypto === undefined) {\n throw new Error('payload-live-preview: no Web Crypto implementation available to sign tokens');\n }\n const secret = secretBytes(options.secret);\n const now = (options.now ?? Date.now)();\n const ttl = Math.min(MAX_TTL_MS, Math.max(1, claims.ttlMs ?? DEFAULT_TTL_MS));\n const id = new Uint8Array(16);\n crypto.getRandomValues(id);\n const wire: WireClaims = {\n v: 1,\n aud: claims.audience,\n ...(claims.path !== undefined ? { pth: claims.path } : {}),\n ...(claims.locale !== undefined ? { loc: claims.locale } : {}),\n ...(claims.subject !== undefined ? { sub: claims.subject } : {}),\n pur: claims.purpose ?? DEFAULT_PURPOSE,\n iat: now,\n exp: now + ttl,\n jti: toBase64Url(id),\n };\n const payload = toBase64Url(encoder().encode(JSON.stringify(wire)));\n const signed = `${TOKEN_VERSION}.${payload}`;\n const key = await hmacKey(crypto, secret);\n const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder().encode(signed)));\n return `${signed}.${toBase64Url(signature)}`;\n}\n\nfunction readToken(\n request: PreviewAuthorizationRequest,\n transport: PreviewTokenTransport | undefined,\n): string | null {\n if (transport?.kind === 'header') {\n return request.headers.get(transport.name ?? DEFAULT_HEADER_NAME);\n }\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n const values = url.searchParams.getAll(transport?.param ?? DEFAULT_QUERY_PARAM);\n return values.length === 1 ? (values[0] ?? null) : null;\n}\n\nfunction parseClaims(bytes: Uint8Array): WireClaims | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const c = parsed as Record<string, unknown>;\n const optionalString = (key: string): boolean =>\n c[key] === undefined || typeof c[key] === 'string';\n if (\n c['v'] !== 1 ||\n typeof c['aud'] !== 'string' ||\n typeof c['pur'] !== 'string' ||\n typeof c['iat'] !== 'number' ||\n typeof c['exp'] !== 'number' ||\n typeof c['jti'] !== 'string' ||\n !optionalString('pth') ||\n !optionalString('loc') ||\n !optionalString('sub')\n ) {\n return undefined;\n }\n return c as unknown as WireClaims;\n}\n\nexport async function authorizeToken(\n request: PreviewAuthorizationRequest,\n strategy: SignedTokenStrategy,\n): Promise<PreviewAuthorization> {\n const secret = secretBytes(strategy.secret);\n const crypto = cryptoFor(strategy.crypto);\n if (crypto === undefined) return refused('unavailable');\n const token = readToken(request, strategy.transport);\n if (token === null || token.length === 0) return refused('missing-credential');\n if (token.length > MAX_TOKEN_LENGTH) return refused('invalid');\n const parts = token.split('.');\n if (parts.length !== 3 || parts[0] !== TOKEN_VERSION) return refused('invalid');\n const payloadText = parts[1] ?? '';\n const payloadBytes = fromBase64Url(payloadText);\n const signature = fromBase64Url(parts[2] ?? '');\n if (payloadBytes === undefined || signature?.byteLength !== 32) return refused('invalid');\n let verified: boolean;\n try {\n const key = await hmacKey(crypto, secret);\n verified = await crypto.subtle.verify(\n 'HMAC',\n key,\n signature,\n encoder().encode(`${TOKEN_VERSION}.${payloadText}`),\n );\n } catch {\n return refused('unavailable');\n }\n if (!verified) return refused('invalid');\n const claims = parseClaims(payloadBytes);\n if (claims === undefined) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.exp <= now || claims.iat > claims.exp) return refused('expired');\n if (claims.aud !== strategy.audience) return refused('wrong-audience');\n if (claims.pur !== (strategy.purpose ?? DEFAULT_PURPOSE)) return refused('wrong-purpose');\n if (claims.pth !== undefined) {\n let pathname: string;\n try {\n pathname = new URL(request.url).pathname;\n } catch {\n return refused('invalid');\n }\n if (claims.pth !== pathname) return refused('wrong-path');\n }\n if (claims.loc !== undefined) {\n const locale = strategy.locale?.(request);\n if (locale !== claims.loc) return refused('wrong-locale');\n }\n if (strategy.replay !== undefined) {\n let first: boolean;\n try {\n first = await consumeTokenId(strategy.replay, claims.jti, claims.exp);\n } catch {\n return refused('unavailable');\n }\n if (!first) return refused('replayed');\n }\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'signed-token',\n subject: claims.sub,\n authorizedAt: now,\n expiresAt: claims.exp,\n scope: {\n audience: claims.aud,\n ...(claims.pth !== undefined ? { path: claims.pth } : {}),\n ...(claims.loc !== undefined ? { locale: claims.loc } : {}),\n },\n payloadHeaders: {},\n }),\n };\n}\n\n/**\n * `true` when `id` was not seen before. A store that returns anything but\n * `true` from `consume` refuses the token: fail closed, since a forgotten\n * return would otherwise admit every replay. The 2.0 beta shape keeps its two\n * steps and its race; that is why it is deprecated rather than emulated.\n */\nasync function consumeTokenId(\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape is accepted until 3.0\n store: PreviewTokenReplayStore | PreviewTokenReplayChecks,\n id: string,\n expiresAt: number,\n): Promise<boolean> {\n if ('consume' in store) {\n // The store is consumer code; its answer is a value, not a type.\n const answer: unknown = await store.consume(id, expiresAt);\n return answer === true;\n }\n warnOnce(\n 'replay-is-used',\n 'A `signed-token` replay store with `isUsed` and `markUsed` is removed in 3.0: implement ' +\n '`consume(id, expiresAt)`, which checks and records in one step (ADR 0026).',\n );\n if (await store.isUsed(id)) return false;\n await store.markUsed(id, expiresAt);\n return true;\n}\n","/**\n * Shared checks for an opt-in Payload document capability (ADR 0006 §5c).\n * The verifier supplies authority; server reads and fragments only compare\n * their requested target against it, before forwarding credentials or rendering.\n */\nimport type {\n AuthorizedPreviewContext,\n AuthorizedPreviewDocument,\n AuthorizedPreviewPayloadScope,\n} from '@/types/authorized-preview';\n\nfunction pathSegment(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value !== '.' &&\n value !== '..' &&\n !/[\\s\\p{Cc}\\p{Cs}/\\\\?#%]/u.test(value)\n );\n}\n\n/** @internal Numeric REST IDs and their string spelling name the same document. */\nexport function isPreviewDocumentID(value: unknown): value is string | number {\n return typeof value === 'number' ? Number.isSafeInteger(value) : pathSegment(value);\n}\n\n/** @internal Refuse ambiguous paths rather than depending on a proxy's decoding rules. */\nexport function payloadAPIBase(serverURL: unknown, apiRoute: unknown = '/api'): string | null {\n if (typeof serverURL !== 'string' || typeof apiRoute !== 'string') return null;\n try {\n const url = new URL(serverURL);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n return null;\n }\n // Inspect the raw path too: URL would otherwise erase dot segments first.\n const rawPath = serverURL.replace(/^[a-z]+:\\/\\/[^/]+/iu, '').replace(/\\/+$/, '');\n const route = apiRoute.replace(/^\\//, '').replace(/\\/$/, '');\n if (\n (rawPath !== '' && !rawPath.slice(1).split('/').every(pathSegment)) ||\n !route.split('/').every(pathSegment)\n ) {\n return null;\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}/${route}`;\n } catch {\n return null;\n }\n}\n\n/** @internal Validate a JavaScript verifier's binding, not merely its TypeScript shape. */\nexport function isValidPayloadScope(value: unknown): value is AuthorizedPreviewPayloadScope {\n if (typeof value !== 'object' || value === null) return false;\n const scope = value as Record<string, unknown>;\n const document = scope['document'] as Record<string, unknown> | null | undefined;\n if (\n payloadAPIBase(scope['serverURL'], scope['apiRoute']) === null ||\n typeof scope['maxDepth'] !== 'number' ||\n !Number.isSafeInteger(scope['maxDepth']) ||\n scope['maxDepth'] < 0 ||\n typeof document !== 'object' ||\n document === null ||\n !pathSegment(document['slug'])\n ) {\n return false;\n }\n return (\n document['kind'] === 'global' ||\n (document['kind'] === 'collection' && isPreviewDocumentID(document['id']))\n );\n}\n\n/** @internal No clock renewal: every consumer checks the original absolute expiry. */\nexport function isPayloadScopeCurrent(context: AuthorizedPreviewContext): boolean {\n return (\n context.expiresAt !== undefined &&\n Number.isFinite(context.expiresAt) &&\n context.expiresAt > Date.now()\n );\n}\n\n/** @internal Both package consumers use the same kind/slug/ID comparison. */\nexport function matchesPreviewDocument(\n expected: AuthorizedPreviewDocument,\n actual: {\n readonly kind: 'collection' | 'global';\n readonly slug: string | undefined;\n readonly id?: unknown;\n },\n): boolean {\n return (\n expected.kind === actual.kind &&\n expected.slug === actual.slug &&\n (expected.kind === 'global' ||\n (isPreviewDocumentID(actual.id) && String(expected.id) === String(actual.id)))\n );\n}\n\n/** @internal A document response or form snapshot cannot contradict its bound identity. */\nexport function matchesPreviewDocumentData(\n expected: AuthorizedPreviewDocument,\n value: unknown,\n): boolean {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const data = value as Record<string, unknown>;\n if (expected.kind === 'global') {\n return !Object.hasOwn(data, 'globalType') || data['globalType'] === expected.slug;\n }\n return (\n Object.hasOwn(data, 'id') &&\n matchesPreviewDocument(expected, { kind: 'collection', slug: expected.slug, id: data['id'] })\n );\n}\n","/**\n * Server-side preview-intent detection. Every signal here is client-controlled:\n * a positive result is a delivery hint, never proof of identity or permission.\n * Verify with `authorizePreviewRequest()` before anything privileged.\n */\n\nimport { warnOnce } from '@/types/dev-warning';\n\n/** `query`: `?preview=true`; `fetch-dest`: `Sec-Fetch-Dest: iframe`; `referer`: an admin-origin Referer. */\nexport type PreviewSignal = 'query' | 'fetch-dest' | 'referer';\n\nexport interface PreviewRequestOptions {\n /** Which signals count. Default: all three. `['query']` is the adapters' 2.0 default. */\n readonly signals?: readonly PreviewSignal[];\n /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */\n readonly queryParams?: readonly string[];\n /** Admin origins whose Referer signals intent, e.g. `['https://cms.example.com']` — the adapters' option of the same name. */\n readonly allowedOrigins?: readonly string[];\n /** @deprecated Use `allowedOrigins`; removed in 3.0. Ignored when `allowedOrigins` is given. */\n readonly adminOrigins?: readonly string[];\n}\n\nconst DEFAULT_QUERY_PARAMS = ['preview', 'draft', 'livePreview'] as const;\n\n/** The request surface the predicate reads; `Request` satisfies it, Nitro/H3 events need a shim. */\nexport interface PreviewRequestLike {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/** Whether `request` carries live-preview intent. Intent, not authorization. */\nexport function hasPreviewIntent(\n request: PreviewRequestLike,\n options: PreviewRequestOptions = {},\n): boolean {\n const signals = new Set<PreviewSignal>(options.signals ?? ['query', 'fetch-dest', 'referer']);\n const queryParams = options.queryParams ?? DEFAULT_QUERY_PARAMS;\n\n if (signals.has('query')) {\n let url: URL | undefined;\n try {\n url = new URL(request.url);\n } catch {\n url = undefined;\n }\n if (url !== undefined) {\n for (const param of queryParams) {\n // The last value of a repeated key, as Next.js matches a `has` rule:\n // `withLivePreview()` marks the response by that reading, and the two\n // have to agree on `?preview=true&preview=0`.\n const value = url.searchParams.getAll(param).at(-1);\n if (value === 'true' || value === '1') return true;\n }\n }\n }\n\n if (signals.has('fetch-dest') && request.headers.get('sec-fetch-dest') === 'iframe') {\n return true;\n }\n\n // The 1.x spelling is honoured until 3.0; the canonical name wins when both are given.\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the alias is read exactly here\n const alias = options.adminOrigins;\n if (alias !== undefined) {\n warnOnce(\n 'admin-origins',\n '`hasPreviewIntent({ adminOrigins })` is removed in 3.0: name the option `allowedOrigins`; ' +\n '`pll migrate` renames it (ADR 0026).',\n );\n }\n const configured = options.allowedOrigins ?? alias ?? [];\n const adminOrigins = signals.has('referer') ? configured : [];\n if (adminOrigins.length > 0) {\n const referer = request.headers.get('referer');\n if (referer !== null) {\n try {\n const refererOrigin = new URL(referer).origin;\n for (const adminOrigin of adminOrigins) {\n try {\n if (new URL(adminOrigin).origin === refererOrigin) return true;\n } catch {\n // A malformed configured origin matches nothing.\n }\n }\n } catch {\n // A malformed referer is not a signal.\n }\n }\n }\n\n return false;\n}\n","/**\n * Typed helpers emitting the `data-payload-*` attributes the runtime reads.\n * `bindByPath` records the path through a Proxy, so a rename follows.\n */\n\nimport type { FieldName } from './paths';\n\n/** A spreadable attribute record: `<h1 {...bind<Homepage>('heroTitle')}>`. */\nexport interface FieldBindingAttributes {\n readonly 'data-payload-field': string;\n readonly 'data-payload-attribute'?: string;\n readonly 'data-payload-type'?: string;\n readonly 'data-payload-richtext'?: string;\n readonly 'data-payload-html'?: string;\n readonly 'data-payload-locale'?: string;\n readonly 'data-payload-alt'?: string;\n readonly 'data-payload-href'?: string;\n readonly 'data-payload-array-template'?: string;\n}\n\nexport interface BindOptions {\n /** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */\n readonly attribute?: string;\n /** Explicit field type, bypassing schema detection. */\n readonly type?: string;\n /** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */\n readonly richtext?: boolean;\n /** Render the value as sanitised HTML rather than text. */\n readonly html?: boolean;\n /** Lock this element to one locale, overriding the message locale. */\n readonly locale?: string;\n /** Field whose value becomes the image `alt`. */\n readonly alt?: string;\n /** Field whose value becomes the link `href`. */\n readonly href?: string;\n /** Markup per array item with `{{value}}` placeholders. */\n readonly arrayTemplate?: string;\n}\n\n/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */\nexport function bind<T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n): FieldBindingAttributes {\n return buildAttributes(field, options);\n}\n\n/** `bindByPath<Homepage>(d => d.slides[0].title)` → `slides.title`; array indices are dropped because bindings resolve against the schema. */\n// `T` is used once, by design: the caller names the document type so the\n// picker is checked against it instead of against an untyped `d`.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nexport function bindByPath<T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n): FieldBindingAttributes {\n const path = recordPath(picker);\n if (path.length === 0) {\n throw new Error(\n 'bindByPath: the picker did not read any field — return data.<field> instead of a constant',\n );\n }\n return buildAttributes(path.join('.'), options);\n}\n\nfunction buildAttributes(field: string, options: BindOptions | undefined): FieldBindingAttributes {\n if (field.length === 0) {\n throw new Error('bind: field name must be a non-empty string');\n }\n const attrs: {\n 'data-payload-field': string;\n 'data-payload-attribute'?: string;\n 'data-payload-type'?: string;\n 'data-payload-richtext'?: string;\n 'data-payload-html'?: string;\n 'data-payload-locale'?: string;\n 'data-payload-alt'?: string;\n 'data-payload-href'?: string;\n 'data-payload-array-template'?: string;\n } = { 'data-payload-field': field };\n if (options?.attribute !== undefined) attrs['data-payload-attribute'] = options.attribute;\n if (options?.type !== undefined) attrs['data-payload-type'] = options.type;\n // Presence attributes: the runtime tests for the attribute, not its value.\n if (options?.richtext === true) attrs['data-payload-richtext'] = '';\n if (options?.html === true) attrs['data-payload-html'] = '';\n if (options?.locale !== undefined) attrs['data-payload-locale'] = options.locale;\n if (options?.alt !== undefined) attrs['data-payload-alt'] = options.alt;\n if (options?.href !== undefined) attrs['data-payload-href'] = options.href;\n if (options?.arrayTemplate !== undefined) {\n attrs['data-payload-array-template'] = options.arrayTemplate;\n }\n return attrs;\n}\n\nfunction recordPath(picker: (data: never) => unknown): string[] {\n const path: string[] = [];\n const proxy: unknown = new Proxy(\n {},\n {\n get(_target, prop: string | symbol): unknown {\n if (typeof prop === 'symbol') return undefined;\n if (/^\\d+$/.test(prop)) return proxy;\n path.push(prop);\n return proxy;\n },\n },\n );\n try {\n picker(proxy as never);\n } catch {\n // A picker doing math or JSX on the proxy throws after recording the path it meant.\n }\n return path;\n}\n","/**\n * Binding helpers gated on one authorization verdict per request: the\n * attributes publish the content model, so an unauthorized response carries\n * none and a binding is suppressed whole, never in part. See ADR 0006.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport { bind, bindByPath, type BindOptions, type FieldBindingAttributes } from './bind';\nimport type { FieldName } from './paths';\n\nexport interface OwnerBindingAttributes {\n readonly 'data-payload-owner': string;\n}\n\n/** The attributes marking a server-rendered boundary (ADR 0011, docs/hybrid.md). */\nexport interface FragmentBoundaryAttributes {\n readonly 'data-payload-fragment': string;\n readonly 'data-payload-fragment-key'?: string;\n readonly 'data-payload-depends'?: string;\n}\n\nexport interface FragmentBoundaryOptions {\n /** Distinguishes boundaries when one registry id renders several on a page. */\n readonly key?: string;\n /** The fields that re-render the boundary. Without it, every update does. */\n readonly dependsOn?: readonly string[];\n}\n\n/**\n * Stricter than the endpoint's own check, which accepts either case: an id is\n * written here, and one that only differs in case from the registry key is a\n * boundary that silently never renders.\n */\nconst REGISTRY_ID = /^[a-z][a-z0-9-]{0,63}$/u;\nconst MAX_KEY_LENGTH = 128;\n\n/** No attributes at all: an unauthorized response is byte-identical to one that never knew about live preview. */\nexport type SuppressedBinding = Readonly<Record<string, never>>;\n\nconst SUPPRESSED: SuppressedBinding = Object.freeze({});\n\n/** The verdict from `authorizePreviewRequest()`, or `null` for a public response. Only a branded context authorizes emission. */\nexport interface PreviewBindingsOptions {\n readonly authorization: AuthorizedPreviewContext | null;\n /** Document this subtree belongs to, emitted as `data-payload-owner`; needed with `scopeBindingsByOwner`. */\n readonly owner?: string;\n}\n\n/** Request-scoped binding helpers carrying one authorization decision. */\nexport interface PreviewBindings {\n readonly authorized: boolean;\n /** Typed field binding, or nothing while unauthorized. */\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Rename-safe field binding, or nothing while unauthorized. */\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Owner marker for this subtree, or nothing while unauthorized or without an owner. */\n owner: () => OwnerBindingAttributes | SuppressedBinding;\n /**\n * A server-rendered boundary, or nothing while unauthorized — the id and the\n * fields it depends on describe the content model as much as a binding does.\n * The registry behind `id` is the endpoint's (docs/hybrid.md).\n */\n boundary: (\n id: string,\n options?: FragmentBoundaryOptions,\n ) => FragmentBoundaryAttributes | SuppressedBinding;\n /**\n * Field paths the page accounts for without binding them, and everything\n * below them (`data-payload-covers`, ADR 0022), or nothing while\n * unauthorized.\n */\n covers: (\n ...paths: readonly string[]\n ) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;\n}\n\n/** Built whether or not the response is authorized, so a bad id fails everywhere, not only in preview. */\nfunction boundaryAttributes(\n id: string,\n options: FragmentBoundaryOptions,\n): FragmentBoundaryAttributes {\n if (!REGISTRY_ID.test(id)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): \"${id}\" is not a registry id — lowercase, starting ` +\n 'with a letter, then letters, digits or hyphens (max 64). The runtime would post it and ' +\n 'the endpoint would refuse it.',\n );\n }\n const key = options.key;\n if (key !== undefined && (key.length === 0 || key.length > MAX_KEY_LENGTH)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): the key for \"${id}\" must be 1 to ${String(MAX_KEY_LENGTH)} characters.`,\n );\n }\n const dependsOn = options.dependsOn ?? [];\n return {\n 'data-payload-fragment': id,\n ...(key !== undefined ? { 'data-payload-fragment-key': key } : {}),\n ...(dependsOn.length > 0 ? { 'data-payload-depends': dependsOn.join(',') } : {}),\n };\n}\n\n/** A path is a field name or a dotted path inside one; whitespace would split it into two. */\nfunction coverAttribute(paths: readonly string[]): Readonly<Record<'data-payload-covers', string>> {\n for (const path of paths) {\n if (path.length === 0 || /[\\s,]/u.test(path)) {\n throw new RangeError(\n `createPreviewBindings().covers(): \"${path}\" is not a field path — a name or a dotted ` +\n 'path without spaces or commas.',\n );\n }\n }\n return { 'data-payload-covers': paths.join(' ') };\n}\n\n/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */\nexport function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings {\n // A copied or hand-written context is a public response.\n const authorized = isAuthorizedPreviewContext(options.authorization);\n const owner = options.owner !== undefined && options.owner.length > 0 ? options.owner : undefined;\n\n return Object.freeze({\n authorized,\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bind<T>(field, bindOptions) : SUPPRESSED,\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bindByPath<T>(picker, bindOptions) : SUPPRESSED,\n owner: (): OwnerBindingAttributes | SuppressedBinding =>\n authorized && owner !== undefined ? { 'data-payload-owner': owner } : SUPPRESSED,\n boundary: (\n id: string,\n boundaryOptions: FragmentBoundaryOptions = {},\n ): FragmentBoundaryAttributes | SuppressedBinding => {\n const attributes = boundaryAttributes(id, boundaryOptions);\n return authorized ? attributes : SUPPRESSED;\n },\n covers: (\n ...paths: readonly string[]\n ): Readonly<Record<'data-payload-covers', string>> | SuppressedBinding => {\n const attributes = coverAttribute(paths);\n return authorized && paths.length > 0 ? attributes : SUPPRESSED;\n },\n });\n}\n","/**\n * `definePreview()`: origin, route and depth bound once, so the initial draft\n * read and the runtime's `mergeDepth` cannot drift apart. Every read takes an\n * explicit authorization verdict; only a real context reads the draft.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport {\n isPayloadScopeCurrent,\n isPreviewDocumentID,\n matchesPreviewDocument,\n matchesPreviewDocumentData,\n payloadAPIBase,\n} from '@security/preview-scope';\n\n/**\n * A `fetch`-compatible function. The read forwards a session cookie, so it is\n * `cache: 'no-store'` (never a framework data cache) and `redirect: 'error'`.\n */\nexport type PreviewFetchFunction = (\n input: string,\n init: {\n readonly headers: Record<string, string>;\n readonly signal: AbortSignal;\n readonly cache: 'no-store';\n readonly redirect: 'error';\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n json(): Promise<unknown>;\n}>;\n\n/** What a read or a failure looked like, for logs and metrics. Never carries a header value. */\nexport type PreviewFetchDiagnostic =\n | {\n readonly kind: 'response';\n readonly url: string;\n readonly status: number;\n readonly draft: boolean;\n readonly durationMs: number;\n }\n | {\n readonly kind: 'failure';\n readonly url: string;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n readonly durationMs: number;\n };\n\n/** The one configuration the initial read and the runtime merge share. */\nexport interface PreviewServerConfig {\n /** Payload origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the read and for the runtime's `mergeDepth`. Required so the two cannot drift. */\n readonly depth: number;\n /** Upper bound for one read. Default 5000 ms. */\n readonly timeoutMs?: number;\n readonly fetch?: PreviewFetchFunction;\n /** Observe every read and failure — for logs and metrics, not for control flow. */\n readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;\n}\n\n/** Why a read did not produce a document. */\nexport type PreviewFetchFailureReason =\n 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';\n\nexport type PreviewFetchResult<T> =\n | {\n readonly ok: true;\n /** The document, or `null` when the query matched nothing. */\n readonly data: T | null;\n /** Whether the draft was requested — `true` exactly when `authorization` was a real context. */\n readonly draft: boolean;\n readonly status: number;\n }\n | {\n readonly ok: false;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n /** The underlying error for `network`, `invalid-json` and `aborted`. */\n readonly cause: unknown;\n };\n\n/** Thrown in `errorMode: 'throw'` instead of returning the failed result. */\nexport class PreviewFetchError extends Error {\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly url: string;\n constructor(\n reason: PreviewFetchFailureReason,\n url: string,\n status: number | undefined,\n cause: unknown,\n ) {\n super(\n `payload-live-preview: preview read failed (${reason}${status === undefined ? '' : ` ${String(status)}`}) for ${url}`,\n { cause },\n );\n this.name = 'PreviewFetchError';\n this.reason = reason;\n this.status = status;\n this.url = url;\n }\n}\n\n/** A scalar operand of a `where` operator; `null` is sent as the literal `null`. */\nexport type PreviewWhereValue = string | number | boolean | null | readonly (string | number)[];\n\n/**\n * A `where` tree in Payload's REST query shape: `{ slug: { equals: 'about' } }`,\n * `{ or: [{ … }, { … }] }`. Arrays of trees are indexed (`where[or][0][…]`).\n */\nexport interface PreviewWhere {\n readonly [key: string]: PreviewWhere | readonly PreviewWhere[] | PreviewWhereValue;\n}\n\n/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */\nexport interface PreviewReadOptions {\n /**\n * The verdict from `authorizePreviewRequest()`. Only a context that passes\n * `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a\n * JSON round trip reads the published document.\n */\n readonly authorization: AuthorizedPreviewContext | null;\n readonly locale?: string;\n /** Extra headers for this read; the context's headers win on conflict. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Abort the read from the outside, e.g. with the request's own signal. */\n readonly signal?: AbortSignal;\n /** `'result'` (default) returns the failed result; `'throw'` throws `PreviewFetchError`. */\n readonly errorMode?: 'result' | 'throw';\n}\n\nexport interface ReadDocumentOptions extends PreviewReadOptions {\n readonly collection: string;\n /** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */\n readonly id?: string | number;\n readonly where?: PreviewWhere;\n}\n\nexport interface ReadGlobalOptions extends PreviewReadOptions {\n readonly global: string;\n}\n\n/** The runtime/adapter options this configuration implies — spread them into the adapter or client options. */\nexport interface PreviewRuntimeOptions {\n readonly serverURL: string;\n readonly apiRoute: string;\n readonly mergeDepth: number;\n}\n\nexport interface PreviewServer {\n readonly config: Readonly<\n Required<Pick<PreviewServerConfig, 'serverURL' | 'apiRoute' | 'depth' | 'timeoutMs'>>\n >;\n /** `{ serverURL, apiRoute, mergeDepth }` — the same depth the reads use. */\n readonly runtimeOptions: PreviewRuntimeOptions;\n fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>>;\n fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst MIN_TIMEOUT_MS = 250;\n\n/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */\nexport function definePreview(config: PreviewServerConfig): PreviewServer {\n const serverURL = normalizeServerURL(config.serverURL);\n const route = config.apiRoute ?? '/api';\n const apiRoute = route.startsWith('/') ? route : `/${route}`;\n if (!Number.isInteger(config.depth) || config.depth < 0) {\n throw new Error(\n `payload-live-preview: depth must be a non-negative integer, got ${String(config.depth)}`,\n );\n }\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, config.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const resolved = Object.freeze({ serverURL, apiRoute, depth: config.depth, timeoutMs });\n const runtimeOptions = Object.freeze({ serverURL, apiRoute, mergeDepth: config.depth });\n const base = `${serverURL}${apiRoute}`;\n const settings: ReadSettings = { config: Object.freeze({ ...config, ...resolved }), timeoutMs };\n\n return Object.freeze({\n config: resolved,\n runtimeOptions,\n async fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const direct = options.id !== undefined;\n const allowed =\n (!direct || (isPreviewDocumentID(options.id) && options.where === undefined)) &&\n readScopeAllows(settings.config, options, {\n kind: 'collection',\n slug: options.collection,\n id: options.id,\n });\n // Reject before traversing a query or encoding its target. In particular,\n // a scoped caller cannot turn a refused filter into unbounded work.\n if (!allowed) return read<T>(settings, base, options, draft, false);\n const query = params(resolved.depth, options, draft);\n if (!direct) {\n query.set('limit', '1');\n if (options.where !== undefined) appendWhere(query, options.where, ['where']);\n }\n const id = isPreviewDocumentID(options.id)\n ? `/${encodeURIComponent(String(options.id))}`\n : '';\n const url = `${base}/${encodeURIComponent(options.collection)}${id}?${query.toString()}`;\n if (direct) return read<T>(settings, url, options, draft, allowed);\n const result = await read<{ docs?: T[] }>(settings, url, options, draft, allowed);\n if (!result.ok) return result;\n const first = result.data?.docs?.[0];\n return { ok: true, data: first ?? null, draft, status: result.status };\n },\n async fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(resolved.depth, options, draft);\n const url = `${base}/globals/${encodeURIComponent(options.global)}?${query.toString()}`;\n return read<T>(\n settings,\n url,\n options,\n draft,\n readScopeAllows(settings.config, options, { kind: 'global', slug: options.global }),\n );\n },\n });\n}\n\n/** What every read takes from the bound configuration. */\ninterface ReadSettings {\n readonly config: PreviewServerConfig;\n readonly timeoutMs: number;\n}\n\n/** One read in flight: what the diagnostic and a thrown failure need to say. */\ninterface ReadAttempt {\n readonly config: PreviewServerConfig;\n readonly url: string;\n readonly draft: boolean;\n readonly errorMode: 'result' | 'throw';\n readonly startedAt: number;\n}\n\nfunction readScopeAllows(\n config: PreviewServerConfig,\n options: PreviewReadOptions,\n target: Parameters<typeof matchesPreviewDocument>[1],\n): boolean {\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n const payload = context?.scope.payload;\n if (payload === undefined || context === null) return true;\n return (\n isPayloadScopeCurrent(context) &&\n (context.scope.locale === undefined || context.scope.locale === options.locale) &&\n payloadAPIBase(config.serverURL, config.apiRoute) ===\n payloadAPIBase(payload.serverURL, payload.apiRoute) &&\n config.depth <= payload.maxDepth &&\n matchesPreviewDocument(payload.document, target)\n );\n}\n\nfunction refuseScope<T>(attempt: ReadAttempt): PreviewFetchResult<T> {\n return settle(attempt, {\n ok: false,\n reason: 'scope',\n status: undefined,\n draft: attempt.draft,\n cause: undefined,\n });\n}\n\nfunction draftFor(options: PreviewReadOptions): boolean {\n return isAuthorizedPreviewContext(options.authorization);\n}\n\nfunction params(depth: number, options: PreviewReadOptions, draft: boolean): URLSearchParams {\n const query = new URLSearchParams();\n query.set('depth', String(depth));\n if (draft) query.set('draft', 'true');\n if (options.locale !== undefined) query.set('locale', options.locale);\n return query;\n}\n\n/** One GET against the REST API; every exit reports to `onDiagnostic` and is shaped as a result. */\nasync function read<T>(\n { config, timeoutMs }: ReadSettings,\n url: string,\n options: PreviewReadOptions,\n draft: boolean,\n allowed: boolean,\n): Promise<PreviewFetchResult<T>> {\n const fetchImpl: PreviewFetchFunction | undefined =\n config.fetch ?? (typeof fetch === 'function' ? fetch : undefined);\n const attempt: ReadAttempt = {\n config,\n url,\n draft,\n errorMode: options.errorMode ?? 'result',\n startedAt: Date.now(),\n };\n if (!allowed) return refuseScope(attempt);\n if (fetchImpl === undefined) {\n return settle(attempt, {\n ok: false,\n reason: 'no-fetch',\n status: undefined,\n draft,\n cause: undefined,\n });\n }\n const timeout = AbortSignal.timeout(timeoutMs);\n const signal =\n options.signal === undefined ? timeout : AbortSignal.any([timeout, options.signal]);\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n let response: Awaited<ReturnType<PreviewFetchFunction>>;\n try {\n // HTTP names are case-insensitive: object spread would keep both Cookie\n // and cookie, which fetch combines instead of choosing the verified one.\n const headers = new Headers({ accept: 'application/json', ...options.headers });\n for (const [name, value] of Object.entries(context?.payloadHeaders ?? {})) {\n headers.set(name, value);\n }\n response = await fetchImpl(url, {\n headers: Object.fromEntries(headers),\n signal,\n cache: 'no-store',\n redirect: 'error',\n });\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: failureReason(cause, options.signal),\n status: undefined,\n draft,\n cause,\n });\n }\n if (context?.scope.payload !== undefined && !isPayloadScopeCurrent(context)) {\n return refuseScope(attempt);\n }\n if (!response.ok) {\n return settle(attempt, {\n ok: false,\n reason: 'http',\n status: response.status,\n draft,\n cause: undefined,\n });\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: 'invalid-json',\n status: response.status,\n draft,\n cause,\n });\n }\n if (context?.scope.payload !== undefined && !isPayloadScopeCurrent(context)) {\n return refuseScope(attempt);\n }\n if (\n context?.scope.payload !== undefined &&\n !matchesPreviewDocumentData(context.scope.payload.document, body)\n ) {\n return refuseScope(attempt);\n }\n return settle(attempt, { ok: true, data: body as T, draft, status: response.status });\n}\n\n/** Report the outcome, then hand it back — or throw it, in `errorMode: 'throw'`. */\nfunction settle<T>(attempt: ReadAttempt, result: PreviewFetchResult<T>): PreviewFetchResult<T> {\n const { config, url, draft } = attempt;\n const durationMs = Date.now() - attempt.startedAt;\n if (result.ok) {\n config.onDiagnostic?.({ kind: 'response', url, status: result.status, draft, durationMs });\n return result;\n }\n config.onDiagnostic?.({\n kind: 'failure',\n url,\n reason: result.reason,\n status: result.status,\n draft,\n durationMs,\n });\n if (attempt.errorMode === 'throw') {\n throw new PreviewFetchError(result.reason, url, result.status, result.cause);\n }\n return result;\n}\n\nfunction normalizeServerURL(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new Error(`payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`);\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new Error(`payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`);\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`;\n}\n\nfunction failureReason(\n cause: unknown,\n external: AbortSignal | undefined,\n): PreviewFetchFailureReason {\n if (external?.aborted === true) return 'aborted';\n const name =\n typeof cause === 'object' && cause !== null ? (cause as { name?: unknown }).name : undefined;\n if (name === 'TimeoutError') return 'timeout';\n if (name === 'AbortError') return 'aborted';\n return 'network';\n}\n\nfunction isWhereTree(value: unknown): value is PreviewWhere {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction queryName(path: readonly string[]): string {\n return path.map((segment, index) => (index === 0 ? segment : `[${segment}]`)).join('');\n}\n\nfunction appendWhere(query: URLSearchParams, node: PreviewWhere, path: readonly string[]): void {\n for (const [key, value] of Object.entries(node)) {\n const nextPath = [...path, key];\n if (isWhereTree(value)) {\n appendWhere(query, value, nextPath);\n } else if (Array.isArray(value) && value.some(isWhereTree)) {\n // `or` / `and`: Payload's query parser turns bracketed indices back into an array.\n (value as readonly PreviewWhere[]).forEach((branch, index) => {\n appendWhere(query, branch, [...nextPath, String(index)]);\n });\n } else if (Array.isArray(value)) {\n query.set(queryName(nextPath), (value as readonly (string | number)[]).map(String).join(','));\n } else {\n // Only `null` is still an object here; Payload's query sanitizers read the literal back.\n query.set(queryName(nextPath), typeof value === 'object' ? 'null' : String(value));\n }\n }\n}\n","/**\n * Authorized preview context: the one verdict every privileged preview\n * decision is keyed on. Intent (`?preview=true`, an iframe destination, a\n * referer) is client-chosen; this turns a session, a signed token or the\n * application's own verifier into a branded context. Threat model: ADR 0006.\n */\n\nimport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n createAuthorizedPreviewContext,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewDocument,\n type AuthorizedPreviewPayloadScope,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n} from '@/types/authorized-preview';\nimport {\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nimport { authorizeSession, type PayloadSessionStrategy } from './preview-session';\nimport { authorizeToken, type SignedTokenStrategy } from './preview-token';\nimport { isValidPayloadScope } from './preview-scope';\n\nexport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewDocument,\n type AuthorizedPreviewPayloadScope,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n};\nexport {\n PreviewConfigurationError,\n type PreviewAuthorization,\n type PreviewAuthorizationOutcome,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nexport { extractCookie, type FetchLike, type PayloadSessionStrategy } from './preview-session';\nexport {\n issuePreviewToken,\n type IssuePreviewTokenOptions,\n type PreviewTokenClaims,\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape exists to be re-exported\n type PreviewTokenReplayChecks,\n type PreviewTokenReplayStore,\n type PreviewTokenTransport,\n type SignedTokenStrategy,\n type SubtleCryptoLike,\n} from './preview-token';\n\n/** Claims a consumer verifier returns for a request it accepts. */\nexport interface PreviewVerifierClaims {\n readonly subject?: string;\n readonly expiresAt?: number;\n readonly scope?: AuthorizedPreviewScope;\n readonly payloadHeaders?: Readonly<Record<string, string>>;\n}\n\n/** The application's own verification: claims authorize, `null` refuses, a throw is `unavailable`. */\nexport interface VerifierStrategy {\n readonly type: 'verifier';\n readonly verify: (\n request: PreviewAuthorizationRequest,\n ) => Promise<PreviewVerifierClaims | null> | PreviewVerifierClaims | null;\n readonly now?: () => number;\n}\n\nexport type PreviewAuthorizationStrategy =\n PayloadSessionStrategy | SignedTokenStrategy | VerifierStrategy;\n\n/** Decide whether `request` is an authorized preview; refusals resolve to `{ authorized: false }` and only a `PreviewConfigurationError` throws. */\nexport async function authorizePreviewRequest(\n request: PreviewAuthorizationRequest,\n strategy: PreviewAuthorizationStrategy,\n): Promise<PreviewAuthorization> {\n switch (strategy.type) {\n case 'payload-session':\n return authorizeSession(request, strategy);\n case 'signed-token':\n return authorizeToken(request, strategy);\n case 'verifier':\n return authorizeVerifier(request, strategy);\n }\n}\n\nasync function authorizeVerifier(\n request: PreviewAuthorizationRequest,\n strategy: VerifierStrategy,\n): Promise<PreviewAuthorization> {\n let claims: PreviewVerifierClaims | null;\n try {\n claims = await strategy.verify(request);\n } catch {\n return refused('unavailable');\n }\n if (claims === null) return refused('invalid');\n if (\n claims.scope?.payload !== undefined &&\n (!isValidPayloadScope(claims.scope.payload) || !Number.isFinite(claims.expiresAt))\n ) {\n return refused('invalid');\n }\n const now = (strategy.now ?? Date.now)();\n if (claims.expiresAt !== undefined && claims.expiresAt <= now) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'verifier',\n subject: claims.subject,\n authorizedAt: now,\n expiresAt: claims.expiresAt,\n scope: claims.scope ?? {},\n payloadHeaders: claims.payloadHeaders ?? {},\n }),\n };\n}\n","/** What a decision publishes for templates on `locals` (Astro, SvelteKit) or `event.context` (Nuxt). */\n\nimport { createPreviewBindings, type PreviewBindings } from '@dsl/preview-bindings';\nimport type { PreviewAuthorizationOutcome } from '@security/preview-verdict';\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\nimport type { PreviewDecision } from './policy';\n\n/**\n * The keys the adapters publish on the request-scoped context — `Astro.locals`,\n * SvelteKit's `event.locals`, Nuxt's `event.context`. Extend the framework's\n * `App.Locals` with it and templates read them typed. Every key is optional:\n * a refused preview withholds them all, and Next.js has no locals at all.\n */\nexport interface LivePreviewLocals {\n /**\n * The request-scoped CSP nonce, for templates that nonce their own scripts.\n * Set by the Astro middleware (prerendered pages included), the SvelteKit\n * handle and the Nuxt server handler or plugin on every request — except a\n * preview the `authorizePreview` hook refused, which gets no preview artefact.\n */\n readonly livePreviewNonce?: string;\n /** The verified `AuthorizedPreviewContext`. Set only when `authorizePreview` authorized an intent-bearing request. */\n readonly livePreviewAuthorization?: AuthorizedPreviewContext;\n /** The hook's `PreviewAuthorizationOutcome`, refusals included. Set whenever the hook ran: on intent, with `authorizePreview` configured. */\n readonly livePreviewAuthorizationOutcome?: PreviewAuthorizationOutcome;\n}\n\n/** The request-scoped nonce, for templates that nonce their own scripts. @internal */\nexport const NONCE_LOCALS_KEY = 'livePreviewNonce' satisfies keyof LivePreviewLocals;\n\n/** The verified `AuthorizedPreviewContext`; absent unless `authorizePreview` authorized. @internal */\nexport const AUTHORIZATION_LOCALS_KEY =\n 'livePreviewAuthorization' satisfies keyof LivePreviewLocals;\n\n/** The hook's `PreviewAuthorizationOutcome`; absent when no hook ran. @internal */\nexport const AUTHORIZATION_OUTCOME_LOCALS_KEY =\n 'livePreviewAuthorizationOutcome' satisfies keyof LivePreviewLocals;\n\n/**\n * What an adapter needs of a framework's request context: the three keys it\n * writes, and nothing else.\n *\n * This is what the adapters' event types declare rather than\n * `Record<string, unknown>`. A framework's own `App.Locals` is an interface\n * without an index signature, so it is not assignable to a record — which made\n * composing our handle with SvelteKit's `Handle` type fail to compile, even\n * though the values matched. The adapters assign through the interface's own\n * keys, so the published type cannot drift from what the code writes either.\n */\nexport type LivePreviewLocalsSink = {\n -readonly [K in keyof LivePreviewLocals]: LivePreviewLocals[K];\n};\n\n/** Publish the nonce alone — a prerendered page has no request to decide for. */\nexport function exposeNonce(locals: LivePreviewLocalsSink, nonce: string): void {\n locals.livePreviewNonce = nonce;\n}\n\n/** Publish what templates may read; after a refusal the nonce is withheld like every other preview artefact. */\nexport function exposeDecision(\n locals: LivePreviewLocalsSink,\n decision: PreviewDecision,\n nonce: string,\n): void {\n if (decision.exposeNonce || !decision.isPreview) locals.livePreviewNonce = nonce;\n if (decision.authorization !== null) locals.livePreviewAuthorization = decision.authorization;\n if (decision.outcome !== undefined) locals.livePreviewAuthorizationOutcome = decision.outcome;\n}\n\n/**\n * The binding helpers for one request, read straight off whatever the framework\n * calls locals — `Astro.locals`, SvelteKit's `event.locals`, Nuxt's\n * `event.context`.\n *\n * `createPreviewBindings({ authorization: locals.livePreviewAuthorization })`\n * says the same thing, and a template that spells it out is welcome to. This\n * exists because generated code should not have to: the build-time annotator\n * writes one call per component, and a helper that takes `unknown` needs no\n * `App.Locals` declaration in the project it writes into. The verdict is\n * checked, not trusted — only a context `authorizePreviewRequest()` produced\n * emits anything (ADR 0006).\n * @internal\n */\nexport function previewBindingsFromLocals(\n locals: unknown,\n options: { readonly owner?: string } = {},\n): PreviewBindings {\n const value =\n typeof locals === 'object' && locals !== null\n ? (locals as Record<string, unknown>)[AUTHORIZATION_LOCALS_KEY]\n : undefined;\n return createPreviewBindings({\n authorization: (value ?? null) as AuthorizedPreviewContext | null,\n ...(options.owner !== undefined ? { owner: options.owner } : {}),\n });\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["AUTHORIZED_PREVIEW_BRAND_KEY","BRAND","Symbol","for","isAuthorizedPreviewContext","value","Object","isFrozen","createAuthorizedPreviewContext","fields","context","scope","freeze","payload","document","payloadHeaders","defineProperty","enumerable","PreviewConfigurationError","Error","name","refused","outcome","authorized","DEFAULT_USERS_SLUG","COOKIE_NAME_PATTERN","COOKIE_VALUE_PATTERN","originNeeded","WeakMap","extractCookie","header","maxLength","length","prefix","values","part","split","trimmed","trim","startsWith","push","slice","test","httpOriginFor","option","url","URL","protocol","origin","warnOnce","key","message","env","globalThis","process","isDevelopmentProcess","holder","warned","Set","has","add","console","warn","cachedEncoder","DEFAULT_PURPOSE","MAX_TTL_MS","TOKEN_VERSION","encoder","TextEncoder","secretBytes","secret","bytes","encode","byteLength","String","cryptoFor","configured","candidate","crypto","subtle","async","hmacKey","importKey","hash","toBase64Url","binary","byte","fromCharCode","btoa","replace","fromBase64Url","text","padded","repeat","atob","Uint8Array","index","charCodeAt","pathSegment","isPreviewDocumentID","Number","isSafeInteger","payloadAPIBase","serverURL","apiRoute","includes","username","password","search","rawPath","route","every","pathname","isPayloadScopeCurrent","expiresAt","isFinite","Date","now","matchesPreviewDocument","expected","actual","kind","slug","id","DEFAULT_QUERY_PARAMS","bind","field","options","buildAttributes","bindByPath","picker","path","proxy","Proxy","get","_target","prop","recordPath","join","attrs","attribute","type","richtext","html","locale","alt","href","arrayTemplate","format","strategy","dependsOn","anchor","REGISTRY_ID","SUPPRESSED","createPreviewBindings","authorization","owner","bindOptions","boundary","boundaryOptions","attributes","RangeError","boundaryAttributes","covers","paths","coverAttribute","PreviewFetchError","reason","status","constructor","cause","super","this","readScopeAllows","config","target","depth","maxDepth","refuseScope","attempt","settle","ok","draft","draftFor","params","query","URLSearchParams","set","read","timeoutMs","allowed","fetchImpl","fetch","errorMode","startedAt","timeout","AbortSignal","signal","any","response","body","headers","Headers","accept","entries","fromEntries","cache","redirect","failureReason","json","Array","isArray","data","hasOwn","matchesPreviewDocumentData","result","durationMs","onDiagnostic","external","aborted","isWhereTree","queryName","map","segment","appendWhere","node","nextPath","some","forEach","branch","request","cookieName","cookieNameFor","sentOrigin","maxCookieLength","Math","max","endpoint","encodeURIComponent","usersSlug","learned","Map","forwarded","cookie","controller","AbortController","abort","isAborted","timer","addEventListener","once","setTimeout","DOMException","user","delete","clearTimeout","removeEventListener","collection","readUser","exp","readExpiry","subject","authorizedAt","authorizeSession","token","transport","searchParams","getAll","param","readToken","parts","payloadText","payloadBytes","signature","verified","verify","claims","parsed","JSON","parse","TextDecoder","decode","c","optionalString","parseClaims","iat","aud","audience","pur","purpose","pth","loc","replay","first","store","consume","isUsed","markUsed","consumeTokenId","jti","sub","authorizeToken","isValidPayloadScope","authorizeVerifier","normalizeServerURL","isInteger","resolved","runtimeOptions","mergeDepth","base","settings","fetchDocument","direct","where","toString","docs","fetchGlobal","global","signals","queryParams","at","alias","adminOrigins","allowedOrigins","referer","refererOrigin","adminOrigin","ttl","min","ttlMs","getRandomValues","wire","v","stringify","signed","sign","locals"],"sources":["../src/types/authorized-preview.ts","../src/security/preview-verdict.ts","../src/security/preview-session.ts","../src/types/dev-warning.ts","../src/security/preview-token.ts","../src/security/preview-scope.ts","../src/adapters/shared/preview-request.ts","../src/dsl/bind.ts","../src/dsl/preview-bindings.ts","../src/server/preview.ts","../src/security/preview-authorization.ts","../src/adapters/shared/locals.ts"],"sourcesContent":["/**\n * The authorized preview context's shape and brand, in the leaf `types` domain\n * so producer and consumers recognise the same context. See ADR 0006.\n */\n\n/**\n * The brand key, in the type and on the object.\n *\n * This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per\n * *declaration*, and the published entries each carry their own declaration\n * file — so `AuthorizedPreviewContext` from the root barrel was a different\n * type from the one in `./server` or in an adapter, and the documented flow\n * (authorize once, hand the context to the adapter hook and to the draft read)\n * did not type-check in any combination a consumer can write.\n *\n * That is the same trap the runtime brand below already avoids by using\n * `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the\n * type. A shared string literal is identical in every bundle for the same\n * reason a registry symbol is.\n *\n * It is not what makes a context trustworthy. `isAuthorizedPreviewContext`\n * decides that, on the registry symbol and on `Object.isFrozen`, and every\n * consumption point calls it — the adapter policy, the draft read and the\n * binding emitter. Writing this key by hand buys nothing.\n */\nexport const AUTHORIZED_PREVIEW_BRAND_KEY = 'payload-live-preview.authorized-preview-context';\n\nexport type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';\n\n/** One server-authorized Payload document, not a query selected by the browser. */\nexport type AuthorizedPreviewDocument =\n | { readonly kind: 'collection'; readonly slug: string; readonly id: string | number }\n | { readonly kind: 'global'; readonly slug: string };\n\n/** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */\nexport interface AuthorizedPreviewPayloadScope {\n /** Payload base URL, distinct from the preview site's audience. */\n readonly serverURL: string;\n /** REST prefix, default `/api`. */\n readonly apiRoute?: string;\n readonly document: AuthorizedPreviewDocument;\n /** Maximum requested population depth, not a field-level ACL. */\n readonly maxDepth: number;\n}\n\n/** What a context is bound to; every field is optional because every strategy binds differently. */\nexport interface AuthorizedPreviewScope {\n /** The site origin the credential was issued for. */\n readonly audience?: string;\n /** The request pathname the credential is valid for. */\n readonly path?: string;\n readonly locale?: string;\n /** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */\n readonly payload?: AuthorizedPreviewPayloadScope;\n}\n\n/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */\nexport interface AuthorizedPreviewContext {\n readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;\n readonly strategy: PreviewAuthorizationStrategyName;\n /** The user id, token subject, or whatever the verifier named. */\n readonly subject: string | undefined;\n /** Unix milliseconds. */\n readonly authorizedAt: number;\n /** Unix milliseconds, or `undefined` when the strategy does not know. */\n readonly expiresAt: number | undefined;\n readonly scope: AuthorizedPreviewScope;\n /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */\n readonly payloadHeaders: Readonly<Record<string, string>>;\n}\n\n// A registry symbol: entries are separate bundles, and a per-bundle `Symbol()`\n// would refuse the other's contexts in the package while unit tests passed.\nconst BRAND = Symbol.for(AUTHORIZED_PREVIEW_BRAND_KEY);\n\n/**\n * Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an\n * unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the\n * brand is a runtime check, not a cryptographic one (ADR 0006).\n */\nexport function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<PropertyKey, unknown>)[BRAND] === true &&\n Object.isFrozen(value)\n );\n}\n\n/** Internal: the one place a context is made. */\nexport function createAuthorizedPreviewContext(\n fields: Omit<AuthorizedPreviewContext, typeof AUTHORIZED_PREVIEW_BRAND_KEY>,\n): AuthorizedPreviewContext {\n const context = {\n ...fields,\n scope: Object.freeze({\n ...fields.scope,\n ...(fields.scope.payload === undefined\n ? {}\n : {\n payload: Object.freeze({\n ...fields.scope.payload,\n document: Object.freeze({ ...fields.scope.payload.document }),\n }),\n }),\n }),\n payloadHeaders: Object.freeze({ ...fields.payloadHeaders }),\n [BRAND]: true,\n [AUTHORIZED_PREVIEW_BRAND_KEY]: true as const,\n };\n // The cookie is HttpOnly in the browser. Not enumerable, so `JSON.stringify(locals)`, a log,\n // a spread or a framework serializer leaves it out; a draft read still reads it by name.\n Object.defineProperty(context, 'payloadHeaders', { enumerable: false });\n // No cast: the frozen object carries the brand key the interface declares.\n return Object.freeze(context);\n}\n","/**\n * The verdict shape every authorization strategy produces, and the one error\n * class that is allowed to escape it. Kept apart from the strategies so the\n * adapters can import the error without pulling in HMAC or session code.\n */\n\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\n\n/** Why a request was refused. `'authorized'` is the one non-refusal. */\nexport type PreviewAuthorizationOutcome =\n | 'authorized'\n | 'missing-credential'\n | 'invalid'\n | 'expired'\n | 'wrong-audience'\n | 'wrong-path'\n | 'wrong-locale'\n | 'wrong-purpose'\n | 'replayed'\n | 'unavailable';\n\n/** The result of `authorizePreviewRequest()`: a verdict, never an exception. */\nexport type PreviewAuthorization =\n | {\n readonly authorized: true;\n readonly outcome: 'authorized';\n readonly context: AuthorizedPreviewContext;\n }\n | {\n readonly authorized: false;\n readonly outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>;\n readonly context: null;\n };\n\n/** The request shape every strategy reads: a URL and a header getter. */\nexport interface PreviewAuthorizationRequest {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n /** Request lifetime, when supplied by the caller. Session fetches follow it. */\n readonly signal?: AbortSignal;\n}\n\n/**\n * A strategy was misconfigured — raised on first use and re-thrown by the\n * adapters rather than reported as `'unavailable'`, so it fails loudly.\n */\nexport class PreviewConfigurationError extends Error {\n override readonly name = 'PreviewConfigurationError';\n}\n\n/** Recognises the error across bundles, where `instanceof` sees a different class copy. */\nexport function isPreviewConfigurationError(error: unknown): error is PreviewConfigurationError {\n return (\n error instanceof PreviewConfigurationError ||\n (error instanceof Error && error.name === 'PreviewConfigurationError')\n );\n}\n\nexport function refused(\n outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>,\n): PreviewAuthorization {\n return { authorized: false, outcome, context: null };\n}\n","/**\n * The `payload-session` strategy: forward exactly one named cookie to\n * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.\n * A cookie-only request preserves working Payload 2 sessions. When `/me`\n * reports no user, one retry adds an `Origin` for Payload 3's CSRF check, and\n * an endpoint that needed it gets it on the first request from then on.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** A `fetch`-compatible function, injectable for tests. */\nexport type FetchLike = (\n input: string,\n init: { readonly headers: Record<string, string>; readonly signal: AbortSignal },\n) => Promise<{ readonly ok: boolean; readonly status: number; json(): Promise<unknown> }>;\n\nexport interface PayloadSessionStrategy {\n readonly type: 'payload-session';\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** Auth collection slug; `users` unless the project renamed it. */\n readonly usersSlug?: string;\n /** Cookie name; Payload's default is `payload-token`. */\n readonly cookieName?: string;\n /**\n * Send this listed origin with the cookie from the first request. Without\n * it, try the existing cookie-only request, then the origin of `serverURL`\n * once when `/me` reports a null user; an endpoint that needed the origin\n * gets it first from then on. Draft reads keep the accepted headers.\n */\n readonly origin?: string;\n /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */\n readonly timeoutMs?: number;\n /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */\n readonly maxCookieLength?: number;\n readonly fetch?: FetchLike;\n /** Clock, Unix milliseconds. Injectable for tests. */\n readonly now?: () => number;\n}\n\nconst DEFAULT_USERS_SLUG = 'users';\nconst DEFAULT_COOKIE_NAME = 'payload-token';\nconst DEFAULT_TIMEOUT_MS = 3_000;\nconst MIN_TIMEOUT_MS = 250;\nconst DEFAULT_MAX_COOKIE_LENGTH = 4_096;\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[A-Za-z0-9._~-]+$/;\n/** Below this an `exp` is Unix seconds; a millisecond timestamp passed it in 1973. */\nconst SECONDS_CEILING = 1e11;\n/** How often a `/me` known to need the origin is asked once more with the cookie alone. */\nconst COOKIE_ONLY_RETRY_MS = 60_000;\n\n/**\n * The `/me` endpoints that answered a user only with the origin, per fetch, with when the\n * cookie alone was last tried. Keyed by endpoint, not by strategy object: the recipes build\n * one per request. Without it, every token `/me` refuses costs two round trips.\n */\nconst originNeeded = new WeakMap<FetchLike, Map<string, number>>();\n\n/**\n * Exactly one value for `name`, or `null` when absent, empty, over `maxLength`\n * or repeated — a repeated cookie is ambiguous and is refused, not guessed.\n */\nexport function extractCookie(\n header: string | null,\n name: string,\n maxLength: number = DEFAULT_MAX_COOKIE_LENGTH,\n): string | null {\n if (header === null || header.length === 0) return null;\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const part of header.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) values.push(trimmed.slice(prefix.length));\n }\n if (values.length !== 1) return null;\n const value = values[0] ?? '';\n // A JWT is `[A-Za-z0-9._~-]`; anything else must not be forwarded in a header.\n if (value.length === 0 || value.length > maxLength || !COOKIE_VALUE_PATTERN.test(value)) {\n return null;\n }\n return value;\n}\n\nfunction cookieNameFor(configured: string | undefined): string {\n const name = configured ?? DEFAULT_COOKIE_NAME;\n if (!COOKIE_NAME_PATTERN.test(name)) {\n throw new PreviewConfigurationError(\n `payload-live-preview: cookie name \"${name}\" is not a valid cookie-name token`,\n );\n }\n return name;\n}\n\n/** The origin of an absolute http(s) URL; `option` names it in the error. */\nfunction httpOriginFor(value: string, option: string): string {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new PreviewConfigurationError(\n `payload-live-preview: ${option} \"${value}\" is not an absolute URL`,\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PreviewConfigurationError(\n `payload-live-preview: ${option} must be http(s), got \"${url.protocol}\"`,\n );\n }\n return url.origin;\n}\n\nexport async function authorizeSession(\n request: PreviewAuthorizationRequest,\n strategy: PayloadSessionStrategy,\n): Promise<PreviewAuthorization> {\n const cookieName = cookieNameFor(strategy.cookieName);\n const origin = httpOriginFor(strategy.serverURL, 'serverURL');\n const sentOrigin =\n strategy.origin === undefined ? undefined : httpOriginFor(strategy.origin, 'origin');\n const value = extractCookie(\n request.headers.get('cookie'),\n cookieName,\n strategy.maxCookieLength ?? DEFAULT_MAX_COOKIE_LENGTH,\n );\n if (value === null) return refused('missing-credential');\n const now = strategy.now ?? Date.now;\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, strategy.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n // Retain AbortSignal.timeout's refusal of non-integral or non-finite values.\n if (!Number.isSafeInteger(timeoutMs)) return refused('unavailable');\n const fetchImpl: FetchLike = strategy.fetch ?? globalThis.fetch;\n const usersSlug = encodeURIComponent(strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n // `depth=0`: the verdict needs the user's id, not its populated relations.\n const endpoint = `${origin}/api/${usersSlug}/me?depth=0`;\n // Registered on the first lesson; two first requests racing may drop one, learned again.\n const learned = originNeeded.get(fetchImpl) ?? new Map<string, number>();\n const forwarded = `${cookieName}=${value}`;\n let payloadHeaders: Record<string, string> = { cookie: forwarded };\n if (sentOrigin !== undefined) payloadHeaders['origin'] = sentOrigin;\n else if (learned.has(endpoint)) payloadHeaders['origin'] = origin;\n const controller = new AbortController();\n const abort = (): void => {\n controller.abort(request.signal?.reason);\n };\n const isAborted = (): boolean => controller.signal.aborted;\n request.signal?.addEventListener('abort', abort, { once: true });\n if (request.signal?.aborted === true) abort();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let body: unknown;\n try {\n if (isAborted()) return refused('unavailable');\n timer = setTimeout(() => {\n controller.abort(new DOMException('The operation timed out', 'TimeoutError'));\n }, timeoutMs);\n for (let attempt = 0; attempt < 2; attempt++) {\n const response = await fetchImpl(endpoint, {\n headers: { ...payloadHeaders, accept: 'application/json' },\n signal: controller.signal,\n });\n if (isAborted()) return refused('unavailable');\n if (!response.ok) return refused(response.status === 401 ? 'invalid' : 'unavailable');\n body = await response.json();\n if (isAborted()) return refused('unavailable');\n const user =\n typeof body === 'object' && body !== null ? (body as { user?: unknown }).user : undefined;\n if (attempt !== 0) {\n // The other form found a user: it is the one this endpoint takes.\n if (typeof user !== 'object' || user === null || Array.isArray(user)) break;\n if (payloadHeaders['origin'] === undefined) {\n learned.delete(endpoint);\n } else {\n originNeeded.set(fetchImpl, learned);\n learned.set(endpoint, now());\n }\n break;\n }\n if (sentOrigin !== undefined || user !== null) break;\n if (payloadHeaders['origin'] === undefined) {\n // Payload 2 accepts an absent Origin; adding one unconditionally can\n // reject its valid session when only the frontend is in the CSRF list.\n payloadHeaders = { cookie: forwarded, origin };\n continue;\n }\n // Known to need the origin: a CMS that stopped needing it is noticed within a minute.\n if (now() - (learned.get(endpoint) ?? 0) < COOKIE_ONLY_RETRY_MS) break;\n learned.set(endpoint, now());\n payloadHeaders = { cookie: forwarded };\n }\n } catch {\n return refused('unavailable');\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n request.signal?.removeEventListener('abort', abort);\n }\n const user = readUser(body, strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n if (user === null) return refused('invalid');\n const expiresAt = readExpiry(body);\n if (expiresAt !== undefined && expiresAt <= now()) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'payload-session',\n subject: user,\n authorizedAt: now(),\n expiresAt,\n scope: {},\n payloadHeaders,\n }),\n };\n}\n\n/** The user id from a `/me` body, or `null` when absent or from another auth collection — Payload answers `/me` for every one of them. */\nfunction readUser(body: unknown, usersSlug: string): string | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const user = (body as { user?: unknown }).user;\n if (typeof user !== 'object' || user === null || Array.isArray(user)) return null;\n const collection =\n (body as { collection?: unknown }).collection ?? (user as { collection?: unknown }).collection;\n if (collection !== undefined && collection !== usersSlug) return null;\n const id = (user as { id?: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n if (typeof id === 'number' && Number.isFinite(id)) return String(id);\n return null;\n}\n\n/** `/me` reports `exp` in Unix seconds; units are decided by magnitude so an expired millisecond value stays expired. */\nfunction readExpiry(body: unknown): number | undefined {\n const exp = (body as { exp?: unknown }).exp;\n if (typeof exp !== 'number' || !Number.isFinite(exp) || exp <= 0) return undefined;\n return exp < SECONDS_CEILING ? exp * 1000 : exp;\n}\n","/**\n * One development warning per process. The set of issued keys lives on\n * `globalThis` so two package copies in one process still warn once.\n */\n\nconst WARNED_KEY = '__payloadLivePreviewDeprecationsWarned';\n\n/** `true` outside a production build; `false` when `NODE_ENV` is production or unknown. */\nexport function isDevelopmentProcess(): boolean {\n try {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n ?.env;\n if (env === undefined) return false;\n return env['NODE_ENV'] !== 'production';\n } catch {\n return false;\n }\n}\n\n/** Warn once per process for `key`, outside production only. */\nexport function warnOnce(key: string, message: string): void {\n if (!isDevelopmentProcess()) return;\n const holder = globalThis as unknown as Record<string, Set<string> | undefined>;\n const warned = (holder[WARNED_KEY] ??= new Set<string>());\n if (warned.has(key)) return;\n warned.add(key);\n try {\n console.warn(`[payload-live-preview] ${message}`);\n } catch {\n // A console that throws must not break a request.\n }\n}\n\n/**\n * The deprecated options the adapters and the inline generator both accept;\n * an adapter hands them on to the generator, and the shared keys keep that to\n * one warning each (ADR 0026).\n */\nexport function warnDeprecatedOptions(options: {\n readonly defaults?: string | undefined;\n readonly onUnboundChange?: string | undefined;\n}): void {\n if (options.defaults === 'v1') {\n warnOnce(\n 'defaults-v1',\n \"`defaults: 'v1'` is removed in 3.0: `pll migrate` writes the 1.x rows it stands for \" +\n 'into your options, where they keep working (ADR 0026).',\n );\n }\n if (options.onUnboundChange !== undefined) {\n warnOnce(\n 'on-unbound-change',\n \"`onUnboundChange` is removed in 3.0: name it `onUnfaithfulPatch`, where 'route' is \" +\n \"'escalate'; `pll migrate` renames it (ADR 0026).\",\n );\n }\n}\n\n/** Test hook: forget every warning issued so far. */\nexport function resetDevWarnings(): void {\n const holder = globalThis as unknown as Record<string, Set<string> | undefined>;\n holder[WARNED_KEY] = undefined;\n}\n","/**\n * The `signed-token` strategy: a short-lived HMAC-SHA256 token minted by the\n * Payload side and verified by the site. Format and bindings: ADR 0006 §3.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport { warnOnce } from '@/types/dev-warning';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** The subset of Web Crypto the token strategy needs, injectable for environments that expose it elsewhere. */\nexport interface SubtleCryptoLike {\n readonly subtle: {\n importKey(\n format: 'raw',\n keyData: Uint8Array,\n algorithm: { readonly name: 'HMAC'; readonly hash: 'SHA-256' },\n extractable: false,\n keyUsages: readonly ('sign' | 'verify')[],\n ): Promise<CryptoKey>;\n sign(algorithm: 'HMAC', key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer>;\n verify(\n algorithm: 'HMAC',\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array,\n ): Promise<boolean>;\n };\n getRandomValues<T extends Uint8Array>(array: T): T;\n}\n\n/** Where a signed token is read from on the request. */\nexport type PreviewTokenTransport =\n | { readonly kind: 'query'; readonly param?: string }\n | { readonly kind: 'header'; readonly name?: string };\n\n/**\n * Optional replay store. Not shipped: a per-process memory is useless behind\n * a load balancer, so the store is a deployment decision.\n */\nexport interface PreviewTokenReplayStore {\n /**\n * Record `id` as used until `expiresAt` and say whether this call was the\n * first: `true` admits the token, anything else refuses it as `replayed`, a\n * throw refuses it as `unavailable`. Check and record in one step (Redis\n * `SET NX PX`, a unique insert): a read followed by a write lets two requests\n * that carry the same token and arrive together both pass.\n */\n consume(id: string, expiresAt: number): Promise<boolean> | boolean;\n}\n\n/**\n * @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one\n * step, so two requests with the same token that arrive together both pass\n * `isUsed` before either reaches `markUsed`. Implement\n * {@link PreviewTokenReplayStore.consume} instead.\n */\nexport interface PreviewTokenReplayChecks {\n isUsed(id: string): Promise<boolean> | boolean;\n markUsed(id: string, expiresAt: number): Promise<void> | void;\n}\n\nexport interface SignedTokenStrategy {\n readonly type: 'signed-token';\n /** Shared secret, at least 32 bytes; shorter ones are refused on first use. */\n readonly secret: string | Uint8Array;\n /** The site origin tokens must name, e.g. `https://www.example.com`. */\n readonly audience: string;\n /** Purpose string tokens must carry. Default `live-preview`. */\n readonly purpose?: string;\n /** Default: query parameter `previewToken`. */\n readonly transport?: PreviewTokenTransport;\n /** Resolve the request's locale for the `loc` binding. Unset: tokens with `loc` are refused as `wrong-locale`. */\n readonly locale?: (request: PreviewAuthorizationRequest) => string | undefined;\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape is accepted until 3.0\n readonly replay?: PreviewTokenReplayStore | PreviewTokenReplayChecks;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\n/** What `issuePreviewToken()` binds a token to. */\nexport interface PreviewTokenClaims {\n /** The site origin the token is for. */\n readonly audience: string;\n /** Request pathname the token opens. Recommended; omit only for a single-page preview. */\n readonly path?: string;\n readonly locale?: string;\n readonly subject?: string;\n readonly purpose?: string;\n /** Lifetime in milliseconds. Default ten minutes; capped at one hour. */\n readonly ttlMs?: number;\n}\n\nexport interface IssuePreviewTokenOptions {\n readonly secret: string | Uint8Array;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\ninterface WireClaims {\n readonly v: 1;\n readonly aud: string;\n readonly pth?: string;\n readonly loc?: string;\n readonly sub?: string;\n readonly pur: string;\n readonly iat: number;\n readonly exp: number;\n readonly jti: string;\n}\n\nconst DEFAULT_PURPOSE = 'live-preview';\nconst DEFAULT_QUERY_PARAM = 'previewToken';\nconst DEFAULT_HEADER_NAME = 'x-preview-token';\nconst DEFAULT_TTL_MS = 10 * 60_000;\nconst MAX_TTL_MS = 60 * 60_000;\nconst MIN_SECRET_BYTES = 32;\nconst TOKEN_VERSION = 'v1';\n/** A real token is a few hundred bytes. */\nconst MAX_TOKEN_LENGTH = 4_096;\n\n// Lazy: a module-scope `new TextEncoder()` is a side effect a bundler must keep.\nlet cachedEncoder: TextEncoder | undefined;\nfunction encoder(): TextEncoder {\n cachedEncoder ??= new TextEncoder();\n return cachedEncoder;\n}\n\nfunction secretBytes(secret: string | Uint8Array): Uint8Array {\n const bytes = typeof secret === 'string' ? encoder().encode(secret) : secret;\n if (bytes.byteLength < MIN_SECRET_BYTES) {\n throw new PreviewConfigurationError(\n `payload-live-preview: the preview token secret must be at least ${String(MIN_SECRET_BYTES)} bytes`,\n );\n }\n return bytes;\n}\n\nfunction cryptoFor(configured: SubtleCryptoLike | undefined): SubtleCryptoLike | undefined {\n if (configured !== undefined) return configured;\n const candidate = (globalThis as { crypto?: unknown }).crypto;\n if (typeof (candidate as { subtle?: unknown } | undefined)?.subtle === 'object') {\n return candidate as SubtleCryptoLike;\n }\n return undefined;\n}\n\nasync function hmacKey(crypto: SubtleCryptoLike, secret: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey('raw', secret, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n 'verify',\n ]);\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(text: string): Uint8Array | undefined {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return undefined;\n const padded =\n text.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (text.length % 4)) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n } catch {\n return undefined;\n }\n}\n\n/** Mint a preview token on the Payload side; pair with `buildLivePreviewUrl`. */\nexport async function issuePreviewToken(\n claims: PreviewTokenClaims,\n options: IssuePreviewTokenOptions,\n): Promise<string> {\n const crypto = cryptoFor(options.crypto);\n if (crypto === undefined) {\n throw new Error('payload-live-preview: no Web Crypto implementation available to sign tokens');\n }\n const secret = secretBytes(options.secret);\n const now = (options.now ?? Date.now)();\n const ttl = Math.min(MAX_TTL_MS, Math.max(1, claims.ttlMs ?? DEFAULT_TTL_MS));\n const id = new Uint8Array(16);\n crypto.getRandomValues(id);\n const wire: WireClaims = {\n v: 1,\n aud: claims.audience,\n ...(claims.path !== undefined ? { pth: claims.path } : {}),\n ...(claims.locale !== undefined ? { loc: claims.locale } : {}),\n ...(claims.subject !== undefined ? { sub: claims.subject } : {}),\n pur: claims.purpose ?? DEFAULT_PURPOSE,\n iat: now,\n exp: now + ttl,\n jti: toBase64Url(id),\n };\n const payload = toBase64Url(encoder().encode(JSON.stringify(wire)));\n const signed = `${TOKEN_VERSION}.${payload}`;\n const key = await hmacKey(crypto, secret);\n const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder().encode(signed)));\n return `${signed}.${toBase64Url(signature)}`;\n}\n\nfunction readToken(\n request: PreviewAuthorizationRequest,\n transport: PreviewTokenTransport | undefined,\n): string | null {\n if (transport?.kind === 'header') {\n return request.headers.get(transport.name ?? DEFAULT_HEADER_NAME);\n }\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n const values = url.searchParams.getAll(transport?.param ?? DEFAULT_QUERY_PARAM);\n return values.length === 1 ? (values[0] ?? null) : null;\n}\n\nfunction parseClaims(bytes: Uint8Array): WireClaims | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const c = parsed as Record<string, unknown>;\n const optionalString = (key: string): boolean =>\n c[key] === undefined || typeof c[key] === 'string';\n if (\n c['v'] !== 1 ||\n typeof c['aud'] !== 'string' ||\n typeof c['pur'] !== 'string' ||\n typeof c['iat'] !== 'number' ||\n typeof c['exp'] !== 'number' ||\n typeof c['jti'] !== 'string' ||\n !optionalString('pth') ||\n !optionalString('loc') ||\n !optionalString('sub')\n ) {\n return undefined;\n }\n return c as unknown as WireClaims;\n}\n\nexport async function authorizeToken(\n request: PreviewAuthorizationRequest,\n strategy: SignedTokenStrategy,\n): Promise<PreviewAuthorization> {\n const secret = secretBytes(strategy.secret);\n const crypto = cryptoFor(strategy.crypto);\n if (crypto === undefined) return refused('unavailable');\n const token = readToken(request, strategy.transport);\n if (token === null || token.length === 0) return refused('missing-credential');\n if (token.length > MAX_TOKEN_LENGTH) return refused('invalid');\n const parts = token.split('.');\n if (parts.length !== 3 || parts[0] !== TOKEN_VERSION) return refused('invalid');\n const payloadText = parts[1] ?? '';\n const payloadBytes = fromBase64Url(payloadText);\n const signature = fromBase64Url(parts[2] ?? '');\n if (payloadBytes === undefined || signature?.byteLength !== 32) return refused('invalid');\n let verified: boolean;\n try {\n const key = await hmacKey(crypto, secret);\n verified = await crypto.subtle.verify(\n 'HMAC',\n key,\n signature,\n encoder().encode(`${TOKEN_VERSION}.${payloadText}`),\n );\n } catch {\n return refused('unavailable');\n }\n if (!verified) return refused('invalid');\n const claims = parseClaims(payloadBytes);\n if (claims === undefined) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.exp <= now || claims.iat > claims.exp) return refused('expired');\n // No issuer of this format signs for longer; one that claims more was not issuePreviewToken.\n if (claims.exp - claims.iat > MAX_TTL_MS) return refused('invalid');\n if (claims.aud !== strategy.audience) return refused('wrong-audience');\n if (claims.pur !== (strategy.purpose ?? DEFAULT_PURPOSE)) return refused('wrong-purpose');\n if (claims.pth !== undefined) {\n let pathname: string;\n try {\n pathname = new URL(request.url).pathname;\n } catch {\n return refused('invalid');\n }\n if (claims.pth !== pathname) return refused('wrong-path');\n }\n if (claims.loc !== undefined) {\n const locale = strategy.locale?.(request);\n if (locale !== claims.loc) return refused('wrong-locale');\n }\n if (strategy.replay !== undefined) {\n let first: boolean;\n try {\n first = await consumeTokenId(strategy.replay, claims.jti, claims.exp);\n } catch {\n return refused('unavailable');\n }\n if (!first) return refused('replayed');\n }\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'signed-token',\n subject: claims.sub,\n authorizedAt: now,\n expiresAt: claims.exp,\n scope: {\n audience: claims.aud,\n ...(claims.pth !== undefined ? { path: claims.pth } : {}),\n ...(claims.loc !== undefined ? { locale: claims.loc } : {}),\n },\n payloadHeaders: {},\n }),\n };\n}\n\n/**\n * `true` when `id` was not seen before. A store that returns anything but\n * `true` from `consume` refuses the token: fail closed, since a forgotten\n * return would otherwise admit every replay. The 2.0 beta shape keeps its two\n * steps and its race; that is why it is deprecated rather than emulated.\n */\nasync function consumeTokenId(\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape is accepted until 3.0\n store: PreviewTokenReplayStore | PreviewTokenReplayChecks,\n id: string,\n expiresAt: number,\n): Promise<boolean> {\n if ('consume' in store) {\n // The store is consumer code; its answer is a value, not a type.\n const answer: unknown = await store.consume(id, expiresAt);\n return answer === true;\n }\n warnOnce(\n 'replay-is-used',\n 'A `signed-token` replay store with `isUsed` and `markUsed` is removed in 3.0: implement ' +\n '`consume(id, expiresAt)`, which checks and records in one step (ADR 0026).',\n );\n if (await store.isUsed(id)) return false;\n await store.markUsed(id, expiresAt);\n return true;\n}\n","/**\n * Shared checks for an opt-in Payload document capability (ADR 0006 §5c).\n * The verifier supplies authority; server reads and fragments only compare\n * their requested target against it, before forwarding credentials or rendering.\n */\nimport type {\n AuthorizedPreviewContext,\n AuthorizedPreviewDocument,\n AuthorizedPreviewPayloadScope,\n} from '@/types/authorized-preview';\n\nfunction pathSegment(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value !== '.' &&\n value !== '..' &&\n !/[\\s\\p{Cc}\\p{Cs}/\\\\?#%]/u.test(value)\n );\n}\n\n/** @internal Numeric REST IDs and their string spelling name the same document. */\nexport function isPreviewDocumentID(value: unknown): value is string | number {\n return typeof value === 'number' ? Number.isSafeInteger(value) : pathSegment(value);\n}\n\n/** @internal Refuse ambiguous paths rather than depending on a proxy's decoding rules. */\nexport function payloadAPIBase(serverURL: unknown, apiRoute: unknown = '/api'): string | null {\n if (typeof serverURL !== 'string' || typeof apiRoute !== 'string') return null;\n try {\n const url = new URL(serverURL);\n if (\n !['http:', 'https:'].includes(url.protocol) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash\n ) {\n return null;\n }\n // Inspect the raw path too: URL would otherwise erase dot segments first.\n const rawPath = serverURL.replace(/^[a-z]+:\\/\\/[^/]+/iu, '').replace(/\\/+$/, '');\n const route = apiRoute.replace(/^\\//, '').replace(/\\/$/, '');\n if (\n (rawPath !== '' && !rawPath.slice(1).split('/').every(pathSegment)) ||\n !route.split('/').every(pathSegment)\n ) {\n return null;\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}/${route}`;\n } catch {\n return null;\n }\n}\n\n/** @internal Validate a JavaScript verifier's binding, not merely its TypeScript shape. */\nexport function isValidPayloadScope(value: unknown): value is AuthorizedPreviewPayloadScope {\n if (typeof value !== 'object' || value === null) return false;\n const scope = value as Record<string, unknown>;\n const document = scope['document'] as Record<string, unknown> | null | undefined;\n if (\n payloadAPIBase(scope['serverURL'], scope['apiRoute']) === null ||\n typeof scope['maxDepth'] !== 'number' ||\n !Number.isSafeInteger(scope['maxDepth']) ||\n scope['maxDepth'] < 0 ||\n typeof document !== 'object' ||\n document === null ||\n !pathSegment(document['slug'])\n ) {\n return false;\n }\n return (\n document['kind'] === 'global' ||\n (document['kind'] === 'collection' && isPreviewDocumentID(document['id']))\n );\n}\n\n/** @internal No clock renewal: every consumer checks the original absolute expiry. */\nexport function isPayloadScopeCurrent(context: AuthorizedPreviewContext): boolean {\n return (\n context.expiresAt !== undefined &&\n Number.isFinite(context.expiresAt) &&\n context.expiresAt > Date.now()\n );\n}\n\n/** @internal Both package consumers use the same kind/slug/ID comparison. */\nexport function matchesPreviewDocument(\n expected: AuthorizedPreviewDocument,\n actual: {\n readonly kind: 'collection' | 'global';\n readonly slug: string | undefined;\n readonly id?: unknown;\n },\n): boolean {\n return (\n expected.kind === actual.kind &&\n expected.slug === actual.slug &&\n (expected.kind === 'global' ||\n (isPreviewDocumentID(actual.id) && String(expected.id) === String(actual.id)))\n );\n}\n\n/** @internal A document response or form snapshot cannot contradict its bound identity. */\nexport function matchesPreviewDocumentData(\n expected: AuthorizedPreviewDocument,\n value: unknown,\n): boolean {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const data = value as Record<string, unknown>;\n if (expected.kind === 'global') {\n return !Object.hasOwn(data, 'globalType') || data['globalType'] === expected.slug;\n }\n return (\n Object.hasOwn(data, 'id') &&\n matchesPreviewDocument(expected, { kind: 'collection', slug: expected.slug, id: data['id'] })\n );\n}\n","/**\n * Server-side preview-intent detection. Every signal here is client-controlled:\n * a positive result is a delivery hint, never proof of identity or permission.\n * Verify with `authorizePreviewRequest()` before anything privileged.\n */\n\nimport { warnOnce } from '@/types/dev-warning';\n\n/** `query`: `?preview=true`; `fetch-dest`: `Sec-Fetch-Dest: iframe`; `referer`: an admin-origin Referer. */\nexport type PreviewSignal = 'query' | 'fetch-dest' | 'referer';\n\nexport interface PreviewRequestOptions {\n /** Which signals count. Default: all three. `['query']` is the adapters' 2.0 default. */\n readonly signals?: readonly PreviewSignal[];\n /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */\n readonly queryParams?: readonly string[];\n /** Admin origins whose Referer signals intent, e.g. `['https://cms.example.com']` — the adapters' option of the same name. */\n readonly allowedOrigins?: readonly string[];\n /** @deprecated Use `allowedOrigins`; removed in 3.0. Ignored when `allowedOrigins` is given. */\n readonly adminOrigins?: readonly string[];\n}\n\nconst DEFAULT_QUERY_PARAMS = ['preview', 'draft', 'livePreview'] as const;\n\n/** The request surface the predicate reads; `Request` satisfies it, Nitro/H3 events need a shim. */\nexport interface PreviewRequestLike {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/** Whether `request` carries live-preview intent. Intent, not authorization. */\nexport function hasPreviewIntent(\n request: PreviewRequestLike,\n options: PreviewRequestOptions = {},\n): boolean {\n const signals = new Set<PreviewSignal>(options.signals ?? ['query', 'fetch-dest', 'referer']);\n const queryParams = options.queryParams ?? DEFAULT_QUERY_PARAMS;\n\n if (signals.has('query')) {\n let url: URL | undefined;\n try {\n url = new URL(request.url);\n } catch {\n url = undefined;\n }\n if (url !== undefined) {\n for (const param of queryParams) {\n // The last value of a repeated key, as Next.js matches a `has` rule:\n // `withLivePreview()` marks the response by that reading, and the two\n // have to agree on `?preview=true&preview=0`.\n const value = url.searchParams.getAll(param).at(-1);\n if (value === 'true' || value === '1') return true;\n }\n }\n }\n\n if (signals.has('fetch-dest') && request.headers.get('sec-fetch-dest') === 'iframe') {\n return true;\n }\n\n // The 1.x spelling is honoured until 3.0; the canonical name wins when both are given.\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the alias is read exactly here\n const alias = options.adminOrigins;\n if (alias !== undefined) {\n warnOnce(\n 'admin-origins',\n '`hasPreviewIntent({ adminOrigins })` is removed in 3.0: name the option `allowedOrigins`; ' +\n '`pll migrate` renames it (ADR 0026).',\n );\n }\n const configured = options.allowedOrigins ?? alias ?? [];\n const adminOrigins = signals.has('referer') ? configured : [];\n if (adminOrigins.length > 0) {\n const referer = request.headers.get('referer');\n if (referer !== null) {\n try {\n const refererOrigin = new URL(referer).origin;\n for (const adminOrigin of adminOrigins) {\n try {\n if (new URL(adminOrigin).origin === refererOrigin) return true;\n } catch {\n // A malformed configured origin matches nothing.\n }\n }\n } catch {\n // A malformed referer is not a signal.\n }\n }\n }\n\n return false;\n}\n","/**\n * Typed helpers emitting the `data-payload-*` attributes the runtime reads.\n * `bindByPath` records the path through a Proxy, so a rename follows.\n */\n\nimport type { FieldName } from './paths';\n\n/** A spreadable attribute record: `<h1 {...bind<Homepage>('heroTitle')}>`. */\nexport interface FieldBindingAttributes {\n readonly 'data-payload-field': string;\n readonly 'data-payload-attribute'?: string;\n readonly 'data-payload-type'?: string;\n readonly 'data-payload-richtext'?: string;\n readonly 'data-payload-html'?: string;\n readonly 'data-payload-locale'?: string;\n readonly 'data-payload-alt'?: string;\n readonly 'data-payload-href'?: string;\n readonly 'data-payload-array-template'?: string;\n readonly 'data-payload-format'?: string;\n readonly 'data-payload-text'?: string;\n readonly 'data-payload-strategy'?: string;\n readonly 'data-payload-depends'?: string;\n readonly 'data-payload-boundary'?: string;\n}\n\nexport interface BindOptions {\n /** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */\n readonly attribute?: string;\n /** Explicit field type, bypassing schema detection. */\n readonly type?: string;\n /** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */\n readonly richtext?: boolean;\n /** Render the value as sanitised HTML rather than text. */\n readonly html?: boolean;\n /** Lock this element to one locale, overriding the message locale. */\n readonly locale?: string;\n /** Field whose value becomes the image `alt`. */\n readonly alt?: string;\n /** Field whose value becomes the link `href`. */\n readonly href?: string;\n /** Markup per array item with `{{value}}` placeholders. */\n readonly arrayTemplate?: string;\n /** How a date or number is written, from `data-payload-format`'s vocabulary. */\n readonly format?: string;\n /** Let a text write replace the element's children. */\n readonly text?: boolean;\n /** Who shows this field: the patch (default), a server-rendered fragment, or the route. */\n readonly strategy?: 'patch' | 'fragment' | 'route';\n /** Fields whose change re-applies this binding under `skipUnchanged`. */\n readonly dependsOn?: readonly string[];\n /**\n * An anchor for a field that may be empty, so an edit that fills it has somewhere to\n * land. Set `hidden` yourself while the value is empty; the runtime keeps it right after that.\n */\n readonly anchor?: boolean;\n}\n\n/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */\nexport function bind<T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n): FieldBindingAttributes {\n return buildAttributes(field, options);\n}\n\n/** `bindByPath<Homepage>(d => d.slides[0].title)` → `slides.title`; array indices are dropped because bindings resolve against the schema. */\n// `T` is used once, by design: the caller names the document type so the\n// picker is checked against it instead of against an untyped `d`.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nexport function bindByPath<T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n): FieldBindingAttributes {\n const path = recordPath(picker);\n if (path.length === 0) {\n throw new Error(\n 'bindByPath: the picker did not read any field — return data.<field> instead of a constant',\n );\n }\n return buildAttributes(path.join('.'), options);\n}\n\nfunction buildAttributes(field: string, options: BindOptions | undefined): FieldBindingAttributes {\n if (field.length === 0) {\n throw new Error('bind: field name must be a non-empty string');\n }\n const attrs: {\n 'data-payload-field': string;\n 'data-payload-attribute'?: string;\n 'data-payload-type'?: string;\n 'data-payload-richtext'?: string;\n 'data-payload-html'?: string;\n 'data-payload-locale'?: string;\n 'data-payload-alt'?: string;\n 'data-payload-href'?: string;\n 'data-payload-array-template'?: string;\n 'data-payload-format'?: string;\n 'data-payload-text'?: string;\n 'data-payload-strategy'?: string;\n 'data-payload-depends'?: string;\n 'data-payload-boundary'?: string;\n } = { 'data-payload-field': field };\n if (options?.attribute !== undefined) attrs['data-payload-attribute'] = options.attribute;\n if (options?.type !== undefined) attrs['data-payload-type'] = options.type;\n // Presence attributes: the runtime tests for the attribute, not its value.\n if (options?.richtext === true) attrs['data-payload-richtext'] = '';\n if (options?.html === true) attrs['data-payload-html'] = '';\n if (options?.locale !== undefined) attrs['data-payload-locale'] = options.locale;\n if (options?.alt !== undefined) attrs['data-payload-alt'] = options.alt;\n if (options?.href !== undefined) attrs['data-payload-href'] = options.href;\n if (options?.arrayTemplate !== undefined) {\n attrs['data-payload-array-template'] = options.arrayTemplate;\n }\n if (options?.format !== undefined) attrs['data-payload-format'] = options.format;\n if (options?.text === true) attrs['data-payload-text'] = '';\n if (options?.strategy !== undefined) attrs['data-payload-strategy'] = options.strategy;\n if (options?.dependsOn !== undefined && options.dependsOn.length > 0) {\n attrs['data-payload-depends'] = options.dependsOn.join(',');\n }\n if (options?.anchor === true) attrs['data-payload-boundary'] = '';\n return attrs;\n}\n\nfunction recordPath(picker: (data: never) => unknown): string[] {\n const path: string[] = [];\n const proxy: unknown = new Proxy(\n {},\n {\n get(_target, prop: string | symbol): unknown {\n if (typeof prop === 'symbol') return undefined;\n if (/^\\d+$/.test(prop)) return proxy;\n path.push(prop);\n return proxy;\n },\n },\n );\n try {\n picker(proxy as never);\n } catch {\n // A picker doing math or JSX on the proxy throws after recording the path it meant.\n }\n return path;\n}\n","/**\n * Binding helpers gated on one authorization verdict per request: the\n * attributes publish the content model, so an unauthorized response carries\n * none and a binding is suppressed whole, never in part. See ADR 0006.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport { bind, bindByPath, type BindOptions, type FieldBindingAttributes } from './bind';\nimport type { FieldName } from './paths';\n\nexport interface OwnerBindingAttributes {\n readonly 'data-payload-owner': string;\n}\n\n/** The attributes marking a server-rendered boundary (ADR 0011, docs/hybrid.md). */\nexport interface FragmentBoundaryAttributes {\n readonly 'data-payload-fragment': string;\n readonly 'data-payload-fragment-key'?: string;\n readonly 'data-payload-depends'?: string;\n}\n\nexport interface FragmentBoundaryOptions {\n /** Distinguishes boundaries when one registry id renders several on a page. */\n readonly key?: string;\n /** The fields that re-render the boundary. Without it, every update does. */\n readonly dependsOn?: readonly string[];\n}\n\n/**\n * Stricter than the endpoint's own check, which accepts either case: an id is\n * written here, and one that only differs in case from the registry key is a\n * boundary that silently never renders.\n */\nconst REGISTRY_ID = /^[a-z][a-z0-9-]{0,63}$/u;\nconst MAX_KEY_LENGTH = 128;\n\n/** No attributes at all: an unauthorized response is byte-identical to one that never knew about live preview. */\nexport type SuppressedBinding = Readonly<Record<string, never>>;\n\nconst SUPPRESSED: SuppressedBinding = Object.freeze({});\n\n/** The verdict from `authorizePreviewRequest()`, or `null` for a public response. Only a branded context authorizes emission. */\nexport interface PreviewBindingsOptions {\n readonly authorization: AuthorizedPreviewContext | null;\n /** Document this subtree belongs to, emitted as `data-payload-owner`; needed with `scopeBindingsByOwner`. */\n readonly owner?: string;\n}\n\n/** Request-scoped binding helpers carrying one authorization decision. */\nexport interface PreviewBindings {\n readonly authorized: boolean;\n /** Typed field binding, or nothing while unauthorized. */\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Rename-safe field binding, or nothing while unauthorized. */\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Owner marker for this subtree, or nothing while unauthorized or without an owner. */\n owner: () => OwnerBindingAttributes | SuppressedBinding;\n /**\n * A server-rendered boundary, or nothing while unauthorized — the id and the\n * fields it depends on describe the content model as much as a binding does.\n * The registry behind `id` is the endpoint's (docs/hybrid.md).\n */\n boundary: (\n id: string,\n options?: FragmentBoundaryOptions,\n ) => FragmentBoundaryAttributes | SuppressedBinding;\n /**\n * Field paths the page accounts for without binding them, and everything\n * below them (`data-payload-covers`, ADR 0022), or nothing while\n * unauthorized.\n */\n covers: (\n ...paths: readonly string[]\n ) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;\n}\n\n/** Built whether or not the response is authorized, so a bad id fails everywhere, not only in preview. */\nfunction boundaryAttributes(\n id: string,\n options: FragmentBoundaryOptions,\n): FragmentBoundaryAttributes {\n if (!REGISTRY_ID.test(id)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): \"${id}\" is not a registry id — lowercase, starting ` +\n 'with a letter, then letters, digits or hyphens (max 64). The runtime would post it and ' +\n 'the endpoint would refuse it.',\n );\n }\n const key = options.key;\n if (key !== undefined && (key.length === 0 || key.length > MAX_KEY_LENGTH)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): the key for \"${id}\" must be 1 to ${String(MAX_KEY_LENGTH)} characters.`,\n );\n }\n const dependsOn = options.dependsOn ?? [];\n return {\n 'data-payload-fragment': id,\n ...(key !== undefined ? { 'data-payload-fragment-key': key } : {}),\n ...(dependsOn.length > 0 ? { 'data-payload-depends': dependsOn.join(',') } : {}),\n };\n}\n\n/** A path is a field name or a dotted path inside one; whitespace would split it into two. */\nfunction coverAttribute(paths: readonly string[]): Readonly<Record<'data-payload-covers', string>> {\n for (const path of paths) {\n if (path.length === 0 || /[\\s,]/u.test(path)) {\n throw new RangeError(\n `createPreviewBindings().covers(): \"${path}\" is not a field path — a name or a dotted ` +\n 'path without spaces or commas.',\n );\n }\n }\n return { 'data-payload-covers': paths.join(' ') };\n}\n\n/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */\nexport function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings {\n // A copied or hand-written context is a public response.\n const authorized = isAuthorizedPreviewContext(options.authorization);\n const owner = options.owner !== undefined && options.owner.length > 0 ? options.owner : undefined;\n\n return Object.freeze({\n authorized,\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bind<T>(field, bindOptions) : SUPPRESSED,\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bindByPath<T>(picker, bindOptions) : SUPPRESSED,\n owner: (): OwnerBindingAttributes | SuppressedBinding =>\n authorized && owner !== undefined ? { 'data-payload-owner': owner } : SUPPRESSED,\n boundary: (\n id: string,\n boundaryOptions: FragmentBoundaryOptions = {},\n ): FragmentBoundaryAttributes | SuppressedBinding => {\n const attributes = boundaryAttributes(id, boundaryOptions);\n return authorized ? attributes : SUPPRESSED;\n },\n covers: (\n ...paths: readonly string[]\n ): Readonly<Record<'data-payload-covers', string>> | SuppressedBinding => {\n const attributes = coverAttribute(paths);\n return authorized && paths.length > 0 ? attributes : SUPPRESSED;\n },\n });\n}\n","/**\n * `definePreview()`: origin, route and depth bound once, so the initial draft\n * read and the runtime's `mergeDepth` cannot drift apart. Every read takes an\n * explicit authorization verdict; only a real context reads the draft.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport {\n isPayloadScopeCurrent,\n isPreviewDocumentID,\n matchesPreviewDocument,\n matchesPreviewDocumentData,\n payloadAPIBase,\n} from '@security/preview-scope';\n\n/**\n * A `fetch`-compatible function. The read forwards a session cookie, so it is\n * `cache: 'no-store'` (never a framework data cache) and `redirect: 'error'`.\n */\nexport type PreviewFetchFunction = (\n input: string,\n init: {\n readonly headers: Record<string, string>;\n readonly signal: AbortSignal;\n readonly cache: 'no-store';\n readonly redirect: 'error';\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n json(): Promise<unknown>;\n}>;\n\n/** What a read or a failure looked like, for logs and metrics. Never carries a header value. */\nexport type PreviewFetchDiagnostic =\n | {\n readonly kind: 'response';\n readonly url: string;\n readonly status: number;\n readonly draft: boolean;\n readonly durationMs: number;\n }\n | {\n readonly kind: 'failure';\n readonly url: string;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n readonly durationMs: number;\n };\n\n/** The one configuration the initial read and the runtime merge share. */\nexport interface PreviewServerConfig {\n /** Payload origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the read and for the runtime's `mergeDepth`. Required so the two cannot drift. */\n readonly depth: number;\n /** Upper bound for one read. Default 5000 ms. */\n readonly timeoutMs?: number;\n readonly fetch?: PreviewFetchFunction;\n /** Observe every read and failure — for logs and metrics, not for control flow. */\n readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;\n}\n\n/** Why a read did not produce a document. */\nexport type PreviewFetchFailureReason =\n 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';\n\nexport type PreviewFetchResult<T> =\n | {\n readonly ok: true;\n /** The document, or `null` when the query matched nothing. */\n readonly data: T | null;\n /** Whether the draft was requested — `true` exactly when `authorization` was a real context. */\n readonly draft: boolean;\n readonly status: number;\n }\n | {\n readonly ok: false;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n /** The underlying error for `network`, `invalid-json` and `aborted`. */\n readonly cause: unknown;\n };\n\n/** Thrown in `errorMode: 'throw'` instead of returning the failed result. */\nexport class PreviewFetchError extends Error {\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly url: string;\n constructor(\n reason: PreviewFetchFailureReason,\n url: string,\n status: number | undefined,\n cause: unknown,\n ) {\n super(\n `payload-live-preview: preview read failed (${reason}${status === undefined ? '' : ` ${String(status)}`}) for ${url}`,\n { cause },\n );\n this.name = 'PreviewFetchError';\n this.reason = reason;\n this.status = status;\n this.url = url;\n }\n}\n\n/** A scalar operand of a `where` operator; `null` is sent as the literal `null`. */\nexport type PreviewWhereValue = string | number | boolean | null | readonly (string | number)[];\n\n/**\n * A `where` tree in Payload's REST query shape: `{ slug: { equals: 'about' } }`,\n * `{ or: [{ … }, { … }] }`. Arrays of trees are indexed (`where[or][0][…]`).\n */\nexport interface PreviewWhere {\n readonly [key: string]: PreviewWhere | readonly PreviewWhere[] | PreviewWhereValue;\n}\n\n/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */\nexport interface PreviewReadOptions {\n /**\n * The verdict from `authorizePreviewRequest()`. Only a context that passes\n * `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a\n * JSON round trip reads the published document.\n */\n readonly authorization: AuthorizedPreviewContext | null;\n readonly locale?: string;\n /** Extra headers for this read; the context's headers win on conflict. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Abort the read from the outside, e.g. with the request's own signal. */\n readonly signal?: AbortSignal;\n /** `'result'` (default) returns the failed result; `'throw'` throws `PreviewFetchError`. */\n readonly errorMode?: 'result' | 'throw';\n}\n\nexport interface ReadDocumentOptions extends PreviewReadOptions {\n readonly collection: string;\n /** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */\n readonly id?: string | number;\n readonly where?: PreviewWhere;\n}\n\nexport interface ReadGlobalOptions extends PreviewReadOptions {\n readonly global: string;\n}\n\n/** The runtime/adapter options this configuration implies — spread them into the adapter or client options. */\nexport interface PreviewRuntimeOptions {\n readonly serverURL: string;\n readonly apiRoute: string;\n readonly mergeDepth: number;\n}\n\nexport interface PreviewServer {\n readonly config: Readonly<\n Required<Pick<PreviewServerConfig, 'serverURL' | 'apiRoute' | 'depth' | 'timeoutMs'>>\n >;\n /** `{ serverURL, apiRoute, mergeDepth }` — the same depth the reads use. */\n readonly runtimeOptions: PreviewRuntimeOptions;\n fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>>;\n fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst MIN_TIMEOUT_MS = 250;\n\n/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */\nexport function definePreview(config: PreviewServerConfig): PreviewServer {\n const serverURL = normalizeServerURL(config.serverURL);\n const route = config.apiRoute ?? '/api';\n const apiRoute = route.startsWith('/') ? route : `/${route}`;\n if (!Number.isInteger(config.depth) || config.depth < 0) {\n throw new Error(\n `payload-live-preview: depth must be a non-negative integer, got ${String(config.depth)}`,\n );\n }\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, config.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const resolved = Object.freeze({ serverURL, apiRoute, depth: config.depth, timeoutMs });\n const runtimeOptions = Object.freeze({ serverURL, apiRoute, mergeDepth: config.depth });\n const base = `${serverURL}${apiRoute}`;\n const settings: ReadSettings = { config: Object.freeze({ ...config, ...resolved }), timeoutMs };\n\n return Object.freeze({\n config: resolved,\n runtimeOptions,\n async fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const direct = options.id !== undefined;\n const allowed =\n (!direct || (isPreviewDocumentID(options.id) && options.where === undefined)) &&\n readScopeAllows(settings.config, options, {\n kind: 'collection',\n slug: options.collection,\n id: options.id,\n });\n // Reject before traversing a query or encoding its target. In particular,\n // a scoped caller cannot turn a refused filter into unbounded work.\n if (!allowed) return read<T>(settings, base, options, draft, false);\n const query = params(resolved.depth, options, draft);\n if (!direct) {\n query.set('limit', '1');\n if (options.where !== undefined) appendWhere(query, options.where, ['where']);\n }\n const id = isPreviewDocumentID(options.id)\n ? `/${encodeURIComponent(String(options.id))}`\n : '';\n const url = `${base}/${encodeURIComponent(options.collection)}${id}?${query.toString()}`;\n if (direct) return read<T>(settings, url, options, draft, allowed);\n const result = await read<{ docs?: T[] }>(settings, url, options, draft, allowed);\n if (!result.ok) return result;\n const first = result.data?.docs?.[0];\n return { ok: true, data: first ?? null, draft, status: result.status };\n },\n async fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(resolved.depth, options, draft);\n const url = `${base}/globals/${encodeURIComponent(options.global)}?${query.toString()}`;\n return read<T>(\n settings,\n url,\n options,\n draft,\n readScopeAllows(settings.config, options, { kind: 'global', slug: options.global }),\n );\n },\n });\n}\n\n/** What every read takes from the bound configuration. */\ninterface ReadSettings {\n readonly config: PreviewServerConfig;\n readonly timeoutMs: number;\n}\n\n/** One read in flight: what the diagnostic and a thrown failure need to say. */\ninterface ReadAttempt {\n readonly config: PreviewServerConfig;\n readonly url: string;\n readonly draft: boolean;\n readonly errorMode: 'result' | 'throw';\n readonly startedAt: number;\n}\n\nfunction readScopeAllows(\n config: PreviewServerConfig,\n options: PreviewReadOptions,\n target: Parameters<typeof matchesPreviewDocument>[1],\n): boolean {\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n const payload = context?.scope.payload;\n if (payload === undefined || context === null) return true;\n return (\n isPayloadScopeCurrent(context) &&\n (context.scope.locale === undefined || context.scope.locale === options.locale) &&\n payloadAPIBase(config.serverURL, config.apiRoute) ===\n payloadAPIBase(payload.serverURL, payload.apiRoute) &&\n config.depth <= payload.maxDepth &&\n matchesPreviewDocument(payload.document, target)\n );\n}\n\nfunction refuseScope<T>(attempt: ReadAttempt): PreviewFetchResult<T> {\n return settle(attempt, {\n ok: false,\n reason: 'scope',\n status: undefined,\n draft: attempt.draft,\n cause: undefined,\n });\n}\n\nfunction draftFor(options: PreviewReadOptions): boolean {\n return isAuthorizedPreviewContext(options.authorization);\n}\n\nfunction params(depth: number, options: PreviewReadOptions, draft: boolean): URLSearchParams {\n const query = new URLSearchParams();\n query.set('depth', String(depth));\n if (draft) query.set('draft', 'true');\n if (options.locale !== undefined) query.set('locale', options.locale);\n return query;\n}\n\n/** One GET against the REST API; every exit reports to `onDiagnostic` and is shaped as a result. */\nasync function read<T>(\n { config, timeoutMs }: ReadSettings,\n url: string,\n options: PreviewReadOptions,\n draft: boolean,\n allowed: boolean,\n): Promise<PreviewFetchResult<T>> {\n const fetchImpl: PreviewFetchFunction | undefined =\n config.fetch ?? (typeof fetch === 'function' ? fetch : undefined);\n const attempt: ReadAttempt = {\n config,\n url,\n draft,\n errorMode: options.errorMode ?? 'result',\n startedAt: Date.now(),\n };\n if (!allowed) return refuseScope(attempt);\n if (fetchImpl === undefined) {\n return settle(attempt, {\n ok: false,\n reason: 'no-fetch',\n status: undefined,\n draft,\n cause: undefined,\n });\n }\n const timeout = AbortSignal.timeout(timeoutMs);\n const signal =\n options.signal === undefined ? timeout : AbortSignal.any([timeout, options.signal]);\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n let response: Awaited<ReturnType<PreviewFetchFunction>>;\n try {\n // HTTP names are case-insensitive: object spread would keep both Cookie\n // and cookie, which fetch combines instead of choosing the verified one.\n const headers = new Headers({ accept: 'application/json', ...options.headers });\n for (const [name, value] of Object.entries(context?.payloadHeaders ?? {})) {\n headers.set(name, value);\n }\n response = await fetchImpl(url, {\n headers: Object.fromEntries(headers),\n signal,\n cache: 'no-store',\n redirect: 'error',\n });\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: failureReason(cause, options.signal),\n status: undefined,\n draft,\n cause,\n });\n }\n if (context?.scope.payload !== undefined && !isPayloadScopeCurrent(context)) {\n return refuseScope(attempt);\n }\n if (!response.ok) {\n return settle(attempt, {\n ok: false,\n reason: 'http',\n status: response.status,\n draft,\n cause: undefined,\n });\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: 'invalid-json',\n status: response.status,\n draft,\n cause,\n });\n }\n if (context?.scope.payload !== undefined && !isPayloadScopeCurrent(context)) {\n return refuseScope(attempt);\n }\n if (\n context?.scope.payload !== undefined &&\n !matchesPreviewDocumentData(context.scope.payload.document, body)\n ) {\n return refuseScope(attempt);\n }\n return settle(attempt, { ok: true, data: body as T, draft, status: response.status });\n}\n\n/** Report the outcome, then hand it back — or throw it, in `errorMode: 'throw'`. */\nfunction settle<T>(attempt: ReadAttempt, result: PreviewFetchResult<T>): PreviewFetchResult<T> {\n const { config, url, draft } = attempt;\n const durationMs = Date.now() - attempt.startedAt;\n if (result.ok) {\n config.onDiagnostic?.({ kind: 'response', url, status: result.status, draft, durationMs });\n return result;\n }\n config.onDiagnostic?.({\n kind: 'failure',\n url,\n reason: result.reason,\n status: result.status,\n draft,\n durationMs,\n });\n if (attempt.errorMode === 'throw') {\n throw new PreviewFetchError(result.reason, url, result.status, result.cause);\n }\n return result;\n}\n\nfunction normalizeServerURL(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new Error(`payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`);\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new Error(`payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`);\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`;\n}\n\nfunction failureReason(\n cause: unknown,\n external: AbortSignal | undefined,\n): PreviewFetchFailureReason {\n if (external?.aborted === true) return 'aborted';\n const name =\n typeof cause === 'object' && cause !== null ? (cause as { name?: unknown }).name : undefined;\n if (name === 'TimeoutError') return 'timeout';\n if (name === 'AbortError') return 'aborted';\n return 'network';\n}\n\nfunction isWhereTree(value: unknown): value is PreviewWhere {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction queryName(path: readonly string[]): string {\n return path.map((segment, index) => (index === 0 ? segment : `[${segment}]`)).join('');\n}\n\nfunction appendWhere(query: URLSearchParams, node: PreviewWhere, path: readonly string[]): void {\n for (const [key, value] of Object.entries(node)) {\n const nextPath = [...path, key];\n if (isWhereTree(value)) {\n appendWhere(query, value, nextPath);\n } else if (Array.isArray(value) && value.some(isWhereTree)) {\n // `or` / `and`: Payload's query parser turns bracketed indices back into an array.\n (value as readonly PreviewWhere[]).forEach((branch, index) => {\n appendWhere(query, branch, [...nextPath, String(index)]);\n });\n } else if (Array.isArray(value)) {\n query.set(queryName(nextPath), (value as readonly (string | number)[]).map(String).join(','));\n } else {\n // Only `null` is still an object here; Payload's query sanitizers read the literal back.\n query.set(queryName(nextPath), typeof value === 'object' ? 'null' : String(value));\n }\n }\n}\n","/**\n * Authorized preview context: the one verdict every privileged preview\n * decision is keyed on. Intent (`?preview=true`, an iframe destination, a\n * referer) is client-chosen; this turns a session, a signed token or the\n * application's own verifier into a branded context. Threat model: ADR 0006.\n */\n\nimport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n createAuthorizedPreviewContext,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewDocument,\n type AuthorizedPreviewPayloadScope,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n} from '@/types/authorized-preview';\nimport {\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nimport { authorizeSession, type PayloadSessionStrategy } from './preview-session';\nimport { authorizeToken, type SignedTokenStrategy } from './preview-token';\nimport { isValidPayloadScope } from './preview-scope';\n\nexport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewDocument,\n type AuthorizedPreviewPayloadScope,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n};\nexport {\n PreviewConfigurationError,\n type PreviewAuthorization,\n type PreviewAuthorizationOutcome,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nexport { extractCookie, type FetchLike, type PayloadSessionStrategy } from './preview-session';\nexport {\n issuePreviewToken,\n type IssuePreviewTokenOptions,\n type PreviewTokenClaims,\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 2.0 beta shape exists to be re-exported\n type PreviewTokenReplayChecks,\n type PreviewTokenReplayStore,\n type PreviewTokenTransport,\n type SignedTokenStrategy,\n type SubtleCryptoLike,\n} from './preview-token';\n\n/** Claims a consumer verifier returns for a request it accepts. */\nexport interface PreviewVerifierClaims {\n readonly subject?: string;\n readonly expiresAt?: number;\n readonly scope?: AuthorizedPreviewScope;\n readonly payloadHeaders?: Readonly<Record<string, string>>;\n}\n\n/** The application's own verification: claims authorize, `null` refuses, a throw is `unavailable`. */\nexport interface VerifierStrategy {\n readonly type: 'verifier';\n readonly verify: (\n request: PreviewAuthorizationRequest,\n ) => Promise<PreviewVerifierClaims | null> | PreviewVerifierClaims | null;\n readonly now?: () => number;\n}\n\nexport type PreviewAuthorizationStrategy =\n PayloadSessionStrategy | SignedTokenStrategy | VerifierStrategy;\n\n/** Decide whether `request` is an authorized preview; refusals resolve to `{ authorized: false }` and only a `PreviewConfigurationError` throws. */\nexport async function authorizePreviewRequest(\n request: PreviewAuthorizationRequest,\n strategy: PreviewAuthorizationStrategy,\n): Promise<PreviewAuthorization> {\n switch (strategy.type) {\n case 'payload-session':\n return authorizeSession(request, strategy);\n case 'signed-token':\n return authorizeToken(request, strategy);\n case 'verifier':\n return authorizeVerifier(request, strategy);\n }\n}\n\nasync function authorizeVerifier(\n request: PreviewAuthorizationRequest,\n strategy: VerifierStrategy,\n): Promise<PreviewAuthorization> {\n let claims: PreviewVerifierClaims | null;\n try {\n claims = await strategy.verify(request);\n } catch {\n return refused('unavailable');\n }\n if (claims === null) return refused('invalid');\n if (\n claims.scope?.payload !== undefined &&\n (!isValidPayloadScope(claims.scope.payload) || !Number.isFinite(claims.expiresAt))\n ) {\n return refused('invalid');\n }\n const now = (strategy.now ?? Date.now)();\n if (claims.expiresAt !== undefined && claims.expiresAt <= now) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'verifier',\n subject: claims.subject,\n authorizedAt: now,\n expiresAt: claims.expiresAt,\n scope: claims.scope ?? {},\n payloadHeaders: claims.payloadHeaders ?? {},\n }),\n };\n}\n","/** What a decision publishes for templates on `locals` (Astro, SvelteKit) or `event.context` (Nuxt). */\n\nimport { createPreviewBindings, type PreviewBindings } from '@dsl/preview-bindings';\nimport type { PreviewAuthorizationOutcome } from '@security/preview-verdict';\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\nimport type { PreviewDecision } from './policy';\n\n/**\n * The keys the adapters publish on the request-scoped context — `Astro.locals`,\n * SvelteKit's `event.locals`, Nuxt's `event.context`. Extend the framework's\n * `App.Locals` with it and templates read them typed. Every key is optional:\n * a refused preview withholds them all, and Next.js has no locals at all.\n */\nexport interface LivePreviewLocals {\n /**\n * The request-scoped CSP nonce, for templates that nonce their own scripts.\n * Set by the Astro middleware (prerendered pages included), the SvelteKit\n * handle and the Nuxt server handler or plugin on every request — except a\n * preview the `authorizePreview` hook refused, which gets no preview artefact.\n */\n readonly livePreviewNonce?: string;\n /** The verified `AuthorizedPreviewContext`. Set only when `authorizePreview` authorized an intent-bearing request. */\n readonly livePreviewAuthorization?: AuthorizedPreviewContext;\n /** The hook's `PreviewAuthorizationOutcome`, refusals included. Set whenever the hook ran: on intent, with `authorizePreview` configured. */\n readonly livePreviewAuthorizationOutcome?: PreviewAuthorizationOutcome;\n /**\n * `true` on a request the Astro middleware decides with `authorizePreview`:\n * without `livePreviewAuthorization`, `PreviewBoundary` and `RichText` then write\n * no binding. Absent on a prerendered page, which has no request to decide for.\n */\n readonly livePreviewRequiresAuthorization?: boolean;\n}\n\n/** The request-scoped nonce, for templates that nonce their own scripts. @internal */\nexport const NONCE_LOCALS_KEY = 'livePreviewNonce' satisfies keyof LivePreviewLocals;\n\n/** The verified `AuthorizedPreviewContext`; absent unless `authorizePreview` authorized. @internal */\nexport const AUTHORIZATION_LOCALS_KEY =\n 'livePreviewAuthorization' satisfies keyof LivePreviewLocals;\n\n/** The hook's `PreviewAuthorizationOutcome`; absent when no hook ran. @internal */\nexport const AUTHORIZATION_OUTCOME_LOCALS_KEY =\n 'livePreviewAuthorizationOutcome' satisfies keyof LivePreviewLocals;\n\n/**\n * What an adapter needs of a framework's request context: the three keys it\n * writes, and nothing else.\n *\n * This is what the adapters' event types declare rather than\n * `Record<string, unknown>`. A framework's own `App.Locals` is an interface\n * without an index signature, so it is not assignable to a record — which made\n * composing our handle with SvelteKit's `Handle` type fail to compile, even\n * though the values matched. The adapters assign through the interface's own\n * keys, so the published type cannot drift from what the code writes either.\n */\nexport type LivePreviewLocalsSink = {\n -readonly [K in keyof LivePreviewLocals]: LivePreviewLocals[K];\n};\n\n/** Publish the nonce alone — a prerendered page has no request to decide for. */\nexport function exposeNonce(locals: LivePreviewLocalsSink, nonce: string): void {\n locals.livePreviewNonce = nonce;\n}\n\n/** Publish what templates may read; after a refusal the nonce is withheld like every other preview artefact. */\nexport function exposeDecision(\n locals: LivePreviewLocalsSink,\n decision: PreviewDecision,\n nonce: string,\n): void {\n if (decision.exposeNonce || !decision.isPreview) locals.livePreviewNonce = nonce;\n if (decision.authorization !== null) locals.livePreviewAuthorization = decision.authorization;\n if (decision.outcome !== undefined) locals.livePreviewAuthorizationOutcome = decision.outcome;\n}\n\n/**\n * The binding helpers for one request, read straight off whatever the framework\n * calls locals — `Astro.locals`, SvelteKit's `event.locals`, Nuxt's\n * `event.context`.\n *\n * `createPreviewBindings({ authorization: locals.livePreviewAuthorization })`\n * says the same thing, and a template that spells it out is welcome to. This\n * exists because generated code should not have to: the build-time annotator\n * writes one call per component, and a helper that takes `unknown` needs no\n * `App.Locals` declaration in the project it writes into. The verdict is\n * checked, not trusted — only a context `authorizePreviewRequest()` produced\n * emits anything (ADR 0006).\n * @public\n */\nexport function previewBindingsFromLocals(\n locals: unknown,\n options: { readonly owner?: string } = {},\n): PreviewBindings {\n const value =\n typeof locals === 'object' && locals !== null\n ? (locals as Record<string, unknown>)[AUTHORIZATION_LOCALS_KEY]\n : undefined;\n return createPreviewBindings({\n authorization: (value ?? null) as AuthorizedPreviewContext | null,\n ...(options.owner !== undefined ? { owner: options.owner } : {}),\n });\n}\n"],"mappings":"aAyBO,IAAMA,EAA+B,kDAgDtCC,EAAQC,OAAOC,IAAIH,GAOlB,SAASI,2BAA2BC,GACzC,MACmB,iBAAVA,GACG,OAAVA,IACmD,IAAlDA,EAAuCJ,IACxCK,OAAOC,SAASF,EAEpB,CAGO,SAASG,EACdC,GAEA,MAAMC,EAAU,IACXD,EACHE,MAAOL,OAAOM,OAAO,IAChBH,EAAOE,cACmB,IAAzBF,EAAOE,MAAME,QACb,GACA,CACEA,QAASP,OAAOM,OAAO,IAClBH,EAAOE,MAAME,QAChBC,SAAUR,OAAOM,OAAO,IAAKH,EAAOE,MAAME,QAAQC,gBAI5DC,eAAgBT,OAAOM,OAAO,IAAKH,EAAOM,iBAC1Cd,CAACA,IAAQ,EACTD,CAACA,IAA+B,GAMlC,OAFAM,OAAOU,eAAeN,EAAS,iBAAkB,CAAEO,YAAY,IAExDX,OAAOM,OAAOF,EACvB,CCrEO,IAAMQ,EAAN,cAAwCC,MAC3BC,KAAO,6BAWpB,SAASC,EACdC,GAEA,MAAO,CAAEC,YAAY,EAAOD,UAASZ,QAAS,KAChD,CChBA,IAAMc,EAAqB,QAKrBC,EAAsB,iCACtBC,EAAuB,qBAWvBC,iBAAA,IAAmBC,QAMlB,SAASC,EACdC,EACAV,EACAW,EAtBgC,MAwBhC,GAAe,OAAXD,GAAqC,IAAlBA,EAAOE,OAAc,OAAO,KACnD,MAAMC,EAAS,GAAGb,KACZc,EAAmB,GACzB,UAAWC,KAAQL,EAAOM,MAAM,KAAM,CACpC,MAAMC,EAAUF,EAAKG,OACjBD,EAAQE,WAAWN,IAASC,EAAOM,KAAKH,EAAQI,MAAMR,EAAOD,QACnE,CACA,GAAsB,IAAlBE,EAAOF,OAAc,OAAO,KAChC,MAAM3B,EAAQ6B,EAAO,IAAM,GAE3B,OAAqB,IAAjB7B,EAAM2B,QAAgB3B,EAAM2B,OAASD,IAAcL,EAAqBgB,KAAKrC,GACxE,KAEFA,CACT,CAaA,SAASsC,EAActC,EAAeuC,GACpC,IAAIC,EACJ,IACEA,EAAM,IAAIC,IAAIzC,EAChB,OACE,MAAM,IAAIa,EACR,yBAAyB0B,MAAWvC,4BAExC,CACA,GAAqB,WAAjBwC,EAAIE,UAA0C,UAAjBF,EAAIE,SACnC,MAAM,IAAI7B,EACR,yBAAyB0B,2BAAgCC,EAAIE,aAGjE,OAAOF,EAAIG,MACb,CChGO,SAASC,EAASC,EAAaC,GACpC,IAbK,WACL,IACE,MAAMC,EAAOC,WAA0EC,SACnFF,IACJ,YAAY,IAARA,GACuB,eAApBA,EAAc,QACvB,OACE,OAAO,CACT,CACF,CAIOG,GAAwB,OAC7B,MAAMC,EAASH,WACTI,EAAUD,EAAiB,4DAAUE,IAC3C,IAAID,EAAOE,IAAIT,GAAf,CACAO,EAAOG,IAAIV,GACX,IACEW,QAAQC,KAAK,0BAA0BX,IACzC,OAEA,CANqB,CAOvB,CCoFA,IAWIY,EAXEC,EAAkB,eAIlBC,EAAa,KAEbC,EAAgB,KAMtB,SAASC,IAEP,OADAJ,IAAkB,IAAIK,YACfL,CACT,CAEA,SAASM,EAAYC,GACnB,MAAMC,EAA0B,iBAAXD,EAAsBH,IAAUK,OAAOF,GAAUA,EACtE,GAAIC,EAAME,WAda,GAerB,MAAM,IAAIvD,EACR,mEAAmEwD,OAhBhD,aAmBvB,OAAOH,CACT,CAEA,SAASI,EAAUC,GACjB,QAAmB,IAAfA,EAA0B,OAAOA,EACrC,MAAMC,EAAaxB,WAAoCyB,OACvD,MAAuE,iBAA3DD,GAAgDE,OACnDF,OADT,CAIF,CAEAG,eAAeC,EAAQH,EAA0BR,GAC/C,OAAOQ,EAAOC,OAAOG,UAAU,MAAOZ,EAAQ,CAAElD,KAAM,OAAQ+D,KAAM,YAAa,EAAO,CACtF,OACA,UAEJ,CAEA,SAASC,EAAYb,GACnB,IAAIc,EAAS,GACb,UAAWC,KAAQf,EAAOc,GAAUX,OAAOa,aAAaD,GACxD,OAAOE,KAAKH,GAAQI,QAAQ,MAAO,KAAKA,QAAQ,MAAO,KAAKA,QAAQ,MAAO,GAC7E,CAEA,SAASC,EAAcC,GACrB,IAAK,mBAAmBjD,KAAKiD,GAAO,OACpC,MAAMC,EACJD,EAAKF,QAAQ,KAAM,KAAKA,QAAQ,KAAM,KAAO,IAAII,QAAQ,EAAKF,EAAK3D,OAAS,GAAM,GACpF,IACE,MAAMqD,EAASS,KAAKF,GACdrB,EAAQ,IAAIwB,WAAWV,EAAOrD,QACpC,QAASgE,EAAQ,EAAGA,EAAQX,EAAOrD,OAAQgE,GAAS,EAAGzB,EAAMyB,GAASX,EAAOY,WAAWD,GACxF,OAAOzB,CACT,OACE,MACF,CACF,CCrKA,SAAS2B,EAAY7F,GACnB,MACmB,iBAAVA,GACPA,EAAM2B,OAAS,GACL,MAAV3B,GACU,OAAVA,IACC,0BAA0BqC,KAAKrC,EAEpC,CAGO,SAAS8F,EAAoB9F,GAClC,MAAwB,iBAAVA,EAAqB+F,OAAOC,cAAchG,GAAS6F,EAAY7F,EAC/E,CAGO,SAASiG,EAAeC,EAAoBC,EAAoB,QACrE,GAAyB,iBAAdD,GAA8C,iBAAbC,EAAuB,OAAO,KAC1E,IACE,MAAM3D,EAAM,IAAIC,IAAIyD,GACpB,IACG,CAAC,QAAS,UAAUE,SAAS5D,EAAIE,WAClCF,EAAI6D,UACJ7D,EAAI8D,UACJ9D,EAAI+D,QACJ/D,EAAIsC,KAEJ,OAAO,KAGT,MAAM0B,EAAUN,EAAUd,QAAQ,sBAAuB,IAAIA,QAAQ,OAAQ,IACvEqB,EAAQN,EAASf,QAAQ,MAAO,IAAIA,QAAQ,MAAO,IACzD,MACe,KAAZoB,IAAmBA,EAAQpE,MAAM,GAAGL,MAAM,KAAK2E,MAAMb,KACrDY,EAAM1E,MAAM,KAAK2E,MAAMb,GAEjB,KAEF,GAAGrD,EAAIG,SAASH,EAAImE,SAASvB,QAAQ,OAAQ,OAAOqB,GAC7D,OACE,OAAO,IACT,CACF,CAyBO,SAASG,EAAsBvG,GACpC,YACwB,IAAtBA,EAAQwG,WACRd,OAAOe,SAASzG,EAAQwG,YACxBxG,EAAQwG,UAAYE,KAAKC,KAE7B,CAGO,SAASC,EACdC,EACAC,GAMA,OACED,EAASE,OAASD,EAAOC,MACzBF,EAASG,OAASF,EAAOE,OACN,WAAlBH,EAASE,MACPtB,EAAoBqB,EAAOG,KAAOjD,OAAO6C,EAASI,MAAQjD,OAAO8C,EAAOG,IAE/E,CC/EA,IAAMC,EAAuB,CAAC,UAAW,QAAS,eCoC3C,SAASC,KACdC,EACAC,GAEA,OAAOC,EAAgBF,EAAOC,EAChC,CAMO,SAASE,WACdC,EACAH,GAEA,MAAMI,EAkDR,SAAoBD,GAClB,MAAMC,EAAiB,GACjBC,EAAiB,IAAIC,MACzB,GACA,CACE,GAAAC,CAAIC,EAASC,GACX,GAAoB,iBAATA,EACX,MAAI,QAAQ9F,KAAK8F,IACjBL,EAAK3F,KAAKgG,GADqBJ,CAGjC,IAGJ,IACEF,EAAOE,EACT,OAEA,CACA,OAAOD,CACT,CArEeM,CAAWP,GACxB,GAAoB,IAAhBC,EAAKnG,OACP,MAAM,IAAIb,MACR,6FAGJ,OAAO6G,EAAgBG,EAAKO,KAAK,KAAMX,EACzC,CAEA,SAASC,EAAgBF,EAAeC,GACtC,GAAqB,IAAjBD,EAAM9F,OACR,MAAM,IAAIb,MAAM,+CAElB,MAAMwH,EAeF,CAAE,qBAAsBb,GAmB5B,YAlB2B,IAAvBC,GAASa,YAAyBD,EAAM,0BAA4BZ,EAAQa,gBAC1D,IAAlBb,GAASc,OAAoBF,EAAM,qBAAuBZ,EAAQc,OAE5C,IAAtBd,GAASe,WAAmBH,EAAM,yBAA2B,KAC3C,IAAlBZ,GAASgB,OAAeJ,EAAM,qBAAuB,SACjC,IAApBZ,GAASiB,SAAsBL,EAAM,uBAAyBZ,EAAQiB,aACrD,IAAjBjB,GAASkB,MAAmBN,EAAM,oBAAsBZ,EAAQkB,UAC9C,IAAlBlB,GAASmB,OAAoBP,EAAM,qBAAuBZ,EAAQmB,WACvC,IAA3BnB,GAASoB,gBACXR,EAAM,+BAAiCZ,EAAQoB,oBAEzB,IAApBpB,GAASqB,SAAsBT,EAAM,uBAAyBZ,EAAQqB,SACpD,IAAlBrB,GAASpC,OAAegD,EAAM,qBAAuB,SAC/B,IAAtBZ,GAASsB,WAAwBV,EAAM,yBAA2BZ,EAAQsB,eACnD,IAAvBtB,GAASuB,WAA2BvB,EAAQuB,UAAUtH,OAAS,IACjE2G,EAAM,wBAA0BZ,EAAQuB,UAAUZ,KAAK,OAEjC,IAApBX,GAASwB,SAAiBZ,EAAM,yBAA2B,IACxDA,CACT,CCrFA,IAAMa,EAAc,0BAMdC,EAAgCnJ,OAAOM,OAAO,IAqF7C,SAAS8I,sBAAsB3B,GAEpC,MAAMxG,EAAanB,2BAA2B2H,EAAQ4B,eAChDC,OAA0B,IAAlB7B,EAAQ6B,OAAuB7B,EAAQ6B,MAAM5H,OAAS,EAAI+F,EAAQ6B,WAAQ,EAExF,OAAOtJ,OAAOM,OAAO,CACnBW,aACAsG,KAAM,CACJC,EACA+B,IAEAtI,EAAasG,KAAQC,EAAO+B,GAAeJ,EAG7CxB,WAAY,CACVC,EACA2B,IAEAtI,EAAa0G,WAAcC,EAAQ2B,GAAeJ,EACpDG,MAAO,IACLrI,QAAwB,IAAVqI,EAAsB,CAAE,qBAAsBA,GAAUH,EACxEK,SAAU,CACRnC,EACAoC,EAA2C,MAE3C,MAAMC,EAhEZ,SACErC,EACAI,GAEA,IAAKyB,EAAY9G,KAAKiF,GACpB,MAAM,IAAIsC,WACR,wCAAwCtC,sKAK5C,MAAMzE,EAAM6E,EAAQ7E,IACpB,QAAY,IAARA,IAAqC,IAAfA,EAAIlB,QAAgBkB,EAAIlB,OA/D7B,KAgEnB,MAAM,IAAIiI,WACR,oDAAoDtC,mBAAoBjD,OAjEvD,oBAoErB,MAAM4E,EAAYvB,EAAQuB,WAAa,GACvC,MAAO,CACL,wBAAyB3B,UACb,IAARzE,EAAoB,CAAE,4BAA6BA,GAAQ,MAC3DoG,EAAUtH,OAAS,EAAI,CAAE,uBAAwBsH,EAAUZ,KAAK,MAAS,GAEjF,CAyCyBwB,CAAmBvC,EAAIoC,GAC1C,OAAOxI,EAAayI,EAAaP,GAEnCU,OAAQ,IACHC,KAEH,MAAMJ,EA5CZ,SAAwBI,GACtB,UAAWjC,KAAQiC,EACjB,GAAoB,IAAhBjC,EAAKnG,QAAgB,SAASU,KAAKyF,GACrC,MAAM,IAAI8B,WACR,sCAAsC9B,8EAK5C,MAAO,CAAE,sBAAuBiC,EAAM1B,KAAK,KAC7C,CAkCyB2B,CAAeD,GAClC,OAAO7I,GAAc6I,EAAMpI,OAAS,EAAIgI,EAAaP,IAG3D,CCtEO,IAAMa,EAAN,cAAgCnJ,MAC5BoJ,OACAC,OACA3H,IACT,WAAA4H,CACEF,EACA1H,EACA2H,EACAE,GAEAC,MACE,8CAA8CJ,SAAoB,IAAXC,EAAuB,GAAK,IAAI9F,OAAO8F,aAAkB3H,IAChH,CAAE6H,UAEJE,KAAKxJ,KAAO,oBACZwJ,KAAKL,OAASA,EACdK,KAAKJ,OAASA,EACdI,KAAK/H,IAAMA,CACb,GAmJF,SAASgI,EACPC,EACA/C,EACAgD,GAEA,MAAMrK,EAAUN,2BAA2B2H,EAAQ4B,eAAiB5B,EAAQ4B,cAAgB,KACtF9I,EAAUH,GAASC,MAAME,QAC/B,YAAgB,IAAZA,GAAqC,OAAZH,GAE3BuG,EAAsBvG,UACI,IAAzBA,EAAQC,MAAMqI,QAAwBtI,EAAQC,MAAMqI,SAAWjB,EAAQiB,SACxE1C,EAAewE,EAAOvE,UAAWuE,EAAOtE,YACtCF,EAAezF,EAAQ0F,UAAW1F,EAAQ2F,WAC5CsE,EAAOE,OAASnK,EAAQoK,UACxB3D,EAAuBzG,EAAQC,SAAUiK,EAE7C,CAEA,SAASG,EAAeC,GACtB,OAAOC,EAAOD,EAAS,CACrBE,IAAI,EACJd,OAAQ,QACRC,YAAQ,EACRc,MAAOH,EAAQG,MACfZ,WAAO,GAEX,CAEA,SAASa,EAASxD,GAChB,OAAO3H,2BAA2B2H,EAAQ4B,cAC5C,CAEA,SAAS6B,EAAOR,EAAejD,EAA6BuD,GAC1D,MAAMG,EAAQ,IAAIC,gBAIlB,OAHAD,EAAME,IAAI,QAASjH,OAAOsG,IACtBM,GAAOG,EAAME,IAAI,QAAS,aACP,IAAnB5D,EAAQiB,QAAWyC,EAAiBE,IAAI,SAAU5D,EAAQiB,QACvDyC,CACT,CAGAzG,eAAe4G,GACbd,OAAEA,EAAAe,UAAQA,GACVhJ,EACAkF,EACAuD,EACAQ,GAEA,MAAMC,EACJjB,EAAOkB,QAA2B,mBAAVA,MAAuBA,WAAQ,GACnDb,EAAuB,CAC3BL,SACAjI,MACAyI,QACAW,UAAWlE,EAAQkE,WAAa,SAChCC,UAAW9E,KAAKC,OAElB,IAAKyE,EAAS,OAAOZ,EAAYC,GACjC,QAAkB,IAAdY,EACF,OAAOX,EAAOD,EAAS,CACrBE,IAAI,EACJd,OAAQ,WACRC,YAAQ,EACRc,QACAZ,WAAO,IAGX,MAAMyB,EAAUC,YAAYD,QAAQN,GAC9BQ,OACe,IAAnBtE,EAAQsE,OAAuBF,EAAUC,YAAYE,IAAI,CAACH,EAASpE,EAAQsE,SACvE3L,EAAUN,2BAA2B2H,EAAQ4B,eAAiB5B,EAAQ4B,cAAgB,KAC5F,IAAI4C,EAmCAC,EAlCJ,IAGE,MAAMC,EAAU,IAAIC,QAAQ,CAAEC,OAAQ,sBAAuB5E,EAAQ0E,UACrE,UAAYrL,EAAMf,KAAUC,OAAOsM,QAAQlM,GAASK,gBAAkB,IACpE0L,EAAQd,IAAIvK,EAAMf,GAEpBkM,QAAiBR,EAAUlJ,EAAK,CAC9B4J,QAASnM,OAAOuM,YAAYJ,GAC5BJ,SACAS,MAAO,WACPC,SAAU,SAEd,OAASrC,GACP,OAAOU,EAAOD,EAAS,CACrBE,IAAI,EACJd,OAAQyC,EAActC,EAAO3C,EAAQsE,QACrC7B,YAAQ,EACRc,QACAZ,SAEJ,CACA,QAA+B,IAA3BhK,GAASC,MAAME,UAA0BoG,EAAsBvG,GACjE,OAAOwK,EAAYC,GAErB,IAAKoB,EAASlB,GACZ,OAAOD,EAAOD,EAAS,CACrBE,IAAI,EACJd,OAAQ,OACRC,OAAQ+B,EAAS/B,OACjBc,QACAZ,WAAO,IAIX,IACE8B,QAAaD,EAASU,MACxB,OAASvC,GACP,OAAOU,EAAOD,EAAS,CACrBE,IAAI,EACJd,OAAQ,eACRC,OAAQ+B,EAAS/B,OACjBc,QACAZ,SAEJ,CACA,YAA+B,IAA3BhK,GAASC,MAAME,UAA0BoG,EAAsBvG,SAItC,IAA3BA,GAASC,MAAME,UJnRZ,SACL0G,EACAlH,GAEA,GAAqB,iBAAVA,GAAgC,OAAVA,GAAkB6M,MAAMC,QAAQ9M,GAAQ,OAAO,EAChF,MAAM+M,EAAO/M,EACb,MAAsB,WAAlBkH,EAASE,MACHnH,OAAO+M,OAAOD,EAAM,eAAiBA,EAAiB,aAAM7F,EAASG,KAG7EpH,OAAO+M,OAAOD,EAAM,OACpB9F,EAAuBC,EAAU,CAAEE,KAAM,aAAcC,KAAMH,EAASG,KAAMC,GAAIyF,EAAS,IAE7F,CIuQKE,CAA2B5M,EAAQC,MAAME,QAAQC,SAAU0L,GAJrDtB,EAAYC,GAQdC,EAAOD,EAAS,CAAEE,IAAI,EAAM+B,KAAMZ,EAAWlB,QAAOd,OAAQ+B,EAAS/B,QAC9E,CAGA,SAASY,EAAUD,EAAsBoC,GACvC,MAAMzC,OAAEA,EAAAjI,IAAQA,EAAAyI,MAAKA,GAAUH,EACzBqC,EAAapG,KAAKC,MAAQ8D,EAAQe,UACxC,GAAIqB,EAAOlC,GAET,OADAP,EAAO2C,eAAe,CAAEhG,KAAM,WAAY5E,MAAK2H,OAAQ+C,EAAO/C,OAAQc,QAAOkC,eACtED,EAUT,GARAzC,EAAO2C,eAAe,CACpBhG,KAAM,UACN5E,MACA0H,OAAQgD,EAAOhD,OACfC,OAAQ+C,EAAO/C,OACfc,QACAkC,eAEwB,UAAtBrC,EAAQc,UACV,MAAM,IAAI3B,EAAkBiD,EAAOhD,OAAQ1H,EAAK0K,EAAO/C,OAAQ+C,EAAO7C,OAExE,OAAO6C,CACT,CAeA,SAASP,EACPtC,EACAgD,GAEA,IAA0B,IAAtBA,GAAUC,QAAkB,MAAO,UACvC,MAAMvM,EACa,iBAAVsJ,GAAgC,OAAVA,EAAkBA,EAA6BtJ,UAAO,EACrF,MAAa,iBAATA,EAAgC,UACvB,eAATA,EAA8B,UAC3B,SACT,CAEA,SAASwM,EAAYvN,GACnB,MAAwB,iBAAVA,GAAgC,OAAVA,IAAmB6M,MAAMC,QAAQ9M,EACvE,CAEA,SAASwN,EAAU1F,GACjB,OAAOA,EAAK2F,IAAI,CAACC,EAAS/H,IAAqB,IAAVA,EAAc+H,EAAU,IAAIA,MAAarF,KAAK,GACrF,CAEA,SAASsF,EAAYvC,EAAwBwC,EAAoB9F,GAC/D,UAAYjF,EAAK7C,KAAUC,OAAOsM,QAAQqB,GAAO,CAC/C,MAAMC,EAAW,IAAI/F,EAAMjF,GACvB0K,EAAYvN,GACd2N,EAAYvC,EAAOpL,EAAO6N,GACjBhB,MAAMC,QAAQ9M,IAAUA,EAAM8N,KAAKP,GAE3CvN,EAAkC+N,QAAQ,CAACC,EAAQrI,KAClDgI,EAAYvC,EAAO4C,EAAQ,IAAIH,EAAUxJ,OAAOsB,OAEzCkH,MAAMC,QAAQ9M,GACvBoL,EAAME,IAAIkC,EAAUK,GAAY7N,EAAuCyN,IAAIpJ,QAAQgE,KAAK,MAGxF+C,EAAME,IAAIkC,EAAUK,GAA4B,iBAAV7N,EAAqB,OAASqE,OAAOrE,GAE/E,CACF,C,mGChYA2E,eACEsJ,EACAjF,GAEA,OAAQA,EAASR,MACf,IAAK,kBACH,ORqCN7D,eACEsJ,EACAjF,GAEA,MAAMkF,EAhCR,SAAuB3J,GACrB,MAAMxD,EAAOwD,GA5Ca,gBA6C1B,IAAKnD,EAAoBiB,KAAKtB,GAC5B,MAAM,IAAIF,EACR,sCAAsCE,uCAG1C,OAAOA,CACT,CAwBqBoN,CAAcnF,EAASkF,YACpCvL,EAASL,EAAc0G,EAAS9C,UAAW,aAC3CkI,OACgB,IAApBpF,EAASrG,YAAuB,EAAYL,EAAc0G,EAASrG,OAAQ,UACvE3C,EAAQwB,EACZyM,EAAQ7B,QAAQnE,IAAI,UACpBiG,EACAlF,EAASqF,iBA/EqB,MAiFhC,GAAc,OAAVrO,EAAgB,OAAOgB,EAAQ,sBACnC,MAAMgG,EAAMgC,EAAShC,KAAOD,KAAKC,IAC3BwE,EAAY8C,KAAKC,IApFF,IAoFsBvF,EAASwC,WArF3B,KAuFzB,IAAKzF,OAAOC,cAAcwF,GAAY,OAAOxK,EAAQ,eACrD,MAAM0K,EAAuB1C,EAAS2C,OAAS3I,WAAW2I,MAGpD6C,EAAW,GAAG7L,SAFF8L,mBAAmBzF,EAAS0F,WAAavN,gBAIrDwN,EAAUrN,EAAa2G,IAAIyD,mBAAS,IAASkD,IAC7CC,EAAY,GAAGX,KAAclO,IACnC,IAAIU,EAAyC,CAAEoO,OAAQD,QACpC,IAAfT,EAA0B1N,EAAuB,OAAI0N,EAChDO,EAAQrL,IAAIkL,KAAW9N,EAAuB,OAAIiC,GAC3D,MAAMoM,EAAa,IAAIC,gBACjBC,EAAQ,KACZF,EAAWE,MAAMhB,EAAQjC,QAAQ9B,SAE7BgF,EAAY,IAAeH,EAAW/C,OAAOsB,QAGnD,IAAI6B,EACAhD,EAHJ8B,EAAQjC,QAAQoD,iBAAiB,QAASH,EAAO,CAAEI,MAAM,KACzB,IAA5BpB,EAAQjC,QAAQsB,SAAkB2B,IAGtC,IACE,GAAIC,IAAa,OAAOlO,EAAQ,eAChCmO,EAAQG,WAAW,KACjBP,EAAWE,MAAM,IAAIM,aAAa,0BAA2B,kBAC5D/D,GACH,QAASV,EAAU,EAAGA,EAAU,EAAGA,IAAW,CAC5C,MAAMoB,QAAiBR,EAAU8C,EAAU,CACzCpC,QAAS,IAAK1L,EAAgB4L,OAAQ,oBACtCN,OAAQ+C,EAAW/C,SAErB,GAAIkD,IAAa,OAAOlO,EAAQ,eAChC,IAAKkL,EAASlB,GAAI,OAAOhK,EAA4B,MAApBkL,EAAS/B,OAAiB,UAAY,eAEvE,GADAgC,QAAaD,EAASU,OAClBsC,IAAa,OAAOlO,EAAQ,eAChC,MAAMwO,EACY,iBAATrD,GAA8B,OAATA,EAAiBA,EAA4BqD,UAAO,EAClF,GAAgB,IAAZ1E,EAAe,CAEjB,GAAoB,iBAAT0E,GAA8B,OAATA,GAAiB3C,MAAMC,QAAQ0C,GAAO,WACrC,IAA7B9O,EAAuB,OACzBiO,EAAQc,OAAOjB,IAEflN,EAAagK,IAAII,EAAWiD,GAC5BA,EAAQrD,IAAIkD,EAAUxH,MAExB,KACF,CACA,QAAmB,IAAfoH,GAAqC,OAAToB,EAAe,MAC/C,QAAiC,IAA7B9O,EAAuB,OAA3B,CAOA,GAAIsG,KAAS2H,EAAQ1G,IAAIuG,IAAa,GAtIf,IAsI0C,MACjEG,EAAQrD,IAAIkD,EAAUxH,KACtBtG,EAAiB,CAAEoO,OAAQD,EAJ3B,MAFEnO,EAAiB,CAAEoO,OAAQD,EAAWlM,SAO1C,CACF,OACE,OAAO3B,EAAQ,cACjB,cACgB,IAAVmO,GAAqBO,aAAaP,GACtClB,EAAQjC,QAAQ2D,oBAAoB,QAASV,EAC/C,CACA,MAAMO,EAmBR,SAAkBrD,EAAeuC,GAC/B,GAAoB,iBAATvC,GAA8B,OAATA,GAAiBU,MAAMC,QAAQX,GAAO,OAAO,KAC7E,MAAMqD,EAAQrD,EAA4BqD,KAC1C,GAAoB,iBAATA,GAA8B,OAATA,GAAiB3C,MAAMC,QAAQ0C,GAAO,OAAO,KAC7E,MAAMI,EACHzD,EAAkCyD,YAAeJ,EAAkCI,WACtF,QAAmB,IAAfA,GAA4BA,IAAelB,EAAW,OAAO,KACjE,MAAMpH,EAAMkI,EAA0BlI,GACtC,MAAkB,iBAAPA,GAAmBA,EAAG3F,OAAS,EAAU2F,EAClC,iBAAPA,GAAmBvB,OAAOe,SAASQ,GAAYjD,OAAOiD,GAC1D,IACT,CA9BeuI,CAAS1D,EAAMnD,EAAS0F,WAAavN,GAClD,GAAa,OAATqO,EAAe,OAAOxO,EAAQ,WAClC,MAAM6F,EA+BR,SAAoBsF,GAClB,MAAM2D,EAAO3D,EAA2B2D,IACxC,MAAmB,iBAARA,IAAqB/J,OAAOe,SAASgJ,IAAQA,GAAO,OAAG,EAC3DA,EAtLe,KAsLe,IAANA,EAAaA,CAC9C,CAnCoBC,CAAW5D,GAC7B,YAAkB,IAAdtF,GAA2BA,GAAaG,IAAchG,EAAQ,WAC3D,CACLE,YAAY,EACZD,QAAS,aACTZ,QAASF,EAA+B,CACtC6I,SAAU,kBACVgH,QAASR,EACTS,aAAcjJ,IACdH,YACAvG,MAAO,GACPI,mBAGN,CQvIawP,CAAiBjC,EAASjF,GACnC,IAAK,eACH,ON2KNrE,eACEsJ,EACAjF,GAEA,MAAM/E,EAASD,EAAYgF,EAAS/E,QAC9BQ,EAASH,EAAU0E,EAASvE,QAClC,QAAe,IAAXA,EAAsB,OAAOzD,EAAQ,eACzC,MAAMmP,EAnDR,SACElC,EACAmC,GAEA,GAAwB,WAApBA,GAAWhJ,KACb,OAAO6G,EAAQ7B,QAAQnE,IAAImI,EAAUrP,MAlGb,mBAoG1B,IAAIyB,EACJ,IACEA,EAAM,IAAIC,IAAIwL,EAAQzL,IACxB,OACE,OAAO,IACT,CACA,MAAMX,EAASW,EAAI6N,aAAaC,OAAOF,GAAWG,OA3GxB,gBA4G1B,OAAyB,IAAlB1O,EAAOF,OAAgBE,EAAO,IAAM,KAAQ,IACrD,CAoCgB2O,CAAUvC,EAASjF,EAASoH,WAC1C,GAAc,OAAVD,GAAmC,IAAjBA,EAAMxO,OAAc,OAAOX,EAAQ,sBACzD,GAAImP,EAAMxO,OA5Ia,KA4Ic,OAAOX,EAAQ,WACpD,MAAMyP,EAAQN,EAAMpO,MAAM,KAC1B,GAAqB,IAAjB0O,EAAM9O,QAAgB8O,EAAM,KAAO5M,EAAe,OAAO7C,EAAQ,WACrE,MAAM0P,EAAcD,EAAM,IAAM,GAC1BE,EAAetL,EAAcqL,GAC7BE,EAAYvL,EAAcoL,EAAM,IAAM,IAC5C,QAAqB,IAAjBE,GAAwD,KAA1BC,GAAWxM,WAAmB,OAAOpD,EAAQ,WAC/E,IAAI6P,EACJ,IACE,MAAMhO,QAAY+B,EAAQH,EAAQR,GAClC4M,QAAiBpM,EAAOC,OAAOoM,OAC7B,OACAjO,EACA+N,EACA9M,IAAUK,OAAO,GAAGN,KAAiB6M,KAEzC,OACE,OAAO1P,EAAQ,cACjB,CACA,IAAK6P,EAAU,OAAO7P,EAAQ,WAC9B,MAAM+P,EAxDR,SAAqB7M,GACnB,IAAI8M,EACJ,IACEA,EAASC,KAAKC,OAAM,IAAIC,aAAcC,OAAOlN,GAC/C,OACE,MACF,CACA,GAAsB,iBAAX8M,GAAkC,OAAXA,EAAiB,OACnD,MAAMK,EAAIL,EACJM,EAAkBzO,QACX,IAAXwO,EAAExO,IAAwC,iBAAXwO,EAAExO,GACnC,OACa,IAAXwO,EAAK,GACe,iBAAbA,EAAO,KACM,iBAAbA,EAAO,KACM,iBAAbA,EAAO,KACM,iBAAbA,EAAO,KACM,iBAAbA,EAAO,KACbC,EAAe,QACfA,EAAe,QACfA,EAAe,OAIXD,OAbP,CAcF,CA+BiBE,CAAYZ,GAC3B,QAAe,IAAXI,EAAsB,OAAO/P,EAAQ,WACzC,MAAMgG,GAAOgC,EAAShC,KAAOD,KAAKC,OAClC,GAAI+J,EAAOjB,KAAO9I,GAAO+J,EAAOS,IAAMT,EAAOjB,IAAK,OAAO9O,EAAQ,WAEjE,GAAI+P,EAAOjB,IAAMiB,EAAOS,IAAM5N,EAAY,OAAO5C,EAAQ,WACzD,GAAI+P,EAAOU,MAAQzI,EAAS0I,SAAU,OAAO1Q,EAAQ,kBACrD,GAAI+P,EAAOY,OAAS3I,EAAS4I,SAAWjO,GAAkB,OAAO3C,EAAQ,iBACzE,QAAmB,IAAf+P,EAAOc,IAAmB,CAC5B,IAAIlL,EACJ,IACEA,EAAW,IAAIlE,IAAIwL,EAAQzL,KAAKmE,QAClC,OACE,OAAO3F,EAAQ,UACjB,CACA,GAAI+P,EAAOc,MAAQlL,EAAU,OAAO3F,EAAQ,aAC9C,CACA,QAAmB,IAAf+P,EAAOe,IAAmB,CAC5B,MAAMnJ,EAASK,EAASL,SAASsF,GACjC,GAAItF,IAAWoI,EAAOe,IAAK,OAAO9Q,EAAQ,eAC5C,CACA,QAAwB,IAApBgI,EAAS+I,OAAsB,CACjC,IAAIC,EACJ,IACEA,QA8BNrN,eAEEsN,EACA3K,EACAT,GAEA,MAAI,YAAaoL,GAGG,UADYA,EAAMC,QAAQ5K,EAAIT,IAGlDjE,EACE,iBACA,6KAGQqP,EAAME,OAAO7K,WACjB2K,EAAMG,SAAS9K,EAAIT,IAClB,GACT,CAjDoBwL,CAAerJ,EAAS+I,OAAQhB,EAAOuB,IAAKvB,EAAOjB,IACnE,OACE,OAAO9O,EAAQ,cACjB,CACA,IAAKgR,EAAO,OAAOhR,EAAQ,WAC7B,CACA,MAAO,CACLE,YAAY,EACZD,QAAS,aACTZ,QAASF,EAA+B,CACtC6I,SAAU,eACVgH,QAASe,EAAOwB,IAChBtC,aAAcjJ,EACdH,UAAWkK,EAAOjB,IAClBxP,MAAO,CACLoR,SAAUX,EAAOU,YACE,IAAfV,EAAOc,IAAoB,CAAE/J,KAAMiJ,EAAOc,KAAQ,WACnC,IAAfd,EAAOe,IAAoB,CAAEnJ,OAAQoI,EAAOe,KAAQ,IAE1DpR,eAAgB,KAGtB,CMtPa8R,CAAevE,EAASjF,GACjC,IAAK,WACH,OAINrE,eACEsJ,EACAjF,GAEA,IAAI+H,EACJ,IACEA,QAAe/H,EAAS8H,OAAO7C,EACjC,OACE,OAAOjN,EAAQ,cACjB,CACA,GAAe,OAAX+P,EAAiB,OAAO/P,EAAQ,WACpC,UAC4B,IAA1B+P,EAAOzQ,OAAOE,SL7CX,SAA6BR,GAClC,GAAqB,iBAAVA,GAAgC,OAAVA,EAAgB,OAAO,EACxD,MAAMM,EAAQN,EACRS,EAAWH,EAAgB,SACjC,QAC4D,OAA1D2F,EAAe3F,EAAiB,UAAGA,EAAgB,WACtB,iBAAtBA,EAAgB,WACtByF,OAAOC,cAAc1F,EAAgB,WACtCA,EAAgB,SAAI,GACA,iBAAbG,GACM,OAAbA,IACCoF,EAAYpF,EAAe,SAKP,WAArBA,EAAe,MACO,eAArBA,EAAe,MAAsBqF,EAAoBrF,EAAa,IAE3E,CK2BMgS,CAAoB1B,EAAOzQ,MAAME,UAAauF,OAAOe,SAASiK,EAAOlK,YAEvE,OAAO7F,EAAQ,WAEjB,MAAMgG,GAAOgC,EAAShC,KAAOD,KAAKC,OAClC,YAAyB,IAArB+J,EAAOlK,WAA2BkK,EAAOlK,WAAaG,EAAYhG,EAAQ,WACvE,CACLE,YAAY,EACZD,QAAS,aACTZ,QAASF,EAA+B,CACtC6I,SAAU,WACVgH,QAASe,EAAOf,QAChBC,aAAcjJ,EACdH,UAAWkK,EAAOlK,UAClBvG,MAAOyQ,EAAOzQ,OAAS,GACvBI,eAAgBqQ,EAAOrQ,gBAAkB,KAG/C,CAnCagS,CAAkBzE,EAASjF,GAExC,E,0HD0FO,SAAuByB,GAC5B,MAAMvE,EAuOR,SAA4BA,GAC1B,IAAI1D,EACJ,IACEA,EAAM,IAAIC,IAAIyD,EAChB,OACE,MAAM,IAAIpF,MAAM,oCAAoCoF,4BACtD,CACA,GAAqB,WAAjB1D,EAAIE,UAA0C,UAAjBF,EAAIE,SACnC,MAAM,IAAI5B,MAAM,yDAAyD0B,EAAIE,aAE/E,MAAO,GAAGF,EAAIG,SAASH,EAAImE,SAASvB,QAAQ,OAAQ,KACtD,CAlPoBuN,CAAmBlI,EAAOvE,WACtCO,EAAQgE,EAAOtE,UAAY,OAC3BA,EAAWM,EAAMvE,WAAW,KAAOuE,EAAQ,IAAIA,IACrD,IAAKV,OAAO6M,UAAUnI,EAAOE,QAAUF,EAAOE,MAAQ,EACpD,MAAM,IAAI7J,MACR,mEAAmEuD,OAAOoG,EAAOE,UAGrF,MAAMa,EAAY8C,KAAKC,IAZF,IAYsB9D,EAAOe,WAbzB,KAcnBqH,EAAW5S,OAAOM,OAAO,CAAE2F,YAAWC,WAAUwE,MAAOF,EAAOE,MAAOa,cACrEsH,EAAiB7S,OAAOM,OAAO,CAAE2F,YAAWC,WAAU4M,WAAYtI,EAAOE,QACzEqI,EAAO,GAAG9M,IAAYC,IACtB8M,EAAyB,CAAExI,OAAQxK,OAAOM,OAAO,IAAKkK,KAAWoI,IAAarH,aAEpF,OAAOvL,OAAOM,OAAO,CACnBkK,OAAQoI,EACRC,iBACA,mBAAMI,CACJxL,GAEA,MAAMuD,EAAQC,EAASxD,GACjByL,OAAwB,IAAfzL,EAAQJ,GACjBmE,IACF0H,GAAWrN,EAAoB4B,EAAQJ,UAAyB,IAAlBI,EAAQ0L,QACxD5I,EAAgByI,EAASxI,OAAQ/C,EAAS,CACxCN,KAAM,aACNC,KAAMK,EAAQkI,WACdtI,GAAII,EAAQJ,KAIhB,IAAKmE,EAAS,OAAOF,EAAQ0H,EAAUD,EAAMtL,EAASuD,GAAO,GAC7D,MAAMG,EAAQD,EAAO0H,EAASlI,MAAOjD,EAASuD,GACzCkI,IACH/H,EAAME,IAAI,QAAS,UACG,IAAlB5D,EAAQ0L,OAAqBzF,EAAYvC,EAAO1D,EAAQ0L,MAAO,CAAC,WAEtE,MAAM9L,EAAKxB,EAAoB4B,EAAQJ,IACnC,IAAImH,mBAAmBpK,OAAOqD,EAAQJ,OACtC,GACE9E,EAAM,GAAGwQ,KAAQvE,mBAAmB/G,EAAQkI,cAActI,KAAM8D,EAAMiI,aAC5E,GAAIF,EAAQ,OAAO5H,EAAQ0H,EAAUzQ,EAAKkF,EAASuD,EAAOQ,GAC1D,MAAMyB,QAAe3B,EAAqB0H,EAAUzQ,EAAKkF,EAASuD,EAAOQ,GACzE,IAAKyB,EAAOlC,GAAI,OAAOkC,EACvB,MAAM8E,EAAQ9E,EAAOH,MAAMuG,OAAO,GAClC,MAAO,CAAEtI,IAAI,EAAM+B,KAAMiF,GAAS,KAAM/G,QAAOd,OAAQ+C,EAAO/C,OAChE,EACA,iBAAMoJ,CACJ7L,GAEA,MAAMuD,EAAQC,EAASxD,GACjB0D,EAAQD,EAAO0H,EAASlI,MAAOjD,EAASuD,GACxCzI,EAAM,GAAGwQ,aAAgBvE,mBAAmB/G,EAAQ8L,WAAWpI,EAAMiI,aAC3E,OAAO9H,EACL0H,EACAzQ,EACAkF,EACAuD,EACAT,EAAgByI,EAASxI,OAAQ/C,EAAS,CAAEN,KAAM,SAAUC,KAAMK,EAAQ8L,SAE9E,GAEJ,E,iDHjNO,SACLvF,EACAvG,EAAiC,IAEjC,MAAM+L,EAAU,IAAIpQ,IAAmBqE,EAAQ+L,SAAW,CAAC,QAAS,aAAc,YAC5EC,EAAchM,EAAQgM,aAAenM,EAE3C,GAAIkM,EAAQnQ,IAAI,SAAU,CACxB,IAAId,EACJ,IACEA,EAAM,IAAIC,IAAIwL,EAAQzL,IACxB,OACEA,OAAM,CACR,CACA,QAAY,IAARA,EACF,UAAW+N,KAASmD,EAAa,CAI/B,MAAM1T,EAAQwC,EAAI6N,aAAaC,OAAOC,GAAOoD,IAAG,GAChD,GAAc,SAAV3T,GAA8B,MAAVA,EAAe,OAAO,CAChD,CAEJ,CAEA,GAAIyT,EAAQnQ,IAAI,eAA2D,WAA1C2K,EAAQ7B,QAAQnE,IAAI,kBACnD,OAAO,EAKT,MAAM2L,EAAQlM,EAAQmM,kBACR,IAAVD,GACFhR,EACE,gBACA,kIAIJ,MAAM2B,EAAamD,EAAQoM,gBAAkBF,GAAS,GAChDC,EAAeJ,EAAQnQ,IAAI,WAAaiB,EAAa,GAC3D,GAAIsP,EAAalS,OAAS,EAAG,CAC3B,MAAMoS,EAAU9F,EAAQ7B,QAAQnE,IAAI,WACpC,GAAgB,OAAZ8L,EACF,IACE,MAAMC,EAAgB,IAAIvR,IAAIsR,GAASpR,OACvC,UAAWsR,KAAeJ,EACxB,IACE,GAAI,IAAIpR,IAAIwR,GAAatR,SAAWqR,EAAe,OAAO,CAC5D,OAEA,CAEJ,OAEA,CAEJ,CAEA,OAAO,CACT,E,wFFwFArP,eACEoM,EACArJ,GAEA,MAAMjD,EAASH,EAAUoD,EAAQjD,QACjC,QAAe,IAAXA,EACF,MAAM,IAAI3D,MAAM,+EAElB,MAAMmD,EAASD,EAAY0D,EAAQzD,QAC7B+C,GAAOU,EAAQV,KAAOD,KAAKC,OAC3BkN,EAAM5F,KAAK6F,IAAIvQ,EAAY0K,KAAKC,IAAI,EAAGwC,EAAOqD,OAvE/B,MAwEf9M,EAAK,IAAI5B,WAAW,IAC1BjB,EAAO4P,gBAAgB/M,GACvB,MAAMgN,EAAmB,CACvBC,EAAG,EACH9C,IAAKV,EAAOW,iBACQ,IAAhBX,EAAOjJ,KAAqB,CAAE+J,IAAKd,EAAOjJ,MAAS,WACjC,IAAlBiJ,EAAOpI,OAAuB,CAAEmJ,IAAKf,EAAOpI,QAAW,WACpC,IAAnBoI,EAAOf,QAAwB,CAAEuC,IAAKxB,EAAOf,SAAY,GAC7D2B,IAAKZ,EAAOa,SAAWjO,EACvB6N,IAAKxK,EACL8I,IAAK9I,EAAMkN,EACX5B,IAAKvN,EAAYuC,IAEb9G,EAAUuE,EAAYjB,IAAUK,OAAO8M,KAAKuD,UAAUF,KACtDG,EAAS,GAAG5Q,KAAiBrD,IAC7BqC,QAAY+B,EAAQH,EAAQR,GAElC,MAAO,GAAGwQ,KAAU1P,EADF,IAAIW,iBAAiBjB,EAAOC,OAAOgQ,KAAK,OAAQ7R,EAAKiB,IAAUK,OAAOsQ,OAE1F,E,kCOvHO,SACLE,EACAjN,EAAuC,IAMvC,OAAO2B,sBAAsB,CAC3BC,eAJkB,iBAAXqL,GAAkC,OAAXA,EACzBA,EAA2D,8BAC5D,IAEqB,aACH,IAAlBjN,EAAQ6B,MAAsB,CAAEA,MAAO7B,EAAQ6B,OAAU,IAEjE","ignoreList":[]}
|
package/dist/server.d.cts
CHANGED
|
@@ -106,7 +106,8 @@ interface PreviewAuthorizationRequest {
|
|
|
106
106
|
* The `payload-session` strategy: forward exactly one named cookie to
|
|
107
107
|
* `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.
|
|
108
108
|
* A cookie-only request preserves working Payload 2 sessions. When `/me`
|
|
109
|
-
* reports no user, one retry adds an `Origin` for Payload 3's CSRF check
|
|
109
|
+
* reports no user, one retry adds an `Origin` for Payload 3's CSRF check, and
|
|
110
|
+
* an endpoint that needed it gets it on the first request from then on.
|
|
110
111
|
*/
|
|
111
112
|
|
|
112
113
|
/** A `fetch`-compatible function, injectable for tests. */
|
|
@@ -129,7 +130,8 @@ interface PayloadSessionStrategy {
|
|
|
129
130
|
/**
|
|
130
131
|
* Send this listed origin with the cookie from the first request. Without
|
|
131
132
|
* it, try the existing cookie-only request, then the origin of `serverURL`
|
|
132
|
-
* once when `/me` reports a null user
|
|
133
|
+
* once when `/me` reports a null user; an endpoint that needed the origin
|
|
134
|
+
* gets it first from then on. Draft reads keep the accepted headers.
|
|
133
135
|
*/
|
|
134
136
|
readonly origin?: string;
|
|
135
137
|
/** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */
|
|
@@ -309,6 +311,11 @@ interface FieldBindingAttributes {
|
|
|
309
311
|
readonly 'data-payload-alt'?: string;
|
|
310
312
|
readonly 'data-payload-href'?: string;
|
|
311
313
|
readonly 'data-payload-array-template'?: string;
|
|
314
|
+
readonly 'data-payload-format'?: string;
|
|
315
|
+
readonly 'data-payload-text'?: string;
|
|
316
|
+
readonly 'data-payload-strategy'?: string;
|
|
317
|
+
readonly 'data-payload-depends'?: string;
|
|
318
|
+
readonly 'data-payload-boundary'?: string;
|
|
312
319
|
}
|
|
313
320
|
interface BindOptions {
|
|
314
321
|
/** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */
|
|
@@ -327,6 +334,19 @@ interface BindOptions {
|
|
|
327
334
|
readonly href?: string;
|
|
328
335
|
/** Markup per array item with `{{value}}` placeholders. */
|
|
329
336
|
readonly arrayTemplate?: string;
|
|
337
|
+
/** How a date or number is written, from `data-payload-format`'s vocabulary. */
|
|
338
|
+
readonly format?: string;
|
|
339
|
+
/** Let a text write replace the element's children. */
|
|
340
|
+
readonly text?: boolean;
|
|
341
|
+
/** Who shows this field: the patch (default), a server-rendered fragment, or the route. */
|
|
342
|
+
readonly strategy?: 'patch' | 'fragment' | 'route';
|
|
343
|
+
/** Fields whose change re-applies this binding under `skipUnchanged`. */
|
|
344
|
+
readonly dependsOn?: readonly string[];
|
|
345
|
+
/**
|
|
346
|
+
* An anchor for a field that may be empty, so an edit that fills it has somewhere to
|
|
347
|
+
* land. Set `hidden` yourself while the value is empty; the runtime keeps it right after that.
|
|
348
|
+
*/
|
|
349
|
+
readonly anchor?: boolean;
|
|
330
350
|
}
|
|
331
351
|
/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */
|
|
332
352
|
declare function bind<T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions): FieldBindingAttributes;
|
|
@@ -401,7 +421,7 @@ declare function createPreviewBindings(options: PreviewBindingsOptions): Preview
|
|
|
401
421
|
* `App.Locals` declaration in the project it writes into. The verdict is
|
|
402
422
|
* checked, not trusted — only a context `authorizePreviewRequest()` produced
|
|
403
423
|
* emits anything (ADR 0006).
|
|
404
|
-
* @
|
|
424
|
+
* @public
|
|
405
425
|
*/
|
|
406
426
|
declare function previewBindingsFromLocals(locals: unknown, options?: {
|
|
407
427
|
readonly owner?: string;
|