@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.
Files changed (113) hide show
  1. package/dist/api/index.d.cts +2 -2
  2. package/dist/api/index.d.mts +2 -2
  3. package/dist/api/index.d.ts +2 -2
  4. package/dist/client/index.d.cts +2 -2
  5. package/dist/client/index.d.mts +2 -2
  6. package/dist/client/index.d.ts +2 -2
  7. package/dist/context/index.d.cts +1 -1
  8. package/dist/context/index.d.mts +1 -1
  9. package/dist/context/index.d.ts +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.mts +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/packages/stack/src/plugins/cms/api/getters.cjs +142 -6
  14. package/dist/packages/stack/src/plugins/cms/api/getters.mjs +139 -8
  15. package/dist/packages/stack/src/plugins/cms/api/operations.cjs +67 -61
  16. package/dist/packages/stack/src/plugins/cms/api/operations.mjs +69 -63
  17. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +26 -8
  18. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +27 -9
  19. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +15 -1
  20. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +14 -2
  21. package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +45 -24
  22. package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +45 -24
  23. package/dist/packages/stack/src/plugins/cms/schemas.cjs +22 -0
  24. package/dist/packages/stack/src/plugins/cms/schemas.mjs +20 -1
  25. package/dist/plugins/api/index.d.cts +2 -2
  26. package/dist/plugins/api/index.d.mts +2 -2
  27. package/dist/plugins/api/index.d.ts +2 -2
  28. package/dist/plugins/blog/api/index.d.cts +2 -2
  29. package/dist/plugins/blog/api/index.d.mts +2 -2
  30. package/dist/plugins/blog/api/index.d.ts +2 -2
  31. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  32. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  33. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  34. package/dist/plugins/blog/client/index.d.cts +2 -2
  35. package/dist/plugins/blog/client/index.d.mts +2 -2
  36. package/dist/plugins/blog/client/index.d.ts +2 -2
  37. package/dist/plugins/blog/query-keys.d.cts +2 -2
  38. package/dist/plugins/blog/query-keys.d.mts +2 -2
  39. package/dist/plugins/blog/query-keys.d.ts +2 -2
  40. package/dist/plugins/client/index.d.cts +2 -2
  41. package/dist/plugins/client/index.d.mts +2 -2
  42. package/dist/plugins/client/index.d.ts +2 -2
  43. package/dist/plugins/cms/api/index.cjs +1 -0
  44. package/dist/plugins/cms/api/index.d.cts +43 -11
  45. package/dist/plugins/cms/api/index.d.mts +43 -11
  46. package/dist/plugins/cms/api/index.d.ts +43 -11
  47. package/dist/plugins/cms/api/index.mjs +1 -1
  48. package/dist/plugins/cms/client/hooks/index.d.cts +90 -45
  49. package/dist/plugins/cms/client/hooks/index.d.mts +90 -45
  50. package/dist/plugins/cms/client/hooks/index.d.ts +90 -45
  51. package/dist/plugins/cms/client/index.d.cts +2 -1
  52. package/dist/plugins/cms/client/index.d.mts +2 -1
  53. package/dist/plugins/cms/client/index.d.ts +2 -1
  54. package/dist/plugins/cms/query-keys.cjs +69 -13
  55. package/dist/plugins/cms/query-keys.d.cts +89 -212
  56. package/dist/plugins/cms/query-keys.d.mts +89 -212
  57. package/dist/plugins/cms/query-keys.d.ts +89 -212
  58. package/dist/plugins/cms/query-keys.mjs +70 -14
  59. package/dist/plugins/kanban/client/index.d.cts +1 -1
  60. package/dist/plugins/kanban/client/index.d.mts +1 -1
  61. package/dist/plugins/kanban/client/index.d.ts +1 -1
  62. package/dist/plugins/route-docs/client/index.d.cts +1 -1
  63. package/dist/plugins/route-docs/client/index.d.mts +1 -1
  64. package/dist/plugins/route-docs/client/index.d.ts +1 -1
  65. package/dist/plugins/ui-builder/client/hooks/index.d.cts +3 -3
  66. package/dist/plugins/ui-builder/client/hooks/index.d.mts +3 -3
  67. package/dist/plugins/ui-builder/client/hooks/index.d.ts +3 -3
  68. package/dist/plugins/ui-builder/client/index.d.cts +3 -3
  69. package/dist/plugins/ui-builder/client/index.d.mts +3 -3
  70. package/dist/plugins/ui-builder/client/index.d.ts +3 -3
  71. package/dist/plugins/ui-builder/query-keys.d.cts +4 -3
  72. package/dist/plugins/ui-builder/query-keys.d.mts +4 -3
  73. package/dist/plugins/ui-builder/query-keys.d.ts +4 -3
  74. package/dist/shared/{stack.DMj68qY0.d.mts → stack.B2jlVVhD.d.mts} +5 -5
  75. package/dist/shared/{stack.Cvnj99cs.d.ts → stack.BB9tubJJ.d.ts} +9 -3
  76. package/dist/shared/{stack.Bt3WG2yb.d.cts → stack.BWduIMKG.d.cts} +35 -35
  77. package/dist/shared/stack.Bw09Lg2z.d.ts +1420 -0
  78. package/dist/shared/{stack.CkEz7vSt.d.mts → stack.C2Pvl-RS.d.mts} +9 -3
  79. package/dist/shared/{stack.C2l2wd_X.d.ts → stack.Ch9-ppmQ.d.ts} +35 -35
  80. package/dist/shared/{stack.Ht8mhDj5.d.cts → stack.D17yFglU.d.cts} +5 -5
  81. package/dist/shared/{stack.CiD115Ov.d.mts → stack.D6fPBfYe.d.mts} +35 -35
  82. package/dist/shared/stack.DFGj0gb7.d.mts +1420 -0
  83. package/dist/shared/{stack.pi0FxoEa.d.ts → stack.Dcfg4GNN.d.ts} +5 -5
  84. package/dist/shared/{stack.3rxARhgS.d.cts → stack.DrB3L7Ih.d.cts} +52 -3
  85. package/dist/shared/{stack.3rxARhgS.d.mts → stack.DrB3L7Ih.d.mts} +52 -3
  86. package/dist/shared/{stack.3rxARhgS.d.ts → stack.DrB3L7Ih.d.ts} +52 -3
  87. package/dist/shared/stack.NNrBKBkM.d.cts +1420 -0
  88. package/dist/shared/{stack.mRRyZBUg.d.cts → stack.Rb87lHqK.d.cts} +9 -3
  89. package/package.json +1 -1
  90. package/src/__tests__/cms-query-keys.test.ts +57 -0
  91. package/src/plugins/api/index.ts +1 -0
  92. package/src/plugins/api/operation.ts +16 -3
  93. package/src/plugins/cms/__tests__/authorization.test.ts +652 -187
  94. package/src/plugins/cms/__tests__/client-runtime.browser.test.tsx +655 -0
  95. package/src/plugins/cms/__tests__/content-read.typecheck.ts +351 -0
  96. package/src/plugins/cms/__tests__/getters.test.ts +286 -0
  97. package/src/plugins/cms/api/getters.ts +270 -12
  98. package/src/plugins/cms/api/index.ts +10 -0
  99. package/src/plugins/cms/api/operations.ts +163 -75
  100. package/src/plugins/cms/api/plugin.ts +50 -12
  101. package/src/plugins/cms/api/query-key-defs.ts +29 -2
  102. package/src/plugins/cms/client/hooks/cms-hooks.tsx +400 -91
  103. package/src/plugins/cms/client/index.ts +9 -0
  104. package/src/plugins/cms/query-keys.ts +171 -16
  105. package/src/plugins/cms/schemas.ts +30 -0
  106. package/src/plugins/cms/types.ts +49 -2
  107. package/src/plugins/media/__tests__/identity-partition.test.tsx +26 -16
  108. package/dist/shared/stack.CAmVUwjV.d.cts +0 -764
  109. package/dist/shared/stack.CQK02ujt.d.ts +0 -764
  110. package/dist/shared/stack.D9x1fR5a.d.mts +0 -764
  111. package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
  112. package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
  113. 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(operationContentItem),
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
- const result = await getters.getAllContentItems(
928
- adapter,
929
- facts.contentType,
930
- input.query
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
- return operationContentItem(getters.serializeContentItemWithType(item));
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: GetContentItemInputSchema,
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
- const result = await listRecordsForRelation(
1525
+ validateOperationContentReadOptions(sourceContentType, input.query);
1526
+ const result = await getters.getContentItemsByRelationForType(
1530
1527
  adapter,
1531
1528
  sourceContentType,
1532
- input.query.field,
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(operationContentItem)
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 result = await listRecordsForRelation(
1717
+ const sourceContentType = await getContentTypeOrThrow(
1722
1718
  adapter,
1723
- await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
1724
- source.fieldName,
1725
- input.query.itemId,
1726
- input.query.limit,
1727
- input.query.offset
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(operationContentItem)
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(operationContentItem),
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
- const result = await getAllContentItems(
926
- adapter,
927
- facts.contentType,
928
- input.query
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
- return operationContentItem(serializeContentItemWithType(item));
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: GetContentItemInputSchema,
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
- const result = await listRecordsForRelation(
1523
+ validateOperationContentReadOptions(sourceContentType, input.query);
1524
+ const result = await getContentItemsByRelationForType(
1528
1525
  adapter,
1529
1526
  sourceContentType,
1530
- input.query.field,
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(operationContentItem)
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 result = await listRecordsForRelation(
1715
+ const sourceContentType = await getContentTypeOrThrow(
1720
1716
  adapter,
1721
- await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
1722
- source.fieldName,
1723
- input.query.itemId,
1724
- input.query.limit,
1725
- input.query.offset
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(operationContentItem)
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, { limit: 20, offset: 0 })
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, limit: 20, offset: 0 }),
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(config.maxPageSize);
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) => ctx.params)
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, { limit: 20, offset: 0 })
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, limit: 20, offset: 0 }),
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(config.maxPageSize);
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) => ctx.params)
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) => ({