@payloadcms/figma 0.1.0-alpha.3 → 0.1.0-alpha.5
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.
- package/dist/api/check-project-subdomain-availability.d.ts +12 -0
- package/dist/api/check-project-subdomain-availability.js +32 -0
- package/dist/api/control-plane-error.d.ts +6 -0
- package/dist/api/control-plane-error.js +8 -0
- package/dist/api/control-plane-fetch.d.ts +7 -0
- package/dist/api/control-plane-fetch.js +37 -0
- package/dist/api/control-plane.d.ts +0 -8
- package/dist/api/control-plane.js +1 -47
- package/dist/api/create-project.d.ts +18 -0
- package/dist/api/create-project.js +37 -0
- package/dist/api/list-project-workspaces.d.ts +8 -0
- package/dist/api/list-project-workspaces.js +28 -0
- package/dist/api/project-resource-type.d.ts +1 -0
- package/dist/api/project-resource-type.js +1 -0
- package/dist/auth/headless-oauth-flow.d.ts +23 -0
- package/dist/auth/headless-oauth-flow.js +69 -0
- package/dist/auth/oauth-flow.d.ts +2 -0
- package/dist/auth/oauth-flow.js +13 -8
- package/dist/auth/token-store.d.ts +4 -1
- package/dist/auth/token-store.js +14 -0
- package/dist/auth/types.d.ts +12 -0
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +39 -4
- package/dist/commands/bootstrap.js +2 -1
- package/dist/commands/deploy.d.ts +10 -0
- package/dist/commands/deploy.js +39 -11
- package/dist/commands/detect-project-resource-type.d.ts +3 -0
- package/dist/commands/detect-project-resource-type.js +28 -0
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +163 -78
- package/dist/commands/login.d.ts +11 -1
- package/dist/commands/login.js +66 -4
- package/dist/commands/prompt-to-create-project/format-project-subdomain.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/format-project-subdomain.js +3 -0
- package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.js +7 -0
- package/dist/commands/prompt-to-create-project/get-default-project-name.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/get-default-project-name.js +13 -0
- package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.d.ts +7 -0
- package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.js +32 -0
- package/dist/commands/prompt-to-create-project/resolve-project-region.d.ts +8 -0
- package/dist/commands/prompt-to-create-project/resolve-project-region.js +27 -0
- package/dist/commands/prompt-to-create-project/resolve-project-subdomain.d.ts +9 -0
- package/dist/commands/prompt-to-create-project/resolve-project-subdomain.js +54 -0
- package/dist/commands/prompt-to-create-project/resolve-text-input.d.ts +9 -0
- package/dist/commands/prompt-to-create-project/resolve-text-input.js +23 -0
- package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.d.ts +12 -0
- package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.js +36 -0
- package/dist/commands/prompt-to-create-project/validate-subdomain.d.ts +1 -0
- package/dist/commands/prompt-to-create-project/validate-subdomain.js +9 -0
- package/dist/commands/prompt-to-create-project.d.ts +24 -0
- package/dist/commands/prompt-to-create-project.js +69 -0
- package/dist/commands/setup-agent.d.ts +12 -0
- package/dist/commands/setup-agent.js +123 -0
- package/dist/commands/write-project-configuration.d.ts +12 -0
- package/dist/commands/write-project-configuration.js +16 -0
- package/dist/db-content-api/index.js +52 -33
- package/dist/db-content-api/utilities/data/injectGlobalType.d.ts +6 -0
- package/dist/db-content-api/utilities/data/injectGlobalType.js +11 -0
- package/dist/db-content-api/utilities/isRecord.d.ts +9 -0
- package/dist/db-content-api/utilities/isRecord.js +19 -0
- package/dist/db-content-api/utilities/joins.js +5 -1
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
- package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
- package/dist/db-content-api/utilities/where.d.ts +2 -6
- package/dist/db-content-api/utilities/where.js +20 -9
- package/dist/exports/views.d.ts +1 -0
- package/dist/exports/views.js +1 -0
- package/dist/imageSizeOptions.d.ts +23 -0
- package/dist/imageSizeOptions.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/lib/write-agent-config.d.ts +12 -0
- package/dist/lib/write-agent-config.js +52 -0
- package/dist/oauth/defaults.d.ts +1 -0
- package/dist/oauth/defaults.js +1 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
- package/dist/oauth/index.js +24 -9
- package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
- package/dist/plugin/build-config.js +22 -9
- package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
- package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
- package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
- package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
- package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
- package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
- package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
- package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
- package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
- package/dist/plugin/cloud-limits/routes.d.ts +2 -0
- package/dist/plugin/cloud-limits/routes.js +4 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
- package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
- package/dist/plugin/cloud-limits/shared.d.ts +1 -1
- package/dist/plugin/cloud-limits/shared.js +25 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
- package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
- package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
- package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
- package/dist/skills/deploy-to-figma/SKILL.md +234 -0
- package/dist/skills/deploy-to-figma/references/cli-reference.md +146 -0
- package/dist/types.d.ts +9 -0
- package/dist/utils/lambda-config.js +4 -14
- package/dist/utils/messages.js +22 -3
- package/dist/utils/payload-generate.d.ts +10 -0
- package/dist/utils/payload-generate.js +59 -0
- package/dist/utils/pnpm-builds.d.ts +14 -0
- package/dist/utils/pnpm-builds.js +125 -0
- package/package.json +9 -1
- package/dist/lib/download-skill.d.ts +0 -12
- package/dist/lib/download-skill.js +0 -77
|
@@ -3,12 +3,14 @@ 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';
|
|
6
|
+
import { redirectInitialAutosaveCreate } from '../plugin/cloud-limits/server/redirectInitialAutosaveCreate.js';
|
|
6
7
|
import { getGlobalSlug } from './temp-utilities/slug.js';
|
|
7
8
|
import { addFallbackSort } from './temp-utilities/sorting.js';
|
|
8
9
|
import { unwrapDocument, unwrapFindResponse } from './temp-utilities/unwrapDocument.js';
|
|
9
10
|
import { createAuthMiddleware, createErrorMiddleware } from './utilities/auth.js';
|
|
10
11
|
import { getClientVersionHeaders } from './utilities/clientVersionHeaders.js';
|
|
11
12
|
import { dataToContentAPI, resolveVersionContent } from './utilities/data/index.js';
|
|
13
|
+
import { injectGlobalType } from './utilities/data/injectGlobalType.js';
|
|
12
14
|
import { validateRelationshipIds } from './utilities/data/validateRelationships.js';
|
|
13
15
|
import { convertPayloadJoinsToContentAPI } from './utilities/joins.js';
|
|
14
16
|
import { addFallbackLocale } from './utilities/locale/index.js';
|
|
@@ -121,7 +123,7 @@ async function createCollection(collectionId) {
|
|
|
121
123
|
}
|
|
122
124
|
async function findMany({ collection, joins, limit, locale: localeArg, page, pagination: _pagination, sort, where }) {
|
|
123
125
|
const locale = addFallbackLocale(localeArg, this.payload);
|
|
124
|
-
const
|
|
126
|
+
const localeNormalizedWhere = normalizeLocaleInWhere(where, this.payload, collection, locale);
|
|
125
127
|
const joinClause = convertPayloadJoinsToContentAPI(this.payload, collection, joins);
|
|
126
128
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/documents:find', {
|
|
127
129
|
body: {
|
|
@@ -133,7 +135,7 @@ async function findMany({ collection, joins, limit, locale: localeArg, page, pag
|
|
|
133
135
|
page: page ?? 1,
|
|
134
136
|
schema: this.documentSchema(),
|
|
135
137
|
sort: addFallbackSort(sort, this.payload, collection),
|
|
136
|
-
where: convertPayloadWhereToContentAPI(
|
|
138
|
+
where: convertPayloadWhereToContentAPI(localeNormalizedWhere, this.payload, collection, false)
|
|
137
139
|
}
|
|
138
140
|
});
|
|
139
141
|
if (error) {
|
|
@@ -165,7 +167,7 @@ async function find(args) {
|
|
|
165
167
|
// TODO: remove Omit when this PR is released: https://github.com/payloadcms/payload/pull/15183
|
|
166
168
|
async function findVersions(args) {
|
|
167
169
|
const sortWithFallback = addFallbackSort(args.sort || '-updatedAt', this.payload, args.collection);
|
|
168
|
-
const whereClause = convertPayloadWhereToContentAPI(args.where
|
|
170
|
+
const whereClause = convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, true);
|
|
169
171
|
const locale = addFallbackLocale(args.locale, this.payload);
|
|
170
172
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/document_versions:find', {
|
|
171
173
|
body: {
|
|
@@ -280,7 +282,7 @@ async function updateVersion(args) {
|
|
|
280
282
|
updatedAt: versionMeta.updatedAt,
|
|
281
283
|
version: versionContent
|
|
282
284
|
},
|
|
283
|
-
where: convertPayloadWhereToContentAPI(where)
|
|
285
|
+
where: convertPayloadWhereToContentAPI(where, this.payload, args.collection, true)
|
|
284
286
|
}
|
|
285
287
|
});
|
|
286
288
|
if (error) {
|
|
@@ -301,9 +303,12 @@ async function updateVersion(args) {
|
|
|
301
303
|
});
|
|
302
304
|
}
|
|
303
305
|
async function deleteVersions(args) {
|
|
304
|
-
|
|
306
|
+
// Globals are addressed by `globalSlug` and map to the `_global-<slug>` collection.
|
|
307
|
+
// Only one of `collection`/`globalSlug` is set.
|
|
308
|
+
const globalSlug = args.globalSlug != null ? getGlobalSlug(args.globalSlug) : undefined;
|
|
309
|
+
const collection = args.collection ?? globalSlug;
|
|
305
310
|
if (collection == null) {
|
|
306
|
-
throw new Error('deleteVersions requires a collection');
|
|
311
|
+
throw new Error('deleteVersions requires a collection or globalSlug');
|
|
307
312
|
}
|
|
308
313
|
const locale = addFallbackLocale(args.locale, this.payload);
|
|
309
314
|
const { error, response: httpResponse } = await this.client.POST('/api/v1/document_versions:delete', {
|
|
@@ -312,7 +317,7 @@ async function deleteVersions(args) {
|
|
|
312
317
|
contentSystemId: this.contentSystemId,
|
|
313
318
|
locale,
|
|
314
319
|
schema: this.documentSchema(),
|
|
315
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
320
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, collection, true)
|
|
316
321
|
}
|
|
317
322
|
});
|
|
318
323
|
if (error) {
|
|
@@ -342,7 +347,7 @@ async function findDistinct(args) {
|
|
|
342
347
|
page: args.page ?? 1,
|
|
343
348
|
schema: this.documentSchema(),
|
|
344
349
|
sort: addFallbackSort(args.sort, this.payload, args.collection),
|
|
345
|
-
where: convertPayloadWhereToContentAPI(args.where
|
|
350
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
346
351
|
}
|
|
347
352
|
});
|
|
348
353
|
if (error) {
|
|
@@ -375,7 +380,7 @@ async function updateMany(args) {
|
|
|
375
380
|
locale,
|
|
376
381
|
schema: this.documentSchema(),
|
|
377
382
|
sort: addFallbackSort(args.sort || '-updatedAt', this.payload, args.collection),
|
|
378
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
383
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
379
384
|
}
|
|
380
385
|
});
|
|
381
386
|
if (error) {
|
|
@@ -415,7 +420,7 @@ async function updateOne(args) {
|
|
|
415
420
|
environmentName: this.environmentName,
|
|
416
421
|
locale,
|
|
417
422
|
schema: this.documentSchema(),
|
|
418
|
-
where: convertPayloadWhereToContentAPI(where)
|
|
423
|
+
where: convertPayloadWhereToContentAPI(where, this.payload, args.collection, false)
|
|
419
424
|
}
|
|
420
425
|
});
|
|
421
426
|
if (error) {
|
|
@@ -447,7 +452,7 @@ async function deleteMany(args) {
|
|
|
447
452
|
environmentName: this.environmentName,
|
|
448
453
|
locale,
|
|
449
454
|
schema: this.documentSchema(),
|
|
450
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
455
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
451
456
|
}
|
|
452
457
|
});
|
|
453
458
|
if (error) {
|
|
@@ -463,7 +468,7 @@ async function deleteOne(args) {
|
|
|
463
468
|
environmentName: this.environmentName,
|
|
464
469
|
locale,
|
|
465
470
|
schema: this.documentSchema(),
|
|
466
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
471
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
467
472
|
}
|
|
468
473
|
});
|
|
469
474
|
if (error) {
|
|
@@ -521,6 +526,12 @@ async function create(args) {
|
|
|
521
526
|
}
|
|
522
527
|
});
|
|
523
528
|
if (error) {
|
|
529
|
+
redirectInitialAutosaveCreate({
|
|
530
|
+
collectionSlug: args.collection,
|
|
531
|
+
errorCode: error.code,
|
|
532
|
+
payload: this.payload,
|
|
533
|
+
req: args.req
|
|
534
|
+
});
|
|
524
535
|
throw createDocumentContentAPIError(this, 'create', error, httpResponse.status, args.collection);
|
|
525
536
|
}
|
|
526
537
|
if (!contentAPIResponse) {
|
|
@@ -570,7 +581,7 @@ async function count(args) {
|
|
|
570
581
|
contentSystemId: this.contentSystemId,
|
|
571
582
|
locale,
|
|
572
583
|
schema: this.documentSchema(),
|
|
573
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
584
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
574
585
|
}
|
|
575
586
|
});
|
|
576
587
|
if (error) {
|
|
@@ -591,7 +602,7 @@ async function countVersions(args) {
|
|
|
591
602
|
contentSystemId: this.contentSystemId,
|
|
592
603
|
locale,
|
|
593
604
|
schema: this.documentSchema(),
|
|
594
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
605
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, true)
|
|
595
606
|
}
|
|
596
607
|
});
|
|
597
608
|
if (error) {
|
|
@@ -604,12 +615,19 @@ async function countVersions(args) {
|
|
|
604
615
|
totalDocs: response.result.count
|
|
605
616
|
};
|
|
606
617
|
}
|
|
618
|
+
// Content API document ids are text. A document id is only ever a string or a number
|
|
619
|
+
// (a custom numeric id), so coerce those to a string and treat anything else as absent.
|
|
620
|
+
function toIdString(value) {
|
|
621
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined;
|
|
622
|
+
}
|
|
607
623
|
async function upsert(args) {
|
|
608
|
-
|
|
624
|
+
// The Content API stores every document id as text; a custom numeric id arrives as a
|
|
625
|
+
// number, so coerce it (matching `create`) before it becomes `createOnMissing.id`.
|
|
626
|
+
let documentId = toIdString(args.data.id);
|
|
609
627
|
if (!documentId && args.where && 'id' in args.where) {
|
|
610
628
|
const idClause = args.where.id;
|
|
611
|
-
if (idClause
|
|
612
|
-
documentId = idClause.equals;
|
|
629
|
+
if (idClause) {
|
|
630
|
+
documentId = toIdString(idClause.equals);
|
|
613
631
|
}
|
|
614
632
|
}
|
|
615
633
|
if (!documentId) {
|
|
@@ -621,21 +639,27 @@ async function upsert(args) {
|
|
|
621
639
|
data: args.data,
|
|
622
640
|
payload: this.payload
|
|
623
641
|
});
|
|
642
|
+
// `createdAt` is create-time metadata, so seed it on `createOnMissing` (used only when no
|
|
643
|
+
// document matches) rather than on `doc`. The server preserves an existing document's
|
|
644
|
+
// `createdAt` on update precisely because `doc` omits it; `updatedAt` is still refreshed on
|
|
645
|
+
// every write via `dataToContentAPI`. Sending `createdAt` on `doc` would overwrite it on
|
|
646
|
+
// every update (e.g. repeated `updateGlobal` calls).
|
|
647
|
+
const createdAt = new Date().toISOString();
|
|
624
648
|
const { data: contentAPIResponse, error, response: httpResponse } = await this.client.POST('/api/v1/documents:update', {
|
|
625
649
|
body: {
|
|
626
650
|
collection: args.collection,
|
|
627
651
|
contentSystemId: this.contentSystemId,
|
|
628
652
|
createOnMissing: {
|
|
629
|
-
id: documentId
|
|
653
|
+
id: documentId,
|
|
654
|
+
createdAt
|
|
630
655
|
},
|
|
631
656
|
doc: dataToContentAPI(this.payload, args.collection, args.data, {
|
|
632
|
-
applyDefaults: true
|
|
633
|
-
createdAt: true
|
|
657
|
+
applyDefaults: true
|
|
634
658
|
}),
|
|
635
659
|
environmentName: this.environmentName,
|
|
636
660
|
locale,
|
|
637
661
|
schema: this.documentSchema(),
|
|
638
|
-
where: convertPayloadWhereToContentAPI(args.where)
|
|
662
|
+
where: convertPayloadWhereToContentAPI(args.where, this.payload, args.collection, false)
|
|
639
663
|
}
|
|
640
664
|
});
|
|
641
665
|
if (error) {
|
|
@@ -682,7 +706,7 @@ async function updateJobs(args) {
|
|
|
682
706
|
} : where;
|
|
683
707
|
const docData = dataToContentAPI(this.payload, collection, data);
|
|
684
708
|
const sortClause = addFallbackSort(sort || '-updatedAt', this.payload, collection);
|
|
685
|
-
const whereQuery = convertPayloadWhereToContentAPI(whereClause);
|
|
709
|
+
const whereQuery = convertPayloadWhereToContentAPI(whereClause, this.payload, collection, false);
|
|
686
710
|
const { data: response, error, response: httpResponse } = await this.client.POST('/api/v1/documents:update', {
|
|
687
711
|
body: {
|
|
688
712
|
collection,
|
|
@@ -716,13 +740,13 @@ async function updateJobs(args) {
|
|
|
716
740
|
payload: this.payload
|
|
717
741
|
}));
|
|
718
742
|
}
|
|
719
|
-
function createGlobal(args) {
|
|
743
|
+
async function createGlobal(args) {
|
|
720
744
|
// Globals are singletons identified by their slug. Use upsert so concurrent calls
|
|
721
745
|
// (e.g. handleSchedules processing multiple queueables with the same null jobStats)
|
|
722
746
|
// don't fail with "A record with this field already exists". The deep-merge semantics
|
|
723
747
|
// of the update path are safe here: callers that want a fresh create (first run) get
|
|
724
748
|
// an effective create, and callers that race a prior create get a merge instead.
|
|
725
|
-
|
|
749
|
+
const doc = await this.upsert({
|
|
726
750
|
collection: getGlobalSlug(args.slug),
|
|
727
751
|
data: {
|
|
728
752
|
...args.data,
|
|
@@ -734,6 +758,7 @@ function createGlobal(args) {
|
|
|
734
758
|
}
|
|
735
759
|
}
|
|
736
760
|
});
|
|
761
|
+
return injectGlobalType(doc, args.slug);
|
|
737
762
|
}
|
|
738
763
|
async function findGlobal(args) {
|
|
739
764
|
const doc = await this.findOne({
|
|
@@ -741,17 +766,10 @@ async function findGlobal(args) {
|
|
|
741
766
|
locale: args.locale,
|
|
742
767
|
where: args.where ?? {}
|
|
743
768
|
});
|
|
744
|
-
|
|
745
|
-
;
|
|
746
|
-
doc.globalType = args.slug;
|
|
747
|
-
}
|
|
748
|
-
return doc;
|
|
769
|
+
return injectGlobalType(doc, args.slug);
|
|
749
770
|
}
|
|
750
771
|
async function updateGlobal(args) {
|
|
751
|
-
|
|
752
|
-
// which overwrites the original timestamp on updates. The fix belongs in the Content API:
|
|
753
|
-
// `createOnMissing` should preserve `createdAt` on updates and only set it on creates.
|
|
754
|
-
return this.upsert({
|
|
772
|
+
const doc = await this.upsert({
|
|
755
773
|
collection: getGlobalSlug(args.slug),
|
|
756
774
|
data: args.data,
|
|
757
775
|
where: {
|
|
@@ -760,6 +778,7 @@ async function updateGlobal(args) {
|
|
|
760
778
|
}
|
|
761
779
|
}
|
|
762
780
|
});
|
|
781
|
+
return injectGlobalType(doc, args.slug);
|
|
763
782
|
}
|
|
764
783
|
// TODO: remove Omit when this PR is released: https://github.com/payloadcms/payload/pull/15183
|
|
765
784
|
function findGlobalVersions(args) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payload expects globals to carry a `globalType` (their slug) alongside the stored data.
|
|
3
|
+
* Globals are stored as `_global-<slug>` collections that have no such column, so the adapter
|
|
4
|
+
* attaches it to every global document it returns.
|
|
5
|
+
*/
|
|
6
|
+
export declare function injectGlobalType<T>(doc: T, slug: string): T;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isRecord } from '../isRecord.js';
|
|
2
|
+
/**
|
|
3
|
+
* Payload expects globals to carry a `globalType` (their slug) alongside the stored data.
|
|
4
|
+
* Globals are stored as `_global-<slug>` collections that have no such column, so the adapter
|
|
5
|
+
* attaches it to every global document it returns.
|
|
6
|
+
*/ export function injectGlobalType(doc, slug) {
|
|
7
|
+
return isRecord(doc) ? {
|
|
8
|
+
...doc,
|
|
9
|
+
globalType: slug
|
|
10
|
+
} : doc;
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A plain, string-keyed object — the shape that can safely be spread and have
|
|
3
|
+
* properties read off it without tripping over arrays, `null`, or exotic objects.
|
|
4
|
+
*
|
|
5
|
+
* Objects carrying symbol-keyed own properties are rejected: symbol keys are how
|
|
6
|
+
* libraries tag a value as something more than data, and a spread copies only the
|
|
7
|
+
* string keys, so treating such a value as a record silently strips the tag.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A plain, string-keyed object — the shape that can safely be spread and have
|
|
3
|
+
* properties read off it without tripping over arrays, `null`, or exotic objects.
|
|
4
|
+
*
|
|
5
|
+
* Objects carrying symbol-keyed own properties are rejected: symbol keys are how
|
|
6
|
+
* libraries tag a value as something more than data, and a spread copies only the
|
|
7
|
+
* string keys, so treating such a value as a record silently strips the tag.
|
|
8
|
+
*/ export function isRecord(value) {
|
|
9
|
+
if (value == null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value !== 'object') {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return Object.getOwnPropertySymbols(value).length === 0;
|
|
19
|
+
}
|
|
@@ -102,6 +102,10 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
102
102
|
const effectiveLimit = joinQuery.limit ?? foundJoin.defaultLimit;
|
|
103
103
|
// Add fallback sort for the joined collection
|
|
104
104
|
const sortWithFallback = addFallbackSort(joinQuery.sort, payload, foundJoin.collectionSlug, foundJoin.defaultSort);
|
|
105
|
+
// A join's `where` filters the *joined* collection, so id coercion resolves against
|
|
106
|
+
// that collection (the first target for a polymorphic join; `id` is coerced regardless).
|
|
107
|
+
const joinedSlug = Array.isArray(foundJoin.collectionSlug) ? foundJoin.collectionSlug[0] : foundJoin.collectionSlug;
|
|
108
|
+
const whereConverted = convertPayloadWhereToContentAPI(joinQuery.where, payload, joinedSlug, false);
|
|
105
109
|
const contentAPIJoin = {
|
|
106
110
|
count: joinQuery.count ?? false,
|
|
107
111
|
path: joinPath,
|
|
@@ -115,7 +119,7 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
115
119
|
sort: sortWithFallback
|
|
116
120
|
},
|
|
117
121
|
...joinQuery.where && {
|
|
118
|
-
where:
|
|
122
|
+
where: whereConverted
|
|
119
123
|
}
|
|
120
124
|
};
|
|
121
125
|
result.push(contentAPIJoin);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Field, Payload } from 'payload';
|
|
2
|
+
export interface ResolveContext {
|
|
3
|
+
readonly fieldCache: Map<string, boolean>;
|
|
4
|
+
readonly fields: Field[];
|
|
5
|
+
readonly isVersions: boolean;
|
|
6
|
+
readonly payload: Payload;
|
|
7
|
+
}
|
|
8
|
+
/** Walks a path's segments against a field list, reporting whether it lands on a
|
|
9
|
+
* relationship/upload field's id-bearing value. */
|
|
10
|
+
export declare function resolvesToRelationshipValue(segments: string[], fields: Field[], ctx: ResolveContext): boolean;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { resolveBlocks } from '../resolveBlocks.js';
|
|
2
|
+
/** Walks a path's segments against a field list, reporting whether it lands on a
|
|
3
|
+
* relationship/upload field's id-bearing value. */ export function resolvesToRelationshipValue(segments, fields, ctx) {
|
|
4
|
+
const [head, ...rest] = segments;
|
|
5
|
+
if (head == null) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const field = findNamedField(fields, head);
|
|
9
|
+
if (field == null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
switch(field.type){
|
|
13
|
+
case 'array':
|
|
14
|
+
return 'fields' in field && resolvesToRelationshipValue(skipLocaleSegment(rest, field, ctx), field.fields, ctx);
|
|
15
|
+
case 'blocks':
|
|
16
|
+
{
|
|
17
|
+
if (!('blocks' in field)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Any block whose fields the remaining path resolves through makes it id-bearing:
|
|
21
|
+
// one path can only mean one thing, whichever block supplied the field.
|
|
22
|
+
const childSegments = skipLocaleSegment(rest, field, ctx);
|
|
23
|
+
return resolveBlocks(field.blocks, ctx.payload.config.blocks).some((block)=>resolvesToRelationshipValue(childSegments, block.fields, ctx));
|
|
24
|
+
}
|
|
25
|
+
case 'group':
|
|
26
|
+
return 'fields' in field && resolvesToRelationshipValue(rest, field.fields, ctx);
|
|
27
|
+
case 'relationship':
|
|
28
|
+
case 'upload':
|
|
29
|
+
// A polymorphic field's id sits at `<path>.value`; its `.relationTo` is a slug.
|
|
30
|
+
if (Array.isArray(field.relationTo)) {
|
|
31
|
+
return rest.length === 1 && rest[0] === 'value';
|
|
32
|
+
}
|
|
33
|
+
// A single-target field's id sits at the field's own path.
|
|
34
|
+
if (rest.length === 0) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// Payload accepts an explicit `<path>.id` for that same value —
|
|
38
|
+
// `getTableColumnFromPath` treats the bare path and `.id` as one column — which the
|
|
39
|
+
// Content API resolves as a traversal into the related collection's, equally text, id.
|
|
40
|
+
return rest.length === 1 && rest[0] === 'id';
|
|
41
|
+
default:
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Finds the field a path segment names. Unnamed containers (row, collapsible, unnamed tab)
|
|
46
|
+
* are transparent to query paths, so their children are searched too; a named tab nests its
|
|
47
|
+
* fields under its own name, exactly like a group. */ function findNamedField(fields, name) {
|
|
48
|
+
for (const field of fields){
|
|
49
|
+
if ('name' in field && field.name) {
|
|
50
|
+
// A named container's children need its name consumed first, so only match the
|
|
51
|
+
// container itself here.
|
|
52
|
+
if (field.name === name) {
|
|
53
|
+
return field;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if ('fields' in field && Array.isArray(field.fields)) {
|
|
58
|
+
const match = findNamedField(field.fields, name);
|
|
59
|
+
if (match) {
|
|
60
|
+
return match;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if ('tabs' in field && Array.isArray(field.tabs)) {
|
|
64
|
+
for (const tab of field.tabs){
|
|
65
|
+
if (!tab.fields) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!tab.name) {
|
|
69
|
+
const match = findNamedField(tab.fields, name);
|
|
70
|
+
if (match) {
|
|
71
|
+
return match;
|
|
72
|
+
}
|
|
73
|
+
} else if (tab.name === name) {
|
|
74
|
+
return {
|
|
75
|
+
name: tab.name,
|
|
76
|
+
type: 'group',
|
|
77
|
+
fields: tab.fields
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
// A localized array/blocks field is stored as a locale map, so its children's paths carry a
|
|
86
|
+
// locale key (`items.en.rel`) — see `normalizeLocaleInWhere`, which runs before this.
|
|
87
|
+
// Locale keys are *only* added to `array` and `blocks`.
|
|
88
|
+
function skipLocaleSegment(segments, field, ctx) {
|
|
89
|
+
const localization = ctx.payload.config.localization;
|
|
90
|
+
if (!localization || !('localized' in field) || !field.localized) {
|
|
91
|
+
return segments;
|
|
92
|
+
}
|
|
93
|
+
const [head] = segments;
|
|
94
|
+
return head != null && localization.localeCodes.includes(head) ? segments.slice(1) : segments;
|
|
95
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Payload, Where } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Stringifies numeric operands on a Where clause's id-bearing paths (`id`, `parent` for
|
|
4
|
+
* versions, and relationship-value paths), so a custom-numeric-id filter matches the
|
|
5
|
+
* Content API's text storage. Returns the clause untouched when there is nothing to
|
|
6
|
+
* convert. `isVersions` treats `parent` (the parent document's id) as id-bearing and looks
|
|
7
|
+
* for the collection's own fields under `version.`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function stringifyIdsInWhere(where: undefined | Where, payload: Payload, collectionSlug: string, isVersions: boolean): undefined | Where;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { resolvesToRelationshipValue } from './resolvesToRelationshipValue.js';
|
|
2
|
+
// The Content API stores every document id and every relationship value as text, but
|
|
3
|
+
// Payload keeps a collection's custom numeric id as a `number`. So a filter that carries
|
|
4
|
+
// a numeric id would never match the stored string form. This is the sole place the
|
|
5
|
+
// adapter translates those query-side ids to the wire's string form (the data-side
|
|
6
|
+
// counterpart is `convertRelationshipIds`); `convertPayloadWhereToContentAPI` itself is
|
|
7
|
+
// purely structural.
|
|
8
|
+
//
|
|
9
|
+
// A condition's path is id-bearing when it resolves to:
|
|
10
|
+
// - `id` — the document's own id (always).
|
|
11
|
+
// - `parent` — the parent document's id, on a *versions* query (`isVersions`).
|
|
12
|
+
// - a relationship/upload field's value: the field's own path for a single-target field
|
|
13
|
+
// (`rel: { equals: 999 }`), or `<path>.value` for a polymorphic one
|
|
14
|
+
// (`rel.value: { equals: 999 }`). A polymorphic field's `.relationTo` is a slug, not
|
|
15
|
+
// an id, so it is deliberately left alone.
|
|
16
|
+
//
|
|
17
|
+
// Every stored id is text, so a bare "stringify any numeric operand on this path" is
|
|
18
|
+
// sufficient — no per-target id-type resolution is needed (which also handles a
|
|
19
|
+
// polymorphic `in` array that mixes targets).
|
|
20
|
+
/**
|
|
21
|
+
* Stringifies numeric operands on a Where clause's id-bearing paths (`id`, `parent` for
|
|
22
|
+
* versions, and relationship-value paths), so a custom-numeric-id filter matches the
|
|
23
|
+
* Content API's text storage. Returns the clause untouched when there is nothing to
|
|
24
|
+
* convert. `isVersions` treats `parent` (the parent document's id) as id-bearing and looks
|
|
25
|
+
* for the collection's own fields under `version.`.
|
|
26
|
+
*/ export function stringifyIdsInWhere(where, payload, collectionSlug, isVersions) {
|
|
27
|
+
if (!where) {
|
|
28
|
+
return where;
|
|
29
|
+
}
|
|
30
|
+
// An unknown slug names no fields, but `id` (and `parent` on a versions query) is
|
|
31
|
+
// id-bearing regardless of the config, so still walk the clause with an empty field list:
|
|
32
|
+
// discarding it here would turn the query into a match-all.
|
|
33
|
+
const config = payload.config.collections.find((c)=>c.slug === collectionSlug);
|
|
34
|
+
const fields = config?.fields ?? [];
|
|
35
|
+
const fieldCache = new Map();
|
|
36
|
+
return transformWhere(where, {
|
|
37
|
+
fieldCache,
|
|
38
|
+
fields,
|
|
39
|
+
isVersions,
|
|
40
|
+
payload
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Recursively transforms a Where clause, stringifying numeric operands on any condition
|
|
44
|
+
* whose path is id-bearing. */ function transformWhere(where, context) {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const [key, value] of Object.entries(where)){
|
|
47
|
+
const keyLower = key.toLowerCase();
|
|
48
|
+
if (keyLower === 'and' || keyLower === 'or') {
|
|
49
|
+
const targetValue = where[key];
|
|
50
|
+
if (targetValue == null || !Array.isArray(targetValue)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
result[key] = targetValue.map((clause)=>transformWhere(clause, context));
|
|
54
|
+
} else if (isIdBearingPath(key, context)) {
|
|
55
|
+
result[key] = stringifyNumericOperands(value);
|
|
56
|
+
} else {
|
|
57
|
+
result[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Whether a condition's path resolves to an id. */ function isIdBearingPath(path, context) {
|
|
63
|
+
const cachedResult = context.fieldCache.get(path);
|
|
64
|
+
if (cachedResult != null) {
|
|
65
|
+
return cachedResult;
|
|
66
|
+
}
|
|
67
|
+
// GraphQL spells a nested path with `__`, since dots are invalid in GraphQL names; the
|
|
68
|
+
// structural conversion normalizes those the same way.
|
|
69
|
+
const segments = path.replace(/__/g, '.').split('.');
|
|
70
|
+
// The document's own id — on a versions query, the version document's own id.
|
|
71
|
+
if (segments.length === 1 && segments[0] === 'id') {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (context.isVersions) {
|
|
75
|
+
// `parent` is a version-only field that contains an ID.
|
|
76
|
+
if (segments.length === 1) {
|
|
77
|
+
return segments[0] === 'parent';
|
|
78
|
+
}
|
|
79
|
+
// The only other paths that can contain IDs are in the version envelope.
|
|
80
|
+
if (segments[0] !== 'version') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Normalize the path segments by removing the `version` envelope for paths
|
|
85
|
+
// in document versions.
|
|
86
|
+
const normalizedSegments = context.isVersions ? segments.slice(1) : segments;
|
|
87
|
+
return resolvesToRelationshipValue(normalizedSegments, context.fields, context);
|
|
88
|
+
}
|
|
89
|
+
function stringifyNumericOperands(condition) {
|
|
90
|
+
// Payload's shorthand — `{ id: 999 }` rather than `{ id: { equals: 999 } }` — carries the
|
|
91
|
+
// operand directly, with no operator keys to walk. The structural conversion reads it as
|
|
92
|
+
// an implicit `equals`, so coerce it in place: walking a primitive as an operator map
|
|
93
|
+
// would drop the condition entirely and turn the query into a match-all.
|
|
94
|
+
if (condition === null || typeof condition !== 'object' || Array.isArray(condition)) {
|
|
95
|
+
return stringifyNumbers(condition);
|
|
96
|
+
}
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const [operator, operand] of Object.entries(condition)){
|
|
99
|
+
result[operator] = stringifyNumbers(operand);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
/** A number becomes its string form; a number inside an array does too (e.g. `in`);
|
|
104
|
+
* everything else passes through untouched. */ function stringifyNumbers(value) {
|
|
105
|
+
if (typeof value === 'number') {
|
|
106
|
+
return String(value);
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.map((entry)=>typeof entry === 'number' ? String(entry) : entry);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import type { Where } from 'payload';
|
|
1
|
+
import type { Payload, Where } from 'payload';
|
|
2
2
|
import type { components } from '../generated/content-api-types.js';
|
|
3
3
|
type WhereClauseV1 = components['schemas']['WhereClauseV1'];
|
|
4
|
-
|
|
5
|
-
/** Internal flag to track if we're inside a logical operator (and/or) */
|
|
6
|
-
insideLogicalOperator?: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClauseV1;
|
|
4
|
+
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, payload: Payload, collectionSlug: string, isVersions: boolean): WhereClauseV1;
|
|
9
5
|
export {};
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import { stringifyIdsInWhere } from './meta/stringifyIdsInWhere.js';
|
|
2
|
+
// Payload's format: { fieldName: { operator: value }, and: [...], or: [...] }
|
|
3
|
+
// Content API expects: { and: [{ path, operator, value }], or: [{ path, operator, value }] }
|
|
4
|
+
//
|
|
5
|
+
// The format stays different. This allows the Content API to evolve its filter schema,
|
|
6
|
+
// for example to support composite types like Point, which are not lexicographically sortable.
|
|
7
|
+
// Semantics must remain identical to Payload.
|
|
8
|
+
export function convertPayloadWhereToContentAPI(where, payload, collectionSlug, isVersions) {
|
|
9
|
+
// Normalize id-bearing operands to the wire's string form (custom numeric ids), then
|
|
10
|
+
// convert Payload's where shape to the Content API's. The two steps are inseparable —
|
|
11
|
+
// every id filter must be coerced — so they live behind this single entry point.
|
|
12
|
+
return convertStructure(stringifyIdsInWhere(where, payload, collectionSlug, isVersions), {
|
|
13
|
+
insideLogicalOperator: false
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
// The structural Payload → Content API conversion. Assumes id operands have already been
|
|
17
|
+
// coerced to the wire's string form (see `convertPayloadWhereToContentAPI`).
|
|
18
|
+
function convertStructure(where, options) {
|
|
2
19
|
// Empty where {} should be { and: [] } not undefined
|
|
3
20
|
// Content API requires a where clause structure even for "no filter"
|
|
4
21
|
if (!where || Object.keys(where).length === 0) {
|
|
@@ -10,7 +27,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
10
27
|
for (const [key, value] of Object.entries(where)){
|
|
11
28
|
const keyLower = key.toLowerCase();
|
|
12
29
|
if (keyLower === 'and' || keyLower === 'or') {
|
|
13
|
-
const nestedConditions = value.map((item)=>
|
|
30
|
+
const nestedConditions = value.map((item)=>convertStructure(item, {
|
|
14
31
|
...options,
|
|
15
32
|
insideLogicalOperator: true
|
|
16
33
|
})).filter((condition)=>{
|
|
@@ -42,7 +59,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
42
59
|
conditions.push({
|
|
43
60
|
operator: 'equals',
|
|
44
61
|
path: fieldPath,
|
|
45
|
-
value
|
|
62
|
+
value
|
|
46
63
|
});
|
|
47
64
|
continue;
|
|
48
65
|
}
|
|
@@ -67,12 +84,6 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
67
84
|
if (finalValue === 'null') {
|
|
68
85
|
finalValue = null;
|
|
69
86
|
}
|
|
70
|
-
// Content API stores all document IDs as strings.
|
|
71
|
-
// Payload sends numeric values for collections with custom numeric ID fields,
|
|
72
|
-
// so we must stringify to match.
|
|
73
|
-
if (fieldPath === 'id' && typeof finalValue === 'number') {
|
|
74
|
-
finalValue = String(finalValue);
|
|
75
|
-
}
|
|
76
87
|
conditions.push({
|
|
77
88
|
operator: op,
|
|
78
89
|
path: fieldPath,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LimitReachedView } from '../plugin/cloud-limits/LimitReachedView/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LimitReachedView } from '../plugin/cloud-limits/LimitReachedView/index.js';
|