payload-smart-cache 1.1.10 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +29 -11
  2. package/dist/exports/create-request.d.ts +15 -1
  3. package/dist/exports/create-request.d.ts.map +1 -1
  4. package/dist/exports/create-request.js +13 -4
  5. package/dist/exports/create-request.js.map +1 -1
  6. package/dist/hooks.d.ts +12 -4
  7. package/dist/hooks.d.ts.map +1 -1
  8. package/dist/hooks.js +98 -107
  9. package/dist/hooks.js.map +1 -1
  10. package/dist/index.d.ts +12 -8
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +28 -18
  13. package/dist/index.js.map +1 -1
  14. package/dist/types.d.ts +18 -7
  15. package/dist/types.d.ts.map +1 -1
  16. package/dist/types.js +1 -1
  17. package/dist/types.js.map +1 -1
  18. package/dist/utils/dependency-graph.d.ts +3 -10
  19. package/dist/utils/dependency-graph.d.ts.map +1 -1
  20. package/dist/utils/dependency-graph.js +27 -50
  21. package/dist/utils/dependency-graph.js.map +1 -1
  22. package/dist/utils/tracked-collections.d.ts.map +1 -1
  23. package/dist/utils/tracked-collections.js.map +1 -1
  24. package/package.json +5 -25
  25. package/dist/components/PublishButton.d.ts +0 -2
  26. package/dist/components/PublishButton.d.ts.map +0 -1
  27. package/dist/components/PublishButton.js +0 -52
  28. package/dist/components/PublishButton.js.map +0 -1
  29. package/dist/const.d.ts +0 -7
  30. package/dist/const.d.ts.map +0 -1
  31. package/dist/const.js +0 -13
  32. package/dist/const.js.map +0 -1
  33. package/dist/endpoints/check.d.ts +0 -3
  34. package/dist/endpoints/check.d.ts.map +0 -1
  35. package/dist/endpoints/check.js +0 -21
  36. package/dist/endpoints/check.js.map +0 -1
  37. package/dist/endpoints/publish.d.ts +0 -4
  38. package/dist/endpoints/publish.d.ts.map +0 -1
  39. package/dist/endpoints/publish.js +0 -109
  40. package/dist/endpoints/publish.js.map +0 -1
  41. package/dist/entities.d.ts +0 -26
  42. package/dist/entities.d.ts.map +0 -1
  43. package/dist/entities.js +0 -28
  44. package/dist/entities.js.map +0 -1
  45. package/dist/exports/client.d.ts +0 -2
  46. package/dist/exports/client.d.ts.map +0 -1
  47. package/dist/exports/client.js +0 -3
  48. package/dist/exports/client.js.map +0 -1
  49. package/dist/exports/rsc.d.ts +0 -2
  50. package/dist/exports/rsc.d.ts.map +0 -1
  51. package/dist/exports/rsc.js +0 -3
  52. package/dist/exports/rsc.js.map +0 -1
  53. package/dist/payload-types.d.ts +0 -241
  54. package/dist/payload-types.d.ts.map +0 -1
  55. package/dist/utils/collection-changes.d.ts +0 -8
  56. package/dist/utils/collection-changes.d.ts.map +0 -1
  57. package/dist/utils/collection-changes.js +0 -27
  58. package/dist/utils/collection-changes.js.map +0 -1
package/README.md CHANGED
@@ -7,14 +7,14 @@ Intelligent, dependency-aware cache invalidation for Next.js + Payload CMS appli
7
7
 
8
8
  ## Overview
9
9
 
10
- payload-smart-cache tracks document changes in a publish queue and builds a dependency graph from your collection relationships. When you publish, it walks the graph and revalidates all affected Next.js cache tags — including indirectly related documents. A publish button in the admin UI shows when unpublished changes exist.
10
+ payload-smart-cache hooks into Payload's save and publish flow to provide automatic, dependency-aware cache invalidation. It builds a dependency graph from your collection and global relationships and walks it on every change, revalidating all affected Next.js cache tags — including indirectly related collections and globals.
11
11
 
12
12
  **Features**
13
13
 
14
- - **Deferred publishing** — changes are queued and only pushed to the cache when you explicitly publish.
15
14
  - **Dependency graph** — automatically discovers relationships between collections, so changing a referenced document revalidates its dependents.
16
15
  - **Tag-based revalidation** — precise, granular cache invalidation via Next.js `revalidateTag()`.
17
- - **Request caching utility** — `createRequestHandler` wraps data-fetching functions with entity-level cache tags for automatic revalidation.
16
+ - **Versions-aware** — for versioned collections, cache invalidation only fires on publish, not on draft saves.
17
+ - **Request caching utility** — `createRequestHandler` wraps data-fetching functions with collection/global-level cache tags for automatic revalidation.
18
18
 
19
19
  ## Installation
20
20
 
@@ -44,7 +44,7 @@ export default buildConfig({
44
44
  });
45
45
  ```
46
46
 
47
- Wrap your data-fetching functions with `createRequestHandler` so they are cached by entity tags and automatically revalidated on publish:
47
+ Wrap your data-fetching functions with `createRequestHandler` so they are cached by collection/global tags and automatically revalidated when those collections or globals change:
48
48
 
49
49
  ```ts
50
50
  import { createRequestHandler } from "payload-smart-cache";
@@ -54,18 +54,36 @@ const getPosts = createRequestHandler(
54
54
  const payload = await getPayload({ config });
55
55
  return payload.find({ collection: "posts" });
56
56
  },
57
- ["posts"], // cache tags — revalidated when posts change
57
+ ["posts"], // collection/global slugs — revalidated when posts change
58
58
  );
59
59
  ```
60
60
 
61
+ You can pass additional cache options as a third argument:
62
+
63
+ ```ts
64
+ const getPosts = createRequestHandler(
65
+ async () => {
66
+ const payload = await getPayload({ config });
67
+ return payload.find({ collection: "posts" });
68
+ },
69
+ ["posts"],
70
+ { revalidate: 60 }, // also revalidate every 60 seconds
71
+ );
72
+ ```
73
+
74
+ | Cache Option | Type | Default | Description |
75
+ | ------------- | ------------------ | ------- | -------------------------------------------------------------------- |
76
+ | `tags` | `string[]` | `[]` | Additional cache tags beyond the collection/global slugs. |
77
+ | `revalidate` | `number \| false` | `false` | Time-based revalidation in seconds, or `false` for tag-based only. |
78
+
61
79
  ### Options
62
80
 
63
- | Option | Type | Default | Description |
64
- | --------------------- | ------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
65
- | `collections` | `CollectionSlug[]` | `[]` | Collections to track changes for. Referenced collections are auto-tracked. |
66
- | `globals` | `GlobalSlug[]` | `[]` | Globals to track changes for. Referenced collections are auto-tracked. |
67
- | `disableAutoTracking` | `boolean` | `false` | Disable automatic tracking of collections referenced via relationship/upload fields. |
68
- | `publishHandler` | `(changes: ChangedDocuments) => void \| Promise<void>` | — | Custom handler called when changes are published. Receives a record mapping collection slugs to arrays of changed document IDs. |
81
+ | Option | Type | Default | Description |
82
+ | --------------------- | ----------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
83
+ | `collections` | `CollectionSlug[]` | `[]` | Collections to track changes for. Referenced collections are auto-tracked. |
84
+ | `globals` | `GlobalSlug[]` | `[]` | Globals to track changes for. Referenced collections are auto-tracked. |
85
+ | `disableAutoTracking` | `boolean` | `false` | Disable automatic tracking of collections referenced via relationship/upload fields. |
86
+ | `onInvalidate` | `(change) => void \| Promise<void>` | — | Called when cache invalidation fires for a registered collection (`{ type: 'collection', slug, docID }`) or global (`{ type: 'global', slug }`). |
69
87
 
70
88
  ## Contributing
71
89
 
@@ -1,3 +1,17 @@
1
1
  import type { EntitySlug } from '../types';
2
- export declare const createRequestHandler: <Data, Inputs extends unknown[]>(handler: (...inputs: Inputs) => Promise<Data>, tags?: EntitySlug[], revalidate?: number | false) => ((...inputs: Inputs) => Promise<Data>);
2
+ export interface RequestHandlerCacheOptions {
3
+ /** Additional cache tags beyond the collection/global slugs. */
4
+ tags?: string[];
5
+ /** Time-based revalidation in seconds, or `false` to rely solely on tag-based revalidation. */
6
+ revalidate?: number | false;
7
+ }
8
+ /**
9
+ * Wraps a data-fetching function with Next.js `unstable_cache`, tagging it with
10
+ * collection/global slugs so it is automatically revalidated when those collections or globals change.
11
+ *
12
+ * @param handler - The async function to cache.
13
+ * @param slugs - Collection or global slugs used as cache tags (e.g. `['posts', 'media']`).
14
+ * @param options - Additional cache options passed to `unstable_cache`.
15
+ */
16
+ export declare const createRequestHandler: <Data, Inputs extends unknown[]>(handler: (...inputs: Inputs) => Promise<Data>, slugs: EntitySlug[], options?: RequestHandlerCacheOptions) => ((...inputs: Inputs) => Promise<Data>);
3
17
  //# sourceMappingURL=create-request.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-request.d.ts","sourceRoot":"","sources":["../../src/exports/create-request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,eAAO,MAAM,oBAAoB,GAAI,IAAI,EAAE,MAAM,SAAS,OAAO,EAAE,WACxD,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,SACtC,UAAU,EAAE,eACP,MAAM,GAAG,KAAK,KACzB,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAIpC,CAAC"}
1
+ {"version":3,"file":"create-request.d.ts","sourceRoot":"","sources":["../../src/exports/create-request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,WAAW,0BAA0B;IACzC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,+FAA+F;IAC/F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,IAAI,EAAE,MAAM,SAAS,OAAO,EAAE,WACxD,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,SACtC,UAAU,EAAE,YACT,0BAA0B,KACnC,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAIpC,CAAC"}
@@ -1,8 +1,17 @@
1
1
  import { unstable_cache } from 'next/cache';
2
- // TODO attach tags to the returned function
3
- export const createRequestHandler = (handler, tags, revalidate = false)=>unstable_cache(handler, undefined, {
4
- tags,
5
- revalidate
2
+ /**
3
+ * Wraps a data-fetching function with Next.js `unstable_cache`, tagging it with
4
+ * collection/global slugs so it is automatically revalidated when those collections or globals change.
5
+ *
6
+ * @param handler - The async function to cache.
7
+ * @param slugs - Collection or global slugs used as cache tags (e.g. `['posts', 'media']`).
8
+ * @param options - Additional cache options passed to `unstable_cache`.
9
+ */ export const createRequestHandler = (handler, slugs, options)=>unstable_cache(handler, undefined, {
10
+ tags: [
11
+ ...slugs,
12
+ ...options?.tags ?? []
13
+ ],
14
+ revalidate: options?.revalidate ?? false
6
15
  });
7
16
 
8
17
  //# sourceMappingURL=create-request.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/create-request.ts"],"sourcesContent":["import { unstable_cache } from 'next/cache';\n\nimport type { EntitySlug } from '../types';\n\n// TODO attach tags to the returned function\nexport const createRequestHandler = <Data, Inputs extends unknown[]>(\n handler: (...inputs: Inputs) => Promise<Data>,\n tags?: EntitySlug[],\n revalidate: number | false = false,\n): ((...inputs: Inputs) => Promise<Data>) =>\n unstable_cache(handler, undefined, {\n tags,\n revalidate,\n });\n"],"names":["unstable_cache","createRequestHandler","handler","tags","revalidate","undefined"],"mappings":"AAAA,SAASA,cAAc,QAAQ,aAAa;AAI5C,4CAA4C;AAC5C,OAAO,MAAMC,uBAAuB,CAClCC,SACAC,MACAC,aAA6B,KAAK,GAElCJ,eAAeE,SAASG,WAAW;QACjCF;QACAC;IACF,GAAG"}
1
+ {"version":3,"sources":["../../src/exports/create-request.ts"],"sourcesContent":["import { unstable_cache } from 'next/cache';\n\nimport type { EntitySlug } from '../types';\n\nexport interface RequestHandlerCacheOptions {\n /** Additional cache tags beyond the collection/global slugs. */\n tags?: string[];\n /** Time-based revalidation in seconds, or `false` to rely solely on tag-based revalidation. */\n revalidate?: number | false;\n}\n\n/**\n * Wraps a data-fetching function with Next.js `unstable_cache`, tagging it with\n * collection/global slugs so it is automatically revalidated when those collections or globals change.\n *\n * @param handler - The async function to cache.\n * @param slugs - Collection or global slugs used as cache tags (e.g. `['posts', 'media']`).\n * @param options - Additional cache options passed to `unstable_cache`.\n */\nexport const createRequestHandler = <Data, Inputs extends unknown[]>(\n handler: (...inputs: Inputs) => Promise<Data>,\n slugs: EntitySlug[],\n options?: RequestHandlerCacheOptions,\n): ((...inputs: Inputs) => Promise<Data>) =>\n unstable_cache(handler, undefined, {\n tags: [...slugs, ...(options?.tags ?? [])],\n revalidate: options?.revalidate ?? false,\n });\n"],"names":["unstable_cache","createRequestHandler","handler","slugs","options","undefined","tags","revalidate"],"mappings":"AAAA,SAASA,cAAc,QAAQ,aAAa;AAW5C;;;;;;;CAOC,GACD,OAAO,MAAMC,uBAAuB,CAClCC,SACAC,OACAC,UAEAJ,eAAeE,SAASG,WAAW;QACjCC,MAAM;eAAIH;eAAWC,SAASE,QAAQ,EAAE;SAAE;QAC1CC,YAAYH,SAASG,cAAc;IACrC,GAAG"}
package/dist/hooks.d.ts CHANGED
@@ -1,5 +1,13 @@
1
- import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, GlobalAfterChangeHook, TypeWithID } from 'payload';
2
- export declare const trackCollectionChange: CollectionAfterChangeHook<TypeWithID>;
3
- export declare const trackCollectionDelete: CollectionAfterDeleteHook<TypeWithID>;
4
- export declare const trackGlobalChange: GlobalAfterChangeHook;
1
+ import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, GlobalAfterChangeHook } from 'payload';
2
+ import type { DocumentInvalidationCallback, DocumentWithStatus } from './types';
3
+ import type { EntitiesGraph } from './utils/dependency-graph';
4
+ export declare function invalidateCollectionCache({ graph, invalidationCallback, }: {
5
+ graph: EntitiesGraph;
6
+ invalidationCallback: DocumentInvalidationCallback | undefined;
7
+ }): CollectionAfterChangeHook<DocumentWithStatus>;
8
+ export declare function invalidateCollectionCacheOnDelete({ graph, invalidationCallback, }: {
9
+ graph: EntitiesGraph;
10
+ invalidationCallback: DocumentInvalidationCallback;
11
+ }): CollectionAfterDeleteHook<DocumentWithStatus>;
12
+ export declare function invalidateGlobalCache(invalidationCallback: DocumentInvalidationCallback): GlobalAfterChangeHook;
5
13
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,qBAAqB,EAAE,yBAAyB,CAC3D,UAAU,CAqCX,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,yBAAyB,CAC3D,UAAU,CAqCX,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,qBAmC/B,CAAC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,yBAAyB,EACzB,yBAAyB,EAEzB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,4BAA4B,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AA4G9D,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,oBAAoB,GACrB,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,oBAAoB,EAAE,4BAA4B,GAAG,SAAS,CAAC;CAChE,GAAG,yBAAyB,CAAC,kBAAkB,CAAC,CAgBhD;AAED,wBAAgB,iCAAiC,CAAC,EAChD,KAAK,EACL,oBAAoB,GACrB,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,oBAAoB,EAAE,4BAA4B,CAAC;CACpD,GAAG,yBAAyB,CAAC,kBAAkB,CAAC,CAWhD;AAED,wBAAgB,qBAAqB,CACnC,oBAAoB,EAAE,4BAA4B,GACjD,qBAAqB,CAWvB"}
package/dist/hooks.js CHANGED
@@ -1,119 +1,110 @@
1
- export const trackCollectionChange = async ({ req: { payload }, doc, collection })=>{
2
- const entityType = collection.slug;
3
- const entityId = doc.id.toString();
4
- // Check if this entity already has a pending change
5
- const { docs: [existingChange] } = await payload.find({
6
- collection: 'publish-queue',
7
- where: {
8
- and: [
9
- {
10
- entityType: {
11
- equals: entityType
12
- }
13
- },
14
- {
15
- entityId: {
16
- equals: entityId
17
- }
18
- }
19
- ]
20
- },
21
- limit: 1
22
- });
23
- if (existingChange) {
24
- // Update existing change record
25
- await payload.update({
26
- collection: 'publish-queue',
27
- id: existingChange.id,
28
- data: {
29
- updatedAt: new Date().toISOString()
30
- }
31
- });
32
- } else {
33
- await payload.create({
34
- collection: 'publish-queue',
35
- data: {
36
- entityType,
37
- entityId
38
- }
1
+ import { revalidateTag } from 'next/cache';
2
+ async function invalidateWithDependents(payload, { graph, invalidationCallback, collection, ids }) {
3
+ const tagsToInvalidate = new Set();
4
+ tagsToInvalidate.add(collection);
5
+ await walkDependents(graph, payload, collection, ids, new Set());
6
+ for (const tag of tagsToInvalidate){
7
+ revalidateTag(tag);
8
+ }
9
+ for (const id of ids){
10
+ await invalidationCallback?.({
11
+ type: 'collection',
12
+ slug: collection,
13
+ docID: id
39
14
  });
40
15
  }
41
- };
42
- export const trackCollectionDelete = async ({ req: { payload }, doc, collection })=>{
43
- const entityType = collection.slug;
44
- const entityId = doc.id.toString();
45
- // Check if this entity already has a pending change
46
- const { docs: [existingChange] } = await payload.find({
47
- collection: 'publish-queue',
48
- where: {
49
- and: [
50
- {
51
- entityType: {
52
- equals: entityType
53
- }
54
- },
55
- {
56
- entityId: {
57
- equals: entityId
16
+ async function walkDependents(graph, payload, changedCollection, changedIds, visited) {
17
+ const dependents = graph.getDependants(changedCollection);
18
+ if (dependents.length === 0) return;
19
+ for (const dependent of dependents){
20
+ if (dependent.entity.type === 'global') {
21
+ tagsToInvalidate.add(dependent.entity.slug);
22
+ continue;
23
+ }
24
+ if (visited.has(dependent.entity.slug)) continue;
25
+ const allAffectedItems = new Map();
26
+ for (const field of dependent.fields){
27
+ const { docs } = await payload.find({
28
+ collection: dependent.entity.slug,
29
+ where: field.polymorphic ? {
30
+ and: [
31
+ {
32
+ [`${field.field}.relationTo`]: {
33
+ equals: changedCollection
34
+ }
35
+ },
36
+ {
37
+ [`${field.field}.value`]: {
38
+ in: changedIds
39
+ }
40
+ }
41
+ ]
42
+ } : {
43
+ [field.field]: {
44
+ in: changedIds
45
+ }
58
46
  }
47
+ });
48
+ for (const item of docs){
49
+ allAffectedItems.set(item.id.toString(), item);
59
50
  }
60
- ]
61
- },
62
- limit: 1
63
- });
64
- if (existingChange) {
65
- // Update existing change record
66
- await payload.update({
67
- collection: 'publish-queue',
68
- id: existingChange.id,
69
- data: {
70
- updatedAt: new Date().toISOString()
71
51
  }
72
- });
73
- } else {
74
- await payload.create({
75
- collection: 'publish-queue',
76
- data: {
77
- entityType,
78
- entityId
52
+ const affectedItems = Array.from(allAffectedItems.values());
53
+ visited.add(dependent.entity.slug);
54
+ if (affectedItems.length === 0) continue;
55
+ tagsToInvalidate.add(dependent.entity.slug);
56
+ for (const item of affectedItems){
57
+ await invalidationCallback?.({
58
+ type: 'collection',
59
+ slug: dependent.entity.slug,
60
+ docID: item.id.toString()
61
+ });
79
62
  }
80
- });
63
+ await walkDependents(graph, payload, dependent.entity.slug, affectedItems.map((item)=>item.id.toString()), visited);
64
+ }
81
65
  }
82
- };
83
- export const trackGlobalChange = async ({ req: { payload }, global })=>{
84
- const entityType = global.slug;
85
- // Check if this entity already has a pending change
86
- const { docs: [existingChange] } = await payload.find({
87
- collection: 'publish-queue',
88
- where: {
89
- and: [
90
- {
91
- entityType: {
92
- equals: entityType
93
- }
94
- }
66
+ }
67
+ export function invalidateCollectionCache({ graph, invalidationCallback }) {
68
+ return async ({ req, doc, collection, previousDoc })=>{
69
+ if (req.context.skipRevalidation) return;
70
+ if (collection.versions?.drafts) {
71
+ if (doc._status === 'draft' && previousDoc._status !== 'published') return;
72
+ }
73
+ await invalidateWithDependents(req.payload, {
74
+ graph,
75
+ invalidationCallback,
76
+ collection: collection.slug,
77
+ ids: [
78
+ doc.id.toString()
95
79
  ]
96
- },
97
- limit: 1
98
- });
99
- if (existingChange) {
100
- // Update existing change record
101
- await payload.update({
102
- collection: 'publish-queue',
103
- id: existingChange.id,
104
- data: {
105
- updatedAt: new Date().toISOString()
106
- }
107
80
  });
108
- } else {
109
- // Create new change record
110
- await payload.create({
111
- collection: 'publish-queue',
112
- data: {
113
- entityType
114
- }
81
+ };
82
+ }
83
+ export function invalidateCollectionCacheOnDelete({ graph, invalidationCallback }) {
84
+ return async ({ req, doc, collection })=>{
85
+ if (req.context.skipRevalidation) return;
86
+ await invalidateWithDependents(req.payload, {
87
+ graph,
88
+ invalidationCallback,
89
+ collection: collection.slug,
90
+ ids: [
91
+ doc.id.toString()
92
+ ]
115
93
  });
116
- }
117
- };
94
+ };
95
+ }
96
+ export function invalidateGlobalCache(invalidationCallback) {
97
+ return async ({ req, global, doc, previousDoc })=>{
98
+ if (global.versions?.drafts) {
99
+ if (doc._status === 'draft' && previousDoc._status !== 'published') return;
100
+ }
101
+ if (req.context.skipRevalidation) return;
102
+ revalidateTag(global.slug);
103
+ await invalidationCallback?.({
104
+ type: 'global',
105
+ slug: global.slug
106
+ });
107
+ };
108
+ }
118
109
 
119
110
  //# sourceMappingURL=hooks.js.map
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionAfterDeleteHook,\n GlobalAfterChangeHook,\n TypeWithID,\n} from 'payload';\n\nexport const trackCollectionChange: CollectionAfterChangeHook<\n TypeWithID\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 trackCollectionDelete: CollectionAfterDeleteHook<\n TypeWithID\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","req","payload","doc","collection","entityType","slug","entityId","id","toString","docs","existingChange","find","where","and","equals","limit","update","data","updatedAt","Date","toISOString","create","trackCollectionDelete","trackGlobalChange","global"],"mappings":"AAOA,OAAO,MAAMA,wBAET,OAAO,EAAEC,KAAK,EAAEC,OAAO,EAAE,EAAEC,GAAG,EAAEC,UAAU,EAAE;IAC9C,MAAMC,aAAaD,WAAWE,IAAI;IAClC,MAAMC,WAAWJ,IAAIK,EAAE,CAACC,QAAQ;IAEhC,oDAAoD;IACpD,MAAM,EACJC,MAAM,CAACC,eAAe,EACvB,GAAG,MAAMT,QAAQU,IAAI,CAAC;QACrBR,YAAY;QACZS,OAAO;YACLC,KAAK;gBACH;oBAAET,YAAY;wBAAEU,QAAQV;oBAAW;gBAAE;gBACrC;oBAAEE,UAAU;wBAAEQ,QAAQR;oBAAS;gBAAE;aAClC;QACH;QACAS,OAAO;IACT;IAEA,IAAIL,gBAAgB;QAClB,gCAAgC;QAChC,MAAMT,QAAQe,MAAM,CAAC;YACnBb,YAAY;YACZI,IAAIG,eAAeH,EAAE;YACrBU,MAAM;gBACJC,WAAW,IAAIC,OAAOC,WAAW;YACnC;QACF;IACF,OAAO;QACL,MAAMnB,QAAQoB,MAAM,CAAC;YACnBlB,YAAY;YACZc,MAAM;gBACJb;gBACAE;YACF;QACF;IACF;AACF,EAAE;AAEF,OAAO,MAAMgB,wBAET,OAAO,EAAEtB,KAAK,EAAEC,OAAO,EAAE,EAAEC,GAAG,EAAEC,UAAU,EAAE;IAC9C,MAAMC,aAAaD,WAAWE,IAAI;IAClC,MAAMC,WAAWJ,IAAIK,EAAE,CAACC,QAAQ;IAEhC,oDAAoD;IACpD,MAAM,EACJC,MAAM,CAACC,eAAe,EACvB,GAAG,MAAMT,QAAQU,IAAI,CAAC;QACrBR,YAAY;QACZS,OAAO;YACLC,KAAK;gBACH;oBAAET,YAAY;wBAAEU,QAAQV;oBAAW;gBAAE;gBACrC;oBAAEE,UAAU;wBAAEQ,QAAQR;oBAAS;gBAAE;aAClC;QACH;QACAS,OAAO;IACT;IAEA,IAAIL,gBAAgB;QAClB,gCAAgC;QAChC,MAAMT,QAAQe,MAAM,CAAC;YACnBb,YAAY;YACZI,IAAIG,eAAeH,EAAE;YACrBU,MAAM;gBACJC,WAAW,IAAIC,OAAOC,WAAW;YACnC;QACF;IACF,OAAO;QACL,MAAMnB,QAAQoB,MAAM,CAAC;YACnBlB,YAAY;YACZc,MAAM;gBACJb;gBACAE;YACF;QACF;IACF;AACF,EAAE;AAEF,OAAO,MAAMiB,oBAA2C,OAAO,EAC7DvB,KAAK,EAAEC,OAAO,EAAE,EAChBuB,MAAM,EACP;IACC,MAAMpB,aAAaoB,OAAOnB,IAAI;IAE9B,oDAAoD;IACpD,MAAM,EACJI,MAAM,CAACC,eAAe,EACvB,GAAG,MAAMT,QAAQU,IAAI,CAAC;QACrBR,YAAY;QACZS,OAAO;YACLC,KAAK;gBAAC;oBAAET,YAAY;wBAAEU,QAAQV;oBAAW;gBAAE;aAAE;QAC/C;QACAW,OAAO;IACT;IAEA,IAAIL,gBAAgB;QAClB,gCAAgC;QAChC,MAAMT,QAAQe,MAAM,CAAC;YACnBb,YAAY;YACZI,IAAIG,eAAeH,EAAE;YACrBU,MAAM;gBACJC,WAAW,IAAIC,OAAOC,WAAW;YACnC;QACF;IACF,OAAO;QACL,2BAA2B;QAC3B,MAAMnB,QAAQoB,MAAM,CAAC;YACnBlB,YAAY;YACZc,MAAM;gBACJb;YACF;QACF;IACF;AACF,EAAE"}
1
+ {"version":3,"sources":["../src/hooks.ts"],"sourcesContent":["import { revalidateTag } from 'next/cache';\nimport type {\n BasePayload,\n CollectionAfterChangeHook,\n CollectionAfterDeleteHook,\n CollectionSlug,\n GlobalAfterChangeHook,\n} from 'payload';\nimport type { DocumentInvalidationCallback, DocumentWithStatus } from './types';\nimport type { EntitiesGraph } from './utils/dependency-graph';\n\nasync function invalidateWithDependents(\n payload: BasePayload,\n {\n graph,\n invalidationCallback,\n collection,\n ids,\n }: {\n graph: EntitiesGraph;\n invalidationCallback: DocumentInvalidationCallback | undefined;\n collection: CollectionSlug;\n ids: string[];\n },\n): Promise<void> {\n const tagsToInvalidate = new Set<string>();\n\n tagsToInvalidate.add(collection);\n\n await walkDependents(graph, payload, collection, ids, new Set());\n\n for (const tag of tagsToInvalidate) {\n revalidateTag(tag);\n }\n\n for (const id of ids) {\n await invalidationCallback?.({\n type: 'collection',\n slug: collection,\n docID: id,\n });\n }\n\n async function walkDependents(\n graph: EntitiesGraph,\n payload: BasePayload,\n changedCollection: CollectionSlug,\n changedIds: string[],\n visited: Set<string>,\n ): Promise<void> {\n const dependents = graph.getDependants(changedCollection);\n\n if (dependents.length === 0) return;\n\n for (const dependent of dependents) {\n if (dependent.entity.type === 'global') {\n tagsToInvalidate.add(dependent.entity.slug);\n continue;\n }\n\n if (visited.has(dependent.entity.slug)) continue;\n\n const allAffectedItems = new Map<string, { id: string | number }>();\n\n for (const field of dependent.fields) {\n const { docs } = await payload.find({\n collection: dependent.entity.slug,\n where: field.polymorphic\n ? {\n and: [\n {\n [`${field.field}.relationTo`]: {\n equals: changedCollection,\n },\n },\n { [`${field.field}.value`]: { in: changedIds } },\n ],\n }\n : {\n [field.field]: {\n in: changedIds,\n },\n },\n });\n\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 tagsToInvalidate.add(dependent.entity.slug);\n\n for (const item of affectedItems) {\n await invalidationCallback?.({\n type: 'collection',\n slug: dependent.entity.slug,\n docID: item.id.toString(),\n });\n }\n\n await walkDependents(\n graph,\n payload,\n dependent.entity.slug,\n affectedItems.map((item) => item.id.toString()),\n visited,\n );\n }\n }\n}\n\nexport function invalidateCollectionCache({\n graph,\n invalidationCallback,\n}: {\n graph: EntitiesGraph;\n invalidationCallback: DocumentInvalidationCallback | undefined;\n}): CollectionAfterChangeHook<DocumentWithStatus> {\n return async ({ req, doc, collection, previousDoc }) => {\n if (req.context.skipRevalidation) return;\n\n if (collection.versions?.drafts) {\n if (doc._status === 'draft' && previousDoc._status !== 'published')\n return;\n }\n\n await invalidateWithDependents(req.payload, {\n graph,\n invalidationCallback,\n collection: collection.slug,\n ids: [doc.id.toString()],\n });\n };\n}\n\nexport function invalidateCollectionCacheOnDelete({\n graph,\n invalidationCallback,\n}: {\n graph: EntitiesGraph;\n invalidationCallback: DocumentInvalidationCallback;\n}): CollectionAfterDeleteHook<DocumentWithStatus> {\n return async ({ req, doc, collection }) => {\n if (req.context.skipRevalidation) return;\n\n await invalidateWithDependents(req.payload, {\n graph,\n invalidationCallback,\n collection: collection.slug,\n ids: [doc.id.toString()],\n });\n };\n}\n\nexport function invalidateGlobalCache(\n invalidationCallback: DocumentInvalidationCallback,\n): GlobalAfterChangeHook {\n return async ({ req, global, doc, previousDoc }) => {\n if (global.versions?.drafts) {\n if (doc._status === 'draft' && previousDoc._status !== 'published')\n return;\n }\n if (req.context.skipRevalidation) return;\n\n revalidateTag(global.slug);\n await invalidationCallback?.({ type: 'global', slug: global.slug });\n };\n}\n"],"names":["revalidateTag","invalidateWithDependents","payload","graph","invalidationCallback","collection","ids","tagsToInvalidate","Set","add","walkDependents","tag","id","type","slug","docID","changedCollection","changedIds","visited","dependents","getDependants","length","dependent","entity","has","allAffectedItems","Map","field","fields","docs","find","where","polymorphic","and","equals","in","item","set","toString","affectedItems","Array","from","values","map","invalidateCollectionCache","req","doc","previousDoc","context","skipRevalidation","versions","drafts","_status","invalidateCollectionCacheOnDelete","invalidateGlobalCache","global"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,26 +1,30 @@
1
1
  import type { CollectionSlug, GlobalSlug, Plugin } from 'payload';
2
- import type { ChangedDocuments } from './types';
3
- export interface SmartCachePluginConfig {
2
+ import type { DocumentInvalidationCallback } from './types';
3
+ export { createRequestHandler, type RequestHandlerCacheOptions, } from './exports/create-request';
4
+ export type { DocumentInvalidation as InvalidationChange, DocumentInvalidationCallback as OnInvalidate, } from './types';
5
+ export interface SmartCachePluginConfig<C extends CollectionSlug = CollectionSlug, G extends GlobalSlug = GlobalSlug> {
4
6
  /**
5
7
  * The collections to track changes for.
6
8
  * By default, collections referenced via relationship or upload fields
7
9
  * are automatically tracked as well.
8
10
  */
9
- collections?: CollectionSlug[];
11
+ collections?: C[];
10
12
  /**
11
13
  * The globals to track changes for.
12
14
  * Collections referenced by these globals via relationship or upload
13
15
  * fields are automatically tracked as well.
14
16
  */
15
- globals?: GlobalSlug[];
17
+ globals?: G[];
16
18
  /**
17
19
  * Disable automatic tracking of collections referenced by the configured ones.
18
20
  * @default false
19
21
  */
20
22
  disableAutoTracking?: boolean;
21
- publishHandler?: (changes: ChangedDocuments) => void | Promise<void>;
23
+ /**
24
+ * Called when cache invalidation is triggered.
25
+ * Only fires for collections/globals explicitly registered in the config.
26
+ */
27
+ onInvalidate?: DocumentInvalidationCallback<C, G>;
22
28
  }
23
- export declare const smartCachePlugin: ({ collections, globals, publishHandler, disableAutoTracking, }: SmartCachePluginConfig) => Plugin;
24
- export { createRequestHandler } from './exports/create-request';
25
- export type { ChangedDocuments } from './types';
29
+ export declare const smartCachePlugin: <C extends CollectionSlug = string, G extends GlobalSlug = string>({ collections, globals, onInvalidate, disableAutoTracking, }: SmartCachePluginConfig<C, G>) => Plugin;
26
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AASlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGhD,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAED,eAAO,MAAM,gBAAgB,mEAMxB,sBAAsB,KAAG,MA8C3B,CAAC;AAEJ,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAMlE,OAAO,KAAK,EAEV,4BAA4B,EAE7B,MAAM,SAAS,CAAC;AAIjB,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,GAChC,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,oBAAoB,IAAI,kBAAkB,EAC1C,4BAA4B,IAAI,YAAY,GAC7C,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,sBAAsB,CACrC,CAAC,SAAS,cAAc,GAAG,cAAc,EACzC,CAAC,SAAS,UAAU,GAAG,UAAU;IAEjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;IACd;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,4BAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACnD;AAED,eAAO,MAAM,gBAAgB,GAEzB,CAAC,SAAS,cAAc,WACxB,CAAC,SAAS,UAAU,yEAMnB,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,MAmDjC,CAAC"}
package/dist/index.js CHANGED
@@ -1,25 +1,24 @@
1
- import { checkEndpoint } from './endpoints/check';
2
- import { createPublishChangesEndpoint } from './endpoints/publish';
3
- import { PublishQueue } from './entities';
4
- import { trackCollectionChange, trackCollectionDelete, trackGlobalChange } from './hooks';
1
+ import { invalidateCollectionCache, invalidateCollectionCacheOnDelete, invalidateGlobalCache } from './hooks';
2
+ import { createDependencyGraph } from './utils/dependency-graph';
5
3
  import { getTrackedCollections } from './utils/tracked-collections';
6
- export const smartCachePlugin = ({ collections = [], globals = [], publishHandler, disableAutoTracking })=>(config)=>{
4
+ export { createRequestHandler } from './exports/create-request';
5
+ export const smartCachePlugin = ({ collections = [], globals = [], onInvalidate, disableAutoTracking })=>(config)=>{
7
6
  if (collections.length + globals.length === 0) {
8
7
  console.warn('[payload-smart-cache] No collections or globals are configured to track changes for, hence this plugin will have no effect.');
9
8
  return config;
10
9
  }
11
- config.admin ??= {};
12
- config.admin.components ??= {};
13
- config.admin.components.actions ??= [];
14
- config.admin.components.actions.push({
15
- path: 'payload-smart-cache/rsc#PublishButton'
10
+ const graph = createDependencyGraph(config);
11
+ const invalidationCallback = wrapInvalidationCallback({
12
+ collections,
13
+ globals,
14
+ onInvalidate: onInvalidate
16
15
  });
17
16
  config.globals ??= [];
18
17
  for (const global of config.globals){
19
18
  if (!globals.includes(global.slug)) continue;
20
19
  global.hooks ??= {};
21
20
  global.hooks.afterChange ??= [];
22
- global.hooks.afterChange.push(trackGlobalChange);
21
+ global.hooks.afterChange.push(invalidateGlobalCache(invalidationCallback));
23
22
  }
24
23
  config.collections ??= [];
25
24
  const collectionsToTrack = disableAutoTracking ? new Set(collections) : getTrackedCollections({
@@ -33,16 +32,27 @@ export const smartCachePlugin = ({ collections = [], globals = [], publishHandle
33
32
  if (!collectionsToTrack.has(collection.slug)) continue;
34
33
  collection.hooks ??= {};
35
34
  collection.hooks.afterChange ??= [];
36
- collection.hooks.afterChange.push(trackCollectionChange);
35
+ collection.hooks.afterChange.push(invalidateCollectionCache({
36
+ graph,
37
+ invalidationCallback
38
+ }));
37
39
  collection.hooks.afterDelete ??= [];
38
- collection.hooks.afterDelete.push(trackCollectionDelete);
40
+ collection.hooks.afterDelete.push(invalidateCollectionCacheOnDelete({
41
+ graph,
42
+ invalidationCallback
43
+ }));
39
44
  }
40
- config.collections.push(PublishQueue);
41
- config.endpoints ??= [];
42
- config.endpoints.push(createPublishChangesEndpoint(publishHandler));
43
- config.endpoints.push(checkEndpoint);
44
45
  return config;
45
46
  };
46
- export { createRequestHandler } from './exports/create-request';
47
+ function wrapInvalidationCallback({ collections, globals, onInvalidate }) {
48
+ if (!onInvalidate) return ()=>void 0;
49
+ const registeredCollections = new Set(collections);
50
+ const registeredGlobals = new Set(globals);
51
+ return (change)=>{
52
+ if (change.type === 'collection' && !registeredCollections.has(change.slug)) return;
53
+ if (change.type === 'global' && !registeredGlobals.has(change.slug)) return;
54
+ return onInvalidate(change);
55
+ };
56
+ }
47
57
 
48
58
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug, Plugin } from 'payload';\nimport { checkEndpoint } from '@/endpoints/check';\nimport { createPublishChangesEndpoint } from '@/endpoints/publish';\nimport { PublishQueue } from '@/entities';\nimport {\n trackCollectionChange,\n trackCollectionDelete,\n trackGlobalChange,\n} from '@/hooks';\nimport type { ChangedDocuments } from '@/types';\nimport { getTrackedCollections } from '@/utils/tracked-collections';\n\nexport interface SmartCachePluginConfig {\n /**\n * The collections to track changes for.\n * By default, collections referenced via relationship or upload fields\n * are automatically tracked as well.\n */\n collections?: CollectionSlug[];\n /**\n * The globals to track changes for.\n * Collections referenced by these globals via relationship or upload\n * fields are automatically tracked as well.\n */\n globals?: GlobalSlug[];\n /**\n * Disable automatic tracking of collections referenced by the configured ones.\n * @default false\n */\n disableAutoTracking?: boolean;\n publishHandler?: (changes: ChangedDocuments) => void | Promise<void>;\n}\n\nexport const smartCachePlugin =\n ({\n collections = [],\n globals = [],\n publishHandler,\n disableAutoTracking,\n }: SmartCachePluginConfig): Plugin =>\n (config) => {\n if (collections.length + globals.length === 0) {\n console.warn(\n '[payload-smart-cache] No collections or globals are configured to track changes for, hence this plugin will have no effect.',\n );\n return config;\n }\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.globals ??= [];\n for (const global of config.globals) {\n if (!globals.includes(global.slug as GlobalSlug)) continue;\n global.hooks ??= {};\n global.hooks.afterChange ??= [];\n global.hooks.afterChange.push(trackGlobalChange);\n }\n\n config.collections ??= [];\n const collectionsToTrack = disableAutoTracking\n ? new Set(collections)\n : getTrackedCollections(\n { collections, globals },\n { collections: config.collections, globals: config.globals },\n );\n for (const collection of config.collections) {\n if (!collectionsToTrack.has(collection.slug as CollectionSlug)) continue;\n collection.hooks ??= {};\n collection.hooks.afterChange ??= [];\n collection.hooks.afterChange.push(trackCollectionChange);\n collection.hooks.afterDelete ??= [];\n collection.hooks.afterDelete.push(trackCollectionDelete);\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 './exports/create-request';\nexport type { ChangedDocuments } from './types';\n"],"names":["checkEndpoint","createPublishChangesEndpoint","PublishQueue","trackCollectionChange","trackCollectionDelete","trackGlobalChange","getTrackedCollections","smartCachePlugin","collections","globals","publishHandler","disableAutoTracking","config","length","console","warn","admin","components","actions","push","path","global","includes","slug","hooks","afterChange","collectionsToTrack","Set","collection","has","afterDelete","endpoints","createRequestHandler"],"mappings":"AACA,SAASA,aAAa,QAAQ,oBAAoB;AAClD,SAASC,4BAA4B,QAAQ,sBAAsB;AACnE,SAASC,YAAY,QAAQ,aAAa;AAC1C,SACEC,qBAAqB,EACrBC,qBAAqB,EACrBC,iBAAiB,QACZ,UAAU;AAEjB,SAASC,qBAAqB,QAAQ,8BAA8B;AAuBpE,OAAO,MAAMC,mBACX,CAAC,EACCC,cAAc,EAAE,EAChBC,UAAU,EAAE,EACZC,cAAc,EACdC,mBAAmB,EACI,GACzB,CAACC;QACC,IAAIJ,YAAYK,MAAM,GAAGJ,QAAQI,MAAM,KAAK,GAAG;YAC7CC,QAAQC,IAAI,CACV;YAEF,OAAOH;QACT;QACAA,OAAOI,KAAK,KAAK,CAAC;QAClBJ,OAAOI,KAAK,CAACC,UAAU,KAAK,CAAC;QAC7BL,OAAOI,KAAK,CAACC,UAAU,CAACC,OAAO,KAAK,EAAE;QACtCN,OAAOI,KAAK,CAACC,UAAU,CAACC,OAAO,CAACC,IAAI,CAAC;YACnCC,MAAM;QACR;QAEAR,OAAOH,OAAO,KAAK,EAAE;QACrB,KAAK,MAAMY,UAAUT,OAAOH,OAAO,CAAE;YACnC,IAAI,CAACA,QAAQa,QAAQ,CAACD,OAAOE,IAAI,GAAiB;YAClDF,OAAOG,KAAK,KAAK,CAAC;YAClBH,OAAOG,KAAK,CAACC,WAAW,KAAK,EAAE;YAC/BJ,OAAOG,KAAK,CAACC,WAAW,CAACN,IAAI,CAACd;QAChC;QAEAO,OAAOJ,WAAW,KAAK,EAAE;QACzB,MAAMkB,qBAAqBf,sBACvB,IAAIgB,IAAInB,eACRF,sBACE;YAAEE;YAAaC;QAAQ,GACvB;YAAED,aAAaI,OAAOJ,WAAW;YAAEC,SAASG,OAAOH,OAAO;QAAC;QAEjE,KAAK,MAAMmB,cAAchB,OAAOJ,WAAW,CAAE;YAC3C,IAAI,CAACkB,mBAAmBG,GAAG,CAACD,WAAWL,IAAI,GAAqB;YAChEK,WAAWJ,KAAK,KAAK,CAAC;YACtBI,WAAWJ,KAAK,CAACC,WAAW,KAAK,EAAE;YACnCG,WAAWJ,KAAK,CAACC,WAAW,CAACN,IAAI,CAAChB;YAClCyB,WAAWJ,KAAK,CAACM,WAAW,KAAK,EAAE;YACnCF,WAAWJ,KAAK,CAACM,WAAW,CAACX,IAAI,CAACf;QACpC;QAEAQ,OAAOJ,WAAW,CAACW,IAAI,CAACjB;QAExBU,OAAOmB,SAAS,KAAK,EAAE;QACvBnB,OAAOmB,SAAS,CAACZ,IAAI,CAAClB,6BAA6BS;QACnDE,OAAOmB,SAAS,CAACZ,IAAI,CAACnB;QAEtB,OAAOY;IACT,EAAE;AAEJ,SAASoB,oBAAoB,QAAQ,2BAA2B"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug, Plugin } from 'payload';\nimport {\n invalidateCollectionCache,\n invalidateCollectionCacheOnDelete,\n invalidateGlobalCache,\n} from './hooks';\nimport type {\n DocumentInvalidation,\n DocumentInvalidationCallback,\n ResolvedPluginOptions,\n} from './types';\nimport { createDependencyGraph } from './utils/dependency-graph';\nimport { getTrackedCollections } from './utils/tracked-collections';\n\nexport {\n createRequestHandler,\n type RequestHandlerCacheOptions,\n} from './exports/create-request';\nexport type {\n DocumentInvalidation as InvalidationChange,\n DocumentInvalidationCallback as OnInvalidate,\n} from './types';\n\nexport interface SmartCachePluginConfig<\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n> {\n /**\n * The collections to track changes for.\n * By default, collections referenced via relationship or upload fields\n * are automatically tracked as well.\n */\n collections?: C[];\n /**\n * The globals to track changes for.\n * Collections referenced by these globals via relationship or upload\n * fields are automatically tracked as well.\n */\n globals?: G[];\n /**\n * Disable automatic tracking of collections referenced by the configured ones.\n * @default false\n */\n disableAutoTracking?: boolean;\n /**\n * Called when cache invalidation is triggered.\n * Only fires for collections/globals explicitly registered in the config.\n */\n onInvalidate?: DocumentInvalidationCallback<C, G>;\n}\n\nexport const smartCachePlugin =\n <\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n >({\n collections = [],\n globals = [],\n onInvalidate,\n disableAutoTracking,\n }: SmartCachePluginConfig<C, G>): Plugin =>\n (config) => {\n if (collections.length + globals.length === 0) {\n console.warn(\n '[payload-smart-cache] No collections or globals are configured to track changes for, hence this plugin will have no effect.',\n );\n return config;\n }\n\n const graph = createDependencyGraph(config);\n\n const invalidationCallback = wrapInvalidationCallback({\n collections,\n globals,\n onInvalidate: onInvalidate as DocumentInvalidationCallback,\n });\n\n config.globals ??= [];\n for (const global of config.globals) {\n if (!globals.includes(global.slug as G)) continue;\n global.hooks ??= {};\n global.hooks.afterChange ??= [];\n global.hooks.afterChange.push(\n invalidateGlobalCache(invalidationCallback),\n );\n }\n\n config.collections ??= [];\n const collectionsToTrack = disableAutoTracking\n ? new Set(collections)\n : getTrackedCollections(\n { collections, globals },\n { collections: config.collections, globals: config.globals },\n );\n for (const collection of config.collections) {\n if (!collectionsToTrack.has(collection.slug as CollectionSlug)) continue;\n collection.hooks ??= {};\n collection.hooks.afterChange ??= [];\n collection.hooks.afterChange.push(\n invalidateCollectionCache({ graph, invalidationCallback }),\n );\n collection.hooks.afterDelete ??= [];\n collection.hooks.afterDelete.push(\n invalidateCollectionCacheOnDelete({\n graph,\n invalidationCallback,\n }),\n );\n }\n\n return config;\n };\n\nfunction wrapInvalidationCallback({\n collections,\n globals,\n onInvalidate,\n}: ResolvedPluginOptions<\n 'collections' | 'globals',\n 'onInvalidate'\n>): DocumentInvalidationCallback {\n if (!onInvalidate) return () => void 0;\n\n const registeredCollections = new Set(collections);\n const registeredGlobals = new Set(globals);\n\n return (change: DocumentInvalidation) => {\n if (change.type === 'collection' && !registeredCollections.has(change.slug))\n return;\n if (change.type === 'global' && !registeredGlobals.has(change.slug)) return;\n return onInvalidate(change);\n };\n}\n"],"names":["invalidateCollectionCache","invalidateCollectionCacheOnDelete","invalidateGlobalCache","createDependencyGraph","getTrackedCollections","createRequestHandler","smartCachePlugin","collections","globals","onInvalidate","disableAutoTracking","config","length","console","warn","graph","invalidationCallback","wrapInvalidationCallback","global","includes","slug","hooks","afterChange","push","collectionsToTrack","Set","collection","has","afterDelete","registeredCollections","registeredGlobals","change","type"],"mappings":"AACA,SACEA,yBAAyB,EACzBC,iCAAiC,EACjCC,qBAAqB,QAChB,UAAU;AAMjB,SAASC,qBAAqB,QAAQ,2BAA2B;AACjE,SAASC,qBAAqB,QAAQ,8BAA8B;AAEpE,SACEC,oBAAoB,QAEf,2BAA2B;AAkClC,OAAO,MAAMC,mBACX,CAGE,EACAC,cAAc,EAAE,EAChBC,UAAU,EAAE,EACZC,YAAY,EACZC,mBAAmB,EACU,GAC/B,CAACC;QACC,IAAIJ,YAAYK,MAAM,GAAGJ,QAAQI,MAAM,KAAK,GAAG;YAC7CC,QAAQC,IAAI,CACV;YAEF,OAAOH;QACT;QAEA,MAAMI,QAAQZ,sBAAsBQ;QAEpC,MAAMK,uBAAuBC,yBAAyB;YACpDV;YACAC;YACAC,cAAcA;QAChB;QAEAE,OAAOH,OAAO,KAAK,EAAE;QACrB,KAAK,MAAMU,UAAUP,OAAOH,OAAO,CAAE;YACnC,IAAI,CAACA,QAAQW,QAAQ,CAACD,OAAOE,IAAI,GAAQ;YACzCF,OAAOG,KAAK,KAAK,CAAC;YAClBH,OAAOG,KAAK,CAACC,WAAW,KAAK,EAAE;YAC/BJ,OAAOG,KAAK,CAACC,WAAW,CAACC,IAAI,CAC3BrB,sBAAsBc;QAE1B;QAEAL,OAAOJ,WAAW,KAAK,EAAE;QACzB,MAAMiB,qBAAqBd,sBACvB,IAAIe,IAAIlB,eACRH,sBACE;YAAEG;YAAaC;QAAQ,GACvB;YAAED,aAAaI,OAAOJ,WAAW;YAAEC,SAASG,OAAOH,OAAO;QAAC;QAEjE,KAAK,MAAMkB,cAAcf,OAAOJ,WAAW,CAAE;YAC3C,IAAI,CAACiB,mBAAmBG,GAAG,CAACD,WAAWN,IAAI,GAAqB;YAChEM,WAAWL,KAAK,KAAK,CAAC;YACtBK,WAAWL,KAAK,CAACC,WAAW,KAAK,EAAE;YACnCI,WAAWL,KAAK,CAACC,WAAW,CAACC,IAAI,CAC/BvB,0BAA0B;gBAAEe;gBAAOC;YAAqB;YAE1DU,WAAWL,KAAK,CAACO,WAAW,KAAK,EAAE;YACnCF,WAAWL,KAAK,CAACO,WAAW,CAACL,IAAI,CAC/BtB,kCAAkC;gBAChCc;gBACAC;YACF;QAEJ;QAEA,OAAOL;IACT,EAAE;AAEJ,SAASM,yBAAyB,EAChCV,WAAW,EACXC,OAAO,EACPC,YAAY,EAIb;IACC,IAAI,CAACA,cAAc,OAAO,IAAM,KAAK;IAErC,MAAMoB,wBAAwB,IAAIJ,IAAIlB;IACtC,MAAMuB,oBAAoB,IAAIL,IAAIjB;IAElC,OAAO,CAACuB;QACN,IAAIA,OAAOC,IAAI,KAAK,gBAAgB,CAACH,sBAAsBF,GAAG,CAACI,OAAOX,IAAI,GACxE;QACF,IAAIW,OAAOC,IAAI,KAAK,YAAY,CAACF,kBAAkBH,GAAG,CAACI,OAAOX,IAAI,GAAG;QACrE,OAAOX,aAAasB;IACtB;AACF"}