payload-live-preview 1.8.0 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +301 -93
- package/dist/adapters/astro/PreviewBoundary.astro +66 -0
- package/dist/adapters/astro/RichText.astro +6 -0
- package/dist/adapters/astro/index.d.ts +165 -134
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +4 -9
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.d.ts +13 -80
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.d.ts +46 -106
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.d.ts +10 -75
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/client.cjs +2 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +747 -0
- package/dist/client.d.ts +747 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/codegen-astro.d.ts +5 -27
- package/dist/codegen-astro.js +1 -1
- package/dist/codegen-astro.js.map +1 -1
- package/dist/codegen-cli.d.ts +3 -16
- package/dist/codegen-cli.js +1 -1
- package/dist/codegen-cli.js.map +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +37 -124
- package/dist/codegen.d.ts +37 -124
- package/dist/codegen.js +1 -1
- package/dist/codegen.js.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +647 -1100
- package/dist/core.d.ts +647 -1100
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/doctor-cli.d.ts +3 -28
- package/dist/doctor-cli.js +1 -1
- package/dist/doctor-cli.js.map +1 -1
- package/dist/doctor.d.ts +16 -32
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fragment.cjs +2 -0
- package/dist/fragment.cjs.map +1 -0
- package/dist/fragment.d.cts +333 -0
- package/dist/fragment.d.ts +333 -0
- package/dist/fragment.js +2 -0
- package/dist/fragment.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +885 -1531
- package/dist/index.d.ts +885 -1531
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lexical.cjs +2 -0
- package/dist/lexical.cjs.map +1 -0
- package/dist/lexical.d.cts +90 -0
- package/dist/lexical.d.ts +90 -0
- package/dist/lexical.js +2 -0
- package/dist/lexical.js.map +1 -0
- package/dist/middleware-CEPsY1P7.d.ts +40 -0
- package/dist/migrate.d.ts +73 -0
- package/dist/migrate.js +2 -0
- package/dist/migrate.js.map +1 -0
- package/dist/options-CEzzfHJu.d.ts +197 -0
- package/dist/payload.cjs +1 -1
- package/dist/payload.cjs.map +1 -1
- package/dist/payload.d.cts +11 -80
- package/dist/payload.d.ts +11 -80
- package/dist/payload.js +1 -1
- package/dist/payload.js.map +1 -1
- package/dist/plugins.cjs +2 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +509 -0
- package/dist/plugins.d.ts +509 -0
- package/dist/plugins.js +2 -0
- package/dist/plugins.js.map +1 -0
- package/dist/{probe-tMHdim5y.d.ts → probe-CYtvj_Lm.d.ts} +43 -77
- package/dist/server.cjs +2 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +435 -0
- package/dist/server.d.ts +435 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/structural.cjs +3 -0
- package/dist/structural.cjs.map +1 -0
- package/dist/structural.d.cts +243 -0
- package/dist/structural.d.ts +243 -0
- package/dist/structural.js +3 -0
- package/dist/structural.js.map +1 -0
- package/package.json +86 -7
- package/dist/middleware-DD4Hwomt.d.ts +0 -189
package/dist/core.d.ts
CHANGED
|
@@ -1,52 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* trusted origin and fires a callback when the heartbeat times out.
|
|
6
|
-
*
|
|
7
|
-
* The heartbeat is reset on every valid message and considered dead
|
|
8
|
-
* after `timeoutMs` of silence. The host typically responds by
|
|
9
|
-
* re-sending the `ready` handshake to attempt reconnection.
|
|
10
|
-
*
|
|
11
|
-
* @module @core/state
|
|
12
|
-
*/
|
|
1
|
+
/** The package version, read from package.json so the entries and the inline runtime cannot drift. */
|
|
2
|
+
declare const VERSION: string;
|
|
3
|
+
|
|
4
|
+
/** Connection status and the heartbeat that decides a disconnect. */
|
|
13
5
|
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected';
|
|
14
6
|
|
|
15
7
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* internal state the runtime does not otherwise expose.
|
|
22
|
-
*
|
|
23
|
-
* A snapshot is a read of what the runtime already holds. It discloses nothing
|
|
24
|
-
* that is not already observable on the page: the trusted origins are inside
|
|
25
|
-
* the injected script, the field names are `data-payload-field` attributes in
|
|
26
|
-
* the DOM, and the counters are derived from those. It is therefore available
|
|
27
|
-
* in production rather than gated to development builds — a preview that
|
|
28
|
-
* misbehaves only on the deployed site is exactly the case where the
|
|
29
|
-
* information is worth having, and a development-only gate would withhold it
|
|
30
|
-
* there.
|
|
31
|
-
*
|
|
32
|
-
* Nothing here is transmitted anywhere. Reading a snapshot performs no I/O.
|
|
33
|
-
*
|
|
34
|
-
* @module @core/inspection/types
|
|
8
|
+
* Shape of the `inspect()` snapshot. It only re-reads what the page already
|
|
9
|
+
* shows — origins from the injected script, field names from the DOM,
|
|
10
|
+
* counters derived from both — so it stays available in production, where a
|
|
11
|
+
* misbehaving preview is exactly the case worth diagnosing. Reading one
|
|
12
|
+
* performs no I/O and transmits nothing.
|
|
35
13
|
*/
|
|
36
14
|
|
|
37
15
|
/** Origin trust as the runtime currently sees it. */
|
|
38
16
|
interface InspectionOrigins {
|
|
39
|
-
/**
|
|
40
|
-
* Origins the runtime is willing to accept messages from, as configured.
|
|
41
|
-
* An empty list means no explicit allow-list was given, which is the
|
|
42
|
-
* condition under which referrer and localhost heuristics decide instead.
|
|
43
|
-
*/
|
|
17
|
+
/** Configured origins; empty means the referrer and localhost heuristics decide instead. */
|
|
44
18
|
readonly trusted: readonly string[];
|
|
45
|
-
/**
|
|
46
|
-
* The origin the runtime locked onto after its first accepted update, or
|
|
47
|
-
* `undefined` while still unlocked. Once locked, every other origin is
|
|
48
|
-
* refused for the rest of the session.
|
|
49
|
-
*/
|
|
19
|
+
/** The origin locked onto by the first accepted update; every other is then refused. */
|
|
50
20
|
readonly locked: string | undefined;
|
|
51
21
|
}
|
|
52
22
|
/** Protocol versions and the capabilities they enable. */
|
|
@@ -59,6 +29,10 @@ interface InspectionProtocol {
|
|
|
59
29
|
readonly negotiated: number;
|
|
60
30
|
/** Capability flags enabled at the negotiated version. */
|
|
61
31
|
readonly capabilities: readonly string[];
|
|
32
|
+
/** The subset of `capabilities` seen on the wire rather than granted by version. */
|
|
33
|
+
readonly observed: readonly string[];
|
|
34
|
+
/** The Payload profile the observed capabilities imply: `unknown`, `payload-2` or `payload-3`. */
|
|
35
|
+
readonly profile: string;
|
|
62
36
|
}
|
|
63
37
|
/** Revision accounting for the update pipeline. */
|
|
64
38
|
interface InspectionRevisions {
|
|
@@ -71,6 +45,19 @@ interface InspectionRevisions {
|
|
|
71
45
|
* when the last update is among the abandoned ones.
|
|
72
46
|
*/
|
|
73
47
|
readonly superseded: number;
|
|
48
|
+
/**
|
|
49
|
+
* Updates whose scheduled writes reached the DOM. What is neither
|
|
50
|
+
* superseded nor completed is in flight or was cancelled; a `superseded`
|
|
51
|
+
* that tracks `accepted` while `completed` stays low is the editor
|
|
52
|
+
* out-typing the pipeline.
|
|
53
|
+
*/
|
|
54
|
+
readonly completed: number;
|
|
55
|
+
/**
|
|
56
|
+
* Bindings not scheduled because their value was identical to the one last
|
|
57
|
+
* applied. Cumulative since start; always `0` unless `skipUnchanged` is on.
|
|
58
|
+
* A large number beside a small `accepted` is the optimisation working.
|
|
59
|
+
*/
|
|
60
|
+
readonly skippedUnchanged: number;
|
|
74
61
|
/** Revision currently in flight, or `undefined` when the pipeline is idle. */
|
|
75
62
|
readonly active: number | undefined;
|
|
76
63
|
}
|
|
@@ -83,22 +70,14 @@ interface InspectionBindings {
|
|
|
83
70
|
/** Those field names, sorted. */
|
|
84
71
|
readonly fieldNames: readonly string[];
|
|
85
72
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* to update: a name here is a markup problem, a name absent from here and
|
|
89
|
-
* from `fieldNames` was never sent.
|
|
73
|
+
* Bound fields some update carried no value for, so the binding kept its old
|
|
74
|
+
* text — why a binding can stay stale while its siblings update. Cumulative.
|
|
90
75
|
*/
|
|
76
|
+
readonly absentFields: readonly string[];
|
|
91
77
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* `orphanFields`, and the exact opposite of it: a binding with no value
|
|
95
|
-
* rather than a value with no binding.
|
|
96
|
-
*
|
|
97
|
-
* This is why a binding can stay stale while its siblings update. Without
|
|
98
|
-
* it the two cases are indistinguishable from the DOM, since neither
|
|
99
|
-
* leaves a trace.
|
|
78
|
+
* Field names that arrived but matched no binding — a markup problem.
|
|
79
|
+
* A name in neither this list nor `fieldNames` was never sent. Cumulative.
|
|
100
80
|
*/
|
|
101
|
-
readonly absentFields: readonly string[];
|
|
102
81
|
readonly orphanFields: readonly string[];
|
|
103
82
|
/** Whether `scopeBindingsByOwner` is active. */
|
|
104
83
|
readonly ownerScoped: boolean;
|
|
@@ -109,11 +88,7 @@ interface InspectionBindings {
|
|
|
109
88
|
interface InspectionScheduler {
|
|
110
89
|
/** Writes buffered for the next flush. */
|
|
111
90
|
readonly pending: number;
|
|
112
|
-
/**
|
|
113
|
-
* Writes held back because their element is offscreen and the binding count
|
|
114
|
-
* is above `visibilityGateThreshold`. These are applied when the element
|
|
115
|
-
* scrolls into view — never, on a page nobody scrolls.
|
|
116
|
-
*/
|
|
91
|
+
/** Writes held back offscreen above `visibilityGateThreshold`; applied when scrolled into view. */
|
|
117
92
|
readonly deferred: number;
|
|
118
93
|
/** The binding count above which the visibility gate starts deferring. */
|
|
119
94
|
readonly visibilityGateThreshold: number;
|
|
@@ -122,23 +97,13 @@ interface InspectionScheduler {
|
|
|
122
97
|
/** Stats of the most recent flush, or `undefined` if none has run. */
|
|
123
98
|
readonly lastFlush: {
|
|
124
99
|
readonly applied: number;
|
|
125
|
-
/**
|
|
126
|
-
* Field names the flush applied, in application order.
|
|
127
|
-
*
|
|
128
|
-
* `applied` is a count, and a count cannot separate "this binding was
|
|
129
|
-
* written" from "this binding was never scheduled" — a stale binding
|
|
130
|
-
* next to a non-zero count is consistent with both. The names can.
|
|
131
|
-
*/
|
|
100
|
+
/** Which fields the flush wrote; a count alone cannot say a binding was skipped. */
|
|
132
101
|
readonly appliedFields: readonly string[];
|
|
133
102
|
readonly deferred: number;
|
|
134
103
|
readonly durationMs: number;
|
|
135
104
|
} | undefined;
|
|
136
105
|
}
|
|
137
|
-
/**
|
|
138
|
-
* A point-in-time read of runtime state, for diagnosing a preview that is not
|
|
139
|
-
* behaving. Every field is a plain value; the snapshot holds no references
|
|
140
|
-
* into runtime internals and does not change after it is returned.
|
|
141
|
-
*/
|
|
106
|
+
/** A point-in-time read; plain values only, with no references into runtime internals. */
|
|
142
107
|
interface LivePreviewInspection {
|
|
143
108
|
/** Package version that produced this snapshot. */
|
|
144
109
|
readonly version: string;
|
|
@@ -153,65 +118,67 @@ interface LivePreviewInspection {
|
|
|
153
118
|
readonly scheduler: InspectionScheduler;
|
|
154
119
|
/** Field types with a registered renderer, sorted. */
|
|
155
120
|
readonly renderers: readonly string[];
|
|
121
|
+
/** Registered plugins and their live registrations; empty on a bare runtime. */
|
|
122
|
+
readonly plugins: readonly PluginInspection[];
|
|
123
|
+
/** Server-rendered fragment boundaries: whether a handler exists and what happened to renders. */
|
|
124
|
+
readonly fragments: InspectionFragments;
|
|
125
|
+
/** Route refreshes: whether a strategy exists, how many ran, failed, or were stopped by the loop guard. */
|
|
126
|
+
readonly route: InspectionRoute;
|
|
127
|
+
}
|
|
128
|
+
/** The route strategy as `inspect()` reports it. */
|
|
129
|
+
interface InspectionRoute {
|
|
130
|
+
readonly handler: boolean;
|
|
131
|
+
readonly refreshes: number;
|
|
132
|
+
readonly failed: number;
|
|
133
|
+
/** Second refresh requests for one revision, refused with LP0805. */
|
|
134
|
+
readonly loopStopped: number;
|
|
135
|
+
}
|
|
136
|
+
/** The fragment strategy as `inspect()` reports it. */
|
|
137
|
+
interface InspectionFragments {
|
|
138
|
+
/** Whether a fragment handler is configured; without one, boundaries are patched. */
|
|
139
|
+
readonly handler: boolean;
|
|
140
|
+
readonly inFlight: number;
|
|
141
|
+
readonly rendered: number;
|
|
142
|
+
readonly failed: number;
|
|
143
|
+
readonly superseded: number;
|
|
144
|
+
}
|
|
145
|
+
/** One plugin as `inspect().plugins` reports it. */
|
|
146
|
+
interface PluginInspection {
|
|
147
|
+
readonly name: string;
|
|
148
|
+
readonly version: string | undefined;
|
|
149
|
+
/** `initializing` while `init` runs, `active` afterwards, `tearing-down` while `destroy` runs. */
|
|
150
|
+
readonly state: 'initializing' | 'active' | 'tearing-down';
|
|
151
|
+
/** Live registrations owned by the plugin's scope, by kind. */
|
|
152
|
+
readonly registrations: {
|
|
153
|
+
readonly transforms: number;
|
|
154
|
+
readonly renderers: number;
|
|
155
|
+
readonly subscriptions: number;
|
|
156
|
+
readonly cleanups: number;
|
|
157
|
+
};
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
/**
|
|
159
|
-
* Payload
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* `window.postMessage`, verified against the shipped admin code
|
|
163
|
-
* (`@payloadcms/ui` → `elements/LivePreview/Window`):
|
|
164
|
-
*
|
|
165
|
-
* - Payload **3.x** sends `{ type, collectionSlug, globalSlug, data,
|
|
166
|
-
* locale, externallyUpdatedRelationship }` on every form edit —
|
|
167
|
-
* `data` holds **raw form values** (relationships as bare IDs) and
|
|
168
|
-
* there is **no** `fieldSchemaJSON`.
|
|
169
|
-
* - Payload **2.x** additionally sent `fieldSchemaJSON` with the
|
|
170
|
-
* first update (client-side merge era).
|
|
171
|
-
* - `previewToken` and `protocolVersion` are extensions of THIS
|
|
172
|
-
* library for custom admin senders; stock Payload never sends
|
|
173
|
-
* them and ignores them in our `ready` handshake.
|
|
174
|
-
*
|
|
175
|
-
* @module @/types/payload-protocol
|
|
176
|
-
*/
|
|
177
|
-
/**
|
|
178
|
-
* A `payload-live-preview` data update.
|
|
179
|
-
*
|
|
180
|
-
* `data` is the serialized document state. `fieldSchemaJSON`, when
|
|
181
|
-
* present (Payload 2.x or custom senders), allows the schema-driven
|
|
182
|
-
* engine to resolve field types without any DOM annotations.
|
|
161
|
+
* Payload's live-preview `postMessage` protocol as the shipped admin sends it:
|
|
162
|
+
* raw form values on every edit, no schema. `previewToken` and
|
|
163
|
+
* `protocolVersion` are extensions of this library for custom senders.
|
|
183
164
|
*/
|
|
184
165
|
interface PayloadLivePreviewMessage {
|
|
185
166
|
readonly type: 'payload-live-preview';
|
|
186
167
|
readonly data?: Record<string, unknown>;
|
|
168
|
+
/** Sent by Payload 2.x and custom senders only. */
|
|
187
169
|
readonly fieldSchemaJSON?: readonly PayloadFieldSchema[];
|
|
188
170
|
readonly globalSlug?: string;
|
|
189
171
|
readonly collectionSlug?: string;
|
|
190
172
|
readonly locale?: string;
|
|
191
173
|
readonly ready?: boolean;
|
|
192
|
-
/**
|
|
193
|
-
* Payload 3.x: most recent relationship-document event (create/update
|
|
194
|
-
* in a drawer), or `null`. The official client ignores it too — it is
|
|
195
|
-
* modeled here for completeness and consumer event access.
|
|
196
|
-
*/
|
|
174
|
+
/** Payload 3.x: the most recent relationship-document event (a drawer save), or `null`. */
|
|
197
175
|
readonly externallyUpdatedRelationship?: PayloadDocumentEventDetail | null;
|
|
198
|
-
/**
|
|
199
|
-
* Optional preview JWT. ⚠️ Library extension — stock Payload never
|
|
200
|
-
* sends one. When `RuntimeOptions.validateToken` is set, the runtime
|
|
201
|
-
* requires every data update to carry a token that passes the
|
|
202
|
-
* validator; messages without a valid token are dropped.
|
|
203
|
-
*/
|
|
176
|
+
/** Library extension: the token `validateToken` checks. Stock Payload never sends one. */
|
|
204
177
|
readonly previewToken?: string;
|
|
205
|
-
/**
|
|
206
|
-
* Highest protocol version the sender supports. ⚠️ Library extension
|
|
207
|
-
* for custom senders — stock Payload sends no version. When absent,
|
|
208
|
-
* the receiver assumes v1.
|
|
209
|
-
*/
|
|
178
|
+
/** Library extension: the highest protocol version the sender supports; absent means v1. */
|
|
210
179
|
readonly protocolVersion?: number;
|
|
211
180
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Payload's `DocumentEvent` shape (admin `providers/DocumentEvents`).
|
|
214
|
-
*/
|
|
181
|
+
/** Payload's `DocumentEvent` shape (admin `providers/DocumentEvents`). */
|
|
215
182
|
interface PayloadDocumentEventDetail {
|
|
216
183
|
readonly entitySlug: string;
|
|
217
184
|
readonly operation?: 'create' | 'update';
|
|
@@ -219,9 +186,7 @@ interface PayloadDocumentEventDetail {
|
|
|
219
186
|
readonly updatedAt?: string;
|
|
220
187
|
readonly [extra: string]: unknown;
|
|
221
188
|
}
|
|
222
|
-
/**
|
|
223
|
-
* Parsed, validated message payload exposed to consumers via events.
|
|
224
|
-
*/
|
|
189
|
+
/** A validated update as events expose it. */
|
|
225
190
|
interface PayloadLivePreviewData {
|
|
226
191
|
readonly fields: Record<string, unknown>;
|
|
227
192
|
readonly schema?: readonly PayloadFieldSchema[];
|
|
@@ -229,13 +194,7 @@ interface PayloadLivePreviewData {
|
|
|
229
194
|
readonly collectionSlug?: string;
|
|
230
195
|
readonly locale?: string;
|
|
231
196
|
}
|
|
232
|
-
/**
|
|
233
|
-
* The subset of Payload's field-schema description we rely on.
|
|
234
|
-
*
|
|
235
|
-
* Payload emits the entire field configuration tree but we only need
|
|
236
|
-
* the discriminator `type` and the nesting via `fields`/`blocks`.
|
|
237
|
-
* Extra properties are preserved opaquely.
|
|
238
|
-
*/
|
|
197
|
+
/** The subset of Payload's field schema the runtime reads; extra properties are kept opaquely. */
|
|
239
198
|
interface PayloadFieldSchema {
|
|
240
199
|
readonly name: string;
|
|
241
200
|
readonly type: PayloadFieldType;
|
|
@@ -256,54 +215,19 @@ interface PayloadBlockSchema {
|
|
|
256
215
|
readonly fields: readonly PayloadFieldSchema[];
|
|
257
216
|
readonly [extra: string]: unknown;
|
|
258
217
|
}
|
|
259
|
-
/**
|
|
260
|
-
* The Payload core field types this library recognises (20 as of
|
|
261
|
-
* Payload 3.x). `tabs` is treated as a structural container; `group`
|
|
262
|
-
* flattens nested fields.
|
|
263
|
-
*/
|
|
218
|
+
/** Payload's core field types. `tabs` is a structural container; `group` flattens. */
|
|
264
219
|
type PayloadFieldType = 'text' | 'textarea' | 'richText' | 'email' | 'number' | 'checkbox' | 'date' | 'select' | 'radio' | 'array' | 'blocks' | 'group' | 'tabs' | 'row' | 'collapsible' | 'relationship' | 'upload' | 'point' | 'json' | 'code' | 'ui';
|
|
265
|
-
/**
|
|
266
|
-
* Payload's `admin.condition` is a function in source, but it cannot
|
|
267
|
-
* be transferred over postMessage. The schema arrives with this slot
|
|
268
|
-
* either absent or, in future Payload versions, replaced by a
|
|
269
|
-
* serialized expression. We model the slot but do not consume it yet.
|
|
270
|
-
*/
|
|
220
|
+
/** `admin.condition` is a function in source and cannot cross `postMessage`; modelled, not consumed. */
|
|
271
221
|
type PayloadFieldCondition = (data: unknown, siblingData: unknown) => boolean;
|
|
272
222
|
|
|
273
223
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* rule, or a bug report referring to `LP0301` keeps meaning the same thing
|
|
278
|
-
* after the sentence around it is rewritten, and a code is greppable in a way
|
|
279
|
-
* that a sentence fragment is not.
|
|
224
|
+
* Every diagnostic code the runtime and tools emit, so a consumer can branch
|
|
225
|
+
* on `e.code` instead of matching message text. A code is never reused for a
|
|
226
|
+
* different meaning; a retired one stays reserved.
|
|
280
227
|
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* was set up in a way that will bite.
|
|
285
|
-
* - `LP02xx` — bindings and markup: an update had nowhere to land.
|
|
286
|
-
* - `LP03xx` — updates and scheduling: the update landed somewhere other than
|
|
287
|
-
* the DOM, or not yet.
|
|
288
|
-
* - `LP04xx` — rendering: a value reached an element and was refused.
|
|
289
|
-
* - `LP05xx` — protocol and messages: something arrived that was not accepted.
|
|
290
|
-
* - `LP06xx` — consumer callbacks: code the consumer supplied threw.
|
|
291
|
-
* - `LP07xx` — audit findings from `pll doctor`, which reports on a served
|
|
292
|
-
* response rather than on a running runtime.
|
|
293
|
-
*
|
|
294
|
-
* A code is part of the public contract. Codes are never reused for a
|
|
295
|
-
* different meaning and never renumbered; a retired code stays retired.
|
|
296
|
-
* `LP0604` is unassigned: a throwing token validator is deliberately treated
|
|
297
|
-
* as a rejection and reported as `LP0502`, so there is nothing distinct to
|
|
298
|
-
* report yet. The number stays reserved rather than being handed to something
|
|
299
|
-
* else.
|
|
300
|
-
*
|
|
301
|
-
* The runtime writes these as string literals at each reporting site rather
|
|
302
|
-
* than reading them from this record, so the inline build pays for the six
|
|
303
|
-
* characters it prints and nothing more. This record exists so consumers can
|
|
304
|
-
* refer to a code by name instead of copying a literal.
|
|
305
|
-
*
|
|
306
|
-
* @module @core/diagnostic-codes
|
|
228
|
+
* Browser-side reporting sites write the literal (`code: 'LP0603'`) rather than
|
|
229
|
+
* reading it from this record: the frozen table would then ship in the inline
|
|
230
|
+
* runtime for the sake of six characters. Server-side tools may import it.
|
|
307
231
|
*/
|
|
308
232
|
/**
|
|
309
233
|
* Every diagnostic code the runtime can report, by name.
|
|
@@ -319,6 +243,8 @@ declare const DIAGNOSTIC_CODES: Readonly<{
|
|
|
319
243
|
readonly NoTrustedOrigin: "LP0101";
|
|
320
244
|
/** Origin trust rests on `document.referrer`, so any framing site is trusted. */
|
|
321
245
|
readonly ReferrerOnlyTrust: "LP0102";
|
|
246
|
+
/** A plugin declares a runtime range this runtime does not satisfy; it was refused. */
|
|
247
|
+
readonly PluginIncompatible: "LP0103";
|
|
322
248
|
/** An update named a field with no binding anchor on the page. */
|
|
323
249
|
readonly OrphanField: "LP0201";
|
|
324
250
|
/** Owner scoping is on and the update names no document it could belong to. */
|
|
@@ -331,6 +257,14 @@ declare const DIAGNOSTIC_CODES: Readonly<{
|
|
|
331
257
|
readonly TextTargetHasChildren: "LP0402";
|
|
332
258
|
/** A structural container has no array template, so the update was skipped. */
|
|
333
259
|
readonly MissingArrayTemplate: "LP0403";
|
|
260
|
+
/** A structural item has no `id`; it pairs positionally, so an insert re-renders every row after it. */
|
|
261
|
+
readonly StructuralItemUnkeyed: "LP0404";
|
|
262
|
+
/** Two structural items share a key; later ones pair positionally. */
|
|
263
|
+
readonly StructuralDuplicateKey: "LP0405";
|
|
264
|
+
/** Every structural key changed while the length did not; the source generates keys per message. */
|
|
265
|
+
readonly StructuralUnstableKeys: "LP0406";
|
|
266
|
+
/** A binding asks for a delivery strategy this release does not have; it is left unchanged. */
|
|
267
|
+
readonly UnsupportedStrategy: "LP0407";
|
|
334
268
|
/** A message was rejected before it reached the update pipeline. */
|
|
335
269
|
readonly MessageRejected: "LP0501";
|
|
336
270
|
/** A preview token was rejected. */
|
|
@@ -361,251 +295,245 @@ declare const DIAGNOSTIC_CODES: Readonly<{
|
|
|
361
295
|
readonly AuditNoBindings: "LP0707";
|
|
362
296
|
/** The URL did not return an HTML page, so nothing else can be judged. */
|
|
363
297
|
readonly AuditNotAPage: "LP0708";
|
|
298
|
+
/** A fragment request failed (network, timeout, server error); the boundary was patched instead. */
|
|
299
|
+
readonly FragmentRequestFailed: "LP0801";
|
|
300
|
+
/** The fragment response had the wrong content type or shape; the boundary was patched instead. */
|
|
301
|
+
readonly FragmentResponseInvalid: "LP0802";
|
|
302
|
+
/** The fragment endpoint refused the preview (not authorized); the boundary was patched instead. */
|
|
303
|
+
readonly FragmentUnauthorized: "LP0803";
|
|
304
|
+
/** A fragment response arrived for a revision that was already superseded and was discarded. */
|
|
305
|
+
readonly FragmentSuperseded: "LP0804";
|
|
306
|
+
/** A route refresh was requested again for the same revision; the loop guard stopped it. */
|
|
307
|
+
readonly RouteRefreshLoop: "LP0805";
|
|
308
|
+
/** A boundary asks for the fragment strategy but no fragment handler is configured; it is patched. */
|
|
309
|
+
readonly FragmentStrategyUnavailable: "LP0806";
|
|
310
|
+
/** A readiness row is not yet at its 2.0 value; `pll doctor --v2` reports it. */
|
|
311
|
+
readonly V2ReadinessGap: "LP0709";
|
|
312
|
+
/** The preview runtime is served to anonymous visitors, not only inside the admin frame. */
|
|
313
|
+
readonly RuntimeOnPublicPage: "LP0710";
|
|
364
314
|
}>;
|
|
365
315
|
/** A diagnostic code, as it appears in log output and on the `error` event. */
|
|
366
316
|
type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[keyof typeof DIAGNOSTIC_CODES];
|
|
367
317
|
|
|
368
318
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
319
|
+
* How a bound region is brought up to date: the runtime patches it, a server
|
|
320
|
+
* renders a `data-payload-fragment` boundary from the unsaved form state, or
|
|
321
|
+
* the whole route refreshes. Core carries only the seam — which boundaries a
|
|
322
|
+
* strategy owns and what it may do — so a patch-only page pays for nothing
|
|
323
|
+
* more; the orchestration lives in `payload-live-preview/fragment`.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
/** What produced an update: the runtime's DOM patching, a server-rendered fragment, or a route refresh. */
|
|
327
|
+
type UpdateSource = 'patch' | 'fragment' | 'route';
|
|
328
|
+
/** One revision, as a strategy sees it, with the runtime capabilities it may use. */
|
|
329
|
+
interface FragmentContext {
|
|
330
|
+
readonly root: ParentNode;
|
|
331
|
+
readonly revision: number;
|
|
332
|
+
readonly receivedAt: number;
|
|
333
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
334
|
+
readonly locale: string | undefined;
|
|
335
|
+
readonly collectionSlug: string | undefined;
|
|
336
|
+
readonly globalSlug: string | undefined;
|
|
337
|
+
/** Aborted when a newer revision arrives or the runtime stops. */
|
|
338
|
+
readonly signal: AbortSignal;
|
|
339
|
+
/** Whether this revision is still the current one. */
|
|
340
|
+
readonly isCurrent: () => boolean;
|
|
341
|
+
/** Morph server-rendered HTML into a boundary (Trusted Types and the keyed morph apply). */
|
|
342
|
+
readonly morph: (boundary: Element, html: string) => void;
|
|
343
|
+
/** Patch the boundary's own bindings from this revision — the deterministic fallback. */
|
|
344
|
+
readonly patch: (boundary: Element) => void;
|
|
345
|
+
/** Debug log through the runtime's logger, with the diagnostic code. */
|
|
346
|
+
readonly log: (code: DiagnosticCode, detail: string) => void;
|
|
347
|
+
/** Report a boundary rendered. */
|
|
348
|
+
readonly rendered: (boundary: Element, id: string, key: string | undefined) => void;
|
|
349
|
+
/** Report a boundary failed and patched instead; emits the `error` and `fragmentRender` events. */
|
|
350
|
+
readonly failed: (boundary: Element, id: string, key: string | undefined, code: DiagnosticCode, reason: string) => void;
|
|
351
|
+
}
|
|
352
|
+
interface FragmentReport {
|
|
353
|
+
readonly rendered: number;
|
|
354
|
+
readonly failed: number;
|
|
355
|
+
readonly superseded: number;
|
|
356
|
+
}
|
|
357
|
+
/** A fragment strategy: which boundaries it owns for a revision, and how it renders them. */
|
|
358
|
+
interface FragmentStrategy {
|
|
359
|
+
/** The boundaries this update re-renders; their inner bindings are left to the strategy. */
|
|
360
|
+
readonly plan: (root: ParentNode, changedFields: ReadonlySet<string>) => readonly Element[];
|
|
361
|
+
/** Render the planned boundaries; resolves once every one settled. */
|
|
362
|
+
readonly render: (context: FragmentContext, boundaries: readonly Element[]) => Promise<FragmentReport>;
|
|
363
|
+
}
|
|
364
|
+
/** One revision, as the route strategy sees it. */
|
|
365
|
+
interface RouteContext {
|
|
366
|
+
readonly revision: number;
|
|
367
|
+
readonly receivedAt: number;
|
|
368
|
+
/** Aborted when a newer revision arrives or the runtime stops. */
|
|
369
|
+
readonly signal: AbortSignal;
|
|
370
|
+
readonly isCurrent: () => boolean;
|
|
371
|
+
readonly log: (code: DiagnosticCode, detail: string) => void;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Whether a revision needs the whole route re-rendered, and how. After a
|
|
375
|
+
* refresh the runtime rescans and re-applies the revision, so the unsaved
|
|
376
|
+
* state lands on the fresh markup.
|
|
377
377
|
*/
|
|
378
|
+
interface RouteStrategy {
|
|
379
|
+
readonly plan: (root: ParentNode, changedFields: ReadonlySet<string>) => boolean;
|
|
380
|
+
readonly refresh: (context: RouteContext) => Promise<'refreshed' | 'failed' | 'superseded'>;
|
|
381
|
+
}
|
|
382
|
+
interface StrategyHandlers {
|
|
383
|
+
readonly fragment?: FragmentStrategy;
|
|
384
|
+
readonly route?: RouteStrategy;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Public event payloads. The map keys are the valid event names; `EventEmitter` is generic over it. */
|
|
378
388
|
|
|
379
389
|
interface LivePreviewEventMap {
|
|
380
|
-
/**
|
|
381
|
-
* Fired once per startup attempt after observers, cache, and message listening
|
|
382
|
-
* are active. A later startup failure may roll that attempt back; retrying then
|
|
383
|
-
* emits a new `init` event.
|
|
384
|
-
*/
|
|
390
|
+
/** Once per startup attempt, after observers, cache and message listening are active. A rolled-back attempt emits it again on retry. */
|
|
385
391
|
readonly init: {
|
|
386
392
|
readonly timestamp: number;
|
|
387
393
|
};
|
|
388
|
-
/**
|
|
394
|
+
/** The first accepted data-bearing update from a trusted origin. */
|
|
389
395
|
readonly connect: {
|
|
390
396
|
readonly origin: string;
|
|
391
397
|
readonly timestamp: number;
|
|
392
398
|
};
|
|
393
|
-
/**
|
|
394
|
-
* Fired when the heartbeat times out or the runtime is destroyed. The
|
|
395
|
-
* `'unload'` reason remains in the public union for 1.x producer compatibility;
|
|
396
|
-
* the built-in runtime does not currently emit it.
|
|
397
|
-
*/
|
|
399
|
+
/** The heartbeat timed out, the runtime was destroyed, or the page was suspended for navigation (`'unload'`). */
|
|
398
400
|
readonly disconnect: {
|
|
399
401
|
readonly reason: 'timeout' | 'destroy' | 'unload';
|
|
400
402
|
readonly timestamp: number;
|
|
401
403
|
};
|
|
402
|
-
/**
|
|
403
|
-
* Fired before the DOM is mutated for an incoming update.
|
|
404
|
-
*
|
|
405
|
-
* Handlers may call `cancel()` before their synchronous or asynchronous
|
|
406
|
-
* work settles to skip exactly this revision.
|
|
407
|
-
*/
|
|
404
|
+
/** Before the DOM is mutated for an update; `cancel()` skips exactly this revision. */
|
|
408
405
|
readonly beforeUpdate: {
|
|
409
406
|
readonly data: PayloadLivePreviewData;
|
|
410
|
-
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
411
407
|
readonly revision?: number;
|
|
408
|
+
/** When the runtime accepted the message, Unix milliseconds. */
|
|
409
|
+
readonly receivedAt?: number;
|
|
410
|
+
/** The strategy that produced the update. */
|
|
411
|
+
readonly source?: UpdateSource;
|
|
412
412
|
readonly cancel: () => void;
|
|
413
413
|
};
|
|
414
|
-
/**
|
|
415
|
-
* Fired after each actual, current DOM application batch. Visibility replay
|
|
416
|
-
* may produce another event for the same revision; cancelled, obsolete, and
|
|
417
|
-
* deferred-only batches produce none.
|
|
418
|
-
*/
|
|
414
|
+
/** After each applied, still-current batch. Visibility replay may emit another for the same revision; cancelled, obsolete and deferred-only batches emit none. */
|
|
419
415
|
readonly afterUpdate: {
|
|
420
416
|
readonly data: PayloadLivePreviewData;
|
|
421
417
|
readonly updatedCount: number;
|
|
422
418
|
readonly durationMs: number;
|
|
423
|
-
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
424
419
|
readonly revision?: number;
|
|
420
|
+
readonly receivedAt?: number;
|
|
421
|
+
readonly source?: UpdateSource;
|
|
425
422
|
};
|
|
426
|
-
/**
|
|
423
|
+
/** Each successful, still-current element write. */
|
|
427
424
|
readonly elementUpdate: {
|
|
428
425
|
readonly element: Element;
|
|
429
426
|
readonly fieldName: string;
|
|
430
427
|
readonly previousValue: unknown;
|
|
431
428
|
readonly nextValue: unknown;
|
|
432
|
-
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
433
429
|
readonly revision?: number;
|
|
430
|
+
readonly receivedAt?: number;
|
|
431
|
+
readonly source?: UpdateSource;
|
|
434
432
|
};
|
|
435
|
-
/**
|
|
433
|
+
/** The element cache was rebuilt (initial scan or mutation). */
|
|
436
434
|
readonly cacheRefresh: {
|
|
437
435
|
readonly elementCount: number;
|
|
438
436
|
readonly fieldCount: number;
|
|
439
437
|
readonly durationMs: number;
|
|
440
438
|
};
|
|
441
|
-
/**
|
|
439
|
+
/** Once per fragment boundary and revision: rendered by the server, or failed (with the LP08xx code) and patched instead. */
|
|
440
|
+
readonly fragmentRender: {
|
|
441
|
+
readonly element: Element;
|
|
442
|
+
readonly id: string;
|
|
443
|
+
readonly key: string | undefined;
|
|
444
|
+
readonly status: 'rendered' | 'failed';
|
|
445
|
+
readonly code?: DiagnosticCode;
|
|
446
|
+
readonly revision: number;
|
|
447
|
+
readonly receivedAt: number;
|
|
448
|
+
};
|
|
449
|
+
/** A `payload-document-event` message arrived (document save). */
|
|
442
450
|
readonly documentSave: {
|
|
443
451
|
readonly timestamp: number;
|
|
444
452
|
};
|
|
445
|
-
/**
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
*/
|
|
453
|
+
/** A data update carried `externallyUpdatedRelationship`: a related document changed in an admin drawer, so the update re-renders unconditionally. */
|
|
454
|
+
readonly relationshipUpdate: {
|
|
455
|
+
readonly detail: PayloadDocumentEventDetail;
|
|
456
|
+
readonly timestamp: number;
|
|
457
|
+
};
|
|
458
|
+
/** An error the runtime caught but cannot fully recover from. Branch on `code`; `context` is the human-readable origin. */
|
|
452
459
|
readonly error: {
|
|
453
460
|
readonly error: Error;
|
|
454
461
|
readonly context: string;
|
|
455
462
|
readonly code: DiagnosticCode;
|
|
456
463
|
};
|
|
457
|
-
/**
|
|
458
|
-
* Fired during destroy after browser observers and the message listener are
|
|
459
|
-
* removed. Event/plugin listeners remain available for this notification and
|
|
460
|
-
* are released by the owning client afterwards.
|
|
461
|
-
*/
|
|
464
|
+
/** During destroy, after observers and the message listener are removed; listeners are released by the owning client afterwards. */
|
|
462
465
|
readonly destroy: {
|
|
463
466
|
readonly timestamp: number;
|
|
464
467
|
};
|
|
465
468
|
}
|
|
466
|
-
/**
|
|
467
|
-
* Handler signature: receives the payload and may return a Promise.
|
|
468
|
-
*
|
|
469
|
-
* Promise-returning handlers are awaited in registration order — this
|
|
470
|
-
* is important for `beforeUpdate.cancel()` semantics.
|
|
471
|
-
*/
|
|
469
|
+
/** Handlers may return a promise; they are awaited in registration order, which `beforeUpdate.cancel()` relies on. */
|
|
472
470
|
type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
|
|
473
|
-
/** Subscription removal function returned by `on()`/`once()`. */
|
|
474
471
|
type Unsubscribe = () => void;
|
|
475
472
|
|
|
476
473
|
/**
|
|
477
|
-
* Per-instance
|
|
478
|
-
*
|
|
479
|
-
* Replaces the legacy module-level singleton. Each `LivePreviewClient`
|
|
480
|
-
* instantiates its own emitter so that calling `destroy()` on one
|
|
481
|
-
* instance cannot clobber listeners attached to another.
|
|
482
|
-
*
|
|
483
|
-
* Design notes:
|
|
484
|
-
*
|
|
485
|
-
* - Generic over an event map type. The default `LivePreviewEventMap`
|
|
486
|
-
* captures every event this library emits, but the implementation
|
|
487
|
-
* is reusable for any map.
|
|
488
|
-
* - Handlers may be `async`. They are awaited sequentially in
|
|
489
|
-
* registration order. This is intentional: `beforeUpdate.cancel()`
|
|
490
|
-
* and similar synchronization rely on stable ordering.
|
|
491
|
-
* - Errors thrown by one handler are logged via `console.error` and
|
|
492
|
-
* do not prevent later handlers from running.
|
|
493
|
-
* - `once()` semantics: registered into a separate map; the entire
|
|
494
|
-
* snapshot for an event is claimed immediately before its dispatch,
|
|
495
|
-
* and remains consumed even if individual handlers throw.
|
|
496
|
-
*
|
|
497
|
-
* @module @events/emitter
|
|
474
|
+
* Per-instance typed emitter: handlers run sequentially in registration order,
|
|
475
|
+
* may be async, and a throwing one is logged and isolated. See ADR 0002.
|
|
498
476
|
*/
|
|
499
477
|
|
|
500
478
|
declare class EventEmitter<TMap extends object = LivePreviewEventMap> {
|
|
501
479
|
#private;
|
|
502
|
-
/**
|
|
503
|
-
* Subscribe `handler` to `event`. Returns an unsubscribe function;
|
|
504
|
-
* call it to remove the handler. The handler is invoked for every
|
|
505
|
-
* subsequent emit until unsubscribed.
|
|
506
|
-
*/
|
|
507
480
|
on<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
508
|
-
/**
|
|
509
|
-
* Subscribe `handler` for a single emit. After the first dispatch,
|
|
510
|
-
* the handler is removed automatically.
|
|
511
|
-
*/
|
|
512
481
|
once<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
513
|
-
/**
|
|
514
|
-
* Remove `handler` from `event`. Works for both `on`- and
|
|
515
|
-
* `once`-registered handlers. No-op when the handler isn't registered.
|
|
516
|
-
*/
|
|
517
482
|
off<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): void;
|
|
518
|
-
/**
|
|
519
|
-
* Dispatch `payload` to every handler registered for `event`.
|
|
520
|
-
*
|
|
521
|
-
* Handlers run sequentially in registration order — regular ones
|
|
522
|
-
* first, then `once` handlers. Async handlers are awaited. Synchronous
|
|
523
|
-
* exceptions are caught, logged, and isolated to the offending handler.
|
|
524
|
-
*/
|
|
483
|
+
/** Dispatch to every handler: regular ones first, then `once` handlers. */
|
|
525
484
|
emit<E extends keyof TMap>(event: E, payload: TMap[E]): Promise<void>;
|
|
526
485
|
/**
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* later callbacks. `emit()` intentionally keeps its established behaviour.
|
|
530
|
-
*
|
|
531
|
-
* Once-handlers are claimed only when dispatch reaches the once phase. Once
|
|
532
|
-
* claimed, the whole snapshot remains consumed even if the predicate changes
|
|
533
|
-
* while one of its handlers is awaiting, matching normal once semantics.
|
|
534
|
-
*
|
|
486
|
+
* Dispatch while `shouldContinue()` holds — checked around every handler, so
|
|
487
|
+
* an obsolete transaction cannot resume across an await.
|
|
535
488
|
* @internal
|
|
536
|
-
* @returns `true` when the complete handler sequence ran while eligible.
|
|
537
489
|
*/
|
|
538
490
|
emitWhile<E extends keyof TMap>(event: E, payload: TMap[E], shouldContinue: () => boolean): Promise<boolean>;
|
|
539
|
-
/**
|
|
540
|
-
* Number of handlers currently registered for `event`.
|
|
541
|
-
* Sum of `on` and `once` registrations.
|
|
542
|
-
*/
|
|
491
|
+
/** Registered handlers for `event`, `on` and `once` together. */
|
|
543
492
|
listenerCount(event: keyof TMap): number;
|
|
544
|
-
/**
|
|
545
|
-
* Remove every handler. Pass an event name to limit the removal to
|
|
546
|
-
* one event; omit it to clear every event on this emitter.
|
|
547
|
-
*
|
|
548
|
-
* Crucially this only affects the current instance — there is no
|
|
549
|
-
* shared global state.
|
|
550
|
-
*/
|
|
493
|
+
/** Remove every handler, or only those of `event`. */
|
|
551
494
|
removeAllListeners(event?: keyof TMap): void;
|
|
552
|
-
/**
|
|
553
|
-
* Snapshot of all event names that currently have at least one
|
|
554
|
-
* registered handler. Useful for debugging.
|
|
555
|
-
*/
|
|
495
|
+
/** Event names with at least one handler. */
|
|
556
496
|
eventNames(): (keyof TMap)[];
|
|
557
497
|
}
|
|
558
498
|
|
|
559
|
-
/**
|
|
560
|
-
* Core type definitions shared across all runtime primitives.
|
|
561
|
-
*
|
|
562
|
-
* @module @core/types
|
|
563
|
-
*/
|
|
499
|
+
/** Types shared by the runtime primitives. */
|
|
564
500
|
|
|
565
|
-
/**
|
|
566
|
-
* Field types recognized by the live preview renderer.
|
|
567
|
-
*
|
|
568
|
-
* Extends Payload's field set with `html` and `url` which are not
|
|
569
|
-
* Payload field types per se but useful DOM-binding categories.
|
|
570
|
-
*/
|
|
501
|
+
/** Payload's field set plus the DOM-binding categories `html`, `url`, `image` and `structural-array`. */
|
|
571
502
|
type FieldType = PayloadFieldType | 'html' | 'url' | 'image' | 'structural-array';
|
|
503
|
+
/** A namespaced project renderer key, so a typo of a built-in type can never become one. */
|
|
504
|
+
type CustomRendererKey = `${string}:${string}`;
|
|
505
|
+
/** What a renderer may be registered under and an element may ask for. */
|
|
506
|
+
type RendererKey = FieldType | CustomRendererKey;
|
|
572
507
|
/**
|
|
573
|
-
* A
|
|
574
|
-
*
|
|
575
|
-
* Stored in the element cache indexed by `fieldName`. Multiple cached
|
|
576
|
-
* entries can share a field name when the same field is rendered in
|
|
577
|
-
* several places (e.g., a title shown in both the header and the
|
|
578
|
-
* page body).
|
|
508
|
+
* A project rich-text renderer, shared by SSR and preview. Its output goes
|
|
509
|
+
* through the sanitizer; call the same `sanitizeHtml()` server-side for byte equality.
|
|
579
510
|
*/
|
|
511
|
+
type RichTextRenderer = (value: unknown, context: {
|
|
512
|
+
readonly fieldName: string;
|
|
513
|
+
readonly element: Element;
|
|
514
|
+
readonly locale: string | undefined;
|
|
515
|
+
}) => string;
|
|
516
|
+
/** One registered binding. Several may share a field name when it renders in several places. */
|
|
580
517
|
interface CachedElement {
|
|
581
518
|
/** The bound DOM element. */
|
|
582
519
|
readonly element: Element;
|
|
583
520
|
/** Payload field path, e.g., `title` or `hero.subtitle`. */
|
|
584
521
|
readonly fieldName: string;
|
|
585
|
-
/**
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
* The cache resolves this from DOM attributes (`data-payload-type`,
|
|
589
|
-
* tag-based heuristics) at build time. When `explicitFieldType` is
|
|
590
|
-
* `false` the lifecycle is allowed to override this with the type
|
|
591
|
-
* learned from the schema once `fieldSchemaJSON` arrives.
|
|
592
|
-
*/
|
|
593
|
-
readonly fieldType: FieldType;
|
|
594
|
-
/**
|
|
595
|
-
* `true` when the field type was set by an explicit
|
|
596
|
-
* `data-payload-type` attribute (consumer trumps schema), `false`
|
|
597
|
-
* when it was inferred via tag-based heuristics (schema may
|
|
598
|
-
* override later). Optional so synthetic bindings (tests / programmatic
|
|
599
|
-
* `cache.add()` callers) need not supply it; the cache always
|
|
600
|
-
* populates it for DOM-derived bindings.
|
|
601
|
-
*/
|
|
522
|
+
/** Resolved from attributes and tag heuristics; the schema may override it unless explicit. */
|
|
523
|
+
readonly fieldType: RendererKey;
|
|
524
|
+
/** Whether `data-payload-type` set the type, in which case the schema must not override it. */
|
|
602
525
|
readonly explicitFieldType?: boolean;
|
|
603
|
-
/**
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
526
|
+
/** Fields this binding depends on (`data-payload-depends`); a change in any re-applies it under `skipUnchanged`. */
|
|
527
|
+
readonly dependsOn?: readonly string[];
|
|
528
|
+
/** Raw `data-payload-strategy` value. */
|
|
529
|
+
readonly strategy?: string;
|
|
530
|
+
/** How the runtime brings this binding up to date; `'unknown'` for an unrecognised strategy. Resolved by the cache. */
|
|
531
|
+
readonly strategyKind?: UpdateSource | 'unknown';
|
|
532
|
+
/** Nearest enclosing `data-payload-fragment` boundary. Resolved by the cache. */
|
|
533
|
+
readonly fragmentBoundary?: Element;
|
|
534
|
+
/** `data-payload-boundary`: an anchor that hides itself while its field is empty. */
|
|
535
|
+
readonly boundary?: boolean;
|
|
536
|
+
/** Write the value into this attribute instead of the content; policed writes. */
|
|
609
537
|
readonly targetAttribute?: string;
|
|
610
538
|
/** Optional sibling-field path bound to the element's `href` attribute. */
|
|
611
539
|
readonly hrefField?: string;
|
|
@@ -619,21 +547,10 @@ interface CachedElement {
|
|
|
619
547
|
readonly arraySeparator?: string;
|
|
620
548
|
/** Optional locale code locked onto this element (overrides the global locale). */
|
|
621
549
|
readonly locale?: string;
|
|
622
|
-
/**
|
|
623
|
-
* Optional document this binding belongs to, resolved from the nearest
|
|
624
|
-
* `data-payload-owner` ancestor (the element itself included).
|
|
625
|
-
*
|
|
626
|
-
* Only consulted while owner scoping is enabled. Without it a field name is
|
|
627
|
-
* the sole identity, so every document rendered on the page competes for the
|
|
628
|
-
* same name.
|
|
629
|
-
*/
|
|
550
|
+
/** The document this binding belongs to, from the nearest `data-payload-owner`. */
|
|
630
551
|
readonly owner?: string;
|
|
631
552
|
}
|
|
632
|
-
/**
|
|
633
|
-
* Context passed to every field renderer. Provides access to the
|
|
634
|
-
* entire field tree so renderers can resolve sibling fields (e.g.,
|
|
635
|
-
* an `<a>` that pulls `href` from a different field).
|
|
636
|
-
*/
|
|
553
|
+
/** What a renderer is given; `allFields` lets it resolve a sibling field. */
|
|
637
554
|
interface RenderContext {
|
|
638
555
|
/** Full field map for the current update — used for sibling lookups. */
|
|
639
556
|
readonly allFields: Record<string, unknown>;
|
|
@@ -641,702 +558,284 @@ interface RenderContext {
|
|
|
641
558
|
readonly locale: string | undefined;
|
|
642
559
|
/** Optional schema descriptor for the field, when available. */
|
|
643
560
|
readonly schema: PayloadFieldSchema | undefined;
|
|
561
|
+
/** The project's rich-text renderer, when the client was configured with one. */
|
|
562
|
+
readonly renderRichText?: RichTextRenderer;
|
|
644
563
|
}
|
|
645
564
|
/**
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* binding plus the new value and apply it to the element. They must
|
|
650
|
-
* not throw — failures should be silenced and logged so that one bad
|
|
651
|
-
* field cannot stop an entire update. The public renderer result remains
|
|
652
|
-
* `void` for 1.x compatibility. Built-in renderers may use an internal exact
|
|
653
|
-
* `false` sentinel when they deliberately perform no DOM write; callers must
|
|
654
|
-
* not rely on that implementation detail.
|
|
565
|
+
* A renderer writes one value into its element. It must not throw: one bad
|
|
566
|
+
* field may not stop an update. The result stays `void` — the internal
|
|
567
|
+
* no-write sentinel is not part of the contract.
|
|
655
568
|
*/
|
|
656
569
|
interface FieldRenderer {
|
|
657
|
-
readonly name:
|
|
570
|
+
readonly name: RendererKey;
|
|
658
571
|
render(target: CachedElement, value: unknown, context: RenderContext): void;
|
|
659
572
|
}
|
|
660
573
|
|
|
661
574
|
/**
|
|
662
|
-
* Plugin
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
* scoped, per-instance context. Plugins can register custom field
|
|
666
|
-
* renderers, transform incoming values, and subscribe to events —
|
|
667
|
-
* the same primitives the built-in renderers use.
|
|
668
|
-
*
|
|
669
|
-
* The system is intentionally minimal. Anything that can be expressed
|
|
670
|
-
* with an event subscription belongs in `events`, not `plugins`.
|
|
671
|
-
*
|
|
672
|
-
* @module @plugins/types
|
|
575
|
+
* Plugin compatibility claims. The range grammar is the subset a plugin
|
|
576
|
+
* author writes (`*`, `1.2.3`, `^`, `~`, `>=`, `>`, `<`, `<=`, space-joined
|
|
577
|
+
* conjunctions, `||` alternatives); prerelease tags are ignored.
|
|
673
578
|
*/
|
|
579
|
+
interface PluginCompatibility {
|
|
580
|
+
/** A semver range the runtime's `VERSION` must satisfy, e.g. `>=2.0.0`. */
|
|
581
|
+
readonly runtime?: string;
|
|
582
|
+
/** The highest postMessage protocol version the plugin understands. */
|
|
583
|
+
readonly protocol?: number;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** Plugin contract: a scoped, per-instance context for renderers, transforms and events. */
|
|
674
587
|
|
|
675
588
|
/** Idempotent handle that releases one plugin-owned resource. */
|
|
676
589
|
type PluginDisposer = () => void;
|
|
677
|
-
/**
|
|
678
|
-
|
|
679
|
-
*
|
|
680
|
-
* This remains nominally compatible with the pre-1.0.4 `EventEmitter` context
|
|
681
|
-
* type. Its methods are registration-scoped: even bulk removal affects only
|
|
682
|
-
* this plugin's subscriptions, never a consumer's or another plugin's
|
|
683
|
-
* listeners.
|
|
684
|
-
*/
|
|
685
|
-
interface PluginEvents extends EventEmitter {
|
|
590
|
+
/** The client's event surface scoped to one registration: bulk removal and introspection see only this plugin's listeners. */
|
|
591
|
+
interface PluginEvents {
|
|
686
592
|
readonly on: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
|
|
687
593
|
readonly once: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
|
|
688
594
|
readonly off: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => void;
|
|
689
595
|
/** Emit through the owning client's event channel. */
|
|
690
596
|
readonly emit: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E]) => Promise<void>;
|
|
691
|
-
/**
|
|
692
|
-
* Guarded emit through the owning client channel. Dispatch stops when the
|
|
693
|
-
* caller predicate becomes false or this plugin registration is removed.
|
|
694
|
-
*/
|
|
597
|
+
/** Guarded emit; dispatch stops when the predicate turns false or this registration is removed. */
|
|
695
598
|
readonly emitWhile: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E], shouldContinue: () => boolean) => Promise<boolean>;
|
|
696
|
-
/** Number of listeners owned by this plugin registration. */
|
|
697
599
|
readonly listenerCount: (event: keyof LivePreviewEventMap) => number;
|
|
698
|
-
/** Remove only listeners owned by this plugin registration. */
|
|
699
600
|
readonly removeAllListeners: (event?: keyof LivePreviewEventMap) => void;
|
|
700
|
-
/** Event names for which this plugin registration owns listeners. */
|
|
701
601
|
readonly eventNames: () => (keyof LivePreviewEventMap)[];
|
|
702
602
|
}
|
|
703
603
|
/**
|
|
704
|
-
* Synchronous
|
|
705
|
-
*
|
|
706
|
-
* order and each receives the preceding transform's result. The final value is
|
|
707
|
-
* frozen into that entry before later renderer or attribute dispatch;
|
|
708
|
-
* `allFields` remains the merged, untransformed update snapshot.
|
|
709
|
-
*
|
|
710
|
-
* Returning a different shape than the input is allowed, but returning a
|
|
711
|
-
* Promise or other thenable is a contract error. If a transform throws or
|
|
712
|
-
* returns a thenable, the chain stops, the runtime emits an `error` event, and
|
|
713
|
-
* dispatch falls back to the original merged value. That fallback still passes
|
|
714
|
-
* through the normal renderer and attribute security controls.
|
|
604
|
+
* Synchronous value transform run in registration order; returning a thenable
|
|
605
|
+
* is a contract error (`LP0602`) that dispatches the untransformed value.
|
|
715
606
|
*/
|
|
716
607
|
type FieldTransform = (value: unknown, context: {
|
|
717
608
|
readonly fieldName: string;
|
|
718
609
|
readonly element: Element;
|
|
719
610
|
readonly allFields: Record<string, unknown>;
|
|
720
611
|
}) => unknown;
|
|
721
|
-
/**
|
|
722
|
-
* Per-instance plugin context. Replaces the legacy singleton context.
|
|
723
|
-
*
|
|
724
|
-
* Plugins receive a context that is bound to the parent
|
|
725
|
-
* `LivePreviewClient`. They can:
|
|
726
|
-
*
|
|
727
|
-
* - subscribe via `events.on/once`,
|
|
728
|
-
* - register field renderers,
|
|
729
|
-
* - register value transforms,
|
|
730
|
-
* - read the client's (frozen) configuration,
|
|
731
|
-
* - log through the client's debug channel.
|
|
732
|
-
*/
|
|
733
612
|
interface PluginContext {
|
|
734
613
|
readonly events: PluginEvents;
|
|
735
|
-
/** Register a renderer owned by this
|
|
614
|
+
/** Register a renderer layer owned by this registration. */
|
|
736
615
|
readonly registerFieldRenderer: (renderer: FieldRenderer) => void;
|
|
737
|
-
/** Register an ordered transform owned by this
|
|
616
|
+
/** Register an ordered transform owned by this registration. */
|
|
738
617
|
readonly registerTransform: (fieldName: string, transform: FieldTransform) => void;
|
|
739
|
-
/**
|
|
740
|
-
* Register any additional synchronous cleanup owned by this registration.
|
|
741
|
-
* The manager invokes it during rollback, `unuse()`, or client destruction.
|
|
742
|
-
* Optional so pre-1.0.4 structural context mocks remain assignable.
|
|
743
|
-
*/
|
|
618
|
+
/** Register a synchronous cleanup run on rollback, `unuse()` and client destruction. */
|
|
744
619
|
readonly registerCleanup?: (cleanup: PluginDisposer) => void;
|
|
745
620
|
readonly getConfig: () => Readonly<Record<string, unknown>>;
|
|
746
621
|
readonly log: (...args: unknown[]) => void;
|
|
747
622
|
}
|
|
748
|
-
/**
|
|
749
|
-
* Plugin definition. `init` runs once per registration; `destroy`
|
|
750
|
-
* runs when the plugin is unregistered or the client is destroyed.
|
|
751
|
-
*
|
|
752
|
-
* Both hooks may return a promise; the manager awaits them.
|
|
753
|
-
*/
|
|
623
|
+
/** `init` runs once per registration, `destroy` on removal; both may return a promise. */
|
|
754
624
|
interface LivePreviewPlugin {
|
|
755
625
|
readonly name: string;
|
|
756
626
|
readonly version?: string;
|
|
627
|
+
/** Checked at registration; a plugin this runtime does not fit is refused (`LP0103`). */
|
|
628
|
+
readonly compat?: PluginCompatibility;
|
|
757
629
|
readonly init: (context: PluginContext) => void | Promise<void>;
|
|
758
630
|
readonly destroy?: () => void | Promise<void>;
|
|
759
631
|
}
|
|
760
632
|
|
|
761
633
|
/**
|
|
762
|
-
* `
|
|
763
|
-
*
|
|
764
|
-
*
|
|
634
|
+
* The two defaults profiles, row by row. 2.0 ships `v2`; `defaults: 'v1'` opts
|
|
635
|
+
* back into the 1.x table for a staged migration. Each row belongs to exactly
|
|
636
|
+
* one side — the adapter's request decision or the browser runtime — and a
|
|
637
|
+
* test asserts the profile assigns every readiness row that has an option, so
|
|
638
|
+
* a row added later cannot be forgotten. See ADR 0007.
|
|
765
639
|
*/
|
|
640
|
+
/** `'v2'` is the 2.0 default; `'v1'` is the 1.x table. */
|
|
641
|
+
type DefaultsProfile = 'v1' | 'v2';
|
|
642
|
+
/** How the runtime decides which windows may post updates. */
|
|
643
|
+
type EventSourcePolicy = 'any' | 'parent-or-opener';
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* `LivePreviewClient` configuration. Defaults are the 2.0 rows; `defaults: 'v1'`
|
|
647
|
+
* restores the 1.x values named per option (ADR 0007).
|
|
648
|
+
*/
|
|
649
|
+
|
|
766
650
|
interface LivePreviewClientConfig {
|
|
767
|
-
/** Explicit
|
|
651
|
+
/** Explicit trusted admin origins. */
|
|
768
652
|
readonly allowedOrigins?: readonly string[];
|
|
769
|
-
/**
|
|
770
|
-
* Payload server origin, e.g. `https://cms.example.com`. When set,
|
|
771
|
-
* every incoming update is re-fetched through the Payload REST API
|
|
772
|
-
* so relationship/upload fields render populated instead of as bare
|
|
773
|
-
* IDs (same strategy as the official client). Recommended for
|
|
774
|
-
* Payload 3.x.
|
|
775
|
-
*/
|
|
653
|
+
/** Payload origin; every update is then re-fetched through the REST API so relations arrive populated. Requires `mergeDepth`. */
|
|
776
654
|
readonly serverURL?: string;
|
|
777
|
-
/** REST
|
|
655
|
+
/** REST route prefix used with `serverURL`. Default `/api`. */
|
|
778
656
|
readonly apiRoute?: string;
|
|
779
|
-
/** Population depth used with `serverURL`.
|
|
657
|
+
/** Population depth used with `serverURL`. Required with it; `defaults: 'v1'` falls back to `1`. */
|
|
780
658
|
readonly mergeDepth?: number;
|
|
781
|
-
/**
|
|
782
|
-
* Custom fetch implementation for the `serverURL` merge request —
|
|
783
|
-
* the equivalent of the official client's `requestHandler`. Use it
|
|
784
|
-
* to attach auth headers or route through your own proxy. Only
|
|
785
|
-
* available on the programmatic client (functions cannot be
|
|
786
|
-
* serialised into the inline script).
|
|
787
|
-
*/
|
|
659
|
+
/** Custom fetch for the `serverURL` merge request: attach auth headers or route through a proxy. */
|
|
788
660
|
readonly mergeFetch?: typeof fetch;
|
|
789
|
-
/**
|
|
661
|
+
/** Verbose console output. Defaults to dev-mode detection. */
|
|
790
662
|
readonly debug?: boolean;
|
|
791
|
-
/** Debounce window for incoming updates.
|
|
663
|
+
/** Debounce window for incoming updates. Default 50 ms. */
|
|
792
664
|
readonly debounceMs?: number;
|
|
793
|
-
/**
|
|
794
|
-
* Heartbeat timeout in ms. Defaults to `0` (disabled) because the
|
|
795
|
-
* Payload admin sends messages only on form edits — there is no
|
|
796
|
-
* protocol keepalive, so an idle timeout would produce false
|
|
797
|
-
* disconnects.
|
|
798
|
-
*/
|
|
665
|
+
/** Heartbeat timeout in ms; `0` disables it (default), because the admin posts only on edits. */
|
|
799
666
|
readonly heartbeatMs?: number;
|
|
800
|
-
/** IntersectionObserver rootMargin
|
|
667
|
+
/** IntersectionObserver `rootMargin`. Default `200px`. */
|
|
801
668
|
readonly intersectionRootMargin?: string;
|
|
802
|
-
/**
|
|
803
|
-
* Restrict each update to the bindings owned by the document it describes.
|
|
804
|
-
*
|
|
805
|
-
* Ownership is declared in markup with `data-payload-owner`, resolved from
|
|
806
|
-
* the nearest ancestor: `global:<slug>`, `collection:<slug>`, or
|
|
807
|
-
* `collection:<slug>:<id>`. Payload already sends the edited document's
|
|
808
|
-
* identity, so nothing else needs configuring.
|
|
809
|
-
*
|
|
810
|
-
* Defaults to `false`, which keeps the 1.x behaviour of matching on the
|
|
811
|
-
* field name alone. Enable it when one page previews more than one document
|
|
812
|
-
* and a field name is therefore no longer a unique identity. While enabled,
|
|
813
|
-
* a binding without an owner is never updated.
|
|
814
|
-
*/
|
|
669
|
+
/** Update only bindings under a matching `data-payload-owner`; an unowned binding is then never updated. Default `false`. */
|
|
815
670
|
readonly scopeBindingsByOwner?: boolean;
|
|
816
|
-
/**
|
|
671
|
+
/** Skip a binding whose value did not change. Default `true`; `defaults: 'v1'` sets `false`. */
|
|
672
|
+
readonly skipUnchanged?: boolean;
|
|
673
|
+
/** Scroll the preview to the field being edited when its value changes. Default `false`. */
|
|
674
|
+
readonly revealEditedField?: boolean;
|
|
675
|
+
/** Fields whose change re-applies other bindings regardless of their own value: `{ price: ['priceLabel'] }`. Used with `skipUnchanged`. */
|
|
676
|
+
readonly dependencies?: Readonly<Record<string, readonly string[]>>;
|
|
677
|
+
/** Strategy handlers beyond patching; `createFragmentStrategy()` from `payload-live-preview/fragment` builds one. */
|
|
678
|
+
readonly strategies?: StrategyHandlers;
|
|
679
|
+
/** Apply every update regardless of visibility. Default `false`. */
|
|
817
680
|
readonly disableVisibilityGate?: boolean;
|
|
818
|
-
/** Cache
|
|
681
|
+
/** Cache size above which off-screen updates wait for intersection. Default `50`. */
|
|
819
682
|
readonly visibilityGateThreshold?: number;
|
|
820
|
-
/**
|
|
821
|
-
* Mount an `aria-live` region for connections, applied updates, and mounted
|
|
822
|
-
* heartbeat-timeout disconnects. Destroy releases it synchronously. Default `true`.
|
|
823
|
-
*/
|
|
683
|
+
/** Mount an `aria-live` region for connections, applied updates and heartbeat disconnects. Default `true`. */
|
|
824
684
|
readonly enableA11y?: boolean;
|
|
825
|
-
/** Locale
|
|
685
|
+
/** Locale of the announcement strings. Defaults to the detected locale. */
|
|
826
686
|
readonly a11yLocale?: string;
|
|
827
|
-
/** Document root
|
|
687
|
+
/** Document root. Default `document`. */
|
|
828
688
|
readonly root?: Document | Element;
|
|
829
|
-
/**
|
|
689
|
+
/** Renderer resolution ahead of the registry; return `undefined` to fall through. */
|
|
690
|
+
readonly resolveRenderer?: (fieldType: RendererKey, target: CachedElement) => FieldRenderer | undefined;
|
|
691
|
+
/** The project's rich-text renderer, shared with SSR. Its output is sanitized. */
|
|
692
|
+
readonly renderRichText?: RichTextRenderer;
|
|
693
|
+
/** Ignore `document.referrer` as an origin source. Default `true`; `defaults: 'v1'` sets `false`. */
|
|
830
694
|
readonly disableReferrerDetection?: boolean;
|
|
831
|
-
/** Disable the
|
|
695
|
+
/** Disable the dev-mode localhost matcher. Default `false`. */
|
|
832
696
|
readonly disableLocalhostMatching?: boolean;
|
|
833
|
-
/**
|
|
697
|
+
/** Which windows may post updates. Default `'parent-or-opener'`; `defaults: 'v1'` sets `'any'`. */
|
|
698
|
+
readonly eventSourcePolicy?: EventSourcePolicy;
|
|
699
|
+
/** Sanitizer policy for rich text and HTML writes. Default `'strict'`; `defaults: 'v1'` sets `'compat'`. */
|
|
700
|
+
readonly sanitizerPolicy?: 'compat' | 'strict';
|
|
701
|
+
/** `'v2'` (default) is the 2.0 table; `'v1'` restores every 1.x row at once. Explicit options win. */
|
|
702
|
+
readonly defaults?: DefaultsProfile;
|
|
703
|
+
/** Start the runtime in the constructor. Default `true`. */
|
|
834
704
|
readonly autoStart?: boolean;
|
|
835
|
-
/**
|
|
836
|
-
* Optional preview-token validator. When set, every data update
|
|
837
|
-
* must carry a `previewToken` field that this function approves.
|
|
838
|
-
*
|
|
839
|
-
* ⚠️ `previewToken` is an extension of THIS library, not part of the
|
|
840
|
-
* stock Payload postMessage protocol — the Payload admin never sends
|
|
841
|
-
* one. Only enable this when a custom admin component adds the token
|
|
842
|
-
* to outgoing messages; against an unmodified Payload admin it would
|
|
843
|
-
* drop every update.
|
|
844
|
-
*/
|
|
705
|
+
/** Preview-token validator; every update must then carry a `previewToken` it approves. Stock Payload sends none. */
|
|
845
706
|
readonly validateToken?: (token: string | undefined, origin: string) => boolean | Promise<boolean>;
|
|
846
707
|
}
|
|
847
708
|
|
|
848
709
|
/**
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
* - Per-instance event emitter (no singleton).
|
|
854
|
-
* - Per-instance plugin manager.
|
|
855
|
-
* - Custom-renderer registration.
|
|
856
|
-
* - Field-name transforms frozen into revision-bound scheduler entries before
|
|
857
|
-
* their later attribute or renderer dispatch.
|
|
858
|
-
*
|
|
859
|
-
* Consumers who want full programmatic control instantiate
|
|
860
|
-
* `LivePreviewClient` directly. Consumers who just want a working
|
|
861
|
-
* inline preview should use `generateInlineScript()` instead.
|
|
862
|
-
*
|
|
863
|
-
* @module @client
|
|
710
|
+
* `LivePreviewClient`: a `LivePreviewRuntime` with its own emitter, plugin
|
|
711
|
+
* manager and renderer registry. A page that only needs a working preview uses
|
|
712
|
+
* `generateInlineScript()` instead.
|
|
864
713
|
*/
|
|
865
714
|
|
|
866
715
|
declare class LivePreviewClient {
|
|
867
716
|
#private;
|
|
868
|
-
constructor(
|
|
717
|
+
constructor(rawConfig?: LivePreviewClientConfig);
|
|
869
718
|
/**
|
|
870
|
-
* Start the runtime
|
|
871
|
-
*
|
|
872
|
-
* active client also return `true`; `false` means the client was destroyed or
|
|
873
|
-
* the page is outside a preview context. A deferred startup can still fail,
|
|
874
|
-
* roll back, and be retried by a later call.
|
|
719
|
+
* Start the runtime: `true` while eligible and started or scheduled for DOM
|
|
720
|
+
* readiness, `false` once destroyed or outside a preview context.
|
|
875
721
|
*/
|
|
876
722
|
start(): boolean;
|
|
877
723
|
/**
|
|
878
|
-
* Release the message ingress
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
* does not re-run module scripts, so a client that stays attached across
|
|
882
|
-
* `pagehide` returns bound to a frozen page and silently stops updating.
|
|
883
|
-
* Plugins, renderers and transforms survive; `resume()` brings the same
|
|
884
|
-
* client back. Returns `false` when there was nothing running to suspend.
|
|
724
|
+
* Release the message ingress, keeping plugins and renderers, for `pagehide`:
|
|
725
|
+
* a bfcache restore re-runs no script, so an attached client would return
|
|
726
|
+
* bound to a frozen page. `false` when nothing was running.
|
|
885
727
|
*/
|
|
886
728
|
suspend(): boolean;
|
|
887
|
-
/**
|
|
888
|
-
* Reacquire after `suspend()`.
|
|
889
|
-
*
|
|
890
|
-
* Deliberately the same path as `start()`: a restored document needs the
|
|
891
|
-
* cache rebuilt, the observers rebound and the handshake rebroadcast, which
|
|
892
|
-
* is exactly what starting does. Returns `false` for a destroyed client or
|
|
893
|
-
* one that was never started.
|
|
894
|
-
*/
|
|
729
|
+
/** Reacquire after `suspend()`: the same path as `start()`, because a restored document needs the same rebuild. */
|
|
895
730
|
resume(): boolean;
|
|
896
|
-
/**
|
|
897
|
-
* Stop the runtime and tear down every plugin. Idempotent.
|
|
898
|
-
*/
|
|
731
|
+
/** Stop the runtime and tear down every plugin. Idempotent; concurrent callers share one promise. */
|
|
899
732
|
destroy(): Promise<void>;
|
|
900
|
-
/**
|
|
901
|
-
* Register a plugin.
|
|
902
|
-
*/
|
|
903
733
|
use(plugin: LivePreviewPlugin): Promise<void>;
|
|
904
|
-
/** Unregister a plugin and release
|
|
734
|
+
/** Unregister a plugin and release everything its registration owned. Still allowed from a destroy hook during teardown. */
|
|
905
735
|
unuse(name: string): Promise<void>;
|
|
906
|
-
/** Rebuild the element cache manually. */
|
|
907
736
|
refreshCache(): void;
|
|
908
|
-
/** Read-only access to the event emitter for `on`/`once`/`off`. */
|
|
909
737
|
get events(): EventEmitter;
|
|
910
|
-
/** Names of
|
|
738
|
+
/** Names of the registered plugins. */
|
|
911
739
|
get plugins(): readonly string[];
|
|
912
|
-
/** Current connection status. */
|
|
913
740
|
get status(): 'disconnected' | 'connecting' | 'connected';
|
|
914
|
-
/** Number of valid updates received so far. */
|
|
915
741
|
/**
|
|
916
|
-
* Point-in-time read of runtime state,
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
* Read `bindings.orphanFields` when a field refuses to update, and
|
|
920
|
-
* `scheduler.deferred` together with `scheduler.visibilityGateActive` when
|
|
921
|
-
* updates stop below the fold.
|
|
742
|
+
* Point-in-time read of runtime state, no I/O: `bindings.orphanFields` when a
|
|
743
|
+
* field refuses to update, `scheduler.deferred` when updates stop below the fold.
|
|
922
744
|
*/
|
|
923
745
|
inspect(): LivePreviewInspection;
|
|
746
|
+
/** Number of accepted updates so far. */
|
|
924
747
|
get updateCount(): number;
|
|
925
748
|
/** `true` once `destroy()` has been called. */
|
|
926
749
|
get destroyed(): boolean;
|
|
927
750
|
}
|
|
928
|
-
/**
|
|
929
|
-
* Convenience factory: instantiate the client, returning `null` when
|
|
930
|
-
* the page is not currently a preview context (top-level navigation).
|
|
931
|
-
* Useful for SSR-style integrations that import this from any context.
|
|
932
|
-
*/
|
|
751
|
+
/** A started client, or `null` when the page is not a preview context. */
|
|
933
752
|
declare function initLivePreview(config?: LivePreviewClientConfig): LivePreviewClient | null;
|
|
934
753
|
|
|
935
|
-
/**
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
* The detection layer needs to know whether the runtime is currently
|
|
939
|
-
* running inside an iframe, in a popup, in development mode, and
|
|
940
|
-
* whether environment variables are accessible. None of these checks
|
|
941
|
-
* is dramatic on its own, but having a single, tested module avoids
|
|
942
|
-
* the duplicated, subtly-different `typeof process` snippets that
|
|
943
|
-
* accumulated in the legacy implementation.
|
|
944
|
-
*
|
|
945
|
-
* @module @detection/environment
|
|
946
|
-
*/
|
|
947
|
-
/**
|
|
948
|
-
* Returns `true` when the current window is inside an iframe.
|
|
949
|
-
*
|
|
950
|
-
* Uses a try/catch around `window.self !== window.top` because the
|
|
951
|
-
* comparison throws in cross-origin iframes — and that very throw is
|
|
952
|
-
* itself a definitive "yes, I am in an iframe" signal.
|
|
953
|
-
*/
|
|
954
|
-
declare function isInIframe(): boolean;
|
|
955
|
-
/**
|
|
956
|
-
* Returns `true` when the current window was opened via `window.open()`
|
|
957
|
-
* and the opener reference is still accessible.
|
|
958
|
-
*/
|
|
959
|
-
declare function isInPopup(): boolean;
|
|
960
|
-
/**
|
|
961
|
-
* Returns `true` when running in a preview-capable context: iframe or
|
|
962
|
-
* popup. Anything else (top-level navigation) should skip live preview
|
|
963
|
-
* initialisation entirely.
|
|
964
|
-
*/
|
|
965
|
-
declare function isInPreviewContext(): boolean;
|
|
966
|
-
/**
|
|
967
|
-
* Returns `true` when the runtime appears to be in development mode.
|
|
968
|
-
*
|
|
969
|
-
* Detection order:
|
|
970
|
-
* 1. `process.env.NODE_ENV !== 'production'`
|
|
971
|
-
* 2. `import.meta.env.DEV === true` (Vite / Astro / SvelteKit)
|
|
972
|
-
* 3. `window.location.hostname` is `localhost`/`127.0.0.1`
|
|
973
|
-
*
|
|
974
|
-
* Returns `false` when none of those are available.
|
|
975
|
-
*/
|
|
976
|
-
declare function isDevMode(): boolean;
|
|
977
|
-
|
|
978
|
-
/**
|
|
979
|
-
* Origin detection and matching.
|
|
980
|
-
*
|
|
981
|
-
* Builds the set of trusted Payload admin origins and exposes a fast
|
|
982
|
-
* matcher used by the message bus. Three sources, in precedence order:
|
|
983
|
-
*
|
|
984
|
-
* 1. Explicit origins passed via configuration. Always trusted.
|
|
985
|
-
* 2. `document.referrer` — **only when no explicit origins are
|
|
986
|
-
* configured**. The browser writes the embedder's origin here
|
|
987
|
-
* when the iframe loads; using it as a fallback gives a
|
|
988
|
-
* zero-config path, but it must never widen an explicit
|
|
989
|
-
* allow-list (the referrer is whoever framed the page, which in
|
|
990
|
-
* an attack is the attacker).
|
|
991
|
-
* 3. Localhost pattern (any port on `localhost` / `127.0.0.1`),
|
|
992
|
-
* enabled when development mode is detected. Replaces the
|
|
993
|
-
* legacy hand-rolled port list.
|
|
994
|
-
*
|
|
995
|
-
* Once the parent's origin is confirmed by an accepted data-bearing
|
|
996
|
-
* update, callers can call `lockOrigin()` to drop every other candidate,
|
|
997
|
-
* making subsequent matches exact. A data-less `ready` handshake does
|
|
998
|
-
* not lock the detector.
|
|
999
|
-
*
|
|
1000
|
-
* @module @detection/origin
|
|
1001
|
-
*/
|
|
1002
|
-
interface OriginDetectorOptions {
|
|
1003
|
-
/** Explicit additional origins to trust. Always allowed. */
|
|
1004
|
-
readonly additionalOrigins?: readonly string[];
|
|
1005
|
-
/** Set to `false` to opt out of `document.referrer` detection. */
|
|
1006
|
-
readonly enableReferrerDetection?: boolean;
|
|
1007
|
-
/** Set to `false` to opt out of localhost-pattern matching. */
|
|
1008
|
-
readonly enableLocalhostMatching?: boolean;
|
|
1009
|
-
/**
|
|
1010
|
-
* Override the dev-mode detection. When `undefined`, dev mode is
|
|
1011
|
-
* inferred from the runtime (see `isDevMode`).
|
|
1012
|
-
*/
|
|
1013
|
-
readonly forceDevMode?: boolean;
|
|
1014
|
-
/** Override `document.referrer` for tests. */
|
|
1015
|
-
readonly referrer?: string;
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Holds the resolved origin policy and exposes a matcher.
|
|
1019
|
-
*
|
|
1020
|
-
* The detector is stateful: it starts with the union of all candidate
|
|
1021
|
-
* sources, then narrows down to a single, verified origin once the
|
|
1022
|
-
* lifecycle calls `lockOrigin()` for its first accepted data-bearing
|
|
1023
|
-
* update.
|
|
1024
|
-
*/
|
|
1025
|
-
declare class OriginDetector {
|
|
1026
|
-
#private;
|
|
1027
|
-
constructor(options?: OriginDetectorOptions);
|
|
1028
|
-
/**
|
|
1029
|
-
* Returns `true` when `origin` is trusted under the current policy.
|
|
1030
|
-
*
|
|
1031
|
-
* If an origin has been locked, only that exact value matches.
|
|
1032
|
-
* Otherwise explicit origins and (in dev mode) localhost-pattern
|
|
1033
|
-
* matches are accepted. The referrer-derived origin is a **fallback,
|
|
1034
|
-
* not a union member**: it only counts when NO explicit origins are
|
|
1035
|
-
* configured. `document.referrer` always names whoever actually
|
|
1036
|
-
* framed the page — if it were unioned with the explicit list, any
|
|
1037
|
-
* site embedding the page in an iframe would become a trusted
|
|
1038
|
-
* postMessage sender despite the consumer having pinned the admin
|
|
1039
|
-
* origin explicitly.
|
|
1040
|
-
*/
|
|
1041
|
-
matches(origin: string): boolean;
|
|
1042
|
-
/**
|
|
1043
|
-
* Narrow the trusted set to a single origin after an accepted
|
|
1044
|
-
* data-bearing update. Subsequent calls to `matches()` only allow
|
|
1045
|
-
* `origin`.
|
|
1046
|
-
*
|
|
1047
|
-
* Returns `true` if locking succeeded; `false` if the candidate is
|
|
1048
|
-
* not currently considered trusted (which would defeat the purpose
|
|
1049
|
-
* of locking).
|
|
1050
|
-
*/
|
|
1051
|
-
lockOrigin(origin: string): boolean;
|
|
1052
|
-
/**
|
|
1053
|
-
* Release the origin lock so any allow-listed origin is trusted
|
|
1054
|
-
* again until the next accepted data-bearing update. The lifecycle
|
|
1055
|
-
* calls this on heartbeat timeout — after the trusted parent disappears,
|
|
1056
|
-
* a different (still-allowed) origin should be free to reconnect.
|
|
1057
|
-
*
|
|
1058
|
-
* Returns the previously-locked origin, or `undefined` when nothing
|
|
1059
|
-
* was locked.
|
|
1060
|
-
*/
|
|
1061
|
-
unlockOrigin(): string | undefined;
|
|
1062
|
-
/** The currently locked origin, or `undefined`. */
|
|
1063
|
-
get lockedOrigin(): string | undefined;
|
|
1064
|
-
/**
|
|
1065
|
-
* Snapshot of every origin currently allowed by the policy. Used by
|
|
1066
|
-
* the message bus to broadcast `ready` messages.
|
|
1067
|
-
*
|
|
1068
|
-
* Localhost pattern matches are expanded into the common dev ports
|
|
1069
|
-
* so the handshake can find a Payload running on its usual port.
|
|
1070
|
-
*/
|
|
1071
|
-
enumerate(): string[];
|
|
1072
|
-
/**
|
|
1073
|
-
* Reports whether `document.referrer` contributed at least one
|
|
1074
|
-
* origin. Useful for surfacing a console warning when running in
|
|
1075
|
-
* production without an explicit origin and the referrer is absent
|
|
1076
|
-
* (because of a `Referrer-Policy: no-referrer` header).
|
|
1077
|
-
*/
|
|
1078
|
-
get referrerWasAvailable(): boolean;
|
|
1079
|
-
/**
|
|
1080
|
-
* Indicates whether the detector is operating in a "production-without-
|
|
1081
|
-
* explicit-origin" configuration. When `true`, the host should log a
|
|
1082
|
-
* loud warning — silent connection failure is the worst outcome.
|
|
1083
|
-
*/
|
|
1084
|
-
get isProductionUnconfigured(): boolean;
|
|
1085
|
-
/**
|
|
1086
|
-
* `true` when the referrer is the ONLY trust source (no explicit
|
|
1087
|
-
* origins, no localhost matching). In that mode any site that frames
|
|
1088
|
-
* the page becomes a trusted postMessage sender — `document.referrer`
|
|
1089
|
-
* always names the actual embedder. Content is still sanitized, but
|
|
1090
|
-
* the origin allow-list no longer restricts who may drive it. Hosts
|
|
1091
|
-
* should surface a warning suggesting explicit `allowedOrigins` and
|
|
1092
|
-
* a `frame-ancestors` policy in production.
|
|
1093
|
-
*/
|
|
1094
|
-
get isReferrerOnlyTrust(): boolean;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
* Locale detection.
|
|
1099
|
-
*
|
|
1100
|
-
* The runtime needs a sensible default locale for date/number renders
|
|
1101
|
-
* before Payload tells us the active locale. Order of precedence:
|
|
1102
|
-
*
|
|
1103
|
-
* 1. The `lang` attribute on `<html>`.
|
|
1104
|
-
* 2. `navigator.language`.
|
|
1105
|
-
* 3. The hard-coded fallback `'en'`.
|
|
1106
|
-
*
|
|
1107
|
-
* The previous version of this library defaulted to `'de'` — a German
|
|
1108
|
-
* default that surprised consumers in other locales. We now use the
|
|
1109
|
-
* browser hint and fall back to English.
|
|
1110
|
-
*
|
|
1111
|
-
* @module @detection/locale
|
|
1112
|
-
*/
|
|
1113
|
-
/**
|
|
1114
|
-
* Returns a BCP-47 locale string suitable for `Intl.*` constructors.
|
|
1115
|
-
*
|
|
1116
|
-
* Always returns a non-empty string. When no signal is available the
|
|
1117
|
-
* fallback (`'en'`) is returned.
|
|
1118
|
-
*/
|
|
1119
|
-
declare function detectInitialLocale(): string;
|
|
1120
|
-
|
|
1121
|
-
/**
|
|
1122
|
-
* Single source of truth for the library version.
|
|
1123
|
-
*
|
|
1124
|
-
* Both the npm package surface (`src/index.ts`, `src/core-entry.ts`)
|
|
1125
|
-
* and the inline runtime (`src/core/runtime.ts`, which gets bundled by
|
|
1126
|
-
* esbuild and embedded into `runtime.generated.ts`) read the version
|
|
1127
|
-
* from here so they never drift.
|
|
1128
|
-
*
|
|
1129
|
-
* Imports `package.json` directly — tsc, tsup, and esbuild all inline
|
|
1130
|
-
* the value at build time when `resolveJsonModule` is enabled.
|
|
1131
|
-
*
|
|
1132
|
-
* @module @/version
|
|
1133
|
-
*/
|
|
1134
|
-
declare const VERSION: string;
|
|
1135
|
-
|
|
1136
|
-
/**
|
|
1137
|
-
* HTML-escape primitives.
|
|
1138
|
-
*
|
|
1139
|
-
* Implementations follow the OWASP XSS Prevention Cheat Sheet: each
|
|
1140
|
-
* special character is replaced with its decimal or hex entity. The
|
|
1141
|
-
* functions are isomorphic — they have no dependency on the DOM and
|
|
1142
|
-
* can run in any JavaScript environment (browser, Node, Deno, Bun).
|
|
1143
|
-
*
|
|
1144
|
-
* @module @security/escape
|
|
1145
|
-
*/
|
|
1146
|
-
/**
|
|
1147
|
-
* Escape every HTML-significant character in `text` so the result is
|
|
1148
|
-
* safe to interpolate into any HTML context (element body or attribute).
|
|
1149
|
-
*
|
|
1150
|
-
* - Coerces non-strings via `String(value)`; pass `null`/`undefined`
|
|
1151
|
-
* intentionally — they become `"null"`/`"undefined"`.
|
|
1152
|
-
* - Cannot fail; returns `""` for the empty input.
|
|
1153
|
-
*/
|
|
754
|
+
/** HTML and CSS escaping primitives (OWASP XSS Prevention Cheat Sheet). DOM-free. */
|
|
755
|
+
/** Escape `text` for an element body or a *quoted* attribute value; strings only, and unquoted attributes are not covered. */
|
|
1154
756
|
declare function escapeHtml(text: string): string;
|
|
1155
|
-
/**
|
|
1156
|
-
* Escape characters that would break out of a double-quoted HTML
|
|
1157
|
-
* attribute value. Stricter than `escapeHtml` for body content but
|
|
1158
|
-
* preserves URL characters (`/`, `=`, etc.) that are common in
|
|
1159
|
-
* `href` and `src` values.
|
|
1160
|
-
*
|
|
1161
|
-
* Only call this for values that have already been validated as safe
|
|
1162
|
-
* URLs or trusted strings; it does NOT block dangerous protocols.
|
|
1163
|
-
*/
|
|
757
|
+
/** Escape a value for a *quoted* HTML attribute, leaving URL characters intact; it validates no scheme, so run `isSafeUrl()` on `href`/`src` first. */
|
|
1164
758
|
declare function escapeHtmlAttribute(value: string): string;
|
|
1165
759
|
|
|
1166
760
|
/**
|
|
1167
|
-
* URL safety
|
|
1168
|
-
*
|
|
1169
|
-
* Single source of truth for URL-protocol decisions used by both the
|
|
1170
|
-
* inline runtime and the high-level client. The previous version of
|
|
1171
|
-
* this library had two implementations that subtly disagreed; this
|
|
1172
|
-
* module replaces both.
|
|
1173
|
-
*
|
|
1174
|
-
* Policy:
|
|
1175
|
-
* - Allow only the protocols in `SAFE_PROTOCOLS`.
|
|
1176
|
-
* - Reject every other protocol explicitly, including `javascript:`,
|
|
1177
|
-
* `data:`, `vbscript:`, `file:`, `blob:`, and any custom scheme.
|
|
1178
|
-
* - Empty strings are NOT considered safe (this is a tightening from
|
|
1179
|
-
* the legacy behaviour and avoids producing `<a href="">` markup).
|
|
1180
|
-
*
|
|
1181
|
-
* @module @security/url-validator
|
|
1182
|
-
*/
|
|
1183
|
-
/**
|
|
1184
|
-
* Returns `true` only when `url` is one of the explicitly allowed forms:
|
|
1185
|
-
*
|
|
1186
|
-
* - Absolute URL with `http:`, `https:`, `mailto:`, or `tel:` protocol
|
|
1187
|
-
* - Protocol-relative URL (`//example.com/...`)
|
|
1188
|
-
* - Same-origin absolute path (`/foo`)
|
|
1189
|
-
* - Hash or query fragment (`#section`, `?q=1`)
|
|
1190
|
-
* - Plain relative path (`foo/bar`, `./foo`, `../foo`)
|
|
761
|
+
* URL safety: the one scheme allow-list the runtime, the client and the
|
|
762
|
+
* sanitizer share. Empty strings are not safe (`<a href="">` is never emitted).
|
|
1191
763
|
*/
|
|
764
|
+
/** `true` only for absolute `http`/`https`/`mailto`/`tel` URLs, protocol-relative URLs, paths, `#`/`?` fragments and relative paths. */
|
|
1192
765
|
declare function isSafeUrl(url: unknown): boolean;
|
|
1193
|
-
/**
|
|
1194
|
-
* Returns `true` when the URL points to an external HTTP(S) origin.
|
|
1195
|
-
* Protocol-relative URLs (`//example.com/...`) count as external —
|
|
1196
|
-
* they resolve to another origin and need the same `noopener`
|
|
1197
|
-
* hardening as absolute ones.
|
|
1198
|
-
*
|
|
1199
|
-
* Used by renderers to decide whether to add `target="_blank"` and the
|
|
1200
|
-
* `noopener noreferrer` rel attributes to anchors. Inputs that fail
|
|
1201
|
-
* `isSafeUrl()` always return `false`.
|
|
1202
|
-
*/
|
|
766
|
+
/** Whether a safe URL points at another HTTP(S) origin, protocol-relative forms included, and so needs `noopener` hardening. */
|
|
1203
767
|
declare function isExternalHttpUrl(url: string): boolean;
|
|
1204
768
|
|
|
769
|
+
/**
|
|
770
|
+
* `'strict'` (default) strips `id`, `name` and `data-payload-*` and passes
|
|
771
|
+
* other `data-*` only via `allowedDataAttributes`; `'compat'` keeps them.
|
|
772
|
+
*/
|
|
773
|
+
type SanitizerPolicyMode = 'compat' | 'strict';
|
|
1205
774
|
interface SanitizeOptions {
|
|
1206
|
-
/**
|
|
775
|
+
/** Overrides the module default set by `setSanitizerPolicy()`. */
|
|
776
|
+
readonly policy?: SanitizerPolicyMode;
|
|
777
|
+
/** `data-*` attributes (full names) that pass under `'strict'`; `data-payload-*` never does. */
|
|
778
|
+
readonly allowedDataAttributes?: readonly string[];
|
|
779
|
+
/**
|
|
780
|
+
* Keep `input`, `button`, `textarea`, `select`, `option` and `label` — only
|
|
781
|
+
* for markup the page author wrote, never for CMS content. `form`,
|
|
782
|
+
* handlers, `style` and unsafe URLs stay dropped.
|
|
783
|
+
*/
|
|
784
|
+
readonly allowFormControls?: boolean;
|
|
785
|
+
/** Extra tags to allow. Lower-case, untrimmed. */
|
|
1207
786
|
readonly additionalAllowedTags?: readonly string[];
|
|
1208
|
-
/**
|
|
787
|
+
/**
|
|
788
|
+
* Extra per-tag attributes, lower-case. Cannot re-admit handlers, `style`,
|
|
789
|
+
* or — under `'strict'` — `id`, `name` and `data-payload-*`.
|
|
790
|
+
*/
|
|
1209
791
|
readonly additionalAllowedAttributes?: Readonly<Record<string, readonly string[]>>;
|
|
792
|
+
/**
|
|
793
|
+
* The input is a page-author item template, so `'strict'` keeps the
|
|
794
|
+
* applier's reconciliation attributes (`TEMPLATE_ATTRIBUTES`) and nothing
|
|
795
|
+
* else — every other `data-payload-*` is still stripped, so a template can
|
|
796
|
+
* never add a binding.
|
|
797
|
+
*/
|
|
798
|
+
readonly templateMode?: boolean;
|
|
1210
799
|
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Minimal `Document`-shaped surface the sanitizer needs. Any DOM
|
|
1213
|
-
* implementation (browser, jsdom, linkedom, happy-dom, parse5 + a
|
|
1214
|
-
* thin adapter) that produces `<template>`-style elements works.
|
|
1215
|
-
*/
|
|
800
|
+
/** The `Document` surface the sanitizer needs; jsdom, linkedom and happy-dom all provide it. */
|
|
1216
801
|
interface SanitizerDocument {
|
|
1217
802
|
createElement: (tagName: string) => {
|
|
1218
803
|
innerHTML: string;
|
|
1219
804
|
readonly content: ParentNode;
|
|
1220
805
|
};
|
|
1221
806
|
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Inject a `Document` implementation for the sanitizer to use when
|
|
1224
|
-
* `globalThis.document` is unavailable.
|
|
1225
|
-
*
|
|
1226
|
-
* Server-side renderers (Node, Bun, Deno without DOM globals) can wire
|
|
1227
|
-
* this up once at startup with any of the popular pure-JS DOM
|
|
1228
|
-
* libraries:
|
|
1229
|
-
*
|
|
1230
|
-
* ```ts
|
|
1231
|
-
* // linkedom — recommended (smallest, fastest)
|
|
1232
|
-
* import { parseHTML } from 'linkedom';
|
|
1233
|
-
* import { setSanitizerDocument } from 'payload-live-preview';
|
|
1234
|
-
* const { document } = parseHTML('<!doctype html><html><body></body></html>');
|
|
1235
|
-
* setSanitizerDocument(document);
|
|
1236
|
-
* ```
|
|
1237
|
-
*
|
|
1238
|
-
* ```ts
|
|
1239
|
-
* // jsdom — heavier, but already in many SSR pipelines
|
|
1240
|
-
* import { JSDOM } from 'jsdom';
|
|
1241
|
-
* setSanitizerDocument(new JSDOM().window.document);
|
|
1242
|
-
* ```
|
|
1243
|
-
*
|
|
1244
|
-
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
1245
|
-
*/
|
|
807
|
+
/** Supply a `Document` for SSR, e.g. linkedom's `parseHTML(...).document`; `null` clears it. */
|
|
1246
808
|
declare function setSanitizerDocument(doc: SanitizerDocument | null): void;
|
|
1247
809
|
/**
|
|
1248
|
-
* Sanitize `html
|
|
1249
|
-
*
|
|
1250
|
-
* The function never returns `undefined`/`null`. On malformed input the
|
|
1251
|
-
* browser parser silently recovers and the sanitizer continues from
|
|
1252
|
-
* the recovered tree.
|
|
810
|
+
* Sanitize `html`; malformed input is recovered by the parser first.
|
|
1253
811
|
*
|
|
1254
812
|
* @throws {SanitizerEnvironmentError} when no DOM is available.
|
|
1255
813
|
*/
|
|
1256
814
|
declare function sanitizeHtml(html: string, options?: SanitizeOptions): string;
|
|
1257
815
|
|
|
1258
816
|
/**
|
|
1259
|
-
* Content-Security-Policy helpers
|
|
1260
|
-
*
|
|
1261
|
-
* Two responsibilities:
|
|
1262
|
-
*
|
|
1263
|
-
* 1. Generate cryptographic nonces so consumers can opt into a strict
|
|
1264
|
-
* `script-src 'nonce-...'` policy and avoid `'unsafe-inline'`.
|
|
1265
|
-
* 2. Build the `frame-ancestors` directive value, restricting which
|
|
1266
|
-
* origins may embed the preview page in an iframe.
|
|
1267
|
-
*
|
|
1268
|
-
* Both functions are isomorphic — they work in browsers, Node, and
|
|
1269
|
-
* edge runtimes that expose Web Crypto via `globalThis.crypto`.
|
|
1270
|
-
*
|
|
1271
|
-
* @module @security/csp
|
|
1272
|
-
*/
|
|
1273
|
-
/**
|
|
1274
|
-
* Minimal Web-Crypto surface area we actually call. The full `Crypto`
|
|
1275
|
-
* type drags in browser-only declarations that don't typecheck cleanly
|
|
1276
|
-
* against Node's `webcrypto` shape — this narrower interface accepts
|
|
1277
|
-
* both without an unsafe cast.
|
|
817
|
+
* Content-Security-Policy helpers: nonce generation, directive builders and
|
|
818
|
+
* the header merge every adapter uses. Isomorphic; needs only Web Crypto.
|
|
1278
819
|
*/
|
|
820
|
+
/** The one Web Crypto method this module calls; accepts Node's `webcrypto` without a cast. */
|
|
1279
821
|
interface WebCryptoLike {
|
|
1280
822
|
getRandomValues: <T extends ArrayBufferView | null>(array: T) => T;
|
|
1281
823
|
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Inject a Web Crypto implementation to use when `globalThis.crypto`
|
|
1284
|
-
* is unavailable.
|
|
1285
|
-
*
|
|
1286
|
-
* Node.js exposed `globalThis.crypto` globally starting with v19. On
|
|
1287
|
-
* Node 18 (still LTS at time of writing) consumers can flip the
|
|
1288
|
-
* `--experimental-global-webcrypto` flag *or* call this function once
|
|
1289
|
-
* at startup:
|
|
1290
|
-
*
|
|
1291
|
-
* ```ts
|
|
1292
|
-
* import { webcrypto } from 'node:crypto';
|
|
1293
|
-
* import { setCspCrypto } from 'payload-live-preview';
|
|
1294
|
-
* setCspCrypto(webcrypto as { getRandomValues: typeof webcrypto.getRandomValues });
|
|
1295
|
-
* ```
|
|
1296
|
-
*
|
|
1297
|
-
* Browser bundles never need to call this — `globalThis.crypto` is
|
|
1298
|
-
* always present there, and the override only matters when the global
|
|
1299
|
-
* is missing.
|
|
1300
|
-
*
|
|
1301
|
-
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
1302
|
-
*/
|
|
824
|
+
/** Supply a Web Crypto implementation for runtimes without `globalThis.crypto`; `null` clears it. */
|
|
1303
825
|
declare function setCspCrypto(crypto: WebCryptoLike | null): void;
|
|
1304
|
-
/**
|
|
1305
|
-
* Generate a base64url-encoded cryptographic nonce.
|
|
1306
|
-
*
|
|
1307
|
-
* Uses `crypto.getRandomValues` everywhere — falls back to throwing
|
|
1308
|
-
* if no Web Crypto implementation is available, because a predictable
|
|
1309
|
-
* nonce would silently defeat the entire CSP.
|
|
1310
|
-
*
|
|
1311
|
-
* @param bytes Number of random bytes (defaults to 16 → 128-bit entropy).
|
|
1312
|
-
*/
|
|
826
|
+
/** A base64url nonce of `bytes` random bytes (default 16); throws without Web Crypto, since a predictable nonce silently defeats the CSP. */
|
|
1313
827
|
declare function generateCspNonce(bytes?: number): string;
|
|
1314
828
|
interface FrameAncestorsOptions {
|
|
1315
829
|
readonly self?: boolean;
|
|
1316
830
|
readonly origins?: readonly string[];
|
|
1317
831
|
readonly allowNone?: boolean;
|
|
1318
832
|
}
|
|
1319
|
-
/**
|
|
1320
|
-
* Build the value for `frame-ancestors` in a `Content-Security-Policy`
|
|
1321
|
-
* header.
|
|
1322
|
-
*
|
|
1323
|
-
* Returns `'none'` if `allowNone: true` and no other sources are
|
|
1324
|
-
* provided. Otherwise concatenates `'self'` (if requested) with the
|
|
1325
|
-
* given origins, deduplicating and trimming entries.
|
|
1326
|
-
*
|
|
1327
|
-
* @example
|
|
1328
|
-
* `frame-ancestors ${buildFrameAncestors({ self: true, origins: [adminUrl] })}`
|
|
1329
|
-
*/
|
|
833
|
+
/** The `frame-ancestors` value: `'self'` and the origins, deduplicated; `'none'` when empty. */
|
|
1330
834
|
declare function buildFrameAncestors(options?: FrameAncestorsOptions): string;
|
|
1331
835
|
/**
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
* `'strict-dynamic'` is **opt-in**: under CSP 3 it makes browsers
|
|
1336
|
-
* ignore `'self'` and every host source in the directive, so any
|
|
1337
|
-
* parser-inserted script without a nonce (framework hydration scripts,
|
|
1338
|
-
* consumer `<script>` tags) would be blocked. Only enable it when every
|
|
1339
|
-
* script on the page carries the nonce.
|
|
836
|
+
* A `script-src` value permitting one nonce plus optional extra sources.
|
|
837
|
+
* `'strict-dynamic'` is opt-in: it makes CSP 3 browsers ignore `'self'` and
|
|
838
|
+
* host sources, so every script on the page must then carry the nonce.
|
|
1340
839
|
*/
|
|
1341
840
|
declare function buildScriptSrcWithNonce(nonce: string, options?: {
|
|
1342
841
|
readonly self?: boolean;
|
|
@@ -1345,126 +844,235 @@ declare function buildScriptSrcWithNonce(nonce: string, options?: {
|
|
|
1345
844
|
}): string;
|
|
1346
845
|
|
|
1347
846
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
*
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
847
|
+
* The authorized preview context's shape and brand, in the leaf `types` domain
|
|
848
|
+
* so producer and consumers recognise the same context. See ADR 0006.
|
|
849
|
+
*/
|
|
850
|
+
/**
|
|
851
|
+
* The brand key, in the type and on the object.
|
|
852
|
+
*
|
|
853
|
+
* This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per
|
|
854
|
+
* *declaration*, and the published entries each carry their own declaration
|
|
855
|
+
* file — so `AuthorizedPreviewContext` from the root barrel was a different
|
|
856
|
+
* type from the one in `./server` or in an adapter, and the documented flow
|
|
857
|
+
* (authorize once, hand the context to the adapter hook and to the draft read)
|
|
858
|
+
* did not type-check in any combination a consumer can write.
|
|
859
|
+
*
|
|
860
|
+
* That is the same trap the runtime brand below already avoids by using
|
|
861
|
+
* `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the
|
|
862
|
+
* type. A shared string literal is identical in every bundle for the same
|
|
863
|
+
* reason a registry symbol is.
|
|
864
|
+
*
|
|
865
|
+
* It is not what makes a context trustworthy. `isAuthorizedPreviewContext`
|
|
866
|
+
* decides that, on the registry symbol and on `Object.isFrozen`, and every
|
|
867
|
+
* consumption point calls it — the adapter policy, the draft read and the
|
|
868
|
+
* binding emitter. Writing this key by hand buys nothing.
|
|
869
|
+
*/
|
|
870
|
+
declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
|
|
871
|
+
type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
|
|
872
|
+
/** What a context is bound to; every field is optional because every strategy binds differently. */
|
|
873
|
+
interface AuthorizedPreviewScope {
|
|
874
|
+
/** The site origin the credential was issued for. */
|
|
875
|
+
readonly audience?: string;
|
|
876
|
+
/** The request pathname the credential is valid for. */
|
|
877
|
+
readonly path?: string;
|
|
878
|
+
readonly locale?: string;
|
|
879
|
+
}
|
|
880
|
+
/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
|
|
881
|
+
interface AuthorizedPreviewContext {
|
|
882
|
+
readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;
|
|
883
|
+
readonly strategy: PreviewAuthorizationStrategyName;
|
|
884
|
+
/** The user id, token subject, or whatever the verifier named. */
|
|
885
|
+
readonly subject: string | undefined;
|
|
886
|
+
/** Unix milliseconds. */
|
|
887
|
+
readonly authorizedAt: number;
|
|
888
|
+
/** Unix milliseconds, or `undefined` when the strategy does not know. */
|
|
889
|
+
readonly expiresAt: number | undefined;
|
|
890
|
+
readonly scope: AuthorizedPreviewScope;
|
|
891
|
+
/** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
|
|
892
|
+
readonly payloadHeaders: Readonly<Record<string, string>>;
|
|
893
|
+
}
|
|
894
|
+
/** Whether `value` was produced by `authorizePreviewRequest()`; a copy or JSON round trip is not. */
|
|
895
|
+
declare function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext;
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Where the runtime is running: framed, opened, or on a developer's machine.
|
|
899
|
+
* Nothing reads bundler-injected variables; a Vite consumer passes
|
|
900
|
+
* `import.meta.env.X` explicitly (docs/astro.md).
|
|
901
|
+
*/
|
|
902
|
+
declare function isInIframe(): boolean;
|
|
903
|
+
declare function isInPopup(): boolean;
|
|
904
|
+
/** An iframe or a popup; top-level navigation never starts the preview. */
|
|
905
|
+
declare function isInPreviewContext(): boolean;
|
|
906
|
+
/** Under Node, `NODE_ENV !== 'production'`; in a browser the hostname, which is the only signal a bundle has. */
|
|
907
|
+
declare function isDevMode(): boolean;
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Which admin origins the runtime trusts: explicit ones always, the
|
|
911
|
+
* `document.referrer` origin only when none is configured, any localhost port
|
|
912
|
+
* in development. See docs/security.md §1.
|
|
1378
913
|
*/
|
|
914
|
+
interface OriginDetectorOptions {
|
|
915
|
+
/** Explicit origins to trust. Always allowed. */
|
|
916
|
+
readonly additionalOrigins?: readonly string[];
|
|
917
|
+
/** Set to `false` to ignore `document.referrer`. */
|
|
918
|
+
readonly enableReferrerDetection?: boolean;
|
|
919
|
+
/** Set to `false` to ignore the localhost pattern even in development. */
|
|
920
|
+
readonly enableLocalhostMatching?: boolean;
|
|
921
|
+
/** Override dev-mode detection. */
|
|
922
|
+
readonly forceDevMode?: boolean;
|
|
923
|
+
/** Override `document.referrer` for tests. */
|
|
924
|
+
readonly referrer?: string;
|
|
925
|
+
}
|
|
926
|
+
/** The trusted-origin policy: the union of the candidate sources, narrowed to one by `lockOrigin()`. */
|
|
927
|
+
declare class OriginDetector {
|
|
928
|
+
#private;
|
|
929
|
+
constructor(options?: OriginDetectorOptions);
|
|
930
|
+
/** Whether `origin` is trusted: the locked origin alone once locked, otherwise the policy above. */
|
|
931
|
+
matches(origin: string): boolean;
|
|
932
|
+
/** Narrow trust to `origin`; refused (`false`) when it is not currently trusted. */
|
|
933
|
+
lockOrigin(origin: string): boolean;
|
|
934
|
+
/** Release the lock (heartbeat timeout) so another allow-listed origin may reconnect. Returns the previous lock. */
|
|
935
|
+
unlockOrigin(): string | undefined;
|
|
936
|
+
get lockedOrigin(): string | undefined;
|
|
937
|
+
/** Every origin the `ready` handshake is broadcast to; the localhost pattern expands to the common dev ports. */
|
|
938
|
+
enumerate(): string[];
|
|
939
|
+
/** Whether `document.referrer` contributed an origin (absent under `Referrer-Policy: no-referrer`). */
|
|
940
|
+
get referrerWasAvailable(): boolean;
|
|
941
|
+
/** Framed, outside development, with no trusted origin at all: the host should warn (LP0101). */
|
|
942
|
+
get isProductionUnconfigured(): boolean;
|
|
943
|
+
/** The referrer is the only trust source, so any framing site may drive the preview (LP0102). */
|
|
944
|
+
get isReferrerOnlyTrust(): boolean;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** The locale for `Intl` renders before Payload names one: `<html lang>`, then `navigator.language`, then `'en'`. */
|
|
948
|
+
declare function detectInitialLocale(): string;
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Protocol negotiation. A capability is granted either by the version a peer
|
|
952
|
+
* announces or by observation, since the stock Payload admin announces none:
|
|
953
|
+
* what it can do is read off the messages it sends. See ADR 0010.
|
|
954
|
+
*/
|
|
955
|
+
/** Bumped only when this library's understanding of the wire format changes. */
|
|
1379
956
|
declare const LIBRARY_PROTOCOL_VERSION = 4;
|
|
1380
|
-
/**
|
|
1381
|
-
|
|
1382
|
-
|
|
957
|
+
/** How a capability becomes active. */
|
|
958
|
+
type CapabilitySource = 'version' | 'observed';
|
|
959
|
+
interface CapabilityDeclaration {
|
|
960
|
+
/** Enabled once the negotiated version reaches this (for announced peers). */
|
|
961
|
+
readonly since: number;
|
|
962
|
+
/** The wire signal that grants this capability to a peer announcing no version. */
|
|
963
|
+
readonly observed?: 'schema' | 'locale' | 'preview-token' | 'document-event' | 'relationship-event';
|
|
964
|
+
}
|
|
965
|
+
/** What activates each capability; the prose lives in `CAPABILITY_DOCUMENTATION`. */
|
|
966
|
+
declare const CAPABILITY_DECLARATIONS: {
|
|
967
|
+
readonly basic: {
|
|
968
|
+
readonly since: 1;
|
|
969
|
+
};
|
|
970
|
+
readonly 'schema-json': {
|
|
971
|
+
readonly since: 2;
|
|
972
|
+
readonly observed: "schema";
|
|
973
|
+
};
|
|
974
|
+
readonly locale: {
|
|
975
|
+
readonly since: 2;
|
|
976
|
+
readonly observed: "locale";
|
|
977
|
+
};
|
|
978
|
+
readonly 'preview-token': {
|
|
979
|
+
readonly since: 3;
|
|
980
|
+
readonly observed: "preview-token";
|
|
981
|
+
};
|
|
982
|
+
readonly 'nested-arrays': {
|
|
983
|
+
readonly since: 4;
|
|
984
|
+
};
|
|
985
|
+
readonly 'recursive-diffs': {
|
|
986
|
+
readonly since: 4;
|
|
987
|
+
};
|
|
988
|
+
readonly 'document-events': {
|
|
989
|
+
readonly since: 4;
|
|
990
|
+
readonly observed: "document-event";
|
|
991
|
+
};
|
|
992
|
+
readonly 'relationship-events': {
|
|
993
|
+
readonly since: 4;
|
|
994
|
+
readonly observed: "relationship-event";
|
|
995
|
+
};
|
|
996
|
+
};
|
|
997
|
+
type ProtocolCapability = keyof typeof CAPABILITY_DECLARATIONS;
|
|
998
|
+
/** Every declared capability, in declaration order. */
|
|
999
|
+
declare const PROTOCOL_CAPABILITIES: readonly ProtocolCapability[];
|
|
1383
1000
|
interface ProtocolNegotiation {
|
|
1384
1001
|
/** This library's protocol version. */
|
|
1385
1002
|
readonly ours: number;
|
|
1386
|
-
/** The remote party's version, when
|
|
1003
|
+
/** The remote party's version, when it announced one. */
|
|
1387
1004
|
readonly theirs: number | undefined;
|
|
1388
1005
|
/** `min(ours, theirs ?? 1)` — the version both sides actually share. */
|
|
1389
1006
|
readonly negotiated: number;
|
|
1390
|
-
/**
|
|
1391
|
-
readonly capabilities: ReadonlySet<
|
|
1007
|
+
/** Capabilities active at the negotiated version or seen on the wire. */
|
|
1008
|
+
readonly capabilities: ReadonlySet<ProtocolCapability>;
|
|
1009
|
+
/** The subset that was observed on the wire rather than granted by version. */
|
|
1010
|
+
readonly observed: ReadonlySet<ProtocolCapability>;
|
|
1392
1011
|
}
|
|
1393
|
-
/**
|
|
1394
|
-
|
|
1395
|
-
*
|
|
1396
|
-
* `theirs === undefined` collapses to v1 — assume the remote party is
|
|
1397
|
-
* an older Payload that does not advertise a version.
|
|
1398
|
-
*
|
|
1399
|
-
* Non-finite or non-positive `theirs` values fall back to v1 as well —
|
|
1400
|
-
* we never trust an obviously-corrupted negotiation byte.
|
|
1401
|
-
*/
|
|
1402
|
-
declare function negotiateProtocol(theirs: number | undefined): ProtocolNegotiation;
|
|
1012
|
+
/** Deterministic; the runtime re-runs it whenever either input changes. */
|
|
1013
|
+
declare function negotiateProtocol(theirs: number | undefined, observed?: Iterable<ProtocolCapability>): ProtocolNegotiation;
|
|
1403
1014
|
declare function hasCapability(negotiation: ProtocolNegotiation, capability: ProtocolCapability): boolean;
|
|
1404
1015
|
|
|
1405
1016
|
/**
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1409
|
-
* The same path strings the runtime reads from `data-payload-field`
|
|
1410
|
-
* are typed here, so a typo like `'heroTitlee'` is rejected at compile
|
|
1411
|
-
* time *and* the runtime side does not need to change.
|
|
1412
|
-
*
|
|
1413
|
-
* @module @dsl/paths
|
|
1017
|
+
* What each capability tells the runtime, for `inspect()` and consumer
|
|
1018
|
+
* diagnostics. Kept out of the runtime module so the inline bundle ships the
|
|
1019
|
+
* rules and not this prose. See ADR 0010.
|
|
1414
1020
|
*/
|
|
1021
|
+
|
|
1022
|
+
interface CapabilityDocumentation {
|
|
1023
|
+
/** The runtime behaviour that depends on the capability. */
|
|
1024
|
+
readonly gates: string;
|
|
1025
|
+
/** What the runtime does without it. */
|
|
1026
|
+
readonly fallback: string;
|
|
1027
|
+
}
|
|
1028
|
+
declare const CAPABILITY_DOCUMENTATION: Readonly<Record<ProtocolCapability, CapabilityDocumentation>>;
|
|
1029
|
+
|
|
1415
1030
|
/**
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1031
|
+
* What the peer does, derived from the capabilities its messages showed —
|
|
1032
|
+
* the runtime never asks which Payload version it is talking to. Unknown
|
|
1033
|
+
* until the admin reveals its shape. See ADR 0010.
|
|
1418
1034
|
*/
|
|
1419
|
-
|
|
1035
|
+
|
|
1036
|
+
type ProtocolProfileName = 'unknown' | 'payload-2' | 'payload-3';
|
|
1037
|
+
interface ProtocolProfile {
|
|
1038
|
+
readonly name: ProtocolProfileName;
|
|
1039
|
+
/** Where field types come from. */
|
|
1040
|
+
readonly fieldTyping: 'schema' | 'heuristic';
|
|
1041
|
+
/**
|
|
1042
|
+
* Who populates relationships. Payload 2.x's admin posts populated data
|
|
1043
|
+
* (it merges client-side with `fieldSchemaJSON`); Payload 3.x posts raw
|
|
1044
|
+
* form values and the runtime asks the REST API when `serverURL` is set.
|
|
1045
|
+
*/
|
|
1046
|
+
readonly populatesRelationships: 'admin' | 'server';
|
|
1047
|
+
/** Whether the peer reports document saves and relationship edits. */
|
|
1048
|
+
readonly documentEvents: boolean;
|
|
1049
|
+
}
|
|
1420
1050
|
/**
|
|
1421
|
-
*
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
1424
|
-
*
|
|
1051
|
+
* `schema-json` on the wire is the Payload 2.x signature: 3.x removed
|
|
1052
|
+
* `fieldSchemaJSON`. Document or relationship events are 3.x-only. Until
|
|
1053
|
+
* either shows, the peer is unknown and treated like 3.x for merging — the
|
|
1054
|
+
* conservative choice, because a needless REST merge costs a request while
|
|
1055
|
+
* a missing one loses populated relationships.
|
|
1425
1056
|
*/
|
|
1057
|
+
declare function detectProtocolProfile(observed: ReadonlySet<ProtocolCapability>): ProtocolProfile;
|
|
1058
|
+
|
|
1059
|
+
/** Type-level field paths, so a binding name is checked at compile time against the codegen schema. */
|
|
1060
|
+
/** String keys of `T`. */
|
|
1061
|
+
type FieldName<T> = Extract<keyof T, string>;
|
|
1062
|
+
/** Dotted paths into `T`, capped at three levels so IntelliSense stays usable. */
|
|
1426
1063
|
type FieldPath<T, Depth extends 0 | 1 | 2 | 3 = 3> = Depth extends 0 ? never : T extends readonly (infer U)[] ? FieldPath<U, Prev<Depth>> : T extends object ? {
|
|
1427
1064
|
[K in Extract<keyof T, string>]: K | (T[K] extends object ? `${K}.${FieldPath<T[K], Prev<Depth>>}` : never);
|
|
1428
1065
|
}[Extract<keyof T, string>] : never;
|
|
1429
|
-
/** Decrement helper for depth-bounded recursion. */
|
|
1430
1066
|
type Prev<N extends 0 | 1 | 2 | 3> = N extends 3 ? 2 : N extends 2 ? 1 : N extends 1 ? 0 : 0;
|
|
1431
|
-
/**
|
|
1432
|
-
* Resolve the value type at a dotted path. Returns `unknown` when the
|
|
1433
|
-
* path does not exist on `T`, so consumers can opt into stricter
|
|
1434
|
-
* `keyof`-based DSLs when they want absolute type safety.
|
|
1435
|
-
*/
|
|
1067
|
+
/** The value type at a dotted path, or `unknown` when the path does not exist. */
|
|
1436
1068
|
type ValueAt<T, P extends string> = P extends `${infer Head}.${infer Rest}` ? Head extends keyof T ? T[Head] extends readonly (infer U)[] ? ValueAt<U, Rest> : T[Head] extends object ? ValueAt<T[Head], Rest> : unknown : unknown : P extends keyof T ? T[P] : unknown;
|
|
1437
1069
|
|
|
1438
1070
|
/**
|
|
1439
|
-
* Typed
|
|
1440
|
-
*
|
|
1441
|
-
* of the field name.
|
|
1442
|
-
*
|
|
1443
|
-
* Two forms are exposed:
|
|
1444
|
-
*
|
|
1445
|
-
* - **`bind<Schema>('heroTitle')`** — string-literal form. Lightest
|
|
1446
|
-
* possible runtime (no Proxy), but a rename in the IDE does NOT
|
|
1447
|
-
* follow into the string.
|
|
1448
|
-
* - **`bindByPath<Schema>(s => s.heroTitle)`** — proxy form. The
|
|
1449
|
-
* callback walks a Proxy that records the access path, returning
|
|
1450
|
-
* the same shape as `bind` so the two are interchangeable. Slightly
|
|
1451
|
-
* heavier runtime, but rename-safe and discourages typos.
|
|
1452
|
-
*
|
|
1453
|
-
* Both helpers also accept a richer return — `bind('image', 'src')` —
|
|
1454
|
-
* to bind an attribute other than the default `data-payload-field`,
|
|
1455
|
-
* matching the cache resolver's `data-payload-attribute` flow.
|
|
1456
|
-
*
|
|
1457
|
-
* @module @dsl/bind
|
|
1071
|
+
* Typed helpers emitting the `data-payload-*` attributes the runtime reads.
|
|
1072
|
+
* `bindByPath` records the path through a Proxy, so a rename follows.
|
|
1458
1073
|
*/
|
|
1459
1074
|
|
|
1460
|
-
/**
|
|
1461
|
-
* Shape returned by every binding helper — a spreadable HTML attribute
|
|
1462
|
-
* record. Astro / JSX / Svelte all accept this style directly:
|
|
1463
|
-
*
|
|
1464
|
-
* ```astro
|
|
1465
|
-
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1466
|
-
* ```
|
|
1467
|
-
*/
|
|
1075
|
+
/** A spreadable attribute record: `<h1 {...bind<Homepage>('heroTitle')}>`. */
|
|
1468
1076
|
interface FieldBindingAttributes {
|
|
1469
1077
|
readonly 'data-payload-field': string;
|
|
1470
1078
|
readonly 'data-payload-attribute'?: string;
|
|
@@ -1472,140 +1080,79 @@ interface FieldBindingAttributes {
|
|
|
1472
1080
|
readonly 'data-payload-richtext'?: string;
|
|
1473
1081
|
readonly 'data-payload-html'?: string;
|
|
1474
1082
|
readonly 'data-payload-locale'?: string;
|
|
1083
|
+
readonly 'data-payload-alt'?: string;
|
|
1084
|
+
readonly 'data-payload-href'?: string;
|
|
1085
|
+
readonly 'data-payload-array-template'?: string;
|
|
1475
1086
|
}
|
|
1476
1087
|
interface BindOptions {
|
|
1477
|
-
/**
|
|
1088
|
+
/** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */
|
|
1478
1089
|
readonly attribute?: string;
|
|
1479
|
-
/** Explicit field
|
|
1090
|
+
/** Explicit field type, bypassing schema detection. */
|
|
1480
1091
|
readonly type?: string;
|
|
1481
|
-
/**
|
|
1482
|
-
* Mark the binding as Lexical rich text. Usually detected automatically;
|
|
1483
|
-
* set it where the initial render is empty and there is nothing to detect.
|
|
1484
|
-
*/
|
|
1092
|
+
/** Mark the binding as Lexical rich text; needed only when the initial render is empty. */
|
|
1485
1093
|
readonly richtext?: boolean;
|
|
1486
1094
|
/** Render the value as sanitised HTML rather than text. */
|
|
1487
1095
|
readonly html?: boolean;
|
|
1488
1096
|
/** Lock this element to one locale, overriding the message locale. */
|
|
1489
1097
|
readonly locale?: string;
|
|
1098
|
+
/** Field whose value becomes the image `alt`. */
|
|
1099
|
+
readonly alt?: string;
|
|
1100
|
+
/** Field whose value becomes the link `href`. */
|
|
1101
|
+
readonly href?: string;
|
|
1102
|
+
/** Markup per array item with `{{value}}` placeholders. */
|
|
1103
|
+
readonly arrayTemplate?: string;
|
|
1490
1104
|
}
|
|
1491
|
-
/**
|
|
1492
|
-
* String-literal binding helper.
|
|
1493
|
-
*
|
|
1494
|
-
* ```ts
|
|
1495
|
-
* import { bind } from 'payload-live-preview';
|
|
1496
|
-
* import type { Homepage } from './payload-types';
|
|
1497
|
-
*
|
|
1498
|
-
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1499
|
-
* <img {...bind<Homepage>('heroImage', { attribute: 'src' })} />
|
|
1500
|
-
* ```
|
|
1501
|
-
*
|
|
1502
|
-
* The generic `T` is the schema interface emitted by `pll-codegen`.
|
|
1503
|
-
* `field` is constrained to `keyof T` so misspellings fail at compile
|
|
1504
|
-
* time. When `T` is omitted, the helper accepts any string — useful
|
|
1505
|
-
* for incremental adoption.
|
|
1506
|
-
*/
|
|
1105
|
+
/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */
|
|
1507
1106
|
declare function bind<T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions): FieldBindingAttributes;
|
|
1508
|
-
/**
|
|
1509
|
-
* Proxy-path binding helper.
|
|
1510
|
-
*
|
|
1511
|
-
* ```ts
|
|
1512
|
-
* <h1 {...bindByPath<Homepage>(d => d.heroTitle)}>{data.heroTitle}</h1>
|
|
1513
|
-
* ```
|
|
1514
|
-
*
|
|
1515
|
-
* The picker is invoked once with a recording Proxy; the recorded path
|
|
1516
|
-
* is joined with `.` and emitted as the binding name. Property access
|
|
1517
|
-
* on nested objects and arrays both work — `d => d.slides[0].title`
|
|
1518
|
-
* becomes `slides.title` (array indices are normalised away because the
|
|
1519
|
-
* runtime resolves bindings against schema, not against positional
|
|
1520
|
-
* indices).
|
|
1521
|
-
*/
|
|
1107
|
+
/** `bindByPath<Homepage>(d => d.slides[0].title)` → `slides.title`; array indices are dropped because bindings resolve against the schema. */
|
|
1522
1108
|
declare function bindByPath<T = Record<string, unknown>>(picker: (data: T) => unknown, options?: BindOptions): FieldBindingAttributes;
|
|
1523
1109
|
|
|
1524
1110
|
/**
|
|
1525
|
-
*
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
* field, and `data-payload-owner` names a global, a collection and often a
|
|
1529
|
-
* document id. Emitted unconditionally they publish the shape of the content
|
|
1530
|
-
* model — and the identity of documents — to every anonymous visitor and
|
|
1531
|
-
* crawler of the page.
|
|
1532
|
-
*
|
|
1533
|
-
* The gate belongs to the application, not to this package: it is the same
|
|
1534
|
-
* decision that already controls draft reads and cache policy. What this
|
|
1535
|
-
* module provides is a place to apply that decision **once per request** so no
|
|
1536
|
-
* individual call site can forget it, and an emission unit that cannot be
|
|
1537
|
-
* partially suppressed.
|
|
1538
|
-
*
|
|
1539
|
-
* That second property matters more than it looks. A binding is not one
|
|
1540
|
-
* attribute: a field travels with its type, its locale, its rich-text marker
|
|
1541
|
-
* and its owner. Gating only the field name leaves the companions behind,
|
|
1542
|
-
* which discloses the taxonomy anyway and leaves the runtime looking at a
|
|
1543
|
-
* binding whose field is gone.
|
|
1544
|
-
*
|
|
1545
|
-
* @module @dsl/preview-bindings
|
|
1111
|
+
* Binding helpers gated on one authorization verdict per request: the
|
|
1112
|
+
* attributes publish the content model, so an unauthorized response carries
|
|
1113
|
+
* none and a binding is suppressed whole, never in part. See ADR 0006.
|
|
1546
1114
|
*/
|
|
1547
1115
|
|
|
1548
|
-
/** Owner marker naming the document a subtree belongs to. */
|
|
1549
1116
|
interface OwnerBindingAttributes {
|
|
1550
1117
|
readonly 'data-payload-owner': string;
|
|
1551
1118
|
}
|
|
1552
|
-
/**
|
|
1553
|
-
* The complete absence of binding attributes.
|
|
1554
|
-
*
|
|
1555
|
-
* Spreading this into markup contributes nothing, which is the point: an
|
|
1556
|
-
* unauthorized response must be byte-identical to one that never knew about
|
|
1557
|
-
* live preview.
|
|
1558
|
-
*/
|
|
1119
|
+
/** No attributes at all: an unauthorized response is byte-identical to one that never knew about live preview. */
|
|
1559
1120
|
type SuppressedBinding = Readonly<Record<string, never>>;
|
|
1560
|
-
interface
|
|
1561
|
-
/**
|
|
1562
|
-
* The application's own authorization verdict for this request.
|
|
1563
|
-
*
|
|
1564
|
-
* Never derive this from `?preview=true`, an iframe destination or a
|
|
1565
|
-
* referer — those are client-controlled intent signals. Use the same
|
|
1566
|
-
* verified result that decides whether drafts may be read.
|
|
1567
|
-
*/
|
|
1568
|
-
readonly authorized: boolean;
|
|
1569
|
-
/**
|
|
1570
|
-
* Document this subtree belongs to, emitted as `data-payload-owner` by
|
|
1571
|
-
* {@link PreviewBindings.owner}. Required only when the page previews more
|
|
1572
|
-
* than one document and the runtime runs with `scopeBindingsByOwner`.
|
|
1573
|
-
*/
|
|
1121
|
+
interface PreviewBindingsCommonOptions {
|
|
1122
|
+
/** Document this subtree belongs to, emitted as `data-payload-owner`; needed with `scopeBindingsByOwner`. */
|
|
1574
1123
|
readonly owner?: string;
|
|
1575
1124
|
}
|
|
1576
|
-
/**
|
|
1125
|
+
/** The verdict from `authorizePreviewRequest()`, or `null` for a public response. Only a branded context authorizes emission. */
|
|
1126
|
+
interface PreviewBindingsOptions extends PreviewBindingsCommonOptions {
|
|
1127
|
+
readonly authorization: AuthorizedPreviewContext | null;
|
|
1128
|
+
}
|
|
1129
|
+
/** Request-scoped binding helpers carrying one authorization decision. */
|
|
1577
1130
|
interface PreviewBindings {
|
|
1578
|
-
/** The verdict these helpers were built with. */
|
|
1579
1131
|
readonly authorized: boolean;
|
|
1580
|
-
/** Typed field binding, or nothing
|
|
1132
|
+
/** Typed field binding, or nothing while unauthorized. */
|
|
1581
1133
|
bind: <T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions) => FieldBindingAttributes | SuppressedBinding;
|
|
1582
|
-
/** Rename-safe field binding, or nothing
|
|
1134
|
+
/** Rename-safe field binding, or nothing while unauthorized. */
|
|
1583
1135
|
bindByPath: <T = Record<string, unknown>>(picker: (data: T) => unknown, options?: BindOptions) => FieldBindingAttributes | SuppressedBinding;
|
|
1584
|
-
/**
|
|
1585
|
-
* Owner marker for the element that owns this subtree, or nothing at all
|
|
1586
|
-
* while unauthorized or when no owner was configured.
|
|
1587
|
-
*/
|
|
1136
|
+
/** Owner marker for this subtree, or nothing while unauthorized or without an owner. */
|
|
1588
1137
|
owner: () => OwnerBindingAttributes | SuppressedBinding;
|
|
1589
1138
|
}
|
|
1139
|
+
/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */
|
|
1140
|
+
declare function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings;
|
|
1141
|
+
|
|
1590
1142
|
/**
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1593
|
-
*
|
|
1594
|
-
* const preview = createPreviewBindings({
|
|
1595
|
-
* authorized: authorization !== null,
|
|
1596
|
-
* owner: `global:${slug}`,
|
|
1597
|
-
* });
|
|
1598
|
-
* ```
|
|
1599
|
-
* ```astro
|
|
1600
|
-
* <section {...preview.owner()}>
|
|
1601
|
-
* <h1 {...preview.bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1602
|
-
* </section>
|
|
1603
|
-
* ```
|
|
1604
|
-
*
|
|
1605
|
-
* While unauthorized every helper returns an empty attribute set, so the
|
|
1606
|
-
* public response carries no `data-payload-*` at all.
|
|
1143
|
+
* Hydrated islands own their subtree, so the runtime never patches inside one.
|
|
1144
|
+
* Instead every flush dispatches `payload-live-preview:update` on each island
|
|
1145
|
+
* root with the update in `detail`. `data-payload-island="patch"` opts back in.
|
|
1607
1146
|
*/
|
|
1608
|
-
declare
|
|
1147
|
+
declare const ISLAND_EVENT = "payload-live-preview:update";
|
|
1148
|
+
interface IslandUpdateDetail {
|
|
1149
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
1150
|
+
readonly revision: number;
|
|
1151
|
+
readonly receivedAt: number;
|
|
1152
|
+
readonly locale: string | undefined;
|
|
1153
|
+
}
|
|
1154
|
+
/** Whether `element` (or an ancestor) is an island that did not opt into patching. */
|
|
1155
|
+
declare function isInsideIsland(element: Element): boolean;
|
|
1609
1156
|
|
|
1610
1157
|
/**
|
|
1611
1158
|
* Narrow public entry — the minimum API for sites that only need to
|
|
@@ -1628,4 +1175,4 @@ declare function createPreviewBindings(options: PreviewBindingsOptions): Preview
|
|
|
1628
1175
|
|
|
1629
1176
|
declare const CORE_ENTRY = true;
|
|
1630
1177
|
|
|
1631
|
-
export { type BindOptions, CORE_ENTRY, type CachedElement, type ConnectionStatus, DIAGNOSTIC_CODES, type DiagnosticCode, EventEmitter, type EventHandler, type FieldBindingAttributes, type FieldName, type FieldPath, type FieldRenderer, type FieldType, type FrameAncestorsOptions, type InspectionBindings, type InspectionOrigins, type InspectionProtocol, type InspectionRevisions, type InspectionScheduler, LIBRARY_PROTOCOL_VERSION, LivePreviewClient, type LivePreviewClientConfig, type LivePreviewEventMap, type LivePreviewInspection, OriginDetector, type OwnerBindingAttributes, type PayloadFieldSchema, type PayloadFieldType, type PayloadLivePreviewData, type PayloadLivePreviewMessage, type PreviewBindings, type PreviewBindingsOptions, type ProtocolCapability, type ProtocolNegotiation, type RenderContext, type SanitizerDocument, type SuppressedBinding, type Unsubscribe, VERSION, type ValueAt, bind, bindByPath, buildFrameAncestors, buildScriptSrcWithNonce, createPreviewBindings, detectInitialLocale, escapeHtml, escapeHtmlAttribute, generateCspNonce, hasCapability, initLivePreview, isDevMode, isExternalHttpUrl, isInIframe, isInPopup, isInPreviewContext, isSafeUrl, negotiateProtocol, sanitizeHtml, setCspCrypto, setSanitizerDocument };
|
|
1178
|
+
export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewScope, type BindOptions, CAPABILITY_DECLARATIONS, CAPABILITY_DOCUMENTATION, CORE_ENTRY, type CachedElement, type CapabilityDeclaration, type CapabilityDocumentation, type CapabilitySource, type ConnectionStatus, type CustomRendererKey, DIAGNOSTIC_CODES, type DefaultsProfile, type DiagnosticCode, EventEmitter, type EventHandler, type EventSourcePolicy, type FieldBindingAttributes, type FieldName, type FieldPath, type FieldRenderer, type FieldType, type FragmentContext, type FragmentReport, type FragmentStrategy, type FrameAncestorsOptions, ISLAND_EVENT, type InspectionBindings, type InspectionFragments, type InspectionOrigins, type InspectionProtocol, type InspectionRevisions, type InspectionRoute, type InspectionScheduler, type IslandUpdateDetail, LIBRARY_PROTOCOL_VERSION, LivePreviewClient, type LivePreviewClientConfig, type LivePreviewEventMap, type LivePreviewInspection, OriginDetector, type OwnerBindingAttributes, PROTOCOL_CAPABILITIES, type PayloadDocumentEventDetail, type PayloadFieldCondition, type PayloadFieldSchema, type PayloadFieldType, type PayloadLivePreviewData, type PayloadLivePreviewMessage, type PluginCompatibility, type PluginInspection, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsCommonOptions, type PreviewBindingsOptions, type ProtocolCapability, type ProtocolNegotiation, type ProtocolProfile, type ProtocolProfileName, type RenderContext, type RendererKey, type RichTextRenderer, type RouteContext, type RouteStrategy, type SanitizerDocument, type SanitizerPolicyMode, type StrategyHandlers, type SuppressedBinding, type Unsubscribe, type UpdateSource, VERSION, type ValueAt, bind, bindByPath, buildFrameAncestors, buildScriptSrcWithNonce, createPreviewBindings, detectInitialLocale, detectProtocolProfile, escapeHtml, escapeHtmlAttribute, generateCspNonce, hasCapability, initLivePreview, isAuthorizedPreviewContext, isDevMode, isExternalHttpUrl, isInIframe, isInPopup, isInPreviewContext, isInsideIsland, isSafeUrl, negotiateProtocol, sanitizeHtml, setCspCrypto, setSanitizerDocument };
|