@payloadcms/figma 0.0.1-alpha.29 → 0.0.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/api/control-plane.d.ts +1 -22
  2. package/dist/api/control-plane.d.ts.map +1 -1
  3. package/dist/api/control-plane.js +15 -54
  4. package/dist/api/control-plane.js.map +1 -1
  5. package/dist/api/figma-api.d.ts.map +1 -1
  6. package/dist/api/figma-api.js +12 -38
  7. package/dist/api/figma-api.js.map +1 -1
  8. package/dist/auth/callback-server.d.ts.map +1 -1
  9. package/dist/auth/callback-server.js +1 -2
  10. package/dist/auth/callback-server.js.map +1 -1
  11. package/dist/auth/crypto-utils.d.ts.map +1 -1
  12. package/dist/auth/crypto-utils.js +5 -5
  13. package/dist/auth/crypto-utils.js.map +1 -1
  14. package/dist/auth/oauth-flow.d.ts +1 -1
  15. package/dist/auth/oauth-flow.d.ts.map +1 -1
  16. package/dist/auth/oauth-flow.js +5 -5
  17. package/dist/auth/oauth-flow.js.map +1 -1
  18. package/dist/auth/project-token.d.ts.map +1 -1
  19. package/dist/auth/project-token.js +4 -9
  20. package/dist/auth/project-token.js.map +1 -1
  21. package/dist/auth/token-store.d.ts +0 -10
  22. package/dist/auth/token-store.d.ts.map +1 -1
  23. package/dist/auth/token-store.js +0 -20
  24. package/dist/auth/token-store.js.map +1 -1
  25. package/dist/cli.js +0 -3
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/deploy.d.ts +0 -4
  28. package/dist/commands/deploy.d.ts.map +1 -1
  29. package/dist/commands/deploy.js +81 -41
  30. package/dist/commands/deploy.js.map +1 -1
  31. package/dist/commands/init.d.ts +5 -1
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +85 -90
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/list-tokens.js +2 -2
  36. package/dist/commands/list-tokens.js.map +1 -1
  37. package/dist/commands/login.d.ts +1 -8
  38. package/dist/commands/login.d.ts.map +1 -1
  39. package/dist/commands/login.js +6 -12
  40. package/dist/commands/login.js.map +1 -1
  41. package/dist/commands/logout.js +2 -2
  42. package/dist/commands/logout.js.map +1 -1
  43. package/dist/config/oauth.d.ts +0 -5
  44. package/dist/config/oauth.d.ts.map +1 -1
  45. package/dist/config/oauth.js +4 -9
  46. package/dist/config/oauth.js.map +1 -1
  47. package/dist/index.d.ts +1 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -3
  50. package/dist/index.js.map +1 -1
  51. package/dist/oauth/components/LoginButton/index.d.ts.map +1 -1
  52. package/dist/oauth/components/LoginButton/index.js +6 -50
  53. package/dist/oauth/components/LoginButton/index.js.map +1 -1
  54. package/dist/oauth/components/LoginButton/index.scss +3 -50
  55. package/dist/oauth/components/LogoutButton/index.d.ts.map +1 -1
  56. package/dist/oauth/components/LogoutButton/index.js +1 -0
  57. package/dist/oauth/components/LogoutButton/index.js.map +1 -1
  58. package/dist/oauth/defaults.d.ts.map +1 -1
  59. package/dist/oauth/defaults.js +5 -29
  60. package/dist/oauth/defaults.js.map +1 -1
  61. package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -1
  62. package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
  63. package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -1
  64. package/dist/oauth/index.d.ts.map +1 -1
  65. package/dist/oauth/index.js +6 -42
  66. package/dist/oauth/index.js.map +1 -1
  67. package/dist/oauth/strategy/index.d.ts.map +1 -1
  68. package/dist/oauth/strategy/index.js +2 -6
  69. package/dist/oauth/strategy/index.js.map +1 -1
  70. package/dist/oauth/types.d.ts +6 -6
  71. package/dist/oauth/types.d.ts.map +1 -1
  72. package/dist/oauth/types.js.map +1 -1
  73. package/dist/oauth/utilities/createCookieOptions.d.ts.map +1 -1
  74. package/dist/oauth/utilities/createCookieOptions.js.map +1 -1
  75. package/dist/oauth/utilities/refreshTokens.d.ts.map +1 -1
  76. package/dist/oauth/utilities/refreshTokens.js +1 -1
  77. package/dist/oauth/utilities/refreshTokens.js.map +1 -1
  78. package/dist/plugin/build-config.d.ts +4 -20
  79. package/dist/plugin/build-config.d.ts.map +1 -1
  80. package/dist/plugin/build-config.js +9 -51
  81. package/dist/plugin/build-config.js.map +1 -1
  82. package/dist/utils/env-management.d.ts +8 -9
  83. package/dist/utils/env-management.d.ts.map +1 -1
  84. package/dist/utils/env-management.js +74 -73
  85. package/dist/utils/env-management.js.map +1 -1
  86. package/dist/utils/formatter.d.ts.map +1 -1
  87. package/dist/utils/formatter.js +7 -1
  88. package/dist/utils/formatter.js.map +1 -1
  89. package/dist/utils/messages.d.ts.map +1 -1
  90. package/dist/utils/messages.js +7 -5
  91. package/dist/utils/messages.js.map +1 -1
  92. package/dist/utils/payload-config-ast.d.ts +3 -22
  93. package/dist/utils/payload-config-ast.d.ts.map +1 -1
  94. package/dist/utils/payload-config-ast.js +31 -89
  95. package/dist/utils/payload-config-ast.js.map +1 -1
  96. package/dist/utils/payload-config-finder.d.ts +2 -2
  97. package/dist/utils/payload-config-finder.d.ts.map +1 -1
  98. package/dist/utils/payload-config-finder.js +9 -48
  99. package/dist/utils/payload-config-finder.js.map +1 -1
  100. package/dist/utils/payload-config-modifier.d.ts.map +1 -1
  101. package/dist/utils/payload-config-modifier.js +4 -33
  102. package/dist/utils/payload-config-modifier.js.map +1 -1
  103. package/dist/utils/payload-package-check.d.ts +0 -5
  104. package/dist/utils/payload-package-check.d.ts.map +1 -1
  105. package/dist/utils/payload-package-check.js +4 -48
  106. package/dist/utils/payload-package-check.js.map +1 -1
  107. package/dist/utils/project.d.ts.map +1 -1
  108. package/dist/utils/project.js +6 -8
  109. package/dist/utils/project.js.map +1 -1
  110. package/dist/utils/s3-upload.d.ts +1 -0
  111. package/dist/utils/s3-upload.d.ts.map +1 -1
  112. package/dist/utils/s3-upload.js +3 -10
  113. package/dist/utils/s3-upload.js.map +1 -1
  114. package/dist/utils/token-display.d.ts.map +1 -1
  115. package/dist/utils/token-display.js +5 -11
  116. package/dist/utils/token-display.js.map +1 -1
  117. package/dist/utils/typescript-validator.d.ts.map +1 -1
  118. package/dist/utils/typescript-validator.js +6 -1
  119. package/dist/utils/typescript-validator.js.map +1 -1
  120. package/package.json +6 -20
  121. package/dist/db-content-api/generated/content-api-types.d.ts +0 -3129
  122. package/dist/db-content-api/generated/content-api-types.d.ts.map +0 -1
  123. package/dist/db-content-api/generated/content-api-types.js +0 -7
  124. package/dist/db-content-api/generated/content-api-types.js.map +0 -1
  125. package/dist/db-content-api/index.d.ts +0 -31
  126. package/dist/db-content-api/index.d.ts.map +0 -1
  127. package/dist/db-content-api/index.js +0 -842
  128. package/dist/db-content-api/index.js.map +0 -1
  129. package/dist/db-content-api/types.d.ts +0 -31
  130. package/dist/db-content-api/types.d.ts.map +0 -1
  131. package/dist/db-content-api/types.js +0 -7
  132. package/dist/db-content-api/types.js.map +0 -1
  133. package/dist/endpoints/health.d.ts +0 -3
  134. package/dist/endpoints/health.d.ts.map +0 -1
  135. package/dist/endpoints/health.js +0 -11
  136. package/dist/endpoints/health.js.map +0 -1
  137. package/dist/exports/client.d.ts +0 -3
  138. package/dist/exports/client.d.ts.map +0 -1
  139. package/dist/exports/client.js +0 -4
  140. package/dist/exports/client.js.map +0 -1
  141. package/dist/oauth/utilities/isDuplicateKeyError.d.ts +0 -7
  142. package/dist/oauth/utilities/isDuplicateKeyError.d.ts.map +0 -1
  143. package/dist/oauth/utilities/isDuplicateKeyError.js +0 -14
  144. package/dist/oauth/utilities/isDuplicateKeyError.js.map +0 -1
  145. package/dist/templates/README.md +0 -19
@@ -1,842 +0,0 @@
1
- import { createDatabaseAdapter, traverseFields } from 'payload';
2
- import { v4 as uuid } from 'uuid';
3
- import { getValidProjectToken } from '../auth/project-token.js';
4
- // Field name mapping between Payload and Content API
5
- // TODO: Update these when Content API renames to internal_id/external_id (or similar)
6
- const PAYLOAD_ID_FIELD = 'id';
7
- const CONTENT_API_KEY_FIELD = 'key';
8
- // Module-level cache for devJwt tokens (encapsulated, not exposed on adapter)
9
- const devJwtCache = new Map();
10
- // Private helper to fetch JWT from dev endpoint (not exposed on adapter type)
11
- async function fetchDevJwt(url, contentSystemId) {
12
- const usesSuperUser = !contentSystemId || contentSystemId === 'test-system' || contentSystemId === '*';
13
- const contentSystemIdForJWT = usesSuperUser ? '*' : contentSystemId;
14
- const response = await fetch(`${url}/dev/jwt`, {
15
- body: JSON.stringify({
16
- content_system_id: contentSystemIdForJWT
17
- }),
18
- headers: {
19
- 'Content-Type': 'application/json'
20
- },
21
- method: 'POST'
22
- });
23
- if (!response.ok) {
24
- throw new Error(`Failed to get dev JWT: ${response.status} ${response.statusText}`);
25
- }
26
- const { token } = await response.json();
27
- return token;
28
- }
29
- async function init() {
30
- // Log which auth mode is being used
31
- if (this.auth.mode === 'apiKey') {
32
- this.payload.logger.info('Using API Key authentication');
33
- } else if (this.auth.mode === 'tokenStore') {
34
- this.payload.logger.info('Using TokenStore authentication (local dev)');
35
- } else {
36
- this.payload.logger.info('Using Dev JWT authentication (testing)');
37
- }
38
- // Drop database if PAYLOAD_DROP_DATABASE is set (for tests)
39
- if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
40
- this.payload.logger.info(`---- DROPPING CONTENT API SYSTEM (${this.contentSystemId}) ----`);
41
- try {
42
- await this.makeRequest('/dev/clear-db', {
43
- body: {
44
- contentSystemId: this.contentSystemId
45
- },
46
- method: 'POST'
47
- });
48
- this.payload.logger.info('---- DROPPED CONTENT API SYSTEM ----');
49
- } catch (error) {
50
- this.payload.logger.warn(`Failed to drop content system: ${error.message}`);
51
- }
52
- }
53
- // Fetch existing collections once
54
- let existingKeys;
55
- try {
56
- const response = await this.makeRequest(`/api/v0/collections?contentSystemId=${this.contentSystemId}`, {
57
- method: 'GET'
58
- });
59
- existingKeys = new Set(response.data.map((c)=>c.key));
60
- this.payload.logger.info(`Found ${existingKeys.size} existing collections`);
61
- } catch (error) {
62
- this.payload.logger.warn(`Failed to fetch collections, will attempt to create all: ${error.message}`);
63
- existingKeys = new Set();
64
- }
65
- // Create only missing collections
66
- for (const collection of this.payload.config.collections){
67
- if (!existingKeys.has(collection.slug)) {
68
- await createCollection.call(this, collection.slug);
69
- }
70
- }
71
- // Create only missing global collections
72
- for (const global of this.payload.config.globals || []){
73
- const globalKey = `_global-${global.slug}`;
74
- if (!existingKeys.has(globalKey)) {
75
- await createCollection.call(this, globalKey);
76
- }
77
- }
78
- return;
79
- }
80
- // Helper to create a collection
81
- async function createCollection(collectionKey) {
82
- try {
83
- await this.makeRequest('/api/v0/collections', {
84
- body: {
85
- name: collectionKey,
86
- contentSystemId: this.contentSystemId,
87
- key: collectionKey
88
- },
89
- method: 'POST'
90
- });
91
- this.payload.logger.info(`Created collection: ${collectionKey}`);
92
- } catch (error) {
93
- const errorMessage = error.message;
94
- // Still handle 409 gracefully in case of race conditions
95
- if (!errorMessage.includes('already exists') && !errorMessage.includes('409')) {
96
- this.payload.logger.warn(`Failed to create collection ${collectionKey}: ${errorMessage}`);
97
- }
98
- }
99
- }
100
- // Helper function to make HTTP requests to the Content API
101
- async function makeRequest(path, options = {}) {
102
- const { body, method = 'POST', retryCount = 0 } = options;
103
- // Build auth header based on auth mode
104
- let authHeader;
105
- if (this.auth.mode === 'apiKey') {
106
- authHeader = {
107
- 'X-Api-Key': this.auth.apiKey
108
- };
109
- } else if (this.auth.mode === 'tokenStore') {
110
- const token = await getValidProjectToken(this.auth.tokenStore, this.contentSystemId);
111
- if (!token) {
112
- throw new Error('Authentication required. Run `npx @payloadcms/figma login` to authenticate.');
113
- }
114
- authHeader = {
115
- Authorization: `Bearer ${token}`
116
- };
117
- } else {
118
- // devJwt - inline cache handling
119
- let token = devJwtCache.get(this.contentSystemId);
120
- if (!token) {
121
- token = await fetchDevJwt(this.url, this.contentSystemId);
122
- devJwtCache.set(this.contentSystemId, token);
123
- this.payload.logger.info(`Dev JWT acquired for content system: ${this.contentSystemId}`);
124
- }
125
- authHeader = {
126
- Authorization: `Bearer ${token}`
127
- };
128
- }
129
- const requestUrl = `${this.url}${path}`;
130
- const response = await fetch(requestUrl, {
131
- body: body ? JSON.stringify(body) : undefined,
132
- headers: {
133
- 'Content-Type': 'application/json',
134
- ...authHeader
135
- },
136
- method
137
- });
138
- // Read response as text first (better for debugging)
139
- const text = await response.text();
140
- // Handle 401 - retry logic differs per mode
141
- if (response.status === 401 && retryCount === 0 && this.auth.mode === 'devJwt') {
142
- this.payload.logger.info('Dev JWT expired, refreshing...');
143
- devJwtCache.delete(this.contentSystemId);
144
- return this.makeRequest(path, {
145
- body,
146
- method,
147
- retryCount: retryCount + 1
148
- });
149
- }
150
- // tokenStore handles refresh internally via getValidProjectToken
151
- // apiKey doesn't expire - 401 means invalid key
152
- // Check HTTP status
153
- if (!response.ok) {
154
- this.payload.logger.error({
155
- msg: `HTTP ${response.status} from ${path}`,
156
- response: text
157
- });
158
- throw new Error(`Content API HTTP ${response.status}: ${text.substring(0, 200)}`);
159
- }
160
- // Parse JSON response
161
- try {
162
- const parsed = JSON.parse(text);
163
- // Check if the response is an error object (HTTP 200 but with error in body)
164
- if (parsed && typeof parsed === 'object' && 'error' in parsed) {
165
- throw new Error(`Content API error: ${parsed.message || parsed.error}`);
166
- }
167
- return parsed;
168
- } catch (error) {
169
- // If it's already our custom error, re-throw it
170
- if (error instanceof Error && error.message.startsWith('Content API error:')) {
171
- throw error;
172
- }
173
- this.payload.logger.error({
174
- err: error instanceof Error ? error : new Error(String(error)),
175
- msg: `Failed to parse JSON response from ${path}`,
176
- response: text
177
- });
178
- throw new Error(`Invalid JSON response from content API: ${text.substring(0, 100)}...`);
179
- }
180
- }
181
- // ⚠️ TEMPORARY WORKAROUND - Remove once Content API is fixed
182
- //
183
- // TODO: Content API should accept Payload's native Where format instead of requiring conversion.
184
- // This converter exists because after a GitHub merge, Content API changed to expect a different format.
185
- //
186
- // Payload's format: { fieldName: { operator: value }, and: [...], or: [...] }
187
- // Content API expects: { and: [{ path, operator, value }], or: [{ path, operator, value }] }
188
- //
189
- // Once Content API accepts Payload's format:
190
- // 1. Remove this function entirely
191
- // 2. Remove all calls to convertPayloadWhereToContentAPI()
192
- // 3. Pass `where` directly to Content API endpoints
193
- // 4. Update CONTENT_API_ISSUES.md to mark Workaround #1 as resolved
194
- function convertPayloadWhereToContentAPI(where, insideLogicalOperator = false) {
195
- // ⚠️ WORKAROUND: Empty where {} should be { and: [] } not undefined
196
- // Content API requires a where clause structure even for "no filter"
197
- if (!where || Object.keys(where).length === 0) {
198
- return {
199
- and: []
200
- };
201
- }
202
- const conditions = [];
203
- for (const [key, value] of Object.entries(where)){
204
- if (key === 'and') {
205
- // Recursively convert nested 'and' conditions
206
- const nestedConditions = value.map((item)=>convertPayloadWhereToContentAPI(item, true));
207
- return {
208
- and: nestedConditions
209
- };
210
- } else if (key === 'or') {
211
- // Recursively convert nested 'or' conditions
212
- const nestedConditions = value.map((item)=>convertPayloadWhereToContentAPI(item, true));
213
- return {
214
- or: nestedConditions
215
- };
216
- } else {
217
- // Convert field conditions: { fieldName: { operator: value } }
218
- // to: { path: fieldName, operator, value }
219
- const operators = value;
220
- for (const [op, operatorValue] of Object.entries(operators)){
221
- let finalValue = operatorValue;
222
- // Add wildcards for contains/like operators (Payload doesn't add them)
223
- if ((op === 'contains' || op === 'like') && typeof operatorValue === 'string') {
224
- finalValue = `%${operatorValue}%`;
225
- }
226
- conditions.push({
227
- // Map Payload's 'id' field to Content API's 'key' field
228
- operator: op,
229
- path: key === PAYLOAD_ID_FIELD ? CONTENT_API_KEY_FIELD : key,
230
- value: finalValue
231
- });
232
- }
233
- }
234
- }
235
- // If inside a logical operator (and/or), return conditions directly
236
- // Otherwise, wrap in 'and' to match WhereClause type
237
- if (insideLogicalOperator && conditions.length === 1) {
238
- return conditions[0];
239
- }
240
- return {
241
- and: conditions
242
- };
243
- }
244
- // Add fallback sort to ensure consistent ordering when sorting by non-unique fields
245
- // Matches MongoDB adapter behavior
246
- function addFallbackSort(sort, collectionConfig) {
247
- if (!sort || !collectionConfig) {
248
- return sort;
249
- }
250
- const sortArray = Array.isArray(sort) ? sort : [
251
- sort
252
- ];
253
- // Determine fallback sort field
254
- let fallbackSort = '-id';
255
- if (collectionConfig.timestamps !== false) {
256
- fallbackSort = '-createdAt';
257
- }
258
- // Check if fallback sort is already included
259
- const hasFallback = sortArray.some((item)=>item === fallbackSort || item === fallbackSort.replace('-', ''));
260
- if (hasFallback) {
261
- return sort;
262
- }
263
- // Check if all sort fields are unique (then no fallback needed)
264
- // For simplicity, we'll always add fallback - checking uniqueness requires field traversal
265
- // which would be expensive. This matches the conservative approach.
266
- // Add fallback sort - always return array to preserve multiple sort fields
267
- return [
268
- ...sortArray,
269
- fallbackSort
270
- ];
271
- }
272
- // ⚠️ TEMPORARY WORKAROUND - Remove once Content API is fixed
273
- //
274
- // TODO: Content API should accept Payload's native Sort format instead of requiring conversion.
275
- // This converter exists because Content API expects a different format.
276
- //
277
- // Payload's format: "-createdAt" or ["createdAt", "-updatedAt"]
278
- // Content API expects: [{ path: "createdAt", direction: "dsc" }]
279
- //
280
- // Once Content API accepts Payload's format:
281
- // 1. Remove this function entirely
282
- // 2. Remove all calls to convertPayloadSortToContentAPI()
283
- // 3. Pass `sort` directly to Content API endpoints
284
- function convertPayloadSortToContentAPI(sort) {
285
- if (!sort) {
286
- return undefined;
287
- }
288
- const sortArray = Array.isArray(sort) ? sort : [
289
- sort
290
- ];
291
- return sortArray.map((field)=>{
292
- let path = field;
293
- let direction = 'asc';
294
- // Check if field starts with '-' for descending
295
- if (field.startsWith('-')) {
296
- path = field.substring(1); // Remove the '-'
297
- direction = 'desc';
298
- }
299
- // ⚠️ WORKAROUND: Strip "version." prefix from field names
300
- // Payload sends "-version.createdAt" for version sort fields
301
- // but Content API expects just "createdAt"
302
- // TODO: Content API should handle version field paths correctly
303
- if (path.startsWith('version.')) {
304
- path = path.substring(8); // Remove "version." prefix
305
- }
306
- return {
307
- direction,
308
- path
309
- };
310
- });
311
- }
312
- /**
313
- * Transform data before sending to Content API (WRITE operations)
314
- *
315
- * Conversions applied:
316
- * - RichText fields: Objects -> JSON strings
317
- * - Date fields: Date objects -> Unix timestamps (numbers)
318
- */ function dataToContentAPI(collectionSlug, data) {
319
- if (!data || typeof data !== 'object') {
320
- return data;
321
- }
322
- // Deep clone to avoid mutating original data
323
- const transformed = JSON.parse(JSON.stringify(data));
324
- // Get collection config
325
- const isGlobal = collectionSlug.startsWith('_global-');
326
- const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
327
- const collectionConfig = isGlobal ? this.payload.config.globals?.find((g)=>g.slug === actualSlug) : this.payload.config.collections.find((c)=>c.slug === actualSlug);
328
- if (!collectionConfig?.fields) {
329
- return transformed;
330
- }
331
- // Use Payload's traverseFields to iterate over all fields
332
- const callback = ({ field, ref })=>{
333
- if (!('name' in field) || !field.name) {
334
- return;
335
- }
336
- if (!ref || typeof ref !== 'object') {
337
- return;
338
- }
339
- const current = ref;
340
- const value = current[field.name];
341
- if (value !== null && value !== undefined) {
342
- // RichText: object -> JSON string
343
- if (field.type === 'richText' && typeof value !== 'string') {
344
- current[field.name] = JSON.stringify(value);
345
- } else if (field.type === 'date') {
346
- const dateValue = value instanceof Date ? value : new Date(value);
347
- if (!isNaN(dateValue.getTime())) {
348
- current[field.name] = dateValue.getTime();
349
- }
350
- }
351
- }
352
- };
353
- traverseFields({
354
- callback,
355
- fields: collectionConfig.fields,
356
- ref: transformed
357
- });
358
- return transformed;
359
- }
360
- /**
361
- * Transform data received from Content API (READ operations)
362
- *
363
- * Conversions applied:
364
- * - RichText fields: JSON strings -> Objects
365
- * - Date fields: Unix timestamps (numbers) -> Date objects
366
- */ function dataFromContentAPI(collectionSlug, data) {
367
- if (!data || typeof data !== 'object') {
368
- return data;
369
- }
370
- // Deep clone to avoid mutating original data
371
- const transformed = JSON.parse(JSON.stringify(data));
372
- // Get collection config
373
- const isGlobal = collectionSlug.startsWith('_global-');
374
- const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
375
- const collectionConfig = isGlobal ? this.payload.config.globals?.find((g)=>g.slug === actualSlug) : this.payload.config.collections.find((c)=>c.slug === actualSlug);
376
- if (!collectionConfig?.fields) {
377
- return transformed;
378
- }
379
- // Use Payload's traverseFields to iterate over all fields
380
- const callback = ({ field, parentPath, ref })=>{
381
- if (!('name' in field) || !field.name) {
382
- return;
383
- }
384
- if (!ref || typeof ref !== 'object') {
385
- return;
386
- }
387
- const current = ref;
388
- const value = current[field.name];
389
- if (value !== null && value !== undefined) {
390
- // RichText: JSON string -> object
391
- if (field.type === 'richText' && typeof value === 'string') {
392
- try {
393
- current[field.name] = JSON.parse(value);
394
- } catch (error) {
395
- const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
396
- this.payload.logger.warn({
397
- err: error instanceof Error ? error : new Error(String(error)),
398
- msg: `Failed to parse richtext field '${fieldPath}' in collection '${collectionSlug}'`
399
- });
400
- }
401
- } else if (field.type === 'date') {
402
- current[field.name] = new Date(value);
403
- }
404
- }
405
- };
406
- traverseFields({
407
- callback,
408
- fields: collectionConfig.fields,
409
- ref: transformed
410
- });
411
- return transformed;
412
- }
413
- // Helper to unwrap Content API document format to Payload format
414
- // Content API returns: { id, data: { ...fields }, createdAt, updatedAt }
415
- // Payload expects: { id, ...fields, createdAt, updatedAt }
416
- function unwrapDocument(doc, collectionSlug) {
417
- if (!doc) {
418
- return doc;
419
- }
420
- // TODO
421
- // Document has 'key', DocumentVersion has 'documentId'
422
- const docId = CONTENT_API_KEY_FIELD in doc ? doc[CONTENT_API_KEY_FIELD] : doc.documentId;
423
- const baseDoc = {
424
- ...doc.data,
425
- createdAt: doc.createdAt,
426
- [PAYLOAD_ID_FIELD]: docId,
427
- updatedAt: doc.updatedAt
428
- };
429
- // Transform data from Content API format to Payload format
430
- if (collectionSlug) {
431
- return dataFromContentAPI.call(this, collectionSlug, baseDoc);
432
- }
433
- return baseDoc;
434
- }
435
- const findMany = async function findMany({ collectionSlug, limit, page, pagination, skip, sort, where }) {
436
- // Payload semantics: limit: 0 means "no limit" (get all documents)
437
- // Pass this through to Content API which follows the same convention
438
- const effectiveLimit = limit;
439
- const offset = skip ?? (limit === 0 ? 0 : (page - 1) * limit);
440
- // Add fallback sort to ensure consistent ordering (matching MongoDB behavior)
441
- const collectionConfig = this.payload.config.collections.find((c)=>c.slug === collectionSlug);
442
- const sortWithFallback = addFallbackSort(sort, collectionConfig);
443
- const response = await this.makeRequest('/api/v0/documents:find', {
444
- body: {
445
- collectionKey: collectionSlug,
446
- contentSystemId: this.contentSystemId,
447
- limit: effectiveLimit,
448
- offset,
449
- sort: convertPayloadSortToContentAPI(sortWithFallback),
450
- where: convertPayloadWhereToContentAPI(where)
451
- },
452
- method: 'POST'
453
- });
454
- return {
455
- docs: response.result.data.map((doc)=>unwrapDocument.call(this, doc, collectionSlug)),
456
- hasNextPage: pagination !== false && limit > 0 && response.result.pagination.total > offset + response.result.data.length,
457
- hasPrevPage: pagination !== false && offset > 0,
458
- limit,
459
- nextPage: pagination !== false && response.result.pagination.total > offset + response.result.data.length ? page + 1 : null,
460
- page,
461
- pagingCounter: offset + 1,
462
- prevPage: pagination !== false && offset > 0 ? page - 1 : null,
463
- totalDocs: response.result.pagination.total,
464
- totalPages: limit > 0 ? Math.ceil(response.result.pagination.total / limit) : 1
465
- };
466
- };
467
- const find = async function find({ collection: collectionSlug, limit = 0, page = 1, pagination, skip, sort, where }) {
468
- // Apply defaultSort from collection config if no sort is provided
469
- const collectionConfig = this.payload.config.collections.find((c)=>c.slug === collectionSlug);
470
- const effectiveSort = sort || collectionConfig?.defaultSort;
471
- return findMany.call(this, {
472
- collectionSlug,
473
- limit,
474
- page,
475
- pagination,
476
- skip,
477
- sort: effectiveSort,
478
- where: where ?? {}
479
- });
480
- };
481
- const findVersions = async function findVersions({ collection: collectionSlug, limit = 0, page = 1, pagination, skip, sort, where }) {
482
- const offset = skip ?? (page - 1) * limit;
483
- const response = await this.makeRequest('/api/v0/document_versions:find', {
484
- body: {
485
- collectionKey: collectionSlug,
486
- contentSystemId: this.contentSystemId,
487
- limit,
488
- offset,
489
- sort: convertPayloadSortToContentAPI(sort),
490
- where: convertPayloadWhereToContentAPI(where ?? {})
491
- },
492
- method: 'POST'
493
- });
494
- return {
495
- docs: response.result.data.map((doc)=>unwrapDocument.call(this, doc, collectionSlug)),
496
- hasNextPage: pagination !== false && limit > 0 && response.result.pagination.total > offset + response.result.data.length,
497
- hasPrevPage: pagination !== false && offset > 0,
498
- limit,
499
- nextPage: pagination !== false && response.result.pagination.total > offset + response.result.data.length ? page + 1 : null,
500
- page,
501
- pagingCounter: offset + 1,
502
- prevPage: pagination !== false && offset > 0 ? page - 1 : null,
503
- totalDocs: response.result.pagination.total,
504
- totalPages: limit > 0 ? Math.ceil(response.result.pagination.total / limit) : 1
505
- };
506
- };
507
- const queryDrafts = async function queryDrafts({ collection: collectionSlug, limit, page, pagination, sort, where = {} }) {
508
- // TODO: review this
509
- // Content API doesn't have a separate "draft" concept
510
- // It only has versions with a "latest" flag
511
- // PayloadCMS drafts would typically be versions where latest=false
512
- // But this mapping may need adjustment based on your use case
513
- const result = await this.findVersions({
514
- collection: collectionSlug,
515
- limit,
516
- page,
517
- pagination,
518
- sort,
519
- where: {
520
- ...where,
521
- // Query non-latest versions as "drafts"
522
- latest: {
523
- equals: false
524
- }
525
- }
526
- });
527
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
528
- return result;
529
- };
530
- const createVersion = async function createVersion({ autosave, collectionSlug, parent, versionData }) {
531
- const response = await this.makeRequest('/api/v0/document_versions:create', {
532
- body: {
533
- collectionKey: collectionSlug,
534
- contentSystemId: this.contentSystemId,
535
- data: dataToContentAPI.call(this, collectionSlug, versionData),
536
- documentKey: parent,
537
- latest: !autosave
538
- },
539
- method: 'POST'
540
- });
541
- // Handle union type response: { id: string } | { data: DocumentVersion }
542
- if ('data' in response.result) {
543
- return unwrapDocument.call(this, response.result.data, collectionSlug);
544
- }
545
- throw new Error('Unexpected response format from createVersion');
546
- };
547
- const updateVersion = async function updateVersion({ id, collection: collectionSlug, versionData, where }) {
548
- const response = await this.makeRequest('/api/v0/document_versions:update', {
549
- body: {
550
- collectionKey: collectionSlug,
551
- contentSystemId: this.contentSystemId,
552
- createOnMissing: false,
553
- data: dataToContentAPI.call(this, collectionSlug, versionData),
554
- where: convertPayloadWhereToContentAPI(where ?? {
555
- id: {
556
- equals: id
557
- }
558
- })
559
- },
560
- method: 'POST'
561
- });
562
- // Handle union type response: { count: number } | { data: DocumentVersion[] }
563
- if (!response?.result || !('data' in response.result)) {
564
- throw new Error('No document data in updateVersion response');
565
- }
566
- return unwrapDocument.call(this, response.result.data[0], collectionSlug);
567
- };
568
- const deleteVersions = async function deleteVersions({ collection: collectionSlug, where }) {
569
- await this.makeRequest('/api/v0/document_versions:delete', {
570
- body: {
571
- collectionKey: collectionSlug,
572
- contentSystemId: this.contentSystemId,
573
- returning: false,
574
- where: convertPayloadWhereToContentAPI(where)
575
- },
576
- method: 'POST'
577
- });
578
- };
579
- const findOne = async function findOne({ collection, where }) {
580
- const { docs: [first] } = await this.find({
581
- collection,
582
- limit: 1,
583
- pagination: false,
584
- where
585
- });
586
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
587
- return first ?? null;
588
- };
589
- const updateMany = async function updateMany({ collection: collectionSlug, data, where }) {
590
- const response = await this.makeRequest('/api/v0/documents:update', {
591
- body: {
592
- collectionKey: collectionSlug,
593
- contentSystemId: this.contentSystemId,
594
- createOnMissing: false,
595
- data: dataToContentAPI.call(this, collectionSlug, data),
596
- returning: {},
597
- where: convertPayloadWhereToContentAPI(where)
598
- },
599
- method: 'POST'
600
- });
601
- // Handle union type: { count: number } | { data: Document[] }
602
- if (response.result && 'data' in response.result) {
603
- return response.result.data.map((doc)=>unwrapDocument.call(this, doc, collectionSlug));
604
- }
605
- return null;
606
- };
607
- const updateOne = async function updateOne({ id, collection: collectionSlug, data, where }) {
608
- const response = await this.makeRequest('/api/v0/documents:update', {
609
- body: {
610
- collectionKey: collectionSlug,
611
- contentSystemId: this.contentSystemId,
612
- createOnMissing: false,
613
- data: dataToContentAPI.call(this, collectionSlug, data),
614
- where: convertPayloadWhereToContentAPI(where ?? {
615
- id: {
616
- equals: id
617
- }
618
- })
619
- },
620
- method: 'POST'
621
- });
622
- // Handle union type: { count: number } | { data: Document[] }
623
- if (response.result && 'data' in response.result) {
624
- const doc = response.result.data[0];
625
- if (!doc) {
626
- throw new Error('No document data in updateOne response');
627
- }
628
- return unwrapDocument.call(this, doc, collectionSlug);
629
- }
630
- throw new Error('Unexpected response format from updateOne');
631
- };
632
- const deleteMany = async function deleteMany({ collection: collectionSlug, where }) {
633
- await this.makeRequest('/api/v0/documents:delete', {
634
- body: {
635
- collectionKey: collectionSlug,
636
- contentSystemId: this.contentSystemId,
637
- returning: false,
638
- where: convertPayloadWhereToContentAPI(where)
639
- },
640
- method: 'POST'
641
- });
642
- };
643
- const deleteOne = async function deleteOne({ collection: collectionSlug, where }) {
644
- await this.makeRequest('/api/v0/documents:delete', {
645
- body: {
646
- collectionKey: collectionSlug,
647
- contentSystemId: this.contentSystemId,
648
- returning: false,
649
- where: convertPayloadWhereToContentAPI(where)
650
- },
651
- method: 'POST'
652
- });
653
- };
654
- const create = async function create({ collection: collectionSlug, data }) {
655
- // Generate a document key if not provided
656
- // Content API has two ID fields:
657
- // - `id` (UUID, auto-generated by DB, internal use only)
658
- // - `key` (text, public document identifier, maps to Payload's `id`)
659
- // If Payload doesn't provide an ID, we generate a UUID
660
- // (same as other SQL-based Payload adapters like db-postgres and db-drizzle)
661
- const key = data[PAYLOAD_ID_FIELD] || uuid();
662
- const response = await this.makeRequest('/api/v0/documents:create', {
663
- body: {
664
- collectionKey: collectionSlug,
665
- contentSystemId: this.contentSystemId,
666
- data: dataToContentAPI.call(this, collectionSlug, data),
667
- key
668
- },
669
- method: 'POST'
670
- });
671
- // Handle union type response: { id: string } | { data: Document }
672
- if ('data' in response.result) {
673
- return unwrapDocument.call(this, response.result.data, collectionSlug);
674
- }
675
- throw new Error('Unexpected response format from create');
676
- };
677
- const count = async function count({ collection: collectionSlug, where }) {
678
- const response = await this.makeRequest('/api/v0/documents:count', {
679
- body: {
680
- collectionKey: collectionSlug,
681
- contentSystemId: this.contentSystemId,
682
- where: convertPayloadWhereToContentAPI(where)
683
- },
684
- method: 'POST'
685
- });
686
- return {
687
- totalDocs: response.result.count
688
- };
689
- };
690
- const countVersions = async function countVersions({ collection: collectionSlug, where }) {
691
- const response = await this.makeRequest('/api/v0/document_versions:count', {
692
- body: {
693
- collectionKey: collectionSlug,
694
- contentSystemId: this.contentSystemId,
695
- where: convertPayloadWhereToContentAPI(where)
696
- },
697
- method: 'POST'
698
- });
699
- return {
700
- totalDocs: response.result.count
701
- };
702
- };
703
- const upsert = async function upsert({ collection: collectionSlug, data, where }) {
704
- const response = await this.makeRequest('/api/v0/documents:update', {
705
- body: {
706
- collectionKey: collectionSlug,
707
- contentSystemId: this.contentSystemId,
708
- createOnMissing: true,
709
- data: dataToContentAPI.call(this, collectionSlug, data),
710
- where: convertPayloadWhereToContentAPI(where)
711
- },
712
- method: 'POST'
713
- });
714
- // Handle union type: { count: number } | { data: Document[] }
715
- if (response.result && 'data' in response.result) {
716
- const doc = response.result.data[0];
717
- if (!doc) {
718
- throw new Error('No document data in upsert response');
719
- }
720
- return unwrapDocument.call(this, doc, collectionSlug);
721
- }
722
- throw new Error('Unexpected response format from upsert');
723
- };
724
- // TODO: global should be a prefix or a resource in the REST API / Table in the DB?
725
- const getGlobalSlug = (slug)=>`_global-${slug}`;
726
- const createGlobal = function({ slug, data }) {
727
- return this.create({
728
- collection: getGlobalSlug(slug),
729
- data: {
730
- ...data,
731
- globalType: slug
732
- }
733
- });
734
- };
735
- const findGlobal = function({ slug, where = {} }) {
736
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
737
- return this.findOne({
738
- collection: getGlobalSlug(slug),
739
- where
740
- });
741
- };
742
- const updateGlobal = function({ slug, data }) {
743
- return this.updateOne({
744
- collection: getGlobalSlug(slug),
745
- data,
746
- where: {}
747
- });
748
- };
749
- const findGlobalVersions = function({ global: slug, limit, page, pagination, skip, sort, where }) {
750
- return this.findVersions({
751
- collection: getGlobalSlug(slug),
752
- limit,
753
- page,
754
- pagination,
755
- skip,
756
- sort,
757
- where
758
- });
759
- };
760
- const createGlobalVersion = function(// eslint-disable-next-line @typescript-eslint/no-explicit-any
761
- { autosave, createdAt, globalSlug, updatedAt, versionData, ...rest }) {
762
- return this.createVersion({
763
- autosave,
764
- collectionSlug: getGlobalSlug(globalSlug),
765
- createdAt,
766
- parent: rest.parent,
767
- updatedAt,
768
- versionData
769
- });
770
- };
771
- const updateGlobalVersion = function({ id, global: slug, versionData, where, ...rest }) {
772
- // UpdateVersion accepts either id OR where, not both
773
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
774
- const args = {
775
- collection: getGlobalSlug(slug),
776
- versionData,
777
- ...rest
778
- };
779
- if (id !== undefined) {
780
- args.id = id;
781
- } else if (where !== undefined) {
782
- args.where = where;
783
- }
784
- return this.updateVersion(args);
785
- };
786
- const countGlobalVersions = function({ global: slug, where }) {
787
- return this.countVersions({
788
- collection: getGlobalSlug(slug),
789
- where
790
- });
791
- };
792
- export const contentAPIAdapter = (opts)=>{
793
- return {
794
- name: 'content_api',
795
- defaultIDType: 'text',
796
- init: ({ payload })=>{
797
- return createDatabaseAdapter({
798
- name: 'content_api',
799
- auth: opts.auth,
800
- beginTransaction: ()=>{
801
- return Promise.resolve('no-op-transaction');
802
- },
803
- commitTransaction: async ()=>{},
804
- contentSystemId: opts.contentSystemId,
805
- count,
806
- countGlobalVersions,
807
- countVersions,
808
- create,
809
- createGlobal,
810
- createGlobalVersion,
811
- createVersion,
812
- defaultIDType: 'text',
813
- deleteMany,
814
- deleteOne,
815
- deleteVersions,
816
- find,
817
- findDistinct: ()=>{
818
- return Promise.reject(new Error('findDistinct is not yet implemented for Content API adapter'));
819
- },
820
- findGlobal,
821
- findGlobalVersions,
822
- findOne,
823
- findVersions,
824
- init,
825
- makeRequest,
826
- packageName: '@payloadcms/db-content-api',
827
- payload,
828
- queryDrafts,
829
- rollbackTransaction: async ()=>{},
830
- updateGlobal,
831
- updateGlobalVersion,
832
- updateMany,
833
- updateOne,
834
- updateVersion,
835
- upsert,
836
- url: opts.url
837
- });
838
- }
839
- };
840
- };
841
-
842
- //# sourceMappingURL=index.js.map