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

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 (229) hide show
  1. package/dist/api/control-plane.d.ts +22 -1
  2. package/dist/api/control-plane.d.ts.map +1 -1
  3. package/dist/api/control-plane.js +54 -15
  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 +42 -12
  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 +2 -1
  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 +9 -4
  20. package/dist/auth/project-token.js.map +1 -1
  21. package/dist/auth/token-store.d.ts +10 -0
  22. package/dist/auth/token-store.d.ts.map +1 -1
  23. package/dist/auth/token-store.js +20 -0
  24. package/dist/auth/token-store.js.map +1 -1
  25. package/dist/cli.js +3 -0
  26. package/dist/cli.js.map +1 -1
  27. package/dist/commands/deploy.d.ts +4 -0
  28. package/dist/commands/deploy.d.ts.map +1 -1
  29. package/dist/commands/deploy.js +68 -126
  30. package/dist/commands/deploy.js.map +1 -1
  31. package/dist/commands/init.d.ts +1 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +90 -85
  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 +8 -1
  38. package/dist/commands/login.d.ts.map +1 -1
  39. package/dist/commands/login.js +12 -6
  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 +5 -0
  44. package/dist/config/oauth.d.ts.map +1 -1
  45. package/dist/config/oauth.js +9 -4
  46. package/dist/config/oauth.js.map +1 -1
  47. package/dist/db-content-api/README.md +108 -0
  48. package/dist/db-content-api/generated/content-api-types.d.ts +3702 -0
  49. package/dist/db-content-api/generated/content-api-types.d.ts.map +1 -0
  50. package/dist/db-content-api/generated/content-api-types.js +7 -0
  51. package/dist/db-content-api/generated/content-api-types.js.map +1 -0
  52. package/dist/db-content-api/index.d.ts +18 -0
  53. package/dist/db-content-api/index.d.ts.map +1 -0
  54. package/dist/db-content-api/index.js +572 -0
  55. package/dist/db-content-api/index.js.map +1 -0
  56. package/dist/db-content-api/temp-utilities/pagination.d.ts +13 -0
  57. package/dist/db-content-api/temp-utilities/pagination.d.ts.map +1 -0
  58. package/dist/db-content-api/temp-utilities/pagination.js +45 -0
  59. package/dist/db-content-api/temp-utilities/pagination.js.map +1 -0
  60. package/dist/db-content-api/temp-utilities/slug.d.ts +2 -0
  61. package/dist/db-content-api/temp-utilities/slug.d.ts.map +1 -0
  62. package/dist/db-content-api/temp-utilities/slug.js +3 -0
  63. package/dist/db-content-api/temp-utilities/slug.js.map +1 -0
  64. package/dist/db-content-api/temp-utilities/sorting.d.ts +7 -0
  65. package/dist/db-content-api/temp-utilities/sorting.d.ts.map +1 -0
  66. package/dist/db-content-api/temp-utilities/sorting.js +71 -0
  67. package/dist/db-content-api/temp-utilities/sorting.js.map +1 -0
  68. package/dist/db-content-api/temp-utilities/unwrapDocument.d.ts +4 -0
  69. package/dist/db-content-api/temp-utilities/unwrapDocument.d.ts.map +1 -0
  70. package/dist/db-content-api/temp-utilities/unwrapDocument.js +53 -0
  71. package/dist/db-content-api/temp-utilities/unwrapDocument.js.map +1 -0
  72. package/dist/db-content-api/utilities/auth.d.ts +30 -0
  73. package/dist/db-content-api/utilities/auth.d.ts.map +1 -0
  74. package/dist/db-content-api/utilities/auth.js +81 -0
  75. package/dist/db-content-api/utilities/auth.js.map +1 -0
  76. package/dist/db-content-api/utilities/data/applyDefaults.d.ts +7 -0
  77. package/dist/db-content-api/utilities/data/applyDefaults.d.ts.map +1 -0
  78. package/dist/db-content-api/utilities/data/applyDefaults.js +59 -0
  79. package/dist/db-content-api/utilities/data/applyDefaults.js.map +1 -0
  80. package/dist/db-content-api/utilities/data/castFieldValue.d.ts +11 -0
  81. package/dist/db-content-api/utilities/data/castFieldValue.d.ts.map +1 -0
  82. package/dist/db-content-api/utilities/data/castFieldValue.js +66 -0
  83. package/dist/db-content-api/utilities/data/castFieldValue.js.map +1 -0
  84. package/dist/db-content-api/utilities/data/index.d.ts +28 -0
  85. package/dist/db-content-api/utilities/data/index.d.ts.map +1 -0
  86. package/dist/db-content-api/utilities/data/index.js +166 -0
  87. package/dist/db-content-api/utilities/data/index.js.map +1 -0
  88. package/dist/db-content-api/utilities/data/stripFields.d.ts +48 -0
  89. package/dist/db-content-api/utilities/data/stripFields.d.ts.map +1 -0
  90. package/dist/db-content-api/utilities/data/stripFields.js +195 -0
  91. package/dist/db-content-api/utilities/data/stripFields.js.map +1 -0
  92. package/dist/db-content-api/utilities/joins.d.ts +55 -0
  93. package/dist/db-content-api/utilities/joins.d.ts.map +1 -0
  94. package/dist/db-content-api/utilities/joins.js +130 -0
  95. package/dist/db-content-api/utilities/joins.js.map +1 -0
  96. package/dist/db-content-api/utilities/where.d.ts +12 -0
  97. package/dist/db-content-api/utilities/where.d.ts.map +1 -0
  98. package/dist/db-content-api/utilities/where.js +107 -0
  99. package/dist/db-content-api/utilities/where.js.map +1 -0
  100. package/dist/endpoints/health.d.ts +3 -0
  101. package/dist/endpoints/health.d.ts.map +1 -0
  102. package/dist/endpoints/health.js +11 -0
  103. package/dist/endpoints/health.js.map +1 -0
  104. package/dist/exports/client.d.ts +4 -0
  105. package/dist/exports/client.d.ts.map +1 -0
  106. package/dist/exports/client.js +5 -0
  107. package/dist/exports/client.js.map +1 -0
  108. package/dist/index.d.ts +2 -1
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +3 -1
  111. package/dist/index.js.map +1 -1
  112. package/dist/oauth/components/LoginButton/index.d.ts.map +1 -1
  113. package/dist/oauth/components/LoginButton/index.js +50 -6
  114. package/dist/oauth/components/LoginButton/index.js.map +1 -1
  115. package/dist/oauth/components/LoginButton/index.scss +50 -3
  116. package/dist/oauth/components/LogoutButton/index.d.ts.map +1 -1
  117. package/dist/oauth/components/LogoutButton/index.js +0 -1
  118. package/dist/oauth/components/LogoutButton/index.js.map +1 -1
  119. package/dist/oauth/defaults.d.ts.map +1 -1
  120. package/dist/oauth/defaults.js +29 -5
  121. package/dist/oauth/defaults.js.map +1 -1
  122. package/dist/oauth/endpoints/getLoginEndpoint.d.ts.map +1 -1
  123. package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
  124. package/dist/oauth/endpoints/getLoginEndpoint.js.map +1 -1
  125. package/dist/oauth/index.d.ts.map +1 -1
  126. package/dist/oauth/index.js +42 -6
  127. package/dist/oauth/index.js.map +1 -1
  128. package/dist/oauth/strategy/index.d.ts.map +1 -1
  129. package/dist/oauth/strategy/index.js +6 -2
  130. package/dist/oauth/strategy/index.js.map +1 -1
  131. package/dist/oauth/types.d.ts +6 -6
  132. package/dist/oauth/types.d.ts.map +1 -1
  133. package/dist/oauth/types.js.map +1 -1
  134. package/dist/oauth/utilities/createCookieOptions.d.ts.map +1 -1
  135. package/dist/oauth/utilities/createCookieOptions.js.map +1 -1
  136. package/dist/oauth/utilities/isDuplicateKeyError.d.ts +7 -0
  137. package/dist/oauth/utilities/isDuplicateKeyError.d.ts.map +1 -0
  138. package/dist/oauth/utilities/isDuplicateKeyError.js +14 -0
  139. package/dist/oauth/utilities/isDuplicateKeyError.js.map +1 -0
  140. package/dist/oauth/utilities/refreshTokens.d.ts.map +1 -1
  141. package/dist/oauth/utilities/refreshTokens.js +1 -1
  142. package/dist/oauth/utilities/refreshTokens.js.map +1 -1
  143. package/dist/plugin/build-config.d.ts +21 -4
  144. package/dist/plugin/build-config.d.ts.map +1 -1
  145. package/dist/plugin/build-config.js +103 -9
  146. package/dist/plugin/build-config.js.map +1 -1
  147. package/dist/storage-content-api/api-types.d.ts +161 -0
  148. package/dist/storage-content-api/api-types.d.ts.map +1 -0
  149. package/dist/storage-content-api/api-types.js +6 -0
  150. package/dist/storage-content-api/api-types.js.map +1 -0
  151. package/dist/storage-content-api/client-uploads/ClientUploadHandler.d.ts +9 -0
  152. package/dist/storage-content-api/client-uploads/ClientUploadHandler.d.ts.map +1 -0
  153. package/dist/storage-content-api/client-uploads/ClientUploadHandler.js +42 -0
  154. package/dist/storage-content-api/client-uploads/ClientUploadHandler.js.map +1 -0
  155. package/dist/storage-content-api/client-uploads/generateSignedURL.d.ts +13 -0
  156. package/dist/storage-content-api/client-uploads/generateSignedURL.d.ts.map +1 -0
  157. package/dist/storage-content-api/client-uploads/generateSignedURL.js +31 -0
  158. package/dist/storage-content-api/client-uploads/generateSignedURL.js.map +1 -0
  159. package/dist/storage-content-api/client.d.ts +6 -0
  160. package/dist/storage-content-api/client.d.ts.map +1 -0
  161. package/dist/storage-content-api/client.js +36 -0
  162. package/dist/storage-content-api/client.js.map +1 -0
  163. package/dist/storage-content-api/generateURL.d.ts +7 -0
  164. package/dist/storage-content-api/generateURL.d.ts.map +1 -0
  165. package/dist/storage-content-api/generateURL.js +7 -0
  166. package/dist/storage-content-api/generateURL.js.map +1 -0
  167. package/dist/storage-content-api/handleDelete.d.ts +8 -0
  168. package/dist/storage-content-api/handleDelete.d.ts.map +1 -0
  169. package/dist/storage-content-api/handleDelete.js +15 -0
  170. package/dist/storage-content-api/handleDelete.js.map +1 -0
  171. package/dist/storage-content-api/handleUpload.d.ts +11 -0
  172. package/dist/storage-content-api/handleUpload.d.ts.map +1 -0
  173. package/dist/storage-content-api/handleUpload.js +136 -0
  174. package/dist/storage-content-api/handleUpload.js.map +1 -0
  175. package/dist/storage-content-api/index.d.ts +5 -0
  176. package/dist/storage-content-api/index.d.ts.map +1 -0
  177. package/dist/storage-content-api/index.js +31 -0
  178. package/dist/storage-content-api/index.js.map +1 -0
  179. package/dist/storage-content-api/staticHandler.d.ts +10 -0
  180. package/dist/storage-content-api/staticHandler.d.ts.map +1 -0
  181. package/dist/storage-content-api/staticHandler.js +55 -0
  182. package/dist/storage-content-api/staticHandler.js.map +1 -0
  183. package/dist/storage-content-api/types.d.ts +10 -0
  184. package/dist/storage-content-api/types.d.ts.map +1 -0
  185. package/dist/storage-content-api/types.js +3 -0
  186. package/dist/storage-content-api/types.js.map +1 -0
  187. package/dist/templates/README.md +19 -0
  188. package/dist/utils/env-management.d.ts +9 -8
  189. package/dist/utils/env-management.d.ts.map +1 -1
  190. package/dist/utils/env-management.js +73 -74
  191. package/dist/utils/env-management.js.map +1 -1
  192. package/dist/utils/formatter.d.ts.map +1 -1
  193. package/dist/utils/formatter.js +1 -7
  194. package/dist/utils/formatter.js.map +1 -1
  195. package/dist/utils/lambda-config.d.ts.map +1 -1
  196. package/dist/utils/lambda-config.js +1 -0
  197. package/dist/utils/lambda-config.js.map +1 -1
  198. package/dist/utils/messages.d.ts.map +1 -1
  199. package/dist/utils/messages.js +5 -7
  200. package/dist/utils/messages.js.map +1 -1
  201. package/dist/utils/payload-config-ast.d.ts +22 -3
  202. package/dist/utils/payload-config-ast.d.ts.map +1 -1
  203. package/dist/utils/payload-config-ast.js +89 -31
  204. package/dist/utils/payload-config-ast.js.map +1 -1
  205. package/dist/utils/payload-config-finder.d.ts +2 -2
  206. package/dist/utils/payload-config-finder.d.ts.map +1 -1
  207. package/dist/utils/payload-config-finder.js +48 -9
  208. package/dist/utils/payload-config-finder.js.map +1 -1
  209. package/dist/utils/payload-config-modifier.d.ts.map +1 -1
  210. package/dist/utils/payload-config-modifier.js +33 -4
  211. package/dist/utils/payload-config-modifier.js.map +1 -1
  212. package/dist/utils/payload-package-check.d.ts +5 -0
  213. package/dist/utils/payload-package-check.d.ts.map +1 -1
  214. package/dist/utils/payload-package-check.js +48 -4
  215. package/dist/utils/payload-package-check.js.map +1 -1
  216. package/dist/utils/project.d.ts.map +1 -1
  217. package/dist/utils/project.js +8 -6
  218. package/dist/utils/project.js.map +1 -1
  219. package/dist/utils/s3-upload.d.ts +0 -1
  220. package/dist/utils/s3-upload.d.ts.map +1 -1
  221. package/dist/utils/s3-upload.js +10 -3
  222. package/dist/utils/s3-upload.js.map +1 -1
  223. package/dist/utils/token-display.d.ts.map +1 -1
  224. package/dist/utils/token-display.js +11 -5
  225. package/dist/utils/token-display.js.map +1 -1
  226. package/dist/utils/typescript-validator.d.ts.map +1 -1
  227. package/dist/utils/typescript-validator.js +1 -6
  228. package/dist/utils/typescript-validator.js.map +1 -1
  229. package/package.json +24 -7
@@ -0,0 +1,166 @@
1
+ import { flattenAllFields, traverseFields } from 'payload';
2
+ import { applyDefaults } from './applyDefaults.js';
3
+ import { castFieldValue } from './castFieldValue.js';
4
+ import { stripFields } from './stripFields.js';
5
+ /**
6
+ * Transform data before sending to Content API (WRITE operations)
7
+ *
8
+ * Conversions applied:
9
+ * - Default values: Apply static defaults for undefined fields (when applyDefaults=true)
10
+ * - Type casting: Convert values to match field types (e.g., number -> string for text fields)
11
+ * - RichText fields: Objects -> JSON strings
12
+ *
13
+ * Note: Date fields are automatically converted to ISO strings by JSON.stringify()
14
+ */ export function dataToContentAPI(payload, collectionSlug, data, options) {
15
+ if (!data || typeof data !== 'object') {
16
+ return data;
17
+ }
18
+ // Deep clone to avoid mutating original data
19
+ const transformed = JSON.parse(JSON.stringify(data));
20
+ // Get collection config
21
+ const isGlobal = collectionSlug.startsWith('_global-');
22
+ const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
23
+ const collectionConfig = isGlobal ? payload.config.globals?.find((g)=>g.slug === actualSlug) : payload.config.collections.find((c)=>c.slug === actualSlug);
24
+ if (!collectionConfig?.fields) {
25
+ return transformed;
26
+ }
27
+ // Apply static defaults recursively to handle nested structures (arrays, groups)
28
+ // This must happen before traverseFields to ensure defaults are in place
29
+ if (options?.applyDefaults) {
30
+ applyDefaults(transformed, collectionConfig.fields);
31
+ }
32
+ // Use Payload's traverseFields to iterate over all fields for transformations
33
+ const callback = ({ field, ref })=>{
34
+ if (!('name' in field) || !field.name) {
35
+ return;
36
+ }
37
+ if (!ref || typeof ref !== 'object') {
38
+ return;
39
+ }
40
+ const current = ref;
41
+ let value = current[field.name];
42
+ // Transform existing values
43
+ if (value !== null && value !== undefined) {
44
+ // Step 1: Apply type casting (e.g., number -> string for text fields)
45
+ value = castFieldValue(field, value);
46
+ current[field.name] = value;
47
+ // Step 2: Apply Content API specific transformations
48
+ // RichText: object -> JSON string
49
+ if (field.type === 'richText' && typeof value !== 'string') {
50
+ current[field.name] = JSON.stringify(value);
51
+ }
52
+ // Date fields are already handled by JSON.stringify() which converts Date -> ISO string
53
+ }
54
+ };
55
+ traverseFields({
56
+ callback,
57
+ fields: collectionConfig.fields,
58
+ ref: transformed
59
+ });
60
+ stripFields({
61
+ config: payload.config,
62
+ data: transformed,
63
+ fields: flattenAllFields({
64
+ cache: true,
65
+ fields: collectionConfig.fields
66
+ }),
67
+ reservedKeys: [
68
+ 'id',
69
+ 'globalType'
70
+ ]
71
+ });
72
+ // Convert numeric ID to string for Content API
73
+ const customIDType = payload.collections?.[collectionSlug]?.customIDType;
74
+ if (customIDType === 'number') {
75
+ transformed.id = String(transformed.id);
76
+ }
77
+ return transformed;
78
+ }
79
+ /**
80
+ * Transform data received from Content API (READ operations)
81
+ *
82
+ * Conversions applied:
83
+ * - RichText fields: JSON strings -> Objects
84
+ *
85
+ * Note: Date fields come as ISO strings from Content API and stay as strings
86
+ * (matching MongoDB and other adapters' behavior)
87
+ */ export function dataFromContentAPI(payload, collectionSlug, data) {
88
+ if (!data || typeof data !== 'object') {
89
+ return data;
90
+ }
91
+ // Deep clone to avoid mutating original data
92
+ const transformed = JSON.parse(JSON.stringify(data));
93
+ // Get collection config
94
+ const isGlobal = collectionSlug.startsWith('_global-');
95
+ const actualSlug = isGlobal ? collectionSlug.substring(8) : collectionSlug;
96
+ const collectionConfig = isGlobal ? payload.config.globals?.find((g)=>g.slug === actualSlug) : payload.config.collections.find((c)=>c.slug === actualSlug);
97
+ if (!collectionConfig?.fields) {
98
+ return transformed;
99
+ }
100
+ // Use Payload's traverseFields to iterate over all fields
101
+ const callback = ({ field, parentPath, ref })=>{
102
+ if (!('name' in field) || !field.name) {
103
+ return;
104
+ }
105
+ if (!ref || typeof ref !== 'object') {
106
+ return;
107
+ }
108
+ const current = ref;
109
+ const value = current[field.name];
110
+ if (value !== null && value !== undefined) {
111
+ // RichText: JSON string -> object
112
+ if (field.type === 'richText' && typeof value === 'string') {
113
+ try {
114
+ current[field.name] = JSON.parse(value);
115
+ } catch (error) {
116
+ const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
117
+ payload.logger.warn({
118
+ err: error instanceof Error ? error : new Error(String(error)),
119
+ msg: `Failed to parse richtext field '${fieldPath}' in collection '${collectionSlug}'`
120
+ });
121
+ }
122
+ }
123
+ // Date fields: Already ISO strings from Content API, no conversion needed
124
+ }
125
+ };
126
+ traverseFields({
127
+ callback,
128
+ fields: collectionConfig.fields,
129
+ ref: transformed
130
+ });
131
+ // Convert string ID to number if collection uses numeric custom ID
132
+ const customIDType = payload.collections?.[collectionSlug]?.customIDType;
133
+ if (customIDType === 'number') {
134
+ transformed.id = Number(transformed.id);
135
+ }
136
+ stripFields({
137
+ config: payload.config,
138
+ data: transformed,
139
+ fields: flattenAllFields({
140
+ cache: true,
141
+ fields: collectionConfig.fields
142
+ }),
143
+ reservedKeys: [
144
+ 'id',
145
+ 'globalType'
146
+ ]
147
+ });
148
+ // Handle auth-specific fields that aren't in the field schema
149
+ // When these fields are null in DB, Content API omits them from response (undefined)
150
+ // But Payload expects them to be null, not undefined
151
+ // Only add them if the collection has auth enabled
152
+ if ('auth' in collectionConfig && collectionConfig.auth) {
153
+ const authFields = [
154
+ 'resetPasswordExpiration',
155
+ 'lockUntil'
156
+ ];
157
+ for (const fieldName of authFields){
158
+ if (!(fieldName in transformed)) {
159
+ transformed[fieldName] = null;
160
+ }
161
+ }
162
+ }
163
+ return transformed;
164
+ }
165
+
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/db-content-api/utilities/data/index.ts"],"sourcesContent":["import type { CollectionConfig, GlobalConfig, Payload, TraverseFieldsCallback } from 'payload'\n\nimport { flattenAllFields, traverseFields } from 'payload'\n\nimport type { components } from '../../generated/content-api-types.js'\n\nimport { applyDefaults } from './applyDefaults.js'\nimport { castFieldValue } from './castFieldValue.js'\nimport { stripFields } from './stripFields.js'\n\ntype DataWithOperations = components['schemas']['DataWithOperations']\n\n/**\n * Transform data before sending to Content API (WRITE operations)\n *\n * Conversions applied:\n * - Default values: Apply static defaults for undefined fields (when applyDefaults=true)\n * - Type casting: Convert values to match field types (e.g., number -> string for text fields)\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 options?: { applyDefaults?: boolean },\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 // Apply static defaults recursively to handle nested structures (arrays, groups)\n // This must happen before traverseFields to ensure defaults are in place\n if (options?.applyDefaults) {\n applyDefaults(transformed, collectionConfig.fields)\n }\n\n // Use Payload's traverseFields to iterate over all fields for transformations\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 let value = current[field.name]\n\n // Transform existing values\n if (value !== null && value !== undefined) {\n // Step 1: Apply type casting (e.g., number -> string for text fields)\n value = castFieldValue(field, value)\n current[field.name] = value\n\n // Step 2: Apply Content API specific transformations\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 stripFields({\n config: payload.config,\n data: transformed,\n fields: flattenAllFields({ cache: true, fields: collectionConfig.fields }),\n reservedKeys: ['id', 'globalType'],\n })\n\n // Convert numeric ID to string for Content API\n const customIDType = payload.collections?.[collectionSlug]?.customIDType\n if (customIDType === 'number') {\n transformed.id = String(transformed.id)\n }\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 // Convert string ID to number if collection uses numeric custom ID\n const customIDType = payload.collections?.[collectionSlug]?.customIDType\n if (customIDType === 'number') {\n transformed.id = Number(transformed.id)\n }\n\n stripFields({\n config: payload.config,\n data: transformed,\n fields: flattenAllFields({ cache: true, fields: collectionConfig.fields }),\n reservedKeys: ['id', 'globalType'],\n })\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)) {\n transformed[fieldName] = null\n }\n }\n }\n\n return transformed\n}\n"],"names":["flattenAllFields","traverseFields","applyDefaults","castFieldValue","stripFields","dataToContentAPI","payload","collectionSlug","data","options","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","cache","reservedKeys","customIDType","id","String","dataFromContentAPI","parentPath","error","fieldPath","logger","warn","err","Error","msg","Number","auth","authFields","fieldName"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { FlattenedField, SanitizedConfig } from 'payload';
2
+ /**
3
+ * Strips fields from data that are not defined in the schema or are invalid.
4
+ *
5
+ * This function was copied from the MongoDB adapter's implementation:
6
+ * @see packages/db-mongodb/src/utilities/transform.ts (stripFields function)
7
+ *
8
+ * @description
9
+ * This utility performs deep sanitization of document data by:
10
+ * 1. **Removing undefined fields**: Strips any field not present in the collection's schema
11
+ * (e.g., sensitive fields like `password`, `apiKey`, or any extra fields not in config)
12
+ * 2. **Removing invalid locales**: Deletes locale keys that don't exist in `config.localization.localeCodes`
13
+ * (e.g., if data contains `{ "es-MX": "..." }` but only `"en"` is configured)
14
+ * 3. **Removing invalid blocks**: Filters out blocks with unknown `blockType` values
15
+ * (prevents data corruption from blocks that don't exist in the schema)
16
+ * 4. **Recursive sanitization**: Processes nested structures (arrays, groups, blocks, localized fields)
17
+ * to ensure deep data integrity
18
+ *
19
+ * @remarks
20
+ * Unlike MongoDB's adapter which uses Mongoose schemas with `strict: true` (rejecting extra fields
21
+ * on write), Content API uses flexible JSONB storage that accepts any fields. Therefore, this
22
+ * function is called on **both read and write operations** to ensure data consistency.
23
+ *
24
+ * The MongoDB adapter also supports an `allowAdditionalKeys` option that allows reading/writing
25
+ * fields not in the schema. For now, Content API does not support this feature - we always
26
+ * sanitize data. If users request this functionality in the future, we can consider adding it.
27
+ *
28
+ * @todo
29
+ * 1. Consider moving this utility to `payload/shared` so both MongoDB and Content API adapters
30
+ * can use the same implementation, reducing code duplication and maintenance burden.
31
+ *
32
+ * 2. Performance optimization: This function iterates through all fields recursively.
33
+ * The `dataToContentAPI` and `dataFromContentAPI` functions in
34
+ * `packages/figma/src/db-content-api/utilities/data.ts` also iterate through fields
35
+ * to transform richText (object ↔ JSON string), custom IDs (number ↔ string), and dates.
36
+ * Consider combining both traversals into a single abstraction to avoid iterating twice.
37
+ *
38
+ * 3. Feature parity: MongoDB adapter has `allowAdditionalKeys` option. We currently always
39
+ * strip additional keys, but could add this as a configurable option if users need it.
40
+ */
41
+ export declare const stripFields: ({ config, data, fields, parentIsLocalized, reservedKeys, }: {
42
+ config: SanitizedConfig;
43
+ data: any;
44
+ fields: FlattenedField[];
45
+ parentIsLocalized?: boolean;
46
+ reservedKeys?: string[];
47
+ }) => void;
48
+ //# sourceMappingURL=stripFields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripFields.d.ts","sourceRoot":"","sources":["../../../../src/db-content-api/utilities/data/stripFields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAI9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,WAAW,+DAMrB;IACD,MAAM,EAAE,eAAe,CAAA;IAEvB,IAAI,EAAE,GAAG,CAAA;IACT,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB,SAmLA,CAAA"}
@@ -0,0 +1,195 @@
1
+ import { fieldShouldBeLocalized } from 'payload/shared';
2
+ /**
3
+ * Strips fields from data that are not defined in the schema or are invalid.
4
+ *
5
+ * This function was copied from the MongoDB adapter's implementation:
6
+ * @see packages/db-mongodb/src/utilities/transform.ts (stripFields function)
7
+ *
8
+ * @description
9
+ * This utility performs deep sanitization of document data by:
10
+ * 1. **Removing undefined fields**: Strips any field not present in the collection's schema
11
+ * (e.g., sensitive fields like `password`, `apiKey`, or any extra fields not in config)
12
+ * 2. **Removing invalid locales**: Deletes locale keys that don't exist in `config.localization.localeCodes`
13
+ * (e.g., if data contains `{ "es-MX": "..." }` but only `"en"` is configured)
14
+ * 3. **Removing invalid blocks**: Filters out blocks with unknown `blockType` values
15
+ * (prevents data corruption from blocks that don't exist in the schema)
16
+ * 4. **Recursive sanitization**: Processes nested structures (arrays, groups, blocks, localized fields)
17
+ * to ensure deep data integrity
18
+ *
19
+ * @remarks
20
+ * Unlike MongoDB's adapter which uses Mongoose schemas with `strict: true` (rejecting extra fields
21
+ * on write), Content API uses flexible JSONB storage that accepts any fields. Therefore, this
22
+ * function is called on **both read and write operations** to ensure data consistency.
23
+ *
24
+ * The MongoDB adapter also supports an `allowAdditionalKeys` option that allows reading/writing
25
+ * fields not in the schema. For now, Content API does not support this feature - we always
26
+ * sanitize data. If users request this functionality in the future, we can consider adding it.
27
+ *
28
+ * @todo
29
+ * 1. Consider moving this utility to `payload/shared` so both MongoDB and Content API adapters
30
+ * can use the same implementation, reducing code duplication and maintenance burden.
31
+ *
32
+ * 2. Performance optimization: This function iterates through all fields recursively.
33
+ * The `dataToContentAPI` and `dataFromContentAPI` functions in
34
+ * `packages/figma/src/db-content-api/utilities/data.ts` also iterate through fields
35
+ * to transform richText (object ↔ JSON string), custom IDs (number ↔ string), and dates.
36
+ * Consider combining both traversals into a single abstraction to avoid iterating twice.
37
+ *
38
+ * 3. Feature parity: MongoDB adapter has `allowAdditionalKeys` option. We currently always
39
+ * strip additional keys, but could add this as a configurable option if users need it.
40
+ */ export const stripFields = ({ config, data, fields, parentIsLocalized = false, reservedKeys = [] })=>{
41
+ for(const k in data){
42
+ if (!fields.some((field)=>field.name === k) && !reservedKeys.includes(k)) {
43
+ delete data[k];
44
+ }
45
+ }
46
+ for (const field of fields){
47
+ reservedKeys = [];
48
+ const fieldData = data[field.name];
49
+ if (!fieldData || typeof fieldData !== 'object') {
50
+ continue;
51
+ }
52
+ const shouldLocalizeField = fieldShouldBeLocalized({
53
+ field,
54
+ parentIsLocalized
55
+ });
56
+ if (field.type === 'blocks') {
57
+ reservedKeys.push('blockType');
58
+ }
59
+ if ('flattenedFields' in field || 'blocks' in field) {
60
+ if (shouldLocalizeField && config.localization) {
61
+ for(const localeKey in fieldData){
62
+ if (!config.localization.localeCodes.some((code)=>code === localeKey)) {
63
+ delete fieldData[localeKey];
64
+ continue;
65
+ }
66
+ const localeData = fieldData[localeKey];
67
+ if (!localeData || typeof localeData !== 'object') {
68
+ continue;
69
+ }
70
+ if (field.type === 'array' || field.type === 'blocks') {
71
+ if (!Array.isArray(localeData)) {
72
+ continue;
73
+ }
74
+ let hasNull = false;
75
+ for(let i = 0; i < localeData.length; i++){
76
+ const data = localeData[i];
77
+ let fields = null;
78
+ if (field.type === 'array') {
79
+ fields = field.flattenedFields;
80
+ } else {
81
+ let maybeBlock = undefined;
82
+ if (field.blockReferences) {
83
+ const maybeBlockReference = field.blockReferences.find((each)=>{
84
+ const slug = typeof each === 'string' ? each : each.slug;
85
+ return slug === data.blockType;
86
+ });
87
+ if (maybeBlockReference) {
88
+ if (typeof maybeBlockReference === 'object') {
89
+ maybeBlock = maybeBlockReference;
90
+ } else {
91
+ maybeBlock = config.blocks?.find((each)=>each.slug === maybeBlockReference);
92
+ }
93
+ }
94
+ }
95
+ if (!maybeBlock) {
96
+ maybeBlock = field.blocks.find((each)=>each.slug === data.blockType);
97
+ }
98
+ if (maybeBlock) {
99
+ fields = maybeBlock.flattenedFields;
100
+ } else {
101
+ localeData[i] = null;
102
+ hasNull = true;
103
+ }
104
+ }
105
+ if (!fields) {
106
+ continue;
107
+ }
108
+ stripFields({
109
+ config,
110
+ data,
111
+ fields,
112
+ parentIsLocalized: parentIsLocalized || field.localized,
113
+ reservedKeys
114
+ });
115
+ }
116
+ if (hasNull) {
117
+ fieldData[localeKey] = localeData.filter(Boolean);
118
+ }
119
+ continue;
120
+ } else {
121
+ stripFields({
122
+ config,
123
+ data: localeData,
124
+ fields: field.flattenedFields,
125
+ parentIsLocalized: parentIsLocalized || field.localized,
126
+ reservedKeys
127
+ });
128
+ }
129
+ }
130
+ continue;
131
+ }
132
+ if (field.type === 'array' || field.type === 'blocks') {
133
+ if (!Array.isArray(fieldData)) {
134
+ continue;
135
+ }
136
+ let hasNull = false;
137
+ for(let i = 0; i < fieldData.length; i++){
138
+ const data = fieldData[i];
139
+ let fields = null;
140
+ if (field.type === 'array') {
141
+ fields = field.flattenedFields;
142
+ } else {
143
+ let maybeBlock = undefined;
144
+ if (field.blockReferences) {
145
+ const maybeBlockReference = field.blockReferences.find((each)=>{
146
+ const slug = typeof each === 'string' ? each : each.slug;
147
+ return slug === data.blockType;
148
+ });
149
+ if (maybeBlockReference) {
150
+ if (typeof maybeBlockReference === 'object') {
151
+ maybeBlock = maybeBlockReference;
152
+ } else {
153
+ maybeBlock = config.blocks?.find((each)=>each.slug === maybeBlockReference);
154
+ }
155
+ }
156
+ }
157
+ if (!maybeBlock) {
158
+ maybeBlock = field.blocks.find((each)=>each.slug === data.blockType);
159
+ }
160
+ if (maybeBlock) {
161
+ fields = maybeBlock.flattenedFields;
162
+ } else {
163
+ fieldData[i] = null;
164
+ hasNull = true;
165
+ }
166
+ }
167
+ if (!fields) {
168
+ continue;
169
+ }
170
+ stripFields({
171
+ config,
172
+ data,
173
+ fields,
174
+ parentIsLocalized: parentIsLocalized || field.localized,
175
+ reservedKeys
176
+ });
177
+ }
178
+ if (hasNull) {
179
+ data[field.name] = fieldData.filter(Boolean);
180
+ }
181
+ continue;
182
+ } else {
183
+ stripFields({
184
+ config,
185
+ data: fieldData,
186
+ fields: field.flattenedFields,
187
+ parentIsLocalized: parentIsLocalized || field.localized,
188
+ reservedKeys
189
+ });
190
+ }
191
+ }
192
+ }
193
+ };
194
+
195
+ //# sourceMappingURL=stripFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/db-content-api/utilities/data/stripFields.ts"],"sourcesContent":["import type { FlattenedBlock, FlattenedField, SanitizedConfig } from 'payload'\n\nimport { fieldShouldBeLocalized } from 'payload/shared'\n\n/**\n * Strips fields from data that are not defined in the schema or are invalid.\n *\n * This function was copied from the MongoDB adapter's implementation:\n * @see packages/db-mongodb/src/utilities/transform.ts (stripFields function)\n *\n * @description\n * This utility performs deep sanitization of document data by:\n * 1. **Removing undefined fields**: Strips any field not present in the collection's schema\n * (e.g., sensitive fields like `password`, `apiKey`, or any extra fields not in config)\n * 2. **Removing invalid locales**: Deletes locale keys that don't exist in `config.localization.localeCodes`\n * (e.g., if data contains `{ \"es-MX\": \"...\" }` but only `\"en\"` is configured)\n * 3. **Removing invalid blocks**: Filters out blocks with unknown `blockType` values\n * (prevents data corruption from blocks that don't exist in the schema)\n * 4. **Recursive sanitization**: Processes nested structures (arrays, groups, blocks, localized fields)\n * to ensure deep data integrity\n *\n * @remarks\n * Unlike MongoDB's adapter which uses Mongoose schemas with `strict: true` (rejecting extra fields\n * on write), Content API uses flexible JSONB storage that accepts any fields. Therefore, this\n * function is called on **both read and write operations** to ensure data consistency.\n *\n * The MongoDB adapter also supports an `allowAdditionalKeys` option that allows reading/writing\n * fields not in the schema. For now, Content API does not support this feature - we always\n * sanitize data. If users request this functionality in the future, we can consider adding it.\n *\n * @todo\n * 1. Consider moving this utility to `payload/shared` so both MongoDB and Content API adapters\n * can use the same implementation, reducing code duplication and maintenance burden.\n *\n * 2. Performance optimization: This function iterates through all fields recursively.\n * The `dataToContentAPI` and `dataFromContentAPI` functions in\n * `packages/figma/src/db-content-api/utilities/data.ts` also iterate through fields\n * to transform richText (object ↔ JSON string), custom IDs (number ↔ string), and dates.\n * Consider combining both traversals into a single abstraction to avoid iterating twice.\n *\n * 3. Feature parity: MongoDB adapter has `allowAdditionalKeys` option. We currently always\n * strip additional keys, but could add this as a configurable option if users need it.\n */\nexport const stripFields = ({\n config,\n data,\n fields,\n parentIsLocalized = false,\n reservedKeys = [],\n}: {\n config: SanitizedConfig\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n data: any\n fields: FlattenedField[]\n parentIsLocalized?: boolean\n reservedKeys?: string[]\n}) => {\n for (const k in data) {\n if (!fields.some((field) => field.name === k) && !reservedKeys.includes(k)) {\n delete data[k]\n }\n }\n\n for (const field of fields) {\n reservedKeys = []\n const fieldData = data[field.name]\n if (!fieldData || typeof fieldData !== 'object') {\n continue\n }\n\n const shouldLocalizeField = fieldShouldBeLocalized({ field, parentIsLocalized })\n\n if (field.type === 'blocks') {\n reservedKeys.push('blockType')\n }\n\n if ('flattenedFields' in field || 'blocks' in field) {\n if (shouldLocalizeField && config.localization) {\n for (const localeKey in fieldData) {\n if (!config.localization.localeCodes.some((code) => code === localeKey)) {\n delete fieldData[localeKey]\n continue\n }\n\n const localeData = fieldData[localeKey]\n\n if (!localeData || typeof localeData !== 'object') {\n continue\n }\n\n if (field.type === 'array' || field.type === 'blocks') {\n if (!Array.isArray(localeData)) {\n continue\n }\n\n let hasNull = false\n for (let i = 0; i < localeData.length; i++) {\n const data = localeData[i]\n let fields: FlattenedField[] | null = null\n\n if (field.type === 'array') {\n fields = field.flattenedFields\n } else {\n let maybeBlock: FlattenedBlock | undefined = undefined\n\n if (field.blockReferences) {\n const maybeBlockReference = field.blockReferences.find((each) => {\n const slug = typeof each === 'string' ? each : each.slug\n return slug === data.blockType\n })\n\n if (maybeBlockReference) {\n if (typeof maybeBlockReference === 'object') {\n maybeBlock = maybeBlockReference\n } else {\n maybeBlock = config.blocks?.find((each) => each.slug === maybeBlockReference)\n }\n }\n }\n\n if (!maybeBlock) {\n maybeBlock = field.blocks.find((each) => each.slug === data.blockType)\n }\n\n if (maybeBlock) {\n fields = maybeBlock.flattenedFields\n } else {\n localeData[i] = null\n hasNull = true\n }\n }\n\n if (!fields) {\n continue\n }\n\n stripFields({\n config,\n data,\n fields,\n parentIsLocalized: parentIsLocalized || field.localized,\n reservedKeys,\n })\n }\n\n if (hasNull) {\n fieldData[localeKey] = localeData.filter(Boolean)\n }\n\n continue\n } else {\n stripFields({\n config,\n data: localeData,\n fields: field.flattenedFields,\n parentIsLocalized: parentIsLocalized || field.localized,\n reservedKeys,\n })\n }\n }\n continue\n }\n\n if (field.type === 'array' || field.type === 'blocks') {\n if (!Array.isArray(fieldData)) {\n continue\n }\n\n let hasNull = false\n\n for (let i = 0; i < fieldData.length; i++) {\n const data = fieldData[i]\n let fields: FlattenedField[] | null = null\n\n if (field.type === 'array') {\n fields = field.flattenedFields\n } else {\n let maybeBlock: FlattenedBlock | undefined = undefined\n\n if (field.blockReferences) {\n const maybeBlockReference = field.blockReferences.find((each) => {\n const slug = typeof each === 'string' ? each : each.slug\n return slug === data.blockType\n })\n\n if (maybeBlockReference) {\n if (typeof maybeBlockReference === 'object') {\n maybeBlock = maybeBlockReference\n } else {\n maybeBlock = config.blocks?.find((each) => each.slug === maybeBlockReference)\n }\n }\n }\n\n if (!maybeBlock) {\n maybeBlock = field.blocks.find((each) => each.slug === data.blockType)\n }\n\n if (maybeBlock) {\n fields = maybeBlock.flattenedFields\n } else {\n fieldData[i] = null\n hasNull = true\n }\n }\n\n if (!fields) {\n continue\n }\n\n stripFields({\n config,\n data,\n fields,\n parentIsLocalized: parentIsLocalized || field.localized,\n reservedKeys,\n })\n }\n\n if (hasNull) {\n data[field.name] = fieldData.filter(Boolean)\n }\n\n continue\n } else {\n stripFields({\n config,\n data: fieldData,\n fields: field.flattenedFields,\n parentIsLocalized: parentIsLocalized || field.localized,\n reservedKeys,\n })\n }\n }\n }\n}\n"],"names":["fieldShouldBeLocalized","stripFields","config","data","fields","parentIsLocalized","reservedKeys","k","some","field","name","includes","fieldData","shouldLocalizeField","type","push","localization","localeKey","localeCodes","code","localeData","Array","isArray","hasNull","i","length","flattenedFields","maybeBlock","undefined","blockReferences","maybeBlockReference","find","each","slug","blockType","blocks","localized","filter","Boolean"],"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