payload-live-preview 2.0.6 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +148 -38
  2. package/dist/adapters/astro/FragmentBridge.astro +25 -0
  3. package/dist/adapters/astro/PreviewBoundary.astro +2 -4
  4. package/dist/adapters/astro/RichText.astro +26 -5
  5. package/dist/adapters/astro/index.d.ts +41 -9
  6. package/dist/adapters/astro/index.js +1 -1
  7. package/dist/adapters/astro/index.js.map +1 -1
  8. package/dist/adapters/astro/middleware-entry.d.ts +3 -3
  9. package/dist/adapters/astro/middleware-entry.js +1 -1
  10. package/dist/adapters/astro/middleware-entry.js.map +1 -1
  11. package/dist/adapters/nextjs/index.d.ts +17 -12
  12. package/dist/adapters/nextjs/index.js +1 -1
  13. package/dist/adapters/nextjs/index.js.map +1 -1
  14. package/dist/adapters/nuxt/index.d.ts +11 -6
  15. package/dist/adapters/nuxt/index.js +1 -1
  16. package/dist/adapters/nuxt/index.js.map +1 -1
  17. package/dist/adapters/nuxt/module.d.ts +64 -13
  18. package/dist/adapters/nuxt/module.js +1 -1
  19. package/dist/adapters/nuxt/module.js.map +1 -1
  20. package/dist/adapters/react/index.d.ts +11 -7
  21. package/dist/adapters/react/index.js +1 -1
  22. package/dist/adapters/react/index.js.map +1 -1
  23. package/dist/adapters/sveltekit/index.d.ts +10 -5
  24. package/dist/adapters/sveltekit/index.js +1 -1
  25. package/dist/adapters/sveltekit/index.js.map +1 -1
  26. package/dist/adapters/vue/index.d.ts +10 -7
  27. package/dist/adapters/vue/index.js +1 -1
  28. package/dist/adapters/vue/index.js.map +1 -1
  29. package/dist/annotate.d.ts +15 -2
  30. package/dist/annotate.js.map +1 -1
  31. package/dist/client.cjs +1 -1
  32. package/dist/client.cjs.map +1 -1
  33. package/dist/client.d.cts +367 -266
  34. package/dist/client.d.ts +367 -266
  35. package/dist/client.js +1 -1
  36. package/dist/client.js.map +1 -1
  37. package/dist/codegen-cli.js.map +1 -1
  38. package/dist/codegen.cjs.map +1 -1
  39. package/dist/codegen.d.cts +7 -1
  40. package/dist/codegen.d.ts +7 -1
  41. package/dist/codegen.js.map +1 -1
  42. package/dist/core.cjs +1 -1
  43. package/dist/core.cjs.map +1 -1
  44. package/dist/core.d.cts +549 -393
  45. package/dist/core.d.ts +549 -393
  46. package/dist/core.js +1 -1
  47. package/dist/core.js.map +1 -1
  48. package/dist/doctor-cli.d.ts +1 -1
  49. package/dist/doctor-cli.js +1 -1
  50. package/dist/doctor-cli.js.map +1 -1
  51. package/dist/doctor.d.ts +2 -2
  52. package/dist/doctor.js +1 -1
  53. package/dist/doctor.js.map +1 -1
  54. package/dist/{document-session-BfBfLVpg.d.ts → document-session-DWi9H3Qk.d.ts} +7 -0
  55. package/dist/{fragment-endpoint-DATKU183.d.ts → fragment-endpoint-B2qcRw5T.d.ts} +22 -7
  56. package/dist/fragment.cjs +1 -1
  57. package/dist/fragment.cjs.map +1 -1
  58. package/dist/fragment.d.cts +37 -16
  59. package/dist/fragment.d.ts +37 -16
  60. package/dist/fragment.js +1 -1
  61. package/dist/fragment.js.map +1 -1
  62. package/dist/index.cjs +1 -1
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +607 -405
  65. package/dist/index.d.ts +607 -405
  66. package/dist/index.js +1 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/lean.cjs +1 -1
  69. package/dist/lean.cjs.map +1 -1
  70. package/dist/lean.d.cts +5 -2
  71. package/dist/lean.d.ts +5 -2
  72. package/dist/lean.js +1 -1
  73. package/dist/lean.js.map +1 -1
  74. package/dist/lexical.cjs +1 -1
  75. package/dist/lexical.cjs.map +1 -1
  76. package/dist/lexical.d.cts +32 -11
  77. package/dist/lexical.d.ts +32 -11
  78. package/dist/lexical.js +1 -1
  79. package/dist/lexical.js.map +1 -1
  80. package/dist/{locals-CNUGYbA_.d.ts → locals-B1mF3aTb.d.ts} +1 -1
  81. package/dist/{middleware-CWObDO37.d.ts → middleware-BGobLN9P.d.ts} +11 -5
  82. package/dist/migrate.js +1 -1
  83. package/dist/migrate.js.map +1 -1
  84. package/dist/{options-uHKrDM2U.d.ts → options-Cm8fC7dj.d.ts} +31 -1
  85. package/dist/payload.cjs +1 -1
  86. package/dist/payload.cjs.map +1 -1
  87. package/dist/payload.d.cts +18 -2
  88. package/dist/payload.d.ts +18 -2
  89. package/dist/payload.js +1 -1
  90. package/dist/payload.js.map +1 -1
  91. package/dist/plugin.cjs +2 -0
  92. package/dist/plugin.cjs.map +1 -0
  93. package/dist/plugin.d.cts +30 -0
  94. package/dist/plugin.d.ts +30 -0
  95. package/dist/plugin.js +2 -0
  96. package/dist/plugin.js.map +1 -0
  97. package/dist/plugins.cjs +1 -1
  98. package/dist/plugins.cjs.map +1 -1
  99. package/dist/plugins.d.cts +102 -29
  100. package/dist/plugins.d.ts +102 -29
  101. package/dist/plugins.js +1 -1
  102. package/dist/plugins.js.map +1 -1
  103. package/dist/{probe-Bzm4fYFL.d.ts → probe-DGPDsnbj.d.ts} +6 -2
  104. package/dist/server.cjs +1 -1
  105. package/dist/server.cjs.map +1 -1
  106. package/dist/server.d.cts +51 -7
  107. package/dist/server.d.ts +51 -7
  108. package/dist/server.js +1 -1
  109. package/dist/server.js.map +1 -1
  110. package/dist/structural.cjs +2 -2
  111. package/dist/structural.cjs.map +1 -1
  112. package/dist/structural.d.cts +61 -13
  113. package/dist/structural.d.ts +61 -13
  114. package/dist/structural.js +2 -2
  115. package/dist/structural.js.map +1 -1
  116. package/package.json +33 -4
package/dist/server.d.cts CHANGED
@@ -24,6 +24,25 @@
24
24
  */
25
25
  declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
26
26
  type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
27
+ /** One server-authorized Payload document, not a query selected by the browser. */
28
+ type AuthorizedPreviewDocument = {
29
+ readonly kind: 'collection';
30
+ readonly slug: string;
31
+ readonly id: string | number;
32
+ } | {
33
+ readonly kind: 'global';
34
+ readonly slug: string;
35
+ };
36
+ /** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */
37
+ interface AuthorizedPreviewPayloadScope {
38
+ /** Payload base URL, distinct from the preview site's audience. */
39
+ readonly serverURL: string;
40
+ /** REST prefix, default `/api`. */
41
+ readonly apiRoute?: string;
42
+ readonly document: AuthorizedPreviewDocument;
43
+ /** Maximum requested population depth, not a field-level ACL. */
44
+ readonly maxDepth: number;
45
+ }
27
46
  /** What a context is bound to; every field is optional because every strategy binds differently. */
28
47
  interface AuthorizedPreviewScope {
29
48
  /** The site origin the credential was issued for. */
@@ -31,6 +50,8 @@ interface AuthorizedPreviewScope {
31
50
  /** The request pathname the credential is valid for. */
32
51
  readonly path?: string;
33
52
  readonly locale?: string;
53
+ /** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */
54
+ readonly payload?: AuthorizedPreviewPayloadScope;
34
55
  }
35
56
  /** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
36
57
  interface AuthorizedPreviewContext {
@@ -46,7 +67,11 @@ interface AuthorizedPreviewContext {
46
67
  /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
47
68
  readonly payloadHeaders: Readonly<Record<string, string>>;
48
69
  }
49
- /** Whether `value` was produced by `authorizePreviewRequest()`; a copy or JSON round trip is not. */
70
+ /**
71
+ * Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an
72
+ * unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the
73
+ * brand is a runtime check, not a cryptographic one (ADR 0006).
74
+ */
50
75
  declare function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext;
51
76
 
52
77
  /**
@@ -73,11 +98,15 @@ interface PreviewAuthorizationRequest {
73
98
  readonly headers: {
74
99
  get(name: string): string | null;
75
100
  };
101
+ /** Request lifetime, when supplied by the caller. Session fetches follow it. */
102
+ readonly signal?: AbortSignal;
76
103
  }
77
104
 
78
105
  /**
79
106
  * The `payload-session` strategy: forward exactly one named cookie to
80
107
  * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.
108
+ * A cookie-only request preserves working Payload 2 sessions. When `/me`
109
+ * reports no user, one retry adds an `Origin` for Payload 3's CSRF check.
81
110
  */
82
111
 
83
112
  /** A `fetch`-compatible function, injectable for tests. */
@@ -97,6 +126,12 @@ interface PayloadSessionStrategy {
97
126
  readonly usersSlug?: string;
98
127
  /** Cookie name; Payload's default is `payload-token`. */
99
128
  readonly cookieName?: string;
129
+ /**
130
+ * Send this listed origin with the cookie from the first request. Without
131
+ * it, try the existing cookie-only request, then the origin of `serverURL`
132
+ * once when `/me` reports a null user. Draft reads keep the accepted headers.
133
+ */
134
+ readonly origin?: string;
100
135
  /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */
101
136
  readonly timeoutMs?: number;
102
137
  /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */
@@ -151,7 +186,7 @@ interface PreviewTokenReplayStore {
151
186
  consume(id: string, expiresAt: number): Promise<boolean> | boolean;
152
187
  }
153
188
  /**
154
- * @deprecated The 1.x store shape, removed in 3.0. Its two calls cannot be one
189
+ * @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one
155
190
  * step, so two requests with the same token that arrive together both pass
156
191
  * `isUsed` before either reaches `markUsed`. Implement
157
192
  * {@link PreviewTokenReplayStore.consume} instead.
@@ -280,7 +315,7 @@ interface BindOptions {
280
315
  readonly attribute?: string;
281
316
  /** Explicit field type, bypassing schema detection. */
282
317
  readonly type?: string;
283
- /** Mark the binding as Lexical rich text; needed only when the initial render is empty. */
318
+ /** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */
284
319
  readonly richtext?: boolean;
285
320
  /** Render the value as sanitised HTML rather than text. */
286
321
  readonly html?: boolean;
@@ -342,6 +377,12 @@ interface PreviewBindings {
342
377
  * The registry behind `id` is the endpoint's (docs/hybrid.md).
343
378
  */
344
379
  boundary: (id: string, options?: FragmentBoundaryOptions) => FragmentBoundaryAttributes | SuppressedBinding;
380
+ /**
381
+ * Field paths the page accounts for without binding them, and everything
382
+ * below them (`data-payload-covers`, ADR 0022), or nothing while
383
+ * unauthorized.
384
+ */
385
+ covers: (...paths: readonly string[]) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;
345
386
  }
346
387
  /** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */
347
388
  declare function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings;
@@ -416,7 +457,7 @@ interface PreviewServerConfig {
416
457
  readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;
417
458
  }
418
459
  /** Why a read did not produce a document. */
419
- type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';
460
+ type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';
420
461
  type PreviewFetchResult<T> = {
421
462
  readonly ok: true;
422
463
  /** The document, or `null` when the query matched nothing. */
@@ -451,8 +492,9 @@ interface PreviewWhere {
451
492
  /** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */
452
493
  interface PreviewReadOptions {
453
494
  /**
454
- * The verdict from `authorizePreviewRequest()`. Only a real context reads the
455
- * draft; `null`, a copy or a JSON round trip reads the published document.
495
+ * The verdict from `authorizePreviewRequest()`. Only a context that passes
496
+ * `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a
497
+ * JSON round trip reads the published document.
456
498
  */
457
499
  readonly authorization: AuthorizedPreviewContext | null;
458
500
  readonly locale?: string;
@@ -465,6 +507,8 @@ interface PreviewReadOptions {
465
507
  }
466
508
  interface ReadDocumentOptions extends PreviewReadOptions {
467
509
  readonly collection: string;
510
+ /** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */
511
+ readonly id?: string | number;
468
512
  readonly where?: PreviewWhere;
469
513
  }
470
514
  interface ReadGlobalOptions extends PreviewReadOptions {
@@ -486,4 +530,4 @@ interface PreviewServer {
486
530
  /** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */
487
531
  declare function definePreview(config: PreviewServerConfig): PreviewServer;
488
532
 
489
- export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
533
+ export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewDocument, type AuthorizedPreviewPayloadScope, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
package/dist/server.d.ts CHANGED
@@ -24,6 +24,25 @@
24
24
  */
25
25
  declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
26
26
  type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
27
+ /** One server-authorized Payload document, not a query selected by the browser. */
28
+ type AuthorizedPreviewDocument = {
29
+ readonly kind: 'collection';
30
+ readonly slug: string;
31
+ readonly id: string | number;
32
+ } | {
33
+ readonly kind: 'global';
34
+ readonly slug: string;
35
+ };
36
+ /** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */
37
+ interface AuthorizedPreviewPayloadScope {
38
+ /** Payload base URL, distinct from the preview site's audience. */
39
+ readonly serverURL: string;
40
+ /** REST prefix, default `/api`. */
41
+ readonly apiRoute?: string;
42
+ readonly document: AuthorizedPreviewDocument;
43
+ /** Maximum requested population depth, not a field-level ACL. */
44
+ readonly maxDepth: number;
45
+ }
27
46
  /** What a context is bound to; every field is optional because every strategy binds differently. */
28
47
  interface AuthorizedPreviewScope {
29
48
  /** The site origin the credential was issued for. */
@@ -31,6 +50,8 @@ interface AuthorizedPreviewScope {
31
50
  /** The request pathname the credential is valid for. */
32
51
  readonly path?: string;
33
52
  readonly locale?: string;
53
+ /** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */
54
+ readonly payload?: AuthorizedPreviewPayloadScope;
34
55
  }
35
56
  /** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
36
57
  interface AuthorizedPreviewContext {
@@ -46,7 +67,11 @@ interface AuthorizedPreviewContext {
46
67
  /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
47
68
  readonly payloadHeaders: Readonly<Record<string, string>>;
48
69
  }
49
- /** Whether `value` was produced by `authorizePreviewRequest()`; a copy or JSON round trip is not. */
70
+ /**
71
+ * Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an
72
+ * unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the
73
+ * brand is a runtime check, not a cryptographic one (ADR 0006).
74
+ */
50
75
  declare function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext;
51
76
 
52
77
  /**
@@ -73,11 +98,15 @@ interface PreviewAuthorizationRequest {
73
98
  readonly headers: {
74
99
  get(name: string): string | null;
75
100
  };
101
+ /** Request lifetime, when supplied by the caller. Session fetches follow it. */
102
+ readonly signal?: AbortSignal;
76
103
  }
77
104
 
78
105
  /**
79
106
  * The `payload-session` strategy: forward exactly one named cookie to
80
107
  * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.
108
+ * A cookie-only request preserves working Payload 2 sessions. When `/me`
109
+ * reports no user, one retry adds an `Origin` for Payload 3's CSRF check.
81
110
  */
82
111
 
83
112
  /** A `fetch`-compatible function, injectable for tests. */
@@ -97,6 +126,12 @@ interface PayloadSessionStrategy {
97
126
  readonly usersSlug?: string;
98
127
  /** Cookie name; Payload's default is `payload-token`. */
99
128
  readonly cookieName?: string;
129
+ /**
130
+ * Send this listed origin with the cookie from the first request. Without
131
+ * it, try the existing cookie-only request, then the origin of `serverURL`
132
+ * once when `/me` reports a null user. Draft reads keep the accepted headers.
133
+ */
134
+ readonly origin?: string;
100
135
  /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */
101
136
  readonly timeoutMs?: number;
102
137
  /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */
@@ -151,7 +186,7 @@ interface PreviewTokenReplayStore {
151
186
  consume(id: string, expiresAt: number): Promise<boolean> | boolean;
152
187
  }
153
188
  /**
154
- * @deprecated The 1.x store shape, removed in 3.0. Its two calls cannot be one
189
+ * @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one
155
190
  * step, so two requests with the same token that arrive together both pass
156
191
  * `isUsed` before either reaches `markUsed`. Implement
157
192
  * {@link PreviewTokenReplayStore.consume} instead.
@@ -280,7 +315,7 @@ interface BindOptions {
280
315
  readonly attribute?: string;
281
316
  /** Explicit field type, bypassing schema detection. */
282
317
  readonly type?: string;
283
- /** Mark the binding as Lexical rich text; needed only when the initial render is empty. */
318
+ /** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */
284
319
  readonly richtext?: boolean;
285
320
  /** Render the value as sanitised HTML rather than text. */
286
321
  readonly html?: boolean;
@@ -342,6 +377,12 @@ interface PreviewBindings {
342
377
  * The registry behind `id` is the endpoint's (docs/hybrid.md).
343
378
  */
344
379
  boundary: (id: string, options?: FragmentBoundaryOptions) => FragmentBoundaryAttributes | SuppressedBinding;
380
+ /**
381
+ * Field paths the page accounts for without binding them, and everything
382
+ * below them (`data-payload-covers`, ADR 0022), or nothing while
383
+ * unauthorized.
384
+ */
385
+ covers: (...paths: readonly string[]) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;
345
386
  }
346
387
  /** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */
347
388
  declare function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings;
@@ -416,7 +457,7 @@ interface PreviewServerConfig {
416
457
  readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;
417
458
  }
418
459
  /** Why a read did not produce a document. */
419
- type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';
460
+ type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';
420
461
  type PreviewFetchResult<T> = {
421
462
  readonly ok: true;
422
463
  /** The document, or `null` when the query matched nothing. */
@@ -451,8 +492,9 @@ interface PreviewWhere {
451
492
  /** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */
452
493
  interface PreviewReadOptions {
453
494
  /**
454
- * The verdict from `authorizePreviewRequest()`. Only a real context reads the
455
- * draft; `null`, a copy or a JSON round trip reads the published document.
495
+ * The verdict from `authorizePreviewRequest()`. Only a context that passes
496
+ * `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a
497
+ * JSON round trip reads the published document.
456
498
  */
457
499
  readonly authorization: AuthorizedPreviewContext | null;
458
500
  readonly locale?: string;
@@ -465,6 +507,8 @@ interface PreviewReadOptions {
465
507
  }
466
508
  interface ReadDocumentOptions extends PreviewReadOptions {
467
509
  readonly collection: string;
510
+ /** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */
511
+ readonly id?: string | number;
468
512
  readonly where?: PreviewWhere;
469
513
  }
470
514
  interface ReadGlobalOptions extends PreviewReadOptions {
@@ -486,4 +530,4 @@ interface PreviewServer {
486
530
  /** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */
487
531
  declare function definePreview(config: PreviewServerConfig): PreviewServer;
488
532
 
489
- export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
533
+ export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewDocument, type AuthorizedPreviewPayloadScope, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- var e="payload-live-preview.authorized-preview-context",t=Symbol.for(e);function isAuthorizedPreviewContext(e){return"object"==typeof e&&null!==e&&!0===e[t]&&Object.isFrozen(e)}function r(r){return Object.freeze({...r,scope:Object.freeze({...r.scope}),payloadHeaders:Object.freeze({...r.payloadHeaders}),[t]:!0,[e]:!0})}var n=class extends Error{name="PreviewConfigurationError"};function o(e){return{authorized:!1,outcome:e,context:null}}var a="users",i=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,s=/^[A-Za-z0-9._~-]+$/;function u(e,t,r=4096){if(null===e||0===e.length)return null;const n=`${t}=`,o=[];for(const t of e.split(";")){const e=t.trim();e.startsWith(n)&&o.push(e.slice(n.length))}if(1!==o.length)return null;const a=o[0]??"";return 0===a.length||a.length>r||!s.test(a)?null:a}var c,l="live-preview",d="v1";function p(){return c??=new TextEncoder,c}function f(e){const t="string"==typeof e?p().encode(e):e;if(t.byteLength<32)throw new n(`payload-live-preview: the preview token secret must be at least ${String(32)} bytes`);return t}function h(e){if(void 0!==e)return e;const t=globalThis.crypto;return"object"==typeof t?.subtle?t:void 0}async function y(e,t){return e.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function v(e){let t="";for(const r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function w(e){if(!/^[A-Za-z0-9_-]*$/.test(e))return;const t=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4);try{const e=atob(t),r=new Uint8Array(e.length);for(let t=0;t<e.length;t+=1)r[t]=e.charCodeAt(t);return r}catch{return}}async function g(e,t){const r=h(t.crypto);if(void 0===r)throw new Error("payload-live-preview: no Web Crypto implementation available to sign tokens");const n=f(t.secret),o=(t.now??Date.now)(),a=Math.min(36e5,Math.max(1,e.ttlMs??6e5)),i=new Uint8Array(16);r.getRandomValues(i);const s={v:1,aud:e.audience,...void 0!==e.path?{pth:e.path}:{},...void 0!==e.locale?{loc:e.locale}:{},...void 0!==e.subject?{sub:e.subject}:{},pur:e.purpose??l,iat:o,exp:o+a,jti:v(i)},u=v(p().encode(JSON.stringify(s))),c=`${d}.${u}`,w=await y(r,n);return`${c}.${v(new Uint8Array(await r.subtle.sign("HMAC",w,p().encode(c))))}`}async function b(e,t){switch(t.type){case"payload-session":return async function(e,t){const s=function(e){const t=e??"payload-token";if(!i.test(t))throw new n(`payload-live-preview: cookie name "${t}" is not a valid cookie-name token`);return t}(t.cookieName),c=function(e){let t;try{t=new URL(e)}catch{throw new n(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new n(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return t.origin}(t.serverURL),l=u(e.headers.get("cookie"),s,t.maxCookieLength??4096);if(null===l)return o("missing-credential");const d=t.now??Date.now,p=Math.max(250,t.timeoutMs??3e3),f=t.fetch??globalThis.fetch,h=encodeURIComponent(t.usersSlug??a),y=`${s}=${l}`;let v;try{const e=await f(`${c}/api/${h}/me?depth=0`,{headers:{cookie:y,accept:"application/json"},signal:AbortSignal.timeout(p)});if(!e.ok)return o(401===e.status?"invalid":"unavailable");v=await e.json()}catch{return o("unavailable")}const w=function(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return null;const r=e.user;if("object"!=typeof r||null===r||Array.isArray(r))return null;const n=e.collection??r.collection;if(void 0!==n&&n!==t)return null;const o=r.id;return"string"==typeof o&&o.length>0?o:"number"==typeof o&&Number.isFinite(o)?String(o):null}(v,t.usersSlug??a);if(null===w)return o("invalid");const g=function(e){const t=e.exp;return"number"!=typeof t||!Number.isFinite(t)||t<=0?void 0:t<1e11?1e3*t:t}(v);return void 0!==g&&g<=d()?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"payload-session",subject:w,authorizedAt:d(),expiresAt:g,scope:{},payloadHeaders:{cookie:y}})}}(e,t);case"signed-token":return async function(e,t){const n=f(t.secret),a=h(t.crypto);if(void 0===a)return o("unavailable");const i=function(e,t){if("header"===t?.kind)return e.headers.get(t.name??"x-preview-token");let r;try{r=new URL(e.url)}catch{return null}const n=r.searchParams.getAll(t?.param??"previewToken");return 1===n.length?n[0]??null:null}(e,t.transport);if(null===i||0===i.length)return o("missing-credential");if(i.length>4096)return o("invalid");const s=i.split(".");if(3!==s.length||s[0]!==d)return o("invalid");const u=s[1]??"",c=w(u),v=w(s[2]??"");if(void 0===c||32!==v?.byteLength)return o("invalid");let g;try{const e=await y(a,n);g=await a.subtle.verify("HMAC",e,v,p().encode(`${d}.${u}`))}catch{return o("unavailable")}if(!g)return o("invalid");const b=function(e){let t;try{t=JSON.parse((new TextDecoder).decode(e))}catch{return}if("object"!=typeof t||null===t)return;const r=t,n=e=>void 0===r[e]||"string"==typeof r[e];return 1===r.v&&"string"==typeof r.aud&&"string"==typeof r.pur&&"number"==typeof r.iat&&"number"==typeof r.exp&&"string"==typeof r.jti&&n("pth")&&n("loc")&&n("sub")?r:void 0}(c);if(void 0===b)return o("invalid");const m=(t.now??Date.now)();if(b.exp<=m||b.iat>b.exp)return o("expired");if(b.aud!==t.audience)return o("wrong-audience");if(b.pur!==(t.purpose??l))return o("wrong-purpose");if(void 0!==b.pth){let t;try{t=new URL(e.url).pathname}catch{return o("invalid")}if(b.pth!==t)return o("wrong-path")}if(void 0!==b.loc){const r=t.locale?.(e);if(r!==b.loc)return o("wrong-locale")}if(void 0!==t.replay){let e;try{e=await async function(e,t,r){return"consume"in e?!0===await e.consume(t,r):!await e.isUsed(t)&&(await e.markUsed(t,r),!0)}(t.replay,b.jti,b.exp)}catch{return o("unavailable")}if(!e)return o("replayed")}return{authorized:!0,outcome:"authorized",context:r({strategy:"signed-token",subject:b.sub,authorizedAt:m,expiresAt:b.exp,scope:{audience:b.aud,...void 0!==b.pth?{path:b.pth}:{},...void 0!==b.loc?{locale:b.loc}:{}},payloadHeaders:{}})}}(e,t);case"verifier":return async function(e,t){let n;try{n=await t.verify(e)}catch{return o("unavailable")}if(null===n)return o("invalid");const a=(t.now??Date.now)();return void 0!==n.expiresAt&&n.expiresAt<=a?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"verifier",subject:n.subject,authorizedAt:a,expiresAt:n.expiresAt,scope:n.scope??{},payloadHeaders:n.payloadHeaders??{}})}}(e,t)}}var m=["preview","draft","livePreview"];function $(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??m;if(r.has("query")){let t;try{t=new URL(e.url)}catch{t=void 0}if(void 0!==t)for(const e of n){const r=t.searchParams.getAll(e).at(-1);if("true"===r||"1"===r)return!0}}if(r.has("fetch-dest")&&"iframe"===e.headers.get("sec-fetch-dest"))return!0;const o=t.allowedOrigins??t.adminOrigins??[],a=r.has("referer")?o:[];if(a.length>0){const t=e.headers.get("referer");if(null!==t)try{const e=new URL(t).origin;for(const t of a)try{if(new URL(t).origin===e)return!0}catch{}}catch{}}return!1}function bind(e,t){return A(e,t)}function bindByPath(e,t){const r=function(e){const t=[],r=new Proxy({},{get(e,n){if("symbol"!=typeof n)return/^\d+$/.test(n)||t.push(n),r}});try{e(r)}catch{}return t}(e);if(0===r.length)throw new Error("bindByPath: the picker did not read any field — return data.<field> instead of a constant");return A(r.join("."),t)}function A(e,t){if(0===e.length)throw new Error("bind: field name must be a non-empty string");const r={"data-payload-field":e};return void 0!==t?.attribute&&(r["data-payload-attribute"]=t.attribute),void 0!==t?.type&&(r["data-payload-type"]=t.type),!0===t?.richtext&&(r["data-payload-richtext"]=""),!0===t?.html&&(r["data-payload-html"]=""),void 0!==t?.locale&&(r["data-payload-locale"]=t.locale),void 0!==t?.alt&&(r["data-payload-alt"]=t.alt),void 0!==t?.href&&(r["data-payload-href"]=t.href),void 0!==t?.arrayTemplate&&(r["data-payload-array-template"]=t.arrayTemplate),r}var x=/^[a-z][a-z0-9-]{0,63}$/u,j=Object.freeze({});function createPreviewBindings(e){const t=isAuthorizedPreviewContext(e.authorization),r=void 0!==e.owner&&e.owner.length>0?e.owner:void 0;return Object.freeze({authorized:t,bind:(e,r)=>t?bind(e,r):j,bindByPath:(e,r)=>t?bindByPath(e,r):j,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:j,boundary:(e,r={})=>{const n=function(e,t){if(!x.test(e))throw new RangeError(`createPreviewBindings().boundary(): "${e}" is not a registry id — lowercase, starting with a letter, then letters, digits or hyphens (max 64). The runtime would post it and the endpoint would refuse it.`);const r=t.key;if(void 0!==r&&(0===r.length||r.length>128))throw new RangeError(`createPreviewBindings().boundary(): the key for "${e}" must be 1 to ${String(128)} characters.`);const n=t.dependsOn??[];return{"data-payload-fragment":e,...void 0!==r?{"data-payload-fragment-key":r}:{},...n.length>0?{"data-payload-depends":n.join(",")}:{}}}(e,r);return t?n:j}})}function z(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})}var k=class extends Error{reason;status;url;constructor(e,t,r,n){super(`payload-live-preview: preview read failed (${e}${void 0===r?"":` ${String(r)}`}) for ${t}`,{cause:n}),this.name="PreviewFetchError",this.reason=e,this.status=r,this.url=t}};function R(e){const t=function(e){let t;try{t=new URL(e)}catch{throw new Error(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new Error(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}(e.serverURL),r=e.apiRoute??"/api",n=r.startsWith("/")?r:`/${r}`;if(!Number.isInteger(e.depth)||e.depth<0)throw new Error(`payload-live-preview: depth must be a non-negative integer, got ${String(e.depth)}`);const o=Math.max(250,e.timeoutMs??5e3),a=Object.freeze({serverURL:t,apiRoute:n,depth:e.depth,timeoutMs:o}),i=Object.freeze({serverURL:t,apiRoute:n,mergeDepth:e.depth}),s=`${t}${n}`,u={config:e,timeoutMs:o};return Object.freeze({config:a,runtimeOptions:i,async fetchDocument(t){const r=U(t),n=P(e.depth,t,r);n.set("limit","1"),void 0!==t.where&&O(n,t.where,["where"]);const o=`${s}/${encodeURIComponent(t.collection)}?${n.toString()}`,a=await S(u,o,t,r);if(!a.ok)return a;const i=a.data?.docs?.[0];return{ok:!0,data:i??null,draft:r,status:a.status}},async fetchGlobal(t){const r=U(t),n=P(e.depth,t,r),o=`${s}/globals/${encodeURIComponent(t.global)}?${n.toString()}`;return S(u,o,t,r)}})}function U(e){return isAuthorizedPreviewContext(e.authorization)}function P(e,t,r){const n=new URLSearchParams;return n.set("depth",String(e)),r&&n.set("draft","true"),void 0!==t.locale&&n.set("locale",t.locale),n}async function S({config:e,timeoutMs:t},r,n,o){const a=e.fetch??("function"==typeof fetch?fetch:void 0),i={config:e,url:r,draft:o,errorMode:n.errorMode??"result",startedAt:Date.now()};if(void 0===a)return L(i,{ok:!1,reason:"no-fetch",status:void 0,draft:o,cause:void 0});const s=AbortSignal.timeout(t),u=void 0===n.signal?s:AbortSignal.any([s,n.signal]),c=isAuthorizedPreviewContext(n.authorization)?n.authorization:null;let l,d;try{l=await a(r,{headers:{accept:"application/json",...n.headers??{},...c?.payloadHeaders??{}},signal:u,cache:"no-store",redirect:"error"})}catch(e){return L(i,{ok:!1,reason:M(e,n.signal),status:void 0,draft:o,cause:e})}if(!l.ok)return L(i,{ok:!1,reason:"http",status:l.status,draft:o,cause:void 0});try{d=await l.json()}catch(e){return L(i,{ok:!1,reason:"invalid-json",status:l.status,draft:o,cause:e})}return L(i,{ok:!0,data:d,draft:o,status:l.status})}function L(e,t){const{config:r,url:n,draft:o}=e,a=Date.now()-e.startedAt;if(t.ok)return r.onDiagnostic?.({kind:"response",url:n,status:t.status,draft:o,durationMs:a}),t;if(r.onDiagnostic?.({kind:"failure",url:n,reason:t.reason,status:t.status,draft:o,durationMs:a}),"throw"===e.errorMode)throw new k(t.reason,n,t.status,t.cause);return t}function M(e,t){if(!0===t?.aborted)return"aborted";const r="object"==typeof e&&null!==e?e.name:void 0;return"TimeoutError"===r?"timeout":"AbortError"===r?"aborted":"network"}function C(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function E(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function O(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];C(o)?O(e,o,t):Array.isArray(o)&&o.some(C)?o.forEach((r,n)=>{O(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(E(t),o.map(String).join(",")):e.set(E(t),"object"==typeof o?"null":String(o))}}export{e as AUTHORIZED_PREVIEW_BRAND_KEY,k as PreviewFetchError,b as authorizePreviewRequest,bind,bindByPath,createPreviewBindings,R as definePreview,u as extractCookie,$ as hasPreviewIntent,isAuthorizedPreviewContext,g as issuePreviewToken,z as previewBindingsFromLocals};
1
+ var e="payload-live-preview.authorized-preview-context",t=Symbol.for(e);function isAuthorizedPreviewContext(e){return"object"==typeof e&&null!==e&&!0===e[t]&&Object.isFrozen(e)}function r(r){return Object.freeze({...r,scope:Object.freeze({...r.scope,...void 0===r.scope.payload?{}:{payload:Object.freeze({...r.scope.payload,document:Object.freeze({...r.scope.payload.document})})}}),payloadHeaders:Object.freeze({...r.payloadHeaders}),[t]:!0,[e]:!0})}var n=class extends Error{name="PreviewConfigurationError"};function o(e){return{authorized:!1,outcome:e,context:null}}var i="users",a=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,s=/^[A-Za-z0-9._~-]+$/;function c(e,t,r=4096){if(null===e||0===e.length)return null;const n=`${t}=`,o=[];for(const t of e.split(";")){const e=t.trim();e.startsWith(n)&&o.push(e.slice(n.length))}if(1!==o.length)return null;const i=o[0]??"";return 0===i.length||i.length>r||!s.test(i)?null:i}function u(e,t){let r;try{r=new URL(e)}catch{throw new n(`payload-live-preview: ${t} "${e}" is not an absolute URL`)}if("https:"!==r.protocol&&"http:"!==r.protocol)throw new n(`payload-live-preview: ${t} must be http(s), got "${r.protocol}"`);return r.origin}function l(e,t){if(!function(){try{const e=globalThis.process?.env;return void 0!==e&&"production"!==e.NODE_ENV}catch{return!1}}())return;const r=globalThis,n=r.__payloadLivePreviewDeprecationsWarned??=/* @__PURE__ */new Set;if(!n.has(e)){n.add(e);try{console.warn(`[payload-live-preview] ${t}`)}catch{}}}var d,p="live-preview",f="v1";function h(){return d??=new TextEncoder,d}function v(e){const t="string"==typeof e?h().encode(e):e;if(t.byteLength<32)throw new n(`payload-live-preview: the preview token secret must be at least ${String(32)} bytes`);return t}function y(e){if(void 0!==e)return e;const t=globalThis.crypto;return"object"==typeof t?.subtle?t:void 0}async function g(e,t){return e.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function w(e){let t="";for(const r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function b(e){if(!/^[A-Za-z0-9_-]*$/.test(e))return;const t=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4);try{const e=atob(t),r=new Uint8Array(e.length);for(let t=0;t<e.length;t+=1)r[t]=e.charCodeAt(t);return r}catch{return}}async function m(e,t){const r=y(t.crypto);if(void 0===r)throw new Error("payload-live-preview: no Web Crypto implementation available to sign tokens");const n=v(t.secret),o=(t.now??Date.now)(),i=Math.min(36e5,Math.max(1,e.ttlMs??6e5)),a=new Uint8Array(16);r.getRandomValues(a);const s={v:1,aud:e.audience,...void 0!==e.path?{pth:e.path}:{},...void 0!==e.locale?{loc:e.locale}:{},...void 0!==e.subject?{sub:e.subject}:{},pur:e.purpose??p,iat:o,exp:o+i,jti:w(a)},c=w(h().encode(JSON.stringify(s))),u=`${f}.${c}`,l=await g(r,n);return`${u}.${w(new Uint8Array(await r.subtle.sign("HMAC",l,h().encode(u))))}`}function $(e){return"string"==typeof e&&e.length>0&&"."!==e&&".."!==e&&!/[\s\p{Cc}\p{Cs}/\\?#%]/u.test(e)}function A(e){return"number"==typeof e?Number.isSafeInteger(e):$(e)}function x(e,t="/api"){if("string"!=typeof e||"string"!=typeof t)return null;try{const r=new URL(e);if(!["http:","https:"].includes(r.protocol)||r.username||r.password||r.search||r.hash)return null;const n=e.replace(/^[a-z]+:\/\/[^/]+/iu,"").replace(/\/+$/,""),o=t.replace(/^\//,"").replace(/\/$/,"");return""!==n&&!n.slice(1).split("/").every($)||!o.split("/").every($)?null:`${r.origin}${r.pathname.replace(/\/+$/,"")}/${o}`}catch{return null}}function j(e){return void 0!==e.expiresAt&&Number.isFinite(e.expiresAt)&&e.expiresAt>Date.now()}function k(e,t){return e.kind===t.kind&&e.slug===t.slug&&("global"===e.kind||A(t.id)&&String(e.id)===String(t.id))}async function z(e,t){switch(t.type){case"payload-session":return async function(e,t){const s=function(e){const t=e??"payload-token";if(!a.test(t))throw new n(`payload-live-preview: cookie name "${t}" is not a valid cookie-name token`);return t}(t.cookieName),l=u(t.serverURL,"serverURL"),d=void 0===t.origin?void 0:u(t.origin,"origin"),p=c(e.headers.get("cookie"),s,t.maxCookieLength??4096);if(null===p)return o("missing-credential");const f=t.now??Date.now,h=Math.max(250,t.timeoutMs??3e3);if(!Number.isSafeInteger(h))return o("unavailable");const v=t.fetch??globalThis.fetch,y=encodeURIComponent(t.usersSlug??i),g=`${s}=${p}`;let w={cookie:g};void 0!==d&&(w.origin=d);const b=new AbortController,m=()=>{b.abort(e.signal?.reason)},$=()=>b.signal.aborted;let A,x;e.signal?.addEventListener("abort",m,{once:!0}),!0===e.signal?.aborted&&m();try{if($())return o("unavailable");A=setTimeout(()=>{b.abort(new DOMException("The operation timed out","TimeoutError"))},h);for(let e=0;e<2;e++){const t=await v(`${l}/api/${y}/me?depth=0`,{headers:{...w,accept:"application/json"},signal:b.signal});if($())return o("unavailable");if(!t.ok)return o(401===t.status?"invalid":"unavailable");if(x=await t.json(),$())return o("unavailable");if(0!==e||void 0!==d||"object"!=typeof x||null===x||null!==x.user)break;w={cookie:g,origin:l}}}catch{return o("unavailable")}finally{void 0!==A&&clearTimeout(A),e.signal?.removeEventListener("abort",m)}const j=function(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return null;const r=e.user;if("object"!=typeof r||null===r||Array.isArray(r))return null;const n=e.collection??r.collection;if(void 0!==n&&n!==t)return null;const o=r.id;return"string"==typeof o&&o.length>0?o:"number"==typeof o&&Number.isFinite(o)?String(o):null}(x,t.usersSlug??i);if(null===j)return o("invalid");const k=function(e){const t=e.exp;return"number"!=typeof t||!Number.isFinite(t)||t<=0?void 0:t<1e11?1e3*t:t}(x);return void 0!==k&&k<=f()?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"payload-session",subject:j,authorizedAt:f(),expiresAt:k,scope:{},payloadHeaders:w})}}(e,t);case"signed-token":return async function(e,t){const n=v(t.secret),i=y(t.crypto);if(void 0===i)return o("unavailable");const a=function(e,t){if("header"===t?.kind)return e.headers.get(t.name??"x-preview-token");let r;try{r=new URL(e.url)}catch{return null}const n=r.searchParams.getAll(t?.param??"previewToken");return 1===n.length?n[0]??null:null}(e,t.transport);if(null===a||0===a.length)return o("missing-credential");if(a.length>4096)return o("invalid");const s=a.split(".");if(3!==s.length||s[0]!==f)return o("invalid");const c=s[1]??"",u=b(c),d=b(s[2]??"");if(void 0===u||32!==d?.byteLength)return o("invalid");let w;try{const e=await g(i,n);w=await i.subtle.verify("HMAC",e,d,h().encode(`${f}.${c}`))}catch{return o("unavailable")}if(!w)return o("invalid");const m=function(e){let t;try{t=JSON.parse((new TextDecoder).decode(e))}catch{return}if("object"!=typeof t||null===t)return;const r=t,n=e=>void 0===r[e]||"string"==typeof r[e];return 1===r.v&&"string"==typeof r.aud&&"string"==typeof r.pur&&"number"==typeof r.iat&&"number"==typeof r.exp&&"string"==typeof r.jti&&n("pth")&&n("loc")&&n("sub")?r:void 0}(u);if(void 0===m)return o("invalid");const $=(t.now??Date.now)();if(m.exp<=$||m.iat>m.exp)return o("expired");if(m.aud!==t.audience)return o("wrong-audience");if(m.pur!==(t.purpose??p))return o("wrong-purpose");if(void 0!==m.pth){let t;try{t=new URL(e.url).pathname}catch{return o("invalid")}if(m.pth!==t)return o("wrong-path")}if(void 0!==m.loc){const r=t.locale?.(e);if(r!==m.loc)return o("wrong-locale")}if(void 0!==t.replay){let e;try{e=await async function(e,t,r){return"consume"in e?!0===await e.consume(t,r):(l("replay-is-used","A `signed-token` replay store with `isUsed` and `markUsed` is removed in 3.0: implement `consume(id, expiresAt)`, which checks and records in one step (ADR 0026)."),!await e.isUsed(t)&&(await e.markUsed(t,r),!0))}(t.replay,m.jti,m.exp)}catch{return o("unavailable")}if(!e)return o("replayed")}return{authorized:!0,outcome:"authorized",context:r({strategy:"signed-token",subject:m.sub,authorizedAt:$,expiresAt:m.exp,scope:{audience:m.aud,...void 0!==m.pth?{path:m.pth}:{},...void 0!==m.loc?{locale:m.loc}:{}},payloadHeaders:{}})}}(e,t);case"verifier":return async function(e,t){let n;try{n=await t.verify(e)}catch{return o("unavailable")}if(null===n)return o("invalid");if(!(void 0===n.scope?.payload||function(e){if("object"!=typeof e||null===e)return!1;const t=e,r=t.document;return!(null===x(t.serverURL,t.apiRoute)||"number"!=typeof t.maxDepth||!Number.isSafeInteger(t.maxDepth)||t.maxDepth<0||"object"!=typeof r||null===r||!$(r.slug))&&("global"===r.kind||"collection"===r.kind&&A(r.id))}(n.scope.payload)&&Number.isFinite(n.expiresAt)))return o("invalid");const i=(t.now??Date.now)();return void 0!==n.expiresAt&&n.expiresAt<=i?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"verifier",subject:n.subject,authorizedAt:i,expiresAt:n.expiresAt,scope:n.scope??{},payloadHeaders:n.payloadHeaders??{}})}}(e,t)}}var R=["preview","draft","livePreview"];function U(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??R;if(r.has("query")){let t;try{t=new URL(e.url)}catch{t=void 0}if(void 0!==t)for(const e of n){const r=t.searchParams.getAll(e).at(-1);if("true"===r||"1"===r)return!0}}if(r.has("fetch-dest")&&"iframe"===e.headers.get("sec-fetch-dest"))return!0;const o=t.adminOrigins;void 0!==o&&l("admin-origins","`hasPreviewIntent({ adminOrigins })` is removed in 3.0: name the option `allowedOrigins`; `pll migrate` renames it (ADR 0026).");const i=t.allowedOrigins??o??[],a=r.has("referer")?i:[];if(a.length>0){const t=e.headers.get("referer");if(null!==t)try{const e=new URL(t).origin;for(const t of a)try{if(new URL(t).origin===e)return!0}catch{}}catch{}}return!1}function bind(e,t){return O(e,t)}function bindByPath(e,t){const r=function(e){const t=[],r=new Proxy({},{get(e,n){if("symbol"!=typeof n)return/^\d+$/.test(n)||t.push(n),r}});try{e(r)}catch{}return t}(e);if(0===r.length)throw new Error("bindByPath: the picker did not read any field — return data.<field> instead of a constant");return O(r.join("."),t)}function O(e,t){if(0===e.length)throw new Error("bind: field name must be a non-empty string");const r={"data-payload-field":e};return void 0!==t?.attribute&&(r["data-payload-attribute"]=t.attribute),void 0!==t?.type&&(r["data-payload-type"]=t.type),!0===t?.richtext&&(r["data-payload-richtext"]=""),!0===t?.html&&(r["data-payload-html"]=""),void 0!==t?.locale&&(r["data-payload-locale"]=t.locale),void 0!==t?.alt&&(r["data-payload-alt"]=t.alt),void 0!==t?.href&&(r["data-payload-href"]=t.href),void 0!==t?.arrayTemplate&&(r["data-payload-array-template"]=t.arrayTemplate),r}var S=/^[a-z][a-z0-9-]{0,63}$/u,P=Object.freeze({});function createPreviewBindings(e){const t=isAuthorizedPreviewContext(e.authorization),r=void 0!==e.owner&&e.owner.length>0?e.owner:void 0;return Object.freeze({authorized:t,bind:(e,r)=>t?bind(e,r):P,bindByPath:(e,r)=>t?bindByPath(e,r):P,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:P,boundary:(e,r={})=>{const n=function(e,t){if(!S.test(e))throw new RangeError(`createPreviewBindings().boundary(): "${e}" is not a registry id — lowercase, starting with a letter, then letters, digits or hyphens (max 64). The runtime would post it and the endpoint would refuse it.`);const r=t.key;if(void 0!==r&&(0===r.length||r.length>128))throw new RangeError(`createPreviewBindings().boundary(): the key for "${e}" must be 1 to ${String(128)} characters.`);const n=t.dependsOn??[];return{"data-payload-fragment":e,...void 0!==r?{"data-payload-fragment-key":r}:{},...n.length>0?{"data-payload-depends":n.join(",")}:{}}}(e,r);return t?n:P},covers:(...e)=>{const r=function(e){for(const t of e)if(0===t.length||/[\s,]/u.test(t))throw new RangeError(`createPreviewBindings().covers(): "${t}" is not a field path — a name or a dotted path without spaces or commas.`);return{"data-payload-covers":e.join(" ")}}(e);return t&&e.length>0?r:P}})}function L(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})}var E=class extends Error{reason;status;url;constructor(e,t,r,n){super(`payload-live-preview: preview read failed (${e}${void 0===r?"":` ${String(r)}`}) for ${t}`,{cause:n}),this.name="PreviewFetchError",this.reason=e,this.status=r,this.url=t}};function C(e){const t=function(e){let t;try{t=new URL(e)}catch{throw new Error(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new Error(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}(e.serverURL),r=e.apiRoute??"/api",n=r.startsWith("/")?r:`/${r}`;if(!Number.isInteger(e.depth)||e.depth<0)throw new Error(`payload-live-preview: depth must be a non-negative integer, got ${String(e.depth)}`);const o=Math.max(250,e.timeoutMs??5e3),i=Object.freeze({serverURL:t,apiRoute:n,depth:e.depth,timeoutMs:o}),a=Object.freeze({serverURL:t,apiRoute:n,mergeDepth:e.depth}),s=`${t}${n}`,c={config:Object.freeze({...e,...i}),timeoutMs:o};return Object.freeze({config:i,runtimeOptions:a,async fetchDocument(e){const t=T(e),r=void 0!==e.id,n=(!r||A(e.id)&&void 0===e.where)&&D(c.config,e,{kind:"collection",slug:e.collection,id:e.id});if(!n)return H(c,s,e,t,!1);const o=N(i.depth,e,t);r||(o.set("limit","1"),void 0!==e.where&&W(o,e.where,["where"]));const a=A(e.id)?`/${encodeURIComponent(String(e.id))}`:"",u=`${s}/${encodeURIComponent(e.collection)}${a}?${o.toString()}`;if(r)return H(c,u,e,t,n);const l=await H(c,u,e,t,n);if(!l.ok)return l;const d=l.data?.docs?.[0];return{ok:!0,data:d??null,draft:t,status:l.status}},async fetchGlobal(e){const t=T(e),r=N(i.depth,e,t),n=`${s}/globals/${encodeURIComponent(e.global)}?${r.toString()}`;return H(c,n,e,t,D(c.config,e,{kind:"global",slug:e.global}))}})}function D(e,t,r){const n=isAuthorizedPreviewContext(t.authorization)?t.authorization:null,o=n?.scope.payload;return void 0===o||null===n||j(n)&&(void 0===n.scope.locale||n.scope.locale===t.locale)&&x(e.serverURL,e.apiRoute)===x(o.serverURL,o.apiRoute)&&e.depth<=o.maxDepth&&k(o.document,r)}function M(e){return B(e,{ok:!1,reason:"scope",status:void 0,draft:e.draft,cause:void 0})}function T(e){return isAuthorizedPreviewContext(e.authorization)}function N(e,t,r){const n=new URLSearchParams;return n.set("depth",String(e)),r&&n.set("draft","true"),void 0!==t.locale&&n.set("locale",t.locale),n}async function H({config:e,timeoutMs:t},r,n,o,i){const a=e.fetch??("function"==typeof fetch?fetch:void 0),s={config:e,url:r,draft:o,errorMode:n.errorMode??"result",startedAt:Date.now()};if(!i)return M(s);if(void 0===a)return B(s,{ok:!1,reason:"no-fetch",status:void 0,draft:o,cause:void 0});const c=AbortSignal.timeout(t),u=void 0===n.signal?c:AbortSignal.any([c,n.signal]),l=isAuthorizedPreviewContext(n.authorization)?n.authorization:null;let d,p;try{const e=new Headers({accept:"application/json",...n.headers});for(const[t,r]of Object.entries(l?.payloadHeaders??{}))e.set(t,r);d=await a(r,{headers:Object.fromEntries(e),signal:u,cache:"no-store",redirect:"error"})}catch(e){return B(s,{ok:!1,reason:I(e,n.signal),status:void 0,draft:o,cause:e})}if(void 0!==l?.scope.payload&&!j(l))return M(s);if(!d.ok)return B(s,{ok:!1,reason:"http",status:d.status,draft:o,cause:void 0});try{p=await d.json()}catch(e){return B(s,{ok:!1,reason:"invalid-json",status:d.status,draft:o,cause:e})}return void 0!==l?.scope.payload&&!j(l)||void 0!==l?.scope.payload&&!function(e,t){if("object"!=typeof t||null===t||Array.isArray(t))return!1;const r=t;return"global"===e.kind?!Object.hasOwn(r,"globalType")||r.globalType===e.slug:Object.hasOwn(r,"id")&&k(e,{kind:"collection",slug:e.slug,id:r.id})}(l.scope.payload.document,p)?M(s):B(s,{ok:!0,data:p,draft:o,status:d.status})}function B(e,t){const{config:r,url:n,draft:o}=e,i=Date.now()-e.startedAt;if(t.ok)return r.onDiagnostic?.({kind:"response",url:n,status:t.status,draft:o,durationMs:i}),t;if(r.onDiagnostic?.({kind:"failure",url:n,reason:t.reason,status:t.status,draft:o,durationMs:i}),"throw"===e.errorMode)throw new E(t.reason,n,t.status,t.cause);return t}function I(e,t){if(!0===t?.aborted)return"aborted";const r="object"==typeof e&&null!==e?e.name:void 0;return"TimeoutError"===r?"timeout":"AbortError"===r?"aborted":"network"}function _(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function F(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function W(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];_(o)?W(e,o,t):Array.isArray(o)&&o.some(_)?o.forEach((r,n)=>{W(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(F(t),o.map(String).join(",")):e.set(F(t),"object"==typeof o?"null":String(o))}}export{e as AUTHORIZED_PREVIEW_BRAND_KEY,E as PreviewFetchError,z as authorizePreviewRequest,bind,bindByPath,createPreviewBindings,C as definePreview,c as extractCookie,U as hasPreviewIntent,isAuthorizedPreviewContext,m as issuePreviewToken,L as previewBindingsFromLocals};
2
2
  //# sourceMappingURL=server.js.map