@payloadcms/figma 0.1.0-alpha.0 → 0.1.0-internal.e071575

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 (34) hide show
  1. package/dist/db-content-api/generated/content-api-types.d.ts +644 -3
  2. package/dist/db-content-api/index.d.ts +3 -0
  3. package/dist/db-content-api/index.js +68 -123
  4. package/dist/db-content-api/utilities/clientVersionHeaders.d.ts +7 -0
  5. package/dist/db-content-api/utilities/clientVersionHeaders.js +15 -0
  6. package/dist/db-content-api/utilities/data/atomicOperations.d.ts +16 -0
  7. package/dist/db-content-api/utilities/data/atomicOperations.js +48 -0
  8. package/dist/db-content-api/utilities/data/convertRelationshipIds.d.ts +25 -0
  9. package/dist/db-content-api/utilities/data/convertRelationshipIds.js +109 -0
  10. package/dist/db-content-api/utilities/data/index.js +71 -55
  11. package/dist/db-content-api/utilities/data/isLocaleMap.d.ts +10 -0
  12. package/dist/db-content-api/utilities/data/isLocaleMap.js +13 -0
  13. package/dist/db-content-api/utilities/data/resolveFieldLocalization.d.ts +18 -0
  14. package/dist/db-content-api/utilities/data/resolveFieldLocalization.js +21 -0
  15. package/dist/db-content-api/utilities/data/validateRelationships.js +19 -12
  16. package/dist/db-content-api/utilities/joins.d.ts +6 -8
  17. package/dist/db-content-api/utilities/joins.js +8 -21
  18. package/dist/db-content-api/utilities/schema/buildDocumentSchema.d.ts +21 -4
  19. package/dist/db-content-api/utilities/schema/buildDocumentSchema.js +118 -36
  20. package/dist/db-content-api/utilities/where.d.ts +2 -12
  21. package/dist/db-content-api/utilities/where.js +1 -12
  22. package/dist/plugin/build-config.js +5 -1
  23. package/dist/plugin/sandbox-upload-fetch.d.ts +30 -0
  24. package/dist/plugin/sandbox-upload-fetch.js +59 -0
  25. package/dist/utils/load-payload-config.d.ts +2 -2
  26. package/package.json +6 -5
  27. package/dist/db-content-api/utilities/meta/buildLocalizedPaths.d.ts +0 -10
  28. package/dist/db-content-api/utilities/meta/buildLocalizedPaths.js +0 -71
  29. package/dist/db-content-api/utilities/meta/buildMeta.d.ts +0 -41
  30. package/dist/db-content-api/utilities/meta/buildMeta.js +0 -39
  31. package/dist/db-content-api/utilities/meta/buildPathTypes.d.ts +0 -16
  32. package/dist/db-content-api/utilities/meta/buildPathTypes.js +0 -243
  33. package/dist/db-content-api/utilities/meta/buildUniquePaths.d.ts +0 -12
  34. package/dist/db-content-api/utilities/meta/buildUniquePaths.js +0 -60
@@ -1,32 +1,53 @@
1
1
  // Built-in document fields the Content API manages itself (stored as columns and
2
2
  // injected into every schema). They must never be sent as declared fields, so any
3
- // top-level field with one of these names is dropped.
3
+ // top-level field with one of these names is dropped. This applies only to
4
+ // documents: blocks have no built-in fields, so a block field named `id` or
5
+ // `createdAt` is a real declared field and must be kept.
4
6
  const BUILT_IN_FIELD_NAMES = new Set([
5
7
  'createdAt',
6
8
  'id',
7
9
  'updatedAt'
8
10
  ]);
11
+ const BLOCK_TYPE_FIELD_NAME = 'blockType';
12
+ // The implicit block-type discriminator carried on every block row. It is not a
13
+ // declared block field, so it is injected into each block entity's paths.
14
+ const BLOCK_TYPE_FIELD = {
15
+ type: 'text',
16
+ hasMany: false,
17
+ localized: false,
18
+ unique: false
19
+ };
9
20
  export function buildDocumentSchema(config) {
10
- const collections = {};
11
- const blocksBySlug = new Map();
21
+ const globalBlocksBySlug = new Map();
12
22
  for (const block of config.blocks ?? []){
13
- blocksBySlug.set(block.slug, block);
23
+ globalBlocksBySlug.set(block.slug, block);
14
24
  }
25
+ const blockRegistry = {
26
+ blocksById: {},
27
+ globalBlocksBySlug,
28
+ idByBlock: new Map()
29
+ };
30
+ const collections = {};
15
31
  for (const collection of config.collections ?? []){
16
- collections[collection.slug] = buildCollectionSchema(collection.slug, collection.fields, blocksBySlug);
32
+ collections[collection.slug] = buildCollectionSchema(collection.slug, collection.fields, blockRegistry);
17
33
  }
18
34
  for (const global of config.globals ?? []){
19
35
  const slug = `_global-${global.slug}`;
20
- collections[slug] = buildCollectionSchema(slug, global.fields, blocksBySlug);
36
+ collections[slug] = buildCollectionSchema(slug, global.fields, blockRegistry);
21
37
  }
22
38
  return {
39
+ blocks: blockRegistry.blocksById,
23
40
  collections,
24
41
  version: 1
25
42
  };
26
43
  }
27
- function buildCollectionSchema(name, fields, blocksBySlug) {
44
+ function buildCollectionSchema(name, fields, blockRegistry) {
28
45
  const paths = {};
29
- collectPaths(fields, '', false, paths, blocksBySlug);
46
+ collectPaths(fields, paths, {
47
+ blockRegistry,
48
+ inBlock: false,
49
+ prefix: ''
50
+ });
30
51
  // `_payloadAutosave` marks a version as an autosave draft. The Content API wraps
31
52
  // version schemas under the `version` envelope, so this becomes `version._payloadAutosave`
32
53
  // there. This adds the field to both the document and document version schemas as there
@@ -44,24 +65,27 @@ function buildCollectionSchema(name, fields, blocksBySlug) {
44
65
  paths
45
66
  };
46
67
  }
47
- function collectPaths(fields, prefix, absorbed, paths, blocksBySlug) {
68
+ function collectPaths(fields, paths, context) {
69
+ const { prefix } = context;
48
70
  for (const field of fields){
49
71
  if (field.type === 'row' || field.type === 'collapsible' || field.type === 'group' && (!('name' in field) || !field.name)) {
50
- collectPaths(field.fields, prefix, absorbed, paths, blocksBySlug);
72
+ collectPaths(field.fields, paths, context);
51
73
  continue;
52
74
  }
53
75
  if (field.type === 'tabs') {
54
76
  for (const tab of field.tabs){
55
- const tabOwnLocalized = 'localized' in tab && tab.localized === true;
56
77
  if ('name' in tab && tab.name) {
57
78
  paths[`${prefix}${tab.name}`] = {
58
79
  type: 'group',
59
80
  hasMany: false,
60
- localized: tabOwnLocalized && !absorbed
81
+ localized: 'localized' in tab && tab.localized === true
61
82
  };
62
- collectPaths(tab.fields, `${prefix}${tab.name}.`, absorbed || tabOwnLocalized, paths, blocksBySlug);
83
+ collectPaths(tab.fields, paths, {
84
+ ...context,
85
+ prefix: `${prefix}${tab.name}.`
86
+ });
63
87
  } else {
64
- collectPaths(tab.fields, prefix, absorbed, paths, blocksBySlug);
88
+ collectPaths(tab.fields, paths, context);
65
89
  }
66
90
  }
67
91
  continue;
@@ -69,20 +93,21 @@ function collectPaths(fields, prefix, absorbed, paths, blocksBySlug) {
69
93
  if (field.type === 'ui' || !('name' in field) || !field.name) {
70
94
  continue;
71
95
  }
72
- collectNamedField(field, prefix, absorbed, paths, blocksBySlug);
96
+ collectNamedField(field, paths, context);
73
97
  }
74
98
  }
75
- function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
99
+ function collectNamedField(field, paths, context) {
100
+ const { inBlock, prefix } = context;
76
101
  if (!('name' in field) || !field.name) {
77
102
  return;
78
103
  }
79
- // Built-in fields are supplied by the Content API, never declared here.
80
- if (prefix === '' && BUILT_IN_FIELD_NAMES.has(field.name)) {
104
+ // Built-in fields are supplied by the Content API, never declared here. Blocks
105
+ // have no built-ins, so their fields are never pruned.
106
+ if (prefix === '' && !inBlock && BUILT_IN_FIELD_NAMES.has(field.name)) {
81
107
  return;
82
108
  }
83
109
  const path = `${prefix}${field.name}`;
84
- const localized = 'localized' in field && field.localized === true && !absorbed;
85
- const childAbsorbed = absorbed || 'localized' in field && field.localized === true;
110
+ const localized = 'localized' in field && field.localized === true;
86
111
  const hasMany = 'hasMany' in field ? Boolean(field.hasMany) : false;
87
112
  const unique = 'unique' in field ? Boolean(field.unique) : false;
88
113
  switch(field.type){
@@ -91,28 +116,19 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
91
116
  type: 'array',
92
117
  localized
93
118
  };
94
- collectPaths(field.fields, `${path}.`, childAbsorbed, paths, blocksBySlug);
119
+ collectPaths(field.fields, paths, {
120
+ ...context,
121
+ prefix: `${path}.`
122
+ });
95
123
  return;
96
124
  case 'blocks':
97
125
  {
126
+ const blocks = collectBlocks(field, context);
98
127
  paths[path] = {
99
128
  type: 'blocks',
129
+ blocks,
100
130
  localized
101
131
  };
102
- paths[`${path}.blockType`] = {
103
- type: 'text',
104
- hasMany: false,
105
- localized: false,
106
- unique: false
107
- };
108
- const blockFields = [];
109
- for (const block of field.blocks){
110
- const resolved = typeof block === 'string' ? blocksBySlug.get(block) : block;
111
- if (resolved) {
112
- blockFields.push(...resolved.fields);
113
- }
114
- }
115
- collectPaths(blockFields, `${path}.`, childAbsorbed, paths, blocksBySlug);
116
132
  return;
117
133
  }
118
134
  case 'checkbox':
@@ -127,6 +143,7 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
127
143
  case 'code':
128
144
  case 'email':
129
145
  case 'point':
146
+ case 'slug':
130
147
  case 'textarea':
131
148
  paths[path] = {
132
149
  type: field.type,
@@ -140,7 +157,10 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
140
157
  hasMany,
141
158
  localized
142
159
  };
143
- collectPaths(field.fields, `${path}.`, childAbsorbed, paths, blocksBySlug);
160
+ collectPaths(field.fields, paths, {
161
+ ...context,
162
+ prefix: `${path}.`
163
+ });
144
164
  return;
145
165
  case 'join':
146
166
  paths[path] = {
@@ -180,3 +200,65 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
180
200
  return;
181
201
  }
182
202
  }
203
+ function collectBlocks(field, context) {
204
+ const blocks = {};
205
+ // Each block type is stored as its own entity; the field records only the
206
+ // block-type -> entity-id mapping. This lets same-named fields across block
207
+ // types keep independent schemas instead of collapsing to one shape.
208
+ for (const ref of field.blocks){
209
+ const blockId = resolveBlockId(ref, context);
210
+ if (blockId != null) {
211
+ const slug = typeof ref === 'string' ? ref : ref.slug;
212
+ blocks[slug] = blockId;
213
+ }
214
+ }
215
+ return blocks;
216
+ }
217
+ // Resolves the block a field references (by inline config or slug), applying
218
+ // Payload's block-type resolution: a globally-registered block wins over a
219
+ // field-local inline block of the same slug. Returns the assigned entity id, or
220
+ // `null` when the slug cannot be resolved.
221
+ function resolveBlockId(ref, context) {
222
+ const { blockRegistry } = context;
223
+ const block = resolveBlockReferenceToBlock(ref, context);
224
+ if (block == null) {
225
+ return null;
226
+ }
227
+ const existingBlockId = blockRegistry.idByBlock.get(block);
228
+ if (existingBlockId != null) {
229
+ return existingBlockId;
230
+ }
231
+ const newBlockId = `block_${blockRegistry.idByBlock.size}`;
232
+ blockRegistry.idByBlock.set(block, newBlockId);
233
+ const newBlockPaths = {
234
+ [BLOCK_TYPE_FIELD_NAME]: {
235
+ ...BLOCK_TYPE_FIELD
236
+ }
237
+ };
238
+ // A block entity is its own path namespace, so prefixing resets here. A block
239
+ // entity is shared by every `blocks` field that references it — including a
240
+ // localized one — so it records each field's declared `localized` flag and
241
+ // lets the Content API resolve locale absorption per reference while walking
242
+ // the path. Everything collected below lives in a block, not a document.
243
+ collectPaths(block.fields, newBlockPaths, {
244
+ blockRegistry,
245
+ inBlock: true,
246
+ prefix: ''
247
+ });
248
+ blockRegistry.blocksById[newBlockId] = {
249
+ name: block.slug,
250
+ paths: newBlockPaths
251
+ };
252
+ return newBlockId;
253
+ }
254
+ function resolveBlockReferenceToBlock(ref, context) {
255
+ // Local blocks are stored inline in the field definition.
256
+ const slug = typeof ref === 'string' ? ref : ref.slug;
257
+ const localBlock = typeof ref === 'string' ? undefined : ref;
258
+ // Global blocks are stored as slugs that reference blocks
259
+ // defined at the config level.
260
+ const globalBlock = context.blockRegistry.globalBlocksBySlug.get(slug);
261
+ // Global blocks must take precedence. This mirrors existing Payload
262
+ // behavior.
263
+ return globalBlock ?? localBlock;
264
+ }
@@ -1,19 +1,9 @@
1
1
  import type { Where } from 'payload';
2
2
  import type { components } from '../generated/content-api-types.js';
3
- type WhereClause = components['schemas']['WhereClause'];
3
+ type WhereClauseV1 = components['schemas']['WhereClauseV1'];
4
4
  type ConvertOptions = {
5
5
  /** Internal flag to track if we're inside a logical operator (and/or) */
6
6
  insideLogicalOperator?: boolean;
7
- /** If true, transform 'parent' field to 'documentId' (used for version queries) */
8
- parentToDocumentId?: boolean;
9
- /**
10
- * If true, strip 'version.' prefix from field paths (used for version queries).
11
- *
12
- * Payload queries version content with paths like 'version._status.en', but Content API
13
- * stores version data directly in the 'data' column (not nested under 'version').
14
- * The 'version' wrapper is only added in API responses for client compatibility.
15
- */
16
- stripVersionPrefix?: boolean;
17
7
  };
18
- export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClause;
8
+ export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClauseV1;
19
9
  export {};
@@ -30,18 +30,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
30
30
  });
31
31
  }
32
32
  } else {
33
- // TODO: fix this in content api
34
- // WORKAROUND: Payload uses 'parent' for version queries, but Content API expects 'documentId'
35
- let fieldPath = key.replace(/__/g, '.');
36
- if (options.parentToDocumentId && key === 'parent') {
37
- fieldPath = 'documentId';
38
- }
39
- // Strip 'version.' prefix from field paths for version queries
40
- // Payload uses paths like 'version._status.en' but Content API stores version data
41
- // directly in the 'data' column, not nested under 'version'
42
- if (options.stripVersionPrefix && fieldPath.startsWith('version.')) {
43
- fieldPath = fieldPath.slice('version.'.length);
44
- }
33
+ const fieldPath = key.replace(/__/g, '.');
45
34
  // Convert field conditions: { fieldName: { operator: value } }
46
35
  // to: { path: fieldName, operator, value }
47
36
  // Handle Payload's shorthand: { fieldName: value } → { fieldName: { equals: value } }
@@ -18,6 +18,7 @@ import * as log from '../utils/log.js';
18
18
  import { logMissingCliAuth } from './auth-preflight.js';
19
19
  import { logMissingContentSystemId } from './bootstrap-preflight.js';
20
20
  import { getDevCookieNames } from './dev-cookie-names.js';
21
+ import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
21
22
  /**
22
23
  * Worker-trusted marker injected by Figma's sites-worker after it validates the
23
24
  * X-Figma-Job-Queues-Auth-Bypass secret on a Payload jobs endpoint. The default
@@ -392,7 +393,10 @@ export async function buildFigmaConfig(config) {
392
393
  debug: !!process.env.DEBUG,
393
394
  disabled: false
394
395
  }),
395
- scheduleExtractPlugin()
396
+ scheduleExtractPlugin(),
397
+ // Not folded into createStoragePlugin: that one is skipped when
398
+ // figma.storage is false, which would break remote uploads for those apps.
399
+ createSandboxUploadFetchPlugin()
396
400
  ]
397
401
  };
398
402
  // Inject figma fields into users collection
@@ -0,0 +1,30 @@
1
+ import type { Config } from 'payload';
2
+ /**
3
+ * True inside the Make sandbox. agentplat injects `FIGMA=1` into every sandbox
4
+ * process; it is absent locally and in the deployed Lambda. `FIGMA=true` is
5
+ * accepted because the Make app template's `vite.config.ts` treats both as
6
+ * sandbox markers.
7
+ *
8
+ * KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
9
+ */
10
+ export declare function isMakeSandbox(): boolean;
11
+ /**
12
+ * Lets upload collections fetch remote files inside the Make sandbox, where DNS
13
+ * maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) and
14
+ * `safeFetch` rejects every resolved range that is not `unicast`. Outside the
15
+ * sandbox this is a no-op.
16
+ *
17
+ * `skipSafeFetch` is forced rather than defaulted with `??`, because
18
+ * `@payloadcms/plugin-cloud-storage` recomputes it on every upload collection
19
+ * and a nullish default would never fire. An app's own setting loses here.
20
+ *
21
+ * `externalFileHeaderFilter` is deliberately left unset: it replaces Payload's
22
+ * cookie logic wholesale, including the branch that forwards cookies for
23
+ * relative URLs.
24
+ *
25
+ * Accepted risks, all bounded by the auth gate on the upload routes and the
26
+ * sandbox's network limits: `getExternalFile` re-validates its three redirect
27
+ * hops only against `pasteURL.allowList`; any session can make the server GET
28
+ * an arbitrary URL and read the body; request cookies reach the fetch target.
29
+ */
30
+ export declare function createSandboxUploadFetchPlugin(): (config: Config) => Config;
@@ -0,0 +1,59 @@
1
+ import * as log from '../utils/log.js';
2
+ /**
3
+ * True inside the Make sandbox. agentplat injects `FIGMA=1` into every sandbox
4
+ * process; it is absent locally and in the deployed Lambda. `FIGMA=true` is
5
+ * accepted because the Make app template's `vite.config.ts` treats both as
6
+ * sandbox markers.
7
+ *
8
+ * KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
9
+ */ export function isMakeSandbox() {
10
+ return process.env.FIGMA === '1' || process.env.FIGMA === 'true';
11
+ }
12
+ /**
13
+ * Lets upload collections fetch remote files inside the Make sandbox, where DNS
14
+ * maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) and
15
+ * `safeFetch` rejects every resolved range that is not `unicast`. Outside the
16
+ * sandbox this is a no-op.
17
+ *
18
+ * `skipSafeFetch` is forced rather than defaulted with `??`, because
19
+ * `@payloadcms/plugin-cloud-storage` recomputes it on every upload collection
20
+ * and a nullish default would never fire. An app's own setting loses here.
21
+ *
22
+ * `externalFileHeaderFilter` is deliberately left unset: it replaces Payload's
23
+ * cookie logic wholesale, including the branch that forwards cookies for
24
+ * relative URLs.
25
+ *
26
+ * Accepted risks, all bounded by the auth gate on the upload routes and the
27
+ * sandbox's network limits: `getExternalFile` re-validates its three redirect
28
+ * hops only against `pasteURL.allowList`; any session can make the server GET
29
+ * an arbitrary URL and read the body; request cookies reach the fetch target.
30
+ */ export function createSandboxUploadFetchPlugin() {
31
+ return (config)=>{
32
+ if (!isMakeSandbox()) {
33
+ return config;
34
+ }
35
+ const modifiedSlugs = [];
36
+ const collections = (config.collections ?? []).map((collection)=>{
37
+ if (!collection.upload) {
38
+ return collection;
39
+ }
40
+ modifiedSlugs.push(collection.slug);
41
+ const upload = collection.upload === true ? {} : collection.upload;
42
+ return {
43
+ ...collection,
44
+ upload: {
45
+ ...upload,
46
+ skipSafeFetch: true
47
+ }
48
+ };
49
+ });
50
+ if (modifiedSlugs.length === 0) {
51
+ return config;
52
+ }
53
+ log.debug(`Make sandbox detected: upload collections skip safeFetch (${modifiedSlugs.join(', ')})`);
54
+ return {
55
+ ...config,
56
+ collections
57
+ };
58
+ };
59
+ }
@@ -1,4 +1,4 @@
1
- import type { Config } from 'payload';
1
+ import type { SanitizedConfig } from 'payload';
2
2
  /**
3
3
  * Load a project's Payload config without initializing Payload or connecting to its database.
4
4
  *
@@ -6,4 +6,4 @@ import type { Config } from 'payload';
6
6
  * project's Payload installation so this also works with strict package-manager layouts where
7
7
  * tsx is not a direct dependency of @payloadcms/figma.
8
8
  */
9
- export declare function loadPayloadConfig(projectPath: string): Promise<Config>;
9
+ export declare function loadPayloadConfig(projectPath: string): Promise<SanitizedConfig>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/figma",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-internal.e071575",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "type": "module",
6
6
  "exports": {
@@ -49,7 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@payloadcms/eslint-config": "3.28.0",
51
51
  "@payloadcms/eslint-plugin": "3.28.0",
52
- "@payloadcms/next": "4.0.0-canary.17",
52
+ "@payloadcms/next": "4.0.0-canary.20",
53
53
  "@swc/cli": "0.7.7",
54
54
  "@types/archiver": "7.0.0",
55
55
  "@types/cross-spawn": "6.0.6",
@@ -66,9 +66,9 @@
66
66
  "vitest": "4.0.15"
67
67
  },
68
68
  "peerDependencies": {
69
- "@payloadcms/plugin-cloud-storage": ">=4.0.0-canary.17 <4.0.0-internal",
70
- "@payloadcms/richtext-lexical": ">=4.0.0-canary.17 <4.0.0-internal",
71
- "payload": ">=4.0.0-canary.17 <4.0.0-internal"
69
+ "@payloadcms/plugin-cloud-storage": ">=4.0.0-canary.20 <4.0.0-internal",
70
+ "@payloadcms/richtext-lexical": ">=4.0.0-canary.20 <4.0.0-internal",
71
+ "payload": ">=4.0.0-canary.20 <4.0.0-internal"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@payloadcms/plugin-cloud-storage": {
@@ -97,6 +97,7 @@
97
97
  "generate:types": "tsx scripts/generate-types.ts",
98
98
  "lint": "eslint .",
99
99
  "lint:fix": "eslint . --fix",
100
+ "payload:cli": "tsx scripts/payload-cli.ts",
100
101
  "start": "pnpm build && node ./bin/cli.js",
101
102
  "test": "TEST_UNIT=true vitest run",
102
103
  "test:int": "TEST_INT=true vitest run --config vitest.config.int.ts",
@@ -1,10 +0,0 @@
1
- import type { Payload } from 'payload';
2
- /**
3
- * Extracts all localized field paths from a collection's field configuration.
4
- * Recursively traverses nested structures (groups, arrays, blocks, tabs) to find fields marked as localized.
5
- *
6
- * @param payload - The Payload instance containing collection configurations
7
- * @param collectionSlug - The slug of the collection to extract localized paths from
8
- * @returns Array of dot-notation paths to localized fields (e.g., ['title', 'metadata.description'])
9
- */
10
- export declare function buildLocalizedPaths(payload: Payload, collectionSlug: string): string[];
@@ -1,71 +0,0 @@
1
- import { resolveBlocks } from '../resolveBlocks.js';
2
- /**
3
- * Extracts all localized field paths from a collection's field configuration.
4
- * Recursively traverses nested structures (groups, arrays, blocks, tabs) to find fields marked as localized.
5
- *
6
- * @param payload - The Payload instance containing collection configurations
7
- * @param collectionSlug - The slug of the collection to extract localized paths from
8
- * @returns Array of dot-notation paths to localized fields (e.g., ['title', 'metadata.description'])
9
- */ export function buildLocalizedPaths(payload, collectionSlug) {
10
- const collectionConfig = payload.config.collections.find((c)=>c.slug === collectionSlug);
11
- if (!collectionConfig) {
12
- return [];
13
- }
14
- const localizedPaths = [];
15
- traverseFields(collectionConfig.fields, localizedPaths, '', false, payload.config.blocks);
16
- // Add _status to localized paths if localizeStatus is enabled for this collection
17
- // _status is a base field added by Payload when versions.drafts is enabled,
18
- // and it becomes localized when versions.drafts.localizeStatus is true
19
- // localizeStatus requires both experimental flag AND collection-level config
20
- const experimentalLocalizeStatus = 'experimental' in payload.config && payload.config.experimental && typeof payload.config.experimental === 'object' && 'localizeStatus' in payload.config.experimental && payload.config.experimental.localizeStatus === true;
21
- const drafts = collectionConfig.versions && typeof collectionConfig.versions === 'object' ? collectionConfig.versions.drafts : false;
22
- const collectionLocalizeStatus = drafts && typeof drafts === 'object' && 'localizeStatus' in drafts && drafts.localizeStatus === true;
23
- const hasLocalizeStatus = payload.config.localization && experimentalLocalizeStatus && collectionLocalizeStatus;
24
- if (hasLocalizeStatus && !localizedPaths.includes('_status')) {
25
- localizedPaths.push('_status');
26
- }
27
- return localizedPaths;
28
- }
29
- /** Recursively traverses fields to find and collect localized paths */ function traverseFields(fields, localizedPaths, parentPath = '', parentIsLocalized = false, configBlocks) {
30
- for (const field of fields){
31
- // Unnamed layout fields (row, collapsible, unnamed group, unnamed tabs) don't store data
32
- // at their own level, but their child fields may be localized. Recurse into them with
33
- // the same parent path so localized fields bubble up correctly.
34
- if (!('name' in field)) {
35
- if ('fields' in field && Array.isArray(field.fields)) {
36
- traverseFields(field.fields, localizedPaths, parentPath, parentIsLocalized, configBlocks);
37
- }
38
- if ('tabs' in field && Array.isArray(field.tabs)) {
39
- for (const tab of field.tabs){
40
- if (tab.fields) {
41
- const tabPath = tab.name ? buildPath(parentPath, tab.name) : parentPath;
42
- traverseFields(tab.fields, localizedPaths, tabPath, parentIsLocalized, configBlocks);
43
- }
44
- }
45
- }
46
- continue;
47
- }
48
- const fieldPath = buildPath(parentPath, field.name);
49
- const isLocalized = 'localized' in field && field.localized === true;
50
- const includeNestedPaths = isLocalized || parentIsLocalized;
51
- // Add field to results if localized or parent is localized
52
- if (includeNestedPaths) {
53
- localizedPaths.push(fieldPath);
54
- }
55
- // Handle blocks field - has a special 'blocks' array structure and each block has its own nested fields
56
- if (field.type === 'blocks' && 'blocks' in field) {
57
- for (const block of resolveBlocks(field.blocks, configBlocks)){
58
- traverseFields(block.fields, localizedPaths, fieldPath, includeNestedPaths, configBlocks);
59
- }
60
- continue;
61
- }
62
- // Handle field type that support nested fields
63
- // Recursively handle nested structures (groups, arrays, tab, etc)
64
- if ('fields' in field) {
65
- traverseFields(field.fields, localizedPaths, fieldPath, includeNestedPaths, configBlocks);
66
- }
67
- }
68
- }
69
- /** Builds a dot-notation path from parent path and field name */ function buildPath(parentPath, fieldName) {
70
- return parentPath ? `${parentPath}.${fieldName}` : fieldName;
71
- }
@@ -1,41 +0,0 @@
1
- import type { Payload, Where } from 'payload';
2
- import type { components } from '../../generated/content-api-types.js';
3
- import type { PathTypesRecord } from '../../temp-utilities/types.js';
4
- /**
5
- * Metadata sent to Content API for query processing.
6
- *
7
- * TODO: This interface is defined here temporarily so Content API can test the integration.
8
- * Once Content API implements support for pathTypes with relationship info, this type
9
- * should be generated from openapi.json and ContentAPIMeta can be removed.
10
- */
11
- export interface ContentAPIMeta {
12
- localizedPaths?: string[];
13
- pathTypes?: PathTypesRecord;
14
- uniquePaths?: {
15
- paths: string[];
16
- }[];
17
- }
18
- /**
19
- * The type that Content API currently expects (from generated types).
20
- * This is more restrictive than ContentAPIMeta until Content API is updated.
21
- */
22
- type ContentAPIMetaGenerated = components['schemas']['RequestMeta'];
23
- export interface BuildMetaOptions {
24
- collection: string;
25
- /** Document data — when provided, unique field constraints are included in meta. */
26
- data?: Record<string, unknown>;
27
- locale: string | undefined;
28
- where?: Where;
29
- }
30
- /**
31
- * Builds the `meta` object for Content API requests.
32
- * Combines pathTypes (for array field handling) and localizedPaths (for locale queries).
33
- *
34
- * @param payload - The Payload instance
35
- * @param options - Options including collection slug, locale, and where clause
36
- * @returns Object with meta property ready to spread into request body, or empty object if no meta needed
37
- */
38
- export declare function buildMeta(payload: Payload, options: BuildMetaOptions): {
39
- meta?: ContentAPIMetaGenerated;
40
- };
41
- export {};
@@ -1,39 +0,0 @@
1
- import { buildLocalizedPaths } from './buildLocalizedPaths.js';
2
- import { buildPathTypes } from './buildPathTypes.js';
3
- import { buildUniquePaths } from './buildUniquePaths.js';
4
- /**
5
- * Builds the `meta` object for Content API requests.
6
- * Combines pathTypes (for array field handling) and localizedPaths (for locale queries).
7
- *
8
- * @param payload - The Payload instance
9
- * @param options - Options including collection slug, locale, and where clause
10
- * @returns Object with meta property ready to spread into request body, or empty object if no meta needed
11
- */ export function buildMeta(payload, options) {
12
- const { collection, data, locale, where } = options;
13
- const meta = {};
14
- // Add pathTypes if there are array fields in the where clause
15
- const pathTypes = buildPathTypes(payload, collection, where);
16
- if (Object.keys(pathTypes).length > 0) {
17
- meta.pathTypes = pathTypes;
18
- }
19
- // Add localizedPaths if locale is specified (but not 'all')
20
- if (locale && locale !== 'all') {
21
- const localizedPaths = buildLocalizedPaths(payload, collection);
22
- if (localizedPaths.length > 0) {
23
- meta.localizedPaths = localizedPaths;
24
- }
25
- }
26
- // Add uniquePaths when data is provided (create/upsert operations)
27
- if (data) {
28
- const uniquePaths = buildUniquePaths(payload, collection, data);
29
- if (uniquePaths.length > 0) {
30
- meta.uniquePaths = uniquePaths;
31
- }
32
- }
33
- // Cast to generated type - Content API will need to be updated to handle
34
- // the new pathTypes format with relationship info. Until then, it will
35
- // ignore the extra fields but still receive the data for testing.
36
- return Object.keys(meta).length > 0 ? {
37
- meta: meta
38
- } : {};
39
- }
@@ -1,16 +0,0 @@
1
- import type { Payload, Where } from 'payload';
2
- import type { PathTypesRecord } from '../../temp-utilities/types.js';
3
- /**
4
- * Builds pathTypes metadata for Content API request.
5
- * Returns a record of paths with their types for proper query generation.
6
- *
7
- * Includes:
8
- * - Array fields (type: "array")
9
- * - Relationship fields (type: { type: "relationship", collection, hasMany })
10
- * - Join fields (type: { type: "join", collection, hasMany, on })
11
- *
12
- * Localized array/blocks fields (stored as locale maps) are handled specially:
13
- * - The localized field itself is NOT marked as 'array' (it's a locale map object)
14
- * - The locale-specific path (e.g. "localizedBlocks.en") IS marked as 'array'
15
- */
16
- export declare function buildPathTypes(payload: Payload, collectionSlug: string, where: undefined | Where): PathTypesRecord;