@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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
OperationHttpError,
|
|
7
7
|
type OperationContext,
|
|
8
8
|
type OperationData,
|
|
9
|
+
type OperationCallSignature,
|
|
9
10
|
} from "@btst/stack/plugins/api";
|
|
10
11
|
import { z } from "zod";
|
|
11
12
|
import { formSchemaToZod } from "@workspace/ui/lib/schema-converter";
|
|
@@ -13,9 +14,13 @@ import { cmsPermissions } from "../permissions";
|
|
|
13
14
|
import {
|
|
14
15
|
createListContentQuerySchema,
|
|
15
16
|
DEFAULT_MAX_PAGE_SIZE,
|
|
17
|
+
contentReadOptionsSchema,
|
|
16
18
|
} from "../schemas";
|
|
17
19
|
import type {
|
|
18
20
|
CMSBackendHooks,
|
|
21
|
+
CMSReadOptions,
|
|
22
|
+
CMSContentFields,
|
|
23
|
+
CMSSelectedData,
|
|
19
24
|
CMSCreateOperationContext,
|
|
20
25
|
CMSDeleteOperationContext,
|
|
21
26
|
CMSOperationData,
|
|
@@ -32,6 +37,10 @@ import type {
|
|
|
32
37
|
import { slugify } from "../utils";
|
|
33
38
|
import {
|
|
34
39
|
getAllContentItems,
|
|
40
|
+
validateContentReadOptions,
|
|
41
|
+
CMSContentSelectionError,
|
|
42
|
+
applyContentReadOptions,
|
|
43
|
+
getContentItemsByRelationForType,
|
|
35
44
|
getContentItemBySlug,
|
|
36
45
|
serializeContentItemWithType,
|
|
37
46
|
serializeContentType,
|
|
@@ -66,16 +75,50 @@ function operationContentItem(item: SerializedContentItemWithType) {
|
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
function operationContentList(
|
|
69
|
-
result: Awaited<ReturnType<typeof getAllContentItems
|
|
78
|
+
result: Awaited<ReturnType<typeof getAllContentItems<undefined>>>,
|
|
79
|
+
options?: CMSReadOptions,
|
|
70
80
|
) {
|
|
71
81
|
return {
|
|
72
|
-
items: result.items.map(
|
|
82
|
+
items: result.items.map((item) =>
|
|
83
|
+
applyContentReadOptions(operationContentItem(item), options),
|
|
84
|
+
),
|
|
73
85
|
total: result.total,
|
|
74
86
|
...(result.limit !== undefined ? { limit: result.limit } : {}),
|
|
75
87
|
...(result.offset !== undefined ? { offset: result.offset } : {}),
|
|
76
88
|
};
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
type ContentReadOperationContent<TOptions> = Omit<
|
|
92
|
+
Pick<
|
|
93
|
+
ReturnType<typeof operationContentItem>,
|
|
94
|
+
"id" | "slug" | "parsedData" | "createdAt" | "updatedAt"
|
|
95
|
+
>,
|
|
96
|
+
"parsedData"
|
|
97
|
+
> & {
|
|
98
|
+
parsedData: TOptions extends {
|
|
99
|
+
fields?: infer TFields extends CMSContentFields;
|
|
100
|
+
}
|
|
101
|
+
? CMSSelectedData<Record<string, unknown>, TFields>
|
|
102
|
+
: ReturnType<typeof operationContentItem>["parsedData"];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
type ContentReadOperationItem<TOptions> = TOptions extends { view: "content" }
|
|
106
|
+
? ContentReadOperationContent<TOptions>
|
|
107
|
+
: TOptions extends { view?: undefined }
|
|
108
|
+
? ReturnType<typeof operationContentItem>
|
|
109
|
+
: "view" extends keyof TOptions
|
|
110
|
+
?
|
|
111
|
+
| ReturnType<typeof operationContentItem>
|
|
112
|
+
| ContentReadOperationContent<TOptions>
|
|
113
|
+
: ReturnType<typeof operationContentItem>;
|
|
114
|
+
|
|
115
|
+
type ContentReadOperationPage<TOptions> = DeepReadonly<{
|
|
116
|
+
items: ContentReadOperationItem<TOptions>[];
|
|
117
|
+
total: number;
|
|
118
|
+
limit: number;
|
|
119
|
+
offset: number;
|
|
120
|
+
}>;
|
|
121
|
+
|
|
79
122
|
const JSONValueSchema: z.ZodType<OperationData> = z.lazy(() =>
|
|
80
123
|
z.union([
|
|
81
124
|
z.string(),
|
|
@@ -114,7 +157,9 @@ export const CMSUpdateContentItemBodySchema = z.object({
|
|
|
114
157
|
|
|
115
158
|
const ListContentTypesInputSchema = EmptyInputSchema;
|
|
116
159
|
const GetContentTypeInputSchema = CMSContentTypeParamsSchema;
|
|
117
|
-
const GetContentItemInputSchema = CMSContentItemParamsSchema
|
|
160
|
+
const GetContentItemInputSchema = CMSContentItemParamsSchema.extend(
|
|
161
|
+
contentReadOptionsSchema.shape,
|
|
162
|
+
);
|
|
118
163
|
const CreateContentItemInputSchema = z.object({
|
|
119
164
|
typeSlug: z.string().min(1),
|
|
120
165
|
body: CMSCreateContentItemBodySchema,
|
|
@@ -161,6 +206,25 @@ export class CMSOperationError extends OperationHttpError {
|
|
|
161
206
|
}
|
|
162
207
|
}
|
|
163
208
|
|
|
209
|
+
function validateOperationContentReadOptions(
|
|
210
|
+
contentType: Pick<ContentType, "jsonSchema"> | undefined,
|
|
211
|
+
options: CMSReadOptions,
|
|
212
|
+
) {
|
|
213
|
+
try {
|
|
214
|
+
validateContentReadOptions(contentType, options);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error instanceof CMSContentSelectionError) {
|
|
217
|
+
throw new CMSOperationError(
|
|
218
|
+
400,
|
|
219
|
+
error.message,
|
|
220
|
+
"INVALID_CONTENT_FIELDS",
|
|
221
|
+
[{ path: ["fields"], message: error.message }],
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
164
228
|
function normalizeOperationError(error: unknown, fallback: string): Error {
|
|
165
229
|
if (error instanceof Error) return error;
|
|
166
230
|
return new Error(typeof error === "string" ? error : fallback, {
|
|
@@ -930,49 +994,6 @@ async function deriveInverseSourceAuthorization(
|
|
|
930
994
|
};
|
|
931
995
|
}
|
|
932
996
|
|
|
933
|
-
async function listRecordsForRelation(
|
|
934
|
-
adapter: Adapter,
|
|
935
|
-
contentType: ContentType,
|
|
936
|
-
fieldName: string,
|
|
937
|
-
targetId: string,
|
|
938
|
-
limit: number,
|
|
939
|
-
offset: number,
|
|
940
|
-
) {
|
|
941
|
-
const relations = await adapter.findMany<ContentRelation>({
|
|
942
|
-
model: "contentRelation",
|
|
943
|
-
where: [
|
|
944
|
-
{ field: "targetId", value: targetId, operator: "eq" as const },
|
|
945
|
-
{ field: "fieldName", value: fieldName, operator: "eq" as const },
|
|
946
|
-
],
|
|
947
|
-
});
|
|
948
|
-
const ids = [...new Set(relations.map((relation) => relation.sourceId))];
|
|
949
|
-
const items: ContentItemWithType[] = [];
|
|
950
|
-
for (const id of ids) {
|
|
951
|
-
const item = await adapter.findOne<ContentItemWithType>({
|
|
952
|
-
model: "contentItem",
|
|
953
|
-
where: [
|
|
954
|
-
{ field: "id", value: id, operator: "eq" as const },
|
|
955
|
-
{
|
|
956
|
-
field: "contentTypeId",
|
|
957
|
-
value: contentType.id,
|
|
958
|
-
operator: "eq" as const,
|
|
959
|
-
},
|
|
960
|
-
],
|
|
961
|
-
join: { contentType: true },
|
|
962
|
-
});
|
|
963
|
-
if (item) items.push(item);
|
|
964
|
-
}
|
|
965
|
-
items.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
966
|
-
return {
|
|
967
|
-
items: items
|
|
968
|
-
.slice(offset, offset + limit)
|
|
969
|
-
.map(serializeContentItemWithType),
|
|
970
|
-
total: items.length,
|
|
971
|
-
limit,
|
|
972
|
-
offset,
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
|
|
976
997
|
type EnsureSynced = () => Promise<void>;
|
|
977
998
|
type GetContentTypesWithCounts = () => Promise<
|
|
978
999
|
Array<ReturnType<typeof serializeContentType> & { itemCount: number }>
|
|
@@ -1214,7 +1235,8 @@ export function createCMSOperations(
|
|
|
1214
1235
|
typeSlug: z.string().min(1),
|
|
1215
1236
|
query: z
|
|
1216
1237
|
.object({ field: z.string().min(1), targetId: z.string().min(1) })
|
|
1217
|
-
.merge(paginationSchema)
|
|
1238
|
+
.merge(paginationSchema)
|
|
1239
|
+
.merge(contentReadOptionsSchema),
|
|
1218
1240
|
});
|
|
1219
1241
|
const GetInverseRelationsInputSchema = z.object({
|
|
1220
1242
|
slug: z.string().min(1),
|
|
@@ -1228,7 +1250,8 @@ export function createCMSOperations(
|
|
|
1228
1250
|
itemId: z.string().min(1),
|
|
1229
1251
|
fieldName: z.string().min(1),
|
|
1230
1252
|
})
|
|
1231
|
-
.merge(paginationSchema)
|
|
1253
|
+
.merge(paginationSchema)
|
|
1254
|
+
.merge(contentReadOptionsSchema),
|
|
1232
1255
|
});
|
|
1233
1256
|
|
|
1234
1257
|
const notifyError = async (
|
|
@@ -1338,11 +1361,17 @@ export function createCMSOperations(
|
|
|
1338
1361
|
cmsPermissions.contentType.read({ contentType: facts.contentType }),
|
|
1339
1362
|
],
|
|
1340
1363
|
execute: async ({ input, facts }) => {
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1364
|
+
if (input.query.fields !== undefined) {
|
|
1365
|
+
validateOperationContentReadOptions(
|
|
1366
|
+
await getContentTypeOrThrow(adapter, ensureSynced, facts.contentType),
|
|
1367
|
+
input.query,
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
const result = await getAllContentItems(adapter, facts.contentType, {
|
|
1371
|
+
...input.query,
|
|
1372
|
+
view: undefined,
|
|
1373
|
+
fields: undefined,
|
|
1374
|
+
});
|
|
1346
1375
|
if (facts.scope === "record") {
|
|
1347
1376
|
if (!facts.recordId) {
|
|
1348
1377
|
if (result.items.length > 0) throw staleRecordError();
|
|
@@ -1355,7 +1384,7 @@ export function createCMSOperations(
|
|
|
1355
1384
|
) {
|
|
1356
1385
|
throw staleRecordError();
|
|
1357
1386
|
}
|
|
1358
|
-
return operationContentList(result);
|
|
1387
|
+
return operationContentList(result, input.query);
|
|
1359
1388
|
}
|
|
1360
1389
|
if (result.items.length > 1) throw staleRecordError();
|
|
1361
1390
|
if (result.items.length === 0) {
|
|
@@ -1370,7 +1399,7 @@ export function createCMSOperations(
|
|
|
1370
1399
|
assertRecordFacts(result.items[0]!, facts);
|
|
1371
1400
|
}
|
|
1372
1401
|
}
|
|
1373
|
-
return operationContentList(result);
|
|
1402
|
+
return operationContentList(result, input.query);
|
|
1374
1403
|
},
|
|
1375
1404
|
onError: ({ error, ...context }) =>
|
|
1376
1405
|
notifyError(
|
|
@@ -1390,7 +1419,7 @@ export function createCMSOperations(
|
|
|
1390
1419
|
additionalPermissions: ({ facts }) => [
|
|
1391
1420
|
cmsPermissions.contentType.read({ contentType: facts.contentType }),
|
|
1392
1421
|
],
|
|
1393
|
-
execute: async ({ facts }) => {
|
|
1422
|
+
execute: async ({ input, facts }) => {
|
|
1394
1423
|
const item = await getRecordOrThrow(
|
|
1395
1424
|
adapter,
|
|
1396
1425
|
ensureSynced,
|
|
@@ -1398,7 +1427,11 @@ export function createCMSOperations(
|
|
|
1398
1427
|
facts.recordId ?? "",
|
|
1399
1428
|
);
|
|
1400
1429
|
assertRecordFacts(item, facts);
|
|
1401
|
-
|
|
1430
|
+
validateOperationContentReadOptions(item.contentType, input);
|
|
1431
|
+
return applyContentReadOptions(
|
|
1432
|
+
operationContentItem(serializeContentItemWithType(item)),
|
|
1433
|
+
input,
|
|
1434
|
+
);
|
|
1402
1435
|
},
|
|
1403
1436
|
onError: ({ error, ...context }) =>
|
|
1404
1437
|
notifyError(
|
|
@@ -1866,7 +1899,7 @@ export function createCMSOperations(
|
|
|
1866
1899
|
});
|
|
1867
1900
|
|
|
1868
1901
|
const getContentItemPopulated = defineOperation({
|
|
1869
|
-
input:
|
|
1902
|
+
input: CMSContentItemParamsSchema,
|
|
1870
1903
|
permission: cmsPermissions.record.read,
|
|
1871
1904
|
facts: async ({ input }) =>
|
|
1872
1905
|
recordFacts(
|
|
@@ -1973,17 +2006,17 @@ export function createCMSOperations(
|
|
|
1973
2006
|
input.query.field,
|
|
1974
2007
|
input.query.targetId,
|
|
1975
2008
|
);
|
|
1976
|
-
|
|
2009
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
2010
|
+
const result = await getContentItemsByRelationForType(
|
|
1977
2011
|
adapter,
|
|
1978
2012
|
sourceContentType,
|
|
1979
|
-
input.query
|
|
1980
|
-
input.query.targetId,
|
|
1981
|
-
input.query.limit,
|
|
1982
|
-
input.query.offset,
|
|
2013
|
+
input.query,
|
|
1983
2014
|
);
|
|
1984
2015
|
return {
|
|
1985
2016
|
...result,
|
|
1986
|
-
items: result.items.map(
|
|
2017
|
+
items: result.items.map((item) =>
|
|
2018
|
+
applyContentReadOptions(operationContentItem(item), input.query),
|
|
2019
|
+
),
|
|
1987
2020
|
};
|
|
1988
2021
|
},
|
|
1989
2022
|
onError: ({ error, ...context }) =>
|
|
@@ -2178,17 +2211,27 @@ export function createCMSOperations(
|
|
|
2178
2211
|
input.slug,
|
|
2179
2212
|
input.query.itemId,
|
|
2180
2213
|
);
|
|
2181
|
-
const
|
|
2214
|
+
const sourceContentType = await getContentTypeOrThrow(
|
|
2182
2215
|
adapter,
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2216
|
+
ensureSynced,
|
|
2217
|
+
facts.contentType,
|
|
2218
|
+
);
|
|
2219
|
+
validateOperationContentReadOptions(sourceContentType, input.query);
|
|
2220
|
+
const result = await getContentItemsByRelationForType(
|
|
2221
|
+
adapter,
|
|
2222
|
+
sourceContentType,
|
|
2223
|
+
{
|
|
2224
|
+
field: source.fieldName,
|
|
2225
|
+
targetId: input.query.itemId,
|
|
2226
|
+
limit: input.query.limit,
|
|
2227
|
+
offset: input.query.offset,
|
|
2228
|
+
},
|
|
2188
2229
|
);
|
|
2189
2230
|
return {
|
|
2190
2231
|
...result,
|
|
2191
|
-
items: result.items.map(
|
|
2232
|
+
items: result.items.map((item) =>
|
|
2233
|
+
applyContentReadOptions(operationContentItem(item), input.query),
|
|
2234
|
+
),
|
|
2192
2235
|
};
|
|
2193
2236
|
} finally {
|
|
2194
2237
|
inverseTargetAuthorizationSnapshots.delete(input as object);
|
|
@@ -2205,14 +2248,59 @@ export function createCMSOperations(
|
|
|
2205
2248
|
return {
|
|
2206
2249
|
listContentTypes,
|
|
2207
2250
|
getContentTypeBySlug,
|
|
2208
|
-
listContentItems
|
|
2209
|
-
|
|
2251
|
+
listContentItems: listContentItems as typeof listContentItems &
|
|
2252
|
+
OperationCallSignature<
|
|
2253
|
+
<
|
|
2254
|
+
const TInput extends DeepReadonly<
|
|
2255
|
+
z.input<typeof ListContentItemsInputSchema>
|
|
2256
|
+
>,
|
|
2257
|
+
>(
|
|
2258
|
+
input: TInput,
|
|
2259
|
+
) => Promise<
|
|
2260
|
+
DeepReadonly<{
|
|
2261
|
+
items: ContentReadOperationItem<TInput["query"]>[];
|
|
2262
|
+
total: number;
|
|
2263
|
+
limit: number;
|
|
2264
|
+
offset: number;
|
|
2265
|
+
}>
|
|
2266
|
+
>
|
|
2267
|
+
>,
|
|
2268
|
+
getContentItem: getContentItem as typeof getContentItem &
|
|
2269
|
+
OperationCallSignature<
|
|
2270
|
+
<
|
|
2271
|
+
const TInput extends DeepReadonly<
|
|
2272
|
+
z.input<typeof GetContentItemInputSchema>
|
|
2273
|
+
>,
|
|
2274
|
+
>(
|
|
2275
|
+
input: TInput,
|
|
2276
|
+
) => Promise<DeepReadonly<ContentReadOperationItem<TInput>>>
|
|
2277
|
+
>,
|
|
2210
2278
|
createContentItem,
|
|
2211
2279
|
updateContentItem,
|
|
2212
2280
|
deleteContentItem,
|
|
2213
2281
|
getContentItemPopulated,
|
|
2214
|
-
listContentByRelation
|
|
2282
|
+
listContentByRelation:
|
|
2283
|
+
listContentByRelation as typeof listContentByRelation &
|
|
2284
|
+
OperationCallSignature<
|
|
2285
|
+
<
|
|
2286
|
+
const TInput extends DeepReadonly<
|
|
2287
|
+
z.input<typeof ListContentByRelationInputSchema>
|
|
2288
|
+
>,
|
|
2289
|
+
>(
|
|
2290
|
+
input: TInput,
|
|
2291
|
+
) => Promise<ContentReadOperationPage<TInput["query"]>>
|
|
2292
|
+
>,
|
|
2215
2293
|
getInverseRelations,
|
|
2216
|
-
listInverseRelationItems
|
|
2294
|
+
listInverseRelationItems:
|
|
2295
|
+
listInverseRelationItems as typeof listInverseRelationItems &
|
|
2296
|
+
OperationCallSignature<
|
|
2297
|
+
<
|
|
2298
|
+
const TInput extends DeepReadonly<
|
|
2299
|
+
z.input<typeof ListInverseRelationItemsInputSchema>
|
|
2300
|
+
>,
|
|
2301
|
+
>(
|
|
2302
|
+
input: TInput,
|
|
2303
|
+
) => Promise<ContentReadOperationPage<TInput["query"]>>
|
|
2304
|
+
>,
|
|
2217
2305
|
} as const;
|
|
2218
2306
|
}
|
|
@@ -7,8 +7,15 @@ import { cmsSchema as dbSchema } from "../db";
|
|
|
7
7
|
import {
|
|
8
8
|
createListContentQuerySchema,
|
|
9
9
|
DEFAULT_MAX_PAGE_SIZE,
|
|
10
|
+
contentReadOptionsSchema,
|
|
11
|
+
contentReadQuerySchema,
|
|
10
12
|
} from "../schemas";
|
|
11
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
CMSBackendConfig,
|
|
15
|
+
CMSBackendHooks,
|
|
16
|
+
ContentType,
|
|
17
|
+
CMSReadOptions,
|
|
18
|
+
} from "../types";
|
|
12
19
|
export { CMS_LIFECYCLE_HOOK_MIGRATIONS } from "./lifecycle-migrations";
|
|
13
20
|
import {
|
|
14
21
|
getAllContentItems,
|
|
@@ -54,12 +61,16 @@ interface CMSPrefetchForRoute {
|
|
|
54
61
|
(
|
|
55
62
|
key: "contentList",
|
|
56
63
|
qc: QueryClient,
|
|
57
|
-
params: {
|
|
64
|
+
params: {
|
|
65
|
+
typeSlug: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
search?: string;
|
|
68
|
+
} & CMSReadOptions,
|
|
58
69
|
): Promise<void>;
|
|
59
70
|
(
|
|
60
71
|
key: "editContent",
|
|
61
72
|
qc: QueryClient,
|
|
62
|
-
params: { typeSlug: string; id: string },
|
|
73
|
+
params: { typeSlug: string; id: string } & CMSReadOptions,
|
|
63
74
|
): Promise<void>;
|
|
64
75
|
}
|
|
65
76
|
|
|
@@ -187,7 +198,12 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
187
198
|
async function prefetchForRoute(
|
|
188
199
|
key: CMSRouteKey,
|
|
189
200
|
queryClient: QueryClient,
|
|
190
|
-
params?:
|
|
201
|
+
params?: {
|
|
202
|
+
typeSlug?: string;
|
|
203
|
+
id?: string;
|
|
204
|
+
limit?: number;
|
|
205
|
+
search?: string;
|
|
206
|
+
} & CMSReadOptions,
|
|
191
207
|
): Promise<void> {
|
|
192
208
|
await ensureSynced(adapter);
|
|
193
209
|
switch (key) {
|
|
@@ -200,13 +216,21 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
200
216
|
break;
|
|
201
217
|
case "contentList": {
|
|
202
218
|
const typeSlug = params?.typeSlug ?? "";
|
|
219
|
+
const options = createListContentQuerySchema(
|
|
220
|
+
config.maxPageSize,
|
|
221
|
+
).parse({
|
|
222
|
+
limit: params?.limit,
|
|
223
|
+
search: params?.search,
|
|
224
|
+
view: params?.view,
|
|
225
|
+
fields: params?.fields,
|
|
226
|
+
});
|
|
203
227
|
const [contentTypes, contentItems] = await Promise.all([
|
|
204
228
|
getContentTypesWithCounts(adapter),
|
|
205
|
-
getAllContentItems(adapter, typeSlug,
|
|
229
|
+
getAllContentItems(adapter, typeSlug, options),
|
|
206
230
|
]);
|
|
207
231
|
queryClient.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
208
232
|
queryClient.setQueryData(
|
|
209
|
-
CMS_QUERY_KEYS.contentList({ typeSlug,
|
|
233
|
+
CMS_QUERY_KEYS.contentList({ typeSlug, ...options }),
|
|
210
234
|
{
|
|
211
235
|
pages: [
|
|
212
236
|
{
|
|
@@ -224,14 +248,20 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
224
248
|
case "editContent": {
|
|
225
249
|
const typeSlug = params?.typeSlug ?? "";
|
|
226
250
|
const id = params?.id ?? "";
|
|
251
|
+
const options = contentReadOptionsSchema.parse({
|
|
252
|
+
view: params?.view,
|
|
253
|
+
fields: params?.fields,
|
|
254
|
+
});
|
|
227
255
|
const [contentTypes, item] = await Promise.all([
|
|
228
256
|
getContentTypesWithCounts(adapter),
|
|
229
|
-
id
|
|
257
|
+
id
|
|
258
|
+
? getContentItemById(adapter, id, options)
|
|
259
|
+
: Promise.resolve(null),
|
|
230
260
|
]);
|
|
231
261
|
queryClient.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
|
|
232
262
|
if (id) {
|
|
233
263
|
queryClient.setQueryData(
|
|
234
|
-
CMS_QUERY_KEYS.contentDetail(typeSlug, id),
|
|
264
|
+
CMS_QUERY_KEYS.contentDetail(typeSlug, id, options),
|
|
235
265
|
item,
|
|
236
266
|
);
|
|
237
267
|
}
|
|
@@ -259,7 +289,9 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
259
289
|
}),
|
|
260
290
|
|
|
261
291
|
routes: (_adapter: Adapter, _context, operations) => {
|
|
262
|
-
const listQuerySchema = createListContentQuerySchema(
|
|
292
|
+
const listQuerySchema = createListContentQuerySchema(
|
|
293
|
+
config.maxPageSize,
|
|
294
|
+
).extend(contentReadQuerySchema.shape);
|
|
263
295
|
const paginationSchema = z.object({
|
|
264
296
|
limit: z.coerce
|
|
265
297
|
.number()
|
|
@@ -301,9 +333,13 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
301
333
|
{
|
|
302
334
|
method: "GET",
|
|
303
335
|
params: CMSContentItemParamsSchema,
|
|
336
|
+
query: contentReadQuerySchema,
|
|
304
337
|
requireRequest: true,
|
|
305
338
|
},
|
|
306
|
-
operations.getContentItem.route((ctx) =>
|
|
339
|
+
operations.getContentItem.route((ctx) => ({
|
|
340
|
+
...ctx.params,
|
|
341
|
+
...ctx.query,
|
|
342
|
+
})),
|
|
307
343
|
);
|
|
308
344
|
const createContentItem = createEndpoint(
|
|
309
345
|
"/content/:typeSlug",
|
|
@@ -356,7 +392,8 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
356
392
|
params: z.object({ typeSlug: z.string().min(1) }),
|
|
357
393
|
query: z
|
|
358
394
|
.object({ field: z.string(), targetId: z.string() })
|
|
359
|
-
.merge(paginationSchema)
|
|
395
|
+
.merge(paginationSchema)
|
|
396
|
+
.merge(contentReadQuerySchema),
|
|
360
397
|
requireRequest: true,
|
|
361
398
|
},
|
|
362
399
|
operations.listContentByRelation.route((ctx) => ({
|
|
@@ -387,7 +424,8 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
|
|
|
387
424
|
}),
|
|
388
425
|
query: z
|
|
389
426
|
.object({ itemId: z.string(), fieldName: z.string() })
|
|
390
|
-
.merge(paginationSchema)
|
|
427
|
+
.merge(paginationSchema)
|
|
428
|
+
.merge(contentReadQuerySchema),
|
|
391
429
|
requireRequest: true,
|
|
392
430
|
},
|
|
393
431
|
operations.listInverseRelationItems.route((ctx) => ({
|
|
@@ -3,12 +3,34 @@
|
|
|
3
3
|
* Shared between query-keys.ts (HTTP path) and prefetchForRoute (DB path)
|
|
4
4
|
* to prevent key drift between SSR loaders and SSG prefetching.
|
|
5
5
|
*/
|
|
6
|
+
import type { CMSReadOptions } from "../types";
|
|
7
|
+
|
|
8
|
+
import { normalizeContentFields } from "../schemas";
|
|
9
|
+
|
|
10
|
+
/** Shared output discriminator for HTTP resources and database prefetch. */
|
|
11
|
+
export function contentReadDiscriminator(options?: CMSReadOptions) {
|
|
12
|
+
return {
|
|
13
|
+
...(options?.view ? { view: options.view } : {}),
|
|
14
|
+
...(options?.fields !== undefined
|
|
15
|
+
? { fields: normalizeContentFields(options.fields) }
|
|
16
|
+
: {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Preserve default detail identities while separating every output shape. */
|
|
21
|
+
export function contentReadKey(options?: CMSReadOptions) {
|
|
22
|
+
return options?.view || options?.fields !== undefined
|
|
23
|
+
? [contentReadDiscriminator(options)]
|
|
24
|
+
: [];
|
|
25
|
+
}
|
|
6
26
|
|
|
7
27
|
export interface ContentListDiscriminator {
|
|
8
28
|
typeSlug: string;
|
|
9
29
|
limit: number;
|
|
10
30
|
offset: number;
|
|
11
31
|
search: string | undefined;
|
|
32
|
+
view?: "content";
|
|
33
|
+
fields?: readonly string[];
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
/**
|
|
@@ -22,8 +44,11 @@ export function contentListDiscriminator(params: {
|
|
|
22
44
|
limit?: number;
|
|
23
45
|
offset?: number;
|
|
24
46
|
search?: string;
|
|
47
|
+
view?: "content";
|
|
48
|
+
fields?: readonly string[];
|
|
25
49
|
}): ContentListDiscriminator {
|
|
26
50
|
return {
|
|
51
|
+
...contentReadDiscriminator(params),
|
|
27
52
|
typeSlug: params.typeSlug,
|
|
28
53
|
limit: params.limit ?? 20,
|
|
29
54
|
offset: params.offset ?? 0,
|
|
@@ -51,12 +76,14 @@ export const CMS_QUERY_KEYS = {
|
|
|
51
76
|
limit?: number;
|
|
52
77
|
offset?: number;
|
|
53
78
|
search?: string;
|
|
79
|
+
view?: "content";
|
|
80
|
+
fields?: readonly string[];
|
|
54
81
|
}) => ["cmsContent", "list", contentListDiscriminator(params)] as const,
|
|
55
82
|
|
|
56
83
|
/**
|
|
57
84
|
* Key for the cmsContent.detail(typeSlug, id) query.
|
|
58
85
|
* Full key: ["cmsContent", "detail", typeSlug, id]
|
|
59
86
|
*/
|
|
60
|
-
contentDetail: (typeSlug: string, id: string) =>
|
|
61
|
-
["cmsContent", "detail", typeSlug, id] as const,
|
|
87
|
+
contentDetail: (typeSlug: string, id: string, options?: CMSReadOptions) =>
|
|
88
|
+
["cmsContent", "detail", typeSlug, id, ...contentReadKey(options)] as const,
|
|
62
89
|
};
|