@decocms/nextjs 8.1.0-next.0 → 8.5.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.
Files changed (89) hide show
  1. package/README.md +97 -328
  2. package/dist/DraftPreviewIndicator.d.ts +7 -0
  3. package/dist/DraftPreviewIndicator.d.ts.map +1 -0
  4. package/dist/DraftPreviewIndicator.js +19 -0
  5. package/dist/DraftPreviewIndicator.js.map +1 -0
  6. package/dist/StudioBridge.d.ts +16 -0
  7. package/dist/StudioBridge.d.ts.map +1 -0
  8. package/dist/StudioBridge.js +17 -0
  9. package/dist/StudioBridge.js.map +1 -0
  10. package/dist/draft.d.ts +37 -0
  11. package/dist/draft.d.ts.map +1 -0
  12. package/dist/draft.js +45 -0
  13. package/dist/draft.js.map +1 -0
  14. package/dist/drafts.d.ts +94 -0
  15. package/dist/drafts.d.ts.map +1 -0
  16. package/dist/drafts.js +210 -0
  17. package/dist/drafts.js.map +1 -0
  18. package/dist/image-loader/index.d.ts +36 -0
  19. package/dist/image-loader/index.d.ts.map +1 -0
  20. package/dist/image-loader/index.js +27 -0
  21. package/dist/image-loader/index.js.map +1 -0
  22. package/dist/index.d.ts +29 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +29 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/instrumentation/index.d.ts +74 -0
  27. package/dist/instrumentation/index.d.ts.map +1 -0
  28. package/dist/instrumentation/index.js +294 -0
  29. package/dist/instrumentation/index.js.map +1 -0
  30. package/dist/proxy/index.d.ts +82 -0
  31. package/dist/proxy/index.d.ts.map +1 -0
  32. package/dist/proxy/index.js +91 -0
  33. package/dist/proxy/index.js.map +1 -0
  34. package/dist/server/index.d.ts +46 -0
  35. package/dist/server/index.d.ts.map +1 -0
  36. package/dist/server/index.js +64 -0
  37. package/dist/server/index.js.map +1 -0
  38. package/dist/server/slot.d.ts +6 -0
  39. package/dist/server/slot.d.ts.map +1 -0
  40. package/dist/server/slot.js +13 -0
  41. package/dist/server/slot.js.map +1 -0
  42. package/dist/studio.d.ts +30 -0
  43. package/dist/studio.d.ts.map +1 -0
  44. package/dist/studio.js +55 -0
  45. package/dist/studio.js.map +1 -0
  46. package/package.json +53 -22
  47. package/src/DraftPreviewIndicator.tsx +17 -16
  48. package/src/StudioBridge.ts +23 -0
  49. package/src/config/index.cjs +78 -0
  50. package/src/config/index.d.cts +27 -0
  51. package/src/draft.ts +32 -215
  52. package/src/drafts.ts +229 -0
  53. package/src/image-loader/index.ts +62 -0
  54. package/src/index.ts +31 -45
  55. package/src/instrumentation/index.ts +337 -0
  56. package/src/proxy/index.ts +164 -0
  57. package/src/server/index.ts +77 -0
  58. package/src/server/slot.ts +14 -0
  59. package/src/studio.ts +54 -0
  60. package/src/ClientOnlySection.tsx +0 -20
  61. package/src/DecoPageRenderer.test.tsx +0 -37
  62. package/src/DecoPageRenderer.tsx +0 -107
  63. package/src/DecoRootLayout.test.tsx +0 -22
  64. package/src/DecoRootLayout.tsx +0 -82
  65. package/src/DeferredSection.test.tsx +0 -27
  66. package/src/DeferredSection.tsx +0 -59
  67. package/src/DraftPreviewBadge.tsx +0 -16
  68. package/src/SectionRenderer.test.tsx +0 -97
  69. package/src/SectionRenderer.tsx +0 -58
  70. package/src/__conformance__/observability.test.ts +0 -66
  71. package/src/config.cjs +0 -92
  72. package/src/config.d.cts +0 -4
  73. package/src/config.test.ts +0 -86
  74. package/src/createDecoPage.draft.test.tsx +0 -91
  75. package/src/createDecoPage.test.tsx +0 -63
  76. package/src/createDecoPage.tsx +0 -173
  77. package/src/createDecoPreviewPage.test.tsx +0 -71
  78. package/src/createDecoPreviewPage.tsx +0 -108
  79. package/src/draft.test.ts +0 -73
  80. package/src/draftConstants.test.ts +0 -21
  81. package/src/draftConstants.ts +0 -30
  82. package/src/draftMiddleware.test.ts +0 -179
  83. package/src/draftMiddleware.ts +0 -163
  84. package/src/draftShell.test.ts +0 -76
  85. package/src/routeHandlers.test.ts +0 -243
  86. package/src/routeHandlers.ts +0 -227
  87. package/src/setup.test.ts +0 -253
  88. package/src/setup.ts +0 -180
  89. package/tsconfig.json +0 -7
@@ -0,0 +1,294 @@
1
+ /**
2
+ * `@decocms/nextjs/instrumentation`: `http.server.request.duration` for a
3
+ * Next.js site, from the request span Next.js already opens.
4
+ *
5
+ * Next.js measures every request it serves: `BaseServer.handleRequest` opens
6
+ * one root span per request and, when the response is done, sets its status,
7
+ * its method and, when Next routed it, the route's template (`/[slug]/p`,
8
+ * not the path). It creates spans only while a tracer provider is registered
9
+ * through `@opentelemetry/api` (the app's copy, or Next's compiled one when
10
+ * the app has none; both read the same global registration).
11
+ *
12
+ * `register()`, the site's `instrumentation.ts` hook, registers a provider
13
+ * whose only job is that span: when it ends, the request is recorded with
14
+ * `recordServerRequest` from `@decocms/blocks/metrics`, into the CMS's
15
+ * telemetry in the same process. No span is exported anywhere and every other
16
+ * span Next.js asks for is a shared non-recording one. With no
17
+ * `@opentelemetry/api` reachable, on the Edge runtime, or when another
18
+ * provider is already registered, it registers nothing and never throws.
19
+ *
20
+ * A site that registers its own OpenTelemetry SDK (`@vercel/otel`,
21
+ * `NodeSDK`) adds `serverRequestSpanProcessor()` to that SDK's span
22
+ * processors instead.
23
+ *
24
+ * Labels: `http.route` is Next's route template (`unmatched` when Next names
25
+ * none); RSC requests count under their page's route, as the requests they
26
+ * are. `cache` is `DRAFT` for a draft render (the proxy forwarded
27
+ * `x-deco-draft`, or the request was rewritten onto `/_draft`) and otherwise
28
+ * `BYPASS`: Next decides its cache (`x-nextjs-cache`) on the response, which
29
+ * the span never sees.
30
+ */
31
+ import { recordServerRequest } from "@decocms/blocks/metrics";
32
+ import { DRAFT_HEADER, DRAFT_ROUTE_PREFIX } from "../draft.js";
33
+ /** The span type Next.js gives its one root span per request. */
34
+ const ROOT_SPAN_TYPE = "BaseServer.handleRequest";
35
+ const REGISTERED = Symbol.for("decocms.nextjs.instrumentation");
36
+ /**
37
+ * Under `next start`, the router runs the proxy (`proxy.ts`/`middleware.ts`)
38
+ * through a `handleRequest` of its own, which ends by "bubbling" the proxy's
39
+ * result back to the router (`next.bubble`). That span is the proxy's, not a
40
+ * request: the request it hands on is measured by its own span.
41
+ */
42
+ const BUBBLE = "next.bubble";
43
+ /** OpenTelemetry's `SpanStatusCode.ERROR`. */
44
+ const STATUS_ERROR = 2;
45
+ /** Records one ended `BaseServer.handleRequest` span. Never throws. */
46
+ function recordRequestSpan(attributes, seconds, errored, draft) {
47
+ const route = typeof attributes["http.route"] === "string" ? attributes["http.route"] : undefined;
48
+ const code = Number(attributes["http.status_code"]);
49
+ // Next sets the status before the span ends; a request that threw is a 5xx whatever it says.
50
+ const status = errored && !(code >= 500) ? 500 : code || 500;
51
+ // Never rejects; the span's route is already a string, so nothing to wait for.
52
+ void recordServerRequest({
53
+ route,
54
+ method: String(attributes["http.method"] ?? ""),
55
+ status,
56
+ cache: draft || isDraftRoute(route) ? "DRAFT" : "BYPASS",
57
+ seconds,
58
+ });
59
+ }
60
+ function isDraftRoute(route) {
61
+ if (route === undefined)
62
+ return false;
63
+ const path = route.replace(/^\/%5Fdraft(?=\/|$)/i, DRAFT_ROUTE_PREFIX);
64
+ return path === DRAFT_ROUTE_PREFIX || path.startsWith(`${DRAFT_ROUTE_PREFIX}/`);
65
+ }
66
+ /**
67
+ * For a site with its own OpenTelemetry SDK: a span processor that records
68
+ * `http.server.request.duration` from Next's request span and leaves every
69
+ * span to the SDK's other processors and exporters.
70
+ *
71
+ * ```ts
72
+ * registerOTel({ serviceName: "my-store", spanProcessors: ["auto", serverRequestSpanProcessor()] });
73
+ * ```
74
+ */
75
+ export function serverRequestSpanProcessor() {
76
+ return {
77
+ onStart() { },
78
+ onEnd(span) {
79
+ try {
80
+ const attributes = span.attributes;
81
+ if (attributes?.["next.span_type"] !== ROOT_SPAN_TYPE || attributes[BUBBLE] === true)
82
+ return;
83
+ const [s, ns] = span.duration;
84
+ recordRequestSpan(attributes, s + ns / 1e9, span.status?.code === STATUS_ERROR, false);
85
+ }
86
+ catch {
87
+ // A metric never breaks a request.
88
+ }
89
+ },
90
+ forceFlush: () => Promise.resolve(),
91
+ shutdown: () => Promise.resolve(),
92
+ };
93
+ }
94
+ /**
95
+ * The site's `instrumentation.ts` hook:
96
+ *
97
+ * ```ts
98
+ * // instrumentation.ts
99
+ * export { register } from "@decocms/nextjs/instrumentation";
100
+ * ```
101
+ *
102
+ * Registers the request-metric tracer provider, once per process. Does
103
+ * nothing on the Edge runtime (the Node.js server's request span covers the
104
+ * Edge pages it serves), without `@opentelemetry/api` (the app's or Next's),
105
+ * without `AsyncLocalStorage`, or when another provider is registered (it
106
+ * then says to use `serverRequestSpanProcessor()`). Never throws.
107
+ */
108
+ export async function register() {
109
+ try {
110
+ const global = globalThis;
111
+ if (global[REGISTERED] === true || typeof global.EdgeRuntime === "string")
112
+ return;
113
+ const Storage = global.AsyncLocalStorage;
114
+ if (typeof Storage !== "function")
115
+ return;
116
+ const api = await loadApi();
117
+ if (api === undefined)
118
+ return;
119
+ global[REGISTERED] = true;
120
+ const draft = api.createContextKey("decocms.nextjs.draft");
121
+ if (!api.trace.setGlobalTracerProvider(requestTracerProvider(api, draft))) {
122
+ console.warn("[@decocms/nextjs] Another OpenTelemetry tracer provider is registered, so register() added nothing. " +
123
+ "To record http.server.request.duration, add serverRequestSpanProcessor() to that provider's span processors.");
124
+ return;
125
+ }
126
+ // Both may already be registered by something else; Next's spans work with either.
127
+ api.context.setGlobalContextManager(storageContextManager(api, new Storage()));
128
+ api.propagation.setGlobalPropagator(draftPropagator(draft));
129
+ }
130
+ catch {
131
+ // Instrumentation never stops a server from starting.
132
+ }
133
+ }
134
+ /**
135
+ * The `@opentelemetry/api` Next.js uses: the app's own when installed (Next's
136
+ * bundlers alias it to Next's compiled copy when it isn't), else Next's
137
+ * compiled copy.
138
+ */
139
+ async function loadApi() {
140
+ try {
141
+ return await import("@opentelemetry/api");
142
+ }
143
+ catch {
144
+ try {
145
+ return (await import("next/dist/compiled/@opentelemetry/api/index.js"));
146
+ }
147
+ catch {
148
+ return undefined;
149
+ }
150
+ }
151
+ }
152
+ function requestTracerProvider(api, DRAFT) {
153
+ const IN_REQUEST = api.createContextKey("decocms.nextjs.inRequest");
154
+ const inert = api.trace.wrapSpanContext(api.INVALID_SPAN_CONTEXT);
155
+ const tracer = {
156
+ startSpan(_name, options, context) {
157
+ const ctx = context ?? api.context.active();
158
+ if (options?.attributes?.["next.span_type"] !== ROOT_SPAN_TYPE)
159
+ return inert;
160
+ // A request Next serves inside another (a nested handleRequest) is already measured.
161
+ if (ctx.getValue(IN_REQUEST) === true)
162
+ return inert;
163
+ return new RequestSpan(api.INVALID_SPAN_CONTEXT, options.attributes, ctx.getValue(DRAFT) === true);
164
+ },
165
+ startActiveSpan(name, ...rest) {
166
+ const fn = rest[rest.length - 1];
167
+ const options = (rest.length > 1 ? rest[0] : undefined);
168
+ const context = (rest.length > 2 ? rest[1] : undefined);
169
+ const span = tracer.startSpan(name, options, context);
170
+ // Only the request span changes the context; every other span costs a call.
171
+ if (span === inert)
172
+ return fn(span);
173
+ const ctx = api.trace
174
+ .setSpan(context ?? api.context.active(), span)
175
+ .setValue(IN_REQUEST, true);
176
+ return api.context.with(ctx, fn, undefined, span);
177
+ },
178
+ };
179
+ // Not named NoopTracerProvider: Next checks that name to decide whether tracing is on.
180
+ return { getTracer: () => tracer };
181
+ }
182
+ /** Marks the request's context when the proxy forwarded an accepted draft. */
183
+ function draftPropagator(DRAFT) {
184
+ return {
185
+ inject() { },
186
+ extract(context, carrier, getter) {
187
+ try {
188
+ const value = getter.get(carrier, DRAFT_HEADER);
189
+ return value !== undefined && value !== "" ? context.setValue(DRAFT, true) : context;
190
+ }
191
+ catch {
192
+ return context;
193
+ }
194
+ },
195
+ fields: () => [],
196
+ };
197
+ }
198
+ /** The request span: keeps only the attributes the metric needs and records on `end()`. */
199
+ class RequestSpan {
200
+ #context;
201
+ #attributes = {};
202
+ #start = performance.now();
203
+ #draft;
204
+ #errored = false;
205
+ #proxy = false;
206
+ #ended = false;
207
+ constructor(context, attributes, draft) {
208
+ this.#context = context;
209
+ this.#draft = draft;
210
+ if (attributes)
211
+ this.setAttributes(attributes);
212
+ }
213
+ spanContext() {
214
+ return this.#context;
215
+ }
216
+ setAttribute(key, value) {
217
+ if (key === "http.route" || key === "http.status_code" || key === "http.method") {
218
+ this.#attributes[key] = value;
219
+ }
220
+ else if (key === BUBBLE && value === true) {
221
+ this.#proxy = true;
222
+ }
223
+ return this;
224
+ }
225
+ setAttributes(attributes) {
226
+ for (const key in attributes)
227
+ this.setAttribute(key, attributes[key]);
228
+ return this;
229
+ }
230
+ setStatus(status) {
231
+ if (status?.code === STATUS_ERROR)
232
+ this.#errored = true;
233
+ return this;
234
+ }
235
+ addEvent() {
236
+ return this;
237
+ }
238
+ addLink() {
239
+ return this;
240
+ }
241
+ addLinks() {
242
+ return this;
243
+ }
244
+ updateName() {
245
+ return this;
246
+ }
247
+ recordException(exception) {
248
+ // The proxy's own invocation ends with an error marked `bubble`, whatever its type.
249
+ if (exception?.bubble === true)
250
+ this.#proxy = true;
251
+ }
252
+ isRecording() {
253
+ return !this.#ended;
254
+ }
255
+ end() {
256
+ if (this.#ended)
257
+ return;
258
+ this.#ended = true;
259
+ if (this.#proxy)
260
+ return;
261
+ try {
262
+ const seconds = (performance.now() - this.#start) / 1000;
263
+ recordRequestSpan(this.#attributes, seconds, this.#errored, this.#draft);
264
+ }
265
+ catch {
266
+ // A metric never breaks a request.
267
+ }
268
+ }
269
+ }
270
+ /** OpenTelemetry's context manager over `AsyncLocalStorage` (what `@opentelemetry/context-async-hooks` does). */
271
+ function storageContextManager(api, storage) {
272
+ const manager = {
273
+ active: () => storage.getStore() ?? api.ROOT_CONTEXT,
274
+ with(context, fn, thisArg, ...args) {
275
+ const call = thisArg === undefined ? fn : fn.bind(thisArg);
276
+ return storage.run(context, call, ...args);
277
+ },
278
+ bind(context, target) {
279
+ if (typeof target !== "function")
280
+ return target;
281
+ const fn = target;
282
+ return function (...args) {
283
+ return manager.with(context, () => fn.apply(this, args));
284
+ };
285
+ },
286
+ enable: () => manager,
287
+ disable() {
288
+ storage.disable();
289
+ return manager;
290
+ },
291
+ };
292
+ return manager;
293
+ }
294
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/instrumentation/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `@decocms/nextjs/proxy`: draft previews in a Next.js proxy (`proxy.ts`, or
3
+ * `middleware.ts` before Next 16). No React and no block map: pass the proxy
4
+ * a CMS handle made with an empty block map and the app's options, which
5
+ * shares the app's instance (one per process) without pulling the site's
6
+ * sections into the proxy bundle:
7
+ *
8
+ * ```ts
9
+ * // src/cms.proxy.ts
10
+ * export const proxyCms = createCMS({ blocks: {}, ...cmsConfig });
11
+ * ```
12
+ *
13
+ * A site with a proxy of its own calls the pieces (`decideDraft`, then
14
+ * `draftRequestHeaders` on the request it forwards, then `applyDraft` on the
15
+ * response); a site without one exports `draftProxy(...)`.
16
+ *
17
+ * Statically rendered pages can't read a draft, so a drafted request to one
18
+ * is rewritten onto a dynamic draft route (`/_draft/<path>`, mounted as
19
+ * `app/%5Fdraft/...`: a literal `_draft` folder is private in Next and never
20
+ * routes).
21
+ *
22
+ * The proxy measures no requests: it runs before Next routes and renders the
23
+ * page. `http.server.request.duration` comes from Next's own request span
24
+ * (`register` from `@decocms/nextjs/instrumentation`).
25
+ */
26
+ import type { CMS } from "@decocms/blocks";
27
+ import { type NextRequest, NextResponse } from "next/server.js";
28
+ import { type PreviewPolicy } from "../drafts.ts";
29
+ export { inferredPreviewHosts, type PreviewPolicy, previewHostDecision, requestLike, } from "../drafts.ts";
30
+ /** What the proxy needs from the CMS: the site's declared preview hosts and their check. */
31
+ export type CMSHandle = Pick<CMS, "draftPointer" | "settings" | "forRelease">;
32
+ /** What the proxy does with a request's draft state. */
33
+ export interface DraftDecision {
34
+ /** The draft this request renders, after the host check; `null` for the release. */
35
+ pointer: string | null;
36
+ /** The `Set-Cookie` the response carries: storing a draft link's pointer, or ending a preview. */
37
+ setCookie: string | null;
38
+ }
39
+ export { DRAFT_ROUTE_PREFIX } from "../draft.ts";
40
+ /**
41
+ * The draft a request renders and the cookie its response sets, under
42
+ * `policy` (the same options the pages pass to `bindDraft`). A request with
43
+ * no `?__draft=` and no draft cookie never reaches the CMS.
44
+ */
45
+ export declare function decideDraft(cms: CMSHandle, request: NextRequest | Request, policy?: PreviewPolicy): Promise<DraftDecision>;
46
+ /**
47
+ * The request headers to forward: the accepted pointer on `x-deco-draft`, so
48
+ * the root layout can bind the draft on the same request (a layout gets no
49
+ * `searchParams`, and on a draft link the cookie is only on the response).
50
+ * A copy that arrives from outside is always removed.
51
+ */
52
+ export declare function draftRequestHeaders(request: NextRequest | Request, decision: DraftDecision): Headers;
53
+ /** Whether `pathname` is on the draft route: a site answers a direct hit there with its 404. */
54
+ export declare function isDraftRoutePath(pathname: string): boolean;
55
+ /**
56
+ * The rewrite of a drafted request onto the draft route, keeping its path and
57
+ * forwarding the pointer; `null` when the request isn't drafted or is already
58
+ * on the draft route.
59
+ */
60
+ export declare function rewriteToDraftRoute(request: NextRequest, decision: DraftDecision): NextResponse | null;
61
+ /**
62
+ * The cookie and the response headers of a draft: `Set-Cookie`, and for a
63
+ * drafted response `Cache-Control: no-store, private`, `Vary: Cookie` and
64
+ * `X-Robots-Tag: noindex, nofollow` (`draftResponseHeaders`). With the
65
+ * pointer in a cookie a draft shares its URL with the published page, so a
66
+ * cache keyed on the URL alone would serve it to visitors.
67
+ */
68
+ export declare function applyDraft<R extends Response>(response: R, decision: DraftDecision): R;
69
+ export interface DraftProxyOptions extends PreviewPolicy {
70
+ /**
71
+ * Which drafted requests go to the draft route (a statically rendered
72
+ * page's). Default: every drafted request outside the draft route. Return
73
+ * `false` for a route that is already dynamic and binds the draft itself.
74
+ */
75
+ rewrite?: (request: NextRequest) => boolean;
76
+ }
77
+ /**
78
+ * The whole draft flow for a site with no proxy of its own:
79
+ * `export const proxy = draftProxy(proxyCms, { site: "my-store" })`.
80
+ */
81
+ export declare function draftProxy(cms: CMSHandle, options?: DraftProxyOptions): (request: NextRequest) => Promise<NextResponse>;
82
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAML,KAAK,aAAa,EAEnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,mBAAmB,EACnB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB,4FAA4F;AAC5F,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC,CAAC;AAE9E,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kGAAkG;IAClG,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,WAAW,GAAG,OAAO,EAC9B,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,aAAa,CAAC,CAYxB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,WAAW,GAAG,OAAO,EAC9B,QAAQ,EAAE,aAAa,GACtB,OAAO,CAKT;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,aAAa,GACtB,YAAY,GAAG,IAAI,CAQrB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,aAAa,GAAG,CAAC,CAMtF;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,SAAS,EACd,OAAO,GAAE,iBAAsB,GAC9B,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAUjD"}
@@ -0,0 +1,91 @@
1
+ import { NextResponse } from "next/server.js";
2
+ import { DRAFT_HEADER, DRAFT_ROUTE_PREFIX, draftResponseHeaders } from "../draft.js";
3
+ import { allowedDraft, draftCookie, endsPreview, fromDraftLink, hasDraftSignal, requestLike, } from "../drafts.js";
4
+ export { inferredPreviewHosts, previewHostDecision, requestLike, } from "../drafts.js";
5
+ export { DRAFT_ROUTE_PREFIX } from "../draft.js";
6
+ const INERT = Object.freeze({ pointer: null, setCookie: null });
7
+ /**
8
+ * The draft a request renders and the cookie its response sets, under
9
+ * `policy` (the same options the pages pass to `bindDraft`). A request with
10
+ * no `?__draft=` and no draft cookie never reaches the CMS.
11
+ */
12
+ export async function decideDraft(cms, request, policy = {}) {
13
+ const url = new URL(request.url);
14
+ const like = requestLike(request.headers, url.pathname + url.search, url.host);
15
+ if (like === null || !hasDraftSignal(like))
16
+ return INERT;
17
+ // Ending a preview is allowed on any host, so a cookie set earlier always goes.
18
+ if (endsPreview(like))
19
+ return { pointer: null, setCookie: draftCookie("", ["Max-Age=0"]) };
20
+ const pointer = await allowedDraft(cms, like, policy);
21
+ if (pointer === null)
22
+ return INERT;
23
+ return {
24
+ pointer,
25
+ setCookie: fromDraftLink(like, pointer) ? draftCookie(encodeURIComponent(pointer)) : null,
26
+ };
27
+ }
28
+ /**
29
+ * The request headers to forward: the accepted pointer on `x-deco-draft`, so
30
+ * the root layout can bind the draft on the same request (a layout gets no
31
+ * `searchParams`, and on a draft link the cookie is only on the response).
32
+ * A copy that arrives from outside is always removed.
33
+ */
34
+ export function draftRequestHeaders(request, decision) {
35
+ const headers = new Headers(request.headers);
36
+ if (decision.pointer)
37
+ headers.set(DRAFT_HEADER, decision.pointer);
38
+ else
39
+ headers.delete(DRAFT_HEADER);
40
+ return headers;
41
+ }
42
+ /** Whether `pathname` is on the draft route: a site answers a direct hit there with its 404. */
43
+ export function isDraftRoutePath(pathname) {
44
+ return pathname === DRAFT_ROUTE_PREFIX || pathname.startsWith(`${DRAFT_ROUTE_PREFIX}/`);
45
+ }
46
+ /**
47
+ * The rewrite of a drafted request onto the draft route, keeping its path and
48
+ * forwarding the pointer; `null` when the request isn't drafted or is already
49
+ * on the draft route.
50
+ */
51
+ export function rewriteToDraftRoute(request, decision) {
52
+ if (!decision.pointer)
53
+ return null;
54
+ const url = request.nextUrl.clone();
55
+ if (isDraftRoutePath(url.pathname))
56
+ return null;
57
+ url.pathname = `${DRAFT_ROUTE_PREFIX}${url.pathname}`;
58
+ return NextResponse.rewrite(url, {
59
+ request: { headers: draftRequestHeaders(request, decision) },
60
+ });
61
+ }
62
+ /**
63
+ * The cookie and the response headers of a draft: `Set-Cookie`, and for a
64
+ * drafted response `Cache-Control: no-store, private`, `Vary: Cookie` and
65
+ * `X-Robots-Tag: noindex, nofollow` (`draftResponseHeaders`). With the
66
+ * pointer in a cookie a draft shares its URL with the published page, so a
67
+ * cache keyed on the URL alone would serve it to visitors.
68
+ */
69
+ export function applyDraft(response, decision) {
70
+ if (decision.setCookie)
71
+ response.headers.append("set-cookie", decision.setCookie);
72
+ for (const [name, value] of Object.entries(draftResponseHeaders(decision.pointer))) {
73
+ response.headers.set(name, value);
74
+ }
75
+ return response;
76
+ }
77
+ /**
78
+ * The whole draft flow for a site with no proxy of its own:
79
+ * `export const proxy = draftProxy(proxyCms, { site: "my-store" })`.
80
+ */
81
+ export function draftProxy(cms, options = {}) {
82
+ const { rewrite = () => true, ...policy } = options;
83
+ return async (request) => {
84
+ const decision = await decideDraft(cms, request, policy);
85
+ const rewritten = rewrite(request) ? rewriteToDraftRoute(request, decision) : null;
86
+ const response = rewritten ??
87
+ NextResponse.next({ request: { headers: draftRequestHeaders(request, decision) } });
88
+ return applyDraft(response, decision);
89
+ };
90
+ }
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/proxy/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `@decocms/nextjs/server`: draft previews in Server Components (pages,
3
+ * layouts, route handlers). Server only: it reads `next/headers`.
4
+ *
5
+ * A page (and, through the `x-deco-draft` header the proxy forwards, the root
6
+ * layout) binds the request's draft before it reads content, then reads
7
+ * through `contentClient(cms)`:
8
+ *
9
+ * ```tsx
10
+ * export default async function Page({ searchParams }) {
11
+ * await bindDraft(cms, { searchParams: await searchParams, site: "my-store" });
12
+ * const client = contentClient(cms);
13
+ * …
14
+ * return <>{sections}<DraftPreviewIndicator /></>;
15
+ * }
16
+ * ```
17
+ *
18
+ * The host check is the proxy's (`decideDraft`), repeated here with the same
19
+ * options, so a route the proxy doesn't match, or a forged `x-deco-draft`,
20
+ * still gets the release on a host that may not preview.
21
+ */
22
+ import type { Client, CMS } from "@decocms/blocks";
23
+ import { type PreviewPolicy } from "../drafts.ts";
24
+ import { activeDraftPointer } from "./slot.ts";
25
+ export { type PreviewPolicy, requestLike } from "../drafts.ts";
26
+ export { activeDraftPointer };
27
+ /** A page's `searchParams` prop, awaited. */
28
+ export type SearchParams = Record<string, string | string[] | undefined>;
29
+ export interface BindDraftOptions extends PreviewPolicy {
30
+ /** The page's own `searchParams` (a layout has none: it reads the proxy's `x-deco-draft`). */
31
+ searchParams?: SearchParams;
32
+ }
33
+ /**
34
+ * Binds the request's draft, if any: the page's own `?__draft=` first (`off`
35
+ * leaves preview), then the pointer the proxy forwarded on `x-deco-draft`,
36
+ * then the draft cookie, each on a host the options allow. Returns whether a
37
+ * draft was bound. With previews off on every host it returns `false`
38
+ * without reading the request, so a static page stays static.
39
+ */
40
+ export declare function bindDraft(cms: Pick<CMS, "draftPointer" | "settings" | "forRelease">, options?: BindDraftOptions): Promise<boolean>;
41
+ /**
42
+ * The content client for this request: the draft `bindDraft` bound, or the
43
+ * release.
44
+ */
45
+ export declare function contentClient(cms: Pick<CMS, "forDraft" | "forRelease">): Client;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAuC,KAAK,aAAa,EAAe,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,kBAAkB,EAAa,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAE,KAAK,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAEzE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,8FAA8F;IAC9F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAOD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC,EAC1D,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,CAAC,GAAG,MAAM,CAG/E"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `@decocms/nextjs/server`: draft previews in Server Components (pages,
3
+ * layouts, route handlers). Server only: it reads `next/headers`.
4
+ *
5
+ * A page (and, through the `x-deco-draft` header the proxy forwards, the root
6
+ * layout) binds the request's draft before it reads content, then reads
7
+ * through `contentClient(cms)`:
8
+ *
9
+ * ```tsx
10
+ * export default async function Page({ searchParams }) {
11
+ * await bindDraft(cms, { searchParams: await searchParams, site: "my-store" });
12
+ * const client = contentClient(cms);
13
+ * …
14
+ * return <>{sections}<DraftPreviewIndicator /></>;
15
+ * }
16
+ * ```
17
+ *
18
+ * The host check is the proxy's (`decideDraft`), repeated here with the same
19
+ * options, so a route the proxy doesn't match, or a forged `x-deco-draft`,
20
+ * still gets the release on a host that may not preview.
21
+ */
22
+ import { headers } from "next/headers.js";
23
+ import { DRAFT_HEADER, DRAFT_PARAM } from "../draft.js";
24
+ import { allowedDraft, isDraftPreviewEnabled, requestLike } from "../drafts.js";
25
+ import { activeDraftPointer, draftSlot } from "./slot.js";
26
+ export { requestLike } from "../drafts.js";
27
+ export { activeDraftPointer };
28
+ function firstParam(searchParams, key) {
29
+ const raw = searchParams?.[key];
30
+ return (Array.isArray(raw) ? raw[0] : raw) ?? null;
31
+ }
32
+ /**
33
+ * Binds the request's draft, if any: the page's own `?__draft=` first (`off`
34
+ * leaves preview), then the pointer the proxy forwarded on `x-deco-draft`,
35
+ * then the draft cookie, each on a host the options allow. Returns whether a
36
+ * draft was bound. With previews off on every host it returns `false`
37
+ * without reading the request, so a static page stays static.
38
+ */
39
+ export async function bindDraft(cms, options = {}) {
40
+ const { searchParams, ...policy } = options;
41
+ if (!(await isDraftPreviewEnabled(cms, policy)))
42
+ return false;
43
+ const param = firstParam(searchParams, DRAFT_PARAM);
44
+ if (param === "off")
45
+ return false;
46
+ const requestHeaders = await headers();
47
+ const forwarded = param ?? requestHeaders.get(DRAFT_HEADER);
48
+ const query = forwarded ? `?${DRAFT_PARAM}=${encodeURIComponent(forwarded)}` : "";
49
+ const request = requestLike(requestHeaders, `/${query}`);
50
+ const pointer = request ? await allowedDraft(cms, request, policy) : null;
51
+ if (pointer === null)
52
+ return false;
53
+ draftSlot().pointer = pointer;
54
+ return true;
55
+ }
56
+ /**
57
+ * The content client for this request: the draft `bindDraft` bound, or the
58
+ * release.
59
+ */
60
+ export function contentClient(cms) {
61
+ const pointer = activeDraftPointer();
62
+ return pointer ? cms.forDraft(pointer) : cms.forRelease();
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAsB,WAAW,EAAE,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE1D,OAAO,EAAsB,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAU9B,SAAS,UAAU,CAAC,YAAsC,EAAE,GAAW;IACrE,MAAM,GAAG,GAAG,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAA0D,EAC1D,OAAO,GAAqB,EAAE;IAE9B,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC;IAC5C,IAAI,CAAC,CAAC,MAAM,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACpD,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,cAAc,GAAG,MAAM,OAAO,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,KAAK,IAAI,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,WAAW,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,MAAM,OAAO,GAAG,WAAW,CAAC,cAAyB,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,SAAS,EAAE,CAAC,OAAO,GAAG,OAAO,CAAC;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAyC;IACrE,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const draftSlot: () => {
2
+ pointer: string | null;
3
+ };
4
+ /** The draft pointer bound to this request (`bindDraft`), or `null` when it renders the release. */
5
+ export declare function activeDraftPointer(): string | null;
6
+ //# sourceMappingURL=slot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../src/server/slot.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,SAAS,QAAa;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAA0B,CAAC;AAEtF,oGAAoG;AACpG,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAElD"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The draft a request renders, held for the request's Server Components.
3
+ * React's `cache()` gives one slot per request in the React Server
4
+ * Components graph, the scope a draft needs; no `next/headers` here, so the
5
+ * root components can read it without binding anything.
6
+ */
7
+ import { cache } from "react";
8
+ export const draftSlot = cache(() => ({ pointer: null }));
9
+ /** The draft pointer bound to this request (`bindDraft`), or `null` when it renders the release. */
10
+ export function activeDraftPointer() {
11
+ return draftSlot().pointer;
12
+ }
13
+ //# sourceMappingURL=slot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.js","sourceRoot":"","sources":["../../src/server/slot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE9B,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC,GAA+B,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAEtF,oGAAoG;AACpG,MAAM,UAAU,kBAAkB;IAChC,OAAO,SAAS,EAAE,CAAC,OAAO,CAAC;AAC7B,CAAC"}