@btst/stack 5.0.0 → 5.1.0
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/index.d.cts +2 -2
- package/dist/api/index.d.mts +2 -2
- package/dist/api/index.d.ts +2 -2
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/context/index.d.cts +1 -1
- package/dist/context/index.d.mts +1 -1
- package/dist/context/index.d.ts +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/stack/src/plugins/cms/api/getters.cjs +142 -6
- package/dist/packages/stack/src/plugins/cms/api/getters.mjs +139 -8
- package/dist/packages/stack/src/plugins/cms/api/operations.cjs +67 -61
- package/dist/packages/stack/src/plugins/cms/api/operations.mjs +69 -63
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +26 -8
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +27 -9
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +15 -1
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +14 -2
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +45 -24
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +45 -24
- package/dist/packages/stack/src/plugins/cms/schemas.cjs +22 -0
- package/dist/packages/stack/src/plugins/cms/schemas.mjs +20 -1
- package/dist/plugins/api/index.d.cts +2 -2
- package/dist/plugins/api/index.d.mts +2 -2
- package/dist/plugins/api/index.d.ts +2 -2
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/client/index.d.cts +2 -2
- package/dist/plugins/client/index.d.mts +2 -2
- package/dist/plugins/client/index.d.ts +2 -2
- package/dist/plugins/cms/api/index.cjs +1 -0
- package/dist/plugins/cms/api/index.d.cts +43 -11
- package/dist/plugins/cms/api/index.d.mts +43 -11
- package/dist/plugins/cms/api/index.d.ts +43 -11
- package/dist/plugins/cms/api/index.mjs +1 -1
- package/dist/plugins/cms/client/hooks/index.d.cts +90 -45
- package/dist/plugins/cms/client/hooks/index.d.mts +90 -45
- package/dist/plugins/cms/client/hooks/index.d.ts +90 -45
- package/dist/plugins/cms/client/index.d.cts +2 -1
- package/dist/plugins/cms/client/index.d.mts +2 -1
- package/dist/plugins/cms/client/index.d.ts +2 -1
- package/dist/plugins/cms/query-keys.cjs +69 -13
- package/dist/plugins/cms/query-keys.d.cts +89 -212
- package/dist/plugins/cms/query-keys.d.mts +89 -212
- package/dist/plugins/cms/query-keys.d.ts +89 -212
- package/dist/plugins/cms/query-keys.mjs +70 -14
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/route-docs/client/index.d.cts +1 -1
- package/dist/plugins/route-docs/client/index.d.mts +1 -1
- package/dist/plugins/route-docs/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +3 -3
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +3 -3
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +3 -3
- package/dist/plugins/ui-builder/client/index.d.cts +3 -3
- package/dist/plugins/ui-builder/client/index.d.mts +3 -3
- package/dist/plugins/ui-builder/client/index.d.ts +3 -3
- package/dist/plugins/ui-builder/query-keys.d.cts +4 -3
- package/dist/plugins/ui-builder/query-keys.d.mts +4 -3
- package/dist/plugins/ui-builder/query-keys.d.ts +4 -3
- package/dist/shared/{stack.DMj68qY0.d.mts → stack.B2jlVVhD.d.mts} +5 -5
- package/dist/shared/{stack.Cvnj99cs.d.ts → stack.BB9tubJJ.d.ts} +9 -3
- package/dist/shared/{stack.Bt3WG2yb.d.cts → stack.BWduIMKG.d.cts} +35 -35
- package/dist/shared/stack.Bw09Lg2z.d.ts +1420 -0
- package/dist/shared/{stack.CkEz7vSt.d.mts → stack.C2Pvl-RS.d.mts} +9 -3
- package/dist/shared/{stack.C2l2wd_X.d.ts → stack.Ch9-ppmQ.d.ts} +35 -35
- package/dist/shared/{stack.Ht8mhDj5.d.cts → stack.D17yFglU.d.cts} +5 -5
- package/dist/shared/{stack.CiD115Ov.d.mts → stack.D6fPBfYe.d.mts} +35 -35
- package/dist/shared/stack.DFGj0gb7.d.mts +1420 -0
- package/dist/shared/{stack.pi0FxoEa.d.ts → stack.Dcfg4GNN.d.ts} +5 -5
- package/dist/shared/{stack.3rxARhgS.d.cts → stack.DrB3L7Ih.d.cts} +52 -3
- package/dist/shared/{stack.3rxARhgS.d.mts → stack.DrB3L7Ih.d.mts} +52 -3
- package/dist/shared/{stack.3rxARhgS.d.ts → stack.DrB3L7Ih.d.ts} +52 -3
- package/dist/shared/stack.NNrBKBkM.d.cts +1420 -0
- package/dist/shared/{stack.mRRyZBUg.d.cts → stack.Rb87lHqK.d.cts} +9 -3
- package/package.json +1 -1
- package/src/__tests__/cms-query-keys.test.ts +57 -0
- package/src/plugins/api/index.ts +1 -0
- package/src/plugins/api/operation.ts +16 -3
- package/src/plugins/cms/__tests__/authorization.test.ts +652 -187
- package/src/plugins/cms/__tests__/client-runtime.browser.test.tsx +655 -0
- package/src/plugins/cms/__tests__/content-read.typecheck.ts +351 -0
- package/src/plugins/cms/__tests__/getters.test.ts +286 -0
- package/src/plugins/cms/api/getters.ts +270 -12
- package/src/plugins/cms/api/index.ts +10 -0
- package/src/plugins/cms/api/operations.ts +163 -75
- package/src/plugins/cms/api/plugin.ts +50 -12
- package/src/plugins/cms/api/query-key-defs.ts +29 -2
- package/src/plugins/cms/client/hooks/cms-hooks.tsx +400 -91
- package/src/plugins/cms/client/index.ts +9 -0
- package/src/plugins/cms/query-keys.ts +171 -16
- package/src/plugins/cms/schemas.ts +30 -0
- package/src/plugins/cms/types.ts +49 -2
- package/src/plugins/media/__tests__/identity-partition.test.tsx +26 -16
- package/dist/shared/stack.CAmVUwjV.d.cts +0 -764
- package/dist/shared/stack.CQK02ujt.d.ts +0 -764
- package/dist/shared/stack.D9x1fR5a.d.mts +0 -764
- package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
|
@@ -25,9 +25,11 @@ function operationContentItem(item) {
|
|
|
25
25
|
...contentType ? { contentType: operationContentType(contentType) } : {}
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
function operationContentList(result) {
|
|
28
|
+
function operationContentList(result, options) {
|
|
29
29
|
return {
|
|
30
|
-
items: result.items.map(
|
|
30
|
+
items: result.items.map(
|
|
31
|
+
(item) => getters.applyContentReadOptions(operationContentItem(item), options)
|
|
32
|
+
),
|
|
31
33
|
total: result.total,
|
|
32
34
|
...result.limit !== void 0 ? { limit: result.limit } : {},
|
|
33
35
|
...result.offset !== void 0 ? { offset: result.offset } : {}
|
|
@@ -65,7 +67,9 @@ const CMSUpdateContentItemBodySchema = z.z.object({
|
|
|
65
67
|
});
|
|
66
68
|
const ListContentTypesInputSchema = EmptyInputSchema;
|
|
67
69
|
const GetContentTypeInputSchema = CMSContentTypeParamsSchema;
|
|
68
|
-
const GetContentItemInputSchema = CMSContentItemParamsSchema
|
|
70
|
+
const GetContentItemInputSchema = CMSContentItemParamsSchema.extend(
|
|
71
|
+
schemas.contentReadOptionsSchema.shape
|
|
72
|
+
);
|
|
69
73
|
const CreateContentItemInputSchema = z.z.object({
|
|
70
74
|
typeSlug: z.z.string().min(1),
|
|
71
75
|
body: CMSCreateContentItemBodySchema
|
|
@@ -82,6 +86,21 @@ class CMSOperationError extends api.OperationHttpError {
|
|
|
82
86
|
this.name = "CMSOperationError";
|
|
83
87
|
}
|
|
84
88
|
}
|
|
89
|
+
function validateOperationContentReadOptions(contentType, options) {
|
|
90
|
+
try {
|
|
91
|
+
getters.validateContentReadOptions(contentType, options);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof getters.CMSContentSelectionError) {
|
|
94
|
+
throw new CMSOperationError(
|
|
95
|
+
400,
|
|
96
|
+
error.message,
|
|
97
|
+
"INVALID_CONTENT_FIELDS",
|
|
98
|
+
[{ path: ["fields"], message: error.message }]
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
85
104
|
function normalizeOperationError(error, fallback) {
|
|
86
105
|
if (error instanceof Error) return error;
|
|
87
106
|
return new Error(typeof error === "string" ? error : fallback, {
|
|
@@ -617,39 +636,6 @@ async function deriveInverseSourceAuthorization(adapter, ensureSynced, targetTyp
|
|
|
617
636
|
sources
|
|
618
637
|
};
|
|
619
638
|
}
|
|
620
|
-
async function listRecordsForRelation(adapter, contentType, fieldName, targetId, limit, offset) {
|
|
621
|
-
const relations = await adapter.findMany({
|
|
622
|
-
model: "contentRelation",
|
|
623
|
-
where: [
|
|
624
|
-
{ field: "targetId", value: targetId, operator: "eq" },
|
|
625
|
-
{ field: "fieldName", value: fieldName, operator: "eq" }
|
|
626
|
-
]
|
|
627
|
-
});
|
|
628
|
-
const ids = [...new Set(relations.map((relation) => relation.sourceId))];
|
|
629
|
-
const items = [];
|
|
630
|
-
for (const id of ids) {
|
|
631
|
-
const item = await adapter.findOne({
|
|
632
|
-
model: "contentItem",
|
|
633
|
-
where: [
|
|
634
|
-
{ field: "id", value: id, operator: "eq" },
|
|
635
|
-
{
|
|
636
|
-
field: "contentTypeId",
|
|
637
|
-
value: contentType.id,
|
|
638
|
-
operator: "eq"
|
|
639
|
-
}
|
|
640
|
-
],
|
|
641
|
-
join: { contentType: true }
|
|
642
|
-
});
|
|
643
|
-
if (item) items.push(item);
|
|
644
|
-
}
|
|
645
|
-
items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
646
|
-
return {
|
|
647
|
-
items: items.slice(offset, offset + limit).map(getters.serializeContentItemWithType),
|
|
648
|
-
total: items.length,
|
|
649
|
-
limit,
|
|
650
|
-
offset
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
639
|
function createCMSOperations(adapter, options) {
|
|
654
640
|
const { ensureSynced, getContentTypesWithCounts, hooks } = options;
|
|
655
641
|
const populatedRelationSnapshots = /* @__PURE__ */ new WeakMap();
|
|
@@ -815,7 +801,7 @@ function createCMSOperations(adapter, options) {
|
|
|
815
801
|
});
|
|
816
802
|
const ListContentByRelationInputSchema = z.z.object({
|
|
817
803
|
typeSlug: z.z.string().min(1),
|
|
818
|
-
query: z.z.object({ field: z.z.string().min(1), targetId: z.z.string().min(1) }).merge(paginationSchema)
|
|
804
|
+
query: z.z.object({ field: z.z.string().min(1), targetId: z.z.string().min(1) }).merge(paginationSchema).merge(schemas.contentReadOptionsSchema)
|
|
819
805
|
});
|
|
820
806
|
const GetInverseRelationsInputSchema = z.z.object({
|
|
821
807
|
slug: z.z.string().min(1),
|
|
@@ -827,7 +813,7 @@ function createCMSOperations(adapter, options) {
|
|
|
827
813
|
query: z.z.object({
|
|
828
814
|
itemId: z.z.string().min(1),
|
|
829
815
|
fieldName: z.z.string().min(1)
|
|
830
|
-
}).merge(paginationSchema)
|
|
816
|
+
}).merge(paginationSchema).merge(schemas.contentReadOptionsSchema)
|
|
831
817
|
});
|
|
832
818
|
const notifyError = async (error, operation, context) => {
|
|
833
819
|
await hooks?.onErrorExecuteContentOperation?.(
|
|
@@ -924,11 +910,17 @@ function createCMSOperations(adapter, options) {
|
|
|
924
910
|
plugins_cms_permissions.cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
925
911
|
],
|
|
926
912
|
execute: async ({ input, facts }) => {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
913
|
+
if (input.query.fields !== void 0) {
|
|
914
|
+
validateOperationContentReadOptions(
|
|
915
|
+
await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
|
|
916
|
+
input.query
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
const result = await getters.getAllContentItems(adapter, facts.contentType, {
|
|
920
|
+
...input.query,
|
|
921
|
+
view: void 0,
|
|
922
|
+
fields: void 0
|
|
923
|
+
});
|
|
932
924
|
if (facts.scope === "record") {
|
|
933
925
|
if (!facts.recordId) {
|
|
934
926
|
if (result.items.length > 0) throw staleRecordError();
|
|
@@ -939,7 +931,7 @@ function createCMSOperations(adapter, options) {
|
|
|
939
931
|
)) {
|
|
940
932
|
throw staleRecordError();
|
|
941
933
|
}
|
|
942
|
-
return operationContentList(result);
|
|
934
|
+
return operationContentList(result, input.query);
|
|
943
935
|
}
|
|
944
936
|
if (result.items.length > 1) throw staleRecordError();
|
|
945
937
|
if (result.items.length === 0) {
|
|
@@ -954,7 +946,7 @@ function createCMSOperations(adapter, options) {
|
|
|
954
946
|
assertRecordFacts(result.items[0], facts);
|
|
955
947
|
}
|
|
956
948
|
}
|
|
957
|
-
return operationContentList(result);
|
|
949
|
+
return operationContentList(result, input.query);
|
|
958
950
|
},
|
|
959
951
|
onError: ({ error, ...context }) => notifyError(
|
|
960
952
|
error,
|
|
@@ -971,7 +963,7 @@ function createCMSOperations(adapter, options) {
|
|
|
971
963
|
additionalPermissions: ({ facts }) => [
|
|
972
964
|
plugins_cms_permissions.cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
973
965
|
],
|
|
974
|
-
execute: async ({ facts }) => {
|
|
966
|
+
execute: async ({ input, facts }) => {
|
|
975
967
|
const item = await getRecordOrThrow(
|
|
976
968
|
adapter,
|
|
977
969
|
ensureSynced,
|
|
@@ -979,7 +971,11 @@ function createCMSOperations(adapter, options) {
|
|
|
979
971
|
facts.recordId ?? ""
|
|
980
972
|
);
|
|
981
973
|
assertRecordFacts(item, facts);
|
|
982
|
-
|
|
974
|
+
validateOperationContentReadOptions(item.contentType, input);
|
|
975
|
+
return getters.applyContentReadOptions(
|
|
976
|
+
operationContentItem(getters.serializeContentItemWithType(item)),
|
|
977
|
+
input
|
|
978
|
+
);
|
|
983
979
|
},
|
|
984
980
|
onError: ({ error, ...context }) => notifyError(
|
|
985
981
|
error,
|
|
@@ -1424,7 +1420,7 @@ function createCMSOperations(adapter, options) {
|
|
|
1424
1420
|
}
|
|
1425
1421
|
});
|
|
1426
1422
|
const getContentItemPopulated = api.defineOperation({
|
|
1427
|
-
input:
|
|
1423
|
+
input: CMSContentItemParamsSchema,
|
|
1428
1424
|
permission: plugins_cms_permissions.cmsPermissions.record.read,
|
|
1429
1425
|
facts: async ({ input }) => recordFacts(
|
|
1430
1426
|
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id)
|
|
@@ -1526,17 +1522,17 @@ function createCMSOperations(adapter, options) {
|
|
|
1526
1522
|
input.query.field,
|
|
1527
1523
|
input.query.targetId
|
|
1528
1524
|
);
|
|
1529
|
-
|
|
1525
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
1526
|
+
const result = await getters.getContentItemsByRelationForType(
|
|
1530
1527
|
adapter,
|
|
1531
1528
|
sourceContentType,
|
|
1532
|
-
input.query
|
|
1533
|
-
input.query.targetId,
|
|
1534
|
-
input.query.limit,
|
|
1535
|
-
input.query.offset
|
|
1529
|
+
input.query
|
|
1536
1530
|
);
|
|
1537
1531
|
return {
|
|
1538
1532
|
...result,
|
|
1539
|
-
items: result.items.map(
|
|
1533
|
+
items: result.items.map(
|
|
1534
|
+
(item) => getters.applyContentReadOptions(operationContentItem(item), input.query)
|
|
1535
|
+
)
|
|
1540
1536
|
};
|
|
1541
1537
|
},
|
|
1542
1538
|
onError: ({ error, ...context }) => notifyError(
|
|
@@ -1718,17 +1714,27 @@ function createCMSOperations(adapter, options) {
|
|
|
1718
1714
|
input.slug,
|
|
1719
1715
|
input.query.itemId
|
|
1720
1716
|
);
|
|
1721
|
-
const
|
|
1717
|
+
const sourceContentType = await getContentTypeOrThrow(
|
|
1722
1718
|
adapter,
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1719
|
+
ensureSynced,
|
|
1720
|
+
facts.contentType
|
|
1721
|
+
);
|
|
1722
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
1723
|
+
const result = await getters.getContentItemsByRelationForType(
|
|
1724
|
+
adapter,
|
|
1725
|
+
sourceContentType,
|
|
1726
|
+
{
|
|
1727
|
+
field: source.fieldName,
|
|
1728
|
+
targetId: input.query.itemId,
|
|
1729
|
+
limit: input.query.limit,
|
|
1730
|
+
offset: input.query.offset
|
|
1731
|
+
}
|
|
1728
1732
|
);
|
|
1729
1733
|
return {
|
|
1730
1734
|
...result,
|
|
1731
|
-
items: result.items.map(
|
|
1735
|
+
items: result.items.map(
|
|
1736
|
+
(item) => getters.applyContentReadOptions(operationContentItem(item), input.query)
|
|
1737
|
+
)
|
|
1732
1738
|
};
|
|
1733
1739
|
} finally {
|
|
1734
1740
|
inverseTargetAuthorizationSnapshots.delete(input);
|
|
@@ -2,9 +2,9 @@ import { defineOperation, OperationHttpError } from '@btst/stack/plugins/api';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { formSchemaToZod } from '../../../../../ui/src/lib/schema-converter.mjs';
|
|
4
4
|
import { cmsPermissions } from '../../../../../../plugins/cms/permissions.mjs';
|
|
5
|
-
import { createListContentQuerySchema, DEFAULT_MAX_PAGE_SIZE } from '../schemas.mjs';
|
|
5
|
+
import { createListContentQuerySchema, DEFAULT_MAX_PAGE_SIZE, contentReadOptionsSchema } from '../schemas.mjs';
|
|
6
6
|
import { slugify } from '../utils.mjs';
|
|
7
|
-
import { serializeContentType, getAllContentItems, getContentItemBySlug, serializeContentItemWithType } from './getters.mjs';
|
|
7
|
+
import { serializeContentType, getAllContentItems, getContentItemBySlug, applyContentReadOptions, serializeContentItemWithType, getContentItemsByRelationForType, validateContentReadOptions, CMSContentSelectionError } from './getters.mjs';
|
|
8
8
|
import { syncRelations, extractRelationFields, isNewRelationValue, isExistingRelationValue } from './relations.mjs';
|
|
9
9
|
|
|
10
10
|
function operationContentType(contentType) {
|
|
@@ -23,9 +23,11 @@ function operationContentItem(item) {
|
|
|
23
23
|
...contentType ? { contentType: operationContentType(contentType) } : {}
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
function operationContentList(result) {
|
|
26
|
+
function operationContentList(result, options) {
|
|
27
27
|
return {
|
|
28
|
-
items: result.items.map(
|
|
28
|
+
items: result.items.map(
|
|
29
|
+
(item) => applyContentReadOptions(operationContentItem(item), options)
|
|
30
|
+
),
|
|
29
31
|
total: result.total,
|
|
30
32
|
...result.limit !== void 0 ? { limit: result.limit } : {},
|
|
31
33
|
...result.offset !== void 0 ? { offset: result.offset } : {}
|
|
@@ -63,7 +65,9 @@ const CMSUpdateContentItemBodySchema = z.object({
|
|
|
63
65
|
});
|
|
64
66
|
const ListContentTypesInputSchema = EmptyInputSchema;
|
|
65
67
|
const GetContentTypeInputSchema = CMSContentTypeParamsSchema;
|
|
66
|
-
const GetContentItemInputSchema = CMSContentItemParamsSchema
|
|
68
|
+
const GetContentItemInputSchema = CMSContentItemParamsSchema.extend(
|
|
69
|
+
contentReadOptionsSchema.shape
|
|
70
|
+
);
|
|
67
71
|
const CreateContentItemInputSchema = z.object({
|
|
68
72
|
typeSlug: z.string().min(1),
|
|
69
73
|
body: CMSCreateContentItemBodySchema
|
|
@@ -80,6 +84,21 @@ class CMSOperationError extends OperationHttpError {
|
|
|
80
84
|
this.name = "CMSOperationError";
|
|
81
85
|
}
|
|
82
86
|
}
|
|
87
|
+
function validateOperationContentReadOptions(contentType, options) {
|
|
88
|
+
try {
|
|
89
|
+
validateContentReadOptions(contentType, options);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof CMSContentSelectionError) {
|
|
92
|
+
throw new CMSOperationError(
|
|
93
|
+
400,
|
|
94
|
+
error.message,
|
|
95
|
+
"INVALID_CONTENT_FIELDS",
|
|
96
|
+
[{ path: ["fields"], message: error.message }]
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
83
102
|
function normalizeOperationError(error, fallback) {
|
|
84
103
|
if (error instanceof Error) return error;
|
|
85
104
|
return new Error(typeof error === "string" ? error : fallback, {
|
|
@@ -615,39 +634,6 @@ async function deriveInverseSourceAuthorization(adapter, ensureSynced, targetTyp
|
|
|
615
634
|
sources
|
|
616
635
|
};
|
|
617
636
|
}
|
|
618
|
-
async function listRecordsForRelation(adapter, contentType, fieldName, targetId, limit, offset) {
|
|
619
|
-
const relations = await adapter.findMany({
|
|
620
|
-
model: "contentRelation",
|
|
621
|
-
where: [
|
|
622
|
-
{ field: "targetId", value: targetId, operator: "eq" },
|
|
623
|
-
{ field: "fieldName", value: fieldName, operator: "eq" }
|
|
624
|
-
]
|
|
625
|
-
});
|
|
626
|
-
const ids = [...new Set(relations.map((relation) => relation.sourceId))];
|
|
627
|
-
const items = [];
|
|
628
|
-
for (const id of ids) {
|
|
629
|
-
const item = await adapter.findOne({
|
|
630
|
-
model: "contentItem",
|
|
631
|
-
where: [
|
|
632
|
-
{ field: "id", value: id, operator: "eq" },
|
|
633
|
-
{
|
|
634
|
-
field: "contentTypeId",
|
|
635
|
-
value: contentType.id,
|
|
636
|
-
operator: "eq"
|
|
637
|
-
}
|
|
638
|
-
],
|
|
639
|
-
join: { contentType: true }
|
|
640
|
-
});
|
|
641
|
-
if (item) items.push(item);
|
|
642
|
-
}
|
|
643
|
-
items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
644
|
-
return {
|
|
645
|
-
items: items.slice(offset, offset + limit).map(serializeContentItemWithType),
|
|
646
|
-
total: items.length,
|
|
647
|
-
limit,
|
|
648
|
-
offset
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
637
|
function createCMSOperations(adapter, options) {
|
|
652
638
|
const { ensureSynced, getContentTypesWithCounts, hooks } = options;
|
|
653
639
|
const populatedRelationSnapshots = /* @__PURE__ */ new WeakMap();
|
|
@@ -813,7 +799,7 @@ function createCMSOperations(adapter, options) {
|
|
|
813
799
|
});
|
|
814
800
|
const ListContentByRelationInputSchema = z.object({
|
|
815
801
|
typeSlug: z.string().min(1),
|
|
816
|
-
query: z.object({ field: z.string().min(1), targetId: z.string().min(1) }).merge(paginationSchema)
|
|
802
|
+
query: z.object({ field: z.string().min(1), targetId: z.string().min(1) }).merge(paginationSchema).merge(contentReadOptionsSchema)
|
|
817
803
|
});
|
|
818
804
|
const GetInverseRelationsInputSchema = z.object({
|
|
819
805
|
slug: z.string().min(1),
|
|
@@ -825,7 +811,7 @@ function createCMSOperations(adapter, options) {
|
|
|
825
811
|
query: z.object({
|
|
826
812
|
itemId: z.string().min(1),
|
|
827
813
|
fieldName: z.string().min(1)
|
|
828
|
-
}).merge(paginationSchema)
|
|
814
|
+
}).merge(paginationSchema).merge(contentReadOptionsSchema)
|
|
829
815
|
});
|
|
830
816
|
const notifyError = async (error, operation, context) => {
|
|
831
817
|
await hooks?.onErrorExecuteContentOperation?.(
|
|
@@ -922,11 +908,17 @@ function createCMSOperations(adapter, options) {
|
|
|
922
908
|
cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
923
909
|
],
|
|
924
910
|
execute: async ({ input, facts }) => {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
911
|
+
if (input.query.fields !== void 0) {
|
|
912
|
+
validateOperationContentReadOptions(
|
|
913
|
+
await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
|
|
914
|
+
input.query
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
const result = await getAllContentItems(adapter, facts.contentType, {
|
|
918
|
+
...input.query,
|
|
919
|
+
view: void 0,
|
|
920
|
+
fields: void 0
|
|
921
|
+
});
|
|
930
922
|
if (facts.scope === "record") {
|
|
931
923
|
if (!facts.recordId) {
|
|
932
924
|
if (result.items.length > 0) throw staleRecordError();
|
|
@@ -937,7 +929,7 @@ function createCMSOperations(adapter, options) {
|
|
|
937
929
|
)) {
|
|
938
930
|
throw staleRecordError();
|
|
939
931
|
}
|
|
940
|
-
return operationContentList(result);
|
|
932
|
+
return operationContentList(result, input.query);
|
|
941
933
|
}
|
|
942
934
|
if (result.items.length > 1) throw staleRecordError();
|
|
943
935
|
if (result.items.length === 0) {
|
|
@@ -952,7 +944,7 @@ function createCMSOperations(adapter, options) {
|
|
|
952
944
|
assertRecordFacts(result.items[0], facts);
|
|
953
945
|
}
|
|
954
946
|
}
|
|
955
|
-
return operationContentList(result);
|
|
947
|
+
return operationContentList(result, input.query);
|
|
956
948
|
},
|
|
957
949
|
onError: ({ error, ...context }) => notifyError(
|
|
958
950
|
error,
|
|
@@ -969,7 +961,7 @@ function createCMSOperations(adapter, options) {
|
|
|
969
961
|
additionalPermissions: ({ facts }) => [
|
|
970
962
|
cmsPermissions.contentType.read({ contentType: facts.contentType })
|
|
971
963
|
],
|
|
972
|
-
execute: async ({ facts }) => {
|
|
964
|
+
execute: async ({ input, facts }) => {
|
|
973
965
|
const item = await getRecordOrThrow(
|
|
974
966
|
adapter,
|
|
975
967
|
ensureSynced,
|
|
@@ -977,7 +969,11 @@ function createCMSOperations(adapter, options) {
|
|
|
977
969
|
facts.recordId ?? ""
|
|
978
970
|
);
|
|
979
971
|
assertRecordFacts(item, facts);
|
|
980
|
-
|
|
972
|
+
validateOperationContentReadOptions(item.contentType, input);
|
|
973
|
+
return applyContentReadOptions(
|
|
974
|
+
operationContentItem(serializeContentItemWithType(item)),
|
|
975
|
+
input
|
|
976
|
+
);
|
|
981
977
|
},
|
|
982
978
|
onError: ({ error, ...context }) => notifyError(
|
|
983
979
|
error,
|
|
@@ -1422,7 +1418,7 @@ function createCMSOperations(adapter, options) {
|
|
|
1422
1418
|
}
|
|
1423
1419
|
});
|
|
1424
1420
|
const getContentItemPopulated = defineOperation({
|
|
1425
|
-
input:
|
|
1421
|
+
input: CMSContentItemParamsSchema,
|
|
1426
1422
|
permission: cmsPermissions.record.read,
|
|
1427
1423
|
facts: async ({ input }) => recordFacts(
|
|
1428
1424
|
await getRecordOrThrow(adapter, ensureSynced, input.typeSlug, input.id)
|
|
@@ -1524,17 +1520,17 @@ function createCMSOperations(adapter, options) {
|
|
|
1524
1520
|
input.query.field,
|
|
1525
1521
|
input.query.targetId
|
|
1526
1522
|
);
|
|
1527
|
-
|
|
1523
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
1524
|
+
const result = await getContentItemsByRelationForType(
|
|
1528
1525
|
adapter,
|
|
1529
1526
|
sourceContentType,
|
|
1530
|
-
input.query
|
|
1531
|
-
input.query.targetId,
|
|
1532
|
-
input.query.limit,
|
|
1533
|
-
input.query.offset
|
|
1527
|
+
input.query
|
|
1534
1528
|
);
|
|
1535
1529
|
return {
|
|
1536
1530
|
...result,
|
|
1537
|
-
items: result.items.map(
|
|
1531
|
+
items: result.items.map(
|
|
1532
|
+
(item) => applyContentReadOptions(operationContentItem(item), input.query)
|
|
1533
|
+
)
|
|
1538
1534
|
};
|
|
1539
1535
|
},
|
|
1540
1536
|
onError: ({ error, ...context }) => notifyError(
|
|
@@ -1716,17 +1712,27 @@ function createCMSOperations(adapter, options) {
|
|
|
1716
1712
|
input.slug,
|
|
1717
1713
|
input.query.itemId
|
|
1718
1714
|
);
|
|
1719
|
-
const
|
|
1715
|
+
const sourceContentType = await getContentTypeOrThrow(
|
|
1720
1716
|
adapter,
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1717
|
+
ensureSynced,
|
|
1718
|
+
facts.contentType
|
|
1719
|
+
);
|
|
1720
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
1721
|
+
const result = await getContentItemsByRelationForType(
|
|
1722
|
+
adapter,
|
|
1723
|
+
sourceContentType,
|
|
1724
|
+
{
|
|
1725
|
+
field: source.fieldName,
|
|
1726
|
+
targetId: input.query.itemId,
|
|
1727
|
+
limit: input.query.limit,
|
|
1728
|
+
offset: input.query.offset
|
|
1729
|
+
}
|
|
1726
1730
|
);
|
|
1727
1731
|
return {
|
|
1728
1732
|
...result,
|
|
1729
|
-
items: result.items.map(
|
|
1733
|
+
items: result.items.map(
|
|
1734
|
+
(item) => applyContentReadOptions(operationContentItem(item), input.query)
|
|
1735
|
+
)
|
|
1730
1736
|
};
|
|
1731
1737
|
} finally {
|
|
1732
1738
|
inverseTargetAuthorizationSnapshots.delete(input);
|
|
@@ -114,13 +114,21 @@ const cmsBackendPlugin = (config) => {
|
|
|
114
114
|
break;
|
|
115
115
|
case "contentList": {
|
|
116
116
|
const typeSlug = params?.typeSlug ?? "";
|
|
117
|
+
const options = schemas.createListContentQuerySchema(
|
|
118
|
+
config.maxPageSize
|
|
119
|
+
).parse({
|
|
120
|
+
limit: params?.limit,
|
|
121
|
+
search: params?.search,
|
|
122
|
+
view: params?.view,
|
|
123
|
+
fields: params?.fields
|
|
124
|
+
});
|
|
117
125
|
const [contentTypes, contentItems] = await Promise.all([
|
|
118
126
|
getContentTypesWithCounts(adapter),
|
|
119
|
-
getters.getAllContentItems(adapter, typeSlug,
|
|
127
|
+
getters.getAllContentItems(adapter, typeSlug, options)
|
|
120
128
|
]);
|
|
121
129
|
queryClient.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
122
130
|
queryClient.setQueryData(
|
|
123
|
-
queryKeyDefs.CMS_QUERY_KEYS.contentList({ typeSlug,
|
|
131
|
+
queryKeyDefs.CMS_QUERY_KEYS.contentList({ typeSlug, ...options }),
|
|
124
132
|
{
|
|
125
133
|
pages: [
|
|
126
134
|
{
|
|
@@ -138,14 +146,18 @@ const cmsBackendPlugin = (config) => {
|
|
|
138
146
|
case "editContent": {
|
|
139
147
|
const typeSlug = params?.typeSlug ?? "";
|
|
140
148
|
const id = params?.id ?? "";
|
|
149
|
+
const options = schemas.contentReadOptionsSchema.parse({
|
|
150
|
+
view: params?.view,
|
|
151
|
+
fields: params?.fields
|
|
152
|
+
});
|
|
141
153
|
const [contentTypes, item] = await Promise.all([
|
|
142
154
|
getContentTypesWithCounts(adapter),
|
|
143
|
-
id ? getters.getContentItemById(adapter, id) : Promise.resolve(null)
|
|
155
|
+
id ? getters.getContentItemById(adapter, id, options) : Promise.resolve(null)
|
|
144
156
|
]);
|
|
145
157
|
queryClient.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
146
158
|
if (id) {
|
|
147
159
|
queryClient.setQueryData(
|
|
148
|
-
queryKeyDefs.CMS_QUERY_KEYS.contentDetail(typeSlug, id),
|
|
160
|
+
queryKeyDefs.CMS_QUERY_KEYS.contentDetail(typeSlug, id, options),
|
|
149
161
|
item
|
|
150
162
|
);
|
|
151
163
|
}
|
|
@@ -167,7 +179,9 @@ const cmsBackendPlugin = (config) => {
|
|
|
167
179
|
prefetchForRoute: createCMSPrefetchForRoute(adapter)
|
|
168
180
|
}),
|
|
169
181
|
routes: (_adapter, _context, operations$1) => {
|
|
170
|
-
const listQuerySchema = schemas.createListContentQuerySchema(
|
|
182
|
+
const listQuerySchema = schemas.createListContentQuerySchema(
|
|
183
|
+
config.maxPageSize
|
|
184
|
+
).extend(schemas.contentReadQuerySchema.shape);
|
|
171
185
|
const paginationSchema = z.z.object({
|
|
172
186
|
limit: z.z.coerce.number().min(1).max(config.maxPageSize ?? schemas.DEFAULT_MAX_PAGE_SIZE).optional().default(20),
|
|
173
187
|
offset: z.z.coerce.number().min(0).optional().default(0)
|
|
@@ -204,9 +218,13 @@ const cmsBackendPlugin = (config) => {
|
|
|
204
218
|
{
|
|
205
219
|
method: "GET",
|
|
206
220
|
params: operations.CMSContentItemParamsSchema,
|
|
221
|
+
query: schemas.contentReadQuerySchema,
|
|
207
222
|
requireRequest: true
|
|
208
223
|
},
|
|
209
|
-
operations$1.getContentItem.route((ctx) =>
|
|
224
|
+
operations$1.getContentItem.route((ctx) => ({
|
|
225
|
+
...ctx.params,
|
|
226
|
+
...ctx.query
|
|
227
|
+
}))
|
|
210
228
|
);
|
|
211
229
|
const createContentItem = api.createEndpoint(
|
|
212
230
|
"/content/:typeSlug",
|
|
@@ -257,7 +275,7 @@ const cmsBackendPlugin = (config) => {
|
|
|
257
275
|
{
|
|
258
276
|
method: "GET",
|
|
259
277
|
params: z.z.object({ typeSlug: z.z.string().min(1) }),
|
|
260
|
-
query: z.z.object({ field: z.z.string(), targetId: z.z.string() }).merge(paginationSchema),
|
|
278
|
+
query: z.z.object({ field: z.z.string(), targetId: z.z.string() }).merge(paginationSchema).merge(schemas.contentReadQuerySchema),
|
|
261
279
|
requireRequest: true
|
|
262
280
|
},
|
|
263
281
|
operations$1.listContentByRelation.route((ctx) => ({
|
|
@@ -286,7 +304,7 @@ const cmsBackendPlugin = (config) => {
|
|
|
286
304
|
slug: z.z.string().min(1),
|
|
287
305
|
sourceType: z.z.string().min(1)
|
|
288
306
|
}),
|
|
289
|
-
query: z.z.object({ itemId: z.z.string(), fieldName: z.z.string() }).merge(paginationSchema),
|
|
307
|
+
query: z.z.object({ itemId: z.z.string(), fieldName: z.z.string() }).merge(paginationSchema).merge(schemas.contentReadQuerySchema),
|
|
290
308
|
requireRequest: true
|
|
291
309
|
},
|
|
292
310
|
operations$1.listInverseRelationItems.route((ctx) => ({
|
|
@@ -2,7 +2,7 @@ import { defineBackendPlugin, createEndpoint } from '@btst/stack/plugins/api';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { zodToFormSchema } from '../../../../../ui/src/lib/schema-converter.mjs';
|
|
4
4
|
import { cmsSchema } from '../db.mjs';
|
|
5
|
-
import { createListContentQuerySchema, DEFAULT_MAX_PAGE_SIZE } from '../schemas.mjs';
|
|
5
|
+
import { createListContentQuerySchema, contentReadQuerySchema, DEFAULT_MAX_PAGE_SIZE, contentReadOptionsSchema } from '../schemas.mjs';
|
|
6
6
|
import { getContentItemById, getAllContentItems, getAllContentTypes } from './getters.mjs';
|
|
7
7
|
import { CMSContentTypeParamsSchema, CMSContentItemParamsSchema, CMSCreateContentItemBodySchema, CMSUpdateContentItemBodySchema, createCMSOperations } from './operations.mjs';
|
|
8
8
|
import { CMS_QUERY_KEYS } from './query-key-defs.mjs';
|
|
@@ -112,13 +112,21 @@ const cmsBackendPlugin = (config) => {
|
|
|
112
112
|
break;
|
|
113
113
|
case "contentList": {
|
|
114
114
|
const typeSlug = params?.typeSlug ?? "";
|
|
115
|
+
const options = createListContentQuerySchema(
|
|
116
|
+
config.maxPageSize
|
|
117
|
+
).parse({
|
|
118
|
+
limit: params?.limit,
|
|
119
|
+
search: params?.search,
|
|
120
|
+
view: params?.view,
|
|
121
|
+
fields: params?.fields
|
|
122
|
+
});
|
|
115
123
|
const [contentTypes, contentItems] = await Promise.all([
|
|
116
124
|
getContentTypesWithCounts(adapter),
|
|
117
|
-
getAllContentItems(adapter, typeSlug,
|
|
125
|
+
getAllContentItems(adapter, typeSlug, options)
|
|
118
126
|
]);
|
|
119
127
|
queryClient.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
120
128
|
queryClient.setQueryData(
|
|
121
|
-
CMS_QUERY_KEYS.contentList({ typeSlug,
|
|
129
|
+
CMS_QUERY_KEYS.contentList({ typeSlug, ...options }),
|
|
122
130
|
{
|
|
123
131
|
pages: [
|
|
124
132
|
{
|
|
@@ -136,14 +144,18 @@ const cmsBackendPlugin = (config) => {
|
|
|
136
144
|
case "editContent": {
|
|
137
145
|
const typeSlug = params?.typeSlug ?? "";
|
|
138
146
|
const id = params?.id ?? "";
|
|
147
|
+
const options = contentReadOptionsSchema.parse({
|
|
148
|
+
view: params?.view,
|
|
149
|
+
fields: params?.fields
|
|
150
|
+
});
|
|
139
151
|
const [contentTypes, item] = await Promise.all([
|
|
140
152
|
getContentTypesWithCounts(adapter),
|
|
141
|
-
id ? getContentItemById(adapter, id) : Promise.resolve(null)
|
|
153
|
+
id ? getContentItemById(adapter, id, options) : Promise.resolve(null)
|
|
142
154
|
]);
|
|
143
155
|
queryClient.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
144
156
|
if (id) {
|
|
145
157
|
queryClient.setQueryData(
|
|
146
|
-
CMS_QUERY_KEYS.contentDetail(typeSlug, id),
|
|
158
|
+
CMS_QUERY_KEYS.contentDetail(typeSlug, id, options),
|
|
147
159
|
item
|
|
148
160
|
);
|
|
149
161
|
}
|
|
@@ -165,7 +177,9 @@ const cmsBackendPlugin = (config) => {
|
|
|
165
177
|
prefetchForRoute: createCMSPrefetchForRoute(adapter)
|
|
166
178
|
}),
|
|
167
179
|
routes: (_adapter, _context, operations) => {
|
|
168
|
-
const listQuerySchema = createListContentQuerySchema(
|
|
180
|
+
const listQuerySchema = createListContentQuerySchema(
|
|
181
|
+
config.maxPageSize
|
|
182
|
+
).extend(contentReadQuerySchema.shape);
|
|
169
183
|
const paginationSchema = z.object({
|
|
170
184
|
limit: z.coerce.number().min(1).max(config.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE).optional().default(20),
|
|
171
185
|
offset: z.coerce.number().min(0).optional().default(0)
|
|
@@ -202,9 +216,13 @@ const cmsBackendPlugin = (config) => {
|
|
|
202
216
|
{
|
|
203
217
|
method: "GET",
|
|
204
218
|
params: CMSContentItemParamsSchema,
|
|
219
|
+
query: contentReadQuerySchema,
|
|
205
220
|
requireRequest: true
|
|
206
221
|
},
|
|
207
|
-
operations.getContentItem.route((ctx) =>
|
|
222
|
+
operations.getContentItem.route((ctx) => ({
|
|
223
|
+
...ctx.params,
|
|
224
|
+
...ctx.query
|
|
225
|
+
}))
|
|
208
226
|
);
|
|
209
227
|
const createContentItem = createEndpoint(
|
|
210
228
|
"/content/:typeSlug",
|
|
@@ -255,7 +273,7 @@ const cmsBackendPlugin = (config) => {
|
|
|
255
273
|
{
|
|
256
274
|
method: "GET",
|
|
257
275
|
params: z.object({ typeSlug: z.string().min(1) }),
|
|
258
|
-
query: z.object({ field: z.string(), targetId: z.string() }).merge(paginationSchema),
|
|
276
|
+
query: z.object({ field: z.string(), targetId: z.string() }).merge(paginationSchema).merge(contentReadQuerySchema),
|
|
259
277
|
requireRequest: true
|
|
260
278
|
},
|
|
261
279
|
operations.listContentByRelation.route((ctx) => ({
|
|
@@ -284,7 +302,7 @@ const cmsBackendPlugin = (config) => {
|
|
|
284
302
|
slug: z.string().min(1),
|
|
285
303
|
sourceType: z.string().min(1)
|
|
286
304
|
}),
|
|
287
|
-
query: z.object({ itemId: z.string(), fieldName: z.string() }).merge(paginationSchema),
|
|
305
|
+
query: z.object({ itemId: z.string(), fieldName: z.string() }).merge(paginationSchema).merge(contentReadQuerySchema),
|
|
288
306
|
requireRequest: true
|
|
289
307
|
},
|
|
290
308
|
operations.listInverseRelationItems.route((ctx) => ({
|