@plenary/next 1.0.2 → 1.1.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/dist/{chunk-J5OXC4XR.js → chunk-7XALU3L6.js} +10 -29
- package/dist/chunk-7XALU3L6.js.map +1 -0
- package/dist/index.cjs +9 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/pages.cjs +9 -28
- package/dist/pages.cjs.map +1 -1
- package/dist/pages.d.cts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1 -1
- package/dist/resolve-CvKtOFT_.d.cts +21 -0
- package/dist/resolve-CvKtOFT_.d.ts +21 -0
- package/package.json +2 -2
- package/dist/chunk-J5OXC4XR.js.map +0 -1
- package/dist/resolve-RT8_HYfz.d.cts +0 -44
- package/dist/resolve-RT8_HYfz.d.ts +0 -44
|
@@ -36,34 +36,15 @@ function createTaggedFetch(baseFetch = fetch) {
|
|
|
36
36
|
|
|
37
37
|
// src/resolve.ts
|
|
38
38
|
import {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
planRevalidation as planPaths,
|
|
40
|
+
isEntryWebhookPayload
|
|
41
41
|
} from "@plenary/client";
|
|
42
|
-
|
|
43
|
-
const { slug, path } = payload.entry;
|
|
44
|
-
if (!slug) return null;
|
|
45
|
-
const dir = path && path !== "/" ? path : "";
|
|
46
|
-
if (!dir && slug === "home") return "/";
|
|
47
|
-
return `${dir}/${slug}`;
|
|
48
|
-
}
|
|
42
|
+
import { entryUrl } from "@plenary/client";
|
|
49
43
|
function planRevalidation(payload, options) {
|
|
50
|
-
const
|
|
51
|
-
if (!isEntryWebhookPayload(payload)) return none;
|
|
52
|
-
const test = payload.event === WEBHOOK_TEST_EVENT;
|
|
53
|
-
if (!test && options.events && !options.events.includes(payload.event)) {
|
|
54
|
-
return none;
|
|
55
|
-
}
|
|
56
|
-
const modelKey = payload.model.key;
|
|
57
|
-
const mapped = modelKey === void 0 ? void 0 : options.paths?.[modelKey];
|
|
58
|
-
let paths;
|
|
59
|
-
if (mapped === void 0) {
|
|
60
|
-
paths = (options.entryUrl ?? entryUrl)(payload);
|
|
61
|
-
} else {
|
|
62
|
-
paths = typeof mapped === "function" ? mapped(payload) : mapped;
|
|
63
|
-
}
|
|
64
|
-
const pathList = paths == null ? [] : typeof paths === "string" ? [paths] : paths;
|
|
44
|
+
const plan = planPaths(payload, options);
|
|
65
45
|
const tags = [];
|
|
66
|
-
if (options.tags) {
|
|
46
|
+
if (plan.relevant && options.tags && isEntryWebhookPayload(payload)) {
|
|
47
|
+
const modelKey = payload.model.key;
|
|
67
48
|
if (modelKey) {
|
|
68
49
|
tags.push(modelTag(modelKey));
|
|
69
50
|
if (payload.changed.some((p) => p === "slug" || p === "path")) tags.push(modelEntriesTag(modelKey));
|
|
@@ -71,7 +52,7 @@ function planRevalidation(payload, options) {
|
|
|
71
52
|
tags.push(entryTag(payload.entry.id));
|
|
72
53
|
if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));
|
|
73
54
|
}
|
|
74
|
-
return {
|
|
55
|
+
return { ...plan, tags };
|
|
75
56
|
}
|
|
76
57
|
|
|
77
58
|
export {
|
|
@@ -79,7 +60,7 @@ export {
|
|
|
79
60
|
entryTag,
|
|
80
61
|
modelEntriesTag,
|
|
81
62
|
createTaggedFetch,
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
planRevalidation,
|
|
64
|
+
entryUrl
|
|
84
65
|
};
|
|
85
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-7XALU3L6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tags.ts","../src/resolve.ts"],"sourcesContent":["/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. The path\n * planning lives in `@plenary/client`; this adds the cache tags. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n planRevalidation as planPaths,\n isEntryWebhookPayload,\n type RevalidateOptions as PathOptions,\n type RevalidationPlan as PathPlan,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\nexport { entryUrl, type RevalidatePaths } from \"@plenary/client\";\n\nexport interface RevalidateOptions extends PathOptions {\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\nexport interface RevalidationPlan extends PathPlan {\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const plan = planPaths(payload, options);\n const tags: string[] = [];\n if (plan.relevant && options.tags && isEntryWebhookPayload(payload)) {\n const modelKey = payload.model.key;\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n return { ...plan, tags };\n}\n"],"mappings":";AAUO,SAAS,SAAS,UAA0B;AACjD,SAAO,iBAAiB,QAAQ;AAClC;AAIO,SAAS,SAAS,UAA0B;AACjD,SAAO,iBAAiB,QAAQ;AAClC;AAKO,SAAS,gBAAgB,UAA0B;AACxD,SAAO,yBAAyB,QAAQ;AAC1C;AAQA,IAAM,cAAc;AAGpB,SAAS,WAAW,KAAiC;AACnD,QAAM,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC;AAC7C,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,gBAAgB,KAAK;AACxC,QAAM,SAAS,CAAC,GAAG,OAAO,OAAO,YAAY,GAAG,GAAG,OAAO,OAAO,gBAAgB,CAAC;AAClF,SAAO,OAAO,WAAW,IAAI,OAAO,CAAC,IAAI;AAC3C;AAeO,SAAS,kBAAkB,YAA0B,OAAqB;AAC/E,UAAQ,CAAC,OAA0B,SAAuB;AACxD,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,MACJ,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAO,MAAM;AAChF,UAAM,QAAQ,WAAW,QAAQ,YAAY,KAAK,GAAG,IAAI;AACzD,QAAI,CAAC,QAAQ,CAAC,EAAG,QAAO,UAAU,OAAO,IAAI;AAE7C,UAAM,QAAQ,mBAAmB,MAAM,CAAC,CAAC;AACzC,UAAM,QAAQ,MAAM,CAAC,IAAI,mBAAmB,MAAM,CAAC,CAAC,IAAI,WAAW,GAAG;AACtE,UAAM,OAAO,QAAQ,CAAC,gBAAgB,KAAK,GAAG,SAAS,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC;AACjF,UAAM,OAAQ,MAAsC;AACpD,UAAM,SAA0B;AAAA,MAC9B,GAAG;AAAA,MACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,GAAI,MAAM,QAAQ,CAAC,GAAI,GAAG,IAAI,EAAE;AAAA,IAC1D;AACA,WAAO,UAAU,OAAO,MAAM;AAAA,EAChC;AACF;;;ACpEA;AAAA,EACE,oBAAoB;AAAA,EACpB;AAAA,OAIK;AAGP,SAAS,gBAAsC;AAaxC,SAAS,iBACd,SACA,SACkB;AAClB,QAAM,OAAO,UAAU,SAAS,OAAO;AACvC,QAAM,OAAiB,CAAC;AACxB,MAAI,KAAK,YAAY,QAAQ,QAAQ,sBAAsB,OAAO,GAAG;AACnE,UAAM,WAAW,QAAQ,MAAM;AAC/B,QAAI,UAAU;AACZ,WAAK,KAAK,SAAS,QAAQ,CAAC;AAC5B,UAAI,QAAQ,QAAQ,KAAK,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,EAAG,MAAK,KAAK,gBAAgB,QAAQ,CAAC;AAAA,IACpG;AACA,SAAK,KAAK,SAAS,QAAQ,MAAM,EAAE,CAAC;AACpC,QAAI,QAAQ,MAAM,KAAM,MAAK,KAAK,SAAS,QAAQ,MAAM,IAAI,CAAC;AAAA,EAChE;AACA,SAAO,EAAE,GAAG,MAAM,KAAK;AACzB;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
createRevalidateHandler: () => createRevalidateHandler,
|
|
24
24
|
createTaggedFetch: () => createTaggedFetch,
|
|
25
25
|
entryTag: () => entryTag,
|
|
26
|
-
entryUrl: () => entryUrl,
|
|
26
|
+
entryUrl: () => import_client2.entryUrl,
|
|
27
27
|
modelEntriesTag: () => modelEntriesTag,
|
|
28
28
|
modelTag: () => modelTag,
|
|
29
29
|
planRevalidation: () => planRevalidation
|
|
@@ -32,7 +32,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
32
32
|
|
|
33
33
|
// src/revalidate.ts
|
|
34
34
|
var import_cache = require("next/cache");
|
|
35
|
-
var
|
|
35
|
+
var import_client3 = require("@plenary/client");
|
|
36
36
|
|
|
37
37
|
// src/resolve.ts
|
|
38
38
|
var import_client = require("@plenary/client");
|
|
@@ -74,31 +74,12 @@ function createTaggedFetch(baseFetch = fetch) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// src/resolve.ts
|
|
77
|
-
|
|
78
|
-
const { slug, path } = payload.entry;
|
|
79
|
-
if (!slug) return null;
|
|
80
|
-
const dir = path && path !== "/" ? path : "";
|
|
81
|
-
if (!dir && slug === "home") return "/";
|
|
82
|
-
return `${dir}/${slug}`;
|
|
83
|
-
}
|
|
77
|
+
var import_client2 = require("@plenary/client");
|
|
84
78
|
function planRevalidation(payload, options) {
|
|
85
|
-
const
|
|
86
|
-
if (!(0, import_client.isEntryWebhookPayload)(payload)) return none;
|
|
87
|
-
const test = payload.event === import_client.WEBHOOK_TEST_EVENT;
|
|
88
|
-
if (!test && options.events && !options.events.includes(payload.event)) {
|
|
89
|
-
return none;
|
|
90
|
-
}
|
|
91
|
-
const modelKey = payload.model.key;
|
|
92
|
-
const mapped = modelKey === void 0 ? void 0 : options.paths?.[modelKey];
|
|
93
|
-
let paths;
|
|
94
|
-
if (mapped === void 0) {
|
|
95
|
-
paths = (options.entryUrl ?? entryUrl)(payload);
|
|
96
|
-
} else {
|
|
97
|
-
paths = typeof mapped === "function" ? mapped(payload) : mapped;
|
|
98
|
-
}
|
|
99
|
-
const pathList = paths == null ? [] : typeof paths === "string" ? [paths] : paths;
|
|
79
|
+
const plan = (0, import_client.planRevalidation)(payload, options);
|
|
100
80
|
const tags = [];
|
|
101
|
-
if (options.tags) {
|
|
81
|
+
if (plan.relevant && options.tags && (0, import_client.isEntryWebhookPayload)(payload)) {
|
|
82
|
+
const modelKey = payload.model.key;
|
|
102
83
|
if (modelKey) {
|
|
103
84
|
tags.push(modelTag(modelKey));
|
|
104
85
|
if (payload.changed.some((p) => p === "slug" || p === "path")) tags.push(modelEntriesTag(modelKey));
|
|
@@ -106,7 +87,7 @@ function planRevalidation(payload, options) {
|
|
|
106
87
|
tags.push(entryTag(payload.entry.id));
|
|
107
88
|
if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));
|
|
108
89
|
}
|
|
109
|
-
return {
|
|
90
|
+
return { ...plan, tags };
|
|
110
91
|
}
|
|
111
92
|
|
|
112
93
|
// src/revalidate.ts
|
|
@@ -114,9 +95,9 @@ function createRevalidateHandler(options) {
|
|
|
114
95
|
return async (request) => {
|
|
115
96
|
let payload;
|
|
116
97
|
try {
|
|
117
|
-
payload = await (0,
|
|
98
|
+
payload = await (0, import_client3.parseWebhookRequest)(request, options);
|
|
118
99
|
} catch (err) {
|
|
119
|
-
if (err instanceof
|
|
100
|
+
if (err instanceof import_client3.PlenaryWebhookError) {
|
|
120
101
|
return Response.json({ message: err.message }, { status: err.status });
|
|
121
102
|
}
|
|
122
103
|
throw err;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/revalidate.ts","../src/resolve.ts","../src/tags.ts"],"sourcesContent":["/**\n * @plenary/next — Next.js glue for Plenary: webhook-driven cache revalidation\n * and cache-tagged content fetching. The Pages Router handler lives in\n * `@plenary/next/pages` so this entry stays free of `next` type imports.\n */\n\nexport { createRevalidateHandler } from \"./revalidate.js\";\nexport {\n entryUrl,\n planRevalidation,\n type RevalidateOptions,\n type RevalidatePaths,\n type RevalidationPlan,\n} from \"./resolve.js\";\nexport { createTaggedFetch, modelTag, modelEntriesTag, entryTag } from \"./tags.js\";\n","import { revalidatePath, revalidateTag } from \"next/cache\";\nimport { parseWebhookRequest, PlenaryWebhookError } from \"@plenary/client\";\nimport { planRevalidation, type RevalidateOptions } from \"./resolve.js\";\n\n/**\n * Builds an App Router route handler that turns Plenary entry webhooks into\n * `revalidatePath`/`revalidateTag` calls.\n *\n * ```ts\n * // app/api/revalidate/route.ts\n * export const POST = createRevalidateHandler({\n * secret: process.env.PLENARY_WEBHOOK_SECRET!,\n * });\n * ```\n *\n * With no `paths` map, each event revalidates the entry's own URL (folder\n * path + slug). Point the webhook at the route, subscribed to the entry\n * events, with the secret in an `x-plenary-secret` header.\n *\n * The console's Test button gets a dry run: a 200 reporting what would have\n * been revalidated, without touching the cache.\n */\nexport function createRevalidateHandler(\n options: RevalidateOptions,\n): (request: Request) => Promise<Response> {\n return async (request) => {\n let payload;\n try {\n payload = await parseWebhookRequest(request, options);\n } catch (err) {\n if (err instanceof PlenaryWebhookError) {\n return Response.json({ message: err.message }, { status: err.status });\n }\n throw err;\n }\n\n const plan = planRevalidation(payload, options);\n if (!plan.relevant) {\n return Response.json({ revalidated: false, ignored: payload.event });\n }\n if (plan.test) {\n return Response.json({ test: true, paths: plan.paths, tags: plan.tags });\n }\n for (const path of plan.paths) revalidatePath(path);\n for (const tag of plan.tags) revalidateTag(tag);\n return Response.json({ revalidated: true, paths: plan.paths, tags: plan.tags });\n };\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n isEntryWebhookPayload,\n WEBHOOK_TEST_EVENT,\n type EntryWebhookEvent,\n type EntryWebhookPayload,\n type ParseWebhookOptions,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\n/** Paths for one model: a fixed path, a list, or a function of the payload.\n * A function may return `null`/`undefined` to revalidate nothing. */\nexport type RevalidatePaths =\n | string\n | string[]\n | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);\n\nexport interface RevalidateOptions extends ParseWebhookOptions {\n /**\n * Extra or overriding paths per model key. A mapped model replaces the\n * default URL entirely — include `entryUrl(payload)` (exported) in the\n * returned list to keep it.\n */\n paths?: Record<string, RevalidatePaths>;\n /** Builds the default URL for unmapped models. Defaults to joining the\n * entry's folder `path` and `slug`; return `null` to skip. */\n entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;\n /** Entry events that trigger revalidation. Default: all five — revalidation\n * is idempotent and cheap, so over-firing beats a stale page. */\n events?: EntryWebhookEvent[];\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\n/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →\n * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home\n * page, `/`. Entries without a slug (singletons) get no default. */\nexport function entryUrl(payload: EntryWebhookPayload): string | null {\n const { slug, path } = payload.entry;\n if (!slug) return null;\n const dir = path && path !== \"/\" ? path : \"\";\n if (!dir && slug === \"home\") return \"/\";\n return `${dir}/${slug}`;\n}\n\nexport interface RevalidationPlan {\n /** Whether this payload is one the handler acts on. */\n relevant: boolean;\n /** True for the console's Test button — report, don't revalidate. */\n test: boolean;\n paths: string[];\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const none = { relevant: false, test: false, paths: [], tags: [] };\n if (!isEntryWebhookPayload(payload)) return none;\n\n const test = payload.event === WEBHOOK_TEST_EVENT;\n if (!test && options.events && !options.events.includes(payload.event as EntryWebhookEvent)) {\n return none;\n }\n\n const modelKey = payload.model.key;\n const mapped = modelKey === undefined ? undefined : options.paths?.[modelKey];\n let paths: string | string[] | null | undefined;\n if (mapped === undefined) {\n paths = (options.entryUrl ?? entryUrl)(payload);\n } else {\n paths = typeof mapped === \"function\" ? mapped(payload) : mapped;\n }\n const pathList = paths == null ? [] : typeof paths === \"string\" ? [paths] : paths;\n\n const tags: string[] = [];\n if (options.tags) {\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n\n return { relevant: true, test, paths: [...new Set(pathList)], tags };\n}\n","/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n"],"mappings":"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","names":["import_client"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/revalidate.ts","../src/resolve.ts","../src/tags.ts"],"sourcesContent":["/**\n * @plenary/next — Next.js glue for Plenary: webhook-driven cache revalidation\n * and cache-tagged content fetching. The Pages Router handler lives in\n * `@plenary/next/pages` so this entry stays free of `next` type imports.\n */\n\nexport { createRevalidateHandler } from \"./revalidate.js\";\nexport {\n entryUrl,\n planRevalidation,\n type RevalidateOptions,\n type RevalidatePaths,\n type RevalidationPlan,\n} from \"./resolve.js\";\nexport { createTaggedFetch, modelTag, modelEntriesTag, entryTag } from \"./tags.js\";\n","import { revalidatePath, revalidateTag } from \"next/cache\";\nimport { parseWebhookRequest, PlenaryWebhookError } from \"@plenary/client\";\nimport { planRevalidation, type RevalidateOptions } from \"./resolve.js\";\n\n/**\n * Builds an App Router route handler that turns Plenary entry webhooks into\n * `revalidatePath`/`revalidateTag` calls.\n *\n * ```ts\n * // app/api/revalidate/route.ts\n * export const POST = createRevalidateHandler({\n * secret: process.env.PLENARY_WEBHOOK_SECRET!,\n * });\n * ```\n *\n * With no `paths` map, each event revalidates the entry's own URL (folder\n * path + slug). Point the webhook at the route, subscribed to the entry\n * events, with the secret in an `x-plenary-secret` header.\n *\n * The console's Test button gets a dry run: a 200 reporting what would have\n * been revalidated, without touching the cache.\n */\nexport function createRevalidateHandler(\n options: RevalidateOptions,\n): (request: Request) => Promise<Response> {\n return async (request) => {\n let payload;\n try {\n payload = await parseWebhookRequest(request, options);\n } catch (err) {\n if (err instanceof PlenaryWebhookError) {\n return Response.json({ message: err.message }, { status: err.status });\n }\n throw err;\n }\n\n const plan = planRevalidation(payload, options);\n if (!plan.relevant) {\n return Response.json({ revalidated: false, ignored: payload.event });\n }\n if (plan.test) {\n return Response.json({ test: true, paths: plan.paths, tags: plan.tags });\n }\n for (const path of plan.paths) revalidatePath(path);\n for (const tag of plan.tags) revalidateTag(tag);\n return Response.json({ revalidated: true, paths: plan.paths, tags: plan.tags });\n };\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. The path\n * planning lives in `@plenary/client`; this adds the cache tags. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n planRevalidation as planPaths,\n isEntryWebhookPayload,\n type RevalidateOptions as PathOptions,\n type RevalidationPlan as PathPlan,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\nexport { entryUrl, type RevalidatePaths } from \"@plenary/client\";\n\nexport interface RevalidateOptions extends PathOptions {\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\nexport interface RevalidationPlan extends PathPlan {\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const plan = planPaths(payload, options);\n const tags: string[] = [];\n if (plan.relevant && options.tags && isEntryWebhookPayload(payload)) {\n const modelKey = payload.model.key;\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n return { ...plan, tags };\n}\n","/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n"],"mappings":"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","names":["import_client","import_client","planPaths"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { R as RevalidateOptions } from './resolve-
|
|
2
|
-
export { a as
|
|
3
|
-
|
|
1
|
+
import { R as RevalidateOptions } from './resolve-CvKtOFT_.cjs';
|
|
2
|
+
export { a as RevalidationPlan, p as planRevalidation } from './resolve-CvKtOFT_.cjs';
|
|
3
|
+
export { RevalidatePaths, entryUrl } from '@plenary/client';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Builds an App Router route handler that turns Plenary entry webhooks into
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { R as RevalidateOptions } from './resolve-
|
|
2
|
-
export { a as
|
|
3
|
-
|
|
1
|
+
import { R as RevalidateOptions } from './resolve-CvKtOFT_.js';
|
|
2
|
+
export { a as RevalidationPlan, p as planRevalidation } from './resolve-CvKtOFT_.js';
|
|
3
|
+
export { RevalidatePaths, entryUrl } from '@plenary/client';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Builds an App Router route handler that turns Plenary entry webhooks into
|
package/dist/index.js
CHANGED
package/dist/pages.cjs
CHANGED
|
@@ -23,7 +23,7 @@ __export(pages_exports, {
|
|
|
23
23
|
createPagesRevalidateHandler: () => createPagesRevalidateHandler
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(pages_exports);
|
|
26
|
-
var
|
|
26
|
+
var import_client3 = require("@plenary/client");
|
|
27
27
|
|
|
28
28
|
// src/resolve.ts
|
|
29
29
|
var import_client = require("@plenary/client");
|
|
@@ -40,31 +40,12 @@ function modelEntriesTag(modelKey) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// src/resolve.ts
|
|
43
|
-
|
|
44
|
-
const { slug, path } = payload.entry;
|
|
45
|
-
if (!slug) return null;
|
|
46
|
-
const dir = path && path !== "/" ? path : "";
|
|
47
|
-
if (!dir && slug === "home") return "/";
|
|
48
|
-
return `${dir}/${slug}`;
|
|
49
|
-
}
|
|
43
|
+
var import_client2 = require("@plenary/client");
|
|
50
44
|
function planRevalidation(payload, options) {
|
|
51
|
-
const
|
|
52
|
-
if (!(0, import_client.isEntryWebhookPayload)(payload)) return none;
|
|
53
|
-
const test = payload.event === import_client.WEBHOOK_TEST_EVENT;
|
|
54
|
-
if (!test && options.events && !options.events.includes(payload.event)) {
|
|
55
|
-
return none;
|
|
56
|
-
}
|
|
57
|
-
const modelKey = payload.model.key;
|
|
58
|
-
const mapped = modelKey === void 0 ? void 0 : options.paths?.[modelKey];
|
|
59
|
-
let paths;
|
|
60
|
-
if (mapped === void 0) {
|
|
61
|
-
paths = (options.entryUrl ?? entryUrl)(payload);
|
|
62
|
-
} else {
|
|
63
|
-
paths = typeof mapped === "function" ? mapped(payload) : mapped;
|
|
64
|
-
}
|
|
65
|
-
const pathList = paths == null ? [] : typeof paths === "string" ? [paths] : paths;
|
|
45
|
+
const plan = (0, import_client.planRevalidation)(payload, options);
|
|
66
46
|
const tags = [];
|
|
67
|
-
if (options.tags) {
|
|
47
|
+
if (plan.relevant && options.tags && (0, import_client.isEntryWebhookPayload)(payload)) {
|
|
48
|
+
const modelKey = payload.model.key;
|
|
68
49
|
if (modelKey) {
|
|
69
50
|
tags.push(modelTag(modelKey));
|
|
70
51
|
if (payload.changed.some((p) => p === "slug" || p === "path")) tags.push(modelEntriesTag(modelKey));
|
|
@@ -72,7 +53,7 @@ function planRevalidation(payload, options) {
|
|
|
72
53
|
tags.push(entryTag(payload.entry.id));
|
|
73
54
|
if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));
|
|
74
55
|
}
|
|
75
|
-
return {
|
|
56
|
+
return { ...plan, tags };
|
|
76
57
|
}
|
|
77
58
|
|
|
78
59
|
// src/pages.ts
|
|
@@ -91,15 +72,15 @@ function createPagesRevalidateHandler(options) {
|
|
|
91
72
|
const fromHeader = req.headers[header];
|
|
92
73
|
const fromQuery = req.query[query];
|
|
93
74
|
const provided = (Array.isArray(fromHeader) ? fromHeader[0] : fromHeader) ?? (Array.isArray(fromQuery) ? fromQuery[0] : fromQuery);
|
|
94
|
-
if (!(0,
|
|
75
|
+
if (!(0, import_client3.verifyWebhookSecret)(provided, options.secret)) {
|
|
95
76
|
res.status(401).json({ message: "Invalid webhook secret" });
|
|
96
77
|
return;
|
|
97
78
|
}
|
|
98
79
|
let payload;
|
|
99
80
|
try {
|
|
100
|
-
payload = (0,
|
|
81
|
+
payload = (0, import_client3.parseWebhookPayload)(req.body);
|
|
101
82
|
} catch (err) {
|
|
102
|
-
if (err instanceof
|
|
83
|
+
if (err instanceof import_client3.PlenaryWebhookError) {
|
|
103
84
|
res.status(err.status).json({ message: err.message });
|
|
104
85
|
return;
|
|
105
86
|
}
|
package/dist/pages.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pages.ts","../src/resolve.ts","../src/tags.ts"],"sourcesContent":["import type { NextApiRequest, NextApiResponse } from \"next\";\nimport {\n parseWebhookPayload,\n verifyWebhookSecret,\n PlenaryWebhookError,\n} from \"@plenary/client\";\nimport { planRevalidation, type RevalidateOptions } from \"./resolve.js\";\n\n/**\n * The Pages Router counterpart of `createRevalidateHandler`, built on\n * `res.revalidate`.\n *\n * ```ts\n * // pages/api/revalidate.ts\n * export default createPagesRevalidateHandler({\n * secret: process.env.PLENARY_WEBHOOK_SECRET!,\n * });\n * ```\n *\n * Cache tags are an App Router feature; the `tags` option is ignored here.\n */\nexport function createPagesRevalidateHandler(\n options: RevalidateOptions,\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n\n return async (req, res) => {\n if (req.method !== \"POST\") {\n res.status(405).json({ message: \"Method not allowed\" });\n return;\n }\n if (!options.secret) {\n res.status(500).json({ message: \"Webhook secret is not configured\" });\n return;\n }\n const fromHeader = req.headers[header];\n const fromQuery = req.query[query];\n const provided =\n (Array.isArray(fromHeader) ? fromHeader[0] : fromHeader) ??\n (Array.isArray(fromQuery) ? fromQuery[0] : fromQuery);\n if (!verifyWebhookSecret(provided, options.secret)) {\n res.status(401).json({ message: \"Invalid webhook secret\" });\n return;\n }\n\n let payload;\n try {\n payload = parseWebhookPayload(req.body);\n } catch (err) {\n if (err instanceof PlenaryWebhookError) {\n res.status(err.status).json({ message: err.message });\n return;\n }\n throw err;\n }\n\n const plan = planRevalidation(payload, options);\n if (!plan.relevant) {\n res.json({ revalidated: false, ignored: payload.event });\n return;\n }\n if (plan.test) {\n res.json({ test: true, paths: plan.paths });\n return;\n }\n await Promise.all(plan.paths.map((p) => res.revalidate(p)));\n res.json({ revalidated: true, paths: plan.paths });\n };\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n isEntryWebhookPayload,\n WEBHOOK_TEST_EVENT,\n type EntryWebhookEvent,\n type EntryWebhookPayload,\n type ParseWebhookOptions,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\n/** Paths for one model: a fixed path, a list, or a function of the payload.\n * A function may return `null`/`undefined` to revalidate nothing. */\nexport type RevalidatePaths =\n | string\n | string[]\n | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);\n\nexport interface RevalidateOptions extends ParseWebhookOptions {\n /**\n * Extra or overriding paths per model key. A mapped model replaces the\n * default URL entirely — include `entryUrl(payload)` (exported) in the\n * returned list to keep it.\n */\n paths?: Record<string, RevalidatePaths>;\n /** Builds the default URL for unmapped models. Defaults to joining the\n * entry's folder `path` and `slug`; return `null` to skip. */\n entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;\n /** Entry events that trigger revalidation. Default: all five — revalidation\n * is idempotent and cheap, so over-firing beats a stale page. */\n events?: EntryWebhookEvent[];\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\n/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →\n * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home\n * page, `/`. Entries without a slug (singletons) get no default. */\nexport function entryUrl(payload: EntryWebhookPayload): string | null {\n const { slug, path } = payload.entry;\n if (!slug) return null;\n const dir = path && path !== \"/\" ? path : \"\";\n if (!dir && slug === \"home\") return \"/\";\n return `${dir}/${slug}`;\n}\n\nexport interface RevalidationPlan {\n /** Whether this payload is one the handler acts on. */\n relevant: boolean;\n /** True for the console's Test button — report, don't revalidate. */\n test: boolean;\n paths: string[];\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const none = { relevant: false, test: false, paths: [], tags: [] };\n if (!isEntryWebhookPayload(payload)) return none;\n\n const test = payload.event === WEBHOOK_TEST_EVENT;\n if (!test && options.events && !options.events.includes(payload.event as EntryWebhookEvent)) {\n return none;\n }\n\n const modelKey = payload.model.key;\n const mapped = modelKey === undefined ? undefined : options.paths?.[modelKey];\n let paths: string | string[] | null | undefined;\n if (mapped === undefined) {\n paths = (options.entryUrl ?? entryUrl)(payload);\n } else {\n paths = typeof mapped === \"function\" ? mapped(payload) : mapped;\n }\n const pathList = paths == null ? [] : typeof paths === \"string\" ? [paths] : paths;\n\n const tags: string[] = [];\n if (options.tags) {\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n\n return { relevant: true, test, paths: [...new Set(pathList)], tags };\n}\n","/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n"],"mappings":"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","names":["import_client"]}
|
|
1
|
+
{"version":3,"sources":["../src/pages.ts","../src/resolve.ts","../src/tags.ts"],"sourcesContent":["import type { NextApiRequest, NextApiResponse } from \"next\";\nimport {\n parseWebhookPayload,\n verifyWebhookSecret,\n PlenaryWebhookError,\n} from \"@plenary/client\";\nimport { planRevalidation, type RevalidateOptions } from \"./resolve.js\";\n\n/**\n * The Pages Router counterpart of `createRevalidateHandler`, built on\n * `res.revalidate`.\n *\n * ```ts\n * // pages/api/revalidate.ts\n * export default createPagesRevalidateHandler({\n * secret: process.env.PLENARY_WEBHOOK_SECRET!,\n * });\n * ```\n *\n * Cache tags are an App Router feature; the `tags` option is ignored here.\n */\nexport function createPagesRevalidateHandler(\n options: RevalidateOptions,\n): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {\n const header = options.secretHeader ?? \"x-plenary-secret\";\n const query = options.secretQuery ?? \"secret\";\n\n return async (req, res) => {\n if (req.method !== \"POST\") {\n res.status(405).json({ message: \"Method not allowed\" });\n return;\n }\n if (!options.secret) {\n res.status(500).json({ message: \"Webhook secret is not configured\" });\n return;\n }\n const fromHeader = req.headers[header];\n const fromQuery = req.query[query];\n const provided =\n (Array.isArray(fromHeader) ? fromHeader[0] : fromHeader) ??\n (Array.isArray(fromQuery) ? fromQuery[0] : fromQuery);\n if (!verifyWebhookSecret(provided, options.secret)) {\n res.status(401).json({ message: \"Invalid webhook secret\" });\n return;\n }\n\n let payload;\n try {\n payload = parseWebhookPayload(req.body);\n } catch (err) {\n if (err instanceof PlenaryWebhookError) {\n res.status(err.status).json({ message: err.message });\n return;\n }\n throw err;\n }\n\n const plan = planRevalidation(payload, options);\n if (!plan.relevant) {\n res.json({ revalidated: false, ignored: payload.event });\n return;\n }\n if (plan.test) {\n res.json({ test: true, paths: plan.paths });\n return;\n }\n await Promise.all(plan.paths.map((p) => res.revalidate(p)));\n res.json({ revalidated: true, paths: plan.paths });\n };\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. The path\n * planning lives in `@plenary/client`; this adds the cache tags. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n planRevalidation as planPaths,\n isEntryWebhookPayload,\n type RevalidateOptions as PathOptions,\n type RevalidationPlan as PathPlan,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\nexport { entryUrl, type RevalidatePaths } from \"@plenary/client\";\n\nexport interface RevalidateOptions extends PathOptions {\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\nexport interface RevalidationPlan extends PathPlan {\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const plan = planPaths(payload, options);\n const tags: string[] = [];\n if (plan.relevant && options.tags && isEntryWebhookPayload(payload)) {\n const modelKey = payload.model.key;\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n return { ...plan, tags };\n}\n","/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,IAAAA,iBAIO;;;ACEP,oBAMO;;;ACHA,SAAS,SAAS,UAA0B;AACjD,SAAO,iBAAiB,QAAQ;AAClC;AAIO,SAAS,SAAS,UAA0B;AACjD,SAAO,iBAAiB,QAAQ;AAClC;AAKO,SAAS,gBAAgB,UAA0B;AACxD,SAAO,yBAAyB,QAAQ;AAC1C;;;ADTA,IAAAC,iBAA+C;AAaxC,SAAS,iBACd,SACA,SACkB;AAClB,QAAM,WAAO,cAAAC,kBAAU,SAAS,OAAO;AACvC,QAAM,OAAiB,CAAC;AACxB,MAAI,KAAK,YAAY,QAAQ,YAAQ,qCAAsB,OAAO,GAAG;AACnE,UAAM,WAAW,QAAQ,MAAM;AAC/B,QAAI,UAAU;AACZ,WAAK,KAAK,SAAS,QAAQ,CAAC;AAC5B,UAAI,QAAQ,QAAQ,KAAK,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,EAAG,MAAK,KAAK,gBAAgB,QAAQ,CAAC;AAAA,IACpG;AACA,SAAK,KAAK,SAAS,QAAQ,MAAM,EAAE,CAAC;AACpC,QAAI,QAAQ,MAAM,KAAM,MAAK,KAAK,SAAS,QAAQ,MAAM,IAAI,CAAC;AAAA,EAChE;AACA,SAAO,EAAE,GAAG,MAAM,KAAK;AACzB;;;ADxBO,SAAS,6BACd,SAC8D;AAC9D,QAAM,SAAS,QAAQ,gBAAgB;AACvC,QAAM,QAAQ,QAAQ,eAAe;AAErC,SAAO,OAAO,KAAK,QAAQ;AACzB,QAAI,IAAI,WAAW,QAAQ;AACzB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,qBAAqB,CAAC;AACtD;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,QAAQ;AACnB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,mCAAmC,CAAC;AACpE;AAAA,IACF;AACA,UAAM,aAAa,IAAI,QAAQ,MAAM;AACrC,UAAM,YAAY,IAAI,MAAM,KAAK;AACjC,UAAM,YACH,MAAM,QAAQ,UAAU,IAAI,WAAW,CAAC,IAAI,gBAC5C,MAAM,QAAQ,SAAS,IAAI,UAAU,CAAC,IAAI;AAC7C,QAAI,KAAC,oCAAoB,UAAU,QAAQ,MAAM,GAAG;AAClD,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,yBAAyB,CAAC;AAC1D;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,oBAAU,oCAAoB,IAAI,IAAI;AAAA,IACxC,SAAS,KAAK;AACZ,UAAI,eAAe,oCAAqB;AACtC,YAAI,OAAO,IAAI,MAAM,EAAE,KAAK,EAAE,SAAS,IAAI,QAAQ,CAAC;AACpD;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAEA,UAAM,OAAO,iBAAiB,SAAS,OAAO;AAC9C,QAAI,CAAC,KAAK,UAAU;AAClB,UAAI,KAAK,EAAE,aAAa,OAAO,SAAS,QAAQ,MAAM,CAAC;AACvD;AAAA,IACF;AACA,QAAI,KAAK,MAAM;AACb,UAAI,KAAK,EAAE,MAAM,MAAM,OAAO,KAAK,MAAM,CAAC;AAC1C;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC;AAC1D,QAAI,KAAK,EAAE,aAAa,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,EACnD;AACF;","names":["import_client","import_client","planPaths"]}
|
package/dist/pages.d.cts
CHANGED
package/dist/pages.d.ts
CHANGED
package/dist/pages.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RevalidateOptions as RevalidateOptions$1, RevalidationPlan as RevalidationPlan$1, WebhookPayload } from '@plenary/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The revalidation decision, shared by the App Router and Pages Router
|
|
5
|
+
* handlers: which paths (and tags) one webhook payload maps to. The path
|
|
6
|
+
* planning lives in `@plenary/client`; this adds the cache tags. Pure — the
|
|
7
|
+
* handlers own the framework calls.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface RevalidateOptions extends RevalidateOptions$1 {
|
|
11
|
+
/** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used
|
|
12
|
+
* by `createTaggedFetch`. App Router only. */
|
|
13
|
+
tags?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface RevalidationPlan extends RevalidationPlan$1 {
|
|
16
|
+
tags: string[];
|
|
17
|
+
}
|
|
18
|
+
/** Decides what one payload revalidates. */
|
|
19
|
+
declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
|
|
20
|
+
|
|
21
|
+
export { type RevalidateOptions as R, type RevalidationPlan as a, planRevalidation as p };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RevalidateOptions as RevalidateOptions$1, RevalidationPlan as RevalidationPlan$1, WebhookPayload } from '@plenary/client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The revalidation decision, shared by the App Router and Pages Router
|
|
5
|
+
* handlers: which paths (and tags) one webhook payload maps to. The path
|
|
6
|
+
* planning lives in `@plenary/client`; this adds the cache tags. Pure — the
|
|
7
|
+
* handlers own the framework calls.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface RevalidateOptions extends RevalidateOptions$1 {
|
|
11
|
+
/** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used
|
|
12
|
+
* by `createTaggedFetch`. App Router only. */
|
|
13
|
+
tags?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface RevalidationPlan extends RevalidationPlan$1 {
|
|
16
|
+
tags: string[];
|
|
17
|
+
}
|
|
18
|
+
/** Decides what one payload revalidates. */
|
|
19
|
+
declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
|
|
20
|
+
|
|
21
|
+
export { type RevalidateOptions as R, type RevalidationPlan as a, planRevalidation as p };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plenary/next",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Plenary Next.js integration: webhook revalidation handlers and cache-tagged fetch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@plenary/client": "1.0
|
|
26
|
+
"@plenary/client": "1.1.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"next": "^14 || ^15 || ^16"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tags.ts","../src/resolve.ts"],"sourcesContent":["/**\n * Cache-tag conventions shared by the tagged fetch and the revalidation\n * handler. A page that fetches Plenary content through\n * {@link createTaggedFetch} is tagged with the model and entry it read, so the\n * webhook handler can revalidate every page that used a piece of content\n * without a path map.\n */\n\n/** Tag carried by list reads of a model's content, which any entry event on\n * the model can change. */\nexport function modelTag(modelKey: string): string {\n return `plenary:model:${modelKey}`;\n}\n\n/** Tag for one entry. The content API addresses entries by slug or UUID, and a\n * webhook payload carries both, so both spellings are revalidated. */\nexport function entryTag(slugOrId: string): string {\n return `plenary:entry:${slugOrId}`;\n}\n\n/** Tag carried by every single-entry read of a model. A webhook revalidates it\n * only when an entry's slug or folder changes: the page at the old URL was\n * cached under the old slug, which the payload no longer names. */\nexport function modelEntriesTag(modelKey: string): string {\n return `plenary:model-entries:${modelKey}`;\n}\n\n/** Next.js extends `RequestInit` with `next`; declared locally so this package\n * compiles against the standard DOM lib. */\ninterface NextRequestInit extends RequestInit {\n next?: { tags?: string[]; revalidate?: number | false };\n}\n\nconst CONTENT_URL = /\\/api\\/v1\\/content\\/([^/?#]+)(?:\\/([^/?#]+))?/;\n\n/** The slug a list read pins with `where: { slug }`, if it pins exactly one. */\nfunction pinnedSlug(url: string): string | undefined {\n const query = url.split(\"?\")[1]?.split(\"#\")[0];\n if (!query) return undefined;\n const params = new URLSearchParams(query);\n const values = [...params.getAll(\"where:slug\"), ...params.getAll(\"where:slug.$eq\")];\n return values.length === 1 ? values[0] : undefined;\n}\n\n/**\n * Wraps a fetch implementation so Plenary content reads carry cache tags.\n * Pass it to `createClient`:\n *\n * ```ts\n * const plenary = createClient({ ...config, fetch: createTaggedFetch() });\n * ```\n *\n * A read of one entry, `/api/v1/content/{model}/{slugOrId}` or a list read\n * pinned with `where: { slug }`, is tagged with that entry, so editing another\n * entry leaves its page cached. Other GET requests to `/api/v1/content/{model}`\n * are tagged with the model. Anything else passes through untouched.\n */\nexport function createTaggedFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return ((input: RequestInfo | URL, init?: RequestInit) => {\n const method = init?.method ?? \"GET\";\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const match = method === \"GET\" ? CONTENT_URL.exec(url) : null;\n if (!match?.[1]) return baseFetch(input, init);\n\n const model = decodeURIComponent(match[1]);\n const entry = match[2] ? decodeURIComponent(match[2]) : pinnedSlug(url);\n const tags = entry ? [modelEntriesTag(model), entryTag(entry)] : [modelTag(model)];\n const next = (init as NextRequestInit | undefined)?.next;\n const merged: NextRequestInit = {\n ...init,\n next: { ...next, tags: [...(next?.tags ?? []), ...tags] },\n };\n return baseFetch(input, merged);\n }) as typeof fetch;\n}\n","/**\n * The revalidation decision, shared by the App Router and Pages Router\n * handlers: which paths (and tags) one webhook payload maps to. Pure — the\n * handlers own the framework calls.\n */\n\nimport {\n isEntryWebhookPayload,\n WEBHOOK_TEST_EVENT,\n type EntryWebhookEvent,\n type EntryWebhookPayload,\n type ParseWebhookOptions,\n type WebhookPayload,\n} from \"@plenary/client\";\nimport { entryTag, modelEntriesTag, modelTag } from \"./tags.js\";\n\n/** Paths for one model: a fixed path, a list, or a function of the payload.\n * A function may return `null`/`undefined` to revalidate nothing. */\nexport type RevalidatePaths =\n | string\n | string[]\n | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);\n\nexport interface RevalidateOptions extends ParseWebhookOptions {\n /**\n * Extra or overriding paths per model key. A mapped model replaces the\n * default URL entirely — include `entryUrl(payload)` (exported) in the\n * returned list to keep it.\n */\n paths?: Record<string, RevalidatePaths>;\n /** Builds the default URL for unmapped models. Defaults to joining the\n * entry's folder `path` and `slug`; return `null` to skip. */\n entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;\n /** Entry events that trigger revalidation. Default: all five — revalidation\n * is idempotent and cheap, so over-firing beats a stale page. */\n events?: EntryWebhookEvent[];\n /** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used\n * by `createTaggedFetch`. App Router only. */\n tags?: boolean;\n}\n\n/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →\n * `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home\n * page, `/`. Entries without a slug (singletons) get no default. */\nexport function entryUrl(payload: EntryWebhookPayload): string | null {\n const { slug, path } = payload.entry;\n if (!slug) return null;\n const dir = path && path !== \"/\" ? path : \"\";\n if (!dir && slug === \"home\") return \"/\";\n return `${dir}/${slug}`;\n}\n\nexport interface RevalidationPlan {\n /** Whether this payload is one the handler acts on. */\n relevant: boolean;\n /** True for the console's Test button — report, don't revalidate. */\n test: boolean;\n paths: string[];\n tags: string[];\n}\n\n/** Decides what one payload revalidates. */\nexport function planRevalidation(\n payload: WebhookPayload,\n options: RevalidateOptions,\n): RevalidationPlan {\n const none = { relevant: false, test: false, paths: [], tags: [] };\n if (!isEntryWebhookPayload(payload)) return none;\n\n const test = payload.event === WEBHOOK_TEST_EVENT;\n if (!test && options.events && !options.events.includes(payload.event as EntryWebhookEvent)) {\n return none;\n }\n\n const modelKey = payload.model.key;\n const mapped = modelKey === undefined ? undefined : options.paths?.[modelKey];\n let paths: string | string[] | null | undefined;\n if (mapped === undefined) {\n paths = (options.entryUrl ?? entryUrl)(payload);\n } else {\n paths = typeof mapped === \"function\" ? mapped(payload) : mapped;\n }\n const pathList = paths == null ? [] : typeof paths === \"string\" ? [paths] : paths;\n\n const tags: string[] = [];\n if (options.tags) {\n if (modelKey) {\n tags.push(modelTag(modelKey));\n if (payload.changed.some((p) => p === \"slug\" || p === \"path\")) tags.push(modelEntriesTag(modelKey));\n }\n tags.push(entryTag(payload.entry.id));\n if (payload.entry.slug) tags.push(entryTag(payload.entry.slug));\n }\n\n return { relevant: true, test, paths: [...new Set(pathList)], tags };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ParseWebhookOptions, EntryWebhookPayload, EntryWebhookEvent, WebhookPayload } from '@plenary/client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The revalidation decision, shared by the App Router and Pages Router
|
|
5
|
-
* handlers: which paths (and tags) one webhook payload maps to. Pure — the
|
|
6
|
-
* handlers own the framework calls.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Paths for one model: a fixed path, a list, or a function of the payload.
|
|
10
|
-
* A function may return `null`/`undefined` to revalidate nothing. */
|
|
11
|
-
type RevalidatePaths = string | string[] | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);
|
|
12
|
-
interface RevalidateOptions extends ParseWebhookOptions {
|
|
13
|
-
/**
|
|
14
|
-
* Extra or overriding paths per model key. A mapped model replaces the
|
|
15
|
-
* default URL entirely — include `entryUrl(payload)` (exported) in the
|
|
16
|
-
* returned list to keep it.
|
|
17
|
-
*/
|
|
18
|
-
paths?: Record<string, RevalidatePaths>;
|
|
19
|
-
/** Builds the default URL for unmapped models. Defaults to joining the
|
|
20
|
-
* entry's folder `path` and `slug`; return `null` to skip. */
|
|
21
|
-
entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;
|
|
22
|
-
/** Entry events that trigger revalidation. Default: all five — revalidation
|
|
23
|
-
* is idempotent and cheap, so over-firing beats a stale page. */
|
|
24
|
-
events?: EntryWebhookEvent[];
|
|
25
|
-
/** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used
|
|
26
|
-
* by `createTaggedFetch`. App Router only. */
|
|
27
|
-
tags?: boolean;
|
|
28
|
-
}
|
|
29
|
-
/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →
|
|
30
|
-
* `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home
|
|
31
|
-
* page, `/`. Entries without a slug (singletons) get no default. */
|
|
32
|
-
declare function entryUrl(payload: EntryWebhookPayload): string | null;
|
|
33
|
-
interface RevalidationPlan {
|
|
34
|
-
/** Whether this payload is one the handler acts on. */
|
|
35
|
-
relevant: boolean;
|
|
36
|
-
/** True for the console's Test button — report, don't revalidate. */
|
|
37
|
-
test: boolean;
|
|
38
|
-
paths: string[];
|
|
39
|
-
tags: string[];
|
|
40
|
-
}
|
|
41
|
-
/** Decides what one payload revalidates. */
|
|
42
|
-
declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
|
|
43
|
-
|
|
44
|
-
export { type RevalidateOptions as R, type RevalidatePaths as a, type RevalidationPlan as b, entryUrl as e, planRevalidation as p };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ParseWebhookOptions, EntryWebhookPayload, EntryWebhookEvent, WebhookPayload } from '@plenary/client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The revalidation decision, shared by the App Router and Pages Router
|
|
5
|
-
* handlers: which paths (and tags) one webhook payload maps to. Pure — the
|
|
6
|
-
* handlers own the framework calls.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Paths for one model: a fixed path, a list, or a function of the payload.
|
|
10
|
-
* A function may return `null`/`undefined` to revalidate nothing. */
|
|
11
|
-
type RevalidatePaths = string | string[] | ((payload: EntryWebhookPayload) => string | string[] | null | undefined);
|
|
12
|
-
interface RevalidateOptions extends ParseWebhookOptions {
|
|
13
|
-
/**
|
|
14
|
-
* Extra or overriding paths per model key. A mapped model replaces the
|
|
15
|
-
* default URL entirely — include `entryUrl(payload)` (exported) in the
|
|
16
|
-
* returned list to keep it.
|
|
17
|
-
*/
|
|
18
|
-
paths?: Record<string, RevalidatePaths>;
|
|
19
|
-
/** Builds the default URL for unmapped models. Defaults to joining the
|
|
20
|
-
* entry's folder `path` and `slug`; return `null` to skip. */
|
|
21
|
-
entryUrl?: (payload: EntryWebhookPayload) => string | null | undefined;
|
|
22
|
-
/** Entry events that trigger revalidation. Default: all five — revalidation
|
|
23
|
-
* is idempotent and cheap, so over-firing beats a stale page. */
|
|
24
|
-
events?: EntryWebhookEvent[];
|
|
25
|
-
/** Also revalidate the `plenary:model:*` / `plenary:entry:*` cache tags used
|
|
26
|
-
* by `createTaggedFetch`. App Router only. */
|
|
27
|
-
tags?: boolean;
|
|
28
|
-
}
|
|
29
|
-
/** Default URL for an entry: folder path + slug (`/blog` + `spring-sale` →
|
|
30
|
-
* `/blog/spring-sale`). A root-folder entry slugged `home` is the site's home
|
|
31
|
-
* page, `/`. Entries without a slug (singletons) get no default. */
|
|
32
|
-
declare function entryUrl(payload: EntryWebhookPayload): string | null;
|
|
33
|
-
interface RevalidationPlan {
|
|
34
|
-
/** Whether this payload is one the handler acts on. */
|
|
35
|
-
relevant: boolean;
|
|
36
|
-
/** True for the console's Test button — report, don't revalidate. */
|
|
37
|
-
test: boolean;
|
|
38
|
-
paths: string[];
|
|
39
|
-
tags: string[];
|
|
40
|
-
}
|
|
41
|
-
/** Decides what one payload revalidates. */
|
|
42
|
-
declare function planRevalidation(payload: WebhookPayload, options: RevalidateOptions): RevalidationPlan;
|
|
43
|
-
|
|
44
|
-
export { type RevalidateOptions as R, type RevalidatePaths as a, type RevalidationPlan as b, entryUrl as e, planRevalidation as p };
|