payload-smart-cache 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +216 -0
  2. package/dist/client/PublishButton.d.ts +2 -0
  3. package/dist/client/PublishButton.d.ts.map +1 -0
  4. package/dist/client/PublishButton.js +34 -0
  5. package/dist/client/PublishButton.js.map +1 -0
  6. package/dist/client/requests.d.ts +12 -0
  7. package/dist/client/requests.d.ts.map +1 -0
  8. package/dist/client/requests.js +58 -0
  9. package/dist/client/requests.js.map +1 -0
  10. package/dist/collections.d.ts +26 -0
  11. package/dist/collections.d.ts.map +1 -0
  12. package/dist/collections.js +30 -0
  13. package/dist/collections.js.map +1 -0
  14. package/dist/const.d.ts +13 -0
  15. package/dist/const.d.ts.map +1 -0
  16. package/dist/const.js +19 -0
  17. package/dist/const.js.map +1 -0
  18. package/dist/endpoints/check.d.ts +3 -0
  19. package/dist/endpoints/check.d.ts.map +1 -0
  20. package/dist/endpoints/check.js +19 -0
  21. package/dist/endpoints/check.js.map +1 -0
  22. package/dist/endpoints/publish.d.ts +4 -0
  23. package/dist/endpoints/publish.d.ts.map +1 -0
  24. package/dist/endpoints/publish.js +68 -0
  25. package/dist/endpoints/publish.js.map +1 -0
  26. package/dist/exports/rsc.d.ts +2 -0
  27. package/dist/exports/rsc.d.ts.map +1 -0
  28. package/dist/exports/rsc.js +5 -0
  29. package/dist/exports/rsc.js.map +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +31 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/server/request.d.ts +3 -0
  35. package/dist/server/request.d.ts.map +1 -0
  36. package/dist/server/request.js +9 -0
  37. package/dist/server/request.js.map +1 -0
  38. package/dist/server/revalidate.d.ts +3 -0
  39. package/dist/server/revalidate.d.ts.map +1 -0
  40. package/dist/server/revalidate.js +9 -0
  41. package/dist/server/revalidate.js.map +1 -0
  42. package/dist/server/track-changes.d.ts +10 -0
  43. package/dist/server/track-changes.d.ts.map +1 -0
  44. package/dist/server/track-changes.js +59 -0
  45. package/dist/server/track-changes.js.map +1 -0
  46. package/dist/types.d.ts +16 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/utils/CollectionChanges.d.ts +8 -0
  49. package/dist/utils/CollectionChanges.d.ts.map +1 -0
  50. package/dist/utils/CollectionChanges.js +20 -0
  51. package/dist/utils/CollectionChanges.js.map +1 -0
  52. package/dist/utils/dependency-graph.d.ts +34 -0
  53. package/dist/utils/dependency-graph.d.ts.map +1 -0
  54. package/dist/utils/dependency-graph.js +101 -0
  55. package/dist/utils/dependency-graph.js.map +1 -0
  56. package/package.json +91 -0
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # payload-smart-cache
2
+
3
+ **payload-smart-cache** manages cache invalidation for your Payload CMS collections.
4
+
5
+ 1. Automatically tracks changes to your collections (create, update, delete).
6
+ 2. Queues changes for publishing and invalidates cache tags when ready.
7
+
8
+ **Features**
9
+
10
+ - Automatic change tracking for configured collections
11
+ - Publish queue to batch cache invalidations
12
+ - Dependency-aware cache invalidation across related collections
13
+ - Next.js cache tag revalidation integration
14
+ - Publish button in admin panel for manual publishing
15
+ - Configurable operations per collection (create, update, delete)
16
+
17
+ ## Getting Started
18
+
19
+ ```bash
20
+ # pnpm
21
+ pnpm add payload-smart-cache
22
+ # yarn
23
+ yarn add payload-smart-cache
24
+ # npm
25
+ npm install payload-smart-cache
26
+ ```
27
+
28
+ ### 1) Configure Payload
29
+
30
+ Add the plugin to your `payload.config.ts`:
31
+
32
+ ```typescript
33
+ import { buildConfig } from "payload";
34
+ import { smartCachPlugin } from "payload-smart-cache";
35
+
36
+ export default buildConfig({
37
+ collections: [Posts, Categories, Users],
38
+ plugins: [
39
+ smartCachPlugin({
40
+ collections: {
41
+ posts: ["create", "update", "delete"],
42
+ categories: ["create", "update"],
43
+ // users: false, // Disable tracking for users
44
+ },
45
+ publishHandler: async (changes) => {
46
+ // Optional: Custom handler for published changes
47
+ console.log("Published changes:", changes);
48
+ },
49
+ }),
50
+ ],
51
+ });
52
+ ```
53
+
54
+ ### 2) Use the Publish Button
55
+
56
+ The plugin automatically adds a **Publish** button to your admin panel header. Click it to process all queued changes and invalidate cache tags.
57
+
58
+ ### 3) Programmatic Publishing
59
+
60
+ You can also trigger publishing programmatically:
61
+
62
+ **Node.js:**
63
+
64
+ ```typescript
65
+ import config from "@payload-config";
66
+ import { getPayload } from "payload";
67
+
68
+ const payload = await getPayload({ config });
69
+ const response = await fetch(`${process.env.PAYLOAD_API_URL}/cache-plugin/publish`, {
70
+ method: "POST",
71
+ });
72
+ ```
73
+
74
+ **Edge runtime:**
75
+
76
+ ```typescript
77
+ const response = await fetch(`${process.env.PAYLOAD_API_URL}/cache-plugin/publish`, {
78
+ method: "POST",
79
+ });
80
+ ```
81
+
82
+ ## Plugin Options
83
+
84
+ The `smartCachPlugin` accepts the following configuration:
85
+
86
+ | Option | Default | Description |
87
+ | ----------------- | -------------------------- | --------------------------------------------------------------------------- |
88
+ | `collections` | All collections tracked | Per-collection configuration for which operations to track |
89
+ | `publishHandler` | - | Optional callback function called after changes are published |
90
+
91
+ ### Collection Configuration
92
+
93
+ You can configure tracking per collection:
94
+
95
+ ```typescript
96
+ smartCachPlugin({
97
+ collections: {
98
+ // Track all operations (create, update, delete)
99
+ posts: ["create", "update", "delete"],
100
+
101
+ // Track only updates
102
+ categories: ["update"],
103
+
104
+ // Disable tracking completely
105
+ users: false,
106
+
107
+ // Use default operations (create, update, delete)
108
+ // If not specified, defaults to all operations
109
+ },
110
+ })
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ 1. **Change Tracking**: When documents are created, updated, or deleted, the plugin automatically adds them to a publish queue.
116
+
117
+ 2. **Dependency Resolution**: When publishing, the plugin analyzes relationships between collections and invalidates cache tags for dependent documents.
118
+
119
+ 3. **Cache Invalidation**: Uses Next.js `revalidateTag` to invalidate cache tags for affected collections.
120
+
121
+ 4. **Publish Queue**: Changes are queued until you explicitly publish them, allowing you to batch invalidations.
122
+
123
+ ## Example Usage
124
+
125
+ Here's a complete example showing how to integrate payload-smart-cache:
126
+
127
+ ```typescript
128
+ // payload.config.ts
129
+ import { buildConfig } from "payload";
130
+ import { smartCachPlugin } from "payload-smart-cache";
131
+
132
+ import { Posts } from "./collections/Posts";
133
+ import { Categories } from "./collections/Categories";
134
+
135
+ export default buildConfig({
136
+ collections: [Posts, Categories],
137
+ plugins: [
138
+ smartCachPlugin({
139
+ collections: {
140
+ posts: ["create", "update", "delete"],
141
+ categories: ["update"], // Only track updates for categories
142
+ },
143
+ publishHandler: async (changes) => {
144
+ // Optional: Send webhook, update external cache, etc.
145
+ await fetch("https://api.example.com/webhook", {
146
+ method: "POST",
147
+ body: JSON.stringify(changes),
148
+ });
149
+ },
150
+ }),
151
+ ],
152
+ });
153
+ ```
154
+
155
+ ```typescript
156
+ // app/api/revalidate/route.ts
157
+ import { NextRequest, NextResponse } from "next/server";
158
+ import config from "@payload-config";
159
+ import { getPayload } from "payload";
160
+
161
+ export async function POST(request: NextRequest) {
162
+ const payload = await getPayload({ config });
163
+
164
+ const response = await fetch(
165
+ `${process.env.PAYLOAD_API_URL}/cache-plugin/publish`,
166
+ {
167
+ method: "POST",
168
+ }
169
+ );
170
+
171
+ if (!response.ok) {
172
+ return NextResponse.json(
173
+ { error: "Failed to publish changes" },
174
+ { status: 500 }
175
+ );
176
+ }
177
+
178
+ return NextResponse.json({ success: true });
179
+ }
180
+ ```
181
+
182
+ ## API Endpoints
183
+
184
+ The plugin provides two endpoints:
185
+
186
+ ### POST `/cache-plugin/publish`
187
+
188
+ Publishes all queued changes and invalidates cache tags.
189
+
190
+ **Response:**
191
+ - `200 OK` - Changes published successfully
192
+ - `200 OK` with message "No changes to publish" - Queue is empty
193
+
194
+ ### GET `/cache-plugin/check`
195
+
196
+ Checks the status of the publish queue.
197
+
198
+ ## Development
199
+
200
+ ```bash
201
+ # Install dependencies
202
+ pnpm install
203
+
204
+ # Start development server
205
+ pnpm dev
206
+
207
+ # Build the plugin
208
+ pnpm build
209
+
210
+ # Run tests
211
+ pnpm test
212
+ ```
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,2 @@
1
+ export declare function PublishButton(): import("react/jsx-runtime").JSX.Element | null;
2
+ //# sourceMappingURL=PublishButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublishButton.d.ts","sourceRoot":"","sources":["../../src/client/PublishButton.tsx"],"names":[],"mappings":"AAOA,wBAAgB,aAAa,mDAiD5B"}
@@ -0,0 +1,34 @@
1
+ "use client";
2
+ import { jsx as m } from "react/jsx-runtime";
3
+ import { useConfig as d, useDocumentEvents as p, Button as b, toast as i } from "@payloadcms/ui";
4
+ import { useState as u, useEffect as c } from "react";
5
+ import { checkChanges as C, publishChanges as P } from "./requests.js";
6
+ function B() {
7
+ const { config: o } = d(), { mostRecentUpdate: r } = p(), t = o.serverURL + o.routes.api, [h, s] = u(!1), [l, n] = u(!1);
8
+ c(() => {
9
+ C(t).then((e) => {
10
+ if (!e.success) return s(!0);
11
+ s(e.hasChanges);
12
+ }).catch(() => s(!0));
13
+ }, [t]), c(() => {
14
+ r && s(!0);
15
+ }, [r]);
16
+ const f = async () => {
17
+ n(!0);
18
+ const e = i.loading("Publishing changes..."), { success: g, message: a } = await P(t);
19
+ if (!g) {
20
+ i.error(a, {
21
+ id: e
22
+ }), n(!1);
23
+ return;
24
+ }
25
+ i.success(a, {
26
+ id: e
27
+ }), s(!1), n(!1);
28
+ };
29
+ return h ? /* @__PURE__ */ m(b, { disabled: l, onClick: f, children: "Publish Changes" }) : null;
30
+ }
31
+ export {
32
+ B as PublishButton
33
+ };
34
+ //# sourceMappingURL=PublishButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublishButton.js","sources":["../../src/client/PublishButton.tsx"],"sourcesContent":["'use client';\n\nimport { Button, toast, useConfig, useDocumentEvents } from '@payloadcms/ui';\nimport { useEffect, useState } from 'react';\n\nimport { checkChanges, publishChanges } from './requests';\n\nexport function PublishButton() {\n const { config } = useConfig();\n const { mostRecentUpdate } = useDocumentEvents();\n\n const apiUrl = config.serverURL + config.routes.api;\n\n const [hasChanges, setHasChanges] = useState(false);\n const [isPublishing, setIsPublishing] = useState(false);\n\n useEffect(() => {\n checkChanges(apiUrl)\n .then((result) => {\n if (!result.success) return setHasChanges(true);\n setHasChanges(result.hasChanges);\n })\n .catch(() => setHasChanges(true));\n }, [apiUrl]);\n\n useEffect(() => {\n if (!mostRecentUpdate) return;\n setHasChanges(true);\n }, [mostRecentUpdate]);\n\n const handlePublish = async () => {\n setIsPublishing(true);\n const toastId = toast.loading('Publishing changes...');\n const { success, message } = await publishChanges(apiUrl);\n\n if (!success) {\n toast.error(message, {\n id: toastId,\n });\n setIsPublishing(false);\n return;\n }\n toast.success(message, {\n id: toastId,\n });\n setHasChanges(false);\n setIsPublishing(false);\n };\n\n if (!hasChanges) return null;\n\n return (\n <Button disabled={isPublishing} onClick={handlePublish}>\n Publish Changes\n </Button>\n );\n}\n"],"names":["PublishButton","config","useConfig","mostRecentUpdate","useDocumentEvents","apiUrl","hasChanges","setHasChanges","useState","isPublishing","setIsPublishing","useEffect","checkChanges","result","handlePublish","toastId","toast","success","message","publishChanges","Button"],"mappings":";;;;;AAOO,SAASA,IAAgB;AAC9B,QAAM,EAAE,QAAAC,EAAA,IAAWC,EAAA,GACb,EAAE,kBAAAC,EAAA,IAAqBC,EAAA,GAEvBC,IAASJ,EAAO,YAAYA,EAAO,OAAO,KAE1C,CAACK,GAAYC,CAAa,IAAIC,EAAS,EAAK,GAC5C,CAACC,GAAcC,CAAe,IAAIF,EAAS,EAAK;AAEtD,EAAAG,EAAU,MAAM;AACd,IAAAC,EAAaP,CAAM,EAChB,KAAK,CAACQ,MAAW;AAChB,UAAI,CAACA,EAAO,QAAS,QAAON,EAAc,EAAI;AAC9C,MAAAA,EAAcM,EAAO,UAAU;AAAA,IACjC,CAAC,EACA,MAAM,MAAMN,EAAc,EAAI,CAAC;AAAA,EACpC,GAAG,CAACF,CAAM,CAAC,GAEXM,EAAU,MAAM;AACd,IAAKR,KACLI,EAAc,EAAI;AAAA,EACpB,GAAG,CAACJ,CAAgB,CAAC;AAErB,QAAMW,IAAgB,YAAY;AAChC,IAAAJ,EAAgB,EAAI;AACpB,UAAMK,IAAUC,EAAM,QAAQ,uBAAuB,GAC/C,EAAE,SAAAC,GAAS,SAAAC,EAAA,IAAY,MAAMC,EAAed,CAAM;AAExD,QAAI,CAACY,GAAS;AACZ,MAAAD,EAAM,MAAME,GAAS;AAAA,QACnB,IAAIH;AAAA,MAAA,CACL,GACDL,EAAgB,EAAK;AACrB;AAAA,IACF;AACA,IAAAM,EAAM,QAAQE,GAAS;AAAA,MACrB,IAAIH;AAAA,IAAA,CACL,GACDR,EAAc,EAAK,GACnBG,EAAgB,EAAK;AAAA,EACvB;AAEA,SAAKJ,sBAGFc,GAAA,EAAO,UAAUX,GAAc,SAASK,GAAe,UAAA,mBAExD,IALsB;AAO1B;"}
@@ -0,0 +1,12 @@
1
+ export declare const publishChanges: (apiUrl: string) => Promise<{
2
+ success: boolean;
3
+ message: string;
4
+ }>;
5
+ export declare const checkChanges: (apiUrl: string) => Promise<{
6
+ success: true;
7
+ hasChanges: boolean;
8
+ } | {
9
+ success: false;
10
+ error: string;
11
+ }>;
12
+ //# sourceMappingURL=requests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../src/client/requests.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,WACjB,MAAM,KACb,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CA4BA,CAAC;AAEF,eAAO,MAAM,YAAY,WACf,MAAM,KACb,OAAO,CACN;IACE,OAAO,EAAE,IAAI,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf,CAyCJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ import a from "zod";
2
+ import { ENDPOINT_CONFIG as r } from "../const.js";
3
+ const g = async (o) => {
4
+ try {
5
+ const s = o + r.publish.path, e = await fetch(s, {
6
+ method: r.publish.method
7
+ });
8
+ if (!e.ok) {
9
+ const { success: t, data: c } = a.object({
10
+ message: a.string()
11
+ }).safeParse(await e.json());
12
+ throw new Error(t ? c.message : "Failed to publish changes");
13
+ }
14
+ return {
15
+ success: !0,
16
+ message: "Changes published successfully!"
17
+ };
18
+ } catch (s) {
19
+ return console.error(s), {
20
+ success: !1,
21
+ message: s instanceof Error ? s.message : "Failed to publish changes"
22
+ };
23
+ }
24
+ }, l = async (o) => {
25
+ try {
26
+ const s = o + r.check.path, e = await fetch(s, {
27
+ method: r.check.method,
28
+ headers: {
29
+ "Content-Type": "application/json"
30
+ }
31
+ });
32
+ if (!e.ok) {
33
+ const { success: n, data: h } = a.object({
34
+ message: a.string()
35
+ }).safeParse(await e.json());
36
+ throw new Error(n ? h.message : "Failed to check changes");
37
+ }
38
+ const { success: t, data: c } = a.object({
39
+ hasChanges: a.boolean()
40
+ }).safeParse(await e.json());
41
+ if (!t)
42
+ throw new Error("Failed to check changes");
43
+ return {
44
+ success: !0,
45
+ hasChanges: c.hasChanges
46
+ };
47
+ } catch (s) {
48
+ return console.error(s), {
49
+ success: !1,
50
+ error: s instanceof Error ? s.message : "Unknown error"
51
+ };
52
+ }
53
+ };
54
+ export {
55
+ l as checkChanges,
56
+ g as publishChanges
57
+ };
58
+ //# sourceMappingURL=requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requests.js","sources":["../../src/client/requests.ts"],"sourcesContent":["import z from 'zod';\n\nimport { ENDPOINT_CONFIG } from '../const';\n\nexport const publishChanges = async (\n apiUrl: string,\n): Promise<{\n success: boolean;\n message: string;\n}> => {\n try {\n const endpointUrl = apiUrl + ENDPOINT_CONFIG.publish.path;\n const response = await fetch(endpointUrl, {\n method: ENDPOINT_CONFIG.publish.method,\n });\n if (!response.ok) {\n const { success, data } = z\n .object({\n message: z.string(),\n })\n .safeParse(await response.json());\n\n throw new Error(success ? data.message : 'Failed to publish changes');\n }\n return {\n success: true,\n message: 'Changes published successfully!',\n };\n } catch (_error) {\n console.error(_error);\n const message =\n _error instanceof Error ? _error.message : 'Failed to publish changes';\n return {\n success: false,\n message: message,\n };\n }\n};\n\nexport const checkChanges = async (\n apiUrl: string,\n): Promise<\n | {\n success: true;\n hasChanges: boolean;\n }\n | {\n success: false;\n error: string;\n }\n> => {\n try {\n const endpointUrl = apiUrl + ENDPOINT_CONFIG.check.path;\n const response = await fetch(endpointUrl, {\n method: ENDPOINT_CONFIG.check.method,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n const { success, data } = z\n .object({\n message: z.string(),\n })\n .safeParse(await response.json());\n\n throw new Error(success ? data.message : 'Failed to check changes');\n }\n\n const { success, data } = z\n .object({\n hasChanges: z.boolean(),\n })\n .safeParse(await response.json());\n\n if (!success) {\n throw new Error('Failed to check changes');\n }\n\n return {\n success: true,\n hasChanges: data.hasChanges,\n };\n } catch (_error) {\n console.error(_error);\n return {\n success: false,\n error: _error instanceof Error ? _error.message : 'Unknown error',\n };\n }\n};\n"],"names":["publishChanges","apiUrl","endpointUrl","ENDPOINT_CONFIG","response","success","data","z","_error","checkChanges"],"mappings":";;AAIO,MAAMA,IAAiB,OAC5BC,MAII;AACJ,MAAI;AACF,UAAMC,IAAcD,IAASE,EAAgB,QAAQ,MAC/CC,IAAW,MAAM,MAAMF,GAAa;AAAA,MACxC,QAAQC,EAAgB,QAAQ;AAAA,IAAA,CACjC;AACD,QAAI,CAACC,EAAS,IAAI;AAChB,YAAM,EAAE,SAAAC,GAAS,MAAAC,MAASC,EACvB,OAAO;AAAA,QACN,SAASA,EAAE,OAAA;AAAA,MAAO,CACnB,EACA,UAAU,MAAMH,EAAS,MAAM;AAElC,YAAM,IAAI,MAAMC,IAAUC,EAAK,UAAU,2BAA2B;AAAA,IACtE;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,IAAA;AAAA,EAEb,SAASE,GAAQ;AACf,mBAAQ,MAAMA,CAAM,GAGb;AAAA,MACL,SAAS;AAAA,MACT,SAHAA,aAAkB,QAAQA,EAAO,UAAU;AAAA,IAG3C;AAAA,EAEJ;AACF,GAEaC,IAAe,OAC1BR,MAUG;AACH,MAAI;AACF,UAAMC,IAAcD,IAASE,EAAgB,MAAM,MAC7CC,IAAW,MAAM,MAAMF,GAAa;AAAA,MACxC,QAAQC,EAAgB,MAAM;AAAA,MAC9B,SAAS;AAAA,QACP,gBAAgB;AAAA,MAAA;AAAA,IAClB,CACD;AACD,QAAI,CAACC,EAAS,IAAI;AAChB,YAAM,EAAE,SAAAC,GAAS,MAAAC,EAAAA,IAASC,EACvB,OAAO;AAAA,QACN,SAASA,EAAE,OAAA;AAAA,MAAO,CACnB,EACA,UAAU,MAAMH,EAAS,MAAM;AAElC,YAAM,IAAI,MAAMC,IAAUC,EAAK,UAAU,yBAAyB;AAAA,IACpE;AAEA,UAAM,EAAE,SAAAD,GAAS,MAAAC,MAASC,EACvB,OAAO;AAAA,MACN,YAAYA,EAAE,QAAA;AAAA,IAAQ,CACvB,EACA,UAAU,MAAMH,EAAS,MAAM;AAElC,QAAI,CAACC;AACH,YAAM,IAAI,MAAM,yBAAyB;AAG3C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAYC,EAAK;AAAA,IAAA;AAAA,EAErB,SAASE,GAAQ;AACf,mBAAQ,MAAMA,CAAM,GACb;AAAA,MACL,SAAS;AAAA,MACT,OAAOA,aAAkB,QAAQA,EAAO,UAAU;AAAA,IAAA;AAAA,EAEtD;AACF;"}
@@ -0,0 +1,26 @@
1
+ export declare const PublishQueue: {
2
+ slug: string;
3
+ admin: {
4
+ hidden: true;
5
+ };
6
+ access: {
7
+ read: ({ req }: import('payload').AccessArgs<any>) => boolean;
8
+ create: ({ req }: import('payload').AccessArgs<any>) => boolean;
9
+ update: ({ req }: import('payload').AccessArgs<any>) => boolean;
10
+ delete: ({ req }: import('payload').AccessArgs<any>) => boolean;
11
+ };
12
+ fields: ({
13
+ name: string;
14
+ type: "text";
15
+ required: true;
16
+ admin?: undefined;
17
+ } | {
18
+ name: string;
19
+ type: "text";
20
+ admin: {
21
+ description: string;
22
+ };
23
+ required?: undefined;
24
+ })[];
25
+ };
26
+ //# sourceMappingURL=collections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../src/collections.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;CAyBG,CAAC"}
@@ -0,0 +1,30 @@
1
+ const t = {
2
+ slug: "publish-queue",
3
+ admin: {
4
+ hidden: !0
5
+ },
6
+ access: {
7
+ read: ({ req: e }) => !!e.user,
8
+ create: ({ req: e }) => !!e.user,
9
+ update: ({ req: e }) => !!e.user,
10
+ delete: ({ req: e }) => !!e.user
11
+ },
12
+ fields: [
13
+ {
14
+ name: "entityType",
15
+ type: "text",
16
+ required: !0
17
+ },
18
+ {
19
+ name: "entityId",
20
+ type: "text",
21
+ admin: {
22
+ description: "ID of the changed entity"
23
+ }
24
+ }
25
+ ]
26
+ };
27
+ export {
28
+ t as PublishQueue
29
+ };
30
+ //# sourceMappingURL=collections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collections.js","sources":["../src/collections.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload';\n\nexport const PublishQueue = {\n slug: 'publish-queue',\n admin: {\n hidden: true,\n },\n access: {\n read: ({ req }) => Boolean(req.user),\n create: ({ req }) => Boolean(req.user),\n update: ({ req }) => Boolean(req.user),\n delete: ({ req }) => Boolean(req.user),\n },\n fields: [\n {\n name: 'entityType',\n type: 'text',\n required: true,\n },\n {\n name: 'entityId',\n type: 'text',\n admin: {\n description: 'ID of the changed entity',\n },\n },\n ],\n} satisfies CollectionConfig;\n"],"names":["PublishQueue","req"],"mappings":"AAEO,MAAMA,IAAe;AAAA,EAC1B,MAAM;AAAA,EACN,OAAO;AAAA,IACL,QAAQ;AAAA,EAAA;AAAA,EAEV,QAAQ;AAAA,IACN,MAAM,CAAC,EAAE,KAAAC,QAAU,EAAQA,EAAI;AAAA,IAC/B,QAAQ,CAAC,EAAE,KAAAA,QAAU,EAAQA,EAAI;AAAA,IACjC,QAAQ,CAAC,EAAE,KAAAA,QAAU,EAAQA,EAAI;AAAA,IACjC,QAAQ,CAAC,EAAE,KAAAA,QAAU,EAAQA,EAAI;AAAA,EAAI;AAAA,EAEvC,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IAAA;AAAA,IAEZ;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,QACL,aAAa;AAAA,MAAA;AAAA,IACf;AAAA,EACF;AAEJ;"}
@@ -0,0 +1,13 @@
1
+ import { CollectionOperation } from './types';
2
+ export declare const ENDPOINT_CONFIG: {
3
+ publish: {
4
+ path: string;
5
+ method: "post";
6
+ };
7
+ check: {
8
+ path: string;
9
+ method: "get";
10
+ };
11
+ };
12
+ export declare const DEFAULT_OPERATIONS: CollectionOperation[];
13
+ //# sourceMappingURL=const.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../src/const.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD,eAAO,MAAM,eAAe;;;;;;;;;CASiC,CAAC;AAE9D,eAAO,MAAM,kBAAkB,EAAE,mBAAmB,EAInD,CAAC"}
package/dist/const.js ADDED
@@ -0,0 +1,19 @@
1
+ const e = {
2
+ publish: {
3
+ path: "/cache-plugin/publish",
4
+ method: "post"
5
+ },
6
+ check: {
7
+ path: "/cache-plugin/check",
8
+ method: "get"
9
+ }
10
+ }, t = [
11
+ "create",
12
+ "update",
13
+ "delete"
14
+ ];
15
+ export {
16
+ t as DEFAULT_OPERATIONS,
17
+ e as ENDPOINT_CONFIG
18
+ };
19
+ //# sourceMappingURL=const.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"const.js","sources":["../src/const.ts"],"sourcesContent":["import type { Endpoint } from 'payload';\n\nimport type { CollectionOperation } from './types';\n\nexport const ENDPOINT_CONFIG = {\n publish: {\n path: '/cache-plugin/publish',\n method: 'post',\n },\n check: {\n path: '/cache-plugin/check',\n method: 'get',\n },\n} satisfies Record<string, Pick<Endpoint, 'path' | 'method'>>;\n\nexport const DEFAULT_OPERATIONS: CollectionOperation[] = [\n 'create',\n 'update',\n 'delete',\n];\n"],"names":["ENDPOINT_CONFIG","DEFAULT_OPERATIONS"],"mappings":"AAIO,MAAMA,IAAkB;AAAA,EAC7B,SAAS;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAAA,EAEV,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EAAA;AAEZ,GAEaC,IAA4C;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AACF;"}
@@ -0,0 +1,3 @@
1
+ import { Endpoint } from 'payload';
2
+ export declare const checkEndpoint: Endpoint;
3
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/endpoints/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAC;AAIxD,eAAO,MAAM,aAAa,EAAE,QAc3B,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { ENDPOINT_CONFIG as n } from "../const.js";
2
+ const c = {
3
+ ...n.check,
4
+ handler: async ({ payload: o }) => {
5
+ const { totalDocs: t } = await o.count({
6
+ collection: "publish-queue"
7
+ });
8
+ return Response.json(
9
+ {
10
+ hasChanges: t > 0
11
+ },
12
+ { status: 200 }
13
+ );
14
+ }
15
+ };
16
+ export {
17
+ c as checkEndpoint
18
+ };
19
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sources":["../../src/endpoints/check.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload';\n\nimport { ENDPOINT_CONFIG } from '../const';\n\nexport const checkEndpoint: Endpoint = {\n ...ENDPOINT_CONFIG.check,\n handler: async ({ payload }: PayloadRequest) => {\n const { totalDocs } = await payload.count({\n collection: 'publish-queue',\n });\n\n return Response.json(\n {\n hasChanges: totalDocs > 0,\n },\n { status: 200 },\n );\n },\n};\n"],"names":["checkEndpoint","ENDPOINT_CONFIG","payload","totalDocs"],"mappings":";AAIO,MAAMA,IAA0B;AAAA,EACrC,GAAGC,EAAgB;AAAA,EACnB,SAAS,OAAO,EAAE,SAAAC,QAA8B;AAC9C,UAAM,EAAE,WAAAC,EAAA,IAAc,MAAMD,EAAQ,MAAM;AAAA,MACxC,YAAY;AAAA,IAAA,CACb;AAED,WAAO,SAAS;AAAA,MACd;AAAA,QACE,YAAYC,IAAY;AAAA,MAAA;AAAA,MAE1B,EAAE,QAAQ,IAAA;AAAA,IAAI;AAAA,EAElB;AACF;"}
@@ -0,0 +1,4 @@
1
+ import { Endpoint } from 'payload';
2
+ import { SmartCachePluginConfig } from '../index';
3
+ export declare const createPublishChangesEndpoint: (publishHandler: SmartCachePluginConfig["publishHandler"]) => Endpoint;
4
+ //# sourceMappingURL=publish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/endpoints/publish.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,QAAQ,EAAkB,MAAM,SAAS,CAAC;AAExE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAMvD,eAAO,MAAM,4BAA4B,mBACvB,sBAAsB,CAAC,gBAAgB,CAAC,KACvD,QAwGD,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { ENDPOINT_CONFIG as w } from "../const.js";
2
+ import { revalidateCache as y } from "../server/revalidate.js";
3
+ import { CollectionChanges as b } from "../utils/CollectionChanges.js";
4
+ import { createDependencyGraph as I } from "../utils/dependency-graph.js";
5
+ const D = (h) => ({
6
+ ...w.publish,
7
+ handler: async ({ payload: i }) => {
8
+ const { docs: s } = await i.find({
9
+ collection: "publish-queue",
10
+ sort: "-updatedAt"
11
+ });
12
+ if (s.length === 0)
13
+ return new Response("No changes to publish", { status: 200 });
14
+ const c = /* @__PURE__ */ new Set(), n = new b();
15
+ n.initialize(s);
16
+ const p = I(i);
17
+ for (const t of s)
18
+ await i.delete({
19
+ collection: "publish-queue",
20
+ id: t.id
21
+ });
22
+ async function l(t, a, r) {
23
+ const d = p.getDependants(t);
24
+ if (d.length !== 0)
25
+ for (const e of d) {
26
+ if (e.entity.type === "global") {
27
+ c.add(e.entity.slug);
28
+ continue;
29
+ }
30
+ if (r.has(e.entity.slug)) continue;
31
+ const u = /* @__PURE__ */ new Map();
32
+ for (const o of e.fields) {
33
+ const { docs: m } = await i.find({
34
+ collection: e.entity.slug,
35
+ where: {
36
+ [o.field]: {
37
+ in: a
38
+ }
39
+ }
40
+ });
41
+ for (const g of m)
42
+ u.set(g.id.toString(), g);
43
+ }
44
+ const f = Array.from(u.values());
45
+ if (r.add(e.entity.slug), f.length !== 0) {
46
+ for (const o of f)
47
+ n.addItem(e.entity.slug, o.id.toString());
48
+ return l(
49
+ e.entity.slug,
50
+ f.map((o) => o.id.toString()),
51
+ r
52
+ );
53
+ }
54
+ }
55
+ }
56
+ for (const [t, a] of n.entries())
57
+ await l(t, Array.from(a), /* @__PURE__ */ new Set());
58
+ for (const t of n.keys())
59
+ c.add(t);
60
+ for (const t of c)
61
+ await y(t);
62
+ return await h?.(n.serialize()), new Response("OK");
63
+ }
64
+ });
65
+ export {
66
+ D as createPublishChangesEndpoint
67
+ };
68
+ //# sourceMappingURL=publish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish.js","sources":["../../src/endpoints/publish.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, PayloadRequest } from 'payload';\nimport { ENDPOINT_CONFIG } from '../const';\nimport type { SmartCachePluginConfig } from '../index';\nimport { revalidateCache } from '../server/revalidate';\nimport type { EntitySlug, PublishQueue } from '../types';\nimport { CollectionChanges } from '../utils/CollectionChanges';\nimport { createDependencyGraph } from '../utils/dependency-graph';\n\nexport const createPublishChangesEndpoint = (\n publishHandler: SmartCachePluginConfig['publishHandler'],\n): Endpoint => ({\n ...ENDPOINT_CONFIG.publish,\n handler: async ({ payload }: PayloadRequest) => {\n const { docs: changesToPublish } = await payload.find({\n collection: 'publish-queue',\n sort: '-updatedAt',\n });\n\n if (changesToPublish.length === 0)\n return new Response('No changes to publish', { status: 200 });\n\n const tagsToInvalidate = new Set<EntitySlug>();\n const collectionChanges = new CollectionChanges();\n\n collectionChanges.initialize(changesToPublish as PublishQueue[]);\n\n const graph = createDependencyGraph(payload);\n\n for (const change of changesToPublish) {\n // Delete the change record (it's been processed)\n await payload.delete({\n collection: 'publish-queue',\n id: change.id,\n });\n }\n\n async function trackAffectedItems(\n collection: CollectionSlug,\n ids: string[],\n visited: Set<string>,\n ): Promise<void> {\n const dependents = graph.getDependants(collection);\n\n if (dependents.length === 0) return;\n\n for (const dependent of dependents) {\n if (dependent.entity.type === 'global') {\n // This causes too many revalidations, but maybe we can skip this completely?\n tagsToInvalidate.add(dependent.entity.slug);\n continue;\n }\n\n if (visited.has(dependent.entity.slug)) continue;\n\n // Query each field separately to avoid duplicate table alias errors\n // when multiple fields map to the same table (e.g., highlights.split-image-text.image\n // and architecture.split-image-text.image both use projects_blocks_split_image_text)\n const allAffectedItems = new Map<\n string,\n {\n id: string | number;\n }\n >();\n\n for (const field of dependent.fields) {\n const { docs } = await payload.find({\n collection: dependent.entity.slug,\n where: {\n [field.field]: {\n in: ids,\n },\n },\n });\n\n // Use a Map keyed by ID to deduplicate results\n for (const item of docs) {\n allAffectedItems.set(item.id.toString(), item);\n }\n }\n\n const affectedItems = Array.from(allAffectedItems.values());\n\n visited.add(dependent.entity.slug);\n\n if (affectedItems.length === 0) continue;\n\n for (const item of affectedItems) {\n collectionChanges.addItem(dependent.entity.slug, item.id.toString());\n }\n\n return trackAffectedItems(\n dependent.entity.slug,\n affectedItems.map((item) => item.id.toString()),\n visited,\n );\n }\n }\n\n for (const [collection, ids] of collectionChanges.entries()) {\n await trackAffectedItems(collection, Array.from(ids), new Set<string>());\n }\n\n for (const entity of collectionChanges.keys()) {\n tagsToInvalidate.add(entity);\n }\n\n for (const tag of tagsToInvalidate) {\n await revalidateCache(tag);\n }\n\n await publishHandler?.(collectionChanges.serialize());\n\n return new Response('OK');\n },\n});\n"],"names":["createPublishChangesEndpoint","publishHandler","ENDPOINT_CONFIG","payload","changesToPublish","tagsToInvalidate","collectionChanges","CollectionChanges","graph","createDependencyGraph","change","trackAffectedItems","collection","ids","visited","dependents","dependent","allAffectedItems","field","docs","item","affectedItems","entity","tag","revalidateCache"],"mappings":";;;;AAQO,MAAMA,IAA+B,CAC1CC,OACc;AAAA,EACd,GAAGC,EAAgB;AAAA,EACnB,SAAS,OAAO,EAAE,SAAAC,QAA8B;AAC9C,UAAM,EAAE,MAAMC,EAAA,IAAqB,MAAMD,EAAQ,KAAK;AAAA,MACpD,YAAY;AAAA,MACZ,MAAM;AAAA,IAAA,CACP;AAED,QAAIC,EAAiB,WAAW;AAC9B,aAAO,IAAI,SAAS,yBAAyB,EAAE,QAAQ,KAAK;AAE9D,UAAMC,wBAAuB,IAAA,GACvBC,IAAoB,IAAIC,EAAA;AAE9B,IAAAD,EAAkB,WAAWF,CAAkC;AAE/D,UAAMI,IAAQC,EAAsBN,CAAO;AAE3C,eAAWO,KAAUN;AAEnB,YAAMD,EAAQ,OAAO;AAAA,QACnB,YAAY;AAAA,QACZ,IAAIO,EAAO;AAAA,MAAA,CACZ;AAGH,mBAAeC,EACbC,GACAC,GACAC,GACe;AACf,YAAMC,IAAaP,EAAM,cAAcI,CAAU;AAEjD,UAAIG,EAAW,WAAW;AAE1B,mBAAWC,KAAaD,GAAY;AAClC,cAAIC,EAAU,OAAO,SAAS,UAAU;AAEtC,YAAAX,EAAiB,IAAIW,EAAU,OAAO,IAAI;AAC1C;AAAA,UACF;AAEA,cAAIF,EAAQ,IAAIE,EAAU,OAAO,IAAI,EAAG;AAKxC,gBAAMC,wBAAuB,IAAA;AAO7B,qBAAWC,KAASF,EAAU,QAAQ;AACpC,kBAAM,EAAE,MAAAG,EAAA,IAAS,MAAMhB,EAAQ,KAAK;AAAA,cAClC,YAAYa,EAAU,OAAO;AAAA,cAC7B,OAAO;AAAA,gBACL,CAACE,EAAM,KAAK,GAAG;AAAA,kBACb,IAAIL;AAAA,gBAAA;AAAA,cACN;AAAA,YACF,CACD;AAGD,uBAAWO,KAAQD;AACjB,cAAAF,EAAiB,IAAIG,EAAK,GAAG,SAAA,GAAYA,CAAI;AAAA,UAEjD;AAEA,gBAAMC,IAAgB,MAAM,KAAKJ,EAAiB,QAAQ;AAI1D,cAFAH,EAAQ,IAAIE,EAAU,OAAO,IAAI,GAE7BK,EAAc,WAAW,GAE7B;AAAA,uBAAWD,KAAQC;AACjB,cAAAf,EAAkB,QAAQU,EAAU,OAAO,MAAMI,EAAK,GAAG,UAAU;AAGrE,mBAAOT;AAAA,cACLK,EAAU,OAAO;AAAA,cACjBK,EAAc,IAAI,CAACD,MAASA,EAAK,GAAG,UAAU;AAAA,cAC9CN;AAAA,YAAA;AAAA;AAAA,QAEJ;AAAA,IACF;AAEA,eAAW,CAACF,GAAYC,CAAG,KAAKP,EAAkB;AAChD,YAAMK,EAAmBC,GAAY,MAAM,KAAKC,CAAG,GAAG,oBAAI,KAAa;AAGzE,eAAWS,KAAUhB,EAAkB;AACrC,MAAAD,EAAiB,IAAIiB,CAAM;AAG7B,eAAWC,KAAOlB;AAChB,YAAMmB,EAAgBD,CAAG;AAG3B,iBAAMtB,IAAiBK,EAAkB,WAAW,GAE7C,IAAI,SAAS,IAAI;AAAA,EAC1B;AACF;"}
@@ -0,0 +1,2 @@
1
+ export { PublishButton } from '../client/PublishButton';
2
+ //# sourceMappingURL=rsc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../src/exports/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { PublishButton as r } from "../client/PublishButton.js";
2
+ export {
3
+ r as PublishButton
4
+ };
5
+ //# sourceMappingURL=rsc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rsc.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,10 @@
1
+ import { CollectionSlug, Plugin } from 'payload';
2
+ import { ChangedDocuments, CollectionOperation } from './types';
3
+ export interface SmartCachePluginConfig {
4
+ collections?: Partial<Record<CollectionSlug, CollectionOperation[] | false>>;
5
+ publishHandler?: (changes: ChangedDocuments) => void | Promise<void>;
6
+ }
7
+ export declare const smartCachePlugin: ({ collections: collectionOperations, publishHandler, }: SmartCachePluginConfig) => Plugin;
8
+ export { createRequestHandler } from './server/request';
9
+ export type { ChangedDocuments } from './types';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAStD,OAAO,KAAK,EACV,gBAAgB,EAEhB,mBAAmB,EACpB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,mBAAmB,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IAC7E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAED,eAAO,MAAM,gBAAgB,2DAIxB,sBAAsB,KAAG,MAwD3B,CAAC;AAEJ,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ import { PublishQueue as i } from "./collections.js";
2
+ import { DEFAULT_OPERATIONS as u } from "./const.js";
3
+ import { checkEndpoint as l } from "./endpoints/check.js";
4
+ import { createPublishChangesEndpoint as m } from "./endpoints/publish.js";
5
+ import { trackCollectionChange as d, trackCollectionDelete as c } from "./server/track-changes.js";
6
+ import { createRequestHandler as x } from "./server/request.js";
7
+ const E = ({
8
+ collections: n = {},
9
+ publishHandler: h
10
+ }) => (e) => (e.admin ??= {}, e.admin.components ??= {}, e.admin.components.actions ??= [], e.admin.components.actions.push({
11
+ path: "payload-smart-cache/rsc#PublishButton"
12
+ }), e.collections ??= [], e.collections.forEach((t) => {
13
+ const s = n[t.slug] ?? u;
14
+ if (s === !1 || s.length === 0) return;
15
+ t.hooks ??= {};
16
+ const { hasDeleteOperation: p, changeOperations: o } = s.reduce(
17
+ (r, a) => (a === "delete" ? r.hasDeleteOperation = !0 : r.changeOperations.push(a), r),
18
+ {
19
+ hasDeleteOperation: !1,
20
+ changeOperations: []
21
+ }
22
+ );
23
+ o.length > 0 && (t.hooks.afterChange ??= [], t.hooks.afterChange.push(
24
+ d(o)
25
+ )), p && (t.hooks.afterDelete ??= [], t.hooks.afterDelete.push(c));
26
+ }), e.collections.push(i), e.endpoints ??= [], e.endpoints.push(m(h)), e.endpoints.push(l), e);
27
+ export {
28
+ x as createRequestHandler,
29
+ E as smartCachePlugin
30
+ };
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { CollectionSlug, Plugin } from 'payload';\nimport { PublishQueue } from './collections';\nimport { DEFAULT_OPERATIONS } from './const';\nimport { checkEndpoint } from './endpoints/check';\nimport { createPublishChangesEndpoint } from './endpoints/publish';\nimport {\n trackCollectionChange,\n trackCollectionDelete,\n} from './server/track-changes';\nimport type {\n ChangedDocuments,\n CollectionChangeOperation,\n CollectionOperation,\n} from './types';\n\nexport interface SmartCachePluginConfig {\n collections?: Partial<Record<CollectionSlug, CollectionOperation[] | false>>;\n publishHandler?: (changes: ChangedDocuments) => void | Promise<void>;\n}\n\nexport const smartCachePlugin =\n ({\n collections: collectionOperations = {},\n publishHandler,\n }: SmartCachePluginConfig): Plugin =>\n (config) => {\n config.admin ??= {};\n config.admin.components ??= {};\n config.admin.components.actions ??= [];\n config.admin.components.actions.push({\n path: 'payload-smart-cache/rsc#PublishButton',\n });\n\n config.collections ??= [];\n config.collections.forEach((collection) => {\n const operations =\n collectionOperations[collection.slug as CollectionSlug] ??\n DEFAULT_OPERATIONS;\n if (operations === false) return;\n if (operations.length === 0) return;\n\n collection.hooks ??= {};\n\n const { hasDeleteOperation, changeOperations } = operations.reduce<{\n hasDeleteOperation: boolean;\n changeOperations: CollectionChangeOperation[];\n }>(\n (acc, operation) => {\n if (operation === 'delete') {\n acc.hasDeleteOperation = true;\n } else {\n acc.changeOperations.push(operation);\n }\n return acc;\n },\n {\n hasDeleteOperation: false,\n changeOperations: [],\n },\n );\n\n if (changeOperations.length > 0) {\n collection.hooks.afterChange ??= [];\n collection.hooks.afterChange.push(\n trackCollectionChange(changeOperations),\n );\n }\n if (hasDeleteOperation) {\n collection.hooks.afterDelete ??= [];\n collection.hooks.afterDelete.push(trackCollectionDelete);\n }\n });\n\n config.collections.push(PublishQueue);\n\n config.endpoints ??= [];\n config.endpoints.push(createPublishChangesEndpoint(publishHandler));\n config.endpoints.push(checkEndpoint);\n\n return config;\n };\n\nexport { createRequestHandler } from './server/request';\nexport type { ChangedDocuments } from './types';\n"],"names":["smartCachePlugin","collectionOperations","publishHandler","config","collection","operations","DEFAULT_OPERATIONS","hasDeleteOperation","changeOperations","acc","operation","trackCollectionChange","trackCollectionDelete","PublishQueue","createPublishChangesEndpoint","checkEndpoint"],"mappings":";;;;;;AAoBO,MAAMA,IACX,CAAC;AAAA,EACC,aAAaC,IAAuB,CAAA;AAAA,EACpC,gBAAAC;AACF,MACA,CAACC,OACCA,EAAO,UAAU,CAAA,GACjBA,EAAO,MAAM,eAAe,CAAA,GAC5BA,EAAO,MAAM,WAAW,YAAY,CAAA,GACpCA,EAAO,MAAM,WAAW,QAAQ,KAAK;AAAA,EACnC,MAAM;AAAA,CACP,GAEDA,EAAO,gBAAgB,CAAA,GACvBA,EAAO,YAAY,QAAQ,CAACC,MAAe;AACzC,QAAMC,IACJJ,EAAqBG,EAAW,IAAsB,KACtDE;AAEF,MADID,MAAe,MACfA,EAAW,WAAW,EAAG;AAE7B,EAAAD,EAAW,UAAU,CAAA;AAErB,QAAM,EAAE,oBAAAG,GAAoB,kBAAAC,EAAA,IAAqBH,EAAW;AAAA,IAI1D,CAACI,GAAKC,OACAA,MAAc,WAChBD,EAAI,qBAAqB,KAEzBA,EAAI,iBAAiB,KAAKC,CAAS,GAE9BD;AAAA,IAET;AAAA,MACE,oBAAoB;AAAA,MACpB,kBAAkB,CAAA;AAAA,IAAC;AAAA,EACrB;AAGF,EAAID,EAAiB,SAAS,MAC5BJ,EAAW,MAAM,gBAAgB,CAAA,GACjCA,EAAW,MAAM,YAAY;AAAA,IAC3BO,EAAsBH,CAAgB;AAAA,EAAA,IAGtCD,MACFH,EAAW,MAAM,gBAAgB,CAAA,GACjCA,EAAW,MAAM,YAAY,KAAKQ,CAAqB;AAE3D,CAAC,GAEDT,EAAO,YAAY,KAAKU,CAAY,GAEpCV,EAAO,cAAc,CAAA,GACrBA,EAAO,UAAU,KAAKW,EAA6BZ,CAAc,CAAC,GAClEC,EAAO,UAAU,KAAKY,CAAa,GAE5BZ;"}
@@ -0,0 +1,3 @@
1
+ import { EntitySlug } from '../types';
2
+ export declare const createRequestHandler: <Data, Inputs extends unknown[]>(handler: (...inputs: Inputs) => Promise<Data>, tags?: EntitySlug[]) => ((...inputs: Inputs) => Promise<Data>);
3
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/server/request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,oBAAoB,GAAI,IAAI,EAAE,MAAM,SAAS,OAAO,EAAE,WACxD,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,SACtC,UAAU,EAAE,KAClB,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAIpC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { unstable_cache as t } from "next/cache";
2
+ const o = (e, a) => t(e, void 0, {
3
+ tags: a,
4
+ revalidate: !1
5
+ });
6
+ export {
7
+ o as createRequestHandler
8
+ };
9
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sources":["../../src/server/request.ts"],"sourcesContent":["import { unstable_cache } from 'next/cache';\n\nimport type { EntitySlug } from '../types';\n\nexport const createRequestHandler = <Data, Inputs extends unknown[]>(\n handler: (...inputs: Inputs) => Promise<Data>,\n tags?: EntitySlug[],\n): ((...inputs: Inputs) => Promise<Data>) =>\n unstable_cache(handler, undefined, {\n tags,\n revalidate: false,\n });\n"],"names":["createRequestHandler","handler","tags","unstable_cache"],"mappings":";AAIO,MAAMA,IAAuB,CAClCC,GACAC,MAEAC,EAAeF,GAAS,QAAW;AAAA,EACjC,MAAAC;AAAA,EACA,YAAY;AACd,CAAC;"}
@@ -0,0 +1,3 @@
1
+ import { EntitySlug } from '../types';
2
+ export declare const revalidateCache: (tag: EntitySlug) => Promise<void>;
3
+ //# sourceMappingURL=revalidate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/server/revalidate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,eAAe,QAAe,UAAU,kBAGpD,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use server";
2
+ import { revalidateTag as o } from "next/cache";
3
+ const a = async (e) => {
4
+ console.log("INVALIDATE CACHE", e), o(e);
5
+ };
6
+ export {
7
+ a as revalidateCache
8
+ };
9
+ //# sourceMappingURL=revalidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revalidate.js","sources":["../../src/server/revalidate.ts"],"sourcesContent":["'use server';\n\nimport { revalidateTag } from 'next/cache';\n\nimport type { EntitySlug } from '../types';\n\nexport const revalidateCache = async (tag: EntitySlug) => {\n console.log('INVALIDATE CACHE', tag);\n revalidateTag(tag);\n};\n"],"names":["revalidateCache","tag","revalidateTag"],"mappings":";;AAMO,MAAMA,IAAkB,OAAOC,MAAoB;AACxD,UAAQ,IAAI,oBAAoBA,CAAG,GACnCC,EAAcD,CAAG;AACnB;"}
@@ -0,0 +1,10 @@
1
+ import { CollectionAfterChangeHook, CollectionAfterDeleteHook, GlobalAfterChangeHook } from 'payload';
2
+ import { CollectionChangeOperation } from '../types';
3
+ export declare const trackCollectionChange: (operations: CollectionChangeOperation[]) => CollectionAfterChangeHook<{
4
+ id: string | number;
5
+ }>;
6
+ export declare const trackCollectionDelete: CollectionAfterDeleteHook<{
7
+ id: string | number;
8
+ }>;
9
+ export declare const trackGlobalChange: GlobalAfterChangeHook;
10
+ //# sourceMappingURL=track-changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track-changes.d.ts","sourceRoot":"","sources":["../../src/server/track-changes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D,eAAO,MAAM,qBAAqB,eAElB,yBAAyB,EAAE,KACtC,yBAAyB,CAAC;IAC3B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,CAuCA,CAAC;AAEJ,eAAO,MAAM,qBAAqB,EAAE,yBAAyB,CAAC;IAC5D,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,CAoCA,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,qBAmC/B,CAAC"}
@@ -0,0 +1,59 @@
1
+ const s = (e) => async ({ req: { payload: t }, doc: c, collection: n, operation: a }) => {
2
+ if (!e.includes(a)) return;
3
+ const i = n.slug, l = c.id.toString(), {
4
+ docs: [u]
5
+ } = await t.find({
6
+ collection: "publish-queue",
7
+ where: {
8
+ and: [
9
+ { entityType: { equals: i } },
10
+ { entityId: { equals: l } }
11
+ ]
12
+ },
13
+ limit: 1
14
+ });
15
+ u ? await t.update({
16
+ collection: "publish-queue",
17
+ id: u.id,
18
+ data: {
19
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
20
+ }
21
+ }) : await t.create({
22
+ collection: "publish-queue",
23
+ data: {
24
+ entityType: i,
25
+ entityId: l
26
+ }
27
+ });
28
+ }, o = async ({ req: { payload: e }, doc: t, collection: c }) => {
29
+ const n = c.slug, a = t.id.toString(), {
30
+ docs: [i]
31
+ } = await e.find({
32
+ collection: "publish-queue",
33
+ where: {
34
+ and: [
35
+ { entityType: { equals: n } },
36
+ { entityId: { equals: a } }
37
+ ]
38
+ },
39
+ limit: 1
40
+ });
41
+ i ? await e.update({
42
+ collection: "publish-queue",
43
+ id: i.id,
44
+ data: {
45
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
46
+ }
47
+ }) : await e.create({
48
+ collection: "publish-queue",
49
+ data: {
50
+ entityType: n,
51
+ entityId: a
52
+ }
53
+ });
54
+ };
55
+ export {
56
+ s as trackCollectionChange,
57
+ o as trackCollectionDelete
58
+ };
59
+ //# sourceMappingURL=track-changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"track-changes.js","sources":["../../src/server/track-changes.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionAfterDeleteHook,\n GlobalAfterChangeHook,\n} from 'payload';\n\nimport type { CollectionChangeOperation } from '../types';\n\nexport const trackCollectionChange =\n (\n operations: CollectionChangeOperation[],\n ): CollectionAfterChangeHook<{\n id: string | number;\n }> =>\n async ({ req: { payload }, doc, collection, operation }) => {\n if (!operations.includes(operation)) return;\n\n const entityType = collection.slug;\n const entityId = doc.id.toString();\n\n // Check if this entity already has a pending change\n const {\n docs: [existingChange],\n } = await payload.find({\n collection: 'publish-queue',\n where: {\n and: [\n { entityType: { equals: entityType } },\n { entityId: { equals: entityId } },\n ],\n },\n limit: 1,\n });\n\n if (existingChange) {\n // Update existing change record\n await payload.update({\n collection: 'publish-queue',\n id: existingChange.id,\n data: {\n updatedAt: new Date().toISOString(),\n },\n });\n } else {\n await payload.create({\n collection: 'publish-queue',\n data: {\n entityType,\n entityId,\n },\n });\n }\n };\n\nexport const trackCollectionDelete: CollectionAfterDeleteHook<{\n id: string | number;\n}> = async ({ req: { payload }, doc, collection }) => {\n const entityType = collection.slug;\n const entityId = doc.id.toString();\n\n // Check if this entity already has a pending change\n const {\n docs: [existingChange],\n } = await payload.find({\n collection: 'publish-queue',\n where: {\n and: [\n { entityType: { equals: entityType } },\n { entityId: { equals: entityId } },\n ],\n },\n limit: 1,\n });\n\n if (existingChange) {\n // Update existing change record\n await payload.update({\n collection: 'publish-queue',\n id: existingChange.id,\n data: {\n updatedAt: new Date().toISOString(),\n },\n });\n } else {\n await payload.create({\n collection: 'publish-queue',\n data: {\n entityType,\n entityId,\n },\n });\n }\n};\n\nexport const trackGlobalChange: GlobalAfterChangeHook = async ({\n req: { payload },\n global,\n}) => {\n const entityType = global.slug;\n\n // Check if this entity already has a pending change\n const {\n docs: [existingChange],\n } = await payload.find({\n collection: 'publish-queue',\n where: {\n and: [{ entityType: { equals: entityType } }],\n },\n limit: 1,\n });\n\n if (existingChange) {\n // Update existing change record\n await payload.update({\n collection: 'publish-queue',\n id: existingChange.id,\n data: {\n updatedAt: new Date().toISOString(),\n },\n });\n } else {\n // Create new change record\n await payload.create({\n collection: 'publish-queue',\n data: {\n entityType,\n },\n });\n }\n};\n"],"names":["trackCollectionChange","operations","payload","doc","collection","operation","entityType","entityId","existingChange","trackCollectionDelete"],"mappings":"AAQO,MAAMA,IACX,CACEC,MAIF,OAAO,EAAE,KAAK,EAAE,SAAAC,EAAA,GAAW,KAAAC,GAAK,YAAAC,GAAY,WAAAC,QAAgB;AAC1D,MAAI,CAACJ,EAAW,SAASI,CAAS,EAAG;AAErC,QAAMC,IAAaF,EAAW,MACxBG,IAAWJ,EAAI,GAAG,SAAA,GAGlB;AAAA,IACJ,MAAM,CAACK,CAAc;AAAA,EAAA,IACnB,MAAMN,EAAQ,KAAK;AAAA,IACrB,YAAY;AAAA,IACZ,OAAO;AAAA,MACL,KAAK;AAAA,QACH,EAAE,YAAY,EAAE,QAAQI,IAAW;AAAA,QACnC,EAAE,UAAU,EAAE,QAAQC,IAAS;AAAA,MAAE;AAAA,IACnC;AAAA,IAEF,OAAO;AAAA,EAAA,CACR;AAED,EAAIC,IAEF,MAAMN,EAAQ,OAAO;AAAA,IACnB,YAAY;AAAA,IACZ,IAAIM,EAAe;AAAA,IACnB,MAAM;AAAA,MACJ,YAAW,oBAAI,KAAA,GAAO,YAAA;AAAA,IAAY;AAAA,EACpC,CACD,IAED,MAAMN,EAAQ,OAAO;AAAA,IACnB,YAAY;AAAA,IACZ,MAAM;AAAA,MACJ,YAAAI;AAAA,MACA,UAAAC;AAAA,IAAA;AAAA,EACF,CACD;AAEL,GAEWE,IAER,OAAO,EAAE,KAAK,EAAE,SAAAP,EAAA,GAAW,KAAAC,GAAK,YAAAC,QAAiB;AACpD,QAAME,IAAaF,EAAW,MACxBG,IAAWJ,EAAI,GAAG,SAAA,GAGlB;AAAA,IACJ,MAAM,CAACK,CAAc;AAAA,EAAA,IACnB,MAAMN,EAAQ,KAAK;AAAA,IACrB,YAAY;AAAA,IACZ,OAAO;AAAA,MACL,KAAK;AAAA,QACH,EAAE,YAAY,EAAE,QAAQI,IAAW;AAAA,QACnC,EAAE,UAAU,EAAE,QAAQC,IAAS;AAAA,MAAE;AAAA,IACnC;AAAA,IAEF,OAAO;AAAA,EAAA,CACR;AAED,EAAIC,IAEF,MAAMN,EAAQ,OAAO;AAAA,IACnB,YAAY;AAAA,IACZ,IAAIM,EAAe;AAAA,IACnB,MAAM;AAAA,MACJ,YAAW,oBAAI,KAAA,GAAO,YAAA;AAAA,IAAY;AAAA,EACpC,CACD,IAED,MAAMN,EAAQ,OAAO;AAAA,IACnB,YAAY;AAAA,IACZ,MAAM;AAAA,MACJ,YAAAI;AAAA,MACA,UAAAC;AAAA,IAAA;AAAA,EACF,CACD;AAEL;"}
@@ -0,0 +1,16 @@
1
+ import { CollectionSlug, GlobalSlug } from 'payload';
2
+ export type EntitySlug = CollectionSlug | GlobalSlug;
3
+ export type CollectionOperation = CollectionChangeOperation | 'delete';
4
+ export type CollectionChangeOperation = 'create' | 'update';
5
+ type CollectionId = string;
6
+ export type ChangedDocuments = Partial<Record<CollectionSlug, CollectionId[]>>;
7
+ export interface PublishQueue {
8
+ id: number;
9
+ entityType: string;
10
+ /**
11
+ * ID of the changed entity
12
+ */
13
+ entityId?: string | null;
14
+ }
15
+ export {};
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,GAAG,QAAQ,CAAC;AAEvE,MAAM,MAAM,yBAAyB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5D,KAAK,YAAY,GAAG,MAAM,CAAC;AAE3B,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAG/E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B"}
@@ -0,0 +1,8 @@
1
+ import { CollectionSlug } from 'payload';
2
+ import { PublishQueue } from '../types';
3
+ export declare class CollectionChanges extends Map<CollectionSlug, Set<string>> {
4
+ initialize(changes: PublishQueue[]): void;
5
+ addItem(collection: CollectionSlug, id: string): void;
6
+ serialize(): Partial<Record<CollectionSlug, string[]>>;
7
+ }
8
+ //# sourceMappingURL=CollectionChanges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionChanges.d.ts","sourceRoot":"","sources":["../../src/utils/CollectionChanges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,qBAAa,iBAAkB,SAAQ,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrE,UAAU,CAAC,OAAO,EAAE,YAAY,EAAE;IAQlC,OAAO,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM;IAS9C,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;CAOvD"}
@@ -0,0 +1,20 @@
1
+ class n extends Map {
2
+ initialize(e) {
3
+ for (const t of e)
4
+ typeof t.entityId == "string" && this.addItem(t.entityType, t.entityId);
5
+ }
6
+ addItem(e, t) {
7
+ const s = this.get(e);
8
+ s ? s.add(t) : this.set(e, /* @__PURE__ */ new Set([t]));
9
+ }
10
+ serialize() {
11
+ const e = {};
12
+ for (const [t, s] of this.entries())
13
+ e[t] = Array.from(s);
14
+ return e;
15
+ }
16
+ }
17
+ export {
18
+ n as CollectionChanges
19
+ };
20
+ //# sourceMappingURL=CollectionChanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CollectionChanges.js","sources":["../../src/utils/CollectionChanges.ts"],"sourcesContent":["import type { CollectionSlug } from 'payload';\n\nimport type { PublishQueue } from '../types';\n\nexport class CollectionChanges extends Map<CollectionSlug, Set<string>> {\n initialize(changes: PublishQueue[]) {\n for (const change of changes) {\n if (typeof change.entityId !== 'string') continue;\n\n this.addItem(change.entityType as CollectionSlug, change.entityId);\n }\n }\n\n addItem(collection: CollectionSlug, id: string) {\n const changedIds = this.get(collection);\n if (changedIds) {\n changedIds.add(id);\n } else {\n this.set(collection, new Set([id]));\n }\n }\n\n serialize(): Partial<Record<CollectionSlug, string[]>> {\n const result: Partial<Record<CollectionSlug, string[]>> = {};\n for (const [collection, ids] of this.entries()) {\n result[collection] = Array.from(ids);\n }\n return result;\n }\n}\n"],"names":["CollectionChanges","changes","change","collection","id","changedIds","result","ids"],"mappings":"AAIO,MAAMA,UAA0B,IAAiC;AAAA,EACtE,WAAWC,GAAyB;AAClC,eAAWC,KAAUD;AACnB,MAAI,OAAOC,EAAO,YAAa,YAE/B,KAAK,QAAQA,EAAO,YAA8BA,EAAO,QAAQ;AAAA,EAErE;AAAA,EAEA,QAAQC,GAA4BC,GAAY;AAC9C,UAAMC,IAAa,KAAK,IAAIF,CAAU;AACtC,IAAIE,IACFA,EAAW,IAAID,CAAE,IAEjB,KAAK,IAAID,GAAY,oBAAI,IAAI,CAACC,CAAE,CAAC,CAAC;AAAA,EAEtC;AAAA,EAEA,YAAuD;AACrD,UAAME,IAAoD,CAAA;AAC1D,eAAW,CAACH,GAAYI,CAAG,KAAK,KAAK;AACnC,MAAAD,EAAOH,CAAU,IAAI,MAAM,KAAKI,CAAG;AAErC,WAAOD;AAAA,EACT;AACF;"}
@@ -0,0 +1,34 @@
1
+ import { Graph } from 'graph-data-structure';
2
+ import { BasePayload, CollectionSlug, FlattenedField, GlobalSlug } from 'payload';
3
+ export declare function createDependencyGraph({ collections, globals }: BasePayload): EntitiesGraph;
4
+ interface FieldRelation {
5
+ field: string;
6
+ collection: CollectionSlug;
7
+ hasMany: boolean;
8
+ }
9
+ type StringifiedEntityReference = `collection|${CollectionSlug}` | `global|${GlobalSlug}`;
10
+ type EntityReference = {
11
+ type: 'collection';
12
+ slug: CollectionSlug;
13
+ } | {
14
+ type: 'global';
15
+ slug: GlobalSlug;
16
+ };
17
+ export declare class EntitiesGraph extends Graph<StringifiedEntityReference, {
18
+ field: string;
19
+ hasMany: boolean;
20
+ }[]> {
21
+ static parseEntityReference(entity: StringifiedEntityReference): EntityReference;
22
+ static stringifyEntityReference(entity: EntityReference): StringifiedEntityReference;
23
+ static getFieldRelations(field: FlattenedField, prevPath: string[]): FieldRelation[];
24
+ getDependants(collection: CollectionSlug): {
25
+ entity: EntityReference;
26
+ fields: {
27
+ field: string;
28
+ hasMany: boolean;
29
+ }[];
30
+ }[];
31
+ addRelations(entity: EntityReference, flattenedFields: FlattenedField[]): void;
32
+ }
33
+ export {};
34
+ //# sourceMappingURL=dependency-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,cAAc,EACd,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB,wBAAgB,qBAAqB,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,WAAW,iBAc1E;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,KAAK,0BAA0B,GAC3B,cAAc,cAAc,EAAE,GAC9B,UAAU,UAAU,EAAE,CAAC;AAE3B,KAAK,eAAe,GAChB;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,cAAc,CAAC;CACtB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;CAClB,CAAC;AAEN,qBAAa,aAAc,SAAQ,KAAK,CACtC,0BAA0B,EAC1B;IACE,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB,EAAE,CACJ;IACC,MAAM,CAAC,oBAAoB,CACzB,MAAM,EAAE,0BAA0B,GACjC,eAAe;IAqBlB,MAAM,CAAC,wBAAwB,CAC7B,MAAM,EAAE,eAAe,GACtB,0BAA0B;IAS7B,MAAM,CAAC,iBAAiB,CACtB,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAAE,GACjB,aAAa,EAAE;IAiClB,aAAa,CAAC,UAAU,EAAE,cAAc;gBAK5B,eAAe;gBACf;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,EAAE;;IAOjD,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE;CAsBxE"}
@@ -0,0 +1,101 @@
1
+ import { Graph as f } from "graph-data-structure";
2
+ import { groupBy as d } from "lodash-es";
3
+ function u({ collections: r, globals: e }) {
4
+ const o = new n();
5
+ for (const {
6
+ config: { slug: t, flattenedFields: l }
7
+ } of Object.values(r))
8
+ o.addRelations({ type: "collection", slug: t }, l);
9
+ for (const { slug: t, flattenedFields: l } of Object.values(e.config))
10
+ o.addRelations({ type: "global", slug: t }, l);
11
+ return o;
12
+ }
13
+ class n extends f {
14
+ static parseEntityReference(e) {
15
+ const [o, t] = e.split("|");
16
+ switch (o) {
17
+ case "collection":
18
+ return {
19
+ type: "collection",
20
+ slug: t
21
+ };
22
+ case "global":
23
+ return {
24
+ type: "global",
25
+ slug: t
26
+ };
27
+ default:
28
+ throw new Error(`Invalid entity reference: ${e}`);
29
+ }
30
+ }
31
+ static stringifyEntityReference(e) {
32
+ switch (e.type) {
33
+ case "collection":
34
+ return `collection|${e.slug}`;
35
+ case "global":
36
+ return `global|${e.slug}`;
37
+ }
38
+ }
39
+ static getFieldRelations(e, o) {
40
+ const t = [...o, e.name];
41
+ switch (e.type) {
42
+ case "upload":
43
+ case "relationship":
44
+ return Array.isArray(e.relationTo) ? [] : [
45
+ {
46
+ field: t.join("."),
47
+ collection: e.relationTo,
48
+ hasMany: e.hasMany ?? !1
49
+ }
50
+ ];
51
+ case "array":
52
+ return e.flattenedFields.flatMap(
53
+ (l) => n.getFieldRelations(l, t)
54
+ );
55
+ case "group":
56
+ return e.flattenedFields.flatMap(
57
+ (l) => n.getFieldRelations(l, t)
58
+ );
59
+ case "blocks":
60
+ return e.blocks.flatMap(
61
+ (l) => l.flattenedFields.flatMap(
62
+ (a) => n.getFieldRelations(a, t)
63
+ )
64
+ );
65
+ default:
66
+ return [];
67
+ }
68
+ }
69
+ getDependants(e) {
70
+ const o = this.edgeProperties.get(
71
+ `collection|${e}`
72
+ );
73
+ return Array.from(o?.entries() ?? []).map(([t, l]) => ({
74
+ entity: n.parseEntityReference(t),
75
+ fields: l
76
+ }));
77
+ }
78
+ addRelations(e, o) {
79
+ const t = o.flatMap(
80
+ (a) => n.getFieldRelations(a, [])
81
+ ), l = Object.entries(
82
+ d(t, "collection")
83
+ ).map(([a, s]) => ({
84
+ collection: a,
85
+ fields: s.map(({ field: c, hasMany: i }) => ({ field: c, hasMany: i }))
86
+ }));
87
+ for (const { collection: a, fields: s } of l)
88
+ this.addEdge(
89
+ `collection|${a}`,
90
+ n.stringifyEntityReference(e),
91
+ {
92
+ props: s
93
+ }
94
+ );
95
+ }
96
+ }
97
+ export {
98
+ n as EntitiesGraph,
99
+ u as createDependencyGraph
100
+ };
101
+ //# sourceMappingURL=dependency-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-graph.js","sources":["../../src/utils/dependency-graph.ts"],"sourcesContent":["import { Graph } from 'graph-data-structure';\nimport { groupBy } from 'lodash-es';\nimport type {\n BasePayload,\n CollectionSlug,\n FlattenedField,\n GlobalSlug,\n} from 'payload';\n\nexport function createDependencyGraph({ collections, globals }: BasePayload) {\n const graph = new EntitiesGraph();\n\n for (const {\n config: { slug, flattenedFields },\n } of Object.values(collections)) {\n graph.addRelations({ type: 'collection', slug }, flattenedFields);\n }\n\n for (const { slug, flattenedFields } of Object.values(globals.config)) {\n graph.addRelations({ type: 'global', slug }, flattenedFields);\n }\n\n return graph;\n}\n\ninterface FieldRelation {\n field: string;\n collection: CollectionSlug;\n hasMany: boolean;\n}\n\ntype StringifiedEntityReference =\n | `collection|${CollectionSlug}`\n | `global|${GlobalSlug}`;\n\ntype EntityReference =\n | {\n type: 'collection';\n slug: CollectionSlug;\n }\n | {\n type: 'global';\n slug: GlobalSlug;\n };\n\nexport class EntitiesGraph extends Graph<\n StringifiedEntityReference,\n {\n field: string;\n hasMany: boolean;\n }[]\n> {\n static parseEntityReference(\n entity: StringifiedEntityReference,\n ): EntityReference {\n const [type, slug] = entity.split('|') as\n | ['collection', CollectionSlug]\n | ['global', GlobalSlug];\n\n switch (type) {\n case 'collection':\n return {\n type: 'collection',\n slug,\n };\n case 'global':\n return {\n type: 'global',\n slug,\n };\n default:\n throw new Error(`Invalid entity reference: ${entity}`);\n }\n }\n\n static stringifyEntityReference(\n entity: EntityReference,\n ): StringifiedEntityReference {\n switch (entity.type) {\n case 'collection':\n return `collection|${entity.slug}`;\n case 'global':\n return `global|${entity.slug}`;\n }\n }\n\n static getFieldRelations(\n field: FlattenedField,\n prevPath: string[],\n ): FieldRelation[] {\n const fieldPath = [...prevPath, field.name];\n\n switch (field.type) {\n case 'upload':\n case 'relationship':\n if (Array.isArray(field.relationTo)) return [];\n return [\n {\n field: fieldPath.join('.'),\n collection: field.relationTo,\n hasMany: field.hasMany ?? false,\n },\n ];\n case 'array':\n return field.flattenedFields.flatMap((subField) =>\n EntitiesGraph.getFieldRelations(subField, fieldPath),\n );\n case 'group':\n return field.flattenedFields.flatMap((subField) =>\n EntitiesGraph.getFieldRelations(subField, fieldPath),\n );\n case 'blocks':\n return field.blocks.flatMap((block) =>\n block.flattenedFields.flatMap((subField) =>\n EntitiesGraph.getFieldRelations(subField, fieldPath),\n ),\n );\n default:\n return [];\n }\n }\n\n getDependants(collection: CollectionSlug) {\n const collectionRelations = this.edgeProperties.get(\n `collection|${collection}`,\n );\n return Array.from(collectionRelations?.entries() ?? []).map<{\n entity: EntityReference;\n fields: { field: string; hasMany: boolean }[];\n }>(([entity, fields]) => ({\n entity: EntitiesGraph.parseEntityReference(entity),\n fields,\n }));\n }\n\n addRelations(entity: EntityReference, flattenedFields: FlattenedField[]) {\n const fieldRelations = flattenedFields.flatMap((field) =>\n EntitiesGraph.getFieldRelations(field, []),\n );\n\n const fieldRelationsByCollection = Object.entries(\n groupBy(fieldRelations, 'collection'),\n ).map(([collection, fields]) => ({\n collection: collection as CollectionSlug,\n fields: fields.map(({ field, hasMany }) => ({ field, hasMany })),\n }));\n\n for (const { collection, fields } of fieldRelationsByCollection) {\n this.addEdge(\n `collection|${collection}`,\n EntitiesGraph.stringifyEntityReference(entity),\n {\n props: fields,\n },\n );\n }\n }\n}\n"],"names":["createDependencyGraph","collections","globals","graph","EntitiesGraph","slug","flattenedFields","Graph","entity","type","field","prevPath","fieldPath","subField","block","collection","collectionRelations","fields","fieldRelations","fieldRelationsByCollection","groupBy","hasMany"],"mappings":";;AASO,SAASA,EAAsB,EAAE,aAAAC,GAAa,SAAAC,KAAwB;AAC3E,QAAMC,IAAQ,IAAIC,EAAA;AAElB,aAAW;AAAA,IACT,QAAQ,EAAE,MAAAC,GAAM,iBAAAC,EAAA;AAAA,EAAgB,KAC7B,OAAO,OAAOL,CAAW;AAC5B,IAAAE,EAAM,aAAa,EAAE,MAAM,cAAc,MAAAE,EAAA,GAAQC,CAAe;AAGlE,aAAW,EAAE,MAAAD,GAAM,iBAAAC,EAAA,KAAqB,OAAO,OAAOJ,EAAQ,MAAM;AAClE,IAAAC,EAAM,aAAa,EAAE,MAAM,UAAU,MAAAE,EAAA,GAAQC,CAAe;AAG9D,SAAOH;AACT;AAsBO,MAAMC,UAAsBG,EAMjC;AAAA,EACA,OAAO,qBACLC,GACiB;AACjB,UAAM,CAACC,GAAMJ,CAAI,IAAIG,EAAO,MAAM,GAAG;AAIrC,YAAQC,GAAA;AAAA,MACN,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAAJ;AAAA,QAAA;AAAA,MAEJ,KAAK;AACH,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAAA;AAAA,QAAA;AAAA,MAEJ;AACE,cAAM,IAAI,MAAM,6BAA6BG,CAAM,EAAE;AAAA,IAAA;AAAA,EAE3D;AAAA,EAEA,OAAO,yBACLA,GAC4B;AAC5B,YAAQA,EAAO,MAAA;AAAA,MACb,KAAK;AACH,eAAO,cAAcA,EAAO,IAAI;AAAA,MAClC,KAAK;AACH,eAAO,UAAUA,EAAO,IAAI;AAAA,IAAA;AAAA,EAElC;AAAA,EAEA,OAAO,kBACLE,GACAC,GACiB;AACjB,UAAMC,IAAY,CAAC,GAAGD,GAAUD,EAAM,IAAI;AAE1C,YAAQA,EAAM,MAAA;AAAA,MACZ,KAAK;AAAA,MACL,KAAK;AACH,eAAI,MAAM,QAAQA,EAAM,UAAU,IAAU,CAAA,IACrC;AAAA,UACL;AAAA,YACE,OAAOE,EAAU,KAAK,GAAG;AAAA,YACzB,YAAYF,EAAM;AAAA,YAClB,SAASA,EAAM,WAAW;AAAA,UAAA;AAAA,QAC5B;AAAA,MAEJ,KAAK;AACH,eAAOA,EAAM,gBAAgB;AAAA,UAAQ,CAACG,MACpCT,EAAc,kBAAkBS,GAAUD,CAAS;AAAA,QAAA;AAAA,MAEvD,KAAK;AACH,eAAOF,EAAM,gBAAgB;AAAA,UAAQ,CAACG,MACpCT,EAAc,kBAAkBS,GAAUD,CAAS;AAAA,QAAA;AAAA,MAEvD,KAAK;AACH,eAAOF,EAAM,OAAO;AAAA,UAAQ,CAACI,MAC3BA,EAAM,gBAAgB;AAAA,YAAQ,CAACD,MAC7BT,EAAc,kBAAkBS,GAAUD,CAAS;AAAA,UAAA;AAAA,QACrD;AAAA,MAEJ;AACE,eAAO,CAAA;AAAA,IAAC;AAAA,EAEd;AAAA,EAEA,cAAcG,GAA4B;AACxC,UAAMC,IAAsB,KAAK,eAAe;AAAA,MAC9C,cAAcD,CAAU;AAAA,IAAA;AAE1B,WAAO,MAAM,KAAKC,GAAqB,QAAA,KAAa,EAAE,EAAE,IAGrD,CAAC,CAACR,GAAQS,CAAM,OAAO;AAAA,MACxB,QAAQb,EAAc,qBAAqBI,CAAM;AAAA,MACjD,QAAAS;AAAA,IAAA,EACA;AAAA,EACJ;AAAA,EAEA,aAAaT,GAAyBF,GAAmC;AACvE,UAAMY,IAAiBZ,EAAgB;AAAA,MAAQ,CAACI,MAC9CN,EAAc,kBAAkBM,GAAO,CAAA,CAAE;AAAA,IAAA,GAGrCS,IAA6B,OAAO;AAAA,MACxCC,EAAQF,GAAgB,YAAY;AAAA,IAAA,EACpC,IAAI,CAAC,CAACH,GAAYE,CAAM,OAAO;AAAA,MAC/B,YAAAF;AAAA,MACA,QAAQE,EAAO,IAAI,CAAC,EAAE,OAAAP,GAAO,SAAAW,SAAe,EAAE,OAAAX,GAAO,SAAAW,IAAU;AAAA,IAAA,EAC/D;AAEF,eAAW,EAAE,YAAAN,GAAY,QAAAE,EAAA,KAAYE;AACnC,WAAK;AAAA,QACH,cAAcJ,CAAU;AAAA,QACxBX,EAAc,yBAAyBI,CAAM;AAAA,QAC7C;AAAA,UACE,OAAOS;AAAA,QAAA;AAAA,MACT;AAAA,EAGN;AACF;"}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "payload-smart-cache",
3
+ "version": "1.0.0",
4
+ "description": "Payload Plugin for Cached Data",
5
+ "keywords": [
6
+ "payload",
7
+ "cached",
8
+ "cache",
9
+ "react",
10
+ "next.js"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/davincicoding-org/payload-plugins.git",
15
+ "directory": "packages/smart-cache"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Michael Zeltner",
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./rsc": {
27
+ "types": "./dist/exports/rsc.d.ts",
28
+ "import": "./dist/exports/rsc.js",
29
+ "default": "./dist/exports/rsc.js"
30
+ },
31
+ "./styles.css": "./dist/styles.css"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "dependencies": {
39
+ "graph-data-structure": "^4.5.0",
40
+ "lodash-es": "^4.17.21",
41
+ "tailwind-merge": "^3.3.1",
42
+ "zod": "^4.3.6"
43
+ },
44
+ "devDependencies": {
45
+ "@payloadcms/ui": "3.72.0",
46
+ "@tailwindcss/typography": "^0.5.16",
47
+ "@tailwindcss/vite": "^4.1.11",
48
+ "@types/lodash-es": "^4.17.12",
49
+ "@types/node": "^22.5.4",
50
+ "@types/react": "19.2.1",
51
+ "@types/react-dom": "19.2.1",
52
+ "next": "15.5.9",
53
+ "payload": "3.72.0",
54
+ "react": "19.2.1",
55
+ "react-dom": "19.2.1",
56
+ "rollup-preserve-directives": "1.1.3",
57
+ "tailwindcss": "^4.1.11",
58
+ "tw-animate-css": "^1.3.6",
59
+ "typescript": "5.7.3",
60
+ "vite": "7.0.6",
61
+ "vite-plugin-dts": "4.5.4",
62
+ "vite-tsconfig-paths": "5.1.4",
63
+ "vitest": "^3.1.2",
64
+ "@repo/configs": "0.0.1"
65
+ },
66
+ "peerDependencies": {
67
+ "@payloadcms/next": ">=3.72.0",
68
+ "@payloadcms/ui": ">=3.72.0",
69
+ "next": ">=15.2.3",
70
+ "payload": ">=3.72.0"
71
+ },
72
+ "engines": {
73
+ "node": "^18.20.2 || >=20.9.0",
74
+ "pnpm": "^9 || ^10"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
79
+ "registry": "https://registry.npmjs.org/",
80
+ "scripts": {
81
+ "prebuild": "pnpm typecheck",
82
+ "build": "vite build",
83
+ "clean": "rm -rf dist && rm -rf node_modules",
84
+ "dev": "vite build --watch",
85
+ "lint": "biome check .",
86
+ "lint:fix": "biome check --write .",
87
+ "test": "vitest run",
88
+ "test:watch": "vitest",
89
+ "typecheck": "tsc --noEmit"
90
+ }
91
+ }