@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.
- package/README.md +97 -328
- package/dist/DraftPreviewIndicator.d.ts +7 -0
- package/dist/DraftPreviewIndicator.d.ts.map +1 -0
- package/dist/DraftPreviewIndicator.js +19 -0
- package/dist/DraftPreviewIndicator.js.map +1 -0
- package/dist/StudioBridge.d.ts +16 -0
- package/dist/StudioBridge.d.ts.map +1 -0
- package/dist/StudioBridge.js +17 -0
- package/dist/StudioBridge.js.map +1 -0
- package/dist/draft.d.ts +37 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +45 -0
- package/dist/draft.js.map +1 -0
- package/dist/drafts.d.ts +94 -0
- package/dist/drafts.d.ts.map +1 -0
- package/dist/drafts.js +210 -0
- package/dist/drafts.js.map +1 -0
- package/dist/image-loader/index.d.ts +36 -0
- package/dist/image-loader/index.d.ts.map +1 -0
- package/dist/image-loader/index.js +27 -0
- package/dist/image-loader/index.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +74 -0
- package/dist/instrumentation/index.d.ts.map +1 -0
- package/dist/instrumentation/index.js +294 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/proxy/index.d.ts +82 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +91 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/server/index.d.ts +46 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +64 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/slot.d.ts +6 -0
- package/dist/server/slot.d.ts.map +1 -0
- package/dist/server/slot.js +13 -0
- package/dist/server/slot.js.map +1 -0
- package/dist/studio.d.ts +30 -0
- package/dist/studio.d.ts.map +1 -0
- package/dist/studio.js +55 -0
- package/dist/studio.js.map +1 -0
- package/package.json +53 -22
- package/src/DraftPreviewIndicator.tsx +17 -16
- package/src/StudioBridge.ts +23 -0
- package/src/config/index.cjs +78 -0
- package/src/config/index.d.cts +27 -0
- package/src/draft.ts +32 -215
- package/src/drafts.ts +229 -0
- package/src/image-loader/index.ts +62 -0
- package/src/index.ts +31 -45
- package/src/instrumentation/index.ts +337 -0
- package/src/proxy/index.ts +164 -0
- package/src/server/index.ts +77 -0
- package/src/server/slot.ts +14 -0
- package/src/studio.ts +54 -0
- package/src/ClientOnlySection.tsx +0 -20
- package/src/DecoPageRenderer.test.tsx +0 -37
- package/src/DecoPageRenderer.tsx +0 -107
- package/src/DecoRootLayout.test.tsx +0 -22
- package/src/DecoRootLayout.tsx +0 -82
- package/src/DeferredSection.test.tsx +0 -27
- package/src/DeferredSection.tsx +0 -59
- package/src/DraftPreviewBadge.tsx +0 -16
- package/src/SectionRenderer.test.tsx +0 -97
- package/src/SectionRenderer.tsx +0 -58
- package/src/__conformance__/observability.test.ts +0 -66
- package/src/config.cjs +0 -92
- package/src/config.d.cts +0 -4
- package/src/config.test.ts +0 -86
- package/src/createDecoPage.draft.test.tsx +0 -91
- package/src/createDecoPage.test.tsx +0 -63
- package/src/createDecoPage.tsx +0 -173
- package/src/createDecoPreviewPage.test.tsx +0 -71
- package/src/createDecoPreviewPage.tsx +0 -108
- package/src/draft.test.ts +0 -73
- package/src/draftConstants.test.ts +0 -21
- package/src/draftConstants.ts +0 -30
- package/src/draftMiddleware.test.ts +0 -179
- package/src/draftMiddleware.ts +0 -163
- package/src/draftShell.test.ts +0 -76
- package/src/routeHandlers.test.ts +0 -243
- package/src/routeHandlers.ts +0 -227
- package/src/setup.test.ts +0 -253
- package/src/setup.ts +0 -180
- 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"}
|