payload-smart-cache 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -6
- package/dist/exports/cache.d.ts +9 -0
- package/dist/exports/cache.d.ts.map +1 -0
- package/dist/exports/cache.js +24 -0
- package/dist/exports/cache.js.map +1 -0
- package/dist/exports/create-tenant-request.d.ts +16 -0
- package/dist/exports/create-tenant-request.d.ts.map +1 -0
- package/dist/exports/create-tenant-request.js +29 -0
- package/dist/exports/create-tenant-request.js.map +1 -0
- package/dist/hooks.d.ts +8 -5
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +65 -29
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/resolve-tenant-id.d.ts +7 -0
- package/dist/utils/resolve-tenant-id.d.ts.map +1 -0
- package/dist/utils/resolve-tenant-id.js +20 -0
- package/dist/utils/resolve-tenant-id.js.map +1 -0
- package/dist/utils/tenant-scoped-collections.d.ts +11 -0
- package/dist/utils/tenant-scoped-collections.d.ts.map +1 -0
- package/dist/utils/tenant-scoped-collections.js +25 -0
- package/dist/utils/tenant-scoped-collections.js.map +1 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -78,12 +78,67 @@ const getPosts = createRequestHandler(
|
|
|
78
78
|
|
|
79
79
|
### Options
|
|
80
80
|
|
|
81
|
-
| Option | Type | Default
|
|
82
|
-
| --------------------- | ----------------------------------------------------------------------------- |
|
|
83
|
-
| `collections` | `CollectionSlug[]` | `[]`
|
|
84
|
-
| `globals` | `GlobalSlug[]` | `[]`
|
|
85
|
-
| `disableAutoTracking` | `boolean` | `false`
|
|
86
|
-
| `onInvalidate` | `(change) => void \| Promise<void>` | —
|
|
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 }`). |
|
|
87
|
+
| `tenantField` | `string` | `undefined` | Name of the tenant relationship field. When set, cache invalidation is scoped per-tenant. Collections without this field use global invalidation. |
|
|
88
|
+
|
|
89
|
+
### Multi-Tenant Support
|
|
90
|
+
|
|
91
|
+
For multi-tenant Payload applications using `@payloadcms/plugin-multi-tenant` (or a custom tenant field), set `tenantField` to scope cache invalidation per tenant. When a tenant's document changes, only that tenant's cached data is revalidated — other tenants' caches remain warm.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
smartCachePlugin({
|
|
95
|
+
collections: ["posts", "media", "members", "events"],
|
|
96
|
+
tenantField: "tenant", // matches your multi-tenant plugin field name
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Collections without the tenant field (e.g., shared content) are automatically detected and use global invalidation as before.
|
|
101
|
+
|
|
102
|
+
#### Tenant-scoped data fetching
|
|
103
|
+
|
|
104
|
+
**Next.js 15+ (unstable_cache)**
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { createTenantRequestHandler } from "payload-smart-cache";
|
|
108
|
+
|
|
109
|
+
const getPosts = createTenantRequestHandler(
|
|
110
|
+
async (tenantId: string, slug: string) => {
|
|
111
|
+
const payload = await getPayload({ config });
|
|
112
|
+
return payload.find({
|
|
113
|
+
collection: "posts",
|
|
114
|
+
where: { slug: { equals: slug }, tenant: { equals: tenantId } },
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
(tenantId) => ["posts", `posts:${tenantId}`],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Usage
|
|
121
|
+
const posts = await getPosts(tenantId, "my-post");
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Next.js 16+ ("use cache" directive)**
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { tenantCacheTag } from "payload-smart-cache/cache";
|
|
128
|
+
|
|
129
|
+
async function getPosts(tenantId: string) {
|
|
130
|
+
"use cache";
|
|
131
|
+
tenantCacheTag("posts", tenantId);
|
|
132
|
+
|
|
133
|
+
const payload = await getPayload({ config });
|
|
134
|
+
return payload.find({
|
|
135
|
+
collection: "posts",
|
|
136
|
+
where: { tenant: { equals: tenantId } },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`tenantCacheTag` requires `cacheComponents: true` in your `next.config`.
|
|
87
142
|
|
|
88
143
|
## Contributing
|
|
89
144
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies tenant-scoped cache tags for use with Next.js 16+ `"use cache"` directive.
|
|
3
|
+
* For each slug, registers both the base tag and a tenant-scoped tag (`slug:tenantId`).
|
|
4
|
+
*
|
|
5
|
+
* @param slugs - Collection/global slug(s) to tag.
|
|
6
|
+
* @param tenantId - The tenant ID to scope the cache tags to.
|
|
7
|
+
*/
|
|
8
|
+
export declare function tenantCacheTag(slugs: string | string[], tenantId: string): void;
|
|
9
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/exports/cache.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,QAAQ,EAAE,MAAM,GACf,IAAI,CAIN"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// cacheTag is available in Next.js 16+ (stable) or 15.x with experimental.dynamicIO.
|
|
2
|
+
// Since the project supports next >= 15.5.9, this import may not resolve in all versions.
|
|
3
|
+
// This module should only be used by consumers on Next.js 16+ with cacheComponents enabled.
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
+
// @ts-expect-error — cacheTag is not exported in Next.js 15 types
|
|
6
|
+
import { cacheTag } from 'next/cache';
|
|
7
|
+
/**
|
|
8
|
+
* Applies tenant-scoped cache tags for use with Next.js 16+ `"use cache"` directive.
|
|
9
|
+
* For each slug, registers both the base tag and a tenant-scoped tag (`slug:tenantId`).
|
|
10
|
+
*
|
|
11
|
+
* @param slugs - Collection/global slug(s) to tag.
|
|
12
|
+
* @param tenantId - The tenant ID to scope the cache tags to.
|
|
13
|
+
*/ export function tenantCacheTag(slugs, tenantId) {
|
|
14
|
+
const slugArray = Array.isArray(slugs) ? slugs : [
|
|
15
|
+
slugs
|
|
16
|
+
];
|
|
17
|
+
const tags = slugArray.flatMap((slug)=>[
|
|
18
|
+
slug,
|
|
19
|
+
`${slug}:${tenantId}`
|
|
20
|
+
]);
|
|
21
|
+
cacheTag(...tags);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/cache.ts"],"sourcesContent":["// cacheTag is available in Next.js 16+ (stable) or 15.x with experimental.dynamicIO.\n// Since the project supports next >= 15.5.9, this import may not resolve in all versions.\n// This module should only be used by consumers on Next.js 16+ with cacheComponents enabled.\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-expect-error — cacheTag is not exported in Next.js 15 types\nimport { cacheTag } from 'next/cache';\n\n/**\n * Applies tenant-scoped cache tags for use with Next.js 16+ `\"use cache\"` directive.\n * For each slug, registers both the base tag and a tenant-scoped tag (`slug:tenantId`).\n *\n * @param slugs - Collection/global slug(s) to tag.\n * @param tenantId - The tenant ID to scope the cache tags to.\n */\nexport function tenantCacheTag(\n slugs: string | string[],\n tenantId: string,\n): void {\n const slugArray = Array.isArray(slugs) ? slugs : [slugs];\n const tags = slugArray.flatMap((slug) => [slug, `${slug}:${tenantId}`]);\n cacheTag(...tags);\n}\n"],"names":["cacheTag","tenantCacheTag","slugs","tenantId","slugArray","Array","isArray","tags","flatMap","slug"],"mappings":"AAAA,qFAAqF;AACrF,0FAA0F;AAC1F,4FAA4F;AAC5F,6DAA6D;AAC7D,kEAAkE;AAClE,SAASA,QAAQ,QAAQ,aAAa;AAEtC;;;;;;CAMC,GACD,OAAO,SAASC,eACdC,KAAwB,EACxBC,QAAgB;IAEhB,MAAMC,YAAYC,MAAMC,OAAO,CAACJ,SAASA,QAAQ;QAACA;KAAM;IACxD,MAAMK,OAAOH,UAAUI,OAAO,CAAC,CAACC,OAAS;YAACA;YAAM,GAAGA,KAAK,CAAC,EAAEN,UAAU;SAAC;IACtEH,YAAYO;AACd"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TenantRequestHandlerOptions {
|
|
2
|
+
/** Time-based revalidation in seconds, or `false` to rely solely on tag-based revalidation. */
|
|
3
|
+
revalidate?: number | false;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a data-fetching function with tenant-scoped Next.js `unstable_cache`.
|
|
7
|
+
* The first argument to the handler is always the tenant ID, used to:
|
|
8
|
+
* - Generate tenant-scoped cache tags via `tagsFn`
|
|
9
|
+
* - Create a separate `unstable_cache` wrapper per tenant (memoized)
|
|
10
|
+
*
|
|
11
|
+
* @param handler - The async function to cache. First arg must be the tenant ID.
|
|
12
|
+
* @param tagsFn - Function that receives the tenant ID and returns cache tags.
|
|
13
|
+
* @param options - Additional cache options.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createTenantRequestHandler: <Data, Inputs extends [string, ...unknown[]]>(handler: (...inputs: Inputs) => Promise<Data>, tagsFn: (tenantId: string) => string[], options?: TenantRequestHandlerOptions) => ((...inputs: Inputs) => Promise<Data>);
|
|
16
|
+
//# sourceMappingURL=create-tenant-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-tenant-request.d.ts","sourceRoot":"","sources":["../../src/exports/create-tenant-request.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,2BAA2B;IAC1C,+FAA+F;IAC/F,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,GACrC,IAAI,EACJ,MAAM,SAAS,CAAC,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,WAE5B,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,UACrC,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,EAAE,YAC5B,2BAA2B,KACpC,CAAC,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAiBvC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { unstable_cache } from 'next/cache';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a data-fetching function with tenant-scoped Next.js `unstable_cache`.
|
|
4
|
+
* The first argument to the handler is always the tenant ID, used to:
|
|
5
|
+
* - Generate tenant-scoped cache tags via `tagsFn`
|
|
6
|
+
* - Create a separate `unstable_cache` wrapper per tenant (memoized)
|
|
7
|
+
*
|
|
8
|
+
* @param handler - The async function to cache. First arg must be the tenant ID.
|
|
9
|
+
* @param tagsFn - Function that receives the tenant ID and returns cache tags.
|
|
10
|
+
* @param options - Additional cache options.
|
|
11
|
+
*/ export const createTenantRequestHandler = (handler, tagsFn, options)=>{
|
|
12
|
+
const cache = new Map();
|
|
13
|
+
return (...inputs)=>{
|
|
14
|
+
const tenantId = inputs[0];
|
|
15
|
+
let cached = cache.get(tenantId);
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cached = unstable_cache(handler, [
|
|
18
|
+
tenantId
|
|
19
|
+
], {
|
|
20
|
+
tags: tagsFn(tenantId),
|
|
21
|
+
revalidate: options?.revalidate ?? false
|
|
22
|
+
});
|
|
23
|
+
cache.set(tenantId, cached);
|
|
24
|
+
}
|
|
25
|
+
return cached(...inputs);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=create-tenant-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/create-tenant-request.ts"],"sourcesContent":["import { unstable_cache } from 'next/cache';\n\nexport interface TenantRequestHandlerOptions {\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 tenant-scoped Next.js `unstable_cache`.\n * The first argument to the handler is always the tenant ID, used to:\n * - Generate tenant-scoped cache tags via `tagsFn`\n * - Create a separate `unstable_cache` wrapper per tenant (memoized)\n *\n * @param handler - The async function to cache. First arg must be the tenant ID.\n * @param tagsFn - Function that receives the tenant ID and returns cache tags.\n * @param options - Additional cache options.\n */\nexport const createTenantRequestHandler = <\n Data,\n Inputs extends [string, ...unknown[]],\n>(\n handler: (...inputs: Inputs) => Promise<Data>,\n tagsFn: (tenantId: string) => string[],\n options?: TenantRequestHandlerOptions,\n): ((...inputs: Inputs) => Promise<Data>) => {\n const cache = new Map<string, (...inputs: Inputs) => Promise<Data>>();\n\n return (...inputs: Inputs): Promise<Data> => {\n const tenantId = inputs[0];\n\n let cached = cache.get(tenantId);\n if (!cached) {\n cached = unstable_cache(handler, [tenantId], {\n tags: tagsFn(tenantId),\n revalidate: options?.revalidate ?? false,\n });\n cache.set(tenantId, cached);\n }\n\n return cached(...inputs);\n };\n};\n"],"names":["unstable_cache","createTenantRequestHandler","handler","tagsFn","options","cache","Map","inputs","tenantId","cached","get","tags","revalidate","set"],"mappings":"AAAA,SAASA,cAAc,QAAQ,aAAa;AAO5C;;;;;;;;;CASC,GACD,OAAO,MAAMC,6BAA6B,CAIxCC,SACAC,QACAC;IAEA,MAAMC,QAAQ,IAAIC;IAElB,OAAO,CAAC,GAAGC;QACT,MAAMC,WAAWD,MAAM,CAAC,EAAE;QAE1B,IAAIE,SAASJ,MAAMK,GAAG,CAACF;QACvB,IAAI,CAACC,QAAQ;YACXA,SAAST,eAAeE,SAAS;gBAACM;aAAS,EAAE;gBAC3CG,MAAMR,OAAOK;gBACbI,YAAYR,SAASQ,cAAc;YACrC;YACAP,MAAMQ,GAAG,CAACL,UAAUC;QACtB;QAEA,OAAOA,UAAUF;IACnB;AACF,EAAE"}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, GlobalAfterChangeHook } from 'payload';
|
|
1
|
+
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, CollectionSlug, GlobalAfterChangeHook } from 'payload';
|
|
2
2
|
import type { DocumentInvalidationCallback, DocumentWithStatus } from './types';
|
|
3
3
|
import type { EntitiesGraph } from './utils/dependency-graph';
|
|
4
|
-
|
|
4
|
+
interface CollectionHookConfig {
|
|
5
5
|
graph: EntitiesGraph;
|
|
6
6
|
invalidationCallback: DocumentInvalidationCallback | undefined;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
tenantField?: string;
|
|
8
|
+
tenantScopedCollections?: Set<CollectionSlug>;
|
|
9
|
+
}
|
|
10
|
+
export declare function invalidateCollectionCache({ graph, invalidationCallback, tenantField, tenantScopedCollections, }: CollectionHookConfig): CollectionAfterChangeHook<DocumentWithStatus>;
|
|
11
|
+
export declare function invalidateCollectionCacheOnDelete({ graph, invalidationCallback, tenantField, tenantScopedCollections, }: CollectionHookConfig & {
|
|
10
12
|
invalidationCallback: DocumentInvalidationCallback;
|
|
11
13
|
}): CollectionAfterDeleteHook<DocumentWithStatus>;
|
|
12
14
|
export declare function invalidateGlobalCache(invalidationCallback: DocumentInvalidationCallback): GlobalAfterChangeHook;
|
|
15
|
+
export {};
|
|
13
16
|
//# sourceMappingURL=hooks.d.ts.map
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,yBAAyB,EACzB,yBAAyB,
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,yBAAyB,EACzB,yBAAyB,EACzB,cAAc,EACd,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;AA4K9D,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,oBAAoB,EAAE,4BAA4B,GAAG,SAAS,CAAC;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC/C;AAED,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,oBAAoB,EACpB,WAAW,EACX,uBAAmC,GACpC,EAAE,oBAAoB,GAAG,yBAAyB,CAAC,kBAAkB,CAAC,CAyBtE;AAED,wBAAgB,iCAAiC,CAAC,EAChD,KAAK,EACL,oBAAoB,EACpB,WAAW,EACX,uBAAmC,GACpC,EAAE,oBAAoB,GAAG;IACxB,oBAAoB,EAAE,4BAA4B,CAAC;CACpD,GAAG,yBAAyB,CAAC,kBAAkB,CAAC,CAoBhD;AAED,wBAAgB,qBAAqB,CACnC,oBAAoB,EAAE,4BAA4B,GACjD,qBAAqB,CAWvB"}
|
package/dist/hooks.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { revalidateTag } from 'next/cache';
|
|
2
|
-
|
|
2
|
+
import { resolveTenantId } from './utils/resolve-tenant-id';
|
|
3
|
+
function buildTag(slug, tenantId, tenantScopedCollections) {
|
|
4
|
+
if (tenantId && tenantScopedCollections.has(slug)) {
|
|
5
|
+
return `${slug}:${tenantId}`;
|
|
6
|
+
}
|
|
7
|
+
return slug;
|
|
8
|
+
}
|
|
9
|
+
async function invalidateWithDependents(payload, { graph, invalidationCallback, collection, ids, tenantId, tenantConfig }) {
|
|
3
10
|
const tagsToInvalidate = new Set();
|
|
4
|
-
tagsToInvalidate.add(collection);
|
|
5
|
-
await walkDependents(graph, payload, collection, ids, new Set());
|
|
11
|
+
tagsToInvalidate.add(buildTag(collection, tenantId, tenantConfig.tenantScopedCollections));
|
|
12
|
+
await walkDependents(graph, payload, collection, ids, new Set(), tenantId, tenantConfig);
|
|
6
13
|
for (const tag of tagsToInvalidate){
|
|
7
14
|
revalidateTag(tag);
|
|
8
15
|
}
|
|
@@ -10,10 +17,11 @@ async function invalidateWithDependents(payload, { graph, invalidationCallback,
|
|
|
10
17
|
await invalidationCallback?.({
|
|
11
18
|
type: 'collection',
|
|
12
19
|
slug: collection,
|
|
13
|
-
docID: id
|
|
20
|
+
docID: id,
|
|
21
|
+
tenantId
|
|
14
22
|
});
|
|
15
23
|
}
|
|
16
|
-
async function walkDependents(graph, payload, changedCollection, changedIds, visited) {
|
|
24
|
+
async function walkDependents(graph, payload, changedCollection, changedIds, visited, tenantId, tenantConfig) {
|
|
17
25
|
const dependents = graph.getDependants(changedCollection);
|
|
18
26
|
if (dependents.length === 0) return;
|
|
19
27
|
for (const dependent of dependents){
|
|
@@ -22,28 +30,41 @@ async function invalidateWithDependents(payload, { graph, invalidationCallback,
|
|
|
22
30
|
continue;
|
|
23
31
|
}
|
|
24
32
|
if (visited.has(dependent.entity.slug)) continue;
|
|
33
|
+
const depIsTenantScoped = tenantConfig.tenantScopedCollections.has(dependent.entity.slug);
|
|
34
|
+
const effectiveTenantId = depIsTenantScoped ? tenantId : undefined;
|
|
25
35
|
const allAffectedItems = new Map();
|
|
26
36
|
for (const field of dependent.fields){
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
[`${field.field}.value`]: {
|
|
38
|
-
in: changedIds
|
|
39
|
-
}
|
|
37
|
+
const baseWhere = field.polymorphic ? {
|
|
38
|
+
and: [
|
|
39
|
+
{
|
|
40
|
+
[`${field.field}.relationTo`]: {
|
|
41
|
+
equals: changedCollection
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
[`${field.field}.value`]: {
|
|
46
|
+
in: changedIds
|
|
40
47
|
}
|
|
41
|
-
]
|
|
42
|
-
} : {
|
|
43
|
-
[field.field]: {
|
|
44
|
-
in: changedIds
|
|
45
48
|
}
|
|
49
|
+
]
|
|
50
|
+
} : {
|
|
51
|
+
[field.field]: {
|
|
52
|
+
in: changedIds
|
|
46
53
|
}
|
|
54
|
+
};
|
|
55
|
+
const where = effectiveTenantId && tenantConfig.tenantField ? {
|
|
56
|
+
and: [
|
|
57
|
+
baseWhere,
|
|
58
|
+
{
|
|
59
|
+
[tenantConfig.tenantField]: {
|
|
60
|
+
equals: effectiveTenantId
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
} : baseWhere;
|
|
65
|
+
const { docs } = await payload.find({
|
|
66
|
+
collection: dependent.entity.slug,
|
|
67
|
+
where
|
|
47
68
|
});
|
|
48
69
|
for (const item of docs){
|
|
49
70
|
allAffectedItems.set(item.id.toString(), item);
|
|
@@ -52,44 +73,59 @@ async function invalidateWithDependents(payload, { graph, invalidationCallback,
|
|
|
52
73
|
const affectedItems = Array.from(allAffectedItems.values());
|
|
53
74
|
visited.add(dependent.entity.slug);
|
|
54
75
|
if (affectedItems.length === 0) continue;
|
|
55
|
-
tagsToInvalidate.add(dependent.entity.slug);
|
|
76
|
+
tagsToInvalidate.add(buildTag(dependent.entity.slug, effectiveTenantId, tenantConfig.tenantScopedCollections));
|
|
56
77
|
for (const item of affectedItems){
|
|
57
78
|
await invalidationCallback?.({
|
|
58
79
|
type: 'collection',
|
|
59
80
|
slug: dependent.entity.slug,
|
|
60
|
-
docID: item.id.toString()
|
|
81
|
+
docID: item.id.toString(),
|
|
82
|
+
tenantId: effectiveTenantId
|
|
61
83
|
});
|
|
62
84
|
}
|
|
63
|
-
await walkDependents(graph, payload, dependent.entity.slug, affectedItems.map((item)=>item.id.toString()), visited);
|
|
85
|
+
await walkDependents(graph, payload, dependent.entity.slug, affectedItems.map((item)=>item.id.toString()), visited, effectiveTenantId, tenantConfig);
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
}
|
|
67
|
-
export function invalidateCollectionCache({ graph, invalidationCallback }) {
|
|
89
|
+
export function invalidateCollectionCache({ graph, invalidationCallback, tenantField, tenantScopedCollections = new Set() }) {
|
|
90
|
+
const tenantConfig = {
|
|
91
|
+
tenantField,
|
|
92
|
+
tenantScopedCollections
|
|
93
|
+
};
|
|
68
94
|
return async ({ req, doc, collection, previousDoc })=>{
|
|
69
95
|
if (req.context.skipRevalidation) return;
|
|
70
96
|
if (collection.versions?.drafts) {
|
|
71
97
|
if (doc._status === 'draft' && previousDoc._status !== 'published') return;
|
|
72
98
|
}
|
|
99
|
+
const tenantId = resolveTenantId(doc, tenantField);
|
|
73
100
|
await invalidateWithDependents(req.payload, {
|
|
74
101
|
graph,
|
|
75
102
|
invalidationCallback,
|
|
76
103
|
collection: collection.slug,
|
|
77
104
|
ids: [
|
|
78
105
|
doc.id.toString()
|
|
79
|
-
]
|
|
106
|
+
],
|
|
107
|
+
tenantId,
|
|
108
|
+
tenantConfig
|
|
80
109
|
});
|
|
81
110
|
};
|
|
82
111
|
}
|
|
83
|
-
export function invalidateCollectionCacheOnDelete({ graph, invalidationCallback }) {
|
|
112
|
+
export function invalidateCollectionCacheOnDelete({ graph, invalidationCallback, tenantField, tenantScopedCollections = new Set() }) {
|
|
113
|
+
const tenantConfig = {
|
|
114
|
+
tenantField,
|
|
115
|
+
tenantScopedCollections
|
|
116
|
+
};
|
|
84
117
|
return async ({ req, doc, collection })=>{
|
|
85
118
|
if (req.context.skipRevalidation) return;
|
|
119
|
+
const tenantId = resolveTenantId(doc, tenantField);
|
|
86
120
|
await invalidateWithDependents(req.payload, {
|
|
87
121
|
graph,
|
|
88
122
|
invalidationCallback,
|
|
89
123
|
collection: collection.slug,
|
|
90
124
|
ids: [
|
|
91
125
|
doc.id.toString()
|
|
92
|
-
]
|
|
126
|
+
],
|
|
127
|
+
tenantId,
|
|
128
|
+
tenantConfig
|
|
93
129
|
});
|
|
94
130
|
};
|
|
95
131
|
}
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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';\nimport { resolveTenantId } from './utils/resolve-tenant-id';\n\ninterface TenantConfig {\n tenantField?: string;\n tenantScopedCollections: Set<CollectionSlug>;\n}\n\nfunction buildTag(\n slug: string,\n tenantId: string | undefined,\n tenantScopedCollections: Set<CollectionSlug>,\n): string {\n if (tenantId && tenantScopedCollections.has(slug as CollectionSlug)) {\n return `${slug}:${tenantId}`;\n }\n return slug;\n}\n\nasync function invalidateWithDependents(\n payload: BasePayload,\n {\n graph,\n invalidationCallback,\n collection,\n ids,\n tenantId,\n tenantConfig,\n }: {\n graph: EntitiesGraph;\n invalidationCallback: DocumentInvalidationCallback | undefined;\n collection: CollectionSlug;\n ids: string[];\n tenantId: string | undefined;\n tenantConfig: TenantConfig;\n },\n): Promise<void> {\n const tagsToInvalidate = new Set<string>();\n\n tagsToInvalidate.add(\n buildTag(collection, tenantId, tenantConfig.tenantScopedCollections),\n );\n\n await walkDependents(\n graph,\n payload,\n collection,\n ids,\n new Set(),\n tenantId,\n tenantConfig,\n );\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 tenantId,\n });\n }\n\n async function walkDependents(\n graph: EntitiesGraph,\n payload: BasePayload,\n changedCollection: CollectionSlug,\n changedIds: string[],\n visited: Set<string>,\n tenantId: string | undefined,\n tenantConfig: TenantConfig,\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 depIsTenantScoped = tenantConfig.tenantScopedCollections.has(\n dependent.entity.slug as CollectionSlug,\n );\n const effectiveTenantId = depIsTenantScoped ? tenantId : undefined;\n\n const allAffectedItems = new Map<string, { id: string | number }>();\n\n for (const field of dependent.fields) {\n const baseWhere = 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 const where =\n effectiveTenantId && tenantConfig.tenantField\n ? {\n and: [\n baseWhere,\n {\n [tenantConfig.tenantField]: {\n equals: effectiveTenantId,\n },\n },\n ],\n }\n : baseWhere;\n\n const { docs } = await payload.find({\n collection: dependent.entity.slug,\n where,\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(\n buildTag(\n dependent.entity.slug,\n effectiveTenantId,\n tenantConfig.tenantScopedCollections,\n ),\n );\n\n for (const item of affectedItems) {\n await invalidationCallback?.({\n type: 'collection',\n slug: dependent.entity.slug,\n docID: item.id.toString(),\n tenantId: effectiveTenantId,\n });\n }\n\n await walkDependents(\n graph,\n payload,\n dependent.entity.slug,\n affectedItems.map((item) => item.id.toString()),\n visited,\n effectiveTenantId,\n tenantConfig,\n );\n }\n }\n}\n\ninterface CollectionHookConfig {\n graph: EntitiesGraph;\n invalidationCallback: DocumentInvalidationCallback | undefined;\n tenantField?: string;\n tenantScopedCollections?: Set<CollectionSlug>;\n}\n\nexport function invalidateCollectionCache({\n graph,\n invalidationCallback,\n tenantField,\n tenantScopedCollections = new Set(),\n}: CollectionHookConfig): CollectionAfterChangeHook<DocumentWithStatus> {\n const tenantConfig: TenantConfig = { tenantField, tenantScopedCollections };\n\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 const tenantId = resolveTenantId(\n doc as Record<string, unknown>,\n tenantField,\n );\n\n await invalidateWithDependents(req.payload, {\n graph,\n invalidationCallback,\n collection: collection.slug,\n ids: [doc.id.toString()],\n tenantId,\n tenantConfig,\n });\n };\n}\n\nexport function invalidateCollectionCacheOnDelete({\n graph,\n invalidationCallback,\n tenantField,\n tenantScopedCollections = new Set(),\n}: CollectionHookConfig & {\n invalidationCallback: DocumentInvalidationCallback;\n}): CollectionAfterDeleteHook<DocumentWithStatus> {\n const tenantConfig: TenantConfig = { tenantField, tenantScopedCollections };\n\n return async ({ req, doc, collection }) => {\n if (req.context.skipRevalidation) return;\n\n const tenantId = resolveTenantId(\n doc as Record<string, unknown>,\n tenantField,\n );\n\n await invalidateWithDependents(req.payload, {\n graph,\n invalidationCallback,\n collection: collection.slug,\n ids: [doc.id.toString()],\n tenantId,\n tenantConfig,\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","resolveTenantId","buildTag","slug","tenantId","tenantScopedCollections","has","invalidateWithDependents","payload","graph","invalidationCallback","collection","ids","tenantConfig","tagsToInvalidate","Set","add","walkDependents","tag","id","type","docID","changedCollection","changedIds","visited","dependents","getDependants","length","dependent","entity","depIsTenantScoped","effectiveTenantId","undefined","allAffectedItems","Map","field","fields","baseWhere","polymorphic","and","equals","in","where","tenantField","docs","find","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,6 +1,7 @@
|
|
|
1
1
|
import type { CollectionSlug, GlobalSlug, Plugin } from 'payload';
|
|
2
2
|
import type { DocumentInvalidationCallback } from './types';
|
|
3
3
|
export { createRequestHandler, type RequestHandlerCacheOptions, } from './exports/create-request';
|
|
4
|
+
export { createTenantRequestHandler, type TenantRequestHandlerOptions, } from './exports/create-tenant-request';
|
|
4
5
|
export type { DocumentInvalidation as InvalidationChange, DocumentInvalidationCallback as OnInvalidate, } from './types';
|
|
5
6
|
export interface SmartCachePluginConfig<C extends CollectionSlug = CollectionSlug, G extends GlobalSlug = GlobalSlug> {
|
|
6
7
|
/**
|
|
@@ -25,6 +26,13 @@ export interface SmartCachePluginConfig<C extends CollectionSlug = CollectionSlu
|
|
|
25
26
|
* Only fires for collections/globals explicitly registered in the config.
|
|
26
27
|
*/
|
|
27
28
|
onInvalidate?: DocumentInvalidationCallback<C, G>;
|
|
29
|
+
/**
|
|
30
|
+
* Name of the tenant relationship field on your collections.
|
|
31
|
+
* When set, cache invalidation is scoped per-tenant.
|
|
32
|
+
* Collections without this field are treated as shared and use global invalidation.
|
|
33
|
+
* Must match the field name used by `@payloadcms/plugin-multi-tenant` or your custom tenant field.
|
|
34
|
+
*/
|
|
35
|
+
tenantField?: string;
|
|
28
36
|
}
|
|
29
|
-
export declare const smartCachePlugin: <C extends CollectionSlug = string, G extends GlobalSlug = string>({ collections, globals, onInvalidate, disableAutoTracking, }: SmartCachePluginConfig<C, G>) => Plugin;
|
|
37
|
+
export declare const smartCachePlugin: <C extends CollectionSlug = string, G extends GlobalSlug = string>({ collections, globals, onInvalidate, disableAutoTracking, tenantField, }: SmartCachePluginConfig<C, G>) => Plugin;
|
|
30
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;AAMlE,OAAO,KAAK,EAEV,4BAA4B,EAE7B,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;AAKjB,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,GAChC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,0BAA0B,EAC1B,KAAK,2BAA2B,GACjC,MAAM,iCAAiC,CAAC;AACzC,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;IAClD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,GAEzB,CAAC,SAAS,cAAc,WACxB,CAAC,SAAS,UAAU,sFAOnB,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,MA8DjC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { invalidateCollectionCache, invalidateCollectionCacheOnDelete, invalidateGlobalCache } from './hooks';
|
|
2
2
|
import { createDependencyGraph } from './utils/dependency-graph';
|
|
3
|
+
import { getTenantScopedCollections } from './utils/tenant-scoped-collections';
|
|
3
4
|
import { getTrackedCollections } from './utils/tracked-collections';
|
|
4
5
|
export { createRequestHandler } from './exports/create-request';
|
|
5
|
-
export
|
|
6
|
+
export { createTenantRequestHandler } from './exports/create-tenant-request';
|
|
7
|
+
export const smartCachePlugin = ({ collections = [], globals = [], onInvalidate, disableAutoTracking, tenantField })=>(config)=>{
|
|
6
8
|
if (collections.length + globals.length === 0) {
|
|
7
9
|
console.warn('[payload-smart-cache] No collections or globals are configured to track changes for, hence this plugin will have no effect.');
|
|
8
10
|
return config;
|
|
@@ -21,6 +23,7 @@ export const smartCachePlugin = ({ collections = [], globals = [], onInvalidate,
|
|
|
21
23
|
global.hooks.afterChange.push(invalidateGlobalCache(invalidationCallback));
|
|
22
24
|
}
|
|
23
25
|
config.collections ??= [];
|
|
26
|
+
const tenantScopedCollections = getTenantScopedCollections(config.collections, tenantField);
|
|
24
27
|
const collectionsToTrack = disableAutoTracking ? new Set(collections) : getTrackedCollections({
|
|
25
28
|
collections,
|
|
26
29
|
globals
|
|
@@ -34,12 +37,16 @@ export const smartCachePlugin = ({ collections = [], globals = [], onInvalidate,
|
|
|
34
37
|
collection.hooks.afterChange ??= [];
|
|
35
38
|
collection.hooks.afterChange.push(invalidateCollectionCache({
|
|
36
39
|
graph,
|
|
37
|
-
invalidationCallback
|
|
40
|
+
invalidationCallback,
|
|
41
|
+
tenantField,
|
|
42
|
+
tenantScopedCollections
|
|
38
43
|
}));
|
|
39
44
|
collection.hooks.afterDelete ??= [];
|
|
40
45
|
collection.hooks.afterDelete.push(invalidateCollectionCacheOnDelete({
|
|
41
46
|
graph,
|
|
42
|
-
invalidationCallback
|
|
47
|
+
invalidationCallback,
|
|
48
|
+
tenantField,
|
|
49
|
+
tenantScopedCollections
|
|
43
50
|
}));
|
|
44
51
|
}
|
|
45
52
|
return config;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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({
|
|
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 { getTenantScopedCollections } from './utils/tenant-scoped-collections';\nimport { getTrackedCollections } from './utils/tracked-collections';\n\nexport {\n createRequestHandler,\n type RequestHandlerCacheOptions,\n} from './exports/create-request';\nexport {\n createTenantRequestHandler,\n type TenantRequestHandlerOptions,\n} from './exports/create-tenant-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 * Name of the tenant relationship field on your collections.\n * When set, cache invalidation is scoped per-tenant.\n * Collections without this field are treated as shared and use global invalidation.\n * Must match the field name used by `@payloadcms/plugin-multi-tenant` or your custom tenant field.\n */\n tenantField?: string;\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 tenantField,\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 tenantScopedCollections = getTenantScopedCollections(\n config.collections,\n tenantField,\n );\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({\n graph,\n invalidationCallback,\n tenantField,\n tenantScopedCollections,\n }),\n );\n collection.hooks.afterDelete ??= [];\n collection.hooks.afterDelete.push(\n invalidateCollectionCacheOnDelete({\n graph,\n invalidationCallback,\n tenantField,\n tenantScopedCollections,\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","getTenantScopedCollections","getTrackedCollections","createRequestHandler","createTenantRequestHandler","smartCachePlugin","collections","globals","onInvalidate","disableAutoTracking","tenantField","config","length","console","warn","graph","invalidationCallback","wrapInvalidationCallback","global","includes","slug","hooks","afterChange","push","tenantScopedCollections","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,0BAA0B,QAAQ,oCAAoC;AAC/E,SAASC,qBAAqB,QAAQ,8BAA8B;AAEpE,SACEC,oBAAoB,QAEf,2BAA2B;AAClC,SACEC,0BAA0B,QAErB,kCAAkC;AAyCzC,OAAO,MAAMC,mBACX,CAGE,EACAC,cAAc,EAAE,EAChBC,UAAU,EAAE,EACZC,YAAY,EACZC,mBAAmB,EACnBC,WAAW,EACkB,GAC/B,CAACC;QACC,IAAIL,YAAYM,MAAM,GAAGL,QAAQK,MAAM,KAAK,GAAG;YAC7CC,QAAQC,IAAI,CACV;YAEF,OAAOH;QACT;QAEA,MAAMI,QAAQf,sBAAsBW;QAEpC,MAAMK,uBAAuBC,yBAAyB;YACpDX;YACAC;YACAC,cAAcA;QAChB;QAEAG,OAAOJ,OAAO,KAAK,EAAE;QACrB,KAAK,MAAMW,UAAUP,OAAOJ,OAAO,CAAE;YACnC,IAAI,CAACA,QAAQY,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,CAC3BxB,sBAAsBiB;QAE1B;QAEAL,OAAOL,WAAW,KAAK,EAAE;QACzB,MAAMkB,0BAA0BvB,2BAC9BU,OAAOL,WAAW,EAClBI;QAEF,MAAMe,qBAAqBhB,sBACvB,IAAIiB,IAAIpB,eACRJ,sBACE;YAAEI;YAAaC;QAAQ,GACvB;YAAED,aAAaK,OAAOL,WAAW;YAAEC,SAASI,OAAOJ,OAAO;QAAC;QAEjE,KAAK,MAAMoB,cAAchB,OAAOL,WAAW,CAAE;YAC3C,IAAI,CAACmB,mBAAmBG,GAAG,CAACD,WAAWP,IAAI,GAAqB;YAChEO,WAAWN,KAAK,KAAK,CAAC;YACtBM,WAAWN,KAAK,CAACC,WAAW,KAAK,EAAE;YACnCK,WAAWN,KAAK,CAACC,WAAW,CAACC,IAAI,CAC/B1B,0BAA0B;gBACxBkB;gBACAC;gBACAN;gBACAc;YACF;YAEFG,WAAWN,KAAK,CAACQ,WAAW,KAAK,EAAE;YACnCF,WAAWN,KAAK,CAACQ,WAAW,CAACN,IAAI,CAC/BzB,kCAAkC;gBAChCiB;gBACAC;gBACAN;gBACAc;YACF;QAEJ;QAEA,OAAOb;IACT,EAAE;AAEJ,SAASM,yBAAyB,EAChCX,WAAW,EACXC,OAAO,EACPC,YAAY,EAIb;IACC,IAAI,CAACA,cAAc,OAAO,IAAM,KAAK;IAErC,MAAMsB,wBAAwB,IAAIJ,IAAIpB;IACtC,MAAMyB,oBAAoB,IAAIL,IAAInB;IAElC,OAAO,CAACyB;QACN,IAAIA,OAAOC,IAAI,KAAK,gBAAgB,CAACH,sBAAsBF,GAAG,CAACI,OAAOZ,IAAI,GACxE;QACF,IAAIY,OAAOC,IAAI,KAAK,YAAY,CAACF,kBAAkBH,GAAG,CAACI,OAAOZ,IAAI,GAAG;QACrE,OAAOZ,aAAawB;IACtB;AACF"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,GAAG,CAAC;AAEhD,MAAM,MAAM,qBAAqB,CAC/B,GAAG,SAAS,MAAM,sBAAsB,GAAG,MAAM,sBAAsB,EACvE,GAAG,SAAS,MAAM,sBAAsB,GAAG,KAAK,IAC9C,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,GAAG,CAAC,GAC7C,IAAI,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;AAEpC,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,CAAC;AAErD,iEAAiE;AACjE,MAAM,MAAM,oBAAoB,CAC9B,CAAC,SAAS,cAAc,GAAG,cAAc,EACzC,CAAC,SAAS,UAAU,GAAG,UAAU,IAE/B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,GAAG,CAAC;AAEhD,MAAM,MAAM,qBAAqB,CAC/B,GAAG,SAAS,MAAM,sBAAsB,GAAG,MAAM,sBAAsB,EACvE,GAAG,SAAS,MAAM,sBAAsB,GAAG,KAAK,IAC9C,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,GAAG,CAAC,GAC7C,IAAI,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;AAEpC,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,UAAU,CAAC;AAErD,iEAAiE;AACjE,MAAM,MAAM,oBAAoB,CAC9B,CAAC,SAAS,cAAc,GAAG,cAAc,EACzC,CAAC,SAAS,UAAU,GAAG,UAAU,IAE/B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC;AAEhC,mDAAmD;AACnD,MAAM,MAAM,4BAA4B,CACtC,CAAC,SAAS,cAAc,GAAG,cAAc,EACzC,CAAC,SAAS,UAAU,GAAG,UAAU,IAC/B,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEjE,8DAA8D;AAC9D,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG;IAC5C,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;CACjC,CAAC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { DocumentID } from '@davincicoding/payload-plugin-kit';\nimport type { CollectionSlug, GlobalSlug, TypeWithID } from 'payload';\nimport type { SmartCachePluginConfig } from '.';\n\nexport type ResolvedPluginOptions<\n Req extends keyof SmartCachePluginConfig = keyof SmartCachePluginConfig,\n Opt extends keyof SmartCachePluginConfig = never,\n> = Pick<Required<SmartCachePluginConfig>, Req> &\n Pick<SmartCachePluginConfig, Opt>;\n\nexport type EntitySlug = CollectionSlug | GlobalSlug;\n\n/** Discriminated union passed to the `onInvalidate` callback. */\nexport type DocumentInvalidation<\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n> =\n | { type: 'collection'; slug: C; docID: DocumentID }\n | { type: 'global'; slug: G };\n\n/** Callback type for the `onInvalidate` option. */\nexport type DocumentInvalidationCallback<\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n> = (change: DocumentInvalidation<C, G>) => void | Promise<void>;\n\n/** A document with an optional draft/publish status field. */\nexport type DocumentWithStatus = TypeWithID & {\n _status?: 'published' | 'draft';\n};\n"],"names":[],"mappings":"AA0BA,4DAA4D,GAC5D,WAEE"}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { DocumentID } from '@davincicoding/payload-plugin-kit';\nimport type { CollectionSlug, GlobalSlug, TypeWithID } from 'payload';\nimport type { SmartCachePluginConfig } from '.';\n\nexport type ResolvedPluginOptions<\n Req extends keyof SmartCachePluginConfig = keyof SmartCachePluginConfig,\n Opt extends keyof SmartCachePluginConfig = never,\n> = Pick<Required<SmartCachePluginConfig>, Req> &\n Pick<SmartCachePluginConfig, Opt>;\n\nexport type EntitySlug = CollectionSlug | GlobalSlug;\n\n/** Discriminated union passed to the `onInvalidate` callback. */\nexport type DocumentInvalidation<\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n> =\n | { type: 'collection'; slug: C; docID: DocumentID; tenantId?: string }\n | { type: 'global'; slug: G };\n\n/** Callback type for the `onInvalidate` option. */\nexport type DocumentInvalidationCallback<\n C extends CollectionSlug = CollectionSlug,\n G extends GlobalSlug = GlobalSlug,\n> = (change: DocumentInvalidation<C, G>) => void | Promise<void>;\n\n/** A document with an optional draft/publish status field. */\nexport type DocumentWithStatus = TypeWithID & {\n _status?: 'published' | 'draft';\n};\n"],"names":[],"mappings":"AA0BA,4DAA4D,GAC5D,WAEE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the tenant ID from a document.
|
|
3
|
+
* Handles raw ID values (string/number) and populated relationship objects.
|
|
4
|
+
* Returns `undefined` if `tenantField` is not set or the field is absent/null.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveTenantId(doc: Record<string, unknown>, tenantField: string | undefined): string | undefined;
|
|
7
|
+
//# sourceMappingURL=resolve-tenant-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-tenant-id.d.ts","sourceRoot":"","sources":["../../src/utils/resolve-tenant-id.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAapB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function hasId(value) {
|
|
2
|
+
return 'id' in value;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Extracts the tenant ID from a document.
|
|
6
|
+
* Handles raw ID values (string/number) and populated relationship objects.
|
|
7
|
+
* Returns `undefined` if `tenantField` is not set or the field is absent/null.
|
|
8
|
+
*/ export function resolveTenantId(doc, tenantField) {
|
|
9
|
+
if (!tenantField) return undefined;
|
|
10
|
+
const value = doc[tenantField];
|
|
11
|
+
if (value == null) return undefined;
|
|
12
|
+
if (typeof value === 'string') return value;
|
|
13
|
+
if (typeof value === 'number') return value.toString();
|
|
14
|
+
if (typeof value === 'object' && hasId(value)) {
|
|
15
|
+
return String(value.id);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=resolve-tenant-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/resolve-tenant-id.ts"],"sourcesContent":["function hasId(value: object): value is { id: unknown } {\n return 'id' in value;\n}\n\n/**\n * Extracts the tenant ID from a document.\n * Handles raw ID values (string/number) and populated relationship objects.\n * Returns `undefined` if `tenantField` is not set or the field is absent/null.\n */\nexport function resolveTenantId(\n doc: Record<string, unknown>,\n tenantField: string | undefined,\n): string | undefined {\n if (!tenantField) return undefined;\n\n const value = doc[tenantField];\n if (value == null) return undefined;\n\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return value.toString();\n if (typeof value === 'object' && hasId(value)) {\n return String(value.id);\n }\n\n return undefined;\n}\n"],"names":["hasId","value","resolveTenantId","doc","tenantField","undefined","toString","String","id"],"mappings":"AAAA,SAASA,MAAMC,KAAa;IAC1B,OAAO,QAAQA;AACjB;AAEA;;;;CAIC,GACD,OAAO,SAASC,gBACdC,GAA4B,EAC5BC,WAA+B;IAE/B,IAAI,CAACA,aAAa,OAAOC;IAEzB,MAAMJ,QAAQE,GAAG,CAACC,YAAY;IAC9B,IAAIH,SAAS,MAAM,OAAOI;IAE1B,IAAI,OAAOJ,UAAU,UAAU,OAAOA;IACtC,IAAI,OAAOA,UAAU,UAAU,OAAOA,MAAMK,QAAQ;IACpD,IAAI,OAAOL,UAAU,YAAYD,MAAMC,QAAQ;QAC7C,OAAOM,OAAON,MAAMO,EAAE;IACxB;IAEA,OAAOH;AACT"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CollectionConfig, CollectionSlug } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Determines which collections have the tenant field by scanning their field configs.
|
|
4
|
+
* Returns a Set of collection slugs that are tenant-tenantScopedSlugs.
|
|
5
|
+
*
|
|
6
|
+
* Only matches top-level field names (or fields inside unnamed containers like
|
|
7
|
+
* unnamed tabs/rows). Fields nested inside named groups/tabs don't match because
|
|
8
|
+
* the multi-tenant plugin adds the tenant field at the top level.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTenantScopedCollections(collections: CollectionConfig[], tenantField: string | undefined): Set<CollectionSlug>;
|
|
11
|
+
//# sourceMappingURL=tenant-scoped-collections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-scoped-collections.d.ts","sourceRoot":"","sources":["../../src/utils/tenant-scoped-collections.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAS,MAAM,SAAS,CAAC;AAEvE;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,GAAG,CAAC,cAAc,CAAC,CAqBrB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { findFields } from '@davincicoding/payload-plugin-kit';
|
|
2
|
+
/**
|
|
3
|
+
* Determines which collections have the tenant field by scanning their field configs.
|
|
4
|
+
* Returns a Set of collection slugs that are tenant-tenantScopedSlugs.
|
|
5
|
+
*
|
|
6
|
+
* Only matches top-level field names (or fields inside unnamed containers like
|
|
7
|
+
* unnamed tabs/rows). Fields nested inside named groups/tabs don't match because
|
|
8
|
+
* the multi-tenant plugin adds the tenant field at the top level.
|
|
9
|
+
*/ export function getTenantScopedCollections(collections, tenantField) {
|
|
10
|
+
if (!tenantField) return new Set();
|
|
11
|
+
const tenantScopedSlugs = new Set();
|
|
12
|
+
for (const collection of collections){
|
|
13
|
+
const match = findFields(collection.fields, (f)=>{
|
|
14
|
+
return 'name' in f && f.name === tenantField;
|
|
15
|
+
});
|
|
16
|
+
// Only consider fields whose path is exactly [tenantField] — i.e. top-level
|
|
17
|
+
const hasTopLevelTenantField = match.some((f)=>f.path.length === 1 && f.path[0] === tenantField);
|
|
18
|
+
if (hasTopLevelTenantField) {
|
|
19
|
+
tenantScopedSlugs.add(collection.slug);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return tenantScopedSlugs;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=tenant-scoped-collections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/tenant-scoped-collections.ts"],"sourcesContent":["import { findFields } from '@davincicoding/payload-plugin-kit';\nimport type { CollectionConfig, CollectionSlug, Field } from 'payload';\n\n/**\n * Determines which collections have the tenant field by scanning their field configs.\n * Returns a Set of collection slugs that are tenant-tenantScopedSlugs.\n *\n * Only matches top-level field names (or fields inside unnamed containers like\n * unnamed tabs/rows). Fields nested inside named groups/tabs don't match because\n * the multi-tenant plugin adds the tenant field at the top level.\n */\nexport function getTenantScopedCollections(\n collections: CollectionConfig[],\n tenantField: string | undefined,\n): Set<CollectionSlug> {\n if (!tenantField) return new Set();\n\n const tenantScopedSlugs = new Set<CollectionSlug>();\n\n for (const collection of collections) {\n const match = findFields(collection.fields, (f: Field) => {\n return 'name' in f && f.name === tenantField;\n });\n\n // Only consider fields whose path is exactly [tenantField] — i.e. top-level\n const hasTopLevelTenantField = match.some(\n (f) => f.path.length === 1 && f.path[0] === tenantField,\n );\n\n if (hasTopLevelTenantField) {\n tenantScopedSlugs.add(collection.slug as CollectionSlug);\n }\n }\n\n return tenantScopedSlugs;\n}\n"],"names":["findFields","getTenantScopedCollections","collections","tenantField","Set","tenantScopedSlugs","collection","match","fields","f","name","hasTopLevelTenantField","some","path","length","add","slug"],"mappings":"AAAA,SAASA,UAAU,QAAQ,oCAAoC;AAG/D;;;;;;;CAOC,GACD,OAAO,SAASC,2BACdC,WAA+B,EAC/BC,WAA+B;IAE/B,IAAI,CAACA,aAAa,OAAO,IAAIC;IAE7B,MAAMC,oBAAoB,IAAID;IAE9B,KAAK,MAAME,cAAcJ,YAAa;QACpC,MAAMK,QAAQP,WAAWM,WAAWE,MAAM,EAAE,CAACC;YAC3C,OAAO,UAAUA,KAAKA,EAAEC,IAAI,KAAKP;QACnC;QAEA,4EAA4E;QAC5E,MAAMQ,yBAAyBJ,MAAMK,IAAI,CACvC,CAACH,IAAMA,EAAEI,IAAI,CAACC,MAAM,KAAK,KAAKL,EAAEI,IAAI,CAAC,EAAE,KAAKV;QAG9C,IAAIQ,wBAAwB;YAC1BN,kBAAkBU,GAAG,CAACT,WAAWU,IAAI;QACvC;IACF;IAEA,OAAOX;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload-smart-cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Payload Plugin for Cached Data",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"import": "./dist/index.js",
|
|
23
23
|
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./cache": {
|
|
26
|
+
"types": "./dist/exports/cache.d.ts",
|
|
27
|
+
"import": "./dist/exports/cache.js",
|
|
28
|
+
"default": "./dist/exports/cache.js"
|
|
24
29
|
}
|
|
25
30
|
},
|
|
26
31
|
"main": "./dist/index.js",
|
|
@@ -31,7 +36,7 @@
|
|
|
31
36
|
"dependencies": {
|
|
32
37
|
"graph-data-structure": "^4.5.0",
|
|
33
38
|
"lodash-es": "^4.17.21",
|
|
34
|
-
"@davincicoding/payload-plugin-kit": "0.0
|
|
39
|
+
"@davincicoding/payload-plugin-kit": "0.1.0"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
37
42
|
"@types/lodash-es": "^4.17.12",
|
|
@@ -55,11 +60,11 @@
|
|
|
55
60
|
},
|
|
56
61
|
"scripts": {
|
|
57
62
|
"prebuild": "pnpm check:types",
|
|
58
|
-
"build": "plugin-build",
|
|
63
|
+
"build": "plugin-kit build",
|
|
59
64
|
"check:types": "tsc --noEmit",
|
|
60
65
|
"dev": "cd dev && pnpm dev",
|
|
61
|
-
"dev:build": "plugin-build --watch",
|
|
62
|
-
"generate:types": "generate-types",
|
|
66
|
+
"dev:build": "plugin-kit build --watch",
|
|
67
|
+
"generate:types": "plugin-kit generate-types",
|
|
63
68
|
"lint": "biome check .",
|
|
64
69
|
"lint:fix": "biome check --write .",
|
|
65
70
|
"test": "vitest run",
|