@plenary/nuxt 0.0.0-stage → 1.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick DeLoughry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,106 @@
1
- # Temporary Holding Version
1
+ # @plenary/nuxt
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Nuxt glue for Plenary: turn entry webhooks into Nitro cache purges, so pages
4
+ cached with `swr`/`cache` route rules or `defineCachedEventHandler` are
5
+ regenerated the moment content changes.
6
+
7
+ ## Setup
8
+
9
+ ```ts
10
+ // nuxt.config.ts
11
+ export default defineNuxtConfig({
12
+ modules: ["@plenary/nuxt"],
13
+ runtimeConfig: {
14
+ plenaryWebhookSecret: "", // NUXT_PLENARY_WEBHOOK_SECRET
15
+ },
16
+ routeRules: {
17
+ "/news/**": { swr: 3600 },
18
+ },
19
+ });
20
+ ```
21
+
22
+ ```ts
23
+ // server/api/revalidate.post.ts
24
+ import { createRevalidateHandler } from "@plenary/nuxt/server";
25
+
26
+ export default createRevalidateHandler({
27
+ secret: useRuntimeConfig().plenaryWebhookSecret,
28
+ });
29
+ ```
30
+
31
+ The module has no options. It registers the package's server runtime with
32
+ Nitro's bundler so the handler can reach Nitro's cache storage. Without it the
33
+ route answers 500 and the server log says `Package import specifier
34
+ "#nitro-internal-virtual/storage" is not defined`: Nitro's storage only exists
35
+ inside the Nitro bundle.
36
+
37
+ In the Plenary console, add a webhook on each model pointing at
38
+ `/api/revalidate`, subscribed to the entry events, with the secret in an
39
+ `x-plenary-secret` custom header (marked secret). That's the whole setup: every
40
+ event purges the cached responses of the entry's own URL, built from its folder
41
+ path and slug (`/news` + `hello-world` → `/news/hello-world`), and the next
42
+ request renders it fresh.
43
+
44
+ When a model's URLs don't mirror folder + slug, or other pages show its
45
+ content, map it:
46
+
47
+ ```ts
48
+ export default createRevalidateHandler({
49
+ secret: useRuntimeConfig().plenaryWebhookSecret,
50
+ paths: {
51
+ "blog-post": ({ entry }) => ["/blog", `/blog/${entry.slug}`],
52
+ "global-settings": "/", // fixed path or list also fine
53
+ },
54
+ });
55
+ ```
56
+
57
+ A mapped model replaces the default URL; compose with `entryUrl` from
58
+ `@plenary/client` to keep it. Unmapped slugless entries (singletons) revalidate nothing. The
59
+ `events` option narrows which entry events fire (default: all five —
60
+ revalidation is idempotent, so over-firing beats a stale page). The console's
61
+ **Test** button gets a dry run: a 200 reporting the paths it would have
62
+ purged.
63
+
64
+ The handler answers 401/405/400 for requests that will never succeed, so bad
65
+ deliveries fail fast in the delivery log instead of retrying for seven hours.
66
+ A successful delivery reports `purged`, the number of cache entries removed:
67
+ a persistent `0` for a page you know is cached usually means the cache lives
68
+ somewhere the handler can't see (below).
69
+
70
+ ## What gets purged
71
+
72
+ Nitro has no cache tags. A cached response is stored under a key derived from
73
+ its request URL (`cache:nitro:routes:_:newshelloworld.<hash>.json` for
74
+ `/news/hello-world`), so the handler lists the `nitro:routes` and
75
+ `nitro:handlers` groups of `useStorage("cache")` and removes every entry filed
76
+ under one of the planned paths — all of a page's variants, including query
77
+ strings and `varies` headers. Nitro keeps only the first 16 word characters of
78
+ the path in the key, so a purge of a long path also removes pages sharing that
79
+ prefix (`/blog/2026/11/03/some-title` and `/blog/2026/11/03/some-other`). That
80
+ is harmless: the next request regenerates them.
81
+
82
+ Things the handler cannot reach:
83
+
84
+ - **Per-instance memory.** Nitro's default `cache` storage in production is
85
+ in-memory, so on a platform that runs several instances the purge only hits
86
+ the one that received the webhook. Mount a shared driver
87
+ (`nitro.storage.cache` → Redis, Cloudflare KV, Vercel KV, …) so every
88
+ instance sees it.
89
+ - **Platform ISR.** The `isr` route rule on Vercel and Netlify caches at the
90
+ platform's CDN, outside Nitro's storage. Use `swr` with a shared cache
91
+ driver instead, or purge through the platform's API.
92
+ - **`defineCachedFunction`.** Its keys are whatever your `getKey` returns, so
93
+ the handler leaves the `nitro:functions` group alone. Give such functions a
94
+ `maxAge` you can live with, or purge them yourself from a custom route built
95
+ on `purgeCachedPaths` (exported here) and `planRevalidation` (from
96
+ `@plenary/client`).
97
+ - **Custom `getKey`** on a cached handler, for the same reason.
98
+
99
+ ## Authentication
100
+
101
+ Plenary does not sign webhook requests yet, so authentication is the shared
102
+ secret above (constant-time compared; also accepted as a `?secret=` query
103
+ parameter, names configurable via `secretHeader`/`secretQuery`). Payload
104
+ parsing, verification, and the path planning (`entryUrl`, `planRevalidation`)
105
+ live in `@plenary/client` for use outside Nuxt; HMAC verification will land
106
+ there when signing ships.
@@ -0,0 +1,19 @@
1
+ import { NuxtModule } from '@nuxt/schema';
2
+
3
+ /**
4
+ * The `@plenary/nuxt` module. It has no options: its one job is to hand the
5
+ * package's server runtime to Nitro's bundler, so a route built with
6
+ * `createRevalidateHandler` (from `@plenary/nuxt/server`) can reach Nitro's
7
+ * cache storage. `nitropack/runtime` only exists inside the Nitro bundle —
8
+ * loaded from node_modules at runtime it fails with `Package import specifier
9
+ * "#nitro-internal-virtual/storage" is not defined` — so the runtime
10
+ * directory is transpiled (inlined) rather than left external.
11
+ *
12
+ * ```ts
13
+ * // nuxt.config.ts
14
+ * export default defineNuxtConfig({ modules: ["@plenary/nuxt"] });
15
+ * ```
16
+ */
17
+ declare const module$1: NuxtModule;
18
+
19
+ export { module$1 as default };
package/dist/module.js ADDED
@@ -0,0 +1,17 @@
1
+ import { createResolver, defineNuxtModule } from "@nuxt/kit";
2
+ const module = defineNuxtModule({
3
+ meta: {
4
+ name: "@plenary/nuxt",
5
+ configKey: "plenary",
6
+ compatibility: { nuxt: ">=4.0.0" }
7
+ },
8
+ setup(_options, nuxt) {
9
+ const { resolve } = createResolver(import.meta.url);
10
+ nuxt.options.build.transpile.push(resolve("./runtime"));
11
+ }
12
+ });
13
+ var module_default = module;
14
+ export {
15
+ module_default as default
16
+ };
17
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/module.ts"],"sourcesContent":["import { createResolver, defineNuxtModule } from \"@nuxt/kit\";\nimport type { NuxtModule } from \"@nuxt/schema\";\n\n/**\n * The `@plenary/nuxt` module. It has no options: its one job is to hand the\n * package's server runtime to Nitro's bundler, so a route built with\n * `createRevalidateHandler` (from `@plenary/nuxt/server`) can reach Nitro's\n * cache storage. `nitropack/runtime` only exists inside the Nitro bundle —\n * loaded from node_modules at runtime it fails with `Package import specifier\n * \"#nitro-internal-virtual/storage\" is not defined` — so the runtime\n * directory is transpiled (inlined) rather than left external.\n *\n * ```ts\n * // nuxt.config.ts\n * export default defineNuxtConfig({ modules: [\"@plenary/nuxt\"] });\n * ```\n */\nconst module: NuxtModule = defineNuxtModule({\n meta: {\n name: \"@plenary/nuxt\",\n configKey: \"plenary\",\n compatibility: { nuxt: \">=4.0.0\" },\n },\n setup(_options, nuxt) {\n const { resolve } = createResolver(import.meta.url);\n nuxt.options.build.transpile.push(resolve(\"./runtime\"));\n },\n});\n\nexport default module;\n"],"mappings":"AAAA,SAAS,gBAAgB,wBAAwB;AAiBjD,MAAM,SAAqB,iBAAiB;AAAA,EAC1C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,eAAe,EAAE,MAAM,UAAU;AAAA,EACnC;AAAA,EACA,MAAM,UAAU,MAAM;AACpB,UAAM,EAAE,QAAQ,IAAI,eAAe,YAAY,GAAG;AAClD,SAAK,QAAQ,MAAM,UAAU,KAAK,QAAQ,WAAW,CAAC;AAAA,EACxD;AACF,CAAC;AAED,IAAO,iBAAQ;","names":[]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Purging Nitro's response cache by path. Nitro has no cache tags: a cached
3
+ * response lives under a storage key derived from the request URL, so
4
+ * revalidating a path means finding and removing the keys that belong to it.
5
+ *
6
+ * `defineCachedEventHandler` (and the `cache`/`swr`/`isr` route rules, which
7
+ * wrap handlers in it) store entries as
8
+ *
9
+ * cache:nitro:routes:<name>:<pathname>.<hash>[:<header>.<hash>…].json
10
+ * cache:nitro:handlers:<name>:<pathname>.<hash>[:<header>.<hash>…].json
11
+ *
12
+ * where `<pathname>` is the URL path with every non-word character stripped
13
+ * and cut to 16 characters (`/news/hello-world` → `newshelloworld`, `/` →
14
+ * `index`) and `<hash>` digests the full URL including its query string. The
15
+ * hash is not recomputed here; entries are matched on the pathname part, so a
16
+ * purge of `/news/hello-world` also removes `/news/hello-world?utm=x` — every
17
+ * cached variant of the page — and, for paths whose stripped form exceeds 16
18
+ * characters, any other page sharing that prefix. Over-purging is harmless:
19
+ * the next request regenerates the page.
20
+ */
21
+ /** The slice of Nitro's `useStorage("cache")` the purge needs. Structural so
22
+ * the published types don't depend on `unstorage`. */
23
+ interface CacheStorage {
24
+ getKeys(base?: string): Promise<string[]>;
25
+ removeItem(key: string): Promise<void>;
26
+ }
27
+ /** The `<pathname>` part Nitro files a path's cached responses under. */
28
+ declare function cacheKeyPathname(path: string): string;
29
+ /** Removes every cached response filed under one of `paths`. Returns the
30
+ * number of entries removed. */
31
+ declare function purgeCachedPaths(storage: CacheStorage, paths: string[]): Promise<number>;
32
+
33
+ export { type CacheStorage, cacheKeyPathname, purgeCachedPaths };
@@ -0,0 +1,29 @@
1
+ const PATH_KEYED_GROUPS = ["nitro:routes", "nitro:handlers"];
2
+ function cacheKeyPathname(path) {
3
+ const pathname = path.split(/[?#]/, 1)[0] ?? "";
4
+ let decoded;
5
+ try {
6
+ decoded = decodeURI(pathname);
7
+ } catch {
8
+ return "-";
9
+ }
10
+ return decoded.replace(/\W/g, "").slice(0, 16) || "index";
11
+ }
12
+ async function purgeCachedPaths(storage, paths) {
13
+ if (paths.length === 0) return 0;
14
+ const wanted = new Set(paths.map(cacheKeyPathname));
15
+ const matches = [];
16
+ for (const group of PATH_KEYED_GROUPS) {
17
+ for (const key of await storage.getKeys(group)) {
18
+ const pathname = key.slice(group.length + 1).split(":")[1]?.split(".")[0];
19
+ if (pathname !== void 0 && wanted.has(pathname)) matches.push(key);
20
+ }
21
+ }
22
+ await Promise.all(matches.map((key) => storage.removeItem(key)));
23
+ return matches.length;
24
+ }
25
+ export {
26
+ cacheKeyPathname,
27
+ purgeCachedPaths
28
+ };
29
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/runtime/server/cache.ts"],"sourcesContent":["/**\n * Purging Nitro's response cache by path. Nitro has no cache tags: a cached\n * response lives under a storage key derived from the request URL, so\n * revalidating a path means finding and removing the keys that belong to it.\n *\n * `defineCachedEventHandler` (and the `cache`/`swr`/`isr` route rules, which\n * wrap handlers in it) store entries as\n *\n * cache:nitro:routes:<name>:<pathname>.<hash>[:<header>.<hash>…].json\n * cache:nitro:handlers:<name>:<pathname>.<hash>[:<header>.<hash>…].json\n *\n * where `<pathname>` is the URL path with every non-word character stripped\n * and cut to 16 characters (`/news/hello-world` → `newshelloworld`, `/` →\n * `index`) and `<hash>` digests the full URL including its query string. The\n * hash is not recomputed here; entries are matched on the pathname part, so a\n * purge of `/news/hello-world` also removes `/news/hello-world?utm=x` — every\n * cached variant of the page — and, for paths whose stripped form exceeds 16\n * characters, any other page sharing that prefix. Over-purging is harmless:\n * the next request regenerates the page.\n */\n\n/** The slice of Nitro's `useStorage(\"cache\")` the purge needs. Structural so\n * the published types don't depend on `unstorage`. */\nexport interface CacheStorage {\n getKeys(base?: string): Promise<string[]>;\n removeItem(key: string): Promise<void>;\n}\n\n/** Cache groups whose keys follow the request-path convention: route rules\n * and `defineCachedEventHandler`. `nitro:functions` keys are caller-defined\n * and left alone. */\nconst PATH_KEYED_GROUPS = [\"nitro:routes\", \"nitro:handlers\"];\n\n/** The `<pathname>` part Nitro files a path's cached responses under. */\nexport function cacheKeyPathname(path: string): string {\n const pathname = path.split(/[?#]/, 1)[0] ?? \"\";\n let decoded: string;\n try {\n decoded = decodeURI(pathname);\n } catch {\n return \"-\";\n }\n return decoded.replace(/\\W/g, \"\").slice(0, 16) || \"index\";\n}\n\n/** Removes every cached response filed under one of `paths`. Returns the\n * number of entries removed. */\nexport async function purgeCachedPaths(storage: CacheStorage, paths: string[]): Promise<number> {\n if (paths.length === 0) return 0;\n const wanted = new Set(paths.map(cacheKeyPathname));\n const matches: string[] = [];\n for (const group of PATH_KEYED_GROUPS) {\n for (const key of await storage.getKeys(group)) {\n // key = <group>:<name>:<pathname>.<hash>[:…].json\n const pathname = key.slice(group.length + 1).split(\":\")[1]?.split(\".\")[0];\n if (pathname !== undefined && wanted.has(pathname)) matches.push(key);\n }\n }\n await Promise.all(matches.map((key) => storage.removeItem(key)));\n return matches.length;\n}\n"],"mappings":"AA+BA,MAAM,oBAAoB,CAAC,gBAAgB,gBAAgB;AAGpD,SAAS,iBAAiB,MAAsB;AACrD,QAAM,WAAW,KAAK,MAAM,QAAQ,CAAC,EAAE,CAAC,KAAK;AAC7C,MAAI;AACJ,MAAI;AACF,cAAU,UAAU,QAAQ;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,EAAE,KAAK;AACpD;AAIA,eAAsB,iBAAiB,SAAuB,OAAkC;AAC9F,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,SAAS,IAAI,IAAI,MAAM,IAAI,gBAAgB,CAAC;AAClD,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,mBAAmB;AACrC,eAAW,OAAO,MAAM,QAAQ,QAAQ,KAAK,GAAG;AAE9C,YAAM,WAAW,IAAI,MAAM,MAAM,SAAS,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,EAAE,CAAC;AACxE,UAAI,aAAa,UAAa,OAAO,IAAI,QAAQ,EAAG,SAAQ,KAAK,GAAG;AAAA,IACtE;AAAA,EACF;AACA,QAAM,QAAQ,IAAI,QAAQ,IAAI,CAAC,QAAQ,QAAQ,WAAW,GAAG,CAAC,CAAC;AAC/D,SAAO,QAAQ;AACjB;","names":[]}
@@ -0,0 +1,4 @@
1
+ export { createRevalidateHandler } from './revalidate.js';
2
+ export { CacheStorage, cacheKeyPathname, purgeCachedPaths } from './cache.js';
3
+ export { RevalidateOptions, RevalidatePaths, RevalidationPlan } from '@plenary/client';
4
+ import 'h3';
@@ -0,0 +1,8 @@
1
+ import { createRevalidateHandler } from "./revalidate.js";
2
+ import { purgeCachedPaths, cacheKeyPathname } from "./cache.js";
3
+ export {
4
+ cacheKeyPathname,
5
+ createRevalidateHandler,
6
+ purgeCachedPaths
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/runtime/server/index.ts"],"sourcesContent":["/**\n * @plenary/nuxt/server — the server side of the Nuxt integration: a webhook\n * handler that purges Nitro's response cache for the paths an entry event\n * touches. Import it from `server/` routes; the `@plenary/nuxt` module must\n * be enabled for it to reach Nitro's storage.\n *\n * The path planner (`entryUrl`, `planRevalidation`) is `@plenary/client`'s and\n * is imported from there: re-exporting it here would make Nitro's bundler warn\n * about unused external imports on every build of a route that doesn't use it.\n */\n\nexport { createRevalidateHandler } from \"./revalidate.js\";\nexport { purgeCachedPaths, cacheKeyPathname, type CacheStorage } from \"./cache.js\";\nexport type { RevalidateOptions, RevalidatePaths, RevalidationPlan } from \"@plenary/client\";\n"],"mappings":"AAWA,SAAS,+BAA+B;AACxC,SAAS,kBAAkB,wBAA2C;","names":[]}
@@ -0,0 +1,57 @@
1
+ import * as _plenary_client from '@plenary/client';
2
+ import { RevalidateOptions } from '@plenary/client';
3
+ import * as h3 from 'h3';
4
+
5
+ /**
6
+ * Builds a Nitro event handler that turns Plenary entry webhooks into cache
7
+ * purges: every cached response of the entry's paths — `swr`/`cache` route
8
+ * rules and `defineCachedEventHandler` routes — is removed, so the next
9
+ * request renders fresh content.
10
+ *
11
+ * ```ts
12
+ * // server/api/revalidate.post.ts
13
+ * import { createRevalidateHandler } from "@plenary/nuxt/server";
14
+ *
15
+ * export default createRevalidateHandler({
16
+ * secret: useRuntimeConfig().plenaryWebhookSecret,
17
+ * });
18
+ * ```
19
+ *
20
+ * With no `paths` map, each event revalidates the entry's own URL (folder
21
+ * path + slug). Point the webhook at the route, subscribed to the entry
22
+ * events, with the secret in an `x-plenary-secret` header.
23
+ *
24
+ * The console's Test button gets a dry run: a 200 reporting what would have
25
+ * been purged, without touching the cache.
26
+ */
27
+ declare function createRevalidateHandler(options: RevalidateOptions): h3.EventHandler<h3.EventHandlerRequest, Promise<{
28
+ message: string;
29
+ revalidated?: undefined;
30
+ ignored?: undefined;
31
+ test?: undefined;
32
+ paths?: undefined;
33
+ purged?: undefined;
34
+ } | {
35
+ revalidated: boolean;
36
+ ignored: _plenary_client.EntryWebhookEvent | "webhook.test" | "form.submitted";
37
+ message?: undefined;
38
+ test?: undefined;
39
+ paths?: undefined;
40
+ purged?: undefined;
41
+ } | {
42
+ test: boolean;
43
+ paths: string[];
44
+ message?: undefined;
45
+ revalidated?: undefined;
46
+ ignored?: undefined;
47
+ purged?: undefined;
48
+ } | {
49
+ revalidated: boolean;
50
+ paths: string[];
51
+ purged: number;
52
+ message?: undefined;
53
+ ignored?: undefined;
54
+ test?: undefined;
55
+ }>>;
56
+
57
+ export { createRevalidateHandler };
@@ -0,0 +1,35 @@
1
+ import { defineEventHandler, setResponseStatus, toWebRequest } from "h3";
2
+ import { useStorage } from "nitropack/runtime";
3
+ import {
4
+ parseWebhookRequest,
5
+ planRevalidation,
6
+ PlenaryWebhookError
7
+ } from "@plenary/client";
8
+ import { purgeCachedPaths } from "./cache.js";
9
+ function createRevalidateHandler(options) {
10
+ return defineEventHandler(async (event) => {
11
+ let payload;
12
+ try {
13
+ payload = await parseWebhookRequest(toWebRequest(event), options);
14
+ } catch (err) {
15
+ if (err instanceof PlenaryWebhookError) {
16
+ setResponseStatus(event, err.status);
17
+ return { message: err.message };
18
+ }
19
+ throw err;
20
+ }
21
+ const plan = planRevalidation(payload, options);
22
+ if (!plan.relevant) {
23
+ return { revalidated: false, ignored: payload.event };
24
+ }
25
+ if (plan.test) {
26
+ return { test: true, paths: plan.paths };
27
+ }
28
+ const purged = await purgeCachedPaths(useStorage("cache"), plan.paths);
29
+ return { revalidated: true, paths: plan.paths, purged };
30
+ });
31
+ }
32
+ export {
33
+ createRevalidateHandler
34
+ };
35
+ //# sourceMappingURL=revalidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/runtime/server/revalidate.ts"],"sourcesContent":["import { defineEventHandler, setResponseStatus, toWebRequest } from \"h3\";\nimport { useStorage } from \"nitropack/runtime\";\nimport {\n parseWebhookRequest,\n planRevalidation,\n PlenaryWebhookError,\n type RevalidateOptions,\n} from \"@plenary/client\";\nimport { purgeCachedPaths } from \"./cache.js\";\n\n/**\n * Builds a Nitro event handler that turns Plenary entry webhooks into cache\n * purges: every cached response of the entry's paths — `swr`/`cache` route\n * rules and `defineCachedEventHandler` routes — is removed, so the next\n * request renders fresh content.\n *\n * ```ts\n * // server/api/revalidate.post.ts\n * import { createRevalidateHandler } from \"@plenary/nuxt/server\";\n *\n * export default createRevalidateHandler({\n * secret: useRuntimeConfig().plenaryWebhookSecret,\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 purged, without touching the cache.\n */\nexport function createRevalidateHandler(options: RevalidateOptions) {\n return defineEventHandler(async (event) => {\n let payload;\n try {\n payload = await parseWebhookRequest(toWebRequest(event), options);\n } catch (err) {\n if (err instanceof PlenaryWebhookError) {\n setResponseStatus(event, err.status);\n return { message: err.message };\n }\n throw err;\n }\n\n const plan = planRevalidation(payload, options);\n if (!plan.relevant) {\n return { revalidated: false, ignored: payload.event };\n }\n if (plan.test) {\n return { test: true, paths: plan.paths };\n }\n const purged = await purgeCachedPaths(useStorage(\"cache\"), plan.paths);\n return { revalidated: true, paths: plan.paths, purged };\n });\n}\n"],"mappings":"AAAA,SAAS,oBAAoB,mBAAmB,oBAAoB;AACpE,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,wBAAwB;AAwB1B,SAAS,wBAAwB,SAA4B;AAClE,SAAO,mBAAmB,OAAO,UAAU;AACzC,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,oBAAoB,aAAa,KAAK,GAAG,OAAO;AAAA,IAClE,SAAS,KAAK;AACZ,UAAI,eAAe,qBAAqB;AACtC,0BAAkB,OAAO,IAAI,MAAM;AACnC,eAAO,EAAE,SAAS,IAAI,QAAQ;AAAA,MAChC;AACA,YAAM;AAAA,IACR;AAEA,UAAM,OAAO,iBAAiB,SAAS,OAAO;AAC9C,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,EAAE,aAAa,OAAO,SAAS,QAAQ,MAAM;AAAA,IACtD;AACA,QAAI,KAAK,MAAM;AACb,aAAO,EAAE,MAAM,MAAM,OAAO,KAAK,MAAM;AAAA,IACzC;AACA,UAAM,SAAS,MAAM,iBAAiB,WAAW,OAAO,GAAG,KAAK,KAAK;AACrE,WAAO,EAAE,aAAa,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,EACxD,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@plenary/nuxt",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.2.0",
4
+ "description": "Plenary Nuxt integration: a module and server handler that turn entry webhooks into Nitro cache purges.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/module.js",
8
+ "module": "./dist/module.js",
9
+ "types": "./dist/module.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/module.d.ts",
13
+ "import": "./dist/module.js"
14
+ },
15
+ "./server": {
16
+ "types": "./dist/runtime/server/index.d.ts",
17
+ "import": "./dist/runtime/server/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "dependencies": {
24
+ "@nuxt/kit": "^4.6.0",
25
+ "h3": "^1.15.0",
26
+ "@plenary/client": "1.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "@nuxt/schema": "^4.6.0",
30
+ "@types/node": "^26.1.2",
31
+ "nitropack": "^2.13.6",
32
+ "unstorage": "^1.17.5"
33
+ },
34
+ "license": "MIT",
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "test": "vitest run",
39
+ "typecheck": "tsc --noEmit"
40
+ }
6
41
  }