@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
|
@@ -1,14 +1,93 @@
|
|
|
1
1
|
import type { DBAdapter as Adapter } from "@btst/db";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_MAX_PAGE_SIZE,
|
|
4
|
+
contentReadOptionsSchema,
|
|
5
|
+
normalizeContentFields,
|
|
6
|
+
} from "../schemas";
|
|
3
7
|
import type {
|
|
4
8
|
ContentType,
|
|
5
9
|
ContentItem,
|
|
6
10
|
ContentItemWithType,
|
|
11
|
+
ContentRelation,
|
|
7
12
|
SerializedContentType,
|
|
8
13
|
SerializedContentItem,
|
|
9
14
|
SerializedContentItemWithType,
|
|
15
|
+
CMSReadOptions,
|
|
16
|
+
CMSContentFields,
|
|
17
|
+
CMSSelectedData,
|
|
18
|
+
CMSReadView,
|
|
19
|
+
CMSContentItemResult,
|
|
10
20
|
} from "../types";
|
|
11
21
|
|
|
22
|
+
/** Invalid CMS content-field selection supplied to a lower-level read. */
|
|
23
|
+
export class CMSContentSelectionError extends Error {}
|
|
24
|
+
|
|
25
|
+
/** @internal Validate against schema properties before reading/projecting a page. */
|
|
26
|
+
export function validateContentReadOptions(
|
|
27
|
+
contentType: Pick<ContentType, "jsonSchema"> | undefined,
|
|
28
|
+
options?: CMSReadOptions,
|
|
29
|
+
): void {
|
|
30
|
+
contentReadOptionsSchema.parse(options ?? {});
|
|
31
|
+
if (options?.fields === undefined) return;
|
|
32
|
+
if (options.view !== "content") {
|
|
33
|
+
throw new CMSContentSelectionError("fields requires view: content");
|
|
34
|
+
}
|
|
35
|
+
const properties = contentType
|
|
36
|
+
? JSON.parse(contentType.jsonSchema).properties
|
|
37
|
+
: undefined;
|
|
38
|
+
for (const field of options.fields) {
|
|
39
|
+
if (!properties || !Object.hasOwn(properties, field)) {
|
|
40
|
+
throw new CMSContentSelectionError(
|
|
41
|
+
`Unknown top-level content field: ${field}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Apply output options only after filtering and request authorization checks. */
|
|
48
|
+
export function applyContentReadOptions<
|
|
49
|
+
TItem extends Pick<
|
|
50
|
+
SerializedContentItemWithType,
|
|
51
|
+
"id" | "slug" | "parsedData" | "createdAt" | "updatedAt"
|
|
52
|
+
>,
|
|
53
|
+
TView extends CMSReadView = undefined,
|
|
54
|
+
const TFields extends CMSContentFields = undefined,
|
|
55
|
+
>(
|
|
56
|
+
item: TItem,
|
|
57
|
+
options?: CMSReadOptions<TView, TFields>,
|
|
58
|
+
): TView extends "content"
|
|
59
|
+
? Omit<
|
|
60
|
+
Pick<TItem, "id" | "slug" | "parsedData" | "createdAt" | "updatedAt">,
|
|
61
|
+
"parsedData"
|
|
62
|
+
> & { parsedData: CMSSelectedData<TItem["parsedData"], TFields> }
|
|
63
|
+
: TItem {
|
|
64
|
+
const result =
|
|
65
|
+
options?.view === "content"
|
|
66
|
+
? {
|
|
67
|
+
id: item.id,
|
|
68
|
+
slug: item.slug,
|
|
69
|
+
parsedData: options.fields
|
|
70
|
+
? Object.fromEntries(
|
|
71
|
+
normalizeContentFields(options.fields)!
|
|
72
|
+
.filter((field) => Object.hasOwn(item.parsedData, field))
|
|
73
|
+
.map((field) => [
|
|
74
|
+
field,
|
|
75
|
+
(item.parsedData as Record<string, unknown>)[field],
|
|
76
|
+
]),
|
|
77
|
+
)
|
|
78
|
+
: item.parsedData,
|
|
79
|
+
createdAt: item.createdAt,
|
|
80
|
+
updatedAt: item.updatedAt,
|
|
81
|
+
}
|
|
82
|
+
: item;
|
|
83
|
+
return result as TView extends "content"
|
|
84
|
+
? Omit<
|
|
85
|
+
Pick<TItem, "id" | "slug" | "parsedData" | "createdAt" | "updatedAt">,
|
|
86
|
+
"parsedData"
|
|
87
|
+
> & { parsedData: CMSSelectedData<TItem["parsedData"], TFields> }
|
|
88
|
+
: TItem;
|
|
89
|
+
}
|
|
90
|
+
|
|
12
91
|
/**
|
|
13
92
|
* Serialize a ContentType for SSR/SSG use (convert dates to strings).
|
|
14
93
|
* Applies lazy migration for legacy schemas (version 1 → 2).
|
|
@@ -145,12 +224,20 @@ function contentItemMatchesSearch(
|
|
|
145
224
|
* @param params - Optional filter/pagination parameters. `search` matches
|
|
146
225
|
* case-insensitively against item slugs and string values in the item data.
|
|
147
226
|
*/
|
|
148
|
-
export async function getAllContentItems
|
|
227
|
+
export async function getAllContentItems<
|
|
228
|
+
TView extends CMSReadView = undefined,
|
|
229
|
+
const TFields extends CMSContentFields = undefined,
|
|
230
|
+
>(
|
|
149
231
|
adapter: Adapter,
|
|
150
232
|
contentTypeSlug: string,
|
|
151
|
-
params?: {
|
|
233
|
+
params?: {
|
|
234
|
+
slug?: string;
|
|
235
|
+
limit?: number;
|
|
236
|
+
offset?: number;
|
|
237
|
+
search?: string;
|
|
238
|
+
} & CMSReadOptions<TView, TFields>,
|
|
152
239
|
): Promise<{
|
|
153
|
-
items:
|
|
240
|
+
items: CMSContentItemResult<Record<string, unknown>, TView, TFields>[];
|
|
154
241
|
total: number;
|
|
155
242
|
limit?: number;
|
|
156
243
|
offset?: number;
|
|
@@ -175,6 +262,7 @@ export async function getAllContentItems(
|
|
|
175
262
|
};
|
|
176
263
|
}
|
|
177
264
|
|
|
265
|
+
validateContentReadOptions(contentType, params);
|
|
178
266
|
const whereConditions: Array<{
|
|
179
267
|
field: string;
|
|
180
268
|
value: string;
|
|
@@ -237,7 +325,7 @@ export async function getAllContentItems(
|
|
|
237
325
|
limit !== undefined ? offset + limit : undefined,
|
|
238
326
|
);
|
|
239
327
|
return {
|
|
240
|
-
items: result,
|
|
328
|
+
items: result.map((item) => applyContentReadOptions(item, params)),
|
|
241
329
|
total,
|
|
242
330
|
limit: params?.limit,
|
|
243
331
|
offset: params?.offset,
|
|
@@ -245,7 +333,7 @@ export async function getAllContentItems(
|
|
|
245
333
|
}
|
|
246
334
|
|
|
247
335
|
return {
|
|
248
|
-
items: result,
|
|
336
|
+
items: result.map((item) => applyContentReadOptions(item, params)),
|
|
249
337
|
total: dbTotal ?? result.length,
|
|
250
338
|
limit: params?.limit,
|
|
251
339
|
offset: params?.offset,
|
|
@@ -263,17 +351,26 @@ export async function getAllContentItems(
|
|
|
263
351
|
* @param adapter - The database adapter
|
|
264
352
|
* @param id - The content item ID (UUID)
|
|
265
353
|
*/
|
|
266
|
-
export async function getContentItemById
|
|
354
|
+
export async function getContentItemById<
|
|
355
|
+
TView extends CMSReadView = undefined,
|
|
356
|
+
const TFields extends CMSContentFields = undefined,
|
|
357
|
+
>(
|
|
267
358
|
adapter: Adapter,
|
|
268
359
|
id: string,
|
|
269
|
-
|
|
360
|
+
options?: CMSReadOptions<TView, TFields>,
|
|
361
|
+
): Promise<CMSContentItemResult<
|
|
362
|
+
Record<string, unknown>,
|
|
363
|
+
TView,
|
|
364
|
+
TFields
|
|
365
|
+
> | null> {
|
|
270
366
|
const item = await adapter.findOne<ContentItemWithType>({
|
|
271
367
|
model: "contentItem",
|
|
272
368
|
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
273
369
|
join: { contentType: true },
|
|
274
370
|
});
|
|
275
371
|
if (!item) return null;
|
|
276
|
-
|
|
372
|
+
validateContentReadOptions(item.contentType, options);
|
|
373
|
+
return applyContentReadOptions(serializeContentItemWithType(item), options);
|
|
277
374
|
}
|
|
278
375
|
|
|
279
376
|
/**
|
|
@@ -288,11 +385,19 @@ export async function getContentItemById(
|
|
|
288
385
|
* @param contentTypeSlug - The slug of the content type
|
|
289
386
|
* @param slug - The slug of the content item
|
|
290
387
|
*/
|
|
291
|
-
export async function getContentItemBySlug
|
|
388
|
+
export async function getContentItemBySlug<
|
|
389
|
+
TView extends CMSReadView = undefined,
|
|
390
|
+
const TFields extends CMSContentFields = undefined,
|
|
391
|
+
>(
|
|
292
392
|
adapter: Adapter,
|
|
293
393
|
contentTypeSlug: string,
|
|
294
394
|
slug: string,
|
|
295
|
-
|
|
395
|
+
options?: CMSReadOptions<TView, TFields>,
|
|
396
|
+
): Promise<CMSContentItemResult<
|
|
397
|
+
Record<string, unknown>,
|
|
398
|
+
TView,
|
|
399
|
+
TFields
|
|
400
|
+
> | null> {
|
|
296
401
|
const contentType = await adapter.findOne<ContentType>({
|
|
297
402
|
model: "contentType",
|
|
298
403
|
where: [
|
|
@@ -307,6 +412,7 @@ export async function getContentItemBySlug(
|
|
|
307
412
|
if (!contentType) {
|
|
308
413
|
return null;
|
|
309
414
|
}
|
|
415
|
+
validateContentReadOptions(contentType, options);
|
|
310
416
|
|
|
311
417
|
const item = await adapter.findOne<ContentItemWithType>({
|
|
312
418
|
model: "contentItem",
|
|
@@ -325,5 +431,157 @@ export async function getContentItemBySlug(
|
|
|
325
431
|
return null;
|
|
326
432
|
}
|
|
327
433
|
|
|
328
|
-
return serializeContentItemWithType(item);
|
|
434
|
+
return applyContentReadOptions(serializeContentItemWithType(item), options);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Parameters for a page of items referencing one relation target. */
|
|
438
|
+
export interface ContentItemsByRelationParams<
|
|
439
|
+
TView extends CMSReadView = CMSReadView,
|
|
440
|
+
TFields extends CMSContentFields = CMSContentFields,
|
|
441
|
+
> extends CMSReadOptions<TView, TFields> {
|
|
442
|
+
/** Declared top-level relation field on the source content type. */
|
|
443
|
+
field: string;
|
|
444
|
+
/** ID of the item referenced by the relation. */
|
|
445
|
+
targetId: string;
|
|
446
|
+
/** Number of items to return (default: 20). */
|
|
447
|
+
limit?: number;
|
|
448
|
+
/** Number of matching items to skip (default: 0). */
|
|
449
|
+
offset?: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** A page of unique related items, ordered by creation time then ID descending. */
|
|
453
|
+
export interface ContentItemsByRelationResult<
|
|
454
|
+
TView extends CMSReadView = undefined,
|
|
455
|
+
TFields extends CMSContentFields = undefined,
|
|
456
|
+
> {
|
|
457
|
+
items: CMSContentItemResult<Record<string, unknown>, TView, TFields>[];
|
|
458
|
+
total: number;
|
|
459
|
+
limit: number;
|
|
460
|
+
offset: number;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Retrieve source items whose relation field references a target item.
|
|
465
|
+
* Relation rows are scanned completely, metadata is read in batches, and full
|
|
466
|
+
* content is fetched only for the requested page. Internal scans grow with the
|
|
467
|
+
* relation count; this does not promise constant query counts or native joins.
|
|
468
|
+
* Set `view: "content"` for identity, parsed content and serialized timestamps
|
|
469
|
+
* without the editor envelope. Omitting it preserves the full item response.
|
|
470
|
+
*
|
|
471
|
+
* @remarks This lower-level read does not authorize requests, validate relation
|
|
472
|
+
* configuration or target access, or compose lifecycle hooks. Callers own those
|
|
473
|
+
* checks and pagination validation. Use `forRequest(request).operations.cms` for
|
|
474
|
+
* request work or trusted operations for validated, lifecycle-aware server work.
|
|
475
|
+
*/
|
|
476
|
+
export async function getContentItemsByRelation<
|
|
477
|
+
TView extends CMSReadView = undefined,
|
|
478
|
+
const TFields extends CMSContentFields = undefined,
|
|
479
|
+
>(
|
|
480
|
+
adapter: Adapter,
|
|
481
|
+
contentTypeSlug: string,
|
|
482
|
+
params: ContentItemsByRelationParams<TView, TFields>,
|
|
483
|
+
): Promise<ContentItemsByRelationResult<TView, TFields>> {
|
|
484
|
+
const contentType = await adapter.findOne<ContentType>({
|
|
485
|
+
model: "contentType",
|
|
486
|
+
where: [{ field: "slug", value: contentTypeSlug, operator: "eq" }],
|
|
487
|
+
});
|
|
488
|
+
if (!contentType) {
|
|
489
|
+
return {
|
|
490
|
+
items: [],
|
|
491
|
+
total: 0,
|
|
492
|
+
limit: params.limit ?? 20,
|
|
493
|
+
offset: params.offset ?? 0,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
validateContentReadOptions(contentType, params);
|
|
497
|
+
const result = await getContentItemsByRelationForType(
|
|
498
|
+
adapter,
|
|
499
|
+
contentType,
|
|
500
|
+
params,
|
|
501
|
+
);
|
|
502
|
+
return {
|
|
503
|
+
...result,
|
|
504
|
+
items: result.items.map((item) => applyContentReadOptions(item, params)),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** @internal Reuses the checked source type without resolving its slug again. */
|
|
509
|
+
export async function getContentItemsByRelationForType(
|
|
510
|
+
adapter: Adapter,
|
|
511
|
+
contentType: ContentType,
|
|
512
|
+
params: ContentItemsByRelationParams,
|
|
513
|
+
): Promise<ContentItemsByRelationResult> {
|
|
514
|
+
const limit = params.limit ?? 20;
|
|
515
|
+
const offset = params.offset ?? 0;
|
|
516
|
+
const batchSize = 100;
|
|
517
|
+
const sourceIds = new Set<string>();
|
|
518
|
+
for (let relationOffset = 0; ; relationOffset += batchSize) {
|
|
519
|
+
const relations = await adapter.findMany<Pick<ContentRelation, "sourceId">>(
|
|
520
|
+
{
|
|
521
|
+
model: "contentRelation",
|
|
522
|
+
where: [
|
|
523
|
+
{ field: "targetId", value: params.targetId, operator: "eq" },
|
|
524
|
+
{ field: "fieldName", value: params.field, operator: "eq" },
|
|
525
|
+
],
|
|
526
|
+
select: ["sourceId"],
|
|
527
|
+
limit: batchSize,
|
|
528
|
+
offset: relationOffset,
|
|
529
|
+
sortBy: { field: "id", direction: "asc" },
|
|
530
|
+
},
|
|
531
|
+
);
|
|
532
|
+
for (const relation of relations) sourceIds.add(relation.sourceId);
|
|
533
|
+
if (relations.length < batchSize) break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const ids = [...sourceIds];
|
|
537
|
+
const metadata: Pick<ContentItem, "id" | "createdAt">[] = [];
|
|
538
|
+
for (let start = 0; start < ids.length; start += batchSize) {
|
|
539
|
+
metadata.push(
|
|
540
|
+
...(await adapter.findMany<Pick<ContentItem, "id" | "createdAt">>({
|
|
541
|
+
model: "contentItem",
|
|
542
|
+
where: [
|
|
543
|
+
{
|
|
544
|
+
field: "id",
|
|
545
|
+
value: ids.slice(start, start + batchSize),
|
|
546
|
+
operator: "in",
|
|
547
|
+
},
|
|
548
|
+
{ field: "contentTypeId", value: contentType.id, operator: "eq" },
|
|
549
|
+
],
|
|
550
|
+
select: ["id", "createdAt"],
|
|
551
|
+
limit: batchSize,
|
|
552
|
+
})),
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
metadata.sort(
|
|
556
|
+
(left, right) =>
|
|
557
|
+
right.createdAt.getTime() - left.createdAt.getTime() ||
|
|
558
|
+
(left.id < right.id ? 1 : left.id > right.id ? -1 : 0),
|
|
559
|
+
);
|
|
560
|
+
const pageIds = metadata.slice(offset, offset + limit).map((item) => item.id);
|
|
561
|
+
const itemsById = new Map<string, ContentItemWithType>();
|
|
562
|
+
for (let start = 0; start < pageIds.length; start += batchSize) {
|
|
563
|
+
const items = await adapter.findMany<ContentItemWithType>({
|
|
564
|
+
model: "contentItem",
|
|
565
|
+
where: [
|
|
566
|
+
{
|
|
567
|
+
field: "id",
|
|
568
|
+
value: pageIds.slice(start, start + batchSize),
|
|
569
|
+
operator: "in",
|
|
570
|
+
},
|
|
571
|
+
{ field: "contentTypeId", value: contentType.id, operator: "eq" },
|
|
572
|
+
],
|
|
573
|
+
limit: batchSize,
|
|
574
|
+
join: { contentType: true },
|
|
575
|
+
});
|
|
576
|
+
for (const item of items) itemsById.set(item.id, item);
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
items: pageIds.flatMap((id) => {
|
|
580
|
+
const item = itemsById.get(id);
|
|
581
|
+
return item ? [serializeContentItemWithType(item)] : [];
|
|
582
|
+
}),
|
|
583
|
+
total: metadata.length,
|
|
584
|
+
limit,
|
|
585
|
+
offset,
|
|
586
|
+
};
|
|
329
587
|
}
|
|
@@ -7,6 +7,9 @@ export {
|
|
|
7
7
|
export {
|
|
8
8
|
getAllContentTypes,
|
|
9
9
|
getAllContentItems,
|
|
10
|
+
getContentItemsByRelation,
|
|
11
|
+
type ContentItemsByRelationParams,
|
|
12
|
+
type ContentItemsByRelationResult,
|
|
10
13
|
getContentItemBySlug,
|
|
11
14
|
getContentItemById,
|
|
12
15
|
serializeContentType,
|
|
@@ -38,4 +41,11 @@ export type {
|
|
|
38
41
|
CMSUpdateOperationContext,
|
|
39
42
|
CMSUpdateResultContext,
|
|
40
43
|
ContentTypeConfig,
|
|
44
|
+
CMSReadOptions,
|
|
45
|
+
CMSContentFields,
|
|
46
|
+
CMSSelectedData,
|
|
47
|
+
CMSReadView,
|
|
48
|
+
SerializedContentItemContent,
|
|
49
|
+
CMSContentItemResult,
|
|
50
|
+
PaginatedContentItems,
|
|
41
51
|
} from "../types";
|