@payloadcms/figma 0.1.0-alpha.1 → 0.1.0-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 (153) hide show
  1. package/dist/api/control-plane.d.ts +1 -1
  2. package/dist/api/control-plane.js +1 -1
  3. package/dist/cli.d.ts +30 -0
  4. package/dist/cli.js +33 -33
  5. package/dist/commands/deploy.d.ts +2 -0
  6. package/dist/commands/deploy.js +22 -12
  7. package/dist/db-content-api/index.d.ts +1 -1
  8. package/dist/db-content-api/index.js +70 -40
  9. package/dist/db-content-api/utilities/schema/getDocumentSchema.d.ts +3 -0
  10. package/dist/db-content-api/utilities/schema/getDocumentSchema.js +24 -0
  11. package/dist/exports/client.d.ts +2 -0
  12. package/dist/exports/client.js +2 -0
  13. package/dist/plugin/build-config.js +39 -4
  14. package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +9 -0
  15. package/dist/plugin/cloud-limits/CloudLimitModal/index.js +78 -0
  16. package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +51 -0
  17. package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +7 -0
  18. package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +169 -0
  19. package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +2 -0
  20. package/dist/plugin/cloud-limits/SlugRegistryContext/index.js +3 -0
  21. package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +9 -0
  22. package/dist/plugin/cloud-limits/SlugTracker/index.js +23 -0
  23. package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.d.ts +2 -0
  24. package/dist/plugin/cloud-limits/icons/ExternalLinkIcon.js +30 -0
  25. package/dist/plugin/cloud-limits/shared.d.ts +10 -0
  26. package/dist/plugin/cloud-limits/shared.js +27 -0
  27. package/dist/plugin/sandbox-upload-fetch.d.ts +32 -0
  28. package/dist/plugin/sandbox-upload-fetch.js +53 -0
  29. package/dist/translations/ar.d.ts +12 -0
  30. package/dist/translations/ar.js +12 -0
  31. package/dist/translations/az.d.ts +12 -0
  32. package/dist/translations/az.js +12 -0
  33. package/dist/translations/bg.d.ts +12 -0
  34. package/dist/translations/bg.js +12 -0
  35. package/dist/translations/bnBd.d.ts +12 -0
  36. package/dist/translations/bnBd.js +12 -0
  37. package/dist/translations/bnIn.d.ts +12 -0
  38. package/dist/translations/bnIn.js +12 -0
  39. package/dist/translations/ca.d.ts +12 -0
  40. package/dist/translations/ca.js +12 -0
  41. package/dist/translations/cs.d.ts +12 -0
  42. package/dist/translations/cs.js +12 -0
  43. package/dist/translations/da.d.ts +12 -0
  44. package/dist/translations/da.js +12 -0
  45. package/dist/translations/de.d.ts +12 -0
  46. package/dist/translations/de.js +12 -0
  47. package/dist/translations/en.d.ts +12 -0
  48. package/dist/translations/en.js +12 -0
  49. package/dist/translations/es.d.ts +12 -0
  50. package/dist/translations/es.js +12 -0
  51. package/dist/translations/et.d.ts +12 -0
  52. package/dist/translations/et.js +12 -0
  53. package/dist/translations/fa.d.ts +12 -0
  54. package/dist/translations/fa.js +12 -0
  55. package/dist/translations/fr.d.ts +12 -0
  56. package/dist/translations/fr.js +12 -0
  57. package/dist/translations/he.d.ts +12 -0
  58. package/dist/translations/he.js +12 -0
  59. package/dist/translations/hr.d.ts +12 -0
  60. package/dist/translations/hr.js +12 -0
  61. package/dist/translations/hu.d.ts +12 -0
  62. package/dist/translations/hu.js +12 -0
  63. package/dist/translations/hy.d.ts +12 -0
  64. package/dist/translations/hy.js +12 -0
  65. package/dist/translations/id.d.ts +12 -0
  66. package/dist/translations/id.js +12 -0
  67. package/dist/translations/index.d.ts +533 -0
  68. package/dist/translations/index.js +90 -0
  69. package/dist/translations/is.d.ts +12 -0
  70. package/dist/translations/is.js +12 -0
  71. package/dist/translations/it.d.ts +12 -0
  72. package/dist/translations/it.js +12 -0
  73. package/dist/translations/ja.d.ts +12 -0
  74. package/dist/translations/ja.js +12 -0
  75. package/dist/translations/ko.d.ts +12 -0
  76. package/dist/translations/ko.js +12 -0
  77. package/dist/translations/lt.d.ts +12 -0
  78. package/dist/translations/lt.js +12 -0
  79. package/dist/translations/lv.d.ts +12 -0
  80. package/dist/translations/lv.js +12 -0
  81. package/dist/translations/my.d.ts +12 -0
  82. package/dist/translations/my.js +12 -0
  83. package/dist/translations/nb.d.ts +12 -0
  84. package/dist/translations/nb.js +12 -0
  85. package/dist/translations/nl.d.ts +12 -0
  86. package/dist/translations/nl.js +12 -0
  87. package/dist/translations/pl.d.ts +12 -0
  88. package/dist/translations/pl.js +12 -0
  89. package/dist/translations/pt.d.ts +12 -0
  90. package/dist/translations/pt.js +12 -0
  91. package/dist/translations/ro.d.ts +12 -0
  92. package/dist/translations/ro.js +12 -0
  93. package/dist/translations/rs.d.ts +12 -0
  94. package/dist/translations/rs.js +12 -0
  95. package/dist/translations/rsLatin.d.ts +12 -0
  96. package/dist/translations/rsLatin.js +12 -0
  97. package/dist/translations/ru.d.ts +12 -0
  98. package/dist/translations/ru.js +12 -0
  99. package/dist/translations/sk.d.ts +12 -0
  100. package/dist/translations/sk.js +12 -0
  101. package/dist/translations/sl.d.ts +12 -0
  102. package/dist/translations/sl.js +12 -0
  103. package/dist/translations/sv.d.ts +12 -0
  104. package/dist/translations/sv.js +12 -0
  105. package/dist/translations/ta.d.ts +12 -0
  106. package/dist/translations/ta.js +12 -0
  107. package/dist/translations/th.d.ts +12 -0
  108. package/dist/translations/th.js +12 -0
  109. package/dist/translations/tr.d.ts +12 -0
  110. package/dist/translations/tr.js +12 -0
  111. package/dist/translations/uk.d.ts +12 -0
  112. package/dist/translations/uk.js +12 -0
  113. package/dist/translations/vi.d.ts +12 -0
  114. package/dist/translations/vi.js +12 -0
  115. package/dist/translations/zh.d.ts +12 -0
  116. package/dist/translations/zh.js +12 -0
  117. package/dist/translations/zhTw.d.ts +12 -0
  118. package/dist/translations/zhTw.js +12 -0
  119. package/dist/types.d.ts +2 -0
  120. package/dist/utils/adapters/nextjs.d.ts +3 -3
  121. package/dist/utils/adapters/nextjs.js +9 -7
  122. package/dist/utils/adapters/nitro.d.ts +3 -3
  123. package/dist/utils/adapters/nitro.js +73 -77
  124. package/dist/utils/adapters/vite.d.ts +1 -1
  125. package/dist/utils/adapters/vite.js +8 -7
  126. package/dist/utils/assert-never.d.ts +1 -0
  127. package/dist/utils/assert-never.js +3 -0
  128. package/dist/utils/asset-collection.d.ts +2 -2
  129. package/dist/utils/asset-collection.js +10 -15
  130. package/dist/utils/build-detection.d.ts +6 -1
  131. package/dist/utils/build-detection.js +68 -89
  132. package/dist/utils/build-lambda-zip.d.ts +5 -6
  133. package/dist/utils/build-lambda-zip.js +31 -48
  134. package/dist/utils/deploy-adapter.d.ts +4 -4
  135. package/dist/utils/deploy-adapter.js +12 -8
  136. package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.d.ts +1 -0
  137. package/dist/utils/deploy-output/inspectNextjsOutput/getNextRelativeAppDir.js +15 -0
  138. package/dist/utils/deploy-output/inspectNextjsOutput.d.ts +12 -0
  139. package/dist/utils/deploy-output/inspectNextjsOutput.js +28 -0
  140. package/dist/utils/deploy-output/inspectNitroOutput.d.ts +9 -0
  141. package/dist/utils/deploy-output/inspectNitroOutput.js +13 -0
  142. package/dist/utils/deploy-output/isFile.d.ts +1 -0
  143. package/dist/utils/deploy-output/isFile.js +4 -0
  144. package/dist/utils/deploy-output/readJson.d.ts +1 -0
  145. package/dist/utils/deploy-output/readJson.js +9 -0
  146. package/dist/utils/deploy-output/resolveNitroOutputLayout.d.ts +8 -0
  147. package/dist/utils/deploy-output/resolveNitroOutputLayout.js +16 -0
  148. package/dist/utils/deploy-output/resolveOutputPath.d.ts +2 -0
  149. package/dist/utils/deploy-output/resolveOutputPath.js +9 -0
  150. package/dist/utils/deploy-output/resolveWithin.d.ts +1 -0
  151. package/dist/utils/deploy-output/resolveWithin.js +12 -0
  152. package/dist/utils/messages.js +2 -0
  153. package/package.json +6 -5
@@ -77,7 +77,7 @@ export interface PerformDeploymentResponse {
77
77
  deploymentId: string;
78
78
  /** Status message */
79
79
  message: string;
80
- /** List of tenant domains */
80
+ /** SiteDomain hostnames returned through the compatibility tenant_domains key */
81
81
  tenantDomains: string[];
82
82
  }
83
83
  export interface PerformDeploymentApiResponse {
@@ -234,7 +234,7 @@ import { parseControlPlaneApiError } from './error-response.js';
234
234
  deploymentId: options.deploymentId,
235
235
  message: 'Deployment initiated successfully',
236
236
  tenantDomains: [
237
- 'example.figmasource.site'
237
+ 'example.figma.site'
238
238
  ]
239
239
  };
240
240
  }
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,33 @@
1
+ export declare const cliArgSpec: {
2
+ readonly '--adapter': StringConstructor;
3
+ readonly '--all': BooleanConstructor;
4
+ readonly '--debug': BooleanConstructor;
5
+ readonly '--dry-run': BooleanConstructor;
6
+ readonly '--env': StringConstructor;
7
+ readonly '--force': BooleanConstructor;
8
+ readonly '--help': BooleanConstructor;
9
+ readonly '--id': StringConstructor;
10
+ readonly '--infra-env': StringConstructor;
11
+ readonly '--json': BooleanConstructor;
12
+ readonly '--name': StringConstructor;
13
+ readonly '--no-skill': BooleanConstructor;
14
+ readonly '--output': StringConstructor;
15
+ readonly '--payload-version': StringConstructor;
16
+ readonly '--skip-auth': BooleanConstructor;
17
+ readonly '--skip-build': BooleanConstructor;
18
+ readonly '--skip-size-check': BooleanConstructor;
19
+ readonly '--template': StringConstructor;
20
+ readonly '--version': BooleanConstructor;
21
+ readonly '--yes': BooleanConstructor;
22
+ readonly '--environment': "--env";
23
+ readonly '-e': "--env";
24
+ readonly '-f': "--force";
25
+ readonly '-h': "--help";
26
+ readonly '-n': "--name";
27
+ readonly '-t': "--template";
28
+ readonly '-v': "--version";
29
+ readonly '-y': "--yes";
30
+ };
1
31
  /**
2
32
  * Entrypoint for bin/cli.js
3
33
  */
package/dist/cli.js CHANGED
@@ -15,6 +15,37 @@ import { upgradeCommand } from './commands/upgrade.js';
15
15
  import { uploadSchemaCommand } from './commands/upload-schema.js';
16
16
  import { setInfraEnvironment } from './constants.js';
17
17
  import { helpMessage } from './utils/messages.js';
18
+ export const cliArgSpec = {
19
+ '--adapter': String,
20
+ '--all': Boolean,
21
+ '--debug': Boolean,
22
+ '--dry-run': Boolean,
23
+ '--env': String,
24
+ '--force': Boolean,
25
+ '--help': Boolean,
26
+ '--id': String,
27
+ '--infra-env': String,
28
+ '--json': Boolean,
29
+ '--name': String,
30
+ '--no-skill': Boolean,
31
+ '--output': String,
32
+ '--payload-version': String,
33
+ '--skip-auth': Boolean,
34
+ '--skip-build': Boolean,
35
+ '--skip-size-check': Boolean,
36
+ '--template': String,
37
+ '--version': Boolean,
38
+ '--yes': Boolean,
39
+ // Aliases
40
+ '--environment': '--env',
41
+ '-e': '--env',
42
+ '-f': '--force',
43
+ '-h': '--help',
44
+ '-n': '--name',
45
+ '-t': '--template',
46
+ '-v': '--version',
47
+ '-y': '--yes'
48
+ };
18
49
  /**
19
50
  * Entrypoint for bin/cli.js
20
51
  */ export async function main() {
@@ -31,39 +62,7 @@ class Main {
31
62
  args;
32
63
  constructor(){
33
64
  // @ts-expect-error bad typings
34
- this.args = arg({
35
- // Hidden flag — intentionally not displayed in help output yet.
36
- // Auto-detection covers most cases; this is an escape hatch for testing.
37
- // May expose in help text once multi-framework support is public.
38
- '--adapter': String,
39
- '--all': Boolean,
40
- '--debug': Boolean,
41
- '--dry-run': Boolean,
42
- '--env': String,
43
- '--force': Boolean,
44
- '--help': Boolean,
45
- '--id': String,
46
- '--infra-env': String,
47
- '--json': Boolean,
48
- '--name': String,
49
- '--no-skill': Boolean,
50
- '--payload-version': String,
51
- '--skip-auth': Boolean,
52
- '--skip-build': Boolean,
53
- '--skip-size-check': Boolean,
54
- '--template': String,
55
- '--version': Boolean,
56
- '--yes': Boolean,
57
- // Aliases
58
- '--environment': '--env',
59
- '-e': '--env',
60
- '-f': '--force',
61
- '-h': '--help',
62
- '-n': '--name',
63
- '-t': '--template',
64
- '-v': '--version',
65
- '-y': '--yes'
66
- }, {
65
+ this.args = arg(cliArgSpec, {
67
66
  permissive: true
68
67
  });
69
68
  // Set debug flag globally
@@ -136,6 +135,7 @@ class Main {
136
135
  adapter: this.args['--adapter'],
137
136
  debug: this.args['--debug'],
138
137
  env: this.args['--env'],
138
+ output: this.args['--output'],
139
139
  skipBuild: this.args['--skip-build'],
140
140
  skipSizeCheck: this.args['--skip-size-check'],
141
141
  yes: this.args['--yes']
@@ -11,6 +11,8 @@ export interface DeployCommandOptions {
11
11
  env?: string;
12
12
  /** CMS Resource ID (overrides FIGMA_PROJECT_ID) */
13
13
  id?: string;
14
+ /** Exact framework build output root */
15
+ output?: string;
14
16
  /** Skip the build step (use existing build) */
15
17
  skipBuild?: boolean;
16
18
  /** Skip the client-side unzipped bundle size pre-flight check */
@@ -1,5 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import spawn from 'cross-spawn';
3
+ import path from 'path';
3
4
  import pc from 'picocolors';
4
5
  import { ControlPlaneError, createDeployment, getBootstrapInfo, performDeployment } from '../api/control-plane.js';
5
6
  import { tryGetCredential } from '../auth/oauth-flow.js';
@@ -64,10 +65,10 @@ import { loginCommand } from './login.js';
64
65
  const environmentName = options.env ?? await getEnvVar(projectPath, 'FIGMA_ENVIRONMENT_NAME') ?? undefined;
65
66
  try {
66
67
  // ===== PRE-FLIGHT CHECKS =====
67
- // Check if in project directory
68
- if (!await isInProjectDirectory(projectPath)) {
69
- p.log.error(pc.red("'deploy' command is not available outside of a Payload project."));
70
- p.note('Create a project or connect an existing one to Figma: npx @payloadcms/figma init', 'Tip');
68
+ // Building requires package.json, but --skip-build can deploy a prebuilt artifact directly.
69
+ if (!options.skipBuild && !await isInProjectDirectory(projectPath)) {
70
+ p.log.error(pc.red('Cannot build because package.json was not found in this directory.'));
71
+ p.note('Run from a package directory, or deploy prebuilt output with --skip-build --output <path>.', 'Tip');
71
72
  process.exit(1);
72
73
  }
73
74
  // Check authentication
@@ -110,13 +111,18 @@ import { loginCommand } from './login.js';
110
111
  process.env.FIGMA_EXTRACT_SCHEDULES = 'true';
111
112
  // ===== BUILD =====
112
113
  let buildInfo;
114
+ const buildOptions = {
115
+ adapter: options.adapter,
116
+ output: options.output
117
+ };
113
118
  if (options.skipBuild) {
114
119
  // User asserts build exists - verify it
115
120
  spinner.start('Checking for build...');
116
- buildInfo = await detectBuild(projectPath);
121
+ buildInfo = await detectBuild(projectPath, buildOptions);
117
122
  if (!buildInfo) {
118
123
  spinner.stop(pc.red('✗ No build found'));
119
- p.log.error('Use deploy without --skip-build to build first.');
124
+ p.log.error(getMissingBuildMessage(projectPath, options));
125
+ p.note('Use deploy without --skip-build to build first.', 'Tip');
120
126
  process.exit(1);
121
127
  }
122
128
  spinner.stop(pc.green('✓ Build found'));
@@ -182,9 +188,9 @@ import { loginCommand } from './login.js';
182
188
  process.exit(1);
183
189
  }
184
190
  // Verify build output
185
- buildInfo = await detectBuild(projectPath);
191
+ buildInfo = await detectBuild(projectPath, buildOptions);
186
192
  if (!buildInfo) {
187
- p.log.error(pc.red('✗ Build completed but build output not found'));
193
+ p.log.error(pc.red(`✗ ${getMissingBuildMessage(projectPath, options)}`));
188
194
  p.note('Ensure your build produces a valid output directory.', 'Check Build Configuration');
189
195
  process.exit(1);
190
196
  }
@@ -192,12 +198,12 @@ import { loginCommand } from './login.js';
192
198
  // ===== DEPLOYMENT PREPARATION =====
193
199
  // Detect framework adapter
194
200
  spinner.start('Detecting framework...');
195
- const adapter = await detectAdapter(projectPath, options.adapter);
201
+ const adapter = await detectAdapter(projectPath, options.adapter, options.output);
196
202
  spinner.stop(pc.green(`✓ Detected ${adapter.name} adapter`));
197
203
  // Collect pages and assets
198
204
  spinner.start('Collecting assets...');
199
- const pages = await adapter.collectPages(projectPath);
200
- const assets = await adapter.collectAssets(projectPath);
205
+ const pages = await adapter.collectPages(projectPath, options.output);
206
+ const assets = await adapter.collectAssets(projectPath, options.output);
201
207
  spinner.stop(pc.green(`✓ ${pages.routes.length} pages, ${assets.routes.length} assets detected`));
202
208
  // Create deployment package (if server adapter)
203
209
  let zipPath = null;
@@ -205,7 +211,7 @@ import { loginCommand } from './login.js';
205
211
  let unzippedBytes = 0;
206
212
  spinner.start('Creating deployment package...');
207
213
  try {
208
- const bundle = await adapter.prepareLambdaBundle(projectPath);
214
+ const bundle = await adapter.prepareLambdaBundle(projectPath, options.output);
209
215
  if (bundle) {
210
216
  zipPath = bundle.zipPath;
211
217
  unzippedBytes = bundle.unzippedBytes;
@@ -342,3 +348,7 @@ import { loginCommand } from './login.js';
342
348
  process.exit(1);
343
349
  }
344
350
  }
351
+ function getMissingBuildMessage(projectPath, options) {
352
+ const location = options.output ? ` at ${path.resolve(projectPath, options.output)}` : '';
353
+ return options.adapter ? `No ${options.adapter} build found${location}.` : `No supported build found${location}.`;
354
+ }
@@ -29,7 +29,7 @@ export type ContentAPIAdapter = {
29
29
  client: ReturnType<typeof createClient<paths>>;
30
30
  clientVersionHeaders: Record<string, string>;
31
31
  contentSystemId: string;
32
- documentSchema: DocumentSchemaWire;
32
+ documentSchema: () => DocumentSchemaWire;
33
33
  environmentName?: string;
34
34
  idType: 'uuid';
35
35
  notifyLocalJobQueue: boolean;
@@ -1,5 +1,5 @@
1
1
  import createClient from 'openapi-fetch';
2
- import { APIError, createDatabaseAdapter } from 'payload';
2
+ import { APIError, createDatabaseAdapter, ValidationError } from 'payload';
3
3
  import { v4 as uuid } from 'uuid';
4
4
  import { FigmaApiError } from '../api/figma-api.js';
5
5
  import { CONTENT_SYSTEM_NOT_FOUND_MESSAGE } from '../constants.js';
@@ -13,7 +13,7 @@ import { validateRelationshipIds } from './utilities/data/validateRelationships.
13
13
  import { convertPayloadJoinsToContentAPI } from './utilities/joins.js';
14
14
  import { addFallbackLocale } from './utilities/locale/index.js';
15
15
  import { normalizeLocaleInWhere } from './utilities/meta/normalizeLocaleInWhere.js';
16
- import { buildDocumentSchema } from './utilities/schema/buildDocumentSchema.js';
16
+ import { getDocumentSchema } from './utilities/schema/getDocumentSchema.js';
17
17
  import { convertPayloadWhereToContentAPI } from './utilities/where.js';
18
18
  /**
19
19
  * Heads-up logged locally whenever a job is queued. Queued jobs are executed by Figma's deployed runner, so
@@ -109,7 +109,7 @@ async function createCollection(collectionId) {
109
109
  }
110
110
  });
111
111
  if (error) {
112
- throw createContentAPIError('createCollection', error, httpResponse.status);
112
+ throw createDocumentContentAPIError(this, 'createCollection', error, httpResponse.status, collectionId);
113
113
  }
114
114
  this.payload.logger.info(`Created collection: ${collectionId}`);
115
115
  } catch (error) {
@@ -131,13 +131,13 @@ async function findMany({ collection, joins, limit, locale: localeArg, page, pag
131
131
  limit,
132
132
  locale,
133
133
  page: page ?? 1,
134
- schema: this.documentSchema,
134
+ schema: this.documentSchema(),
135
135
  sort: addFallbackSort(sort, this.payload, collection),
136
136
  where: convertPayloadWhereToContentAPI(normalizedWhere ?? {})
137
137
  }
138
138
  });
139
139
  if (error) {
140
- throw createContentAPIError('findMany', error, httpResponse.status);
140
+ throw createDocumentContentAPIError(this, 'findMany', error, httpResponse.status, collection);
141
141
  }
142
142
  if (!response) {
143
143
  throw new Error('No response from findMany');
@@ -176,13 +176,13 @@ async function findVersions(args) {
176
176
  limit: args.limit,
177
177
  locale,
178
178
  page: args.page ?? 1,
179
- schema: this.documentSchema,
179
+ schema: this.documentSchema(),
180
180
  sort: sortWithFallback,
181
181
  where: whereClause
182
182
  }
183
183
  });
184
184
  if (error) {
185
- throw createContentAPIError('findVersions', error, httpResponse.status);
185
+ throw createDocumentContentAPIError(this, 'findVersions', error, httpResponse.status, args.collection);
186
186
  }
187
187
  if (!response) {
188
188
  throw new Error('No response from findVersions');
@@ -230,7 +230,7 @@ async function createVersion(args) {
230
230
  body: {
231
231
  collection: args.collectionSlug,
232
232
  contentSystemId: this.contentSystemId,
233
- schema: this.documentSchema,
233
+ schema: this.documentSchema(),
234
234
  versionDoc: {
235
235
  createdAt: args.createdAt,
236
236
  latest: true,
@@ -241,7 +241,7 @@ async function createVersion(args) {
241
241
  }
242
242
  });
243
243
  if (error) {
244
- throw createContentAPIError('createVersion', error, httpResponse.status);
244
+ throw createDocumentContentAPIError(this, 'createVersion', error, httpResponse.status, args.collectionSlug);
245
245
  }
246
246
  if (!response) {
247
247
  throw new Error('No response from createVersion');
@@ -272,7 +272,7 @@ async function updateVersion(args) {
272
272
  contentSystemId: this.contentSystemId,
273
273
  createOnMissing: false,
274
274
  locale,
275
- schema: this.documentSchema,
275
+ schema: this.documentSchema(),
276
276
  versionDoc: {
277
277
  createdAt: versionMeta.createdAt,
278
278
  latest: versionMeta.latest,
@@ -284,7 +284,7 @@ async function updateVersion(args) {
284
284
  }
285
285
  });
286
286
  if (error) {
287
- throw createContentAPIError('updateVersion', error, httpResponse.status);
287
+ throw createDocumentContentAPIError(this, 'updateVersion', error, httpResponse.status, args.collection);
288
288
  }
289
289
  if (!response?.result || !('data' in response.result)) {
290
290
  throw new Error('No document data in updateVersion response');
@@ -301,21 +301,22 @@ async function updateVersion(args) {
301
301
  });
302
302
  }
303
303
  async function deleteVersions(args) {
304
- if (!args.collection) {
304
+ const collection = args.collection;
305
+ if (collection == null) {
305
306
  throw new Error('deleteVersions requires a collection');
306
307
  }
307
308
  const locale = addFallbackLocale(args.locale, this.payload);
308
309
  const { error, response: httpResponse } = await this.client.POST('/api/v1/document_versions:delete', {
309
310
  body: {
310
- collection: args.collection,
311
+ collection,
311
312
  contentSystemId: this.contentSystemId,
312
313
  locale,
313
- schema: this.documentSchema,
314
+ schema: this.documentSchema(),
314
315
  where: convertPayloadWhereToContentAPI(args.where)
315
316
  }
316
317
  });
317
318
  if (error) {
318
- throw createContentAPIError('deleteVersions', error, httpResponse.status);
319
+ throw createDocumentContentAPIError(this, 'deleteVersions', error, httpResponse.status, collection);
319
320
  }
320
321
  }
321
322
  async function findOne(args) {
@@ -339,13 +340,13 @@ async function findDistinct(args) {
339
340
  limit: args.limit,
340
341
  locale,
341
342
  page: args.page ?? 1,
342
- schema: this.documentSchema,
343
+ schema: this.documentSchema(),
343
344
  sort: addFallbackSort(args.sort, this.payload, args.collection),
344
345
  where: convertPayloadWhereToContentAPI(args.where ?? {})
345
346
  }
346
347
  });
347
348
  if (error) {
348
- throw createContentAPIError('findDistinct', error, httpResponse.status);
349
+ throw createDocumentContentAPIError(this, 'findDistinct', error, httpResponse.status, args.collection);
349
350
  }
350
351
  const { data, ...pagination } = response.result;
351
352
  return {
@@ -372,13 +373,13 @@ async function updateMany(args) {
372
373
  environmentName: this.environmentName,
373
374
  limit: args.limit,
374
375
  locale,
375
- schema: this.documentSchema,
376
+ schema: this.documentSchema(),
376
377
  sort: addFallbackSort(args.sort || '-updatedAt', this.payload, args.collection),
377
378
  where: convertPayloadWhereToContentAPI(args.where)
378
379
  }
379
380
  });
380
381
  if (error) {
381
- throw createContentAPIError('updateMany', error, httpResponse.status);
382
+ throw createDocumentContentAPIError(this, 'updateMany', error, httpResponse.status, args.collection);
382
383
  }
383
384
  if (!contentAPIResponse) {
384
385
  throw new Error('No response from updateMany');
@@ -413,12 +414,12 @@ async function updateOne(args) {
413
414
  doc: dataToContentAPI(this.payload, args.collection, args.data),
414
415
  environmentName: this.environmentName,
415
416
  locale,
416
- schema: this.documentSchema,
417
+ schema: this.documentSchema(),
417
418
  where: convertPayloadWhereToContentAPI(where)
418
419
  }
419
420
  });
420
421
  if (error) {
421
- throw createContentAPIError('updateOne', error, httpResponse.status);
422
+ throw createDocumentContentAPIError(this, 'updateOne', error, httpResponse.status, args.collection);
422
423
  }
423
424
  if (!contentAPIResponse) {
424
425
  throw new Error('No response from updateOne');
@@ -445,12 +446,12 @@ async function deleteMany(args) {
445
446
  contentSystemId: this.contentSystemId,
446
447
  environmentName: this.environmentName,
447
448
  locale,
448
- schema: this.documentSchema,
449
+ schema: this.documentSchema(),
449
450
  where: convertPayloadWhereToContentAPI(args.where)
450
451
  }
451
452
  });
452
453
  if (error) {
453
- throw createContentAPIError('deleteMany', error, httpResponse.status);
454
+ throw createDocumentContentAPIError(this, 'deleteMany', error, httpResponse.status, args.collection);
454
455
  }
455
456
  }
456
457
  async function deleteOne(args) {
@@ -461,12 +462,12 @@ async function deleteOne(args) {
461
462
  contentSystemId: this.contentSystemId,
462
463
  environmentName: this.environmentName,
463
464
  locale,
464
- schema: this.documentSchema,
465
+ schema: this.documentSchema(),
465
466
  where: convertPayloadWhereToContentAPI(args.where)
466
467
  }
467
468
  });
468
469
  if (error) {
469
- throw createContentAPIError('deleteOne', error, httpResponse.status);
470
+ throw createDocumentContentAPIError(this, 'deleteOne', error, httpResponse.status, args.collection);
470
471
  }
471
472
  if (!response?.result || !('data' in response.result)) {
472
473
  throw new Error('No document data in deleteOne response');
@@ -516,11 +517,11 @@ async function create(args) {
516
517
  },
517
518
  environmentName: this.environmentName,
518
519
  locale,
519
- schema: this.documentSchema
520
+ schema: this.documentSchema()
520
521
  }
521
522
  });
522
523
  if (error) {
523
- throw createContentAPIError('create', error, httpResponse.status);
524
+ throw createDocumentContentAPIError(this, 'create', error, httpResponse.status, args.collection);
524
525
  }
525
526
  if (!contentAPIResponse) {
526
527
  throw new Error('No response from create');
@@ -568,12 +569,12 @@ async function count(args) {
568
569
  collection: args.collection,
569
570
  contentSystemId: this.contentSystemId,
570
571
  locale,
571
- schema: this.documentSchema,
572
+ schema: this.documentSchema(),
572
573
  where: convertPayloadWhereToContentAPI(args.where)
573
574
  }
574
575
  });
575
576
  if (error) {
576
- throw createContentAPIError('count', error, httpResponse.status);
577
+ throw createDocumentContentAPIError(this, 'count', error, httpResponse.status, args.collection);
577
578
  }
578
579
  if (!response) {
579
580
  throw new Error('No response from count');
@@ -589,12 +590,12 @@ async function countVersions(args) {
589
590
  collection: args.collection,
590
591
  contentSystemId: this.contentSystemId,
591
592
  locale,
592
- schema: this.documentSchema,
593
+ schema: this.documentSchema(),
593
594
  where: convertPayloadWhereToContentAPI(args.where)
594
595
  }
595
596
  });
596
597
  if (error) {
597
- throw createContentAPIError('countVersions', error, httpResponse.status);
598
+ throw createDocumentContentAPIError(this, 'countVersions', error, httpResponse.status, args.collection);
598
599
  }
599
600
  if (!response) {
600
601
  throw new Error('No response from countVersions');
@@ -633,12 +634,12 @@ async function upsert(args) {
633
634
  }),
634
635
  environmentName: this.environmentName,
635
636
  locale,
636
- schema: this.documentSchema,
637
+ schema: this.documentSchema(),
637
638
  where: convertPayloadWhereToContentAPI(args.where)
638
639
  }
639
640
  });
640
641
  if (error) {
641
- throw createContentAPIError('upsert', error, httpResponse.status);
642
+ throw createDocumentContentAPIError(this, 'upsert', error, httpResponse.status, args.collection);
642
643
  }
643
644
  if (!contentAPIResponse) {
644
645
  throw new Error('No response from upsert');
@@ -658,6 +659,7 @@ async function upsert(args) {
658
659
  });
659
660
  }
660
661
  async function updateJobs(args) {
662
+ const collection = 'payload-jobs';
661
663
  const { id, limit, returning, sort, where } = args;
662
664
  if (id == null && where == null) {
663
665
  throw new Error('updateJobs requires either id or a where clause');
@@ -678,12 +680,12 @@ async function updateJobs(args) {
678
680
  equals: String(id)
679
681
  }
680
682
  } : where;
681
- const docData = dataToContentAPI(this.payload, 'payload-jobs', data);
682
- const sortClause = addFallbackSort(sort || '-updatedAt', this.payload, 'payload-jobs');
683
+ const docData = dataToContentAPI(this.payload, collection, data);
684
+ const sortClause = addFallbackSort(sort || '-updatedAt', this.payload, collection);
683
685
  const whereQuery = convertPayloadWhereToContentAPI(whereClause);
684
686
  const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/documents:update', {
685
687
  body: {
686
- collection: 'payload-jobs',
688
+ collection,
687
689
  contentSystemId: this.contentSystemId,
688
690
  createOnMissing: false,
689
691
  doc: docData,
@@ -691,13 +693,13 @@ async function updateJobs(args) {
691
693
  ...limit != null && {
692
694
  limit
693
695
  },
694
- schema: this.documentSchema,
696
+ schema: this.documentSchema(),
695
697
  sort: sortClause,
696
698
  where: whereQuery
697
699
  }
698
700
  });
699
701
  if (error) {
700
- throw createContentAPIError('updateJobs', error, httpResponse.status);
702
+ throw createDocumentContentAPIError(this, 'updateJobs', error, httpResponse.status, collection);
701
703
  }
702
704
  if (!response) {
703
705
  throw new Error('No response from updateJobs');
@@ -709,7 +711,7 @@ async function updateJobs(args) {
709
711
  return [];
710
712
  }
711
713
  return response.result.data.map((doc)=>unwrapDocument({
712
- collectionSlug: 'payload-jobs',
714
+ collectionSlug: collection,
713
715
  doc,
714
716
  payload: this.payload
715
717
  }));
@@ -809,6 +811,13 @@ function countGlobalVersions(args) {
809
811
  where: args.where
810
812
  });
811
813
  }
814
+ function createDocumentContentAPIError(adapter, operation, error, status, collectionSlug) {
815
+ const validationError = maybeCreateUniqueValidationError(error, collectionSlug, adapter);
816
+ if (validationError != null) {
817
+ return validationError;
818
+ }
819
+ return createContentAPIError(operation, error, status);
820
+ }
812
821
  function createContentAPIError(operation, error, status) {
813
822
  return new APIError(`Content API ${operation} error: ${error.message}`, status, {
814
823
  code: error.code,
@@ -816,6 +825,27 @@ function createContentAPIError(operation, error, status) {
816
825
  field: error.field
817
826
  });
818
827
  }
828
+ // A unique-constraint violation from the Content API is Payload's `ValidationError` contract:
829
+ // reconstruct it with the offending field path(s) so callers can map the error back to a field
830
+ // (matching what Payload's other database adapters throw). Falls back to a generic APIError when
831
+ // the collection or offending field is unknown.
832
+ function maybeCreateUniqueValidationError(error, collectionSlug, adapter) {
833
+ if (error.constraint !== 'unique_constraint') {
834
+ return null;
835
+ }
836
+ const paths = (error.field ?? '').split(',').map((path)=>path.trim()).filter((path)=>path.length > 0);
837
+ if (paths.length === 0) {
838
+ adapter.payload.logger.error(`Unique constraint violation but no offending field.`);
839
+ return null;
840
+ }
841
+ return new ValidationError({
842
+ collection: collectionSlug,
843
+ errors: paths.map((path)=>({
844
+ message: error.message,
845
+ path
846
+ }))
847
+ });
848
+ }
819
849
  export const contentAPIAdapter = (opts)=>({
820
850
  name: 'content-api',
821
851
  defaultIDType: 'text',
@@ -855,7 +885,7 @@ export const contentAPIAdapter = (opts)=>({
855
885
  deleteMany: deleteMany,
856
886
  deleteOne: deleteOne,
857
887
  deleteVersions: deleteVersions,
858
- documentSchema: buildDocumentSchema(payload.config),
888
+ documentSchema: ()=>getDocumentSchema(payload.config),
859
889
  environmentName: opts.environmentName,
860
890
  find: find,
861
891
  findDistinct: findDistinct,
@@ -0,0 +1,3 @@
1
+ import type { Config, SanitizedConfig } from 'payload';
2
+ import type { DocumentSchemaWire } from './buildDocumentSchema.js';
3
+ export declare function getDocumentSchema(config: Config | SanitizedConfig): DocumentSchemaWire;
@@ -0,0 +1,24 @@
1
+ import { buildDocumentSchema } from './buildDocumentSchema.js';
2
+ // The schema built by `getDocumentSchema`, keyed by the config it was built from.
3
+ // Weakly held so a config discarded by HMR takes its schema with it.
4
+ let schemaByConfig = new WeakMap();
5
+ // Retrieves a document schema for the given config, building it if necessary.
6
+ // The last output is memoized, so if this config matches the last config we just return
7
+ // the previous output instead of rebuilding it.
8
+ export function getDocumentSchema(config) {
9
+ const existing = schemaByConfig.get(config);
10
+ if (existing != null) {
11
+ return existing;
12
+ }
13
+ const schema = buildDocumentSchema(config);
14
+ // Clear the cache of any previous config, so only the most recent one is held.
15
+ // This ensures that we never accidentally leak memory by holding on to more
16
+ // and more schemas if the configs are never garbage collected.
17
+ schemaByConfig = new WeakMap([
18
+ [
19
+ config,
20
+ schema
21
+ ]
22
+ ]);
23
+ return schema;
24
+ }
@@ -2,3 +2,5 @@ export { FigmaAvatar } from '../oauth/components/FigmaAvatar/index.js';
2
2
  export { FigmaAvatarCell } from '../oauth/components/FigmaAvatarCell/index.js';
3
3
  export { DefaultLoginButton } from '../oauth/components/LoginButton/index.js';
4
4
  export { LogoutButton } from '../oauth/components/LogoutButton/index.js';
5
+ export { FigmaCloudLimitProvider } from '../plugin/cloud-limits/CloudLimitProvider/index.js';
6
+ export { FigmaCloudLimitSlugTracker } from '../plugin/cloud-limits/SlugTracker/index.js';
@@ -2,3 +2,5 @@ export { FigmaAvatar } from '../oauth/components/FigmaAvatar/index.js';
2
2
  export { FigmaAvatarCell } from '../oauth/components/FigmaAvatarCell/index.js';
3
3
  export { DefaultLoginButton } from '../oauth/components/LoginButton/index.js';
4
4
  export { LogoutButton } from '../oauth/components/LogoutButton/index.js';
5
+ export { FigmaCloudLimitProvider } from '../plugin/cloud-limits/CloudLimitProvider/index.js';
6
+ export { FigmaCloudLimitSlugTracker } from '../plugin/cloud-limits/SlugTracker/index.js';