payload-live-preview 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +41 -34
  2. package/dist/adapters/astro/RichText.astro +1 -1
  3. package/dist/adapters/astro/index.d.ts +8 -8
  4. package/dist/adapters/astro/index.js +1 -1
  5. package/dist/adapters/astro/index.js.map +1 -1
  6. package/dist/adapters/astro/middleware-entry.d.ts +3 -3
  7. package/dist/adapters/astro/middleware-entry.js +1 -1
  8. package/dist/adapters/astro/middleware-entry.js.map +1 -1
  9. package/dist/adapters/nextjs/index.d.ts +2 -2
  10. package/dist/adapters/nextjs/index.js +1 -1
  11. package/dist/adapters/nextjs/index.js.map +1 -1
  12. package/dist/adapters/nuxt/index.d.ts +4 -4
  13. package/dist/adapters/nuxt/index.js +1 -1
  14. package/dist/adapters/nuxt/index.js.map +1 -1
  15. package/dist/adapters/nuxt/module.d.ts +13 -5
  16. package/dist/adapters/nuxt/module.js.map +1 -1
  17. package/dist/adapters/react/index.d.ts +8 -6
  18. package/dist/adapters/react/index.js.map +1 -1
  19. package/dist/adapters/sveltekit/index.d.ts +5 -5
  20. package/dist/adapters/sveltekit/index.js +1 -1
  21. package/dist/adapters/sveltekit/index.js.map +1 -1
  22. package/dist/adapters/vue/index.d.ts +3 -3
  23. package/dist/adapters/vue/index.js.map +1 -1
  24. package/dist/annotate.js +1 -1
  25. package/dist/annotate.js.map +1 -1
  26. package/dist/client.cjs +1 -1
  27. package/dist/client.cjs.map +1 -1
  28. package/dist/client.js +1 -1
  29. package/dist/client.js.map +1 -1
  30. package/dist/codegen-astro.d.ts +4 -1
  31. package/dist/codegen-astro.js.map +1 -1
  32. package/dist/codegen-cli.js +1 -1
  33. package/dist/codegen-cli.js.map +1 -1
  34. package/dist/codegen.cjs +1 -1
  35. package/dist/codegen.cjs.map +1 -1
  36. package/dist/codegen.js +1 -1
  37. package/dist/codegen.js.map +1 -1
  38. package/dist/core.cjs +1 -1
  39. package/dist/core.cjs.map +1 -1
  40. package/dist/core.d.cts +1 -1
  41. package/dist/core.d.ts +1 -1
  42. package/dist/core.js +1 -1
  43. package/dist/core.js.map +1 -1
  44. package/dist/doctor-cli.d.ts +1 -1
  45. package/dist/doctor-cli.js +1 -1
  46. package/dist/doctor-cli.js.map +1 -1
  47. package/dist/doctor.d.ts +4 -2
  48. package/dist/doctor.js +1 -1
  49. package/dist/doctor.js.map +1 -1
  50. package/dist/{fragment-endpoint-3Mlz7GDz.d.ts → fragment-endpoint-DATKU183.d.ts} +2 -2
  51. package/dist/fragment.cjs.map +1 -1
  52. package/dist/fragment.d.cts +6 -3
  53. package/dist/fragment.d.ts +6 -3
  54. package/dist/fragment.js.map +1 -1
  55. package/dist/index.cjs +1 -1
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +10 -10
  58. package/dist/index.d.ts +10 -10
  59. package/dist/index.js +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/lean.cjs +1 -1
  62. package/dist/lean.cjs.map +1 -1
  63. package/dist/lean.d.cts +3 -3
  64. package/dist/lean.d.ts +3 -3
  65. package/dist/lean.js +1 -1
  66. package/dist/lean.js.map +1 -1
  67. package/dist/lexical.cjs +1 -1
  68. package/dist/lexical.cjs.map +1 -1
  69. package/dist/lexical.d.cts +11 -1
  70. package/dist/lexical.d.ts +11 -1
  71. package/dist/lexical.js +1 -1
  72. package/dist/lexical.js.map +1 -1
  73. package/dist/{locals-DBXDMxOA.d.ts → locals-CNUGYbA_.d.ts} +1 -1
  74. package/dist/{middleware-Bx9I7PXW.d.ts → middleware-CWObDO37.d.ts} +2 -2
  75. package/dist/migrate.d.ts +6 -0
  76. package/dist/migrate.js +1 -1
  77. package/dist/migrate.js.map +1 -1
  78. package/dist/{options-BW1eqXvc.d.ts → options-uHKrDM2U.d.ts} +2 -1
  79. package/dist/plugins.cjs +1 -1
  80. package/dist/plugins.cjs.map +1 -1
  81. package/dist/plugins.js +1 -1
  82. package/dist/plugins.js.map +1 -1
  83. package/dist/{probe-CELeUciC.d.ts → probe-DqaItcwX.d.ts} +15 -0
  84. package/dist/server.cjs.map +1 -1
  85. package/dist/server.d.cts +1 -1
  86. package/dist/server.d.ts +1 -1
  87. package/dist/server.js.map +1 -1
  88. package/dist/structural.cjs +2 -2
  89. package/dist/structural.cjs.map +1 -1
  90. package/dist/structural.js +2 -2
  91. package/dist/structural.js.map +1 -1
  92. package/package.json +6 -4
@@ -4,9 +4,9 @@ export { a as DocumentSnapshot } from '../../document-session-BfBfLVpg.js';
4
4
 
5
5
  /**
6
6
  * The Vue composable: Payload's `useLivePreview` shape, with this package's
7
- * merge underneath — one request at a time, the newer one winning, an HTTP
8
- * error refused instead of becoming the document, and a cache that belongs to
9
- * this call alone (ADR 0002).
7
+ * merge underneath — one request per accepted update, a newer one aborting the
8
+ * one in flight, an HTTP error refused instead of becoming the document, and a
9
+ * session that belongs to this call alone (ADR 0002).
10
10
  *
11
11
  * The same `DocumentSession` the React hook uses; only the reactivity is Vue's.
12
12
  * It touches no DOM: a composable re-renders your components, which is the
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["observeThenableResult","value","then","Promise","resolve","catch","noopDiagnostic","isPathSegment","maxLength","length","includes","character","codePoint","codePointAt","hasControlCharacter","isSafeSlug","DataMerger","serverURL","apiRoute","depth","fetchFn","log","inflight","attempt","destroyDepth","constructor","options","this","end","endsWith","slice","withoutTrailingSlashes","route","diagnostic","startsWith","args","canMerge","request","endpointOf","globalSlug","encodeURIComponent","collectionSlug","id","data","Number","isFinite","String","merge","status","previous","controller","superseded","abort","endpoint","fetch","AbortController","url","response","method","credentials","headers","body","JSON","stringify","flattenLocales","locale","signal","ok","merged","json","Array","isArray","doc","error","destroy","parseFieldSchema","raw","out","entry","parsed","parseFieldEntry","push","record","name","type","fields","blocks","slug","parseBlockSchema","result","optionalFieldOk","check","optionalTypeOk","isPlainObject","prototype","Object","getPrototypeOf","NULL_AS_ABSENT_FIELDS","MessageBus","matcher","handlers","boundListener","attachedTarget","generation","revision","queueHead","queueTail","attach","target","window","committed","listener","event","receive","origin","reportInvalid","resetQueue","addEventListener","removeEventListener","detach","advanceGeneration","sendReady","targets","origins","payload","ready","protocolVersion","postMessage","matchesOrigin","matchesSource","v","x","isLivePreviewMessage","dispatchUpdate","message","normalized","field","Reflect","get","deleteProperty","fieldSchemaJSON","normalizeLivePreviewMessage","isDocumentEventMessage","invokeHandler","onDocumentEvent","focusFieldOf","onFocusField","hasUpdateData","isCurrentGeneration","messageRevision","validator","validateToken","commitUpdate","pending","verdict","next","enqueue","previewToken","settleValidation","approved","drainQueue","dequeue","onUpdate","reason","onInvalid","sourcePolicy","source","parent","opener","handler","expected","LOCALHOST_PATTERN","VALID_ORIGIN_PATTERN","LOCALHOST_HANDSHAKE_PORTS","OriginDetector","state","explicit","Set","additionalOrigins","normalised","normaliseOrigin","add","referrerOrigin","enableReferrerDetection","referrer","document","URL","allowLocalhost","enableLocalhostMatching","forceDevMode","isDevMode","nodeEnv","process","env","readNodeEnv","host","location","hostname","matches","has","size","test","lockOrigin","unlockOrigin","lockedOrigin","enumerate","port","referrerWasAvailable","isProductionUnconfigured","isInIframe","self","top","isReferrerOnlyTrust","trimmed","trim","protocol","reasonFrom","line","label","detail","Error","DocumentSession","merger","bus","detector","listeners","initial","snapshot","lastLog","attached","initialData","allowedOrigins","handle","eventSourcePolicy","isLoading","subscribe","delete","getSnapshot","getServerSnapshot","readyTargets","publish","current","useLivePreviewDocument","getCurrentScope","connection","session","shallowRef","onScopeDispose","computed"],"sources":["../../../src/core/thenable.ts","../../../src/core/diagnostics.ts","../../../src/core/data-merger.ts","../../../src/schema/parser.ts","../../../src/core/message-guards.ts","../../../src/core/message-bus.ts","../../../src/core/protocol-version.ts","../../../src/detection/origin.ts","../../../src/detection/environment.ts","../../../src/adapters/shared/document-session.ts","../../../src/adapters/vue/index.ts"],"sourcesContent":["/**\n * Detect an invalid async result in a synchronous extension point.\n *\n * Rejected native Promises otherwise become unhandled after the caller falls\n * back synchronously. Assimilating the result through a handled Promise keeps\n * that contract violation observable only through the runtime's explicit\n * error channel.\n */\nexport function observeThenableResult(value: unknown): boolean {\n if ((typeof value !== 'object' || value === null) && typeof value !== 'function') return false;\n if (typeof (value as { readonly then?: unknown }).then !== 'function') return false;\n void Promise.resolve(value).catch(() => undefined);\n return true;\n}\n","import { observeThenableResult } from './thenable';\n\n/**\n * Diagnostics are observability, never control flow: a consumer logger may\n * throw or return a rejected thenable, and reporting a failure must not become\n * a second failure. Invocation stays synchronous for ordinary loggers.\n */\nexport function isolateDiagnostic(\n diagnostic: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => void {\n return (...args): void => {\n try {\n observeThenableResult(diagnostic(...args));\n } catch {\n // Diagnostics must not become a second failure boundary.\n }\n };\n}\n\n/** Shared allocation-free default for disabled diagnostic channels. */\nexport function noopDiagnostic(): void {\n // Intentionally empty.\n}\n\n// Access, invocation and thenable inspection all happen inside the wrapper, so\n// a missing or hostile console cannot escape.\nexport const safeConsoleError = isolateDiagnostic((...args) =>\n (console.error as (...values: unknown[]) => unknown)(...args),\n);\n\nexport const safeConsoleWarn = isolateDiagnostic((...args) =>\n (console.warn as (...values: unknown[]) => unknown)(...args),\n);\n\nexport const safeConsoleDebug = isolateDiagnostic((...args) =>\n // eslint-disable-next-line no-console -- best-effort development diagnostics\n (console.debug as (...values: unknown[]) => unknown)(...args),\n);\n","/**\n * Server-side merge for Payload 3.x, whose admin posts raw form values with\n * relationships as bare ids. Like the official client, the update is re-fetched\n * through the REST API with `X-Payload-HTTP-Method-Override: GET`, which\n * merges the form values into the stored document and returns it populated.\n * Only the newest request matters: a newer merge aborts the one in flight.\n */\n\nimport { isolateDiagnostic, noopDiagnostic } from './diagnostics';\n\nexport interface DataMergerOptions {\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Defaults to `/api`. */\n readonly apiRoute?: string;\n /** Population depth requested from the server. Defaults to `1`. */\n readonly depth?: number;\n readonly fetchFn?: typeof fetch;\n readonly log?: (...args: unknown[]) => void;\n}\n\nexport interface MergeRequest {\n readonly collectionSlug?: string | undefined;\n readonly globalSlug?: string | undefined;\n readonly data: Record<string, unknown>;\n readonly locale?: string | undefined;\n}\n\n/** `superseded`: a newer merge replaced this one. `unavailable`: fall back to the raw values. */\nexport type MergeResult =\n | { readonly status: 'merged'; readonly doc: Record<string, unknown> }\n | { readonly status: 'superseded' }\n | { readonly status: 'unavailable' };\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n // An iterated character always has a code point; 0 keeps the type and fails closed.\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint <= 0x1f || codePoint === 0x7f) return true;\n }\n return false;\n}\n\n/** One path segment: non-empty, bounded, not a dot segment, without separators or control characters. */\nfunction isPathSegment(value: string, maxLength: number): boolean {\n return (\n value.length > 0 &&\n value.length <= maxLength &&\n value !== '.' &&\n value !== '..' &&\n !value.includes('/') &&\n !value.includes('\\\\') &&\n !hasControlCharacter(value)\n );\n}\n\n/** A slug is one of Payload's own names; `?` and `#` never belong in one. */\nfunction isSafeSlug(value: string): boolean {\n return isPathSegment(value, 128) && !value.includes('?') && !value.includes('#');\n}\n\n/** An id comes from the form values; a string one is encoded, so `?` and `#` may appear. */\nfunction isSafeId(value: unknown): value is string | number {\n if (typeof value === 'number') return Number.isFinite(value);\n return typeof value === 'string' && isPathSegment(value, 512);\n}\n\n/**\n * Trailing slashes off a base URL, without a regular expression: `/\\/+$/`\n * backtracks over a long run of slashes and takes quadratic time on input the\n * page supplies (CodeQL js/polynomial-redos).\n */\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length;\n while (value.endsWith('/', end)) end -= 1;\n return value.slice(0, end);\n}\n\nexport class DataMerger {\n private readonly serverURL: string;\n private readonly apiRoute: string;\n private readonly depth: number;\n private readonly fetchFn: typeof fetch | undefined;\n private readonly log: (...args: unknown[]) => void;\n private inflight: AbortController | null = null;\n private attempt = 0;\n private destroyDepth = 0;\n\n constructor(options: DataMergerOptions) {\n this.serverURL = withoutTrailingSlashes(options.serverURL);\n const route = options.apiRoute ?? '/api';\n this.apiRoute = route.startsWith('/') ? route : `/${route}`;\n this.depth = options.depth ?? 1;\n this.fetchFn = options.fetchFn;\n this.log = isolateDiagnostic(options.log ?? noopDiagnostic);\n }\n\n /** Collections need an `id` in the form values; globals only their slug. */\n canMerge(request: MergeRequest): boolean {\n return this.endpointOf(request) !== null;\n }\n\n /**\n * The path under `apiRoute`, or `null` when no safe one exists. A request\n * accessor that throws counts as none, and so does a lone surrogate, which\n * `encodeURIComponent` refuses with a `URIError`.\n */\n private endpointOf(request: MergeRequest): string | null {\n try {\n // An empty global slug means \"collection document\" on the wire.\n const globalSlug = request.globalSlug;\n if (globalSlug) {\n return isSafeSlug(globalSlug) ? `globals/${encodeURIComponent(globalSlug)}` : null;\n }\n const collectionSlug = request.collectionSlug ?? '';\n const id: unknown = request.data['id'];\n if (!isSafeSlug(collectionSlug) || !isSafeId(id)) return null;\n return `${encodeURIComponent(collectionSlug)}/${encodeURIComponent(String(id))}`;\n } catch {\n return null;\n }\n }\n\n async merge(request: MergeRequest): Promise<MergeResult> {\n if (this.destroyDepth > 0) return { status: 'superseded' };\n // Claim the attempt before aborting: abort listeners and fetch shims are\n // reentrant, and a newer merge they start must win. Nothing but this class\n // aborts the signal, and only after the counter moved, so the counter\n // alone decides who was superseded.\n const attempt = (this.attempt += 1);\n const previous = this.inflight;\n this.inflight = null;\n let controller: AbortController | null = null;\n const superseded = (): boolean => this.attempt !== attempt;\n try {\n previous?.abort();\n if (superseded()) return { status: 'superseded' };\n // The endpoint reads the request's own values, which may be consumer\n // getters that start a newer merge before throwing.\n const endpoint = this.endpointOf(request);\n if (endpoint === null) {\n return superseded() ? { status: 'superseded' } : { status: 'unavailable' };\n }\n const fetchFn = this.fetchFn ?? (typeof fetch === 'function' ? fetch : undefined);\n if (fetchFn === undefined) return { status: 'unavailable' };\n controller = new AbortController();\n this.inflight = controller;\n const url = `${this.serverURL}${this.apiRoute}/${endpoint}`;\n const response = await fetchFn(url, {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Payload-HTTP-Method-Override': 'GET',\n },\n // The admin already flattens locales before posting; an absent locale\n // is left out of the JSON.\n body: JSON.stringify({\n data: request.data,\n depth: this.depth,\n flattenLocales: false,\n locale: request.locale,\n }),\n signal: controller.signal,\n });\n // A fetch shim may ignore the signal; the attempt is authoritative.\n if (superseded()) return { status: 'superseded' };\n if (!response.ok) {\n this.log('merge HTTP', response.status, url);\n return { status: 'unavailable' };\n }\n const merged: unknown = await response.json();\n if (superseded()) return { status: 'superseded' };\n if (merged === null || typeof merged !== 'object' || Array.isArray(merged)) {\n this.log('merge invalid', url);\n return { status: 'unavailable' };\n }\n return { status: 'merged', doc: merged as Record<string, unknown> };\n } catch (error) {\n if (superseded()) return { status: 'superseded' };\n this.log('merge exception', error);\n return { status: 'unavailable' };\n } finally {\n // Only the newest attempt's controller is ever in flight: a superseded\n // attempt's is gone already, and its successor's is left alone.\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n /** Abort any in-flight merge. The merger stays usable across a stop/start cycle. */\n destroy(): void {\n this.attempt += 1;\n const inflight = this.inflight;\n this.inflight = null;\n this.destroyDepth += 1;\n try {\n inflight?.abort();\n } finally {\n this.destroyDepth -= 1;\n }\n }\n}\n","/**\n * Validates the `fieldSchemaJSON` the admin sends. Entries that fail\n * validation are dropped, never thrown: a Payload/runtime version mismatch\n * must not take the preview down.\n */\n\nimport type { PayloadBlockSchema, PayloadFieldSchema } from '@/types/payload-protocol';\n\nexport function parseFieldSchema(raw: unknown): readonly PayloadFieldSchema[] {\n if (!Array.isArray(raw)) return [];\n const out: PayloadFieldSchema[] = [];\n for (const entry of raw) {\n const parsed = parseFieldEntry(entry);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\nfunction parseFieldEntry(value: unknown): PayloadFieldSchema | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const record = value as Record<string, unknown>;\n const name = record['name'];\n const type = record['type'];\n if (typeof name !== 'string' || name.length === 0) return undefined;\n if (typeof type !== 'string' || type.length === 0) return undefined;\n\n const fields = parseFieldSchema(record['fields']);\n const blocks = parseBlockSchema(record['blocks']);\n\n const result: Record<string, unknown> = { ...record, name, type };\n if (fields.length > 0) result['fields'] = fields;\n else delete result['fields'];\n if (blocks.length > 0) result['blocks'] = blocks;\n else delete result['blocks'];\n\n return result as unknown as PayloadFieldSchema;\n}\n\nfunction parseBlockSchema(raw: unknown): readonly PayloadBlockSchema[] {\n if (!Array.isArray(raw)) return [];\n const out: PayloadBlockSchema[] = [];\n for (const entry of raw) {\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n const slug = record['slug'];\n if (typeof slug !== 'string' || slug.length === 0) continue;\n const fields = parseFieldSchema(record['fields']);\n out.push({ ...record, slug, fields });\n }\n return out;\n}\n","/** Shape guards for the messages the bus accepts. `null` counts as absent, as JSON bridges send it. */\n\nimport type {\n PayloadDocumentEventMessage,\n PayloadFieldSchema,\n PayloadLivePreviewMessage,\n} from '@/types/payload-protocol';\nimport { parseFieldSchema } from '@schema/parser';\n\nexport function isObjectMessage(value: unknown): value is { type: string } {\n return (\n typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string'\n );\n}\n\n/** A `payload-live-preview` message: `data` a plain object when present, scalars of the right type or absent. */\nexport function isLivePreviewMessage(value: { type: string }): value is PayloadLivePreviewMessage {\n const v = value as Record<string, unknown>;\n if (v['data'] !== undefined && v['data'] !== null && !isPlainObject(v['data'])) return false;\n return (\n optionalTypeOk(v['fieldSchemaJSON'], (x) => Array.isArray(x)) &&\n optionalTypeOk(v['globalSlug'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['collectionSlug'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['locale'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['ready'], (x) => typeof x === 'boolean') &&\n optionalTypeOk(v['previewToken'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['protocolVersion'], (x) => typeof x === 'number')\n );\n}\n\n/** The non-empty field name of a `payload-live-preview-focus` message, else `undefined`. */\nexport function focusFieldOf(value: { type: string }): string | undefined {\n const field = (value as Record<string, unknown>)['field'];\n return typeof field === 'string' && field.length > 0 ? field : undefined;\n}\n\n/** Stock Payload sends only the `type`; custom senders may add the typed fields (omitted, never `null`). */\nexport function isDocumentEventMessage(value: {\n type: string;\n}): value is PayloadDocumentEventMessage {\n const v = value as Record<string, unknown>;\n return (\n optionalFieldOk(v['action'], (x) => x === 'updated' || x === 'created' || x === 'deleted') &&\n optionalFieldOk(v['slug'], (x) => typeof x === 'string') &&\n optionalFieldOk(v['id'], (x) => typeof x === 'string' || Number.isFinite(x))\n );\n}\n\nfunction optionalFieldOk(value: unknown, check: (v: unknown) => boolean): boolean {\n return value === undefined || check(value);\n}\n\nfunction optionalTypeOk(value: unknown, check: (v: unknown) => boolean): boolean {\n return value === undefined || value === null || check(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const prototype = Object.getPrototypeOf(value) as object | null;\n return prototype === null || Object.getPrototypeOf(prototype) === null;\n}\n\nconst NULL_AS_ABSENT_FIELDS = [\n 'data',\n 'fieldSchemaJSON',\n 'globalSlug',\n 'collectionSlug',\n 'locale',\n 'ready',\n 'previewToken',\n 'protocolVersion',\n] as const satisfies readonly (keyof PayloadLivePreviewMessage)[];\n\n/**\n * Drop `null` optionals so downstream sees the strict public type, and parse\n * the field schema entry by entry (malformed entries are dropped). Unknown\n * fields stay, for protocol evolution.\n */\nexport function normalizeLivePreviewMessage(\n message: PayloadLivePreviewMessage,\n): PayloadLivePreviewMessage {\n const normalized = { ...message };\n for (const field of NULL_AS_ABSENT_FIELDS) {\n const wireValue: unknown = Reflect.get(normalized, field);\n if (wireValue === null) Reflect.deleteProperty(normalized, field);\n }\n if (!Array.isArray(normalized.fieldSchemaJSON)) return normalized;\n let fieldSchemaJSON: readonly PayloadFieldSchema[];\n try {\n fieldSchemaJSON = parseFieldSchema(normalized.fieldSchemaJSON);\n } catch {\n fieldSchemaJSON = [];\n }\n return { ...normalized, fieldSchemaJSON };\n}\n","/**\n * postMessage ingress: origin and source checks, per-type shape guards, the\n * ordered preview-token validation queue, and the `ready` handshake. The bus\n * never throws; a rejected message is dropped or reported via `onInvalid`.\n * Consumer callbacks may detach or re-attach the bus synchronously, so\n * anything that runs after one re-checks the generation it started in.\n * See ADR 0004.\n */\n\nimport type {\n PayloadDocumentEventMessage,\n PayloadLivePreviewMessage,\n} from '@/types/payload-protocol';\nimport {\n focusFieldOf,\n isDocumentEventMessage,\n isLivePreviewMessage,\n isObjectMessage,\n isPlainObject,\n normalizeLivePreviewMessage,\n} from './message-guards';\nimport { LIBRARY_PROTOCOL_VERSION } from './protocol-version';\nimport { observeThenableResult } from './thenable';\n\nexport type OriginMatcher = (origin: string) => boolean;\n\n/** Identity of a data-bearing update: `revision` is monotonic, `generation` names the listener attachment. */\nexport interface MessageRevision {\n readonly revision: number;\n readonly generation: number;\n}\nexport function sameRevision(a: MessageRevision, b: MessageRevision): boolean {\n return a.generation === b.generation && a.revision === b.revision;\n}\n\nexport type InvalidReason = 'origin' | 'shape' | 'type' | 'token' | 'source';\n\nexport interface MessageHandlers {\n /** A validated update; data-bearing ones carry their revision, ready handshakes do not. */\n readonly onUpdate: (\n msg: PayloadLivePreviewMessage,\n origin: string,\n messageRevision?: MessageRevision,\n ) => void;\n readonly onDocumentEvent: (msg: PayloadDocumentEventMessage, origin: string) => void;\n /** The admin reports the cursor moved into a field (reveal tier 2). */\n readonly onFocusField?: (field: string, origin: string) => void;\n readonly onInvalid?: (reason: InvalidReason, origin: string) => void;\n /** `'parent-or-opener'` refuses every window but the framing/opening one, even from an allowed origin. */\n readonly sourcePolicy?: 'any' | 'parent-or-opener';\n /**\n * Gate for data-bearing updates. Verdicts commit in arrival order, so an\n * earlier update is dispatched before a later one even when its validation\n * resolves later; an error counts as rejection.\n */\n readonly validateToken?: (\n token: string | undefined,\n origin: string,\n ) => boolean | Promise<boolean>;\n}\n\n/** One entry of the validation queue: a singly linked list in arrival order. */\ninterface PendingValidation {\n readonly generation: number;\n readonly message: PayloadLivePreviewMessage;\n readonly origin: string;\n readonly messageRevision: MessageRevision | undefined;\n /** `undefined` until the validator settled; only literal `true` approves. */\n verdict: boolean | undefined;\n next: PendingValidation | undefined;\n}\n\nexport class MessageBus {\n private readonly matcher: OriginMatcher;\n private readonly handlers: MessageHandlers;\n private boundListener: ((event: MessageEvent) => void) | undefined = undefined;\n private attachedTarget: Window | undefined = undefined;\n private generation = 0;\n private revision = 0;\n private queueHead: PendingValidation | undefined = undefined;\n private queueTail: PendingValidation | undefined = undefined;\n\n constructor(matcher: OriginMatcher, handlers: MessageHandlers) {\n this.matcher = matcher;\n this.handlers = handlers;\n }\n\n /** Start listening on `target`. Idempotent. */\n attach(target: Window = window): void {\n if (this.attachedTarget !== undefined) return;\n let committed = false;\n const listener = (event: MessageEvent): void => {\n // Each attachment owns its listener; a stale one must not borrow the new generation.\n if (!committed || this.boundListener !== listener) return;\n const generation = this.generation;\n try {\n this.receive(event, generation, target);\n } catch {\n // A synthetic event can carry throwing accessors; that is a shape failure.\n let origin: string;\n try {\n origin = event.origin;\n } catch {\n origin = '';\n }\n this.reportInvalid('shape', origin, generation);\n }\n };\n this.generation += 1;\n this.resetQueue();\n this.attachedTarget = target;\n this.boundListener = listener;\n try {\n target.addEventListener('message', listener);\n } catch (error) {\n // Invalidate before rollback: a hook may already have attached a newer generation.\n if (this.boundListener === listener) {\n this.attachedTarget = undefined;\n this.boundListener = undefined;\n this.resetQueue();\n }\n try {\n target.removeEventListener('message', listener);\n } catch {\n // Keep the original failure.\n }\n throw error;\n }\n if (this.boundListener === listener) {\n committed = true;\n } else {\n try {\n target.removeEventListener('message', listener);\n } catch {\n // The listener is identity-gated above even if removal fails.\n }\n }\n }\n\n /** Stop listening. Idempotent. */\n detach(): void {\n const target = this.attachedTarget;\n const listener = this.boundListener;\n if (target === undefined || listener === undefined) return;\n // Invalidate first so a listener already on the stack sees an obsolete generation.\n this.attachedTarget = undefined;\n this.boundListener = undefined;\n this.resetQueue();\n target.removeEventListener('message', listener);\n }\n\n /** Invalidate pending validations while staying attached (heartbeat expiry). */\n advanceGeneration(): boolean {\n if (this.attachedTarget === undefined) return false;\n this.generation += 1;\n this.resetQueue();\n return true;\n }\n\n /** Post the `ready` handshake to every target for every origin. */\n static sendReady(targets: readonly Window[], origins: readonly string[]): void {\n const payload: PayloadLivePreviewMessage = {\n type: 'payload-live-preview',\n ready: true,\n protocolVersion: LIBRARY_PROTOCOL_VERSION,\n };\n for (const target of targets) {\n for (const origin of origins) {\n try {\n target.postMessage(payload, origin);\n } catch {\n // A malformed origin is the consumer's configuration problem.\n }\n }\n }\n }\n\n private receive(event: MessageEvent, generation: number, target: Window): void {\n const origin = event.origin;\n if (!this.matchesOrigin(origin, generation)) {\n this.reportInvalid('origin', origin, generation);\n return;\n }\n if (!this.matchesSource(event, target)) {\n this.reportInvalid('source', origin, generation);\n return;\n }\n const data: unknown = event.data;\n if (!isObjectMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n switch (data.type) {\n case 'payload-live-preview':\n if (!isLivePreviewMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.dispatchUpdate(normalizeLivePreviewMessage(data), origin, generation);\n return;\n case 'payload-document-event':\n if (!isDocumentEventMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.invokeHandler(generation, this.handlers.onDocumentEvent, data, origin);\n return;\n case 'payload-live-preview-focus': {\n const field = focusFieldOf(data);\n if (field === undefined) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.invokeHandler(generation, this.handlers.onFocusField, field, origin);\n return;\n }\n default:\n this.reportInvalid('type', origin, generation);\n }\n }\n\n private dispatchUpdate(\n message: PayloadLivePreviewMessage,\n origin: string,\n generation: number,\n ): void {\n const hasUpdateData = isPlainObject(message.data);\n if (!this.isCurrentGeneration(generation)) return;\n // A shape-valid data update consumes its revision before validation; a rejected token leaves a gap.\n const messageRevision = hasUpdateData\n ? { generation, revision: (this.revision += 1) }\n : undefined;\n const validator = this.handlers.validateToken;\n if (!this.isCurrentGeneration(generation)) return;\n if (validator === undefined) {\n this.commitUpdate(message, origin, generation, messageRevision);\n return;\n }\n // The data-less ready handshake carries no token and passes, so the admin learns we listen.\n if (message.ready === true && message.data === undefined) {\n this.commitUpdate(message, origin, generation, undefined);\n return;\n }\n const pending: PendingValidation = {\n generation,\n message,\n origin,\n messageRevision,\n verdict: undefined,\n next: undefined,\n };\n this.enqueue(pending);\n let verdict: unknown;\n try {\n // Invoked eagerly; only the commit waits for earlier queue entries.\n verdict = validator(message.previewToken, origin);\n } catch {\n this.settleValidation(pending, false);\n return;\n }\n if (typeof verdict === 'boolean') {\n this.settleValidation(pending, verdict);\n return;\n }\n // Assimilate any thenable; only literal `true` approves.\n void Promise.resolve(verdict).then(\n (approved) => {\n this.settleValidation(pending, approved === true);\n },\n () => {\n this.settleValidation(pending, false);\n },\n );\n }\n\n private settleValidation(pending: PendingValidation, approved: boolean): void {\n pending.verdict = approved;\n this.drainQueue(pending.generation);\n }\n\n private drainQueue(generation: number): void {\n while (this.isCurrentGeneration(generation)) {\n const pending = this.queueHead;\n if (pending === undefined) return;\n // The matcher may have narrowed since ingress (origin lock); an obsolete\n // origin must not hold the queue head and block the locked origin's work.\n if (!this.matchesOrigin(pending.origin, generation)) {\n if (!this.dequeue(pending)) continue;\n this.reportInvalid('origin', pending.origin, pending.generation);\n continue;\n }\n if (pending.verdict === undefined) return;\n if (!this.dequeue(pending)) continue;\n if (pending.verdict) {\n this.commitUpdate(\n pending.message,\n pending.origin,\n pending.generation,\n pending.messageRevision,\n );\n } else {\n this.reportInvalid('token', pending.origin, pending.generation);\n }\n }\n }\n\n private commitUpdate(\n message: PayloadLivePreviewMessage,\n origin: string,\n generation: number,\n messageRevision: MessageRevision | undefined,\n ): void {\n if (messageRevision === undefined) {\n this.invokeHandler(generation, this.handlers.onUpdate, message, origin);\n } else {\n this.invokeHandler(generation, this.handlers.onUpdate, message, origin, messageRevision);\n }\n }\n\n private reportInvalid(reason: InvalidReason, origin: string, generation: number): void {\n this.invokeHandler(generation, this.handlers.onInvalid, reason, origin);\n }\n\n /** Under `'parent-or-opener'`, whether the event came from the attached window's parent or opener. */\n private matchesSource(event: MessageEvent, target: Window): boolean {\n if (this.handlers.sourcePolicy !== 'parent-or-opener') return true;\n try {\n // Only a window is a source: a missing one matches nothing, not even a missing opener.\n const source: unknown = event.source;\n if (typeof source !== 'object' || source === null) return false;\n const parent = target.parent;\n if (parent !== target && source === parent) return true;\n const opener: unknown = target.opener;\n return source === opener;\n } catch {\n return false;\n }\n }\n\n /** Fail-closed: only literal `true` from the matcher, and only within the generation it ran in. */\n private matchesOrigin(origin: string, generation: number): boolean {\n try {\n const approved: unknown = this.isCurrentGeneration(generation) && this.matcher(origin);\n return approved === true && this.isCurrentGeneration(generation);\n } catch {\n return false;\n }\n }\n\n /** Callbacks run only in a current generation and must not unwind the listener or the drain. */\n private invokeHandler<TArgs extends unknown[]>(\n generation: number,\n handler: ((...args: TArgs) => unknown) | undefined,\n ...args: TArgs\n ): void {\n if (handler === undefined || !this.isCurrentGeneration(generation)) return;\n try {\n observeThenableResult(handler(...args));\n } catch {\n // Isolated by contract.\n }\n }\n\n private isCurrentGeneration(generation: number): boolean {\n return this.attachedTarget !== undefined && this.generation === generation;\n }\n\n private enqueue(pending: PendingValidation): void {\n if (this.queueTail === undefined) this.queueHead = pending;\n else this.queueTail.next = pending;\n this.queueTail = pending;\n }\n\n /** Advance the head past `expected`, unless a matcher reset the queue meanwhile. */\n private dequeue(expected: PendingValidation): boolean {\n if (this.queueHead !== expected) return false;\n this.queueHead = expected.next;\n if (this.queueHead === undefined) this.queueTail = undefined;\n return true;\n }\n\n private resetQueue(): void {\n this.queueHead = undefined;\n this.queueTail = undefined;\n }\n}\n","/**\n * Protocol negotiation. A capability is granted either by the version a peer\n * announces or by observation, since the stock Payload admin announces none:\n * what it can do is read off the messages it sends. See ADR 0010.\n */\n\n/** Bumped only when this library's understanding of the wire format changes. @internal */\nexport const LIBRARY_PROTOCOL_VERSION = 4;\n\n/** How a capability becomes active. @internal */\nexport type CapabilitySource = 'version' | 'observed';\n\n/** @internal */\nexport interface CapabilityDeclaration {\n /** Enabled once the negotiated version reaches this (for announced peers). */\n readonly since: number;\n /** The wire signal that grants this capability to a peer announcing no version. */\n readonly observed?:\n 'schema' | 'locale' | 'preview-token' | 'document-event' | 'relationship-event';\n}\n\n/** What activates each capability; the prose lives in `CAPABILITY_DOCUMENTATION`. */\nexport const CAPABILITY_DECLARATIONS = {\n basic: { since: 1 },\n 'schema-json': { since: 2, observed: 'schema' },\n locale: { since: 2, observed: 'locale' },\n 'preview-token': { since: 3, observed: 'preview-token' },\n 'nested-arrays': { since: 4 },\n 'recursive-diffs': { since: 4 },\n 'document-events': { since: 4, observed: 'document-event' },\n 'relationship-events': { since: 4, observed: 'relationship-event' },\n} as const satisfies Record<string, CapabilityDeclaration>;\n\nexport type ProtocolCapability = keyof typeof CAPABILITY_DECLARATIONS;\n\n/** Every declared capability, in declaration order. @internal */\nexport const PROTOCOL_CAPABILITIES = Object.keys(\n CAPABILITY_DECLARATIONS,\n) as readonly ProtocolCapability[];\n\nexport interface ProtocolNegotiation {\n /** This library's protocol version. */\n readonly ours: number;\n /** The remote party's version, when it announced one. */\n readonly theirs: number | undefined;\n /** `min(ours, theirs ?? 1)` — the version both sides actually share. */\n readonly negotiated: number;\n /** Capabilities active at the negotiated version or seen on the wire. */\n readonly capabilities: ReadonlySet<ProtocolCapability>;\n /** The subset that was observed on the wire rather than granted by version. */\n readonly observed: ReadonlySet<ProtocolCapability>;\n}\n\n/** Deterministic; the runtime re-runs it whenever either input changes. */\nexport function negotiateProtocol(\n theirs: number | undefined,\n observed: Iterable<ProtocolCapability> = [],\n): ProtocolNegotiation {\n const sanitisedTheirs =\n typeof theirs === 'number' && Number.isFinite(theirs) && theirs >= 1\n ? Math.floor(theirs)\n : undefined;\n const effectiveTheirs = sanitisedTheirs ?? 1;\n const negotiated = Math.min(LIBRARY_PROTOCOL_VERSION, effectiveTheirs);\n const seen = new Set<ProtocolCapability>(observed);\n const capabilities = new Set<ProtocolCapability>();\n for (const capability of PROTOCOL_CAPABILITIES) {\n if (negotiated >= CAPABILITY_DECLARATIONS[capability].since || seen.has(capability)) {\n capabilities.add(capability);\n }\n }\n return {\n ours: LIBRARY_PROTOCOL_VERSION,\n ...(sanitisedTheirs !== undefined ? { theirs: sanitisedTheirs } : { theirs: undefined }),\n negotiated,\n capabilities,\n observed: seen,\n };\n}\n\n/** @internal */\nexport function hasCapability(\n negotiation: ProtocolNegotiation,\n capability: ProtocolCapability,\n): boolean {\n return negotiation.capabilities.has(capability);\n}\n\n/** The capabilities a single message demonstrates, by its shape alone. */\nexport function observeCapabilities(message: {\n readonly fieldSchemaJSON?: unknown;\n readonly locale?: unknown;\n readonly previewToken?: unknown;\n readonly externallyUpdatedRelationship?: unknown;\n}): readonly ProtocolCapability[] {\n const seen: ProtocolCapability[] = [];\n if (Array.isArray(message.fieldSchemaJSON)) seen.push('schema-json');\n if (typeof message.locale === 'string') seen.push('locale');\n if (typeof message.previewToken === 'string') seen.push('preview-token');\n if (\n typeof message.externallyUpdatedRelationship === 'object' &&\n message.externallyUpdatedRelationship !== null\n ) {\n seen.push('relationship-events');\n }\n return seen;\n}\n","/**\n * Which admin origins the runtime trusts: explicit ones always, the\n * `document.referrer` origin only when none is configured, any localhost port\n * in development. See docs/security.md §1.\n */\n\nimport { isDevMode, isInIframe } from './environment';\n\nconst LOCALHOST_PATTERN = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(?::\\d+)?$/i;\nconst VALID_ORIGIN_PATTERN = /^https?:\\/\\/[^/?#\\s]+$/i;\n\n/** Ports the handshake broadcasts to when the localhost pattern is on; matching itself is unrestricted. */\nconst LOCALHOST_HANDSHAKE_PORTS: readonly number[] = [\n 3000, 3001, 3333, 4000, 4321, 5000, 5173, 5174, 8000, 8080, 8888, 9000,\n];\n\nexport interface OriginDetectorOptions {\n /** Explicit origins to trust. Always allowed. */\n readonly additionalOrigins?: readonly string[];\n /** Set to `false` to ignore `document.referrer`. */\n readonly enableReferrerDetection?: boolean;\n /** Set to `false` to ignore the localhost pattern even in development. */\n readonly enableLocalhostMatching?: boolean;\n /** Override dev-mode detection. */\n readonly forceDevMode?: boolean;\n /** Override `document.referrer` for tests. */\n readonly referrer?: string;\n}\n\nconst enum OriginSlot {\n ExplicitOrigins,\n ReferrerOrigin,\n AllowLocalhost,\n ReferrerWasAvailable,\n LockedOrigin,\n}\n\ntype OriginState = [\n explicitOrigins: ReadonlySet<string>,\n referrerOrigin: string | undefined,\n allowLocalhost: boolean,\n referrerWasAvailable: boolean,\n lockedOrigin: string | undefined,\n];\n\n/** The trusted-origin policy: the union of the candidate sources, narrowed to one by `lockOrigin()`. */\nexport class OriginDetector {\n readonly #state: OriginState;\n\n constructor(options: OriginDetectorOptions = {}) {\n const explicit = new Set<string>();\n for (const origin of options.additionalOrigins ?? []) {\n const normalised = normaliseOrigin(origin);\n if (normalised !== undefined) explicit.add(normalised);\n }\n let referrerOrigin: string | undefined;\n if (options.enableReferrerDetection ?? true) {\n const referrer =\n options.referrer ?? (typeof document !== 'undefined' ? document.referrer : '');\n if (referrer.length > 0) {\n try {\n const origin = new URL(referrer).origin;\n if (origin && origin !== 'null') referrerOrigin = origin;\n } catch {\n // opaque referrer\n }\n }\n }\n const allowLocalhost =\n (options.enableLocalhostMatching ?? true) && (options.forceDevMode ?? isDevMode());\n this.#state = [\n explicit,\n referrerOrigin,\n allowLocalhost,\n referrerOrigin !== undefined,\n undefined,\n ];\n }\n\n /** Whether `origin` is trusted: the locked origin alone once locked, otherwise the policy above. */\n matches(origin: string): boolean {\n if (origin.length === 0 || origin === 'null') return false;\n if (this.#state[OriginSlot.LockedOrigin] !== undefined) {\n return origin === this.#state[OriginSlot.LockedOrigin];\n }\n if (this.#state[OriginSlot.ExplicitOrigins].has(origin)) return true;\n // The referrer is a fallback, not a union member: any embedder lands there.\n if (\n this.#state[OriginSlot.ExplicitOrigins].size === 0 &&\n this.#state[OriginSlot.ReferrerOrigin] !== undefined &&\n origin === this.#state[OriginSlot.ReferrerOrigin]\n ) {\n return true;\n }\n if (this.#state[OriginSlot.AllowLocalhost] && LOCALHOST_PATTERN.test(origin)) return true;\n return false;\n }\n\n /** Narrow trust to `origin`; refused (`false`) when it is not currently trusted. */\n lockOrigin(origin: string): boolean {\n if (!this.matches(origin)) return false;\n this.#state[OriginSlot.LockedOrigin] = origin;\n return true;\n }\n\n /** Release the lock (heartbeat timeout) so another allow-listed origin may reconnect. Returns the previous lock. */\n unlockOrigin(): string | undefined {\n const previous = this.#state[OriginSlot.LockedOrigin];\n this.#state[OriginSlot.LockedOrigin] = undefined;\n return previous;\n }\n\n get lockedOrigin(): string | undefined {\n return this.#state[OriginSlot.LockedOrigin];\n }\n\n /** Every origin the `ready` handshake is broadcast to; the localhost pattern expands to the common dev ports. */\n enumerate(): string[] {\n if (this.#state[OriginSlot.LockedOrigin] !== undefined) {\n return [this.#state[OriginSlot.LockedOrigin]];\n }\n const result = new Set<string>(this.#state[OriginSlot.ExplicitOrigins]);\n if (\n this.#state[OriginSlot.ExplicitOrigins].size === 0 &&\n this.#state[OriginSlot.ReferrerOrigin] !== undefined\n ) {\n result.add(this.#state[OriginSlot.ReferrerOrigin]);\n }\n if (this.#state[OriginSlot.AllowLocalhost]) {\n for (const port of LOCALHOST_HANDSHAKE_PORTS) {\n result.add(`http://localhost:${String(port)}`);\n result.add(`http://127.0.0.1:${String(port)}`);\n }\n }\n return [...result];\n }\n\n /** Whether `document.referrer` contributed an origin (absent under `Referrer-Policy: no-referrer`). */\n get referrerWasAvailable(): boolean {\n return this.#state[OriginSlot.ReferrerWasAvailable];\n }\n\n /** Framed, outside development, with no trusted origin at all: the host should warn (LP0101). */\n get isProductionUnconfigured(): boolean {\n if (!isInIframe()) return false;\n if (this.#state[OriginSlot.ExplicitOrigins].size > 0) return false;\n if (this.#state[OriginSlot.AllowLocalhost]) return false;\n if (this.#state[OriginSlot.ReferrerOrigin] !== undefined) return false;\n return true;\n }\n\n /** The referrer is the only trust source, so any framing site may drive the preview (LP0102). */\n get isReferrerOnlyTrust(): boolean {\n if (this.#state[OriginSlot.ExplicitOrigins].size > 0) return false;\n if (this.#state[OriginSlot.AllowLocalhost]) return false;\n return this.#state[OriginSlot.ReferrerOrigin] !== undefined;\n }\n}\n\n/** The canonical `http(s)://host[:port]` form of `value`, or `undefined` when it is not one. */\nexport function normaliseOrigin(value: string): string | undefined {\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return undefined;\n if (!VALID_ORIGIN_PATTERN.test(url.origin)) return undefined;\n return url.origin;\n } catch {\n return undefined;\n }\n}\n","/**\n * Where the runtime is running: framed, opened, or on a developer's machine.\n * Nothing reads bundler-injected variables; a Vite consumer passes\n * `import.meta.env.X` explicitly (docs/astro.md).\n * @internal\n */\n\nexport function isInIframe(): boolean {\n if (typeof window === 'undefined') return false;\n try {\n return window.self !== window.top;\n } catch {\n // A cross-origin parent throws on the comparison; that is an iframe.\n return true;\n }\n}\n\n/** @internal */\nexport function isInPopup(): boolean {\n if (typeof window === 'undefined') return false;\n return window.opener != null;\n}\n\n/** An iframe or a popup; top-level navigation never starts the preview. @internal */\nexport function isInPreviewContext(): boolean {\n return isInIframe() || isInPopup();\n}\n\n/** Under Node, `NODE_ENV !== 'production'`; in a browser the hostname, which is the only signal a bundle has. @internal */\nexport function isDevMode(): boolean {\n const nodeEnv = readNodeEnv();\n if (nodeEnv !== undefined) return nodeEnv !== 'production';\n if (typeof window !== 'undefined') {\n const host = window.location.hostname;\n return host === 'localhost' || host === '127.0.0.1';\n }\n return false;\n}\n\nfunction readNodeEnv(): string | undefined {\n if (typeof process === 'undefined') return undefined;\n const value = process.env['NODE_ENV'];\n return typeof value === 'string' ? value : undefined;\n}\n","/**\n * The document a framework hook exposes, kept outside any framework: the\n * message bus decides which windows may speak, `DataMerger` turns the admin's\n * raw form values back into a populated document, and this class holds the one\n * snapshot a `useSyncExternalStore`-shaped API reads.\n *\n * It touches no DOM. A hook that re-renders a component tree needs the data,\n * not the bindings — everything about elements lives in the runtime instead.\n *\n * The session is per instance (ADR 0002): two hooks on one page have two\n * mergers, two revisions and two snapshots, and neither can hand the other a\n * document it never asked for.\n */\n\nimport { DataMerger } from '@core/data-merger';\nimport { MessageBus } from '@core/message-bus';\nimport { OriginDetector } from '@detection/origin';\nimport type { PayloadLivePreviewMessage } from '@/types/payload-protocol';\n\n/**\n * `idle` — connected, nothing received yet. `live` — the newest update was\n * merged and is in `data`. `unavailable` — it could not be, and `data` is the\n * last good document rather than a half-merged one.\n */\nexport type DocumentStatus = 'idle' | 'live' | 'unavailable';\n\nexport interface DocumentSnapshot<T> {\n /** The newest document that merged; never a partially applied one. */\n readonly data: T;\n /** No update has settled yet, or a merge is in flight. */\n readonly isLoading: boolean;\n readonly status: DocumentStatus;\n /** Why the last update did not merge; `undefined` while `status` is not `unavailable`. */\n readonly error: Error | undefined;\n}\n\nexport interface DocumentSessionOptions {\n /** Payload server origin the update is re-fetched from. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the merge. Default `1`, as Payload's own hook. */\n readonly depth?: number;\n /**\n * Admin origins allowed to post updates. Without one the session falls back\n * to the localhost matcher in development and trusts nothing in production.\n */\n readonly allowedOrigins?: readonly string[];\n /** `'parent-or-opener'` (default) refuses every window but the framing or opening one. */\n readonly eventSourcePolicy?: 'any' | 'parent-or-opener';\n /** Trust `document.referrer` as an origin source. Default `false`. */\n readonly enableReferrerDetection?: boolean;\n /** Match `localhost` origins in development. Default `true`. */\n readonly enableLocalhostMatching?: boolean;\n /** Injected for tests. */\n readonly fetchFn?: typeof fetch;\n /** Window to listen on. Default the global one. */\n readonly target?: Window;\n}\n\ntype Listener = () => void;\n\n/** What the merger logged for the attempt that failed, turned into one sentence. */\nfunction reasonFrom(line: readonly unknown[]): string {\n const [label, detail] = line;\n if (label === 'merge HTTP') return `the server answered ${String(detail)}`;\n if (label === 'merge invalid') return 'the server answered with something other than a document';\n if (label === 'merge exception') {\n return detail instanceof Error ? detail.message : 'the request failed';\n }\n return 'the update could not be merged';\n}\n\nexport class DocumentSession<T> {\n readonly #merger: DataMerger;\n readonly #bus: MessageBus;\n readonly #detector: OriginDetector;\n readonly #target: Window | undefined;\n readonly #listeners = new Set<Listener>();\n readonly #initial: DocumentSnapshot<T>;\n #snapshot: DocumentSnapshot<T>;\n /** Newest handled message; an older merge that settles later is ignored. */\n #revision = 0;\n #lastLog: readonly unknown[] = [];\n #attached = false;\n\n constructor(initialData: T, options: DocumentSessionOptions) {\n this.#detector = new OriginDetector({\n ...(options.allowedOrigins !== undefined\n ? { additionalOrigins: options.allowedOrigins }\n : {}),\n enableReferrerDetection: options.enableReferrerDetection ?? false,\n ...(options.enableLocalhostMatching !== undefined\n ? { enableLocalhostMatching: options.enableLocalhostMatching }\n : {}),\n });\n this.#merger = new DataMerger({\n serverURL: options.serverURL,\n ...(options.apiRoute !== undefined ? { apiRoute: options.apiRoute } : {}),\n ...(options.depth !== undefined ? { depth: options.depth } : {}),\n ...(options.fetchFn !== undefined ? { fetchFn: options.fetchFn } : {}),\n log: (...args: unknown[]) => {\n this.#lastLog = args;\n },\n });\n this.#bus = new MessageBus((origin) => this.#detector.matches(origin), {\n onUpdate: (message) => {\n void this.#handle(message);\n },\n onDocumentEvent: () => {\n // A save is the admin's business; the document itself arrives as an\n // update like any other.\n },\n sourcePolicy: options.eventSourcePolicy ?? 'parent-or-opener',\n });\n this.#target = options.target;\n this.#initial = { data: initialData, isLoading: true, status: 'idle', error: undefined };\n this.#snapshot = this.#initial;\n }\n\n /**\n * Attach on the first subscriber and detach with the last, so a component\n * that mounts twice (React's strict mode) listens once and a page that\n * unmounts the hook stops listening at all.\n */\n subscribe = (listener: Listener): (() => void) => {\n this.#listeners.add(listener);\n if (!this.#attached) this.#attach();\n return () => {\n this.#listeners.delete(listener);\n if (this.#listeners.size === 0) this.#detach();\n };\n };\n\n getSnapshot = (): DocumentSnapshot<T> => this.#snapshot;\n\n /** Server rendering has no window and no messages: the initial document, not loading. */\n getServerSnapshot = (): DocumentSnapshot<T> => this.#initial;\n\n #attach(): void {\n if (typeof window === 'undefined' && this.#target === undefined) return;\n this.#attached = true;\n this.#bus.attach(this.#target ?? window);\n MessageBus.sendReady(this.#readyTargets(), this.#detector.enumerate());\n }\n\n #detach(): void {\n if (!this.#attached) return;\n this.#attached = false;\n this.#bus.detach();\n this.#merger.destroy();\n }\n\n #readyTargets(): Window[] {\n const host = this.#target ?? (typeof window === 'undefined' ? undefined : window);\n if (host === undefined) return [];\n const targets: Window[] = [];\n if (host.parent !== host) targets.push(host.parent);\n const opener: unknown = host.opener;\n if (opener !== null && opener !== undefined && opener !== host) targets.push(opener as Window);\n return targets;\n }\n\n async #handle(message: PayloadLivePreviewMessage): Promise<void> {\n const data = message.data;\n if (data === undefined) return;\n const revision = (this.#revision += 1);\n this.#publish({ ...this.#snapshot, isLoading: true });\n this.#lastLog = [];\n const result = await this.#merger.merge({\n data,\n ...(message.collectionSlug !== undefined ? { collectionSlug: message.collectionSlug } : {}),\n ...(message.globalSlug !== undefined ? { globalSlug: message.globalSlug } : {}),\n ...(message.locale !== undefined ? { locale: message.locale } : {}),\n });\n // A newer message owns the state now, whatever this one turned out to be.\n if (revision !== this.#revision) return;\n if (result.status === 'superseded') return;\n if (result.status === 'unavailable') {\n this.#publish({\n ...this.#snapshot,\n isLoading: false,\n status: 'unavailable',\n error: new Error(`live preview: ${reasonFrom(this.#lastLog)}`),\n });\n return;\n }\n // The server decides the document's shape; `T` is what the caller says it is.\n this.#publish({\n data: result.doc as T,\n isLoading: false,\n status: 'live',\n error: undefined,\n });\n }\n\n #publish(next: DocumentSnapshot<T>): void {\n const current = this.#snapshot;\n if (\n current.data === next.data &&\n current.isLoading === next.isLoading &&\n current.status === next.status &&\n current.error === next.error\n ) {\n return;\n }\n this.#snapshot = next;\n for (const listener of [...this.#listeners]) listener();\n }\n}\n","/**\n * The Vue composable: Payload's `useLivePreview` shape, with this package's\n * merge underneath — one request at a time, the newer one winning, an HTTP\n * error refused instead of becoming the document, and a cache that belongs to\n * this call alone (ADR 0002).\n *\n * The same `DocumentSession` the React hook uses; only the reactivity is Vue's.\n * It touches no DOM: a composable re-renders your components, which is the\n * other side of the trade the runtime makes (docs/vue.md).\n *\n * `vue` is an optional peer. This entry and the Nuxt fragment renderer are the\n * only places that need it.\n */\n\nimport { computed, getCurrentScope, onScopeDispose, shallowRef, type ComputedRef } from 'vue';\nimport {\n DocumentSession,\n type DocumentSessionOptions,\n type DocumentSnapshot,\n type DocumentStatus,\n} from '@adapters/shared/document-session';\n\nexport type { DocumentSessionOptions, DocumentSnapshot, DocumentStatus };\n\nexport interface UseLivePreviewDocumentOptions<T> extends DocumentSessionOptions {\n /** The document the page was rendered from; returned until an update merges. */\n readonly initialData: T;\n}\n\n/** One ref per field of the snapshot, so a template can bind them individually. */\nexport interface LivePreviewDocumentRefs<T> {\n /** The newest document that merged; never a partially applied one. */\n readonly data: ComputedRef<T>;\n /** No update has settled yet, or a merge is in flight. */\n readonly isLoading: ComputedRef<boolean>;\n readonly status: ComputedRef<DocumentStatus>;\n /** Why the last update did not merge; `undefined` unless `status` is `unavailable`. */\n readonly error: ComputedRef<Error | undefined>;\n}\n\n/**\n * Subscribe to the admin's updates and return the merged document as refs.\n *\n * ```vue\n * <script setup lang=\"ts\">\n * const { data, status } = useLivePreviewDocument<Page>({\n * serverURL: import.meta.env.PUBLIC_PAYLOAD_URL,\n * allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_URL],\n * initialData: page,\n * depth: 1,\n * });\n * </script>\n * ```\n *\n * `data` and `isLoading` are Payload's two, with the same meaning; `status` and\n * `error` are added, so a merge that fails is visible rather than looking like a\n * document that did not change.\n *\n * Call it from `setup()` or inside an `effectScope()`: the subscription is\n * released when that scope is disposed, and there is nowhere else to release it.\n */\nexport function useLivePreviewDocument<T>(\n options: UseLivePreviewDocumentOptions<T>,\n): LivePreviewDocumentRefs<T> {\n const scope = getCurrentScope();\n if (scope === undefined) {\n throw new Error(\n 'payload-live-preview: useLivePreviewDocument() needs an effect scope — call it from ' +\n 'setup() or inside effectScope(), so its window listener and any request in flight are ' +\n 'released with the component.',\n );\n }\n const { initialData, ...connection } = options;\n const session = new DocumentSession<T>(initialData, connection);\n const snapshot = shallowRef<DocumentSnapshot<T>>(session.getSnapshot());\n onScopeDispose(\n session.subscribe(() => {\n // The session publishes a new object only when something changed, so a\n // shallow ref is enough to make every computed below re-evaluate.\n snapshot.value = session.getSnapshot();\n }),\n );\n return {\n data: computed(() => snapshot.value.data),\n isLoading: computed(() => snapshot.value.isLoading),\n status: computed(() => snapshot.value.status),\n error: computed(() => snapshot.value.error),\n };\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["observeThenableResult","value","then","Promise","resolve","catch","noopDiagnostic","isPathSegment","maxLength","length","includes","character","codePoint","codePointAt","hasControlCharacter","isSafeSlug","DataMerger","serverURL","apiRoute","depth","fetchFn","log","inflight","attempt","destroyDepth","constructor","options","this","end","endsWith","slice","withoutTrailingSlashes","route","diagnostic","startsWith","args","canMerge","request","endpointOf","globalSlug","encodeURIComponent","collectionSlug","id","data","Number","isFinite","String","merge","status","previous","controller","superseded","abort","endpoint","fetch","AbortController","url","response","method","credentials","headers","body","JSON","stringify","flattenLocales","locale","signal","ok","merged","json","Array","isArray","doc","error","destroy","parseFieldSchema","raw","out","entry","parsed","parseFieldEntry","push","record","name","type","fields","blocks","slug","parseBlockSchema","result","optionalFieldOk","check","optionalTypeOk","isPlainObject","prototype","Object","getPrototypeOf","NULL_AS_ABSENT_FIELDS","MessageBus","matcher","handlers","boundListener","attachedTarget","generation","revision","queueHead","queueTail","attach","target","window","committed","listener","event","receive","origin","reportInvalid","resetQueue","addEventListener","removeEventListener","detach","advanceGeneration","sendReady","targets","origins","payload","ready","protocolVersion","postMessage","matchesOrigin","matchesSource","v","x","isLivePreviewMessage","dispatchUpdate","message","normalized","field","Reflect","get","deleteProperty","fieldSchemaJSON","normalizeLivePreviewMessage","isDocumentEventMessage","invokeHandler","onDocumentEvent","focusFieldOf","onFocusField","hasUpdateData","isCurrentGeneration","messageRevision","validator","validateToken","commitUpdate","pending","verdict","next","enqueue","previewToken","settleValidation","approved","drainQueue","dequeue","onUpdate","reason","onInvalid","sourcePolicy","source","parent","opener","handler","expected","LOCALHOST_PATTERN","VALID_ORIGIN_PATTERN","LOCALHOST_HANDSHAKE_PORTS","OriginDetector","state","explicit","Set","additionalOrigins","normalised","normaliseOrigin","add","referrerOrigin","enableReferrerDetection","referrer","document","URL","allowLocalhost","enableLocalhostMatching","forceDevMode","isDevMode","nodeEnv","process","env","readNodeEnv","host","location","hostname","matches","has","size","test","lockOrigin","unlockOrigin","lockedOrigin","enumerate","port","referrerWasAvailable","isProductionUnconfigured","isInIframe","self","top","isReferrerOnlyTrust","trimmed","trim","protocol","reasonFrom","line","label","detail","Error","DocumentSession","merger","bus","detector","listeners","initial","snapshot","lastLog","attached","initialData","allowedOrigins","handle","eventSourcePolicy","isLoading","subscribe","delete","getSnapshot","getServerSnapshot","readyTargets","publish","current","useLivePreviewDocument","getCurrentScope","connection","session","shallowRef","onScopeDispose","computed"],"sources":["../../../src/core/thenable.ts","../../../src/core/diagnostics.ts","../../../src/core/data-merger.ts","../../../src/schema/parser.ts","../../../src/core/message-guards.ts","../../../src/core/message-bus.ts","../../../src/core/protocol-version.ts","../../../src/detection/origin.ts","../../../src/detection/environment.ts","../../../src/adapters/shared/document-session.ts","../../../src/adapters/vue/index.ts"],"sourcesContent":["/**\n * Detect an invalid async result in a synchronous extension point.\n *\n * Rejected native Promises otherwise become unhandled after the caller falls\n * back synchronously. Assimilating the result through a handled Promise keeps\n * that contract violation observable only through the runtime's explicit\n * error channel.\n */\nexport function observeThenableResult(value: unknown): boolean {\n if ((typeof value !== 'object' || value === null) && typeof value !== 'function') return false;\n if (typeof (value as { readonly then?: unknown }).then !== 'function') return false;\n void Promise.resolve(value).catch(() => undefined);\n return true;\n}\n","import { observeThenableResult } from './thenable';\n\n/**\n * Diagnostics are observability, never control flow: a consumer logger may\n * throw or return a rejected thenable, and reporting a failure must not become\n * a second failure. Invocation stays synchronous for ordinary loggers.\n */\nexport function isolateDiagnostic(\n diagnostic: (...args: unknown[]) => unknown,\n): (...args: unknown[]) => void {\n return (...args): void => {\n try {\n observeThenableResult(diagnostic(...args));\n } catch {\n // Diagnostics must not become a second failure boundary.\n }\n };\n}\n\n/** Shared allocation-free default for disabled diagnostic channels. */\nexport function noopDiagnostic(): void {\n // Intentionally empty.\n}\n\n// Access, invocation and thenable inspection all happen inside the wrapper, so\n// a missing or hostile console cannot escape.\nexport const safeConsoleError = isolateDiagnostic((...args) =>\n (console.error as (...values: unknown[]) => unknown)(...args),\n);\n\nexport const safeConsoleWarn = isolateDiagnostic((...args) =>\n (console.warn as (...values: unknown[]) => unknown)(...args),\n);\n\nexport const safeConsoleDebug = isolateDiagnostic((...args) =>\n // eslint-disable-next-line no-console -- best-effort development diagnostics\n (console.debug as (...values: unknown[]) => unknown)(...args),\n);\n","/**\n * Server-side merge for Payload 3.x, whose admin posts raw form values with\n * relationships as bare ids. Like the official client, the update is re-fetched\n * through the REST API with `X-Payload-HTTP-Method-Override: GET`, which\n * merges the form values into the stored document and returns it populated.\n * Only the newest request matters: a newer merge aborts the one in flight.\n */\n\nimport { isolateDiagnostic, noopDiagnostic } from './diagnostics';\n\nexport interface DataMergerOptions {\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Defaults to `/api`. */\n readonly apiRoute?: string;\n /** Population depth requested from the server. Defaults to `1`. */\n readonly depth?: number;\n readonly fetchFn?: typeof fetch;\n readonly log?: (...args: unknown[]) => void;\n}\n\nexport interface MergeRequest {\n readonly collectionSlug?: string | undefined;\n readonly globalSlug?: string | undefined;\n readonly data: Record<string, unknown>;\n readonly locale?: string | undefined;\n}\n\n/** `superseded`: a newer merge replaced this one. `unavailable`: fall back to the raw values. */\nexport type MergeResult =\n | { readonly status: 'merged'; readonly doc: Record<string, unknown> }\n | { readonly status: 'superseded' }\n | { readonly status: 'unavailable' };\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n // An iterated character always has a code point; 0 keeps the type and fails closed.\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint <= 0x1f || codePoint === 0x7f) return true;\n }\n return false;\n}\n\n/** One path segment: non-empty, bounded, not a dot segment, without separators or control characters. */\nfunction isPathSegment(value: string, maxLength: number): boolean {\n return (\n value.length > 0 &&\n value.length <= maxLength &&\n value !== '.' &&\n value !== '..' &&\n !value.includes('/') &&\n !value.includes('\\\\') &&\n !hasControlCharacter(value)\n );\n}\n\n/** A slug is one of Payload's own names; `?` and `#` never belong in one. */\nfunction isSafeSlug(value: string): boolean {\n return isPathSegment(value, 128) && !value.includes('?') && !value.includes('#');\n}\n\n/** An id comes from the form values; a string one is encoded, so `?` and `#` may appear. */\nfunction isSafeId(value: unknown): value is string | number {\n if (typeof value === 'number') return Number.isFinite(value);\n return typeof value === 'string' && isPathSegment(value, 512);\n}\n\n/**\n * Trailing slashes off a base URL, without a regular expression: `/\\/+$/`\n * backtracks over a long run of slashes and takes quadratic time on input the\n * page supplies (CodeQL js/polynomial-redos).\n */\nfunction withoutTrailingSlashes(value: string): string {\n let end = value.length;\n while (value.endsWith('/', end)) end -= 1;\n return value.slice(0, end);\n}\n\nexport class DataMerger {\n private readonly serverURL: string;\n private readonly apiRoute: string;\n private readonly depth: number;\n private readonly fetchFn: typeof fetch | undefined;\n private readonly log: (...args: unknown[]) => void;\n private inflight: AbortController | null = null;\n private attempt = 0;\n private destroyDepth = 0;\n\n constructor(options: DataMergerOptions) {\n this.serverURL = withoutTrailingSlashes(options.serverURL);\n const route = options.apiRoute ?? '/api';\n this.apiRoute = route.startsWith('/') ? route : `/${route}`;\n this.depth = options.depth ?? 1;\n this.fetchFn = options.fetchFn;\n this.log = isolateDiagnostic(options.log ?? noopDiagnostic);\n }\n\n /** Collections need an `id` in the form values; globals only their slug. */\n canMerge(request: MergeRequest): boolean {\n return this.endpointOf(request) !== null;\n }\n\n /**\n * The path under `apiRoute`, or `null` when no safe one exists. A request\n * accessor that throws counts as none, and so does a lone surrogate, which\n * `encodeURIComponent` refuses with a `URIError`.\n */\n private endpointOf(request: MergeRequest): string | null {\n try {\n // An empty global slug means \"collection document\" on the wire.\n const globalSlug = request.globalSlug;\n if (globalSlug) {\n return isSafeSlug(globalSlug) ? `globals/${encodeURIComponent(globalSlug)}` : null;\n }\n const collectionSlug = request.collectionSlug ?? '';\n const id: unknown = request.data['id'];\n if (!isSafeSlug(collectionSlug) || !isSafeId(id)) return null;\n return `${encodeURIComponent(collectionSlug)}/${encodeURIComponent(String(id))}`;\n } catch {\n return null;\n }\n }\n\n async merge(request: MergeRequest): Promise<MergeResult> {\n if (this.destroyDepth > 0) return { status: 'superseded' };\n // Claim the attempt before aborting: abort listeners and fetch shims are\n // reentrant, and a newer merge they start must win. Nothing but this class\n // aborts the signal, and only after the counter moved, so the counter\n // alone decides who was superseded.\n const attempt = (this.attempt += 1);\n const previous = this.inflight;\n this.inflight = null;\n let controller: AbortController | null = null;\n const superseded = (): boolean => this.attempt !== attempt;\n try {\n previous?.abort();\n if (superseded()) return { status: 'superseded' };\n // The endpoint reads the request's own values, which may be consumer\n // getters that start a newer merge before throwing.\n const endpoint = this.endpointOf(request);\n if (endpoint === null) {\n return superseded() ? { status: 'superseded' } : { status: 'unavailable' };\n }\n const fetchFn = this.fetchFn ?? (typeof fetch === 'function' ? fetch : undefined);\n if (fetchFn === undefined) return { status: 'unavailable' };\n controller = new AbortController();\n this.inflight = controller;\n const url = `${this.serverURL}${this.apiRoute}/${endpoint}`;\n const response = await fetchFn(url, {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Payload-HTTP-Method-Override': 'GET',\n },\n // The admin already flattens locales before posting; an absent locale\n // is left out of the JSON.\n body: JSON.stringify({\n data: request.data,\n depth: this.depth,\n flattenLocales: false,\n locale: request.locale,\n }),\n signal: controller.signal,\n });\n // A fetch shim may ignore the signal; the attempt is authoritative.\n if (superseded()) return { status: 'superseded' };\n if (!response.ok) {\n this.log('merge HTTP', response.status, url);\n return { status: 'unavailable' };\n }\n const merged: unknown = await response.json();\n if (superseded()) return { status: 'superseded' };\n if (merged === null || typeof merged !== 'object' || Array.isArray(merged)) {\n this.log('merge invalid', url);\n return { status: 'unavailable' };\n }\n return { status: 'merged', doc: merged as Record<string, unknown> };\n } catch (error) {\n if (superseded()) return { status: 'superseded' };\n this.log('merge exception', error);\n return { status: 'unavailable' };\n } finally {\n // Only the newest attempt's controller is ever in flight: a superseded\n // attempt's is gone already, and its successor's is left alone.\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n /** Abort any in-flight merge. The merger stays usable across a stop/start cycle. */\n destroy(): void {\n this.attempt += 1;\n const inflight = this.inflight;\n this.inflight = null;\n this.destroyDepth += 1;\n try {\n inflight?.abort();\n } finally {\n this.destroyDepth -= 1;\n }\n }\n}\n","/**\n * Validates the `fieldSchemaJSON` the admin sends. Entries that fail\n * validation are dropped, never thrown: a Payload/runtime version mismatch\n * must not take the preview down.\n */\n\nimport type { PayloadBlockSchema, PayloadFieldSchema } from '@/types/payload-protocol';\n\nexport function parseFieldSchema(raw: unknown): readonly PayloadFieldSchema[] {\n if (!Array.isArray(raw)) return [];\n const out: PayloadFieldSchema[] = [];\n for (const entry of raw) {\n const parsed = parseFieldEntry(entry);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\nfunction parseFieldEntry(value: unknown): PayloadFieldSchema | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const record = value as Record<string, unknown>;\n const name = record['name'];\n const type = record['type'];\n if (typeof name !== 'string' || name.length === 0) return undefined;\n if (typeof type !== 'string' || type.length === 0) return undefined;\n\n const fields = parseFieldSchema(record['fields']);\n const blocks = parseBlockSchema(record['blocks']);\n\n const result: Record<string, unknown> = { ...record, name, type };\n if (fields.length > 0) result['fields'] = fields;\n else delete result['fields'];\n if (blocks.length > 0) result['blocks'] = blocks;\n else delete result['blocks'];\n\n return result as unknown as PayloadFieldSchema;\n}\n\nfunction parseBlockSchema(raw: unknown): readonly PayloadBlockSchema[] {\n if (!Array.isArray(raw)) return [];\n const out: PayloadBlockSchema[] = [];\n for (const entry of raw) {\n if (typeof entry !== 'object' || entry === null) continue;\n const record = entry as Record<string, unknown>;\n const slug = record['slug'];\n if (typeof slug !== 'string' || slug.length === 0) continue;\n const fields = parseFieldSchema(record['fields']);\n out.push({ ...record, slug, fields });\n }\n return out;\n}\n","/** Shape guards for the messages the bus accepts. `null` counts as absent, as JSON bridges send it. */\n\nimport type {\n PayloadDocumentEventMessage,\n PayloadFieldSchema,\n PayloadLivePreviewMessage,\n} from '@/types/payload-protocol';\nimport { parseFieldSchema } from '@schema/parser';\n\nexport function isObjectMessage(value: unknown): value is { type: string } {\n return (\n typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string'\n );\n}\n\n/** A `payload-live-preview` message: `data` a plain object when present, scalars of the right type or absent. */\nexport function isLivePreviewMessage(value: { type: string }): value is PayloadLivePreviewMessage {\n const v = value as Record<string, unknown>;\n if (v['data'] !== undefined && v['data'] !== null && !isPlainObject(v['data'])) return false;\n return (\n optionalTypeOk(v['fieldSchemaJSON'], (x) => Array.isArray(x)) &&\n optionalTypeOk(v['globalSlug'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['collectionSlug'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['locale'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['ready'], (x) => typeof x === 'boolean') &&\n optionalTypeOk(v['previewToken'], (x) => typeof x === 'string') &&\n optionalTypeOk(v['protocolVersion'], (x) => typeof x === 'number')\n );\n}\n\n/** The non-empty field name of a `payload-live-preview-focus` message, else `undefined`. */\nexport function focusFieldOf(value: { type: string }): string | undefined {\n const field = (value as Record<string, unknown>)['field'];\n return typeof field === 'string' && field.length > 0 ? field : undefined;\n}\n\n/** Stock Payload sends only the `type`; custom senders may add the typed fields (omitted, never `null`). */\nexport function isDocumentEventMessage(value: {\n type: string;\n}): value is PayloadDocumentEventMessage {\n const v = value as Record<string, unknown>;\n return (\n optionalFieldOk(v['action'], (x) => x === 'updated' || x === 'created' || x === 'deleted') &&\n optionalFieldOk(v['slug'], (x) => typeof x === 'string') &&\n optionalFieldOk(v['id'], (x) => typeof x === 'string' || Number.isFinite(x))\n );\n}\n\nfunction optionalFieldOk(value: unknown, check: (v: unknown) => boolean): boolean {\n return value === undefined || check(value);\n}\n\nfunction optionalTypeOk(value: unknown, check: (v: unknown) => boolean): boolean {\n return value === undefined || value === null || check(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const prototype = Object.getPrototypeOf(value) as object | null;\n return prototype === null || Object.getPrototypeOf(prototype) === null;\n}\n\nconst NULL_AS_ABSENT_FIELDS = [\n 'data',\n 'fieldSchemaJSON',\n 'globalSlug',\n 'collectionSlug',\n 'locale',\n 'ready',\n 'previewToken',\n 'protocolVersion',\n] as const satisfies readonly (keyof PayloadLivePreviewMessage)[];\n\n/**\n * Drop `null` optionals so downstream sees the strict public type, and parse\n * the field schema entry by entry (malformed entries are dropped). Unknown\n * fields stay, for protocol evolution.\n */\nexport function normalizeLivePreviewMessage(\n message: PayloadLivePreviewMessage,\n): PayloadLivePreviewMessage {\n const normalized = { ...message };\n for (const field of NULL_AS_ABSENT_FIELDS) {\n const wireValue: unknown = Reflect.get(normalized, field);\n if (wireValue === null) Reflect.deleteProperty(normalized, field);\n }\n if (!Array.isArray(normalized.fieldSchemaJSON)) return normalized;\n let fieldSchemaJSON: readonly PayloadFieldSchema[];\n try {\n fieldSchemaJSON = parseFieldSchema(normalized.fieldSchemaJSON);\n } catch {\n fieldSchemaJSON = [];\n }\n return { ...normalized, fieldSchemaJSON };\n}\n","/**\n * postMessage ingress: origin and source checks, per-type shape guards, the\n * ordered preview-token validation queue, and the `ready` handshake. The bus\n * never throws; a rejected message is dropped or reported via `onInvalid`.\n * Consumer callbacks may detach or re-attach the bus synchronously, so\n * anything that runs after one re-checks the generation it started in.\n * See ADR 0004.\n */\n\nimport type {\n PayloadDocumentEventMessage,\n PayloadLivePreviewMessage,\n} from '@/types/payload-protocol';\nimport {\n focusFieldOf,\n isDocumentEventMessage,\n isLivePreviewMessage,\n isObjectMessage,\n isPlainObject,\n normalizeLivePreviewMessage,\n} from './message-guards';\nimport { LIBRARY_PROTOCOL_VERSION } from './protocol-version';\nimport { observeThenableResult } from './thenable';\n\nexport type OriginMatcher = (origin: string) => boolean;\n\n/** Identity of a data-bearing update: `revision` is monotonic, `generation` names the listener attachment. */\nexport interface MessageRevision {\n readonly revision: number;\n readonly generation: number;\n}\nexport function sameRevision(a: MessageRevision, b: MessageRevision): boolean {\n return a.generation === b.generation && a.revision === b.revision;\n}\n\nexport type InvalidReason = 'origin' | 'shape' | 'type' | 'token' | 'source';\n\nexport interface MessageHandlers {\n /** A validated update; data-bearing ones carry their revision, ready handshakes do not. */\n readonly onUpdate: (\n msg: PayloadLivePreviewMessage,\n origin: string,\n messageRevision?: MessageRevision,\n ) => void;\n readonly onDocumentEvent: (msg: PayloadDocumentEventMessage, origin: string) => void;\n /** The admin reports the cursor moved into a field (reveal tier 2). */\n readonly onFocusField?: (field: string, origin: string) => void;\n readonly onInvalid?: (reason: InvalidReason, origin: string) => void;\n /** `'parent-or-opener'` refuses every window but the framing/opening one, even from an allowed origin. */\n readonly sourcePolicy?: 'any' | 'parent-or-opener';\n /**\n * Gate for data-bearing updates. Verdicts commit in arrival order, so an\n * earlier update is dispatched before a later one even when its validation\n * resolves later; an error counts as rejection.\n */\n readonly validateToken?: (\n token: string | undefined,\n origin: string,\n ) => boolean | Promise<boolean>;\n}\n\n/** One entry of the validation queue: a singly linked list in arrival order. */\ninterface PendingValidation {\n readonly generation: number;\n readonly message: PayloadLivePreviewMessage;\n readonly origin: string;\n readonly messageRevision: MessageRevision | undefined;\n /** `undefined` until the validator settled; only literal `true` approves. */\n verdict: boolean | undefined;\n next: PendingValidation | undefined;\n}\n\nexport class MessageBus {\n private readonly matcher: OriginMatcher;\n private readonly handlers: MessageHandlers;\n private boundListener: ((event: MessageEvent) => void) | undefined = undefined;\n private attachedTarget: Window | undefined = undefined;\n private generation = 0;\n private revision = 0;\n private queueHead: PendingValidation | undefined = undefined;\n private queueTail: PendingValidation | undefined = undefined;\n\n constructor(matcher: OriginMatcher, handlers: MessageHandlers) {\n this.matcher = matcher;\n this.handlers = handlers;\n }\n\n /** Start listening on `target`. Idempotent. */\n attach(target: Window = window): void {\n if (this.attachedTarget !== undefined) return;\n let committed = false;\n const listener = (event: MessageEvent): void => {\n // Each attachment owns its listener; a stale one must not borrow the new generation.\n if (!committed || this.boundListener !== listener) return;\n const generation = this.generation;\n try {\n this.receive(event, generation, target);\n } catch {\n // A synthetic event can carry throwing accessors; that is a shape failure.\n let origin: string;\n try {\n origin = event.origin;\n } catch {\n origin = '';\n }\n this.reportInvalid('shape', origin, generation);\n }\n };\n this.generation += 1;\n this.resetQueue();\n this.attachedTarget = target;\n this.boundListener = listener;\n try {\n target.addEventListener('message', listener);\n } catch (error) {\n // Invalidate before rollback: a hook may already have attached a newer generation.\n if (this.boundListener === listener) {\n this.attachedTarget = undefined;\n this.boundListener = undefined;\n this.resetQueue();\n }\n try {\n target.removeEventListener('message', listener);\n } catch {\n // Keep the original failure.\n }\n throw error;\n }\n if (this.boundListener === listener) {\n committed = true;\n } else {\n try {\n target.removeEventListener('message', listener);\n } catch {\n // The listener is identity-gated above even if removal fails.\n }\n }\n }\n\n /** Stop listening. Idempotent. */\n detach(): void {\n const target = this.attachedTarget;\n const listener = this.boundListener;\n if (target === undefined || listener === undefined) return;\n // Invalidate first so a listener already on the stack sees an obsolete generation.\n this.attachedTarget = undefined;\n this.boundListener = undefined;\n this.resetQueue();\n target.removeEventListener('message', listener);\n }\n\n /** Invalidate pending validations while staying attached (heartbeat expiry). */\n advanceGeneration(): boolean {\n if (this.attachedTarget === undefined) return false;\n this.generation += 1;\n this.resetQueue();\n return true;\n }\n\n /** Post the `ready` handshake to every target for every origin. */\n static sendReady(targets: readonly Window[], origins: readonly string[]): void {\n const payload: PayloadLivePreviewMessage = {\n type: 'payload-live-preview',\n ready: true,\n protocolVersion: LIBRARY_PROTOCOL_VERSION,\n };\n for (const target of targets) {\n for (const origin of origins) {\n try {\n target.postMessage(payload, origin);\n } catch {\n // A malformed origin is the consumer's configuration problem.\n }\n }\n }\n }\n\n private receive(event: MessageEvent, generation: number, target: Window): void {\n const origin = event.origin;\n if (!this.matchesOrigin(origin, generation)) {\n this.reportInvalid('origin', origin, generation);\n return;\n }\n if (!this.matchesSource(event, target)) {\n this.reportInvalid('source', origin, generation);\n return;\n }\n const data: unknown = event.data;\n if (!isObjectMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n switch (data.type) {\n case 'payload-live-preview':\n if (!isLivePreviewMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.dispatchUpdate(normalizeLivePreviewMessage(data), origin, generation);\n return;\n case 'payload-document-event':\n if (!isDocumentEventMessage(data)) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.invokeHandler(generation, this.handlers.onDocumentEvent, data, origin);\n return;\n case 'payload-live-preview-focus': {\n const field = focusFieldOf(data);\n if (field === undefined) {\n this.reportInvalid('shape', origin, generation);\n return;\n }\n this.invokeHandler(generation, this.handlers.onFocusField, field, origin);\n return;\n }\n default:\n this.reportInvalid('type', origin, generation);\n }\n }\n\n private dispatchUpdate(\n message: PayloadLivePreviewMessage,\n origin: string,\n generation: number,\n ): void {\n const hasUpdateData = isPlainObject(message.data);\n if (!this.isCurrentGeneration(generation)) return;\n // A shape-valid data update consumes its revision before validation; a rejected token leaves a gap.\n const messageRevision = hasUpdateData\n ? { generation, revision: (this.revision += 1) }\n : undefined;\n const validator = this.handlers.validateToken;\n if (!this.isCurrentGeneration(generation)) return;\n if (validator === undefined) {\n this.commitUpdate(message, origin, generation, messageRevision);\n return;\n }\n // The data-less ready handshake carries no token and passes, so the admin learns we listen.\n if (message.ready === true && message.data === undefined) {\n this.commitUpdate(message, origin, generation, undefined);\n return;\n }\n const pending: PendingValidation = {\n generation,\n message,\n origin,\n messageRevision,\n verdict: undefined,\n next: undefined,\n };\n this.enqueue(pending);\n let verdict: unknown;\n try {\n // Invoked eagerly; only the commit waits for earlier queue entries.\n verdict = validator(message.previewToken, origin);\n } catch {\n this.settleValidation(pending, false);\n return;\n }\n if (typeof verdict === 'boolean') {\n this.settleValidation(pending, verdict);\n return;\n }\n // Assimilate any thenable; only literal `true` approves.\n void Promise.resolve(verdict).then(\n (approved) => {\n this.settleValidation(pending, approved === true);\n },\n () => {\n this.settleValidation(pending, false);\n },\n );\n }\n\n private settleValidation(pending: PendingValidation, approved: boolean): void {\n pending.verdict = approved;\n this.drainQueue(pending.generation);\n }\n\n private drainQueue(generation: number): void {\n while (this.isCurrentGeneration(generation)) {\n const pending = this.queueHead;\n if (pending === undefined) return;\n // The matcher may have narrowed since ingress (origin lock); an obsolete\n // origin must not hold the queue head and block the locked origin's work.\n if (!this.matchesOrigin(pending.origin, generation)) {\n if (!this.dequeue(pending)) continue;\n this.reportInvalid('origin', pending.origin, pending.generation);\n continue;\n }\n if (pending.verdict === undefined) return;\n if (!this.dequeue(pending)) continue;\n if (pending.verdict) {\n this.commitUpdate(\n pending.message,\n pending.origin,\n pending.generation,\n pending.messageRevision,\n );\n } else {\n this.reportInvalid('token', pending.origin, pending.generation);\n }\n }\n }\n\n private commitUpdate(\n message: PayloadLivePreviewMessage,\n origin: string,\n generation: number,\n messageRevision: MessageRevision | undefined,\n ): void {\n if (messageRevision === undefined) {\n this.invokeHandler(generation, this.handlers.onUpdate, message, origin);\n } else {\n this.invokeHandler(generation, this.handlers.onUpdate, message, origin, messageRevision);\n }\n }\n\n private reportInvalid(reason: InvalidReason, origin: string, generation: number): void {\n this.invokeHandler(generation, this.handlers.onInvalid, reason, origin);\n }\n\n /** Under `'parent-or-opener'`, whether the event came from the attached window's parent or opener. */\n private matchesSource(event: MessageEvent, target: Window): boolean {\n if (this.handlers.sourcePolicy !== 'parent-or-opener') return true;\n try {\n // Only a window is a source: a missing one matches nothing, not even a missing opener.\n const source: unknown = event.source;\n if (typeof source !== 'object' || source === null) return false;\n const parent = target.parent;\n if (parent !== target && source === parent) return true;\n const opener: unknown = target.opener;\n return source === opener;\n } catch {\n return false;\n }\n }\n\n /** Fail-closed: only literal `true` from the matcher, and only within the generation it ran in. */\n private matchesOrigin(origin: string, generation: number): boolean {\n try {\n const approved: unknown = this.isCurrentGeneration(generation) && this.matcher(origin);\n return approved === true && this.isCurrentGeneration(generation);\n } catch {\n return false;\n }\n }\n\n /** Callbacks run only in a current generation and must not unwind the listener or the drain. */\n private invokeHandler<TArgs extends unknown[]>(\n generation: number,\n handler: ((...args: TArgs) => unknown) | undefined,\n ...args: TArgs\n ): void {\n if (handler === undefined || !this.isCurrentGeneration(generation)) return;\n try {\n observeThenableResult(handler(...args));\n } catch {\n // Isolated by contract.\n }\n }\n\n private isCurrentGeneration(generation: number): boolean {\n return this.attachedTarget !== undefined && this.generation === generation;\n }\n\n private enqueue(pending: PendingValidation): void {\n if (this.queueTail === undefined) this.queueHead = pending;\n else this.queueTail.next = pending;\n this.queueTail = pending;\n }\n\n /** Advance the head past `expected`, unless a matcher reset the queue meanwhile. */\n private dequeue(expected: PendingValidation): boolean {\n if (this.queueHead !== expected) return false;\n this.queueHead = expected.next;\n if (this.queueHead === undefined) this.queueTail = undefined;\n return true;\n }\n\n private resetQueue(): void {\n this.queueHead = undefined;\n this.queueTail = undefined;\n }\n}\n","/**\n * Protocol negotiation. A capability is granted either by the version a peer\n * announces or by observation, since the stock Payload admin announces none:\n * what it can do is read off the messages it sends. See ADR 0010.\n */\n\n/** Bumped only when this library's understanding of the wire format changes. @internal */\nexport const LIBRARY_PROTOCOL_VERSION = 4;\n\n/** How a capability becomes active. @internal */\nexport type CapabilitySource = 'version' | 'observed';\n\n/** @internal */\nexport interface CapabilityDeclaration {\n /** Enabled once the negotiated version reaches this (for announced peers). */\n readonly since: number;\n /** The wire signal that grants this capability to a peer announcing no version. */\n readonly observed?:\n 'schema' | 'locale' | 'preview-token' | 'document-event' | 'relationship-event';\n}\n\n/** What activates each capability; the prose lives in `CAPABILITY_DOCUMENTATION`. */\nexport const CAPABILITY_DECLARATIONS = {\n basic: { since: 1 },\n 'schema-json': { since: 2, observed: 'schema' },\n locale: { since: 2, observed: 'locale' },\n 'preview-token': { since: 3, observed: 'preview-token' },\n 'nested-arrays': { since: 4 },\n 'recursive-diffs': { since: 4 },\n 'document-events': { since: 4, observed: 'document-event' },\n 'relationship-events': { since: 4, observed: 'relationship-event' },\n} as const satisfies Record<string, CapabilityDeclaration>;\n\nexport type ProtocolCapability = keyof typeof CAPABILITY_DECLARATIONS;\n\n/** Every declared capability, in declaration order. @internal */\nexport const PROTOCOL_CAPABILITIES = Object.keys(\n CAPABILITY_DECLARATIONS,\n) as readonly ProtocolCapability[];\n\nexport interface ProtocolNegotiation {\n /** This library's protocol version. */\n readonly ours: number;\n /** The remote party's version, when it announced one. */\n readonly theirs: number | undefined;\n /** `min(ours, theirs ?? 1)` — the version both sides actually share. */\n readonly negotiated: number;\n /** Capabilities active at the negotiated version or seen on the wire. */\n readonly capabilities: ReadonlySet<ProtocolCapability>;\n /** The subset that was observed on the wire rather than granted by version. */\n readonly observed: ReadonlySet<ProtocolCapability>;\n}\n\n/** Deterministic; the runtime re-runs it whenever either input changes. */\nexport function negotiateProtocol(\n theirs: number | undefined,\n observed: Iterable<ProtocolCapability> = [],\n): ProtocolNegotiation {\n const sanitisedTheirs =\n typeof theirs === 'number' && Number.isFinite(theirs) && theirs >= 1\n ? Math.floor(theirs)\n : undefined;\n const effectiveTheirs = sanitisedTheirs ?? 1;\n const negotiated = Math.min(LIBRARY_PROTOCOL_VERSION, effectiveTheirs);\n const seen = new Set<ProtocolCapability>(observed);\n const capabilities = new Set<ProtocolCapability>();\n for (const capability of PROTOCOL_CAPABILITIES) {\n if (negotiated >= CAPABILITY_DECLARATIONS[capability].since || seen.has(capability)) {\n capabilities.add(capability);\n }\n }\n return {\n ours: LIBRARY_PROTOCOL_VERSION,\n ...(sanitisedTheirs !== undefined ? { theirs: sanitisedTheirs } : { theirs: undefined }),\n negotiated,\n capabilities,\n observed: seen,\n };\n}\n\n/** @internal */\nexport function hasCapability(\n negotiation: ProtocolNegotiation,\n capability: ProtocolCapability,\n): boolean {\n return negotiation.capabilities.has(capability);\n}\n\n/** The capabilities a single message demonstrates, by its shape alone. */\nexport function observeCapabilities(message: {\n readonly fieldSchemaJSON?: unknown;\n readonly locale?: unknown;\n readonly previewToken?: unknown;\n readonly externallyUpdatedRelationship?: unknown;\n}): readonly ProtocolCapability[] {\n const seen: ProtocolCapability[] = [];\n if (Array.isArray(message.fieldSchemaJSON)) seen.push('schema-json');\n if (typeof message.locale === 'string') seen.push('locale');\n if (typeof message.previewToken === 'string') seen.push('preview-token');\n if (\n typeof message.externallyUpdatedRelationship === 'object' &&\n message.externallyUpdatedRelationship !== null\n ) {\n seen.push('relationship-events');\n }\n return seen;\n}\n","/**\n * Which admin origins the runtime trusts: explicit ones always, the\n * `document.referrer` origin only when none is configured, any localhost port\n * in development. See docs/security.md §1.\n */\n\nimport { isDevMode, isInIframe } from './environment';\n\nconst LOCALHOST_PATTERN = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(?::\\d+)?$/i;\nconst VALID_ORIGIN_PATTERN = /^https?:\\/\\/[^/?#\\s]+$/i;\n\n/** Ports the handshake broadcasts to when the localhost pattern is on; matching itself is unrestricted. */\nconst LOCALHOST_HANDSHAKE_PORTS: readonly number[] = [\n 3000, 3001, 3333, 4000, 4321, 5000, 5173, 5174, 8000, 8080, 8888, 9000,\n];\n\nexport interface OriginDetectorOptions {\n /** Explicit origins to trust. Always allowed. */\n readonly additionalOrigins?: readonly string[];\n /** Set to `false` to ignore `document.referrer`. */\n readonly enableReferrerDetection?: boolean;\n /** Set to `false` to ignore the localhost pattern even in development. */\n readonly enableLocalhostMatching?: boolean;\n /** Override dev-mode detection. */\n readonly forceDevMode?: boolean;\n /** Override `document.referrer` for tests. */\n readonly referrer?: string;\n}\n\nconst enum OriginSlot {\n ExplicitOrigins,\n ReferrerOrigin,\n AllowLocalhost,\n ReferrerWasAvailable,\n LockedOrigin,\n}\n\ntype OriginState = [\n explicitOrigins: ReadonlySet<string>,\n referrerOrigin: string | undefined,\n allowLocalhost: boolean,\n referrerWasAvailable: boolean,\n lockedOrigin: string | undefined,\n];\n\n/** The trusted-origin policy: the union of the candidate sources, narrowed to one by `lockOrigin()`. */\nexport class OriginDetector {\n readonly #state: OriginState;\n\n constructor(options: OriginDetectorOptions = {}) {\n const explicit = new Set<string>();\n for (const origin of options.additionalOrigins ?? []) {\n const normalised = normaliseOrigin(origin);\n if (normalised !== undefined) explicit.add(normalised);\n }\n let referrerOrigin: string | undefined;\n if (options.enableReferrerDetection ?? true) {\n const referrer =\n options.referrer ?? (typeof document !== 'undefined' ? document.referrer : '');\n if (referrer.length > 0) {\n try {\n const origin = new URL(referrer).origin;\n if (origin && origin !== 'null') referrerOrigin = origin;\n } catch {\n // opaque referrer\n }\n }\n }\n const allowLocalhost =\n (options.enableLocalhostMatching ?? true) && (options.forceDevMode ?? isDevMode());\n this.#state = [\n explicit,\n referrerOrigin,\n allowLocalhost,\n referrerOrigin !== undefined,\n undefined,\n ];\n }\n\n /** Whether `origin` is trusted: the locked origin alone once locked, otherwise the policy above. */\n matches(origin: string): boolean {\n if (origin.length === 0 || origin === 'null') return false;\n if (this.#state[OriginSlot.LockedOrigin] !== undefined) {\n return origin === this.#state[OriginSlot.LockedOrigin];\n }\n if (this.#state[OriginSlot.ExplicitOrigins].has(origin)) return true;\n // The referrer is a fallback, not a union member: any embedder lands there.\n if (\n this.#state[OriginSlot.ExplicitOrigins].size === 0 &&\n this.#state[OriginSlot.ReferrerOrigin] !== undefined &&\n origin === this.#state[OriginSlot.ReferrerOrigin]\n ) {\n return true;\n }\n if (this.#state[OriginSlot.AllowLocalhost] && LOCALHOST_PATTERN.test(origin)) return true;\n return false;\n }\n\n /** Narrow trust to `origin`; refused (`false`) when it is not currently trusted. */\n lockOrigin(origin: string): boolean {\n if (!this.matches(origin)) return false;\n this.#state[OriginSlot.LockedOrigin] = origin;\n return true;\n }\n\n /** Release the lock (heartbeat timeout) so another allow-listed origin may reconnect. Returns the previous lock. */\n unlockOrigin(): string | undefined {\n const previous = this.#state[OriginSlot.LockedOrigin];\n this.#state[OriginSlot.LockedOrigin] = undefined;\n return previous;\n }\n\n get lockedOrigin(): string | undefined {\n return this.#state[OriginSlot.LockedOrigin];\n }\n\n /** Every origin the `ready` handshake is broadcast to; the localhost pattern expands to the common dev ports. */\n enumerate(): string[] {\n if (this.#state[OriginSlot.LockedOrigin] !== undefined) {\n return [this.#state[OriginSlot.LockedOrigin]];\n }\n const result = new Set<string>(this.#state[OriginSlot.ExplicitOrigins]);\n if (\n this.#state[OriginSlot.ExplicitOrigins].size === 0 &&\n this.#state[OriginSlot.ReferrerOrigin] !== undefined\n ) {\n result.add(this.#state[OriginSlot.ReferrerOrigin]);\n }\n if (this.#state[OriginSlot.AllowLocalhost]) {\n for (const port of LOCALHOST_HANDSHAKE_PORTS) {\n result.add(`http://localhost:${String(port)}`);\n result.add(`http://127.0.0.1:${String(port)}`);\n }\n }\n return [...result];\n }\n\n /** Whether `document.referrer` contributed an origin (absent under `Referrer-Policy: no-referrer`). */\n get referrerWasAvailable(): boolean {\n return this.#state[OriginSlot.ReferrerWasAvailable];\n }\n\n /** Framed, outside development, with no trusted origin at all: the host should warn (LP0101). */\n get isProductionUnconfigured(): boolean {\n if (!isInIframe()) return false;\n if (this.#state[OriginSlot.ExplicitOrigins].size > 0) return false;\n if (this.#state[OriginSlot.AllowLocalhost]) return false;\n if (this.#state[OriginSlot.ReferrerOrigin] !== undefined) return false;\n return true;\n }\n\n /** The referrer is the only trust source, so any framing site may drive the preview (LP0102). */\n get isReferrerOnlyTrust(): boolean {\n if (this.#state[OriginSlot.ExplicitOrigins].size > 0) return false;\n if (this.#state[OriginSlot.AllowLocalhost]) return false;\n return this.#state[OriginSlot.ReferrerOrigin] !== undefined;\n }\n}\n\n/** The canonical `http(s)://host[:port]` form of `value`, or `undefined` when it is not one. */\nexport function normaliseOrigin(value: string): string | undefined {\n const trimmed = value.trim();\n if (trimmed.length === 0) return undefined;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return undefined;\n if (!VALID_ORIGIN_PATTERN.test(url.origin)) return undefined;\n return url.origin;\n } catch {\n return undefined;\n }\n}\n","/**\n * Where the runtime is running: framed, opened, or on a developer's machine.\n * Nothing reads bundler-injected variables; a Vite consumer passes\n * `import.meta.env.X` explicitly (docs/astro.md).\n * @internal\n */\n\nexport function isInIframe(): boolean {\n if (typeof window === 'undefined') return false;\n try {\n return window.self !== window.top;\n } catch {\n // A cross-origin parent throws on the comparison; that is an iframe.\n return true;\n }\n}\n\n/** @internal */\nexport function isInPopup(): boolean {\n if (typeof window === 'undefined') return false;\n return window.opener != null;\n}\n\n/** An iframe or a popup; top-level navigation never starts the preview. @internal */\nexport function isInPreviewContext(): boolean {\n return isInIframe() || isInPopup();\n}\n\n/** Under Node, `NODE_ENV !== 'production'`; in a browser the hostname, which is the only signal a bundle has. @internal */\nexport function isDevMode(): boolean {\n const nodeEnv = readNodeEnv();\n if (nodeEnv !== undefined) return nodeEnv !== 'production';\n if (typeof window !== 'undefined') {\n const host = window.location.hostname;\n return host === 'localhost' || host === '127.0.0.1';\n }\n return false;\n}\n\nfunction readNodeEnv(): string | undefined {\n if (typeof process === 'undefined') return undefined;\n const value = process.env['NODE_ENV'];\n return typeof value === 'string' ? value : undefined;\n}\n","/**\n * The document a framework hook exposes, kept outside any framework: the\n * message bus decides which windows may speak, `DataMerger` turns the admin's\n * raw form values back into a populated document, and this class holds the one\n * snapshot a `useSyncExternalStore`-shaped API reads.\n *\n * It touches no DOM. A hook that re-renders a component tree needs the data,\n * not the bindings — everything about elements lives in the runtime instead.\n *\n * The session is per instance (ADR 0002): two hooks on one page have two\n * mergers, two revisions and two snapshots, and neither can hand the other a\n * document it never asked for.\n */\n\nimport { DataMerger } from '@core/data-merger';\nimport { MessageBus } from '@core/message-bus';\nimport { OriginDetector } from '@detection/origin';\nimport type { PayloadLivePreviewMessage } from '@/types/payload-protocol';\n\n/**\n * `idle` — connected, nothing received yet. `live` — the newest update was\n * merged and is in `data`. `unavailable` — it could not be, and `data` is the\n * last good document rather than a half-merged one.\n */\nexport type DocumentStatus = 'idle' | 'live' | 'unavailable';\n\nexport interface DocumentSnapshot<T> {\n /** The newest document that merged; never a partially applied one. */\n readonly data: T;\n /** No update has settled yet, or a merge is in flight. */\n readonly isLoading: boolean;\n readonly status: DocumentStatus;\n /** Why the last update did not merge; `undefined` while `status` is not `unavailable`. */\n readonly error: Error | undefined;\n}\n\nexport interface DocumentSessionOptions {\n /** Payload server origin the update is re-fetched from. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the merge. Default `1`, as Payload's own hook. */\n readonly depth?: number;\n /**\n * Admin origins allowed to post updates. Without one the session falls back\n * to the localhost matcher in development and trusts nothing in production.\n */\n readonly allowedOrigins?: readonly string[];\n /** `'parent-or-opener'` (default) refuses every window but the framing or opening one. */\n readonly eventSourcePolicy?: 'any' | 'parent-or-opener';\n /** Trust `document.referrer` as an origin source. Default `false`. */\n readonly enableReferrerDetection?: boolean;\n /** Match `localhost` origins in development. Default `true`. */\n readonly enableLocalhostMatching?: boolean;\n /** Injected for tests. */\n readonly fetchFn?: typeof fetch;\n /** Window to listen on. Default the global one. */\n readonly target?: Window;\n}\n\ntype Listener = () => void;\n\n/** What the merger logged for the attempt that failed, turned into one sentence. */\nfunction reasonFrom(line: readonly unknown[]): string {\n const [label, detail] = line;\n if (label === 'merge HTTP') return `the server answered ${String(detail)}`;\n if (label === 'merge invalid') return 'the server answered with something other than a document';\n if (label === 'merge exception') {\n return detail instanceof Error ? detail.message : 'the request failed';\n }\n return 'the update could not be merged';\n}\n\nexport class DocumentSession<T> {\n readonly #merger: DataMerger;\n readonly #bus: MessageBus;\n readonly #detector: OriginDetector;\n readonly #target: Window | undefined;\n readonly #listeners = new Set<Listener>();\n readonly #initial: DocumentSnapshot<T>;\n #snapshot: DocumentSnapshot<T>;\n /** Newest handled message; an older merge that settles later is ignored. */\n #revision = 0;\n #lastLog: readonly unknown[] = [];\n #attached = false;\n\n constructor(initialData: T, options: DocumentSessionOptions) {\n this.#detector = new OriginDetector({\n ...(options.allowedOrigins !== undefined\n ? { additionalOrigins: options.allowedOrigins }\n : {}),\n enableReferrerDetection: options.enableReferrerDetection ?? false,\n ...(options.enableLocalhostMatching !== undefined\n ? { enableLocalhostMatching: options.enableLocalhostMatching }\n : {}),\n });\n this.#merger = new DataMerger({\n serverURL: options.serverURL,\n ...(options.apiRoute !== undefined ? { apiRoute: options.apiRoute } : {}),\n ...(options.depth !== undefined ? { depth: options.depth } : {}),\n ...(options.fetchFn !== undefined ? { fetchFn: options.fetchFn } : {}),\n log: (...args: unknown[]) => {\n this.#lastLog = args;\n },\n });\n this.#bus = new MessageBus((origin) => this.#detector.matches(origin), {\n onUpdate: (message) => {\n void this.#handle(message);\n },\n onDocumentEvent: () => {\n // A save is the admin's business; the document itself arrives as an\n // update like any other.\n },\n sourcePolicy: options.eventSourcePolicy ?? 'parent-or-opener',\n });\n this.#target = options.target;\n this.#initial = { data: initialData, isLoading: true, status: 'idle', error: undefined };\n this.#snapshot = this.#initial;\n }\n\n /**\n * Attach on the first subscriber and detach with the last, so a component\n * that mounts twice (React's strict mode) listens once and a page that\n * unmounts the hook stops listening at all.\n */\n subscribe = (listener: Listener): (() => void) => {\n this.#listeners.add(listener);\n if (!this.#attached) this.#attach();\n return () => {\n this.#listeners.delete(listener);\n if (this.#listeners.size === 0) this.#detach();\n };\n };\n\n getSnapshot = (): DocumentSnapshot<T> => this.#snapshot;\n\n /** Server rendering has no window and no messages: the initial snapshot a client render starts from. */\n getServerSnapshot = (): DocumentSnapshot<T> => this.#initial;\n\n #attach(): void {\n if (typeof window === 'undefined' && this.#target === undefined) return;\n this.#attached = true;\n this.#bus.attach(this.#target ?? window);\n MessageBus.sendReady(this.#readyTargets(), this.#detector.enumerate());\n }\n\n #detach(): void {\n if (!this.#attached) return;\n this.#attached = false;\n this.#bus.detach();\n this.#merger.destroy();\n }\n\n #readyTargets(): Window[] {\n const host = this.#target ?? (typeof window === 'undefined' ? undefined : window);\n if (host === undefined) return [];\n const targets: Window[] = [];\n if (host.parent !== host) targets.push(host.parent);\n const opener: unknown = host.opener;\n if (opener !== null && opener !== undefined && opener !== host) targets.push(opener as Window);\n return targets;\n }\n\n async #handle(message: PayloadLivePreviewMessage): Promise<void> {\n const data = message.data;\n if (data === undefined) return;\n const revision = (this.#revision += 1);\n this.#publish({ ...this.#snapshot, isLoading: true });\n this.#lastLog = [];\n const result = await this.#merger.merge({\n data,\n ...(message.collectionSlug !== undefined ? { collectionSlug: message.collectionSlug } : {}),\n ...(message.globalSlug !== undefined ? { globalSlug: message.globalSlug } : {}),\n ...(message.locale !== undefined ? { locale: message.locale } : {}),\n });\n // A newer message owns the state now, whatever this one turned out to be.\n if (revision !== this.#revision) return;\n if (result.status === 'superseded') return;\n if (result.status === 'unavailable') {\n this.#publish({\n ...this.#snapshot,\n isLoading: false,\n status: 'unavailable',\n error: new Error(`live preview: ${reasonFrom(this.#lastLog)}`),\n });\n return;\n }\n // The server decides the document's shape; `T` is what the caller says it is.\n this.#publish({\n data: result.doc as T,\n isLoading: false,\n status: 'live',\n error: undefined,\n });\n }\n\n #publish(next: DocumentSnapshot<T>): void {\n const current = this.#snapshot;\n if (\n current.data === next.data &&\n current.isLoading === next.isLoading &&\n current.status === next.status &&\n current.error === next.error\n ) {\n return;\n }\n this.#snapshot = next;\n for (const listener of [...this.#listeners]) listener();\n }\n}\n","/**\n * The Vue composable: Payload's `useLivePreview` shape, with this package's\n * merge underneath — one request per accepted update, a newer one aborting the\n * one in flight, an HTTP error refused instead of becoming the document, and a\n * session that belongs to this call alone (ADR 0002).\n *\n * The same `DocumentSession` the React hook uses; only the reactivity is Vue's.\n * It touches no DOM: a composable re-renders your components, which is the\n * other side of the trade the runtime makes (docs/vue.md).\n *\n * `vue` is an optional peer. This entry and the Nuxt fragment renderer are the\n * only places that need it.\n */\n\nimport { computed, getCurrentScope, onScopeDispose, shallowRef, type ComputedRef } from 'vue';\nimport {\n DocumentSession,\n type DocumentSessionOptions,\n type DocumentSnapshot,\n type DocumentStatus,\n} from '@adapters/shared/document-session';\n\nexport type { DocumentSessionOptions, DocumentSnapshot, DocumentStatus };\n\nexport interface UseLivePreviewDocumentOptions<T> extends DocumentSessionOptions {\n /** The document the page was rendered from; returned until an update merges. */\n readonly initialData: T;\n}\n\n/** One ref per field of the snapshot, so a template can bind them individually. */\nexport interface LivePreviewDocumentRefs<T> {\n /** The newest document that merged; never a partially applied one. */\n readonly data: ComputedRef<T>;\n /** No update has settled yet, or a merge is in flight. */\n readonly isLoading: ComputedRef<boolean>;\n readonly status: ComputedRef<DocumentStatus>;\n /** Why the last update did not merge; `undefined` unless `status` is `unavailable`. */\n readonly error: ComputedRef<Error | undefined>;\n}\n\n/**\n * Subscribe to the admin's updates and return the merged document as refs.\n *\n * ```vue\n * <script setup lang=\"ts\">\n * const { data, status } = useLivePreviewDocument<Page>({\n * serverURL: import.meta.env.PUBLIC_PAYLOAD_URL,\n * allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_URL],\n * initialData: page,\n * depth: 1,\n * });\n * </script>\n * ```\n *\n * `data` and `isLoading` are Payload's two, with the same meaning; `status` and\n * `error` are added, so a merge that fails is visible rather than looking like a\n * document that did not change.\n *\n * Call it from `setup()` or inside an `effectScope()`: the subscription is\n * released when that scope is disposed, and there is nowhere else to release it.\n */\nexport function useLivePreviewDocument<T>(\n options: UseLivePreviewDocumentOptions<T>,\n): LivePreviewDocumentRefs<T> {\n const scope = getCurrentScope();\n if (scope === undefined) {\n throw new Error(\n 'payload-live-preview: useLivePreviewDocument() needs an effect scope — call it from ' +\n 'setup() or inside effectScope(), so its window listener and any request in flight are ' +\n 'released with the component.',\n );\n }\n const { initialData, ...connection } = options;\n const session = new DocumentSession<T>(initialData, connection);\n const snapshot = shallowRef<DocumentSnapshot<T>>(session.getSnapshot());\n onScopeDispose(\n session.subscribe(() => {\n // The session publishes a new object only when something changed, so a\n // shallow ref is enough to make every computed below re-evaluate.\n snapshot.value = session.getSnapshot();\n }),\n );\n return {\n data: computed(() => snapshot.value.data),\n isLoading: computed(() => snapshot.value.isLoading),\n status: computed(() => snapshot.value.status),\n error: computed(() => snapshot.value.error),\n };\n}\n"],"mappings":"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","ignoreList":[]}
package/dist/annotate.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as e}from"fs";import"fs/promises";import"path";var t=/<([a-z][a-z0-9-]*)((?:"[^"]*"|'[^']*'|[^>"'])*)>([^<]*)<\/\1\s*>/gu,n=/^\s*\{([^{}]*)\}\s*$/u,r=/\{([^{}]*)\}/u,s=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/u,o="data-payload-field";function i(e,t){let n=1;for(let r=0;r<t;r+=1)10===e.charCodeAt(r)&&(n+=1);return n}function a(e){return e.slice(e.indexOf(".")+1)}function l(e){const t=e.replace(/\s+/gu," ").trim();return t.length>60?`${t.slice(0,57)}…`:t}function c(e,t){const n=e.trim();if(0===n.length)return"the element is empty";if(!s.test(n))return"not a plain field access — a call, an operator or an index cannot be traced to one field";const r=a(n);return t.has(r)?void 0:`the schema has no field \`${r}\`; the binding would name something that never arrives`}function f(e){return`${o}="${e.path}"`}var u="__lpPreview",d="__lpPreviewBindingsFromLocals",p=`import { previewBindingsFromLocals as ${d} } from 'payload-live-preview/server';`,h=`const ${u} = ${d}(Astro.locals);`,$="---";function m(e){if(!e.startsWith($))return{frontmatter:"",bodyOffset:0};const t=e.indexOf(`\n${$}`,3);if(-1===t)return{frontmatter:"",bodyOffset:0};const n=t+1+3;return{frontmatter:e.slice(3,t),bodyOffset:n}}function v(e,t){const n=`${p}\n${h}\n`;return 0===t?`${$}\n${n}${$}\n${e}`:`${$}\n${n}${e.slice(3,t)}${e.slice(t)}`}var g=e=>!e.startsWith("\0")&&!e.includes("?")&&e.endsWith(".astro");function b(e,s){const d=function(e){const t=/* @__PURE__ */new Set;for(const n of[...e.globals,...e.collections])for(const e of n.fields)e.path.includes("*")||t.add(e.path);return t}(s.inventory),{bodyOffset:p}=m(e),h=e.slice(p),{candidates:$,refusals:g}=function(e,s){const f=[],u=[];for(const d of e.matchAll(t)){const[,t="",p="",h=""]=d,$=i(e,d.index);if(p.includes(o))continue;const m=n.exec(h);if(null===m){null!==r.exec(h)&&u.push({line:$,reason:"the value is printed beside other content — a binding replaces the whole text, so the markup has to be split first",expression:l(h)});continue}const v=m[1]??"",g=c(v,s.paths);void 0===g?f.push({insertAt:d.index+1+t.length,path:a(v.trim()),line:$,tag:t}):u.push({line:$,reason:g,expression:l(v)})}return{candidates:f,refusals:u}}(h,{paths:d});if(0===$.length)return{code:e,refusals:g};const b=$.map(e=>({...e,insertAt:e.insertAt+p})),A=!0!==s.allowPublicBindings,w=function(e,t,n=f){let r=e;for(const e of[...t].sort((e,t)=>t.insertAt-e.insertAt))r=`${r.slice(0,e.insertAt)} ${n(e)}${r.slice(e.insertAt)}`;return r}(e,b,e=>A?`{...${u}.bind('${e.path}')}`:f(e));return{code:A?v(w,p):w,refusals:g}}function A(t){const n=t.include??g,r=t.readFile??(t=>e(t,"utf8"));return{name:"payload-live-preview:annotate",enforce:"pre",load(e){if(!n(e))return null;const s=r(e),{code:o,refusals:i}=b(s,t);return i.length>0&&t.onRefusals?.(e,i),{code:o,map:null}}}}export{b as annotateSource,A as livePreviewAnnotate,m as splitFrontmatter};
1
+ import{readFileSync as e}from"fs";import"fs/promises";import"path";var t=/<([a-z][a-z0-9-]*)((?:"[^"]*"|'[^']*'|[^>"'])*)>([^<]*)<\/\1\s*>/gu,n=/^\s*\{([^{}]*)\}\s*$/u,s=/\{([^{}]*)\}/u,o=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/u,r="data-payload-field",i=[/\.(?:map|flatMap|forEach)\(\s*(?:async\s+)?\(?\s*([A-Za-z_$][\w$]*)/gu,/\bfor\s*\(\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s+of\b/gu,/\{#each\s[^}]*?\sas\s+([A-Za-z_$][\w$]*)/gu];function a(e,t){let n=1;for(let s=0;s<t;s+=1)10===e.charCodeAt(s)&&(n+=1);return n}function c(e){return e.slice(e.indexOf(".")+1)}function l(e){const t=e.replace(/\s+/gu," ").trim();return t.length>60?`${t.slice(0,57)}…`:t}function f(e,t,n){const s=e.trim();if(0===s.length)return"the element is empty";if(!o.test(s))return"not a plain field access — a call, an operator or an index cannot be traced to one field";const r=c(s),i=s.slice(0,s.indexOf("."));return n.has(i)?`\`${i}\` is a loop item — a binding names a field of the document, and \`${r}\` there is a different value, or none`:t.has(r)?void 0:`the schema has no field \`${r}\`; the binding would name something that never arrives`}function u(e){return`${r}="${e.path}"`}var d="__lpPreview",h="__lpPreviewBindingsFromLocals",p=`import { previewBindingsFromLocals as ${h} } from 'payload-live-preview/server';`,$=`const ${d} = ${h}(Astro.locals);`,m="---";function v(e){if(!e.startsWith(m))return{frontmatter:"",bodyOffset:0};const t=e.indexOf(`\n${m}`,3);if(-1===t)return{frontmatter:"",bodyOffset:0};const n=t+1+3;return{frontmatter:e.slice(3,t),bodyOffset:n}}function g(e,t){const n=`${p}\n${$}\n`;return 0===t?`${m}\n${n}${m}\n${e}`:`${m}\n${n}${e.slice(3,t)}${e.slice(t)}`}var A=e=>!e.startsWith("\0")&&!e.includes("?")&&e.endsWith(".astro");function b(e,o){const h=function(e){const t=/* @__PURE__ */new Set;for(const n of[...e.globals,...e.collections])for(const e of n.fields)e.path.includes("*")||t.add(e.path);return t}(o.inventory),{bodyOffset:p}=v(e),$=e.slice(p),{candidates:m,refusals:A}=function(e,o){const u=[],d=[],h=function(e){const t=/* @__PURE__ */new Set;for(const n of i)for(const s of e.matchAll(n))void 0!==s[1]&&t.add(s[1]);return t}(e);for(const i of e.matchAll(t)){const[,t="",p="",$=""]=i,m=a(e,i.index);if(p.includes(r))continue;const v=n.exec($);if(null===v){null!==s.exec($)&&d.push({line:m,reason:"the value is printed beside other content — a binding replaces the whole text, so the markup has to be split first",expression:l($)});continue}const g=v[1]??"",A=f(g,o.paths,h);void 0===A?u.push({insertAt:i.index+1+t.length,path:c(g.trim()),line:m,tag:t}):d.push({line:m,reason:A,expression:l(g)})}return{candidates:u,refusals:d}}($,{paths:h});if(0===m.length)return{code:e,refusals:A};const b=m.map(e=>({...e,insertAt:e.insertAt+p})),w=!0!==o.allowPublicBindings,x=function(e,t,n=u){let s=e;for(const e of[...t].sort((e,t)=>t.insertAt-e.insertAt))s=`${s.slice(0,e.insertAt)} ${n(e)}${s.slice(e.insertAt)}`;return s}(e,b,e=>w?`{...${d}.bind('${e.path}')}`:u(e));return{code:w?g(x,p):x,refusals:A}}function w(t){const n=t.include??A,s=t.readFile??(t=>e(t,"utf8"));return{name:"payload-live-preview:annotate",enforce:"pre",load(e){if(!n(e))return null;const o=s(e),{code:r,refusals:i}=b(o,t);return i.length>0&&t.onRefusals?.(e,i),{code:r,map:null}}}}export{b as annotateSource,w as livePreviewAnnotate,v as splitFrontmatter};
2
2
  //# sourceMappingURL=annotate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"annotate.js","names":["ELEMENT","ONLY_INTERPOLATION","ANY_INTERPOLATION","MEMBER_CHAIN","FIELD_ATTRIBUTE","lineAt","source","index","line","position","charCodeAt","pathOf","chain","slice","indexOf","describe","expression","flat","replace","trim","length","refusalReason","paths","trimmed","test","path","has","fieldAttribute","candidate","HELPER","IMPORT_ALIAS","IMPORT_LINE","HELPER_LINE","FENCE","splitFrontmatter","code","startsWith","frontmatter","bodyOffset","end","withHelper","lines","isAstro","id","includes","endsWith","annotateSource","options","inventory","Set","entry","globals","collections","field","fields","add","annotatablePaths","body","candidates","refusals","match","matchAll","tag","attributes","content","only","exec","push","reason","insertAt","scanTemplate","shifted","map","gated","allowPublicBindings","rewritten","render","out","sort","a","b","applyAnnotations","livePreviewAnnotate","include","read","readFile","readFileSync","name","enforce","load","onRefusals"],"sources":["../src/codegen/annotate/scan.ts","../src/codegen/annotate/vite-plugin.ts","../src/codegen/annotate/index.ts"],"sourcesContent":["/**\n * Find the places in a template where a field value is printed on its own, and\n * decide — never guess — which of them can carry a binding.\n *\n * The shape this recognises is deliberately narrow, and it is the same in every\n * template language the package adapts:\n *\n * ```\n * <h1>{page.title}</h1> Astro, JSX, Svelte\n * ```\n *\n * An element whose entire content is one member expression, rooted at some\n * variable, whose remaining path is a field the schema actually has. Everything\n * else — an interpolation next to text, a call, a ternary, a path the schema\n * does not know, a component instead of an element — is reported instead, with\n * the reason. A wrong `data-payload-field` is worse than a missing one: it makes\n * the runtime write a value into markup that was never meant for it.\n *\n * Markup rather than an AST on purpose. Astro and Svelte templates are not\n * TypeScript, so ts-morph would only cover the JSX third of the problem, and\n * three parsers would be three chances to disagree about which shape is safe.\n * The shape above is unambiguous enough to scan for.\n */\n\n/** A place that can carry a binding, with the attribute it should get. @internal */\nexport interface AnnotationCandidate {\n /** Offset where the attribute is inserted, inside the opening tag. */\n readonly insertAt: number;\n /** The binding path, as `data-payload-field` must spell it. */\n readonly path: string;\n /** 1-based line, for the report. */\n readonly line: number;\n readonly tag: string;\n}\n\n/** A place that looks like an output but was left alone, and why. */\nexport interface AnnotationRefusal {\n readonly line: number;\n readonly reason: string;\n /** The source of the interpolation, trimmed, for the report. */\n readonly expression: string;\n}\n\nexport interface ScanResult {\n readonly candidates: readonly AnnotationCandidate[];\n readonly refusals: readonly AnnotationRefusal[];\n}\n\nexport interface ScanOptions {\n /** Field paths the schema has, exactly as a binding must spell them. */\n readonly paths: ReadonlySet<string>;\n}\n\n/**\n * `<tag attrs>content</tag>` with no element inside it, the closing tag\n * repeating the name. Only lowercase tags: an uppercase one is a component,\n * whose children are its own business.\n */\nconst ELEMENT = /<([a-z][a-z0-9-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([^<]*)<\\/\\1\\s*>/gu;\n\n/** The whole content is one interpolation, and nothing else. */\nconst ONLY_INTERPOLATION = /^\\s*\\{([^{}]*)\\}\\s*$/u;\n\n/** An interpolation somewhere in the content, which is a different case. */\nconst ANY_INTERPOLATION = /\\{([^{}]*)\\}/u;\n\n/** A plain member chain: `page.title`, `data.hero.eyebrow`. Nothing callable, nothing indexed. */\nconst MEMBER_CHAIN = /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)+$/u;\n\nconst FIELD_ATTRIBUTE = 'data-payload-field';\n\nfunction lineAt(source: string, index: number): number {\n let line = 1;\n for (let position = 0; position < index; position += 1) {\n if (source.charCodeAt(position) === 10) line += 1;\n }\n return line;\n}\n\n/**\n * The binding path a chain would address: everything after the root variable.\n * `page.hero.title` → `hero.title`, and a lone `title` is not a chain at all —\n * a bare identifier says nothing about which document it came from.\n */\nfunction pathOf(chain: string): string {\n return chain.slice(chain.indexOf('.') + 1);\n}\n\nfunction describe(expression: string): string {\n const flat = expression.replace(/\\s+/gu, ' ').trim();\n return flat.length > 60 ? `${flat.slice(0, 57)}…` : flat;\n}\n\n/** Why this interpolation cannot become a binding, in the words of the report. */\nfunction refusalReason(expression: string, paths: ReadonlySet<string>): string | undefined {\n const trimmed = expression.trim();\n if (trimmed.length === 0) return 'the element is empty';\n if (!MEMBER_CHAIN.test(trimmed)) {\n return 'not a plain field access — a call, an operator or an index cannot be traced to one field';\n }\n const path = pathOf(trimmed);\n if (!paths.has(path)) {\n return `the schema has no field \\`${path}\\`; the binding would name something that never arrives`;\n }\n return undefined;\n}\n\nexport function scanTemplate(source: string, options: ScanOptions): ScanResult {\n const candidates: AnnotationCandidate[] = [];\n const refusals: AnnotationRefusal[] = [];\n for (const match of source.matchAll(ELEMENT)) {\n const [, tag = '', attributes = '', content = ''] = match;\n const line = lineAt(source, match.index);\n if (attributes.includes(FIELD_ATTRIBUTE)) continue;\n const only = ONLY_INTERPOLATION.exec(content);\n if (only === null) {\n // A value printed beside a label — `Published {page.publishedAt}`. The\n // runtime writes an element's whole text, so a binding here would eat the\n // label; splitting the markup is a decision for whoever wrote it.\n const beside = ANY_INTERPOLATION.exec(content);\n if (beside !== null) {\n refusals.push({\n line,\n reason:\n 'the value is printed beside other content — a binding replaces the whole text, so the markup has to be split first',\n expression: describe(content),\n });\n }\n continue;\n }\n const expression = only[1] ?? '';\n const reason = refusalReason(expression, options.paths);\n if (reason !== undefined) {\n refusals.push({ line, reason, expression: describe(expression) });\n continue;\n }\n candidates.push({\n // After `<tag`, before whatever the element already carries.\n insertAt: match.index + 1 + tag.length,\n path: pathOf(expression.trim()),\n line,\n tag,\n });\n }\n return { candidates, refusals };\n}\n\n/** The attribute a candidate becomes: what `pll-codegen annotate` writes into a file. */\nexport function fieldAttribute(candidate: AnnotationCandidate): string {\n return `${FIELD_ATTRIBUTE}=\"${candidate.path}\"`;\n}\n\n/**\n * Apply the candidates back-to-front, so every earlier offset still holds.\n *\n * `render` decides what a candidate becomes, because the two callers write\n * different things from the same decision: the codemod writes the attribute\n * into the file a person reads, and the build-time annotator writes a call to\n * the authorization-bound helper. What may be annotated at all is decided in\n * one place — above — and never here.\n */\nexport function applyAnnotations(\n source: string,\n candidates: readonly AnnotationCandidate[],\n render: (candidate: AnnotationCandidate) => string = fieldAttribute,\n): string {\n let out = source;\n for (const candidate of [...candidates].sort((a, b) => b.insertAt - a.insertAt)) {\n out = `${out.slice(0, candidate.insertAt)} ${render(candidate)}${out.slice(candidate.insertAt)}`;\n }\n return out;\n}\n","/**\n * `livePreviewAnnotate()` — the annotator as a build step, so a template needs\n * no binding attributes written into it by hand.\n *\n * It rewrites the same shape `pll-codegen annotate` rewrites, decided by the\n * same scanner (`./scan`), into a call rather than a fixed attribute:\n *\n * ```astro\n * <h1>{page.title}</h1>\n * → <h1 {...__lpPreview.bind('title')}>{page.title}</h1>\n * ```\n *\n * The difference is who may see it. A fixed attribute is in the HTML of every\n * visitor and describes the content model to all of them; the call resolves per\n * request against the authorization the adapter published, so an unauthorized\n * response carries no `data-payload-*` at all (ADR 0006). A static build has no\n * request to resolve against and therefore emits nothing — `allowPublicBindings`\n * is how you say you want the attribute anyway, out loud.\n *\n * Astro only, for a reason rather than a lack of time: the rewrite needs a\n * template whose own scope can reach the request context, which frontmatter and\n * `Astro.locals` give and a Svelte or Vue component does not — there the value\n * would have to travel through `load` or a payload, where a function cannot go.\n *\n * Vite compatibility is a recorded fact, not a guess: `name`, `enforce` and\n * `load(id)` are the whole surface used here, and all three have been stable\n * since Vite 5. The majors that matter are in `quality/compat-matrix.json`,\n * which is what the tests read.\n *\n * `load` rather than `transform`, which is where a source rewrite would\n * normally go. Astro compiles `.astro` in a `transform` of its own, registered\n * `enforce: 'pre'` and ahead of anything a config contributes, so by the time\n * any user transform runs the markup is gone — measured, not assumed: a probe\n * plugin at `pre` received compiled output importing from Astro's own compiler\n * runtime, not the template. Every `load` runs before every `transform`, so\n * that is where the file is still a template. Reading it is what Rollup's\n * default loader would have done anyway.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { annotatablePaths, type AnnotatableSchema } from './index';\nimport { applyAnnotations, fieldAttribute, scanTemplate, type AnnotationRefusal } from './scan';\n\n/** The helper the rewritten markup calls, and the import that defines it. */\nconst HELPER = '__lpPreview';\nconst IMPORT_ALIAS = '__lpPreviewBindingsFromLocals';\nconst IMPORT_LINE = `import { previewBindingsFromLocals as ${IMPORT_ALIAS} } from 'payload-live-preview/server';`;\nconst HELPER_LINE = `const ${HELPER} = ${IMPORT_ALIAS}(Astro.locals);`;\nconst FENCE = '---';\n\nexport interface AnnotatePluginOptions {\n /** The schema, from `generateTypes().inventory` or the `--inventory` file. */\n readonly inventory: AnnotatableSchema;\n /**\n * Write the plain `data-payload-field` attribute instead of the gated call.\n * A statically built site has no request to authorize, so this is the only\n * way it can carry bindings — and it publishes the field names to everyone.\n * Default `false`.\n */\n readonly allowPublicBindings?: boolean;\n /** Which modules to transform. Default: every `.astro` file. */\n readonly include?: (id: string) => boolean;\n /** Called once per file with what the scanner would not annotate. */\n readonly onRefusals?: (file: string, refusals: readonly AnnotationRefusal[]) => void;\n /** Reads a template. Defaults to the file system; injected by tests. */\n readonly readFile?: (path: string) => string;\n}\n\n/** The slice of Vite's plugin shape this uses; three hooks, stable since Vite 5. */\nexport interface AnnotateVitePlugin {\n readonly name: string;\n readonly enforce: 'pre';\n load(id: string): { code: string; map: null } | null;\n}\n\n/** Everything before the second `---`, and where the template starts. @internal */\nexport function splitFrontmatter(code: string): {\n readonly frontmatter: string;\n readonly bodyOffset: number;\n} {\n if (!code.startsWith(FENCE)) return { frontmatter: '', bodyOffset: 0 };\n const end = code.indexOf(`\\n${FENCE}`, FENCE.length);\n if (end === -1) return { frontmatter: '', bodyOffset: 0 };\n const bodyOffset = end + 1 + FENCE.length;\n return { frontmatter: code.slice(FENCE.length, end), bodyOffset };\n}\n\n/** The frontmatter with the helper in it, whether or not the file had one. */\nfunction withHelper(code: string, bodyOffset: number): string {\n const lines = `${IMPORT_LINE}\\n${HELPER_LINE}\\n`;\n if (bodyOffset === 0) return `${FENCE}\\n${lines}${FENCE}\\n${code}`;\n return `${FENCE}\\n${lines}${code.slice(FENCE.length, bodyOffset)}${code.slice(bodyOffset)}`;\n}\n\n/**\n * A plain `.astro` path. Not a sub-request (`?astro&type=style`), not a virtual\n * module (`\\0…`): those are Astro's own, and loading them is not this plugin's\n * business.\n */\nconst isAstro = (id: string): boolean =>\n !id.startsWith('\\0') && !id.includes('?') && id.endsWith('.astro');\n\n/**\n * The transform on its own, so a test can call it without a bundler and a\n * caller can see exactly what a file becomes.\n * @internal\n */\nexport function annotateSource(\n code: string,\n options: AnnotatePluginOptions,\n): { readonly code: string; readonly refusals: readonly AnnotationRefusal[] } {\n const paths = annotatablePaths(options.inventory);\n const { bodyOffset } = splitFrontmatter(code);\n // Frontmatter is TypeScript, not markup: scanning it would match a template\n // written inside a string, and annotate something no browser ever sees.\n const body = code.slice(bodyOffset);\n const { candidates, refusals } = scanTemplate(body, { paths });\n if (candidates.length === 0) return { code, refusals };\n\n const shifted = candidates.map((candidate) => ({\n ...candidate,\n insertAt: candidate.insertAt + bodyOffset,\n }));\n const gated = options.allowPublicBindings !== true;\n const rewritten = applyAnnotations(code, shifted, (candidate) =>\n gated ? `{...${HELPER}.bind('${candidate.path}')}` : fieldAttribute(candidate),\n );\n return {\n code: gated ? withHelper(rewritten, bodyOffset) : rewritten,\n refusals,\n };\n}\n\n/**\n * ```js\n * // astro.config.mjs\n * vite: { plugins: [livePreviewAnnotate({ inventory })] }\n * ```\n */\nexport function livePreviewAnnotate(options: AnnotatePluginOptions): AnnotateVitePlugin {\n const include = options.include ?? isAstro;\n const read = options.readFile ?? ((path: string) => readFileSync(path, 'utf8'));\n return {\n name: 'payload-live-preview:annotate',\n enforce: 'pre',\n load(id) {\n if (!include(id)) return null;\n const source = read(id);\n const { code, refusals } = annotateSource(source, options);\n if (refusals.length > 0) options.onRefusals?.(id, refusals);\n // The unannotated source is still returned: this loader replaces the\n // default one, so declining would leave the module empty.\n return { code, map: null };\n },\n };\n}\n","/**\n * `pll-codegen annotate`: put `data-payload-field` where a template already\n * prints a field, and report every place it would have had to guess.\n *\n * The schema decides what exists — the same inventory `--inventory` writes, so\n * an annotation and a runtime binding spell a path the same way. What the tool\n * cannot attribute is listed, never invented: an unbound field costs an editor\n * one invisible edit, a wrongly bound one writes a value into the wrong element\n * on every keystroke.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { relative } from 'node:path';\nimport type { PreviewInventory } from '../inventory';\nimport {\n applyAnnotations,\n scanTemplate,\n type AnnotationCandidate,\n type AnnotationRefusal,\n} from './scan';\n\nexport type { AnnotationCandidate, AnnotationRefusal } from './scan';\n\n/** Templates this scanner understands; a `.vue` template uses `{{ … }}` and is not one yet. @internal */\nexport const ANNOTATABLE_EXTENSIONS: readonly string[] = ['.astro', '.jsx', '.tsx', '.svelte'];\n\n/** @internal */\nexport interface AnnotateFileResult {\n readonly file: string;\n /** The annotated source; identical to the input when nothing was added. */\n readonly code: string;\n readonly annotations: readonly AnnotationCandidate[];\n readonly refusals: readonly AnnotationRefusal[];\n readonly changed: boolean;\n}\n\n/** @internal */\nexport interface AnnotateResult {\n readonly files: readonly AnnotateFileResult[];\n readonly annotationCount: number;\n readonly refusalCount: number;\n /** Whether the files were written; `false` is a dry run. */\n readonly written: boolean;\n}\n\n/** @internal */\nexport interface AnnotateOptions {\n /** Template files to read. */\n readonly files: readonly string[];\n /** The schema's addressable paths, from `generateTypes().inventory`. */\n readonly inventory: PreviewInventory;\n /** Write the annotated sources back. Without it nothing is touched. */\n readonly write?: boolean;\n /** Resolves the paths in the report. Defaults to `process.cwd()`. */\n readonly cwd?: string;\n /** Reader/writer injection for tests. */\n readonly io?: {\n readonly read: (path: string) => Promise<string>;\n readonly write: (path: string, content: string) => Promise<void>;\n };\n}\n\n/**\n * The part of an inventory the annotator reads: the paths, and nothing else.\n *\n * `PreviewInventory` from `generateTypes()` satisfies it, and so does an object\n * a project writes by hand. Naming the minimum keeps the build plugin's public\n * surface free of the extracted-field union behind the full inventory — a dozen\n * types a consumer of a Vite plugin has no reason to meet.\n */\nexport interface AnnotatableSchema {\n readonly globals: readonly { readonly fields: readonly { readonly path: string }[] }[];\n readonly collections: readonly { readonly fields: readonly { readonly path: string }[] }[];\n}\n\n/**\n * Every path the schema can address, in one set.\n *\n * An array's items are spelled `slides.*.caption` in the inventory because that\n * is how the runtime resolves them; a template writes `slide.caption` inside a\n * loop, where nothing connects `slide` to `slides`. Those paths are therefore\n * kept out: the scanner would otherwise match a name that only looks right.\n * @internal\n */\nexport function annotatablePaths(inventory: AnnotatableSchema): ReadonlySet<string> {\n const paths = new Set<string>();\n for (const entry of [...inventory.globals, ...inventory.collections]) {\n for (const field of entry.fields) {\n if (!field.path.includes('*')) paths.add(field.path);\n }\n }\n return paths;\n}\n\n/** @internal */\nexport async function annotateTemplates(options: AnnotateOptions): Promise<AnnotateResult> {\n const read = options.io?.read ?? ((path: string) => readFile(path, 'utf8'));\n const write =\n options.io?.write ?? ((path: string, content: string) => writeFile(path, content, 'utf8'));\n const root = options.cwd ?? process.cwd();\n const paths = annotatablePaths(options.inventory);\n const files: AnnotateFileResult[] = [];\n\n for (const file of options.files) {\n const source = await read(file);\n const { candidates, refusals } = scanTemplate(source, { paths });\n const code = candidates.length === 0 ? source : applyAnnotations(source, candidates);\n const changed = code !== source;\n if (changed && options.write === true) await write(file, code);\n files.push({\n file: relative(root, file) || file,\n code,\n annotations: candidates,\n refusals,\n changed,\n });\n }\n\n return {\n files,\n annotationCount: files.reduce((total, file) => total + file.annotations.length, 0),\n refusalCount: files.reduce((total, file) => total + file.refusals.length, 0),\n written: options.write === true,\n };\n}\n\n/** The report, one line per annotation and per refusal, in file order. @internal */\nexport function formatAnnotateReport(result: AnnotateResult): string {\n const lines: string[] = [];\n for (const file of result.files) {\n if (file.annotations.length === 0 && file.refusals.length === 0) continue;\n lines.push(file.file);\n for (const annotation of file.annotations) {\n lines.push(` ${String(annotation.line)}: <${annotation.tag}> → ${annotation.path}`);\n }\n for (const refusal of file.refusals) {\n lines.push(\n ` ${String(refusal.line)}: left alone — ${refusal.reason} (${refusal.expression})`,\n );\n }\n }\n const verb = result.written ? 'annotated' : 'would annotate';\n lines.push(\n `${verb} ${String(result.annotationCount)} element(s); ` +\n `${String(result.refusalCount)} left for a human`,\n );\n return `${lines.join('\\n')}\\n`;\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"annotate.js","names":["ELEMENT","ONLY_INTERPOLATION","ANY_INTERPOLATION","MEMBER_CHAIN","FIELD_ATTRIBUTE","LOOP_VARIABLES","lineAt","source","index","line","position","charCodeAt","pathOf","chain","slice","indexOf","describe","expression","flat","replace","trim","length","refusalReason","paths","items","trimmed","test","path","root","has","fieldAttribute","candidate","HELPER","IMPORT_ALIAS","IMPORT_LINE","HELPER_LINE","FENCE","splitFrontmatter","code","startsWith","frontmatter","bodyOffset","end","withHelper","lines","isAstro","id","includes","endsWith","annotateSource","options","inventory","Set","entry","globals","collections","field","fields","add","annotatablePaths","body","candidates","refusals","names","pattern","match","matchAll","loopVariables","tag","attributes","content","only","exec","push","reason","insertAt","scanTemplate","shifted","map","gated","allowPublicBindings","rewritten","render","out","sort","a","b","applyAnnotations","livePreviewAnnotate","include","read","readFile","readFileSync","name","enforce","load","onRefusals"],"sources":["../src/codegen/annotate/scan.ts","../src/codegen/annotate/vite-plugin.ts","../src/codegen/annotate/index.ts"],"sourcesContent":["/**\n * Find the places in a template where a field value is printed on its own, and\n * decide — never guess — which of them can carry a binding.\n *\n * The shape this recognises is deliberately narrow, and it is the same in every\n * template language the package adapts:\n *\n * ```\n * <h1>{page.title}</h1> Astro, JSX, Svelte\n * ```\n *\n * An element whose entire content is one member expression, rooted at some\n * variable, whose remaining path is a field the schema actually has. Everything\n * else — an interpolation next to text, a call, a ternary, a path the schema\n * does not know, a component instead of an element — is reported instead, with\n * the reason. A wrong `data-payload-field` is worse than a missing one: it makes\n * the runtime write a value into markup that was never meant for it.\n *\n * Markup rather than an AST on purpose. Astro and Svelte templates are not\n * TypeScript, so ts-morph would only cover the JSX third of the problem, and\n * three parsers would be three chances to disagree about which shape is safe.\n * The shape above is unambiguous enough to scan for.\n */\n\n/** A place that can carry a binding, with the attribute it should get. @internal */\nexport interface AnnotationCandidate {\n /** Offset where the attribute is inserted, inside the opening tag. */\n readonly insertAt: number;\n /** The binding path, as `data-payload-field` must spell it. */\n readonly path: string;\n /** 1-based line, for the report. */\n readonly line: number;\n readonly tag: string;\n}\n\n/** A place that looks like an output but was left alone, and why. */\nexport interface AnnotationRefusal {\n readonly line: number;\n readonly reason: string;\n /** The source of the interpolation, trimmed, for the report. */\n readonly expression: string;\n}\n\nexport interface ScanResult {\n readonly candidates: readonly AnnotationCandidate[];\n readonly refusals: readonly AnnotationRefusal[];\n}\n\nexport interface ScanOptions {\n /** Field paths the schema has, exactly as a binding must spell them. */\n readonly paths: ReadonlySet<string>;\n}\n\n/**\n * `<tag attrs>content</tag>` with no element inside it, the closing tag\n * repeating the name. Only lowercase tags: an uppercase one is a component,\n * whose children are its own business.\n */\nconst ELEMENT = /<([a-z][a-z0-9-]*)((?:\"[^\"]*\"|'[^']*'|[^>\"'])*)>([^<]*)<\\/\\1\\s*>/gu;\n\n/** The whole content is one interpolation, and nothing else. */\nconst ONLY_INTERPOLATION = /^\\s*\\{([^{}]*)\\}\\s*$/u;\n\n/** An interpolation somewhere in the content, which is a different case. */\nconst ANY_INTERPOLATION = /\\{([^{}]*)\\}/u;\n\n/** A plain member chain: `page.title`, `data.hero.eyebrow`. Nothing callable, nothing indexed. */\nconst MEMBER_CHAIN = /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)+$/u;\n\nconst FIELD_ATTRIBUTE = 'data-payload-field';\n\n/**\n * Where a template names one array item at a time: a `.map`, `.flatMap` or\n * `.forEach` callback's first parameter, a `for … of` variable, a Svelte\n * `{#each … as item}`. A chain rooted at such a name prints an item, and the\n * item's `title` is not the document's `title`, however alike the paths look.\n * A name bound this way anywhere in the file is treated as an item everywhere:\n * refusing one real field access costs a line in the report, binding an item\n * costs the preview.\n */\nconst LOOP_VARIABLES: readonly RegExp[] = [\n /\\.(?:map|flatMap|forEach)\\(\\s*(?:async\\s+)?\\(?\\s*([A-Za-z_$][\\w$]*)/gu,\n /\\bfor\\s*\\(\\s*(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s+of\\b/gu,\n /\\{#each\\s[^}]*?\\sas\\s+([A-Za-z_$][\\w$]*)/gu,\n];\n\nfunction loopVariables(source: string): ReadonlySet<string> {\n const names = new Set<string>();\n for (const pattern of LOOP_VARIABLES) {\n for (const match of source.matchAll(pattern)) {\n if (match[1] !== undefined) names.add(match[1]);\n }\n }\n return names;\n}\n\nfunction lineAt(source: string, index: number): number {\n let line = 1;\n for (let position = 0; position < index; position += 1) {\n if (source.charCodeAt(position) === 10) line += 1;\n }\n return line;\n}\n\n/**\n * The binding path a chain would address: everything after the root variable.\n * `page.hero.title` → `hero.title`, and a lone `title` is not a chain at all —\n * a bare identifier says nothing about which document it came from.\n */\nfunction pathOf(chain: string): string {\n return chain.slice(chain.indexOf('.') + 1);\n}\n\nfunction describe(expression: string): string {\n const flat = expression.replace(/\\s+/gu, ' ').trim();\n return flat.length > 60 ? `${flat.slice(0, 57)}…` : flat;\n}\n\n/** Why this interpolation cannot become a binding, in the words of the report. */\nfunction refusalReason(\n expression: string,\n paths: ReadonlySet<string>,\n items: ReadonlySet<string>,\n): string | undefined {\n const trimmed = expression.trim();\n if (trimmed.length === 0) return 'the element is empty';\n if (!MEMBER_CHAIN.test(trimmed)) {\n return 'not a plain field access — a call, an operator or an index cannot be traced to one field';\n }\n const path = pathOf(trimmed);\n const root = trimmed.slice(0, trimmed.indexOf('.'));\n if (items.has(root)) {\n return `\\`${root}\\` is a loop item — a binding names a field of the document, and \\`${path}\\` there is a different value, or none`;\n }\n if (!paths.has(path)) {\n return `the schema has no field \\`${path}\\`; the binding would name something that never arrives`;\n }\n return undefined;\n}\n\nexport function scanTemplate(source: string, options: ScanOptions): ScanResult {\n const candidates: AnnotationCandidate[] = [];\n const refusals: AnnotationRefusal[] = [];\n const items = loopVariables(source);\n for (const match of source.matchAll(ELEMENT)) {\n const [, tag = '', attributes = '', content = ''] = match;\n const line = lineAt(source, match.index);\n if (attributes.includes(FIELD_ATTRIBUTE)) continue;\n const only = ONLY_INTERPOLATION.exec(content);\n if (only === null) {\n // A value printed beside a label — `Published {page.publishedAt}`. The\n // runtime writes an element's whole text, so a binding here would eat the\n // label; splitting the markup is a decision for whoever wrote it.\n const beside = ANY_INTERPOLATION.exec(content);\n if (beside !== null) {\n refusals.push({\n line,\n reason:\n 'the value is printed beside other content — a binding replaces the whole text, so the markup has to be split first',\n expression: describe(content),\n });\n }\n continue;\n }\n const expression = only[1] ?? '';\n const reason = refusalReason(expression, options.paths, items);\n if (reason !== undefined) {\n refusals.push({ line, reason, expression: describe(expression) });\n continue;\n }\n candidates.push({\n // After `<tag`, before whatever the element already carries.\n insertAt: match.index + 1 + tag.length,\n path: pathOf(expression.trim()),\n line,\n tag,\n });\n }\n return { candidates, refusals };\n}\n\n/** The attribute a candidate becomes: what `pll-codegen annotate` writes into a file. */\nexport function fieldAttribute(candidate: AnnotationCandidate): string {\n return `${FIELD_ATTRIBUTE}=\"${candidate.path}\"`;\n}\n\n/**\n * Apply the candidates back-to-front, so every earlier offset still holds.\n *\n * `render` decides what a candidate becomes, because the two callers write\n * different things from the same decision: the codemod writes the attribute\n * into the file a person reads, and the build-time annotator writes a call to\n * the authorization-bound helper. What may be annotated at all is decided in\n * one place — above — and never here.\n */\nexport function applyAnnotations(\n source: string,\n candidates: readonly AnnotationCandidate[],\n render: (candidate: AnnotationCandidate) => string = fieldAttribute,\n): string {\n let out = source;\n for (const candidate of [...candidates].sort((a, b) => b.insertAt - a.insertAt)) {\n out = `${out.slice(0, candidate.insertAt)} ${render(candidate)}${out.slice(candidate.insertAt)}`;\n }\n return out;\n}\n","/**\n * `livePreviewAnnotate()` — the annotator as a build step, so a template needs\n * no binding attributes written into it by hand.\n *\n * It rewrites the same shape `pll-codegen annotate` rewrites, decided by the\n * same scanner (`./scan`), into a call rather than a fixed attribute:\n *\n * ```astro\n * <h1>{page.title}</h1>\n * → <h1 {...__lpPreview.bind('title')}>{page.title}</h1>\n * ```\n *\n * The difference is who may see it. A fixed attribute is in the HTML of every\n * visitor and describes the content model to all of them; the call resolves per\n * request against the authorization the adapter published, so an unauthorized\n * response carries no `data-payload-*` at all (ADR 0006). A static build has no\n * request to resolve against and therefore emits nothing — `allowPublicBindings`\n * is how you say you want the attribute anyway, out loud.\n *\n * Astro only, for a reason rather than a lack of time: the rewrite needs a\n * template whose own scope can reach the request context, which frontmatter and\n * `Astro.locals` give and a Svelte or Vue component does not — there the value\n * would have to travel through `load` or a payload, where a function cannot go.\n *\n * Vite compatibility is a recorded fact, not a guess: `name`, `enforce` and\n * `load(id)` are the whole surface used here, and all three have been stable\n * since Vite 5. The majors that matter are in `quality/compat-matrix.json`,\n * which is what the tests read.\n *\n * `load` rather than `transform`, which is where a source rewrite would\n * normally go. Astro compiles `.astro` in a `transform` of its own, registered\n * `enforce: 'pre'` and ahead of anything a config contributes, so by the time\n * any user transform runs the markup is gone — measured, not assumed: a probe\n * plugin at `pre` received compiled output importing from Astro's own compiler\n * runtime, not the template. Every `load` runs before every `transform`, so\n * that is where the file is still a template. Reading it is what Rollup's\n * default loader would have done anyway.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { annotatablePaths, type AnnotatableSchema } from './index';\nimport { applyAnnotations, fieldAttribute, scanTemplate, type AnnotationRefusal } from './scan';\n\n/** The helper the rewritten markup calls, and the import that defines it. */\nconst HELPER = '__lpPreview';\nconst IMPORT_ALIAS = '__lpPreviewBindingsFromLocals';\nconst IMPORT_LINE = `import { previewBindingsFromLocals as ${IMPORT_ALIAS} } from 'payload-live-preview/server';`;\nconst HELPER_LINE = `const ${HELPER} = ${IMPORT_ALIAS}(Astro.locals);`;\nconst FENCE = '---';\n\nexport interface AnnotatePluginOptions {\n /** The schema, from `generateTypes().inventory` or the `--inventory` file. */\n readonly inventory: AnnotatableSchema;\n /**\n * Write the plain `data-payload-field` attribute instead of the gated call.\n * A statically built site has no request to authorize, so this is the only\n * way it can carry bindings — and it publishes the field names to everyone.\n * Default `false`.\n */\n readonly allowPublicBindings?: boolean;\n /** Which modules to transform. Default: every `.astro` file. */\n readonly include?: (id: string) => boolean;\n /** Called once per file with what the scanner would not annotate. */\n readonly onRefusals?: (file: string, refusals: readonly AnnotationRefusal[]) => void;\n /** Reads a template. Defaults to the file system; injected by tests. */\n readonly readFile?: (path: string) => string;\n}\n\n/** The slice of Vite's plugin shape this uses; three hooks, stable since Vite 5. */\nexport interface AnnotateVitePlugin {\n readonly name: string;\n readonly enforce: 'pre';\n load(id: string): { code: string; map: null } | null;\n}\n\n/** Everything before the second `---`, and where the template starts. @internal */\nexport function splitFrontmatter(code: string): {\n readonly frontmatter: string;\n readonly bodyOffset: number;\n} {\n if (!code.startsWith(FENCE)) return { frontmatter: '', bodyOffset: 0 };\n const end = code.indexOf(`\\n${FENCE}`, FENCE.length);\n if (end === -1) return { frontmatter: '', bodyOffset: 0 };\n const bodyOffset = end + 1 + FENCE.length;\n return { frontmatter: code.slice(FENCE.length, end), bodyOffset };\n}\n\n/** The frontmatter with the helper in it, whether or not the file had one. */\nfunction withHelper(code: string, bodyOffset: number): string {\n const lines = `${IMPORT_LINE}\\n${HELPER_LINE}\\n`;\n if (bodyOffset === 0) return `${FENCE}\\n${lines}${FENCE}\\n${code}`;\n return `${FENCE}\\n${lines}${code.slice(FENCE.length, bodyOffset)}${code.slice(bodyOffset)}`;\n}\n\n/**\n * A plain `.astro` path. Not a sub-request (`?astro&type=style`), not a virtual\n * module (`\\0…`): those are Astro's own, and loading them is not this plugin's\n * business.\n */\nconst isAstro = (id: string): boolean =>\n !id.startsWith('\\0') && !id.includes('?') && id.endsWith('.astro');\n\n/**\n * The transform on its own, so a test can call it without a bundler and a\n * caller can see exactly what a file becomes.\n * @internal\n */\nexport function annotateSource(\n code: string,\n options: AnnotatePluginOptions,\n): { readonly code: string; readonly refusals: readonly AnnotationRefusal[] } {\n const paths = annotatablePaths(options.inventory);\n const { bodyOffset } = splitFrontmatter(code);\n // Frontmatter is TypeScript, not markup: scanning it would match a template\n // written inside a string, and annotate something no browser ever sees.\n const body = code.slice(bodyOffset);\n const { candidates, refusals } = scanTemplate(body, { paths });\n if (candidates.length === 0) return { code, refusals };\n\n const shifted = candidates.map((candidate) => ({\n ...candidate,\n insertAt: candidate.insertAt + bodyOffset,\n }));\n const gated = options.allowPublicBindings !== true;\n const rewritten = applyAnnotations(code, shifted, (candidate) =>\n gated ? `{...${HELPER}.bind('${candidate.path}')}` : fieldAttribute(candidate),\n );\n return {\n code: gated ? withHelper(rewritten, bodyOffset) : rewritten,\n refusals,\n };\n}\n\n/**\n * ```js\n * // astro.config.mjs\n * vite: { plugins: [livePreviewAnnotate({ inventory })] }\n * ```\n */\nexport function livePreviewAnnotate(options: AnnotatePluginOptions): AnnotateVitePlugin {\n const include = options.include ?? isAstro;\n const read = options.readFile ?? ((path: string) => readFileSync(path, 'utf8'));\n return {\n name: 'payload-live-preview:annotate',\n enforce: 'pre',\n load(id) {\n if (!include(id)) return null;\n const source = read(id);\n const { code, refusals } = annotateSource(source, options);\n if (refusals.length > 0) options.onRefusals?.(id, refusals);\n // The unannotated source is still returned: this loader replaces the\n // default one, so declining would leave the module empty.\n return { code, map: null };\n },\n };\n}\n","/**\n * `pll-codegen annotate`: put `data-payload-field` where a template already\n * prints a field, and report every place it would have had to guess.\n *\n * The schema decides what exists — the same inventory `--inventory` writes, so\n * an annotation and a runtime binding spell a path the same way. What the tool\n * cannot attribute is listed, never invented: an unbound field costs an editor\n * one invisible edit, a wrongly bound one writes a value into the wrong element\n * on every keystroke.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { relative } from 'node:path';\nimport type { PreviewInventory } from '../inventory';\nimport {\n applyAnnotations,\n scanTemplate,\n type AnnotationCandidate,\n type AnnotationRefusal,\n} from './scan';\n\nexport type { AnnotationCandidate, AnnotationRefusal } from './scan';\n\n/** Templates this scanner understands; a `.vue` template uses `{{ … }}` and is not one yet. @internal */\nexport const ANNOTATABLE_EXTENSIONS: readonly string[] = ['.astro', '.jsx', '.tsx', '.svelte'];\n\n/** @internal */\nexport interface AnnotateFileResult {\n readonly file: string;\n /** The annotated source; identical to the input when nothing was added. */\n readonly code: string;\n readonly annotations: readonly AnnotationCandidate[];\n readonly refusals: readonly AnnotationRefusal[];\n readonly changed: boolean;\n}\n\n/** @internal */\nexport interface AnnotateResult {\n readonly files: readonly AnnotateFileResult[];\n readonly annotationCount: number;\n readonly refusalCount: number;\n /** Whether the files were written; `false` is a dry run. */\n readonly written: boolean;\n}\n\n/** @internal */\nexport interface AnnotateOptions {\n /** Template files to read. */\n readonly files: readonly string[];\n /** The schema's addressable paths, from `generateTypes().inventory`. */\n readonly inventory: PreviewInventory;\n /** Write the annotated sources back. Without it nothing is touched. */\n readonly write?: boolean;\n /** Resolves the paths in the report. Defaults to `process.cwd()`. */\n readonly cwd?: string;\n /** Reader/writer injection for tests. */\n readonly io?: {\n readonly read: (path: string) => Promise<string>;\n readonly write: (path: string, content: string) => Promise<void>;\n };\n}\n\n/**\n * The part of an inventory the annotator reads: the paths, and nothing else.\n *\n * `PreviewInventory` from `generateTypes()` satisfies it, and so does an object\n * a project writes by hand. Naming the minimum keeps the build plugin's public\n * surface free of the extracted-field union behind the full inventory — a dozen\n * types a consumer of a Vite plugin has no reason to meet.\n */\nexport interface AnnotatableSchema {\n readonly globals: readonly { readonly fields: readonly { readonly path: string }[] }[];\n readonly collections: readonly { readonly fields: readonly { readonly path: string }[] }[];\n}\n\n/**\n * Every path the schema can address, in one set.\n *\n * An array's items are spelled `slides.*.caption` in the inventory because that\n * is how the runtime resolves them; a template writes `slide.caption` inside a\n * loop, where nothing connects `slide` to `slides`. Those paths are therefore\n * kept out: the scanner would otherwise match a name that only looks right.\n * @internal\n */\nexport function annotatablePaths(inventory: AnnotatableSchema): ReadonlySet<string> {\n const paths = new Set<string>();\n for (const entry of [...inventory.globals, ...inventory.collections]) {\n for (const field of entry.fields) {\n if (!field.path.includes('*')) paths.add(field.path);\n }\n }\n return paths;\n}\n\n/** @internal */\nexport async function annotateTemplates(options: AnnotateOptions): Promise<AnnotateResult> {\n const read = options.io?.read ?? ((path: string) => readFile(path, 'utf8'));\n const write =\n options.io?.write ?? ((path: string, content: string) => writeFile(path, content, 'utf8'));\n const root = options.cwd ?? process.cwd();\n const paths = annotatablePaths(options.inventory);\n const files: AnnotateFileResult[] = [];\n\n for (const file of options.files) {\n const source = await read(file);\n const { candidates, refusals } = scanTemplate(source, { paths });\n const code = candidates.length === 0 ? source : applyAnnotations(source, candidates);\n const changed = code !== source;\n if (changed && options.write === true) await write(file, code);\n files.push({\n file: relative(root, file) || file,\n code,\n annotations: candidates,\n refusals,\n changed,\n });\n }\n\n return {\n files,\n annotationCount: files.reduce((total, file) => total + file.annotations.length, 0),\n refusalCount: files.reduce((total, file) => total + file.refusals.length, 0),\n written: options.write === true,\n };\n}\n\n/** The report, one line per annotation and per refusal, in file order. @internal */\nexport function formatAnnotateReport(result: AnnotateResult): string {\n const lines: string[] = [];\n for (const file of result.files) {\n if (file.annotations.length === 0 && file.refusals.length === 0) continue;\n lines.push(file.file);\n for (const annotation of file.annotations) {\n lines.push(` ${String(annotation.line)}: <${annotation.tag}> → ${annotation.path}`);\n }\n for (const refusal of file.refusals) {\n lines.push(\n ` ${String(refusal.line)}: left alone — ${refusal.reason} (${refusal.expression})`,\n );\n }\n }\n const verb = result.written ? 'annotated' : 'would annotate';\n lines.push(\n `${verb} ${String(result.annotationCount)} element(s); ` +\n `${String(result.refusalCount)} left for a human`,\n );\n return `${lines.join('\\n')}\\n`;\n}\n"],"mappings":"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","ignoreList":[]}