@payloadcms/figma 0.0.1-alpha.29 → 0.0.1-alpha.30
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/dist/api/figma-api.d.ts.map +1 -1
- package/dist/api/figma-api.js +4 -0
- package/dist/api/figma-api.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +32 -50
- package/dist/commands/deploy.js.map +1 -1
- package/dist/db-content-api/README.md +108 -0
- package/dist/db-content-api/generated/content-api-types.d.ts +763 -190
- package/dist/db-content-api/generated/content-api-types.d.ts.map +1 -1
- package/dist/db-content-api/generated/content-api-types.js.map +1 -1
- package/dist/db-content-api/index.d.ts +6 -19
- package/dist/db-content-api/index.d.ts.map +1 -1
- package/dist/db-content-api/index.js +396 -687
- package/dist/db-content-api/index.js.map +1 -1
- package/dist/db-content-api/temp-utilities/pagination.d.ts +13 -0
- package/dist/db-content-api/temp-utilities/pagination.d.ts.map +1 -0
- package/dist/db-content-api/temp-utilities/pagination.js +45 -0
- package/dist/db-content-api/temp-utilities/pagination.js.map +1 -0
- package/dist/db-content-api/temp-utilities/slug.d.ts +2 -0
- package/dist/db-content-api/temp-utilities/slug.d.ts.map +1 -0
- package/dist/db-content-api/temp-utilities/slug.js +3 -0
- package/dist/db-content-api/temp-utilities/slug.js.map +1 -0
- package/dist/db-content-api/temp-utilities/sorting.d.ts +7 -0
- package/dist/db-content-api/temp-utilities/sorting.d.ts.map +1 -0
- package/dist/db-content-api/temp-utilities/sorting.js +71 -0
- package/dist/db-content-api/temp-utilities/sorting.js.map +1 -0
- package/dist/db-content-api/temp-utilities/unwrapDocument.d.ts +4 -0
- package/dist/db-content-api/temp-utilities/unwrapDocument.d.ts.map +1 -0
- package/dist/db-content-api/temp-utilities/unwrapDocument.js +45 -0
- package/dist/db-content-api/temp-utilities/unwrapDocument.js.map +1 -0
- package/dist/db-content-api/utilities/auth.d.ts +30 -0
- package/dist/db-content-api/utilities/auth.d.ts.map +1 -0
- package/dist/db-content-api/utilities/auth.js +81 -0
- package/dist/db-content-api/utilities/auth.js.map +1 -0
- package/dist/db-content-api/utilities/data.d.ts +24 -0
- package/dist/db-content-api/utilities/data.d.ts.map +1 -0
- package/dist/db-content-api/utilities/data.js +118 -0
- package/dist/db-content-api/utilities/data.js.map +1 -0
- package/dist/db-content-api/utilities/joins.d.ts +55 -0
- package/dist/db-content-api/utilities/joins.d.ts.map +1 -0
- package/dist/db-content-api/utilities/joins.js +130 -0
- package/dist/db-content-api/utilities/joins.js.map +1 -0
- package/dist/db-content-api/utilities/where.d.ts +12 -0
- package/dist/db-content-api/utilities/where.d.ts.map +1 -0
- package/dist/db-content-api/utilities/where.js +63 -0
- package/dist/db-content-api/utilities/where.js.map +1 -0
- package/dist/plugin/build-config.d.ts +1 -0
- package/dist/plugin/build-config.d.ts.map +1 -1
- package/dist/plugin/build-config.js +38 -0
- package/dist/plugin/build-config.js.map +1 -1
- package/dist/storage-content-api/api-types.d.ts +161 -0
- package/dist/storage-content-api/api-types.d.ts.map +1 -0
- package/dist/storage-content-api/api-types.js +6 -0
- package/dist/storage-content-api/api-types.js.map +1 -0
- package/dist/storage-content-api/client.d.ts +6 -0
- package/dist/storage-content-api/client.d.ts.map +1 -0
- package/dist/storage-content-api/client.js +36 -0
- package/dist/storage-content-api/client.js.map +1 -0
- package/dist/storage-content-api/generateURL.d.ts +7 -0
- package/dist/storage-content-api/generateURL.d.ts.map +1 -0
- package/dist/storage-content-api/generateURL.js +7 -0
- package/dist/storage-content-api/generateURL.js.map +1 -0
- package/dist/storage-content-api/handleDelete.d.ts +8 -0
- package/dist/storage-content-api/handleDelete.d.ts.map +1 -0
- package/dist/storage-content-api/handleDelete.js +15 -0
- package/dist/storage-content-api/handleDelete.js.map +1 -0
- package/dist/storage-content-api/handleUpload.d.ts +11 -0
- package/dist/storage-content-api/handleUpload.d.ts.map +1 -0
- package/dist/storage-content-api/handleUpload.js +126 -0
- package/dist/storage-content-api/handleUpload.js.map +1 -0
- package/dist/storage-content-api/index.d.ts +5 -0
- package/dist/storage-content-api/index.d.ts.map +1 -0
- package/dist/storage-content-api/index.js +31 -0
- package/dist/storage-content-api/index.js.map +1 -0
- package/dist/storage-content-api/staticHandler.d.ts +10 -0
- package/dist/storage-content-api/staticHandler.d.ts.map +1 -0
- package/dist/storage-content-api/staticHandler.js +35 -0
- package/dist/storage-content-api/staticHandler.js.map +1 -0
- package/dist/storage-content-api/types.d.ts +10 -0
- package/dist/storage-content-api/types.d.ts.map +1 -0
- package/dist/storage-content-api/types.js +3 -0
- package/dist/storage-content-api/types.js.map +1 -0
- package/dist/utils/lambda-config.d.ts.map +1 -1
- package/dist/utils/lambda-config.js +1 -0
- package/dist/utils/lambda-config.js.map +1 -1
- package/package.json +5 -2
- package/dist/db-content-api/types.d.ts +0 -31
- package/dist/db-content-api/types.d.ts.map +0 -1
- package/dist/db-content-api/types.js +0 -7
- package/dist/db-content-api/types.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/db-content-api/utilities/auth.ts"],"sourcesContent":["import type { Middleware } from 'openapi-fetch'\nimport type { Payload } from 'payload'\n\nimport type { TokenStore } from '../../auth/token-store.js'\n\nimport { getValidProjectToken } from '../../auth/project-token.js'\n\n// Auth mode types\ntype ApiKeyAuth = { apiKey: string; mode: 'apiKey' }\ntype TokenStoreAuth = { mode: 'tokenStore'; tokenStore: TokenStore }\ntype DevJwtAuth = { mode: 'devJwt' }\nexport type AuthMode = ApiKeyAuth | DevJwtAuth | TokenStoreAuth\n\n// Module-level cache for dev JWT tokens\nconst devJwtCache = new Map<string, string>()\n\nexport async function fetchDevJwt(url: string, contentSystemId: string): Promise<string> {\n const usesSuperUser =\n !contentSystemId || contentSystemId === 'test-system' || contentSystemId === '*'\n const contentSystemIdForJWT = usesSuperUser ? '*' : contentSystemId\n\n const response = await fetch(`${url}/dev/jwt`, {\n body: JSON.stringify({ content_system_id: contentSystemIdForJWT }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new Error(`Failed to get dev JWT: ${response.status} ${response.statusText}`)\n }\n\n const { token } = await response.json()\n return token\n}\n\nexport function createAuthMiddleware(opts: {\n auth: AuthMode\n contentSystemId: string\n logger: Payload['logger']\n url: string\n}): Middleware {\n return {\n async onRequest({ request }) {\n if (opts.auth.mode === 'apiKey') {\n request.headers.set('X-Api-Key', opts.auth.apiKey)\n } else if (opts.auth.mode === 'tokenStore') {\n const token = await getValidProjectToken(opts.auth.tokenStore, opts.contentSystemId)\n if (!token) {\n throw new Error(\n 'Authentication required. Run `npx @payloadcms/figma login` to authenticate.',\n )\n }\n request.headers.set('Authorization', `Bearer ${token}`)\n } else {\n let token = devJwtCache.get(opts.contentSystemId)\n if (!token) {\n token = await fetchDevJwt(opts.url, opts.contentSystemId)\n devJwtCache.set(opts.contentSystemId, token)\n opts.logger.info(`Dev JWT acquired for content system: ${opts.contentSystemId}`)\n }\n request.headers.set('Authorization', `Bearer ${token}`)\n }\n return request\n },\n }\n}\n\nexport function createErrorMiddleware(opts: {\n auth: AuthMode\n contentSystemId: string\n logger: Payload['logger']\n url: string\n}): Middleware {\n return {\n async onResponse({ request, response }) {\n if (!response.ok) {\n // Handle 401 retry for dev JWT\n if (response.status === 401 && opts.auth.mode === 'devJwt') {\n const retryCount = parseInt(request.headers.get('X-Retry-Count') || '0', 10)\n if (retryCount < 1) {\n opts.logger.info('Dev JWT expired, refreshing...')\n devJwtCache.delete(opts.contentSystemId)\n\n const newToken = await fetchDevJwt(opts.url, opts.contentSystemId)\n devJwtCache.set(opts.contentSystemId, newToken)\n\n // duplex: 'half' is required for Node.js fetch with streaming body\n const newRequest = new Request(request.url, {\n body: request.body,\n duplex: 'half',\n headers: new Headers(request.headers),\n method: request.method,\n } as RequestInit)\n newRequest.headers.set('X-Retry-Count', '1')\n newRequest.headers.set('Authorization', `Bearer ${newToken}`)\n\n return fetch(newRequest)\n }\n }\n\n const text = await response.clone().text()\n opts.logger.error({\n msg: `HTTP ${response.status} from ${request.url}`,\n response: text,\n })\n }\n return response\n },\n }\n}\n"],"names":["getValidProjectToken","devJwtCache","Map","fetchDevJwt","url","contentSystemId","usesSuperUser","contentSystemIdForJWT","response","fetch","body","JSON","stringify","content_system_id","headers","method","ok","Error","status","statusText","token","json","createAuthMiddleware","opts","onRequest","request","auth","mode","set","apiKey","tokenStore","get","logger","info","createErrorMiddleware","onResponse","retryCount","parseInt","delete","newToken","newRequest","Request","duplex","Headers","text","clone","error","msg"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Payload } from 'payload';
|
|
2
|
+
import type { components } from '../generated/content-api-types.js';
|
|
3
|
+
type DataWithOperations = components['schemas']['DataWithOperations'];
|
|
4
|
+
/**
|
|
5
|
+
* Transform data before sending to Content API (WRITE operations)
|
|
6
|
+
*
|
|
7
|
+
* Conversions applied:
|
|
8
|
+
* - RichText fields: Objects -> JSON strings
|
|
9
|
+
*
|
|
10
|
+
* Note: Date fields are automatically converted to ISO strings by JSON.stringify()
|
|
11
|
+
*/
|
|
12
|
+
export declare function dataToContentAPI(payload: Payload, collectionSlug: string, data: unknown): DataWithOperations;
|
|
13
|
+
/**
|
|
14
|
+
* Transform data received from Content API (READ operations)
|
|
15
|
+
*
|
|
16
|
+
* Conversions applied:
|
|
17
|
+
* - RichText fields: JSON strings -> Objects
|
|
18
|
+
*
|
|
19
|
+
* Note: Date fields come as ISO strings from Content API and stay as strings
|
|
20
|
+
* (matching MongoDB and other adapters' behavior)
|
|
21
|
+
*/
|
|
22
|
+
export declare function dataFromContentAPI(payload: Payload, collectionSlug: string, data: unknown): unknown;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../src/db-content-api/utilities/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkC,OAAO,EAA0B,MAAM,SAAS,CAAA;AAI9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAA;AAErE;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,OAAO,GACZ,kBAAkB,CA2CpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,OAAO,GACZ,OAAO,CAgET"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { traverseFields } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Transform data before sending to Content API (WRITE operations)
|
|
4
|
+
*
|
|
5
|
+
* Conversions applied:
|
|
6
|
+
* - RichText fields: Objects -> JSON strings
|
|
7
|
+
*
|
|
8
|
+
* Note: Date fields are automatically converted to ISO strings by JSON.stringify()
|
|
9
|
+
*/ export function dataToContentAPI(payload, collectionSlug, data) {
|
|
10
|
+
if (!data || typeof data !== 'object') {
|
|
11
|
+
return data;
|
|
12
|
+
}
|
|
13
|
+
// Deep clone to avoid mutating original data
|
|
14
|
+
const transformed = JSON.parse(JSON.stringify(data));
|
|
15
|
+
// Get collection config
|
|
16
|
+
const isGlobal = collectionSlug.startsWith('_global-');
|
|
17
|
+
const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
|
|
18
|
+
const collectionConfig = isGlobal ? payload.config.globals?.find((g)=>g.slug === actualSlug) : payload.config.collections.find((c)=>c.slug === actualSlug);
|
|
19
|
+
if (!collectionConfig?.fields) {
|
|
20
|
+
return transformed;
|
|
21
|
+
}
|
|
22
|
+
// Use Payload's traverseFields to iterate over all fields
|
|
23
|
+
const callback = ({ field, ref })=>{
|
|
24
|
+
if (!('name' in field) || !field.name) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!ref || typeof ref !== 'object') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const current = ref;
|
|
31
|
+
const value = current[field.name];
|
|
32
|
+
if (value !== null && value !== undefined) {
|
|
33
|
+
// RichText: object -> JSON string
|
|
34
|
+
if (field.type === 'richText' && typeof value !== 'string') {
|
|
35
|
+
current[field.name] = JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
// Date fields are already handled by JSON.stringify() which converts Date -> ISO string
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
traverseFields({
|
|
41
|
+
callback,
|
|
42
|
+
fields: collectionConfig.fields,
|
|
43
|
+
ref: transformed
|
|
44
|
+
});
|
|
45
|
+
return transformed;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Transform data received from Content API (READ operations)
|
|
49
|
+
*
|
|
50
|
+
* Conversions applied:
|
|
51
|
+
* - RichText fields: JSON strings -> Objects
|
|
52
|
+
*
|
|
53
|
+
* Note: Date fields come as ISO strings from Content API and stay as strings
|
|
54
|
+
* (matching MongoDB and other adapters' behavior)
|
|
55
|
+
*/ export function dataFromContentAPI(payload, collectionSlug, data) {
|
|
56
|
+
if (!data || typeof data !== 'object') {
|
|
57
|
+
return data;
|
|
58
|
+
}
|
|
59
|
+
// Deep clone to avoid mutating original data
|
|
60
|
+
const transformed = JSON.parse(JSON.stringify(data));
|
|
61
|
+
// Get collection config
|
|
62
|
+
const isGlobal = collectionSlug.startsWith('_global-');
|
|
63
|
+
const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
|
|
64
|
+
const collectionConfig = isGlobal ? payload.config.globals?.find((g)=>g.slug === actualSlug) : payload.config.collections.find((c)=>c.slug === actualSlug);
|
|
65
|
+
if (!collectionConfig?.fields) {
|
|
66
|
+
return transformed;
|
|
67
|
+
}
|
|
68
|
+
// Use Payload's traverseFields to iterate over all fields
|
|
69
|
+
const callback = ({ field, parentPath, ref })=>{
|
|
70
|
+
if (!('name' in field) || !field.name) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!ref || typeof ref !== 'object') {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const current = ref;
|
|
77
|
+
const value = current[field.name];
|
|
78
|
+
if (value !== null && value !== undefined) {
|
|
79
|
+
// RichText: JSON string -> object
|
|
80
|
+
if (field.type === 'richText' && typeof value === 'string') {
|
|
81
|
+
try {
|
|
82
|
+
current[field.name] = JSON.parse(value);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
|
|
85
|
+
payload.logger.warn({
|
|
86
|
+
err: error instanceof Error ? error : new Error(String(error)),
|
|
87
|
+
msg: `Failed to parse richtext field '${fieldPath}' in collection '${collectionSlug}'`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Date fields: Already ISO strings from Content API, no conversion needed
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
traverseFields({
|
|
95
|
+
callback,
|
|
96
|
+
fields: collectionConfig.fields,
|
|
97
|
+
ref: transformed
|
|
98
|
+
});
|
|
99
|
+
// Handle auth-specific fields that aren't in the field schema
|
|
100
|
+
// When these fields are null in DB, Content API omits them from response (undefined)
|
|
101
|
+
// But Payload expects them to be null, not undefined
|
|
102
|
+
// Only add them if the collection has auth enabled
|
|
103
|
+
if ('auth' in collectionConfig && collectionConfig.auth) {
|
|
104
|
+
const authFields = [
|
|
105
|
+
'resetPasswordExpiration',
|
|
106
|
+
'lockUntil'
|
|
107
|
+
];
|
|
108
|
+
for (const fieldName of authFields){
|
|
109
|
+
if (!(fieldName in transformed)) {
|
|
110
|
+
;
|
|
111
|
+
transformed[fieldName] = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return transformed;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/db-content-api/utilities/data.ts"],"sourcesContent":["import type { CollectionConfig, GlobalConfig, Payload, TraverseFieldsCallback } from 'payload'\n\nimport { traverseFields } from 'payload'\n\nimport type { components } from '../generated/content-api-types.js'\n\ntype DataWithOperations = components['schemas']['DataWithOperations']\n\n/**\n * Transform data before sending to Content API (WRITE operations)\n *\n * Conversions applied:\n * - RichText fields: Objects -> JSON strings\n *\n * Note: Date fields are automatically converted to ISO strings by JSON.stringify()\n */\nexport function dataToContentAPI(\n payload: Payload,\n collectionSlug: string,\n data: unknown,\n): DataWithOperations {\n if (!data || typeof data !== 'object') {\n return data as DataWithOperations\n }\n\n // Deep clone to avoid mutating original data\n const transformed = JSON.parse(JSON.stringify(data))\n\n // Get collection config\n const isGlobal = collectionSlug.startsWith('_global-')\n const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug\n const collectionConfig: CollectionConfig | GlobalConfig | undefined = isGlobal\n ? payload.config.globals?.find((g) => g.slug === actualSlug)\n : payload.config.collections.find((c) => c.slug === actualSlug)\n\n if (!collectionConfig?.fields) {\n return transformed as DataWithOperations\n }\n\n // Use Payload's traverseFields to iterate over all fields\n const callback: TraverseFieldsCallback = ({ field, ref }) => {\n if (!('name' in field) || !field.name) {\n return\n }\n if (!ref || typeof ref !== 'object') {\n return\n }\n\n const current = ref as Record<string, unknown>\n const value = current[field.name]\n\n if (value !== null && value !== undefined) {\n // RichText: object -> JSON string\n if (field.type === 'richText' && typeof value !== 'string') {\n current[field.name] = JSON.stringify(value)\n }\n // Date fields are already handled by JSON.stringify() which converts Date -> ISO string\n }\n }\n\n traverseFields({ callback, fields: collectionConfig.fields, ref: transformed })\n\n return transformed as DataWithOperations\n}\n\n/**\n * Transform data received from Content API (READ operations)\n *\n * Conversions applied:\n * - RichText fields: JSON strings -> Objects\n *\n * Note: Date fields come as ISO strings from Content API and stay as strings\n * (matching MongoDB and other adapters' behavior)\n */\nexport function dataFromContentAPI(\n payload: Payload,\n collectionSlug: string,\n data: unknown,\n): unknown {\n if (!data || typeof data !== 'object') {\n return data\n }\n\n // Deep clone to avoid mutating original data\n const transformed = JSON.parse(JSON.stringify(data))\n\n // Get collection config\n const isGlobal = collectionSlug.startsWith('_global-')\n const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug\n const collectionConfig: CollectionConfig | GlobalConfig | undefined = isGlobal\n ? payload.config.globals?.find((g) => g.slug === actualSlug)\n : payload.config.collections.find((c) => c.slug === actualSlug)\n\n if (!collectionConfig?.fields) {\n return transformed\n }\n\n // Use Payload's traverseFields to iterate over all fields\n const callback: TraverseFieldsCallback = ({ field, parentPath, ref }) => {\n if (!('name' in field) || !field.name) {\n return\n }\n if (!ref || typeof ref !== 'object') {\n return\n }\n\n const current = ref as Record<string, unknown>\n const value = current[field.name]\n\n if (value !== null && value !== undefined) {\n // RichText: JSON string -> object\n if (field.type === 'richText' && typeof value === 'string') {\n try {\n current[field.name] = JSON.parse(value)\n } catch (error) {\n const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name\n payload.logger.warn({\n err: error instanceof Error ? error : new Error(String(error)),\n msg: `Failed to parse richtext field '${fieldPath}' in collection '${collectionSlug}'`,\n })\n }\n }\n // Date fields: Already ISO strings from Content API, no conversion needed\n }\n }\n\n traverseFields({ callback, fields: collectionConfig.fields, ref: transformed })\n\n // Handle auth-specific fields that aren't in the field schema\n // When these fields are null in DB, Content API omits them from response (undefined)\n // But Payload expects them to be null, not undefined\n // Only add them if the collection has auth enabled\n if ('auth' in collectionConfig && collectionConfig.auth) {\n const authFields = ['resetPasswordExpiration', 'lockUntil']\n for (const fieldName of authFields) {\n if (!(fieldName in (transformed as Record<string, unknown>))) {\n ;(transformed as Record<string, unknown>)[fieldName] = null\n }\n }\n }\n\n return transformed\n}\n"],"names":["traverseFields","dataToContentAPI","payload","collectionSlug","data","transformed","JSON","parse","stringify","isGlobal","startsWith","actualSlug","substring","collectionConfig","config","globals","find","g","slug","collections","c","fields","callback","field","ref","name","current","value","undefined","type","dataFromContentAPI","parentPath","error","fieldPath","logger","warn","err","Error","String","msg","auth","authFields","fieldName"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { JoinQuery, Payload, SanitizedCollectionConfig } from 'payload';
|
|
2
|
+
import type { components } from '../generated/content-api-types.js';
|
|
3
|
+
type ContentAPIJoin = components['schemas']['JoinClause'][number];
|
|
4
|
+
/**
|
|
5
|
+
* Convert Payload's JoinQuery to Content API's join format.
|
|
6
|
+
*
|
|
7
|
+
* ## Content API Join Limitations
|
|
8
|
+
*
|
|
9
|
+
* The following Payload join features are NOT YET supported by Content API:
|
|
10
|
+
*
|
|
11
|
+
* 1. **hasMany relationships** - Content API's join uses `data#>'{path}'#>>'{}'` to extract
|
|
12
|
+
* a single ID. HasMany relationships store arrays `["id1", "id2"]` which can't match.
|
|
13
|
+
*
|
|
14
|
+
* 2. **Array field relationships** - `on: 'array.category'` expects iteration over array
|
|
15
|
+
* elements, but Content API treats the path as a single JSONB extraction.
|
|
16
|
+
*
|
|
17
|
+
* 3. **Polymorphic relationships** - Stored as `{ relationTo, value }` objects, but
|
|
18
|
+
* Content API expects a simple ID string.
|
|
19
|
+
*
|
|
20
|
+
* 4. **Localized relationship fields** - Locale-aware joins not implemented.
|
|
21
|
+
*
|
|
22
|
+
* Note: "Where querying through joins" (e.g., `where: { 'relatedPosts.title': { equals: 'x' } }`)
|
|
23
|
+
* is a separate Payload feature that filters documents based on joined data. This is NOT
|
|
24
|
+
* a join feature but a where clause feature, and would need separate Content API support.
|
|
25
|
+
*
|
|
26
|
+
* Payload's JoinQuery format (at runtime):
|
|
27
|
+
* {
|
|
28
|
+
* 'group.relatedPosts': { // joinPath - where results go in the document
|
|
29
|
+
* sort: '-title', // Payload sort format
|
|
30
|
+
* limit: 10,
|
|
31
|
+
* page: 2, // page-based pagination
|
|
32
|
+
* where: { ... }, // Payload where format
|
|
33
|
+
* count: true
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* Content API's join format:
|
|
38
|
+
* [{
|
|
39
|
+
* collectionId: 'posts', // collection to join FROM
|
|
40
|
+
* on: 'group.category', // field in joined collection pointing to this doc
|
|
41
|
+
* path: 'group.relatedPosts', // where to put results
|
|
42
|
+
* count: false,
|
|
43
|
+
* where: { ... }, // Content API where format
|
|
44
|
+
* sort: [{ path, direction }], // Content API sort format
|
|
45
|
+
* limit: 10,
|
|
46
|
+
* offset: 10 // offset-based pagination
|
|
47
|
+
* }]
|
|
48
|
+
*
|
|
49
|
+
* The mapping requires the collection config to resolve:
|
|
50
|
+
* - Which collection each join field refers to
|
|
51
|
+
* - What the 'on' field is for each join
|
|
52
|
+
*/
|
|
53
|
+
export declare function convertPayloadJoinsToContentAPI(payload: Payload, collectionConfig: SanitizedCollectionConfig, joins: false | JoinQuery | undefined): ContentAPIJoin[] | undefined;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=joins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"joins.d.ts","sourceRoot":"","sources":["../../../src/db-content-api/utilities/joins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAE5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAMnE,KAAK,cAAc,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,gBAAgB,EAAE,yBAAyB,EAC3C,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GACnC,cAAc,EAAE,GAAG,SAAS,CAkF9B"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { convertPayloadSortToContentAPI } from '../temp-utilities/sorting.js';
|
|
2
|
+
import { convertPayloadWhereToContentAPI } from './where.js';
|
|
3
|
+
/**
|
|
4
|
+
* Convert Payload's JoinQuery to Content API's join format.
|
|
5
|
+
*
|
|
6
|
+
* ## Content API Join Limitations
|
|
7
|
+
*
|
|
8
|
+
* The following Payload join features are NOT YET supported by Content API:
|
|
9
|
+
*
|
|
10
|
+
* 1. **hasMany relationships** - Content API's join uses `data#>'{path}'#>>'{}'` to extract
|
|
11
|
+
* a single ID. HasMany relationships store arrays `["id1", "id2"]` which can't match.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Array field relationships** - `on: 'array.category'` expects iteration over array
|
|
14
|
+
* elements, but Content API treats the path as a single JSONB extraction.
|
|
15
|
+
*
|
|
16
|
+
* 3. **Polymorphic relationships** - Stored as `{ relationTo, value }` objects, but
|
|
17
|
+
* Content API expects a simple ID string.
|
|
18
|
+
*
|
|
19
|
+
* 4. **Localized relationship fields** - Locale-aware joins not implemented.
|
|
20
|
+
*
|
|
21
|
+
* Note: "Where querying through joins" (e.g., `where: { 'relatedPosts.title': { equals: 'x' } }`)
|
|
22
|
+
* is a separate Payload feature that filters documents based on joined data. This is NOT
|
|
23
|
+
* a join feature but a where clause feature, and would need separate Content API support.
|
|
24
|
+
*
|
|
25
|
+
* Payload's JoinQuery format (at runtime):
|
|
26
|
+
* {
|
|
27
|
+
* 'group.relatedPosts': { // joinPath - where results go in the document
|
|
28
|
+
* sort: '-title', // Payload sort format
|
|
29
|
+
* limit: 10,
|
|
30
|
+
* page: 2, // page-based pagination
|
|
31
|
+
* where: { ... }, // Payload where format
|
|
32
|
+
* count: true
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* Content API's join format:
|
|
37
|
+
* [{
|
|
38
|
+
* collectionId: 'posts', // collection to join FROM
|
|
39
|
+
* on: 'group.category', // field in joined collection pointing to this doc
|
|
40
|
+
* path: 'group.relatedPosts', // where to put results
|
|
41
|
+
* count: false,
|
|
42
|
+
* where: { ... }, // Content API where format
|
|
43
|
+
* sort: [{ path, direction }], // Content API sort format
|
|
44
|
+
* limit: 10,
|
|
45
|
+
* offset: 10 // offset-based pagination
|
|
46
|
+
* }]
|
|
47
|
+
*
|
|
48
|
+
* The mapping requires the collection config to resolve:
|
|
49
|
+
* - Which collection each join field refers to
|
|
50
|
+
* - What the 'on' field is for each join
|
|
51
|
+
*/ export function convertPayloadJoinsToContentAPI(payload, collectionConfig, joins) {
|
|
52
|
+
// joins === false means explicitly disable all joins
|
|
53
|
+
// joins === undefined means use defaults (no custom query params)
|
|
54
|
+
if (joins === false || !joins) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const result = [];
|
|
58
|
+
// Iterate through all join paths in the query
|
|
59
|
+
for (const [joinPath, joinQueryValue] of Object.entries(joins)){
|
|
60
|
+
// joinQuery === false means skip this specific join
|
|
61
|
+
if (joinQueryValue === false) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Normalize undefined/empty to empty object
|
|
65
|
+
const joinQuery = joinQueryValue ?? {};
|
|
66
|
+
// Find the SanitizedJoin config for this joinPath
|
|
67
|
+
// SanitizedJoins is indexed by the collection being joined FROM
|
|
68
|
+
let foundJoin;
|
|
69
|
+
// Search through all collections in the joins config
|
|
70
|
+
for (const [collectionSlug, sanitizedJoins] of Object.entries(collectionConfig.joins)){
|
|
71
|
+
for (const sanitizedJoin of sanitizedJoins){
|
|
72
|
+
if (sanitizedJoin.joinPath === joinPath) {
|
|
73
|
+
foundJoin = {
|
|
74
|
+
collectionSlug,
|
|
75
|
+
on: sanitizedJoin.field.on
|
|
76
|
+
};
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (foundJoin) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Also check polymorphic joins
|
|
85
|
+
if (!foundJoin) {
|
|
86
|
+
for (const sanitizedJoin of collectionConfig.polymorphicJoins){
|
|
87
|
+
if (sanitizedJoin.joinPath === joinPath) {
|
|
88
|
+
// For polymorphic joins, collection is an array - use first one
|
|
89
|
+
const collections = sanitizedJoin.field.collection;
|
|
90
|
+
foundJoin = {
|
|
91
|
+
collectionSlug: Array.isArray(collections) ? collections[0] : collections,
|
|
92
|
+
on: sanitizedJoin.field.on
|
|
93
|
+
};
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!foundJoin) {
|
|
99
|
+
payload.logger.warn(`Join path '${joinPath}' not found in collection config for '${collectionConfig.slug}'`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// Convert pagination: Payload uses page, Content API JoinClause uses offset
|
|
103
|
+
let offset;
|
|
104
|
+
if (joinQuery.page && joinQuery.page > 1 && joinQuery.limit) {
|
|
105
|
+
offset = (joinQuery.page - 1) * joinQuery.limit;
|
|
106
|
+
}
|
|
107
|
+
const contentAPIJoin = {
|
|
108
|
+
collectionId: foundJoin.collectionSlug,
|
|
109
|
+
count: joinQuery.count ?? false,
|
|
110
|
+
on: foundJoin.on,
|
|
111
|
+
path: joinPath,
|
|
112
|
+
...joinQuery.limit !== undefined && {
|
|
113
|
+
limit: joinQuery.limit
|
|
114
|
+
},
|
|
115
|
+
...offset !== undefined && {
|
|
116
|
+
offset
|
|
117
|
+
},
|
|
118
|
+
...joinQuery.sort && {
|
|
119
|
+
sort: convertPayloadSortToContentAPI(joinQuery.sort)
|
|
120
|
+
},
|
|
121
|
+
...joinQuery.where && {
|
|
122
|
+
where: convertPayloadWhereToContentAPI(joinQuery.where)
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
result.push(contentAPIJoin);
|
|
126
|
+
}
|
|
127
|
+
return result.length > 0 ? result : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=joins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/db-content-api/utilities/joins.ts"],"sourcesContent":["import type { JoinQuery, Payload, SanitizedCollectionConfig } from 'payload'\n\nimport type { components } from '../generated/content-api-types.js'\n\nimport { convertPayloadSortToContentAPI } from '../temp-utilities/sorting.js'\nimport { convertPayloadWhereToContentAPI } from './where.js'\n\n// JoinClause is an array type, so we extract the element type\ntype ContentAPIJoin = components['schemas']['JoinClause'][number]\n\n/**\n * Convert Payload's JoinQuery to Content API's join format.\n *\n * ## Content API Join Limitations\n *\n * The following Payload join features are NOT YET supported by Content API:\n *\n * 1. **hasMany relationships** - Content API's join uses `data#>'{path}'#>>'{}'` to extract\n * a single ID. HasMany relationships store arrays `[\"id1\", \"id2\"]` which can't match.\n *\n * 2. **Array field relationships** - `on: 'array.category'` expects iteration over array\n * elements, but Content API treats the path as a single JSONB extraction.\n *\n * 3. **Polymorphic relationships** - Stored as `{ relationTo, value }` objects, but\n * Content API expects a simple ID string.\n *\n * 4. **Localized relationship fields** - Locale-aware joins not implemented.\n *\n * Note: \"Where querying through joins\" (e.g., `where: { 'relatedPosts.title': { equals: 'x' } }`)\n * is a separate Payload feature that filters documents based on joined data. This is NOT\n * a join feature but a where clause feature, and would need separate Content API support.\n *\n * Payload's JoinQuery format (at runtime):\n * {\n * 'group.relatedPosts': { // joinPath - where results go in the document\n * sort: '-title', // Payload sort format\n * limit: 10,\n * page: 2, // page-based pagination\n * where: { ... }, // Payload where format\n * count: true\n * }\n * }\n *\n * Content API's join format:\n * [{\n * collectionId: 'posts', // collection to join FROM\n * on: 'group.category', // field in joined collection pointing to this doc\n * path: 'group.relatedPosts', // where to put results\n * count: false,\n * where: { ... }, // Content API where format\n * sort: [{ path, direction }], // Content API sort format\n * limit: 10,\n * offset: 10 // offset-based pagination\n * }]\n *\n * The mapping requires the collection config to resolve:\n * - Which collection each join field refers to\n * - What the 'on' field is for each join\n */\nexport function convertPayloadJoinsToContentAPI(\n payload: Payload,\n collectionConfig: SanitizedCollectionConfig,\n joins: false | JoinQuery | undefined,\n): ContentAPIJoin[] | undefined {\n // joins === false means explicitly disable all joins\n // joins === undefined means use defaults (no custom query params)\n if (joins === false || !joins) {\n return undefined\n }\n\n const result: ContentAPIJoin[] = []\n\n // Iterate through all join paths in the query\n for (const [joinPath, joinQueryValue] of Object.entries(joins)) {\n // joinQuery === false means skip this specific join\n if (joinQueryValue === false) {\n continue\n }\n\n // Normalize undefined/empty to empty object\n const joinQuery = joinQueryValue ?? {}\n\n // Find the SanitizedJoin config for this joinPath\n // SanitizedJoins is indexed by the collection being joined FROM\n let foundJoin: { collectionSlug: string; on: string } | undefined\n\n // Search through all collections in the joins config\n for (const [collectionSlug, sanitizedJoins] of Object.entries(collectionConfig.joins)) {\n for (const sanitizedJoin of sanitizedJoins) {\n if (sanitizedJoin.joinPath === joinPath) {\n foundJoin = {\n collectionSlug,\n on: sanitizedJoin.field.on,\n }\n break\n }\n }\n if (foundJoin) {\n break\n }\n }\n\n // Also check polymorphic joins\n if (!foundJoin) {\n for (const sanitizedJoin of collectionConfig.polymorphicJoins) {\n if (sanitizedJoin.joinPath === joinPath) {\n // For polymorphic joins, collection is an array - use first one\n const collections = sanitizedJoin.field.collection\n foundJoin = {\n collectionSlug: Array.isArray(collections) ? collections[0] : collections,\n on: sanitizedJoin.field.on,\n }\n break\n }\n }\n }\n\n if (!foundJoin) {\n payload.logger.warn(\n `Join path '${joinPath}' not found in collection config for '${collectionConfig.slug}'`,\n )\n continue\n }\n\n // Convert pagination: Payload uses page, Content API JoinClause uses offset\n let offset: number | undefined\n if (joinQuery.page && joinQuery.page > 1 && joinQuery.limit) {\n offset = (joinQuery.page - 1) * joinQuery.limit\n }\n\n const contentAPIJoin: ContentAPIJoin = {\n collectionId: foundJoin.collectionSlug,\n count: joinQuery.count ?? false,\n on: foundJoin.on,\n path: joinPath,\n ...(joinQuery.limit !== undefined && { limit: joinQuery.limit }),\n ...(offset !== undefined && { offset }),\n ...(joinQuery.sort && { sort: convertPayloadSortToContentAPI(joinQuery.sort) }),\n ...(joinQuery.where && { where: convertPayloadWhereToContentAPI(joinQuery.where) }),\n }\n\n result.push(contentAPIJoin)\n }\n\n return result.length > 0 ? result : undefined\n}\n"],"names":["convertPayloadSortToContentAPI","convertPayloadWhereToContentAPI","convertPayloadJoinsToContentAPI","payload","collectionConfig","joins","undefined","result","joinPath","joinQueryValue","Object","entries","joinQuery","foundJoin","collectionSlug","sanitizedJoins","sanitizedJoin","on","field","polymorphicJoins","collections","collection","Array","isArray","logger","warn","slug","offset","page","limit","contentAPIJoin","collectionId","count","path","sort","where","push","length"],"mappings":"AAIA,SAASA,8BAA8B,QAAQ,+BAA8B;AAC7E,SAASC,+BAA+B,QAAQ,aAAY;AAK5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDC,GACD,OAAO,SAASC,gCACdC,OAAgB,EAChBC,gBAA2C,EAC3CC,KAAoC;IAEpC,qDAAqD;IACrD,kEAAkE;IAClE,IAAIA,UAAU,SAAS,CAACA,OAAO;QAC7B,OAAOC;IACT;IAEA,MAAMC,SAA2B,EAAE;IAEnC,8CAA8C;IAC9C,KAAK,MAAM,CAACC,UAAUC,eAAe,IAAIC,OAAOC,OAAO,CAACN,OAAQ;QAC9D,oDAAoD;QACpD,IAAII,mBAAmB,OAAO;YAC5B;QACF;QAEA,4CAA4C;QAC5C,MAAMG,YAAYH,kBAAkB,CAAC;QAErC,kDAAkD;QAClD,gEAAgE;QAChE,IAAII;QAEJ,qDAAqD;QACrD,KAAK,MAAM,CAACC,gBAAgBC,eAAe,IAAIL,OAAOC,OAAO,CAACP,iBAAiBC,KAAK,EAAG;YACrF,KAAK,MAAMW,iBAAiBD,eAAgB;gBAC1C,IAAIC,cAAcR,QAAQ,KAAKA,UAAU;oBACvCK,YAAY;wBACVC;wBACAG,IAAID,cAAcE,KAAK,CAACD,EAAE;oBAC5B;oBACA;gBACF;YACF;YACA,IAAIJ,WAAW;gBACb;YACF;QACF;QAEA,+BAA+B;QAC/B,IAAI,CAACA,WAAW;YACd,KAAK,MAAMG,iBAAiBZ,iBAAiBe,gBAAgB,CAAE;gBAC7D,IAAIH,cAAcR,QAAQ,KAAKA,UAAU;oBACvC,gEAAgE;oBAChE,MAAMY,cAAcJ,cAAcE,KAAK,CAACG,UAAU;oBAClDR,YAAY;wBACVC,gBAAgBQ,MAAMC,OAAO,CAACH,eAAeA,WAAW,CAAC,EAAE,GAAGA;wBAC9DH,IAAID,cAAcE,KAAK,CAACD,EAAE;oBAC5B;oBACA;gBACF;YACF;QACF;QAEA,IAAI,CAACJ,WAAW;YACdV,QAAQqB,MAAM,CAACC,IAAI,CACjB,CAAC,WAAW,EAAEjB,SAAS,sCAAsC,EAAEJ,iBAAiBsB,IAAI,CAAC,CAAC,CAAC;YAEzF;QACF;QAEA,4EAA4E;QAC5E,IAAIC;QACJ,IAAIf,UAAUgB,IAAI,IAAIhB,UAAUgB,IAAI,GAAG,KAAKhB,UAAUiB,KAAK,EAAE;YAC3DF,SAAS,AAACf,CAAAA,UAAUgB,IAAI,GAAG,CAAA,IAAKhB,UAAUiB,KAAK;QACjD;QAEA,MAAMC,iBAAiC;YACrCC,cAAclB,UAAUC,cAAc;YACtCkB,OAAOpB,UAAUoB,KAAK,IAAI;YAC1Bf,IAAIJ,UAAUI,EAAE;YAChBgB,MAAMzB;YACN,GAAII,UAAUiB,KAAK,KAAKvB,aAAa;gBAAEuB,OAAOjB,UAAUiB,KAAK;YAAC,CAAC;YAC/D,GAAIF,WAAWrB,aAAa;gBAAEqB;YAAO,CAAC;YACtC,GAAIf,UAAUsB,IAAI,IAAI;gBAAEA,MAAMlC,+BAA+BY,UAAUsB,IAAI;YAAE,CAAC;YAC9E,GAAItB,UAAUuB,KAAK,IAAI;gBAAEA,OAAOlC,gCAAgCW,UAAUuB,KAAK;YAAE,CAAC;QACpF;QAEA5B,OAAO6B,IAAI,CAACN;IACd;IAEA,OAAOvB,OAAO8B,MAAM,GAAG,IAAI9B,SAASD;AACtC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Where } from 'payload';
|
|
2
|
+
import type { components } from '../generated/content-api-types.js';
|
|
3
|
+
type WhereClause = components['schemas']['WhereClause'];
|
|
4
|
+
type ConvertOptions = {
|
|
5
|
+
/** Internal flag to track if we're inside a logical operator (and/or) */
|
|
6
|
+
insideLogicalOperator?: boolean;
|
|
7
|
+
/** If true, transform 'parent' field to 'documentId' (used for version queries) */
|
|
8
|
+
parentToDocumentId?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClause;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=where.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../../../src/db-content-api/utilities/where.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,KAAK,WAAW,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,aAAa,CAAC,CAAA;AASvD,KAAK,cAAc,GAAG;IACpB,yEAAyE;IACzE,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,SAAS,GAAG,KAAK,EACxB,OAAO,GAAE,cAAmB,GAC3B,WAAW,CA2Db"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
2
|
+
// Empty where {} should be { and: [] } not undefined
|
|
3
|
+
// Content API requires a where clause structure even for "no filter"
|
|
4
|
+
if (!where || Object.keys(where).length === 0) {
|
|
5
|
+
return {
|
|
6
|
+
and: []
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
const conditions = [];
|
|
10
|
+
for (const [key, value] of Object.entries(where)){
|
|
11
|
+
if (key === 'and') {
|
|
12
|
+
// Recursively convert nested 'and' conditions, preserving options
|
|
13
|
+
const nestedConditions = value.map((item)=>convertPayloadWhereToContentAPI(item, {
|
|
14
|
+
...options,
|
|
15
|
+
insideLogicalOperator: true
|
|
16
|
+
}));
|
|
17
|
+
return {
|
|
18
|
+
and: nestedConditions
|
|
19
|
+
};
|
|
20
|
+
} else if (key === 'or') {
|
|
21
|
+
// Recursively convert nested 'or' conditions, preserving options
|
|
22
|
+
const nestedConditions = value.map((item)=>convertPayloadWhereToContentAPI(item, {
|
|
23
|
+
...options,
|
|
24
|
+
insideLogicalOperator: true
|
|
25
|
+
}));
|
|
26
|
+
return {
|
|
27
|
+
or: nestedConditions
|
|
28
|
+
};
|
|
29
|
+
} else {
|
|
30
|
+
// TODO: fix this in content api
|
|
31
|
+
// WORKAROUND: Payload uses 'parent' for version queries, but Content API expects 'documentId'
|
|
32
|
+
let fieldPath = key;
|
|
33
|
+
if (options.parentToDocumentId && key === 'parent') {
|
|
34
|
+
fieldPath = 'documentId';
|
|
35
|
+
}
|
|
36
|
+
// Convert field conditions: { fieldName: { operator: value } }
|
|
37
|
+
// to: { path: fieldName, operator, value }
|
|
38
|
+
const operators = value;
|
|
39
|
+
for (const [op, operatorValue] of Object.entries(operators)){
|
|
40
|
+
let finalValue = operatorValue;
|
|
41
|
+
// Add wildcards for contains/like operators (Payload doesn't add them)
|
|
42
|
+
if ((op === 'contains' || op === 'like') && typeof operatorValue === 'string') {
|
|
43
|
+
finalValue = `%${operatorValue}%`;
|
|
44
|
+
}
|
|
45
|
+
conditions.push({
|
|
46
|
+
operator: op,
|
|
47
|
+
path: fieldPath,
|
|
48
|
+
value: finalValue
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// If inside a logical operator (and/or), return conditions directly
|
|
54
|
+
// Otherwise, wrap in 'and' to match WhereClause type
|
|
55
|
+
if (options.insideLogicalOperator && conditions.length === 1) {
|
|
56
|
+
return conditions[0];
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
and: conditions
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=where.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/db-content-api/utilities/where.ts"],"sourcesContent":["import type { Where } from 'payload'\n\nimport type { components } from '../generated/content-api-types.js'\n\ntype WhereClause = components['schemas']['WhereClause']\n\n// Payload's format: { fieldName: { operator: value }, and: [...], or: [...] }\n// Content API expects: { and: [{ path, operator, value }], or: [{ path, operator, value }] }\n//\n// The format stays different. This allows the Content API to evolve its filter schema,\n// for example to support composite types like Point, which are not lexicographically sortable.\n// Semantics must remain identical to Payload.\n\ntype ConvertOptions = {\n /** Internal flag to track if we're inside a logical operator (and/or) */\n insideLogicalOperator?: boolean\n /** If true, transform 'parent' field to 'documentId' (used for version queries) */\n parentToDocumentId?: boolean\n}\n\nexport function convertPayloadWhereToContentAPI(\n where: undefined | Where,\n options: ConvertOptions = {},\n): WhereClause {\n // Empty where {} should be { and: [] } not undefined\n // Content API requires a where clause structure even for \"no filter\"\n if (!where || Object.keys(where).length === 0) {\n return { and: [] }\n }\n\n // Extract the condition type (the variant with 'path', 'operator', 'value') from WhereClause union\n type WhereCondition = Extract<WhereClause, { path: string }>\n const conditions: WhereClause[] = []\n\n for (const [key, value] of Object.entries(where)) {\n if (key === 'and') {\n // Recursively convert nested 'and' conditions, preserving options\n const nestedConditions = (value as Where[]).map((item) =>\n convertPayloadWhereToContentAPI(item, { ...options, insideLogicalOperator: true }),\n )\n return { and: nestedConditions }\n } else if (key === 'or') {\n // Recursively convert nested 'or' conditions, preserving options\n const nestedConditions = (value as Where[]).map((item) =>\n convertPayloadWhereToContentAPI(item, { ...options, insideLogicalOperator: true }),\n )\n return { or: nestedConditions }\n } else {\n // TODO: fix this in content api\n // WORKAROUND: Payload uses 'parent' for version queries, but Content API expects 'documentId'\n let fieldPath = key\n if (options.parentToDocumentId && key === 'parent') {\n fieldPath = 'documentId'\n }\n\n // Convert field conditions: { fieldName: { operator: value } }\n // to: { path: fieldName, operator, value }\n const operators = value as Record<string, unknown>\n for (const [op, operatorValue] of Object.entries(operators)) {\n let finalValue = operatorValue\n\n // Add wildcards for contains/like operators (Payload doesn't add them)\n if ((op === 'contains' || op === 'like') && typeof operatorValue === 'string') {\n finalValue = `%${operatorValue}%`\n }\n\n conditions.push({\n operator: op as WhereCondition['operator'],\n path: fieldPath,\n value: finalValue,\n } as WhereCondition)\n }\n }\n }\n\n // If inside a logical operator (and/or), return conditions directly\n // Otherwise, wrap in 'and' to match WhereClause type\n if (options.insideLogicalOperator && conditions.length === 1) {\n return conditions[0]\n }\n\n return { and: conditions }\n}\n"],"names":["convertPayloadWhereToContentAPI","where","options","Object","keys","length","and","conditions","key","value","entries","nestedConditions","map","item","insideLogicalOperator","or","fieldPath","parentToDocumentId","operators","op","operatorValue","finalValue","push","operator","path"],"mappings":"AAoBA,OAAO,SAASA,gCACdC,KAAwB,EACxBC,UAA0B,CAAC,CAAC;IAE5B,qDAAqD;IACrD,qEAAqE;IACrE,IAAI,CAACD,SAASE,OAAOC,IAAI,CAACH,OAAOI,MAAM,KAAK,GAAG;QAC7C,OAAO;YAAEC,KAAK,EAAE;QAAC;IACnB;IAIA,MAAMC,aAA4B,EAAE;IAEpC,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIN,OAAOO,OAAO,CAACT,OAAQ;QAChD,IAAIO,QAAQ,OAAO;YACjB,kEAAkE;YAClE,MAAMG,mBAAmB,AAACF,MAAkBG,GAAG,CAAC,CAACC,OAC/Cb,gCAAgCa,MAAM;oBAAE,GAAGX,OAAO;oBAAEY,uBAAuB;gBAAK;YAElF,OAAO;gBAAER,KAAKK;YAAiB;QACjC,OAAO,IAAIH,QAAQ,MAAM;YACvB,iEAAiE;YACjE,MAAMG,mBAAmB,AAACF,MAAkBG,GAAG,CAAC,CAACC,OAC/Cb,gCAAgCa,MAAM;oBAAE,GAAGX,OAAO;oBAAEY,uBAAuB;gBAAK;YAElF,OAAO;gBAAEC,IAAIJ;YAAiB;QAChC,OAAO;YACL,gCAAgC;YAChC,8FAA8F;YAC9F,IAAIK,YAAYR;YAChB,IAAIN,QAAQe,kBAAkB,IAAIT,QAAQ,UAAU;gBAClDQ,YAAY;YACd;YAEA,+DAA+D;YAC/D,2CAA2C;YAC3C,MAAME,YAAYT;YAClB,KAAK,MAAM,CAACU,IAAIC,cAAc,IAAIjB,OAAOO,OAAO,CAACQ,WAAY;gBAC3D,IAAIG,aAAaD;gBAEjB,uEAAuE;gBACvE,IAAI,AAACD,CAAAA,OAAO,cAAcA,OAAO,MAAK,KAAM,OAAOC,kBAAkB,UAAU;oBAC7EC,aAAa,CAAC,CAAC,EAAED,cAAc,CAAC,CAAC;gBACnC;gBAEAb,WAAWe,IAAI,CAAC;oBACdC,UAAUJ;oBACVK,MAAMR;oBACNP,OAAOY;gBACT;YACF;QACF;IACF;IAEA,oEAAoE;IACpE,qDAAqD;IACrD,IAAInB,QAAQY,qBAAqB,IAAIP,WAAWF,MAAM,KAAK,GAAG;QAC5D,OAAOE,UAAU,CAAC,EAAE;IACtB;IAEA,OAAO;QAAED,KAAKC;IAAW;AAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"build-config.d.ts","sourceRoot":"","sources":["../../src/plugin/build-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AActD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC1B,GAAG;IACF,KAAK,EAAE;QACL,eAAe,EAAE,MAAM,CAAA;QACvB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;CACF,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;AAE7B;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CA+GpF"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
|
|
1
2
|
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
|
2
3
|
import { buildConfig as payloadBuildConfig } from 'payload';
|
|
3
4
|
import { getTokenStore } from '../auth/token-store.js';
|
|
4
5
|
import { contentAPIAdapter } from '../db-content-api/index.js';
|
|
5
6
|
import { health } from '../endpoints/health.js';
|
|
6
7
|
import { oAuth2Plugin } from '../oauth/index.js';
|
|
8
|
+
import { contentApiStorageAdapter } from '../storage-content-api/index.js';
|
|
7
9
|
/**
|
|
8
10
|
* Figma platform wrapper for Payload's buildConfig.
|
|
9
11
|
* Automatically injects:
|
|
@@ -52,6 +54,39 @@ import { oAuth2Plugin } from '../oauth/index.js';
|
|
|
52
54
|
url
|
|
53
55
|
});
|
|
54
56
|
}
|
|
57
|
+
// Build storage plugin if there are upload collections and storage is not disabled
|
|
58
|
+
const uploadCollections = (config.collections || []).filter((c)=>c.upload);
|
|
59
|
+
let storagePlugin;
|
|
60
|
+
if (uploadCollections.length > 0 && config.figma.storage !== false) {
|
|
61
|
+
const storageConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
|
|
62
|
+
baseUrl: url,
|
|
63
|
+
contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
|
|
64
|
+
} : process.env.FIGMA_DEV_JWT === 'true' ? {
|
|
65
|
+
auth: {
|
|
66
|
+
mode: 'devJwt'
|
|
67
|
+
},
|
|
68
|
+
baseUrl: url,
|
|
69
|
+
contentSystemId
|
|
70
|
+
} : {
|
|
71
|
+
auth: {
|
|
72
|
+
mode: 'tokenStore',
|
|
73
|
+
tokenStore: getTokenStore()
|
|
74
|
+
},
|
|
75
|
+
baseUrl: url,
|
|
76
|
+
contentSystemId
|
|
77
|
+
};
|
|
78
|
+
const adapter = contentApiStorageAdapter(storageConfig);
|
|
79
|
+
const collectionsMap = uploadCollections.reduce((acc, c)=>{
|
|
80
|
+
acc[c.slug] = {
|
|
81
|
+
adapter,
|
|
82
|
+
disableLocalStorage: true
|
|
83
|
+
};
|
|
84
|
+
return acc;
|
|
85
|
+
}, {});
|
|
86
|
+
storagePlugin = cloudStoragePlugin({
|
|
87
|
+
collections: collectionsMap
|
|
88
|
+
});
|
|
89
|
+
}
|
|
55
90
|
// Build complete config with Figma platform defaults
|
|
56
91
|
const configWithFigmaDefaults = {
|
|
57
92
|
...config,
|
|
@@ -68,6 +103,9 @@ import { oAuth2Plugin } from '../oauth/index.js';
|
|
|
68
103
|
// Add oauth to plugins if not already present
|
|
69
104
|
plugins: [
|
|
70
105
|
...config.plugins ?? [],
|
|
106
|
+
...storagePlugin ? [
|
|
107
|
+
storagePlugin
|
|
108
|
+
] : [],
|
|
71
109
|
oAuth2Plugin({
|
|
72
110
|
collections: [
|
|
73
111
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugin/build-config.ts"],"sourcesContent":["import type { Config, SanitizedConfig } from 'payload'\n\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { buildConfig as payloadBuildConfig } from 'payload'\n\nimport { getTokenStore } from '../auth/token-store.js'\nimport { contentAPIAdapter } from '../db-content-api/index.js'\nimport { health } from '../endpoints/health.js'\nimport { oAuth2Plugin } from '../oauth/index.js'\n\n/**\n * Configuration type for Payload projects on the Figma platform.\n *\n * Differences from standard Payload Config:\n * - `db`: Omitted (auto-injected by Figma platform)\n * - `secret`: Omitted (auto-injected by Figma platform)\n * - `editor`: Optional (defaults to lexicalEditor() if not provided)\n *\n * @example\n * // Minimal config\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...]\n * }\n *\n * @example\n * // With custom editor\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...],\n * editor: lexicalEditor({ features: [...] })\n * }\n *\n * @example\n * // Disable Content System (use custom db)\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!,\n * useContentSystem: false\n * },\n * db: mongooseAdapter({ url: process.env.DATABASE_URI }),\n * collections: [...]\n * }\n */\nexport type FigmaConfig = {\n editor?: Config['editor']\n} & {\n figma: {\n contentSystemId: string\n useContentSystem?: boolean\n }\n} & Omit<Config, 'db' | 'editor' | 'secret'> &\n Partial<Pick<Config, 'db'>>\n\n/**\n * Figma platform wrapper for Payload's buildConfig.\n * Automatically injects:\n * - Figma database adapter\n * - Secret (platform-provided)\n * - Default lexicalEditor (if no custom editor specified)\n *\n * @param config - Payload configuration (db and secret omitted, editor optional)\n * @returns Promise<SanitizedConfig> - Built Payload configuration\n */\nexport async function buildFigmaConfig(config: FigmaConfig): Promise<SanitizedConfig> {\n // Validate required environment variable\n if (!config.figma.contentSystemId) {\n throw new Error(\n 'FIGMA_CONTENT_SYSTEM_ID is required. ' +\n 'Ensure your .env file contains: FIGMA_CONTENT_SYSTEM_ID=your-content-system-id',\n )\n }\n\n const DEFAULT_CONTENT_API_URL =\n 'https://us-east-1.cms-tenants-001-staging.figmacontentstaging.com'\n\n const url = process.env.FIGMA_CONTENT_API_URL || DEFAULT_CONTENT_API_URL\n const { contentSystemId } = config.figma\n\n // Determine database adapter based on environment\n let db: Config['db']\n\n if (config.figma.useContentSystem === false) {\n db = config.db!\n } else if (process.env.FIGMA_CONTENT_API_ACCESS_KEY) {\n db = contentAPIAdapter({\n auth: { apiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY, mode: 'apiKey' },\n contentSystemId,\n url,\n })\n } else if (process.env.FIGMA_DEV_JWT === 'true') {\n db = contentAPIAdapter({\n auth: { mode: 'devJwt' },\n contentSystemId,\n url,\n })\n } else {\n db = contentAPIAdapter({\n auth: { mode: 'tokenStore', tokenStore: getTokenStore() },\n contentSystemId,\n url,\n })\n }\n\n // Build complete config with Figma platform defaults\n const configWithFigmaDefaults: Config = {\n ...config,\n custom: {\n figma: config.figma || {},\n },\n db,\n editor: config.editor ?? lexicalEditor(),\n endpoints: [...(config.endpoints ?? []), health],\n secret: 'MY_SECRET',\n // Add oauth to plugins if not already present\n plugins: [\n ...(config.plugins ?? []),\n oAuth2Plugin({\n collections: [\n {\n slug: 'users',\n // Use adapter because Figma app doesn't support OIDC scopes\n authorizationURLParams: {\n response_mode: 'query',\n response_type: 'code',\n },\n clientID: process.env.FIGMA_OAUTH_CLIENT_ID!,\n clientSecret: process.env.FIGMA_OAUTH_CLIENT_SECRET,\n identityMetadata: 'https://staging.figma.com/.well-known/openid-configuration',\n scope: ['current_user:read'],\n token: 'id_token', // Adapter creates JWT id_token from Figma user info\n usernameField: 'email',\n },\n ],\n debug: !!process.env.DEBUG,\n disabled: false,\n }),\n ],\n }\n\n // Delegate to Payload's buildConfig\n return await payloadBuildConfig(configWithFigmaDefaults)\n}\n"],"names":["lexicalEditor","buildConfig","payloadBuildConfig","getTokenStore","contentAPIAdapter","health","oAuth2Plugin","buildFigmaConfig","config","figma","contentSystemId","Error","DEFAULT_CONTENT_API_URL","url","process","env","FIGMA_CONTENT_API_URL","db","useContentSystem","FIGMA_CONTENT_API_ACCESS_KEY","auth","apiKey","mode","FIGMA_DEV_JWT","tokenStore","
|
|
1
|
+
{"version":3,"sources":["../../src/plugin/build-config.ts"],"sourcesContent":["import type { Config, SanitizedConfig } from 'payload'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { buildConfig as payloadBuildConfig } from 'payload'\n\nimport type { StorageClientConfig } from '../storage-content-api/index.js'\n\nimport { getTokenStore } from '../auth/token-store.js'\nimport { contentAPIAdapter } from '../db-content-api/index.js'\nimport { health } from '../endpoints/health.js'\nimport { oAuth2Plugin } from '../oauth/index.js'\nimport { contentApiStorageAdapter } from '../storage-content-api/index.js'\n\n/**\n * Configuration type for Payload projects on the Figma platform.\n *\n * Differences from standard Payload Config:\n * - `db`: Omitted (auto-injected by Figma platform)\n * - `secret`: Omitted (auto-injected by Figma platform)\n * - `editor`: Optional (defaults to lexicalEditor() if not provided)\n *\n * @example\n * // Minimal config\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...]\n * }\n *\n * @example\n * // With custom editor\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!\n * },\n * collections: [...],\n * editor: lexicalEditor({ features: [...] })\n * }\n *\n * @example\n * // Disable Content System (use custom db)\n * const config: FigmaConfig = {\n * figma: {\n * contentSystemId: process.env.FIGMA_CONTENT_SYSTEM_ID!,\n * useContentSystem: false\n * },\n * db: mongooseAdapter({ url: process.env.DATABASE_URI }),\n * collections: [...]\n * }\n */\nexport type FigmaConfig = {\n editor?: Config['editor']\n} & {\n figma: {\n contentSystemId: string\n storage?: boolean\n useContentSystem?: boolean\n }\n} & Omit<Config, 'db' | 'editor' | 'secret'> &\n Partial<Pick<Config, 'db'>>\n\n/**\n * Figma platform wrapper for Payload's buildConfig.\n * Automatically injects:\n * - Figma database adapter\n * - Secret (platform-provided)\n * - Default lexicalEditor (if no custom editor specified)\n *\n * @param config - Payload configuration (db and secret omitted, editor optional)\n * @returns Promise<SanitizedConfig> - Built Payload configuration\n */\nexport async function buildFigmaConfig(config: FigmaConfig): Promise<SanitizedConfig> {\n // Validate required environment variable\n if (!config.figma.contentSystemId) {\n throw new Error(\n 'FIGMA_CONTENT_SYSTEM_ID is required. ' +\n 'Ensure your .env file contains: FIGMA_CONTENT_SYSTEM_ID=your-content-system-id',\n )\n }\n\n const DEFAULT_CONTENT_API_URL =\n 'https://us-east-1.cms-tenants-001-staging.figmacontentstaging.com'\n\n const url = process.env.FIGMA_CONTENT_API_URL || DEFAULT_CONTENT_API_URL\n const { contentSystemId } = config.figma\n\n // Determine database adapter based on environment\n let db: Config['db']\n\n if (config.figma.useContentSystem === false) {\n db = config.db!\n } else if (process.env.FIGMA_CONTENT_API_ACCESS_KEY) {\n db = contentAPIAdapter({\n auth: { apiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY, mode: 'apiKey' },\n contentSystemId,\n url,\n })\n } else if (process.env.FIGMA_DEV_JWT === 'true') {\n db = contentAPIAdapter({\n auth: { mode: 'devJwt' },\n contentSystemId,\n url,\n })\n } else {\n db = contentAPIAdapter({\n auth: { mode: 'tokenStore', tokenStore: getTokenStore() },\n contentSystemId,\n url,\n })\n }\n\n // Build storage plugin if there are upload collections and storage is not disabled\n const uploadCollections = (config.collections || []).filter((c) => c.upload)\n let storagePlugin: ReturnType<typeof cloudStoragePlugin> | undefined\n\n if (uploadCollections.length > 0 && config.figma.storage !== false) {\n const storageConfig: StorageClientConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY\n ? { baseUrl: url, contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY }\n : process.env.FIGMA_DEV_JWT === 'true'\n ? {\n auth: { mode: 'devJwt' },\n baseUrl: url,\n contentSystemId,\n }\n : {\n auth: { mode: 'tokenStore', tokenStore: getTokenStore() },\n baseUrl: url,\n contentSystemId,\n }\n\n const adapter = contentApiStorageAdapter(storageConfig)\n\n const collectionsMap = uploadCollections.reduce(\n (acc, c) => {\n acc[c.slug] = { adapter, disableLocalStorage: true }\n return acc\n },\n {} as Record<string, { adapter: typeof adapter; disableLocalStorage: boolean }>,\n )\n\n storagePlugin = cloudStoragePlugin({ collections: collectionsMap })\n }\n\n // Build complete config with Figma platform defaults\n const configWithFigmaDefaults: Config = {\n ...config,\n custom: {\n figma: config.figma || {},\n },\n db,\n editor: config.editor ?? lexicalEditor(),\n endpoints: [...(config.endpoints ?? []), health],\n secret: 'MY_SECRET',\n // Add oauth to plugins if not already present\n plugins: [\n ...(config.plugins ?? []),\n ...(storagePlugin ? [storagePlugin] : []),\n oAuth2Plugin({\n collections: [\n {\n slug: 'users',\n // Use adapter because Figma app doesn't support OIDC scopes\n authorizationURLParams: {\n response_mode: 'query',\n response_type: 'code',\n },\n clientID: process.env.FIGMA_OAUTH_CLIENT_ID!,\n clientSecret: process.env.FIGMA_OAUTH_CLIENT_SECRET,\n identityMetadata: 'https://staging.figma.com/.well-known/openid-configuration',\n scope: ['current_user:read'],\n token: 'id_token', // Adapter creates JWT id_token from Figma user info\n usernameField: 'email',\n },\n ],\n debug: !!process.env.DEBUG,\n disabled: false,\n }),\n ],\n }\n\n // Delegate to Payload's buildConfig\n return await payloadBuildConfig(configWithFigmaDefaults)\n}\n"],"names":["cloudStoragePlugin","lexicalEditor","buildConfig","payloadBuildConfig","getTokenStore","contentAPIAdapter","health","oAuth2Plugin","contentApiStorageAdapter","buildFigmaConfig","config","figma","contentSystemId","Error","DEFAULT_CONTENT_API_URL","url","process","env","FIGMA_CONTENT_API_URL","db","useContentSystem","FIGMA_CONTENT_API_ACCESS_KEY","auth","apiKey","mode","FIGMA_DEV_JWT","tokenStore","uploadCollections","collections","filter","c","upload","storagePlugin","length","storage","storageConfig","baseUrl","contentApiKey","adapter","collectionsMap","reduce","acc","slug","disableLocalStorage","configWithFigmaDefaults","custom","editor","endpoints","secret","plugins","authorizationURLParams","response_mode","response_type","clientID","FIGMA_OAUTH_CLIENT_ID","clientSecret","FIGMA_OAUTH_CLIENT_SECRET","identityMetadata","scope","token","usernameField","debug","DEBUG","disabled"],"mappings":"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"}
|