@payloadcms/figma 0.0.1-alpha.58 → 0.0.1-alpha.59

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 (48) hide show
  1. package/dist/api/control-plane.d.ts +8 -7
  2. package/dist/api/control-plane.js +16 -23
  3. package/dist/api/figma-api.d.ts +3 -2
  4. package/dist/api/figma-api.js +6 -9
  5. package/dist/auth/credentials.d.ts +21 -0
  6. package/dist/auth/credentials.js +21 -0
  7. package/dist/auth/oauth-flow.d.ts +13 -4
  8. package/dist/auth/oauth-flow.js +34 -8
  9. package/dist/auth/project-token.js +5 -5
  10. package/dist/cli.js +5 -0
  11. package/dist/commands/deploy.d.ts +3 -0
  12. package/dist/commands/deploy.js +67 -37
  13. package/dist/commands/env.js +4 -4
  14. package/dist/commands/init.js +8 -8
  15. package/dist/commands/login.js +3 -3
  16. package/dist/commands/upgrade.js +11 -11
  17. package/dist/db-content-api/index.js +48 -11
  18. package/dist/db-content-api/temp-utilities/sorting.d.ts +1 -1
  19. package/dist/db-content-api/temp-utilities/sorting.js +4 -1
  20. package/dist/db-content-api/temp-utilities/unwrapDocument.d.ts +7 -2
  21. package/dist/db-content-api/temp-utilities/unwrapDocument.js +11 -5
  22. package/dist/db-content-api/utilities/data/castFieldValue.js +11 -12
  23. package/dist/db-content-api/utilities/data/index.d.ts +1 -1
  24. package/dist/db-content-api/utilities/data/index.js +45 -20
  25. package/dist/db-content-api/utilities/joins.js +10 -4
  26. package/dist/db-content-api/utilities/meta/buildLocalizedPaths.js +14 -6
  27. package/dist/db-content-api/utilities/meta/buildPathTypes.js +19 -2
  28. package/dist/db-content-api/utilities/where.js +15 -45
  29. package/dist/oauth/endpoints/getLoginEndpoint.js +10 -2
  30. package/dist/oauth/utilities/refreshTokens.js +4 -1
  31. package/dist/plugin/build-config.js +4 -4
  32. package/dist/utils/adapters/nextjs.d.ts +9 -0
  33. package/dist/utils/adapters/nextjs.js +59 -0
  34. package/dist/utils/adapters/nitro.d.ts +9 -0
  35. package/dist/utils/adapters/nitro.js +164 -0
  36. package/dist/utils/adapters/vite.d.ts +10 -0
  37. package/dist/utils/adapters/vite.js +32 -0
  38. package/dist/utils/asset-collection.d.ts +24 -0
  39. package/dist/utils/asset-collection.js +53 -0
  40. package/dist/utils/build-detection.d.ts +5 -11
  41. package/dist/utils/build-detection.js +74 -11
  42. package/dist/utils/deploy-adapter.d.ts +38 -0
  43. package/dist/utils/deploy-adapter.js +58 -0
  44. package/dist/utils/fs-utils.d.ts +6 -0
  45. package/dist/utils/fs-utils.js +27 -0
  46. package/dist/utils/s3-upload.d.ts +7 -1
  47. package/dist/utils/s3-upload.js +4 -3
  48. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import spawn from 'cross-spawn';
3
- import path from 'path';
4
3
  import pc from 'picocolors';
5
4
  import { ControlPlaneError, createDeployment, getBootstrapInfo, performDeployment } from '../api/control-plane.js';
6
- import { tryGetAccessToken } from '../auth/oauth-flow.js';
5
+ import { tryGetCredential } from '../auth/oauth-flow.js';
7
6
  import { getTokenStore } from '../auth/token-store.js';
8
7
  import { getInfraEnvironment, getProjectNotFoundMessage } from '../constants.js';
9
- import { collectStaticAssets, createLambdaZip, getFileSize } from '../utils/asset-collection.js';
8
+ import { getFileSize } from '../utils/asset-collection.js';
10
9
  import { detectBuild, getBuildCommand } from '../utils/build-detection.js';
10
+ import { detectAdapter } from '../utils/deploy-adapter.js';
11
11
  import { getEnvVar } from '../utils/env-management.js';
12
12
  import * as log from '../utils/log.js';
13
13
  import { getPackageManager } from '../utils/package-manager.js';
@@ -71,12 +71,12 @@ import { loginCommand } from './login.js';
71
71
  }
72
72
  // Check authentication
73
73
  const tokenStore = getTokenStore();
74
- let accessToken = await tryGetAccessToken(tokenStore);
75
- if (!accessToken) {
74
+ let credential = await tryGetCredential(tokenStore);
75
+ if (!credential) {
76
76
  p.log.message('Please log in to continue');
77
77
  await loginCommand();
78
- accessToken = await tryGetAccessToken(tokenStore);
79
- if (!accessToken) {
78
+ credential = await tryGetCredential(tokenStore);
79
+ if (!credential) {
80
80
  p.log.error(pc.red('✗ Authentication failed'));
81
81
  process.exit(1);
82
82
  }
@@ -85,7 +85,7 @@ import { loginCommand } from './login.js';
85
85
  spinner.start('Resolving deployment target...');
86
86
  let tenantInstanceId;
87
87
  try {
88
- const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
88
+ const bootstrapInfo = await getBootstrapInfo(credential, projectId);
89
89
  const resolvedEnv = resolveEnvironment({
90
90
  environmentName,
91
91
  environments: bootstrapInfo.environments
@@ -183,68 +183,98 @@ import { loginCommand } from './login.js';
183
183
  // Verify build output
184
184
  buildInfo = await detectBuild(projectPath);
185
185
  if (!buildInfo) {
186
- p.log.error(pc.red('✗ Build completed but standalone build not found'));
187
- p.note('Ensure next.config has output: "standalone"', 'Check Lambda Configuration');
186
+ p.log.error(pc.red('✗ Build completed but build output not found'));
187
+ p.note('Ensure your build produces a valid output directory.', 'Check Build Configuration');
188
188
  process.exit(1);
189
189
  }
190
190
  }
191
191
  // ===== DEPLOYMENT PREPARATION =====
192
- // Create Lambda zip
192
+ // Detect framework adapter
193
+ spinner.start('Detecting framework...');
194
+ const adapter = await detectAdapter(projectPath, options.adapter);
195
+ spinner.stop(pc.green(`✓ Detected ${adapter.name} adapter`));
196
+ // Collect pages and assets
197
+ spinner.start('Collecting assets...');
198
+ const pages = await adapter.collectPages(projectPath);
199
+ const assets = await adapter.collectAssets(projectPath);
200
+ const allAssetKeys = [
201
+ ...pages.uploadKeys,
202
+ ...assets.uploadKeys
203
+ ];
204
+ const combinedPathMap = {
205
+ ...pages.pathMap,
206
+ ...assets.pathMap
207
+ };
208
+ spinner.stop(pc.green(`✓ ${pages.routes.length} pages, ${assets.routes.length} assets detected`));
209
+ // Create deployment package (if server adapter)
210
+ let zipPath = null;
211
+ let zipSize = 0;
193
212
  spinner.start('Creating deployment package...');
194
213
  try {
195
- await createLambdaZip(projectPath);
196
- spinner.stop(pc.green('✓ Deployment package created'));
214
+ zipPath = await adapter.prepareLambdaBundle(projectPath);
215
+ if (zipPath) {
216
+ zipSize = await getFileSize(zipPath);
217
+ spinner.stop(pc.green(`✓ Deployment package created (${formatBytes(zipSize)})`));
218
+ } else {
219
+ spinner.stop(pc.green('✓ Static site — no server package needed'));
220
+ }
197
221
  } catch (error) {
198
222
  spinner.stop(pc.red('✗ Failed to create deployment package'));
199
223
  throw error;
200
224
  }
201
- // Collect static assets
202
- spinner.start('Collecting static assets...');
203
- const staticAssets = await collectStaticAssets(projectPath);
204
- const zipPath = path.join(projectPath, 'lambda.zip');
205
- const zipSize = await getFileSize(zipPath);
206
- spinner.stop(pc.green(`✓ ${staticAssets.length} static assets detected`));
207
- log.debug(`Lambda zip size: ${formatBytes(zipSize)}`);
208
- log.debug(`Static assets: ${staticAssets.length} files`);
225
+ log.debug(`Adapter: ${adapter.name}`);
226
+ if (zipPath) {
227
+ log.debug(`Lambda zip size: ${formatBytes(zipSize)}`);
228
+ }
229
+ log.debug(`Pages: ${pages.routes.length} routes`);
230
+ log.debug(`Assets: ${assets.routes.length} files`);
209
231
  // ===== DEPLOYMENT CONFIRMATION =====
210
232
  if (!options.yes) {
211
233
  p.note([
234
+ `${pc.cyan('Adapter:')} ${adapter.name}`,
212
235
  `${pc.cyan('Build ID:')} ${buildInfo.buildId}`,
213
- `${pc.cyan('Date:')} ${formatDate(buildInfo.timestamp)}`,
214
- `${pc.cyan('Code size:')} ${formatBytes(zipSize)}`,
215
- `${pc.cyan('Static assets:')} ${staticAssets.length} files`
236
+ zipPath ? `${pc.cyan('Code size:')} ${formatBytes(zipSize)}` : `${pc.cyan('Code:')} No server`,
237
+ `${pc.cyan('Pages:')} ${pages.routes.length} routes`,
238
+ `${pc.cyan('Assets:')} ${assets.routes.length} files`
216
239
  ].join('\n'), 'Details');
217
240
  }
218
241
  // ===== DEPLOYMENT EXECUTION =====
219
242
  // Step 1: Create deployment
220
243
  spinner.start('Creating deployment...');
221
- const createResponse = await createDeployment(accessToken, tenantInstanceId, {
222
- staticAssets
244
+ const createResponse = await createDeployment(credential, tenantInstanceId, {
245
+ staticAssets: allAssetKeys
223
246
  });
224
247
  const deploymentId = createResponse.deploymentId;
225
248
  const codeUploadUrl = createResponse.codeUploadUrl;
226
249
  const staticAssetUploadUrls = createResponse.staticAssetUploadUrls;
227
250
  log.debug(`Deployment ID: ${deploymentId}`);
228
- // Step 2: Upload code
229
- spinner.message(`Uploading code (${formatBytes(zipSize)})...`);
230
- await uploadLambdaZip(zipPath, codeUploadUrl);
231
- spinner.stop(pc.green(`✓ Code uploaded (${formatBytes(zipSize)})`));
251
+ // Step 2: Upload code (skip for static sites)
252
+ if (zipPath) {
253
+ spinner.message(`Uploading code (${formatBytes(zipSize)})...`);
254
+ await uploadLambdaZip(zipPath, codeUploadUrl);
255
+ spinner.stop(pc.green(`✓ Code uploaded (${formatBytes(zipSize)})`));
256
+ }
232
257
  // Step 3: Upload static assets
233
- if (staticAssets.length > 0) {
234
- spinner.start('Uploading static assets...');
235
- const uploadResults = await uploadStaticAssets(projectPath, staticAssetUploadUrls, (uploaded, total)=>{
236
- spinner.message(`Uploading static assets (${uploaded}/${total})...`);
258
+ if (allAssetKeys.length > 0) {
259
+ spinner.start('Uploading assets...');
260
+ const uploadResults = await uploadStaticAssets({
261
+ assetUrls: staticAssetUploadUrls,
262
+ onProgress: (uploaded, total)=>{
263
+ spinner.message(`Uploading assets (${uploaded}/${total})...`);
264
+ },
265
+ pathMap: combinedPathMap,
266
+ projectPath
237
267
  });
238
268
  if (uploadResults.assetsFailed > 0) {
239
- spinner.stop(pc.yellow(`⚠ ${uploadResults.assetsUploaded}/${staticAssets.length} assets uploaded (${uploadResults.assetsFailed} failed)`));
269
+ spinner.stop(pc.yellow(`⚠ ${uploadResults.assetsUploaded}/${allAssetKeys.length} assets uploaded (${uploadResults.assetsFailed} failed)`));
240
270
  p.log.warning(`${uploadResults.assetsFailed} assets failed to upload. Deployment may be incomplete.`);
241
271
  } else {
242
- spinner.stop(pc.green(`✓ Static assets uploaded (${uploadResults.assetsUploaded} files, ${formatBytes(uploadResults.totalBytesUploaded)})`));
272
+ spinner.stop(pc.green(`✓ Assets uploaded (${uploadResults.assetsUploaded} files, ${formatBytes(uploadResults.totalBytesUploaded)})`));
243
273
  }
244
274
  }
245
275
  // Step 4: Perform deployment
246
276
  spinner.start('Performing deployment...');
247
- const result = await performDeployment(accessToken, tenantInstanceId, {
277
+ const result = await performDeployment(credential, tenantInstanceId, {
248
278
  deploymentId
249
279
  });
250
280
  const tenantDomains = result.tenantDomains ?? [];
@@ -1,7 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { getBootstrapInfo } from '../api/control-plane.js';
4
- import { getValidAccessToken } from '../auth/oauth-flow.js';
4
+ import { getValidCredential } from '../auth/oauth-flow.js';
5
5
  import { getTokenStore } from '../auth/token-store.js';
6
6
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
7
7
  import { addOrUpdateEnvVar, getEnvVar } from '../utils/env-management.js';
@@ -22,8 +22,8 @@ import * as log from '../utils/log.js';
22
22
  const currentEnv = await getEnvVar(cwd, 'FIGMA_ENVIRONMENT_NAME');
23
23
  // Authenticate
24
24
  const tokenStore = getTokenStore();
25
- const accessToken = await getValidAccessToken(tokenStore);
26
- if (!accessToken) {
25
+ const credential = await getValidCredential(tokenStore);
26
+ if (!credential) {
27
27
  p.log.error(pc.red('Not authenticated. Run `@payloadcms/figma login` first.'));
28
28
  process.exit(1);
29
29
  }
@@ -32,7 +32,7 @@ import * as log from '../utils/log.js';
32
32
  spinner.start('Fetching environments...');
33
33
  let environments;
34
34
  try {
35
- const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
35
+ const bootstrapInfo = await getBootstrapInfo(credential, projectId);
36
36
  environments = bootstrapInfo.environments;
37
37
  cacheAllEnvironments(tokenStore, projectId, bootstrapInfo);
38
38
  } catch (error) {
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import pc from 'picocolors';
5
5
  import { ControlPlaneError, getBootstrapInfo } from '../api/control-plane.js';
6
6
  import { FigmaApiError } from '../api/figma-api.js';
7
- import { tryGetAccessToken } from '../auth/oauth-flow.js';
7
+ import { tryGetCredential } from '../auth/oauth-flow.js';
8
8
  import { getValidProjectToken, ProjectTokenError } from '../auth/project-token.js';
9
9
  import { getTokenStore } from '../auth/token-store.js';
10
10
  import { getInfraEnvironment, getProjectNotFoundMessage } from '../constants.js';
@@ -81,14 +81,14 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
81
81
  if (!tokenStore.hasValidTokens()) {
82
82
  // Need to refresh or authenticate - show spinner for API call
83
83
  s.start('Checking authentication...');
84
- const token = await tryGetAccessToken(tokenStore);
85
- if (!token) {
84
+ const credential = await tryGetCredential(tokenStore);
85
+ if (!credential) {
86
86
  s.stop('Authentication required to continue');
87
87
  await loginCommand({
88
88
  showNextSteps: false
89
89
  });
90
- const newToken = await tryGetAccessToken(tokenStore);
91
- if (!newToken) {
90
+ const newCredential = await tryGetCredential(tokenStore);
91
+ if (!newCredential) {
92
92
  p.log.error('Authentication failed');
93
93
  process.exit(1);
94
94
  }
@@ -113,10 +113,10 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
113
113
  let oauthClientId = null;
114
114
  let oauthClientSecret = null;
115
115
  if (!options.skipAuth) {
116
- const accessToken = await tryGetAccessToken(tokenStore);
117
- if (accessToken) {
116
+ const credential = await tryGetCredential(tokenStore);
117
+ if (credential) {
118
118
  try {
119
- bootstrapInfo = await getBootstrapInfo(accessToken, cmsResourceId);
119
+ bootstrapInfo = await getBootstrapInfo(credential, cmsResourceId);
120
120
  } catch (error) {
121
121
  log.debug(`Control Plane Error: ${error.statusCode} - ${error.message}`);
122
122
  if (error instanceof ControlPlaneError && (error.statusCode === 404 || error.statusCode === 410)) {
@@ -1,7 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { CallbackError } from '../auth/callback-server.js';
4
- import { executeOAuthFlow, getValidAccessToken, OAuthFlowError } from '../auth/oauth-flow.js';
4
+ import { executeOAuthFlow, getValidCredential, OAuthFlowError } from '../auth/oauth-flow.js';
5
5
  import { getTokenStore } from '../auth/token-store.js';
6
6
  import { getOAuthConfig } from '../config/oauth.js';
7
7
  import { getInfraEnvironment } from '../constants.js';
@@ -20,8 +20,8 @@ import * as log from '../utils/log.js';
20
20
  const tokenStore = getTokenStore(env);
21
21
  // Check for existing valid tokens
22
22
  try {
23
- const existingToken = await getValidAccessToken(tokenStore);
24
- if (existingToken) {
23
+ const existingCredential = await getValidCredential(tokenStore);
24
+ if (existingCredential) {
25
25
  const tokens = tokenStore.getTokens();
26
26
  p.log.warn(pc.yellow(`Already logged in${formatEnvSuffix(env)}`));
27
27
  if (tokens?.userId) {
@@ -5,7 +5,7 @@ import pc from 'picocolors';
5
5
  import { Project } from 'ts-morph';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { getBootstrapInfo, getCmsResourceId } from '../api/control-plane.js';
8
- import { getValidAccessToken } from '../auth/oauth-flow.js';
8
+ import { getValidCredential } from '../auth/oauth-flow.js';
9
9
  import { getTokenStore } from '../auth/token-store.js';
10
10
  import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
11
11
  import { addOrUpdateEnvVar, getEnvVar, removeEnvVar } from '../utils/env-management.js';
@@ -110,16 +110,16 @@ async function migrateProjectId({ changes, cwd, isDryRun, warnings = [] }) {
110
110
  return;
111
111
  }
112
112
  const store = getTokenStore();
113
- const accessToken = await getValidAccessToken(store);
114
- if (!accessToken) {
113
+ const credential = await getValidCredential(store);
114
+ if (!credential) {
115
115
  warnings.push('Not authenticated — could not resolve FIGMA_PROJECT_ID. Run `npx @payloadcms/figma login` first.');
116
116
  return;
117
117
  }
118
- const cmsResourceId = await getCmsResourceId(accessToken, oldContentSystemId);
118
+ const cmsResourceId = await getCmsResourceId(credential, oldContentSystemId);
119
119
  await addOrUpdateEnvVar(cwd, 'FIGMA_PROJECT_ID', cmsResourceId);
120
120
  // Resolve actual environment name from bootstrap API
121
121
  try {
122
- const bootstrapInfo = await getBootstrapInfo(accessToken, cmsResourceId);
122
+ const bootstrapInfo = await getBootstrapInfo(credential, cmsResourceId);
123
123
  cacheAllEnvironments(store, cmsResourceId, bootstrapInfo);
124
124
  const envName = bootstrapInfo.environments[0]?.name ?? 'production';
125
125
  await addOrUpdateEnvVar(cwd, 'FIGMA_ENVIRONMENT_NAME', envName);
@@ -144,11 +144,11 @@ async function ensureBootstrapCached(params) {
144
144
  if (store.getBootstrapData(projectId, environmentName)) {
145
145
  return;
146
146
  }
147
- const accessToken = await getValidAccessToken(store);
148
- if (!accessToken) {
147
+ const credential = await getValidCredential(store);
148
+ if (!credential) {
149
149
  return;
150
150
  }
151
- const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
151
+ const bootstrapInfo = await getBootstrapInfo(credential, projectId);
152
152
  cacheAllEnvironments(store, projectId, bootstrapInfo);
153
153
  // Fix FIGMA_ENVIRONMENT_NAME if it doesn't match any actual environment
154
154
  const envNames = bootstrapInfo.environments.map((e)=>e.name);
@@ -190,10 +190,10 @@ async function migrateRemoveDeprecatedEnvVars({ changes, cwd, isDryRun, warnings
190
190
  const bootstrapData = store.getBootstrapData(projectId, environmentName);
191
191
  if (!bootstrapData) {
192
192
  // Try fetching from API
193
- const accessToken = await getValidAccessToken(store);
194
- if (accessToken) {
193
+ const credential = await getValidCredential(store);
194
+ if (credential) {
195
195
  try {
196
- const bootstrapInfo = await getBootstrapInfo(accessToken, projectId);
196
+ const bootstrapInfo = await getBootstrapInfo(credential, projectId);
197
197
  cacheAllEnvironments(store, projectId, bootstrapInfo);
198
198
  } catch {
199
199
  warnings.push('Could not verify bootstrap data — keeping deprecated env vars. Run `npx @payloadcms/figma init` to complete migration.');
@@ -116,7 +116,7 @@ async function findMany({ collection, joins, limit, locale: localeArg, page, pag
116
116
  if (!response) {
117
117
  throw new Error('No response from findMany');
118
118
  }
119
- return unwrapFindResponse(this.payload, response.result, collection);
119
+ return unwrapFindResponse(this.payload, response.result, collection, localeArg);
120
120
  }
121
121
  async function find(args) {
122
122
  const collectionConfig = this.payload.config.collections.find((c)=>c.slug === args.collection);
@@ -124,8 +124,11 @@ async function find(args) {
124
124
  collection: args.collection,
125
125
  joins: args.joins,
126
126
  // TODO: FindArgs.limit should be 'number' not 'number | undefined'
127
- // is sanitized by Payload Core before calling the adapter (defaults to 10 or 0 based on pagination)
128
- limit: args.limit,
127
+ // Payload Core sanitizes limit before calling the adapter when going through findOperation
128
+ // (defaults to 10 or 0 based on pagination), but some callers (e.g. deleteOperation) call
129
+ // db.find() directly without a limit. Default to 0 (unlimited) to avoid the Content API
130
+ // falling back to its own default of 10.
131
+ limit: args.limit ?? 0,
129
132
  locale: args.locale,
130
133
  page: args.page ?? 1,
131
134
  pagination: args.pagination,
@@ -165,7 +168,7 @@ async function findVersions(args) {
165
168
  if (!response) {
166
169
  throw new Error('No response from findVersions');
167
170
  }
168
- return unwrapFindResponse(this.payload, response.result, args.collection);
171
+ return unwrapFindResponse(this.payload, response.result, args.collection, args.locale);
169
172
  }
170
173
  async function queryDrafts(args) {
171
174
  const versionsResult = await this.findVersions({
@@ -216,7 +219,11 @@ async function createVersion(args) {
216
219
  if (!response) {
217
220
  throw new Error('No response from createVersion');
218
221
  }
219
- return unwrapDocument(this.payload, response.result, args.collectionSlug);
222
+ return unwrapDocument({
223
+ collectionSlug: args.collectionSlug,
224
+ doc: response.result,
225
+ payload: this.payload
226
+ });
220
227
  }
221
228
  async function updateVersion(args) {
222
229
  const where = args.where ?? {
@@ -262,7 +269,12 @@ async function updateVersion(args) {
262
269
  if (!doc) {
263
270
  return null;
264
271
  }
265
- return unwrapDocument(this.payload, doc, args.collection);
272
+ return unwrapDocument({
273
+ collectionSlug: args.collection,
274
+ doc,
275
+ locale: args.locale,
276
+ payload: this.payload
277
+ });
266
278
  }
267
279
  async function deleteVersions(args) {
268
280
  if (!args.collection) {
@@ -357,7 +369,12 @@ async function updateMany(args) {
357
369
  throw new Error('No response from updateMany');
358
370
  }
359
371
  if (response.result && 'data' in response.result) {
360
- return response.result.data.map((doc)=>unwrapDocument(this.payload, doc, args.collection));
372
+ return response.result.data.map((doc)=>unwrapDocument({
373
+ collectionSlug: args.collection,
374
+ doc,
375
+ locale: args.locale,
376
+ payload: this.payload
377
+ }));
361
378
  }
362
379
  return null;
363
380
  }
@@ -397,7 +414,12 @@ async function updateOne(args) {
397
414
  if (!doc) {
398
415
  return null;
399
416
  }
400
- return unwrapDocument(this.payload, doc, args.collection);
417
+ return unwrapDocument({
418
+ collectionSlug: args.collection,
419
+ doc,
420
+ locale: args.locale,
421
+ payload: this.payload
422
+ });
401
423
  }
402
424
  async function deleteMany(args) {
403
425
  const locale = addFallbackLocale(args.req?.locale, this.payload);
@@ -444,7 +466,12 @@ async function deleteOne(args) {
444
466
  if (!doc) {
445
467
  return null;
446
468
  }
447
- return unwrapDocument(this.payload, doc, args.collection);
469
+ return unwrapDocument({
470
+ collectionSlug: args.collection,
471
+ doc,
472
+ locale: args.req?.locale === null ? undefined : args.req?.locale,
473
+ payload: this.payload
474
+ });
448
475
  }
449
476
  async function create(args) {
450
477
  const isGlobalCollection = args.collection.startsWith('_global-');
@@ -487,7 +514,12 @@ async function create(args) {
487
514
  if (!response) {
488
515
  throw new Error('No response from create');
489
516
  }
490
- return unwrapDocument(this.payload, response.result, args.collection);
517
+ return unwrapDocument({
518
+ collectionSlug: args.collection,
519
+ doc: response.result,
520
+ locale: args.locale,
521
+ payload: this.payload
522
+ });
491
523
  }
492
524
  async function clearDatabase() {
493
525
  this.payload.logger.info(`---- CLEARING CONTENT API DATABASE ----`);
@@ -604,7 +636,12 @@ async function upsert(args) {
604
636
  if (!doc) {
605
637
  throw new Error('No document data in upsert response');
606
638
  }
607
- return unwrapDocument(this.payload, doc, args.collection);
639
+ return unwrapDocument({
640
+ collectionSlug: args.collection,
641
+ doc,
642
+ locale: args.locale,
643
+ payload: this.payload
644
+ });
608
645
  }
609
646
  function createGlobal(args) {
610
647
  return this.create({
@@ -1,3 +1,3 @@
1
1
  import type { Payload } from 'payload';
2
- export declare function addFallbackSort(sort: string | string[] | undefined, payload: Payload, collectionSlug: string): string | string[] | undefined;
2
+ export declare function addFallbackSort(sort: string | string[] | undefined, payload: Payload, collectionSlug: string, defaultSort?: string | string[]): string | string[] | undefined;
3
3
  //# sourceMappingURL=sorting.d.ts.map
@@ -1,9 +1,12 @@
1
1
  // Add fallback sort to ensure consistent ordering when sorting by non-unique fields
2
2
  // Matches MongoDB adapter behavior
3
3
  // TO-DECIDE: Should this live here or in content-api?
4
- export function addFallbackSort(sort, payload, collectionSlug) {
4
+ export function addFallbackSort(sort, payload, collectionSlug, defaultSort) {
5
5
  const collectionConfig = payload.config.collections.find((c)=>c.slug === collectionSlug);
6
6
  if (!sort || !collectionConfig) {
7
+ if (defaultSort) {
8
+ return addFallbackSort(defaultSort, payload, collectionSlug);
9
+ }
7
10
  return sort;
8
11
  }
9
12
  const sortArray = Array.isArray(sort) ? sort : [
@@ -6,8 +6,13 @@ type DocumentResponse = {
6
6
  type DocumentVersionResponse = {
7
7
  id: string;
8
8
  } & components['schemas']['DocumentVersion'];
9
- export declare function unwrapDocument(payload: Payload, doc: DocumentResponse | DocumentVersionResponse, collectionSlug?: string): Record<string, unknown>;
10
- export declare const unwrapFindResponse: (payload: Payload, response: components["schemas"]["FindDocumentsResponse"]["result"] | components["schemas"]["FindDocumentVersionsResponse"]["result"], collection: string) => {
9
+ export declare function unwrapDocument(args: {
10
+ collectionSlug?: string;
11
+ doc: DocumentResponse | DocumentVersionResponse;
12
+ locale?: string;
13
+ payload: Payload;
14
+ }): Record<string, unknown>;
15
+ export declare const unwrapFindResponse: (payload: Payload, response: components["schemas"]["FindDocumentsResponse"]["result"] | components["schemas"]["FindDocumentVersionsResponse"]["result"], collection: string, locale?: string) => {
11
16
  totalDocs: number;
12
17
  limit: number;
13
18
  page: number;
@@ -6,7 +6,8 @@ import { dataFromContentAPI } from '../utilities/data/index.js';
6
6
  // Payload expects:
7
7
  // - For documents: { id, ...fields, createdAt, updatedAt }
8
8
  // - For versions: { id, parent, latest, version: { ...fields }, createdAt, updatedAt }
9
- export function unwrapDocument(payload, doc, collectionSlug) {
9
+ export function unwrapDocument(args) {
10
+ const { collectionSlug, doc, locale, payload } = args;
10
11
  if (!doc) {
11
12
  return doc;
12
13
  }
@@ -21,7 +22,7 @@ export function unwrapDocument(payload, doc, collectionSlug) {
21
22
  // Transform data from Content API format to Payload format
22
23
  if (collectionSlug) {
23
24
  // Only transform the nested 'version' field
24
- baseDoc.version = dataFromContentAPI(payload, collectionSlug, baseDoc.version);
25
+ baseDoc.version = dataFromContentAPI(payload, collectionSlug, baseDoc.version, locale);
25
26
  // Also convert version ID if collection uses numeric custom ID
26
27
  const customIDType = payload.collections?.[collectionSlug]?.customIDType;
27
28
  if (customIDType === 'number') {
@@ -38,14 +39,19 @@ export function unwrapDocument(payload, doc, collectionSlug) {
38
39
  };
39
40
  // Transform data from Content API format to Payload format
40
41
  if (collectionSlug) {
41
- return dataFromContentAPI(payload, collectionSlug, baseDoc);
42
+ return dataFromContentAPI(payload, collectionSlug, baseDoc, locale);
42
43
  }
43
44
  return baseDoc;
44
45
  }
45
- export const unwrapFindResponse = (payload, response, collection)=>{
46
+ export const unwrapFindResponse = (payload, response, collection, locale)=>{
46
47
  const { data, ...paginationFields } = response;
47
48
  return {
48
- docs: data.map((doc)=>unwrapDocument(payload, doc, collection)),
49
+ docs: data.map((doc)=>unwrapDocument({
50
+ collectionSlug: collection,
51
+ doc: doc,
52
+ locale,
53
+ payload
54
+ })),
49
55
  ...paginationFields
50
56
  };
51
57
  };
@@ -10,7 +10,6 @@
10
10
  return value;
11
11
  }
12
12
  if (field.type === 'number') {
13
- // Handle hasMany: true - array of numbers
14
13
  if ('hasMany' in field && field.hasMany && Array.isArray(value)) {
15
14
  return value.map((item)=>{
16
15
  if (typeof item === 'number') {
@@ -19,20 +18,21 @@
19
18
  if (typeof item === 'string' || typeof item === 'boolean') {
20
19
  return Number(item);
21
20
  }
22
- return item // Pass through other types (will likely fail validation)
23
- ;
21
+ return item;
24
22
  });
25
23
  }
26
- // Handle single number
27
24
  if (typeof value !== 'number') {
28
25
  if (typeof value === 'string' || typeof value === 'boolean') {
29
26
  return Number(value);
30
27
  }
31
- // For objects/arrays (when not hasMany), return as-is (will likely fail validation later)
32
28
  return value;
33
29
  }
34
30
  }
35
31
  if (field.type === 'checkbox' && typeof value !== 'boolean') {
32
+ // Localized fields store locale-keyed objects like { en: true } — don't cast
33
+ if ('localized' in field && field.localized && typeof value === 'object' && value !== null) {
34
+ return value;
35
+ }
36
36
  if (typeof value === 'string') {
37
37
  if (value.toLowerCase() === 'true' || value === '1') {
38
38
  return true;
@@ -45,24 +45,23 @@
45
45
  }
46
46
  // Matches Mongoose behavior: stores dates as ISO strings
47
47
  if (field.type === 'date' && typeof value !== 'string') {
48
+ // Localized fields store locale-keyed objects like { en: '2024-01-01' } — don't cast
49
+ if ('localized' in field && field.localized && typeof value === 'object' && value !== null) {
50
+ return value;
51
+ }
48
52
  if (value instanceof Date) {
49
53
  return value.toISOString();
50
54
  }
51
55
  if (typeof value === 'number') {
52
56
  return new Date(value).toISOString();
53
57
  }
54
- // For other types, return as-is (will likely fail validation)
55
58
  return value;
56
59
  }
57
- // Text-like fields: convert non-strings to JSON strings
58
- // This handles text, textarea, email, and code fields
59
- // EXCEPTIONS:
60
- // - Localized fields are stored as objects like {"en": "value", "es": "valor"}
61
- // - hasMany fields are stored as arrays like ["value1", "value2"]
60
+ // Text-like fields: convert non-strings to JSON strings.
61
+ // Exceptions: localized (stored as locale map) and hasMany (stored as array).
62
62
  if ((field.type === 'text' || field.type === 'textarea' || field.type === 'email' || field.type === 'code') && typeof value !== 'string' && !('localized' in field && field.localized) && !('hasMany' in field && field.hasMany && Array.isArray(value))) {
63
63
  return JSON.stringify(value);
64
64
  }
65
- // No casting needed for other types or already correct types
66
65
  return value;
67
66
  }
68
67
 
@@ -44,6 +44,6 @@ export declare function dataToContentAPI(payload: Payload, collectionSlug: strin
44
44
  * object by filtering out known metadata keys.
45
45
  */
46
46
  export declare function resolveVersionContent(version: unknown, versionMeta: Record<string, unknown>): unknown;
47
- export declare function dataFromContentAPI(payload: Payload, collectionSlug: string, data: unknown): unknown;
47
+ export declare function dataFromContentAPI(payload: Payload, collectionSlug: string, data: unknown, locale?: string): unknown;
48
48
  export {};
49
49
  //# sourceMappingURL=index.d.ts.map