@datum-cloud/strapi-revalidate 0.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/.env.example +5 -0
- package/LICENSE +121 -0
- package/README.md +227 -0
- package/dist/cache/drivers/file.d.ts +37 -0
- package/dist/cache/drivers/file.d.ts.map +1 -0
- package/dist/cache/drivers/file.js +151 -0
- package/dist/cache/drivers/file.js.map +1 -0
- package/dist/cache/drivers/memory.d.ts +20 -0
- package/dist/cache/drivers/memory.d.ts.map +1 -0
- package/dist/cache/drivers/memory.js +76 -0
- package/dist/cache/drivers/memory.js.map +1 -0
- package/dist/cache/drivers/redis.d.ts +25 -0
- package/dist/cache/drivers/redis.d.ts.map +1 -0
- package/dist/cache/drivers/redis.js +36 -0
- package/dist/cache/drivers/redis.js.map +1 -0
- package/dist/cache/index.d.ts +28 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +54 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/manager.d.ts +55 -0
- package/dist/cache/manager.d.ts.map +1 -0
- package/dist/cache/manager.js +73 -0
- package/dist/cache/manager.js.map +1 -0
- package/dist/cache/types.d.ts +33 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +9 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/client/graphql.d.ts +46 -0
- package/dist/client/graphql.d.ts.map +1 -0
- package/dist/client/graphql.js +97 -0
- package/dist/client/graphql.js.map +1 -0
- package/dist/client/index.d.ts +28 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +38 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/rest.d.ts +23 -0
- package/dist/client/rest.d.ts.map +1 -0
- package/dist/client/rest.js +25 -0
- package/dist/client/rest.js.map +1 -0
- package/dist/content/articles.d.ts +36 -0
- package/dist/content/articles.d.ts.map +1 -0
- package/dist/content/articles.js +157 -0
- package/dist/content/articles.js.map +1 -0
- package/dist/content/authors.d.ts +28 -0
- package/dist/content/authors.d.ts.map +1 -0
- package/dist/content/authors.js +115 -0
- package/dist/content/authors.js.map +1 -0
- package/dist/content/index.d.ts +11 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/types/config.d.ts +108 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +49 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/strapi.d.ts +103 -0
- package/dist/types/strapi.d.ts.map +1 -0
- package/dist/types/strapi.js +11 -0
- package/dist/types/strapi.js.map +1 -0
- package/dist/webhook/handler.d.ts +38 -0
- package/dist/webhook/handler.d.ts.map +1 -0
- package/dist/webhook/handler.js +156 -0
- package/dist/webhook/handler.js.map +1 -0
- package/dist/webhook/index.d.ts +9 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +7 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/tags.d.ts +22 -0
- package/dist/webhook/tags.d.ts.map +1 -0
- package/dist/webhook/tags.js +39 -0
- package/dist/webhook/tags.js.map +1 -0
- package/dist/webhook/types.d.ts +60 -0
- package/dist/webhook/types.d.ts.map +1 -0
- package/dist/webhook/types.js +6 -0
- package/dist/webhook/types.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Strapi v5 content types.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the shape returned by the default Strapi v5 GraphQL API.
|
|
5
|
+
* They are intentionally narrow: only fields that strapi-revalidate's
|
|
6
|
+
* built-in helpers query are included. Consumers fetching custom content
|
|
7
|
+
* types should declare their own interfaces and pass them through
|
|
8
|
+
* `createStrapiClient().query<T>()`.
|
|
9
|
+
*/
|
|
10
|
+
/** A single Strapi media format (thumbnail, small, medium, large). */
|
|
11
|
+
export interface StrapiImageFormat {
|
|
12
|
+
url: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
}
|
|
16
|
+
/** A Strapi media asset with optional responsive formats. */
|
|
17
|
+
export interface StrapiImage {
|
|
18
|
+
url: string;
|
|
19
|
+
alternativeText?: string;
|
|
20
|
+
caption?: string;
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
formats?: {
|
|
24
|
+
thumbnail?: StrapiImageFormat;
|
|
25
|
+
small?: StrapiImageFormat;
|
|
26
|
+
medium?: StrapiImageFormat;
|
|
27
|
+
large?: StrapiImageFormat;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Minimal author reference embedded in other content types. */
|
|
31
|
+
export interface StrapiAuthor {
|
|
32
|
+
documentId?: string;
|
|
33
|
+
slug?: string;
|
|
34
|
+
name: string;
|
|
35
|
+
isTeam?: boolean;
|
|
36
|
+
avatar?: StrapiImage;
|
|
37
|
+
}
|
|
38
|
+
/** Social handles for a Strapi author. */
|
|
39
|
+
export interface StrapiSocial {
|
|
40
|
+
twitter?: string;
|
|
41
|
+
linkedin?: string;
|
|
42
|
+
github?: string;
|
|
43
|
+
discord?: string;
|
|
44
|
+
email?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Full author record (Strapi `author` content type). */
|
|
47
|
+
export interface StrapiAuthorFull {
|
|
48
|
+
documentId: string;
|
|
49
|
+
slug?: string;
|
|
50
|
+
name: string;
|
|
51
|
+
title?: string;
|
|
52
|
+
bio?: string;
|
|
53
|
+
isTeam?: boolean;
|
|
54
|
+
team?: string;
|
|
55
|
+
avatar?: StrapiImage;
|
|
56
|
+
social?: StrapiSocial;
|
|
57
|
+
location?: string;
|
|
58
|
+
timezone?: string;
|
|
59
|
+
}
|
|
60
|
+
/** GraphQL response shape for the `authors` query. */
|
|
61
|
+
export interface StrapiAuthorsResponse {
|
|
62
|
+
authors: StrapiAuthorFull[];
|
|
63
|
+
}
|
|
64
|
+
/** Category reference on an article. */
|
|
65
|
+
export interface StrapiCategory {
|
|
66
|
+
name: string;
|
|
67
|
+
slug: string;
|
|
68
|
+
}
|
|
69
|
+
/** SEO component fields. */
|
|
70
|
+
export interface StrapiSeo {
|
|
71
|
+
metaTitle?: string;
|
|
72
|
+
metaDescription?: string;
|
|
73
|
+
ogTitle?: string;
|
|
74
|
+
ogDescription?: string;
|
|
75
|
+
shareImage?: {
|
|
76
|
+
url?: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** A block of an article body (Strapi Dynamic Zone entry). */
|
|
80
|
+
export interface StrapiBlock {
|
|
81
|
+
__typename?: string;
|
|
82
|
+
id?: string;
|
|
83
|
+
body?: string;
|
|
84
|
+
title?: string;
|
|
85
|
+
}
|
|
86
|
+
/** Article record (Strapi `article` content type). */
|
|
87
|
+
export interface StrapiArticle {
|
|
88
|
+
documentId: string;
|
|
89
|
+
title: string;
|
|
90
|
+
slug: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
originalPublishedAt?: string;
|
|
93
|
+
blocks?: StrapiBlock[];
|
|
94
|
+
cover?: StrapiImage;
|
|
95
|
+
author?: StrapiAuthor;
|
|
96
|
+
category?: StrapiCategory;
|
|
97
|
+
seo?: StrapiSeo;
|
|
98
|
+
}
|
|
99
|
+
/** GraphQL response shape for the `articles` query. */
|
|
100
|
+
export interface StrapiArticlesResponse {
|
|
101
|
+
articles: StrapiArticle[];
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=strapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strapi.d.ts","sourceRoot":"","sources":["../../src/types/strapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Strapi v5 content types.
|
|
3
|
+
*
|
|
4
|
+
* These mirror the shape returned by the default Strapi v5 GraphQL API.
|
|
5
|
+
* They are intentionally narrow: only fields that strapi-revalidate's
|
|
6
|
+
* built-in helpers query are included. Consumers fetching custom content
|
|
7
|
+
* types should declare their own interfaces and pass them through
|
|
8
|
+
* `createStrapiClient().query<T>()`.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=strapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strapi.js","sourceRoot":"","sources":["../../src/types/strapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic webhook handler.
|
|
3
|
+
*
|
|
4
|
+
* `createWebhookHandler(config)` returns an `async (req, res) => void`
|
|
5
|
+
* function whose `req` and `res` accept the lowest-common-denominator
|
|
6
|
+
* shapes used by Astro, Next.js (Route Handlers), SvelteKit, Express,
|
|
7
|
+
* and raw Node http. Each framework's docs page in the README shows the
|
|
8
|
+
* single line of glue code needed.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour:
|
|
11
|
+
* 1. 405 if method isn't POST.
|
|
12
|
+
* 2. 401 if a secret is configured and the request's secret doesn't match
|
|
13
|
+
* (constant-time compare; supports `Authorization: Bearer <secret>`,
|
|
14
|
+
* `X-Strapi-Signature`, or a raw `strapi-webhook-secret` header).
|
|
15
|
+
* 3. 400 if the body isn't a parseable Strapi v5 webhook payload.
|
|
16
|
+
* 4. Resolves tags via `mapModelToTags`, calls `cache.invalidate(tag)` for
|
|
17
|
+
* each, and invokes `onRevalidate` if configured.
|
|
18
|
+
* 5. 200 with `{ ok: true, tags }`.
|
|
19
|
+
*/
|
|
20
|
+
import type { CacheManager } from '../cache/manager.js';
|
|
21
|
+
import type { RevalidateConfig, RevalidateConfigInput } from '../types/config.js';
|
|
22
|
+
import type { WebhookRequest, WebhookResponse } from './types.js';
|
|
23
|
+
/** Async function shape returned by `createWebhookHandler`. */
|
|
24
|
+
export type WebhookHandler = (req: WebhookRequest, res: WebhookResponse) => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Build a webhook handler bound to a validated config and the cache
|
|
27
|
+
* manager derived from it.
|
|
28
|
+
*
|
|
29
|
+
* Accepts either a `RevalidateConfigInput` (raw user object — gets validated)
|
|
30
|
+
* or a pair of pre-built dependencies, so power users can share a single
|
|
31
|
+
* `CacheManager` across handlers and content fetchers.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createWebhookHandler(input: RevalidateConfigInput): WebhookHandler;
|
|
34
|
+
export declare function createWebhookHandler(deps: {
|
|
35
|
+
config: RevalidateConfig;
|
|
36
|
+
cache: CacheManager;
|
|
37
|
+
}): WebhookHandler;
|
|
38
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/webhook/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAElF,OAAO,KAAK,EAGV,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1F;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,qBAAqB,GAAG,cAAc,CAAC;AACnF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,YAAY,CAAC;CACrB,GAAG,cAAc,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic webhook handler.
|
|
3
|
+
*
|
|
4
|
+
* `createWebhookHandler(config)` returns an `async (req, res) => void`
|
|
5
|
+
* function whose `req` and `res` accept the lowest-common-denominator
|
|
6
|
+
* shapes used by Astro, Next.js (Route Handlers), SvelteKit, Express,
|
|
7
|
+
* and raw Node http. Each framework's docs page in the README shows the
|
|
8
|
+
* single line of glue code needed.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour:
|
|
11
|
+
* 1. 405 if method isn't POST.
|
|
12
|
+
* 2. 401 if a secret is configured and the request's secret doesn't match
|
|
13
|
+
* (constant-time compare; supports `Authorization: Bearer <secret>`,
|
|
14
|
+
* `X-Strapi-Signature`, or a raw `strapi-webhook-secret` header).
|
|
15
|
+
* 3. 400 if the body isn't a parseable Strapi v5 webhook payload.
|
|
16
|
+
* 4. Resolves tags via `mapModelToTags`, calls `cache.invalidate(tag)` for
|
|
17
|
+
* each, and invokes `onRevalidate` if configured.
|
|
18
|
+
* 5. 200 with `{ ok: true, tags }`.
|
|
19
|
+
*/
|
|
20
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
21
|
+
import { createCache } from '../cache/index.js';
|
|
22
|
+
import { revalidateConfigSchema } from '../types/config.js';
|
|
23
|
+
import { mapModelToTags } from './tags.js';
|
|
24
|
+
export function createWebhookHandler(input) {
|
|
25
|
+
const { config, cache } = resolveDeps(input);
|
|
26
|
+
return async function handle(req, res) {
|
|
27
|
+
if (req.method && req.method.toUpperCase() !== 'POST') {
|
|
28
|
+
res.status(405);
|
|
29
|
+
res.json({ ok: false, error: 'Method not allowed' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const configuredSecret = config.webhook?.secret;
|
|
33
|
+
if (configuredSecret) {
|
|
34
|
+
const provided = readSecret(req);
|
|
35
|
+
if (!provided || !constantTimeEqual(provided, configuredSecret)) {
|
|
36
|
+
res.status(401);
|
|
37
|
+
res.json({ ok: false, error: 'Invalid secret' });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let payload;
|
|
42
|
+
try {
|
|
43
|
+
payload = await readPayload(req);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
res.status(400);
|
|
47
|
+
res.json({
|
|
48
|
+
ok: false,
|
|
49
|
+
error: err instanceof Error ? err.message : 'Bad payload',
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const tags = mapModelToTags(payload, config.webhook?.tagMap);
|
|
54
|
+
for (const tag of tags) {
|
|
55
|
+
await cache.invalidate(tag);
|
|
56
|
+
}
|
|
57
|
+
const event = {
|
|
58
|
+
event: payload.event,
|
|
59
|
+
model: payload.model,
|
|
60
|
+
uid: payload.uid,
|
|
61
|
+
entryId: payload.entry.documentId,
|
|
62
|
+
slug: typeof payload.entry.slug === 'string' ? payload.entry.slug : undefined,
|
|
63
|
+
tags,
|
|
64
|
+
};
|
|
65
|
+
if (config.webhook?.onRevalidate) {
|
|
66
|
+
try {
|
|
67
|
+
await config.webhook.onRevalidate(event);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// The cache is already invalidated; surface the hook failure to the caller
|
|
71
|
+
// but treat the revalidation itself as successful.
|
|
72
|
+
console.error('[strapi-revalidate] onRevalidate hook threw:', err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
res.status(200);
|
|
76
|
+
res.json({ ok: true, tags });
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function resolveDeps(input) {
|
|
80
|
+
if (isPreBuilt(input))
|
|
81
|
+
return input;
|
|
82
|
+
const config = revalidateConfigSchema.parse(input);
|
|
83
|
+
const cache = createCache(config);
|
|
84
|
+
return { config, cache };
|
|
85
|
+
}
|
|
86
|
+
function isPreBuilt(value) {
|
|
87
|
+
return (typeof value === 'object' &&
|
|
88
|
+
value !== null &&
|
|
89
|
+
'config' in value &&
|
|
90
|
+
'cache' in value &&
|
|
91
|
+
typeof value.cache === 'object');
|
|
92
|
+
}
|
|
93
|
+
function readSecret(req) {
|
|
94
|
+
// Headers can arrive as Fetch-style Headers (`.get()`) or as a plain map.
|
|
95
|
+
const direct = headerValue(req, 'authorization');
|
|
96
|
+
if (direct) {
|
|
97
|
+
const match = /^Bearer\s+(.+)$/i.exec(direct);
|
|
98
|
+
if (match)
|
|
99
|
+
return match[1].trim();
|
|
100
|
+
}
|
|
101
|
+
return (headerValue(req, 'x-strapi-signature') ??
|
|
102
|
+
headerValue(req, 'strapi-webhook-secret') ??
|
|
103
|
+
null);
|
|
104
|
+
}
|
|
105
|
+
function headerValue(req, name) {
|
|
106
|
+
const headers = req.headers;
|
|
107
|
+
if (typeof headers.get === 'function') {
|
|
108
|
+
return headers.get(name);
|
|
109
|
+
}
|
|
110
|
+
const lower = name.toLowerCase();
|
|
111
|
+
const raw = headers[lower] ?? headers[name];
|
|
112
|
+
if (typeof raw === 'string')
|
|
113
|
+
return raw;
|
|
114
|
+
if (Array.isArray(raw) && typeof raw[0] === 'string')
|
|
115
|
+
return raw[0];
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
async function readPayload(req) {
|
|
119
|
+
let raw;
|
|
120
|
+
if (typeof req.json === 'function') {
|
|
121
|
+
raw = await req.json();
|
|
122
|
+
}
|
|
123
|
+
else if (typeof req.text === 'function') {
|
|
124
|
+
const text = await req.text();
|
|
125
|
+
raw = text.length > 0 ? JSON.parse(text) : {};
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
throw new Error('Request object exposes neither json() nor text()');
|
|
129
|
+
}
|
|
130
|
+
if (!isWebhookPayload(raw)) {
|
|
131
|
+
throw new Error('Payload is not a valid Strapi v5 webhook event');
|
|
132
|
+
}
|
|
133
|
+
return raw;
|
|
134
|
+
}
|
|
135
|
+
function isWebhookPayload(value) {
|
|
136
|
+
if (typeof value !== 'object' || value === null)
|
|
137
|
+
return false;
|
|
138
|
+
const v = value;
|
|
139
|
+
if (typeof v.event !== 'string' || typeof v.model !== 'string' || typeof v.uid !== 'string') {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (typeof v.entry !== 'object' || v.entry === null)
|
|
143
|
+
return false;
|
|
144
|
+
const entry = v.entry;
|
|
145
|
+
return typeof entry.documentId === 'string' || typeof entry.id === 'number';
|
|
146
|
+
}
|
|
147
|
+
function constantTimeEqual(a, b) {
|
|
148
|
+
// timingSafeEqual requires equal-length buffers; pad both to the longer length so the
|
|
149
|
+
// length check itself is constant-time-safe (different lengths still return false).
|
|
150
|
+
const bufA = Buffer.from(a, 'utf-8');
|
|
151
|
+
const bufB = Buffer.from(b, 'utf-8');
|
|
152
|
+
if (bufA.length !== bufB.length)
|
|
153
|
+
return false;
|
|
154
|
+
return timingSafeEqual(bufA, bufB);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/webhook/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public webhook surface — re-exports for the `strapi-revalidate/webhook`
|
|
3
|
+
* subpath.
|
|
4
|
+
*/
|
|
5
|
+
export { createWebhookHandler } from './handler.js';
|
|
6
|
+
export type { WebhookHandler } from './handler.js';
|
|
7
|
+
export { mapModelToTags } from './tags.js';
|
|
8
|
+
export type { WebhookEvent, WebhookEventName, WebhookPayload, WebhookRequest, WebhookResponse, } from './types.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhook/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strapi UID → cache tag mapping.
|
|
3
|
+
*
|
|
4
|
+
* Default rules:
|
|
5
|
+
* - `api::article.article` → `["articles"]` (+ `article:<slug>` if entry has slug)
|
|
6
|
+
* - `api::author.author` → `["authors"]` (+ `author:<slug>` if entry has slug)
|
|
7
|
+
* - any other `api::<name>.<name>` → `[<name>]` (+ `<name>:<slug>` if present)
|
|
8
|
+
*
|
|
9
|
+
* Override per-UID via `config.webhook.tagMap` — passing `["foo", "bar"]`
|
|
10
|
+
* replaces the default tag list for that UID (the slug-derived tag is still
|
|
11
|
+
* appended automatically when present).
|
|
12
|
+
*/
|
|
13
|
+
import type { WebhookPayload } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Resolve cache tags for a webhook payload.
|
|
16
|
+
*
|
|
17
|
+
* @param payload - Raw Strapi v5 webhook payload.
|
|
18
|
+
* @param overrides - Optional UID → tag list overrides from config.
|
|
19
|
+
* @returns Deduplicated list of tags to invalidate.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mapModelToTags(payload: WebhookPayload, overrides?: Record<string, string[]>): string[];
|
|
22
|
+
//# sourceMappingURL=tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/webhook/tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAQjD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACnC,MAAM,EAAE,CAKV"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strapi UID → cache tag mapping.
|
|
3
|
+
*
|
|
4
|
+
* Default rules:
|
|
5
|
+
* - `api::article.article` → `["articles"]` (+ `article:<slug>` if entry has slug)
|
|
6
|
+
* - `api::author.author` → `["authors"]` (+ `author:<slug>` if entry has slug)
|
|
7
|
+
* - any other `api::<name>.<name>` → `[<name>]` (+ `<name>:<slug>` if present)
|
|
8
|
+
*
|
|
9
|
+
* Override per-UID via `config.webhook.tagMap` — passing `["foo", "bar"]`
|
|
10
|
+
* replaces the default tag list for that UID (the slug-derived tag is still
|
|
11
|
+
* appended automatically when present).
|
|
12
|
+
*/
|
|
13
|
+
/** Built-in UID → tag list. */
|
|
14
|
+
const DEFAULT_TAG_MAP = {
|
|
15
|
+
'api::article.article': ['articles'],
|
|
16
|
+
'api::author.author': ['authors'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Resolve cache tags for a webhook payload.
|
|
20
|
+
*
|
|
21
|
+
* @param payload - Raw Strapi v5 webhook payload.
|
|
22
|
+
* @param overrides - Optional UID → tag list overrides from config.
|
|
23
|
+
* @returns Deduplicated list of tags to invalidate.
|
|
24
|
+
*/
|
|
25
|
+
export function mapModelToTags(payload, overrides) {
|
|
26
|
+
const base = overrides?.[payload.uid] ?? DEFAULT_TAG_MAP[payload.uid] ?? [extractModel(payload)];
|
|
27
|
+
const slug = typeof payload.entry?.slug === 'string' ? payload.entry.slug : undefined;
|
|
28
|
+
const tags = slug ? [...base, `${payload.model}:${slug}`] : base;
|
|
29
|
+
return Array.from(new Set(tags));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract the model name from a UID. `api::article.article` → `article`.
|
|
33
|
+
* Falls back to `payload.model` if the UID is in an unexpected shape.
|
|
34
|
+
*/
|
|
35
|
+
function extractModel(payload) {
|
|
36
|
+
const match = /^api::([^.]+)\./.exec(payload.uid);
|
|
37
|
+
return match?.[1] ?? payload.model;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/webhook/tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+BAA+B;AAC/B,MAAM,eAAe,GAA6B;IAChD,sBAAsB,EAAE,CAAC,UAAU,CAAC;IACpC,oBAAoB,EAAE,CAAC,SAAS,CAAC;CAClC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAuB,EACvB,SAAoC;IAEpC,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IACjG,MAAM,IAAI,GAAG,OAAO,OAAO,CAAC,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACtF,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,OAAuB;IAC3C,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strapi v5 webhook payload types and the normalized event surfaced to
|
|
3
|
+
* downstream handlers.
|
|
4
|
+
*/
|
|
5
|
+
/** All webhook event names Strapi v5 emits. */
|
|
6
|
+
export type WebhookEventName = 'entry.create' | 'entry.update' | 'entry.delete' | 'entry.publish' | 'entry.unpublish' | 'media.create' | 'media.update' | 'media.delete';
|
|
7
|
+
/** Raw payload Strapi v5 POSTs to the webhook URL. */
|
|
8
|
+
export interface WebhookPayload {
|
|
9
|
+
event: WebhookEventName;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
/** Singular model name, e.g. `"article"`. */
|
|
12
|
+
model: string;
|
|
13
|
+
/** Strapi UID, e.g. `"api::article.article"`. */
|
|
14
|
+
uid: string;
|
|
15
|
+
entry: {
|
|
16
|
+
id: number;
|
|
17
|
+
documentId: string;
|
|
18
|
+
slug?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Normalized event passed to `onRevalidate` after tag resolution. */
|
|
23
|
+
export interface WebhookEvent {
|
|
24
|
+
event: WebhookEventName;
|
|
25
|
+
model: string;
|
|
26
|
+
uid: string;
|
|
27
|
+
entryId: string;
|
|
28
|
+
slug?: string;
|
|
29
|
+
/** Cache tags resolved from `uid` (and `slug`, if present). */
|
|
30
|
+
tags: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Minimal request interface the webhook handler accepts.
|
|
34
|
+
*
|
|
35
|
+
* Designed so an Astro `APIContext.request`, a Next.js `Request`, a
|
|
36
|
+
* SvelteKit `RequestEvent.request`, an Express `req`, and a Node
|
|
37
|
+
* `IncomingMessage` can all satisfy it (after light adapter code in the
|
|
38
|
+
* user's route handler).
|
|
39
|
+
*/
|
|
40
|
+
export interface WebhookRequest {
|
|
41
|
+
method?: string;
|
|
42
|
+
headers: {
|
|
43
|
+
get?: (name: string) => string | null;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
text?: () => Promise<string>;
|
|
47
|
+
json?: () => Promise<unknown>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Minimal response interface the webhook handler writes to.
|
|
51
|
+
*
|
|
52
|
+
* The handler always calls `status(code)` first and then `json(body)`. Both
|
|
53
|
+
* calls are independent — `status()`'s return value is ignored — so adapters
|
|
54
|
+
* in framework routes can be a few lines of capture-and-forward.
|
|
55
|
+
*/
|
|
56
|
+
export interface WebhookResponse {
|
|
57
|
+
status: (code: number) => unknown;
|
|
58
|
+
json: (body: unknown) => void | Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/webhook/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,+CAA+C;AAC/C,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,cAAc,GACd,cAAc,GACd,eAAe,GACf,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,cAAc,CAAC;AAEnB,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,gBAAgB,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QACP,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/webhook/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@datum-cloud/strapi-revalidate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic runtime cache invalidation for Strapi v5 in SSR frontends. Receives Strapi publish webhooks, clears affected cache tags, next request serves fresh data.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"strapi",
|
|
7
|
+
"strapi-v5",
|
|
8
|
+
"cache",
|
|
9
|
+
"revalidate",
|
|
10
|
+
"webhook",
|
|
11
|
+
"ssr",
|
|
12
|
+
"astro",
|
|
13
|
+
"nextjs",
|
|
14
|
+
"sveltekit",
|
|
15
|
+
"nuxt"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/datum-cloud/strapi-revalidate#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/datum-cloud/strapi-revalidate/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/datum-cloud/strapi-revalidate.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "datum.net",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./cache": {
|
|
36
|
+
"types": "./dist/cache/index.d.ts",
|
|
37
|
+
"import": "./dist/cache/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./client": {
|
|
40
|
+
"types": "./dist/client/index.d.ts",
|
|
41
|
+
"import": "./dist/client/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./content": {
|
|
44
|
+
"types": "./dist/content/index.d.ts",
|
|
45
|
+
"import": "./dist/content/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./webhook": {
|
|
48
|
+
"types": "./dist/webhook/handler.d.ts",
|
|
49
|
+
"import": "./dist/webhook/handler.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"README.md",
|
|
55
|
+
"LICENSE",
|
|
56
|
+
".env.example"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"prepublishOnly": "npm run build"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=18"
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"zod": "^3.23.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"typescript": ">=5.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"typescript": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"optionalDependencies": {
|
|
83
|
+
"ioredis": "^5.0.0"
|
|
84
|
+
},
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"@types/node": "^20.0.0",
|
|
87
|
+
"typescript": "^5.4.0",
|
|
88
|
+
"vitest": "^1.6.0"
|
|
89
|
+
}
|
|
90
|
+
}
|