@cms0/cms0 0.2.20 → 0.2.22
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/README.md +11 -0
- package/dist/cjs/custom-types/registry.cjs +6 -0
- package/dist/cjs/index.cjs +400 -65
- package/dist/cjs/libs/cli/config-loader.cjs +45 -4
- package/dist/cjs/libs/cli/publisher.cjs +24 -11
- package/dist/esm/custom-types/registry.js +6 -0
- package/dist/esm/index.js +401 -66
- package/dist/esm/libs/cli/config-loader.js +45 -4
- package/dist/esm/libs/cli/publisher.js +24 -11
- package/dist/types/custom-types/index.d.ts +2 -1
- package/dist/types/custom-types/index.d.ts.map +1 -1
- package/dist/types/custom-types/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +131 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/libs/cli/config-loader.d.ts.map +1 -1
- package/dist/types/libs/cli/publisher.d.ts.map +1 -1
- package/package.json +13 -9
- package/dist/cjs/index-old-1.cjs +0 -866
- package/dist/cjs/index-old.cjs +0 -1016
- package/dist/cjs/libs/cli/descriptor-builder.cjs +0 -273
- package/dist/cjs/utils/index.cjs +0 -2
- package/dist/esm/index-old-1.js +0 -862
- package/dist/esm/index-old.js +0 -1012
- package/dist/esm/libs/cli/descriptor-builder.js +0 -268
- package/dist/esm/utils/index.js +0 -1
- package/dist/types/index-old-1.d.ts +0 -175
- package/dist/types/index-old-1.d.ts.map +0 -1
- package/dist/types/index-old.d.ts +0 -151
- package/dist/types/index-old.d.ts.map +0 -1
- package/dist/types/libs/cli/descriptor-builder.d.ts +0 -5
- package/dist/types/libs/cli/descriptor-builder.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts +0 -2
- package/dist/types/utils/index.d.ts.map +0 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { attachCms0ProvenanceRoot, enableCms0ProvenanceTracking, isCms0CanvasTransportEnabled, registerCms0CollectionItemIdentity, } from "@cms0/cms0/provenance";
|
|
2
2
|
import { ensureBrowserSchemaDescriptorDevSubscription, getActiveSchemaDescriptor, invalidateBrowserSchemaDescriptorCache, resolveBrowserSchemaDescriptor, } from "@cms0/cms0/schema-descriptors";
|
|
3
|
-
import { buildZodSchemasFromDescriptor, decodeTaggedUnionValue, getUnionBranchKeys, } from "@cms0/shared";
|
|
3
|
+
import { buildZodSchemasFromDescriptor, decodeTaggedUnionValue, deriveAssetFilenameFromStorageKey, deriveAssetKindFromStorageKey, getAssetLogicalPath, getUnionBranchKeys, inferAssetKindFromFilename, normalizeAssetFilename, serializeGraphQueryOptions, } from "@cms0/shared";
|
|
4
4
|
import { getCustomInlineTypeMetadata } from "./custom-types/registry.js";
|
|
5
5
|
const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
|
|
6
6
|
const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
|
|
@@ -15,7 +15,7 @@ function normalizeAssetBaseUrl(baseUrl) {
|
|
|
15
15
|
return trimmed.replace(/\/+$/, "");
|
|
16
16
|
}
|
|
17
17
|
function encodeFilenameForUrl(filename) {
|
|
18
|
-
const normalized = filename
|
|
18
|
+
const normalized = normalizeAssetFilename(filename);
|
|
19
19
|
if (!normalized)
|
|
20
20
|
return "";
|
|
21
21
|
return normalized
|
|
@@ -24,11 +24,32 @@ function encodeFilenameForUrl(filename) {
|
|
|
24
24
|
.map((segment) => encodeURIComponent(segment))
|
|
25
25
|
.join("/");
|
|
26
26
|
}
|
|
27
|
+
function resolveAssetUrlSuffix(uploadsPath, asset) {
|
|
28
|
+
const storageKey = asset.storageKey?.trim();
|
|
29
|
+
const derivedSuffix = storageKey?.startsWith("uploads/")
|
|
30
|
+
? storageKey.slice("uploads/".length)
|
|
31
|
+
: getAssetLogicalPath(asset.kind, asset.filename).slice("uploads/".length);
|
|
32
|
+
if (uploadsPath.endsWith("/uploads/images") ||
|
|
33
|
+
uploadsPath.endsWith("/uploads/videos") ||
|
|
34
|
+
uploadsPath.endsWith("/uploads/files")) {
|
|
35
|
+
return encodeFilenameForUrl(asset.filename);
|
|
36
|
+
}
|
|
37
|
+
if (uploadsPath !== "/uploads" && !uploadsPath.endsWith("/uploads")) {
|
|
38
|
+
return encodeFilenameForUrl(asset.filename);
|
|
39
|
+
}
|
|
40
|
+
return encodeFilenameForUrl(derivedSuffix);
|
|
41
|
+
}
|
|
27
42
|
function buildAssetUrlBuilder(apiBaseUrl, assetOptions) {
|
|
28
43
|
const uploadsPath = normalizeUploadsPath(assetOptions?.uploadsPath);
|
|
44
|
+
const customResolver = assetOptions?.resolveUrl;
|
|
29
45
|
const explicitBaseUrl = normalizeAssetBaseUrl(assetOptions?.baseUrl);
|
|
30
46
|
if (explicitBaseUrl) {
|
|
31
|
-
return (
|
|
47
|
+
return (asset) => {
|
|
48
|
+
const customUrl = customResolver?.(asset)?.trim();
|
|
49
|
+
if (customUrl)
|
|
50
|
+
return customUrl;
|
|
51
|
+
return `${explicitBaseUrl}${uploadsPath}/${resolveAssetUrlSuffix(uploadsPath, asset)}`;
|
|
52
|
+
};
|
|
32
53
|
}
|
|
33
54
|
let inferredBaseUrl = "";
|
|
34
55
|
try {
|
|
@@ -37,13 +58,21 @@ function buildAssetUrlBuilder(apiBaseUrl, assetOptions) {
|
|
|
37
58
|
catch {
|
|
38
59
|
inferredBaseUrl = "";
|
|
39
60
|
}
|
|
40
|
-
return (
|
|
61
|
+
return (asset) => {
|
|
62
|
+
const customUrl = customResolver?.(asset)?.trim();
|
|
63
|
+
if (customUrl)
|
|
64
|
+
return customUrl;
|
|
65
|
+
return `${inferredBaseUrl}${uploadsPath}/${resolveAssetUrlSuffix(uploadsPath, asset)}`;
|
|
66
|
+
};
|
|
41
67
|
}
|
|
42
68
|
function maybeAttachAssetUrl(value, assetUrlBuilder) {
|
|
43
69
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
44
70
|
return value;
|
|
45
71
|
const source = value;
|
|
46
|
-
const
|
|
72
|
+
const storageKey = typeof source.storageKey === "string" ? source.storageKey.trim() : "";
|
|
73
|
+
const filename = typeof source.filename === "string" && source.filename.trim().length > 0
|
|
74
|
+
? source.filename.trim()
|
|
75
|
+
: deriveAssetFilenameFromStorageKey(storageKey) ?? "";
|
|
47
76
|
if (!filename)
|
|
48
77
|
return value;
|
|
49
78
|
const isAssetShape = typeof source.mimeType === "string" ||
|
|
@@ -57,9 +86,17 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
|
|
|
57
86
|
const existingUrl = typeof source.url === "string" && source.url.trim().length > 0
|
|
58
87
|
? source.url
|
|
59
88
|
: null;
|
|
89
|
+
const kind = deriveAssetKindFromStorageKey(storageKey) ?? inferAssetKindFromFilename(filename);
|
|
60
90
|
return {
|
|
61
91
|
...source,
|
|
62
|
-
|
|
92
|
+
filename,
|
|
93
|
+
...(storageKey ? { storageKey } : {}),
|
|
94
|
+
url: existingUrl ??
|
|
95
|
+
assetUrlBuilder({
|
|
96
|
+
filename,
|
|
97
|
+
kind,
|
|
98
|
+
...(storageKey ? { storageKey } : {}),
|
|
99
|
+
}),
|
|
63
100
|
};
|
|
64
101
|
}
|
|
65
102
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -85,7 +122,9 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
85
122
|
function extractId(value) {
|
|
86
123
|
if (typeof value === "string" && value.length > 0)
|
|
87
124
|
return value;
|
|
88
|
-
if (value &&
|
|
125
|
+
if (value &&
|
|
126
|
+
typeof value === "object" &&
|
|
127
|
+
typeof value.id === "string") {
|
|
89
128
|
return value.id;
|
|
90
129
|
}
|
|
91
130
|
return null;
|
|
@@ -175,7 +214,9 @@ function isUnionDescriptor(desc) {
|
|
|
175
214
|
return desc.kind === "union" && Array.isArray(desc.anyOf);
|
|
176
215
|
}
|
|
177
216
|
function isScalarDescriptor(desc) {
|
|
178
|
-
return isPrimitiveDescriptor(desc) ||
|
|
217
|
+
return (isPrimitiveDescriptor(desc) ||
|
|
218
|
+
isEnumDescriptor(desc) ||
|
|
219
|
+
isUnionDescriptor(desc));
|
|
179
220
|
}
|
|
180
221
|
function isModelRefDescriptor(desc) {
|
|
181
222
|
return desc.kind === "modelRef";
|
|
@@ -189,7 +230,10 @@ function isArrayDescriptor(desc) {
|
|
|
189
230
|
function attachIdIfObject(value, id) {
|
|
190
231
|
if (!id)
|
|
191
232
|
return value;
|
|
192
|
-
if (value &&
|
|
233
|
+
if (value &&
|
|
234
|
+
typeof value === "object" &&
|
|
235
|
+
!Array.isArray(value) &&
|
|
236
|
+
!value.id) {
|
|
193
237
|
return { ...value, id };
|
|
194
238
|
}
|
|
195
239
|
return value;
|
|
@@ -321,11 +365,13 @@ function toPlainText(value) {
|
|
|
321
365
|
return "";
|
|
322
366
|
}
|
|
323
367
|
function coerceRichTextValue(value) {
|
|
324
|
-
const source = value && typeof value === "object"
|
|
368
|
+
const source = value && typeof value === "object"
|
|
369
|
+
? value
|
|
370
|
+
: {};
|
|
325
371
|
const nested = source.value && typeof source.value === "object"
|
|
326
372
|
? source.value
|
|
327
373
|
: null;
|
|
328
|
-
const normalizedValue = source.value !== undefined ? source.value : value ?? {};
|
|
374
|
+
const normalizedValue = source.value !== undefined ? source.value : (value ?? {});
|
|
329
375
|
const html = typeof source.html === "string"
|
|
330
376
|
? source.html
|
|
331
377
|
: typeof nested?.html === "string"
|
|
@@ -340,8 +386,12 @@ function normalizeLocaleTag(value) {
|
|
|
340
386
|
return typeof value === "string" ? value.trim() : "";
|
|
341
387
|
}
|
|
342
388
|
function normalizeLocalizedMap(source, normalizeValue) {
|
|
343
|
-
const record = source && typeof source === "object"
|
|
344
|
-
|
|
389
|
+
const record = source && typeof source === "object"
|
|
390
|
+
? source
|
|
391
|
+
: {};
|
|
392
|
+
const localesSource = record.locales &&
|
|
393
|
+
typeof record.locales === "object" &&
|
|
394
|
+
!Array.isArray(record.locales)
|
|
345
395
|
? record.locales
|
|
346
396
|
: {};
|
|
347
397
|
const locales = {};
|
|
@@ -413,8 +463,11 @@ function coerceCustomTypeValue(descriptor, value, options) {
|
|
|
413
463
|
return value;
|
|
414
464
|
if (customType === "RichText")
|
|
415
465
|
return coerceRichTextValue(value);
|
|
416
|
-
if (customType === "File" ||
|
|
417
|
-
|
|
466
|
+
if (customType === "File" ||
|
|
467
|
+
customType === "Image" ||
|
|
468
|
+
customType === "Video") {
|
|
469
|
+
return maybeAttachAssetUrl(value, options?.assetUrlBuilder ??
|
|
470
|
+
((asset) => asset.storageKey ?? asset.filename));
|
|
418
471
|
}
|
|
419
472
|
if (customType === "LocalizedString") {
|
|
420
473
|
return coerceLocalizedStringValue(value, options?.locale, options?.defaultLocale);
|
|
@@ -489,7 +542,10 @@ function decodeUnionValueFromRawField(descriptor, raw) {
|
|
|
489
542
|
if (direct) {
|
|
490
543
|
return { ...direct, rowId: extractId(raw) };
|
|
491
544
|
}
|
|
492
|
-
if (raw &&
|
|
545
|
+
if (raw &&
|
|
546
|
+
typeof raw === "object" &&
|
|
547
|
+
!Array.isArray(raw) &&
|
|
548
|
+
"value" in raw) {
|
|
493
549
|
const nested = decodeUnionEnvelope(descriptor, raw.value);
|
|
494
550
|
if (nested) {
|
|
495
551
|
return { ...nested, rowId: extractId(raw) };
|
|
@@ -507,17 +563,22 @@ function ensureCollectionEnvelope(data, path) {
|
|
|
507
563
|
total: Number(data.total ?? data.items.length ?? 0),
|
|
508
564
|
};
|
|
509
565
|
}
|
|
566
|
+
if (data && typeof data === "object" && Array.isArray(data.data)) {
|
|
567
|
+
const pagination = data.pagination;
|
|
568
|
+
return {
|
|
569
|
+
items: data.data,
|
|
570
|
+
total: Number((pagination && typeof pagination === "object"
|
|
571
|
+
? pagination.total
|
|
572
|
+
: undefined) ?? data.data.length ?? 0),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
510
575
|
throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
|
|
511
576
|
}
|
|
512
577
|
async function readCanvasModelRefCollectionIdentities(path, descriptor, context) {
|
|
513
578
|
const cache = context.canvasModelRefCollectionIdentityCache;
|
|
514
579
|
if (!cache)
|
|
515
580
|
return null;
|
|
516
|
-
const cacheKey = [
|
|
517
|
-
path,
|
|
518
|
-
descriptor.model,
|
|
519
|
-
context.options.locale ?? "",
|
|
520
|
-
].join("::");
|
|
581
|
+
const cacheKey = [path, descriptor.model, context.options.locale ?? ""].join("::");
|
|
521
582
|
const existing = cache.get(cacheKey);
|
|
522
583
|
if (existing) {
|
|
523
584
|
return existing;
|
|
@@ -576,7 +637,7 @@ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem
|
|
|
576
637
|
nextTrail.add(nodeKey);
|
|
577
638
|
const promise = (async () => {
|
|
578
639
|
const modelPath = `models/${modelName}/${id}`;
|
|
579
|
-
const resolvedModelPath = `
|
|
640
|
+
const resolvedModelPath = `_graph/models/${encodeURIComponent(modelName)}/${encodeURIComponent(id)}`;
|
|
580
641
|
const rawModel = inlineModel ??
|
|
581
642
|
(await (async () => {
|
|
582
643
|
const query = {
|
|
@@ -665,7 +726,9 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
665
726
|
: await context.requestJson(childPath, {
|
|
666
727
|
query: {
|
|
667
728
|
raw: 1,
|
|
668
|
-
...(context.options.locale
|
|
729
|
+
...(context.options.locale
|
|
730
|
+
? { locale: context.options.locale }
|
|
731
|
+
: {}),
|
|
669
732
|
},
|
|
670
733
|
});
|
|
671
734
|
const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail, inlineChildRaw !== undefined, childProjectionPathTokens);
|
|
@@ -688,7 +751,9 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
688
751
|
: await context.requestJson(childPath, {
|
|
689
752
|
query: {
|
|
690
753
|
raw: 1,
|
|
691
|
-
...(context.options.locale
|
|
754
|
+
...(context.options.locale
|
|
755
|
+
? { locale: context.options.locale }
|
|
756
|
+
: {}),
|
|
692
757
|
},
|
|
693
758
|
});
|
|
694
759
|
const normalizedChild = await normalizeObjectField(propertyDescriptor, childPath, childRaw, context, trail, false, childProjectionPathTokens);
|
|
@@ -722,11 +787,15 @@ async function normalizeArrayField(descriptor, path, raw, context, trail, inline
|
|
|
722
787
|
}
|
|
723
788
|
const id = extractId(row);
|
|
724
789
|
const valueWithId = attachIdForScalarDescriptor(itemDescriptor, normalizedValue, id);
|
|
725
|
-
const isObjectValue = valueWithId &&
|
|
790
|
+
const isObjectValue = valueWithId &&
|
|
791
|
+
typeof valueWithId === "object" &&
|
|
792
|
+
!Array.isArray(valueWithId);
|
|
726
793
|
if (!id) {
|
|
727
794
|
return valueWithId;
|
|
728
795
|
}
|
|
729
|
-
return isObjectValue
|
|
796
|
+
return isObjectValue
|
|
797
|
+
? { id, ...valueWithId }
|
|
798
|
+
: { id, value: valueWithId };
|
|
730
799
|
});
|
|
731
800
|
}
|
|
732
801
|
if (isModelRefDescriptor(itemDescriptor)) {
|
|
@@ -759,9 +828,7 @@ async function normalizeArrayField(descriptor, path, raw, context, trail, inline
|
|
|
759
828
|
if (isObjectDescriptor(itemDescriptor)) {
|
|
760
829
|
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
761
830
|
const rowId = extractId(row);
|
|
762
|
-
const rowPath = rowId
|
|
763
|
-
? `${path}/${encodeURIComponent(rowId)}`
|
|
764
|
-
: path;
|
|
831
|
+
const rowPath = rowId ? `${path}/${encodeURIComponent(rowId)}` : path;
|
|
765
832
|
return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true, projectionPathTokens);
|
|
766
833
|
});
|
|
767
834
|
}
|
|
@@ -927,7 +994,7 @@ function createResourceRegistry(descriptor) {
|
|
|
927
994
|
};
|
|
928
995
|
}
|
|
929
996
|
function normalizeBaseUrl(baseUrl) {
|
|
930
|
-
const cleaned = baseUrl?.trim().replace(
|
|
997
|
+
const cleaned = baseUrl?.trim().replace(/\/+$/, "") ?? "";
|
|
931
998
|
if (!cleaned) {
|
|
932
999
|
throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
|
|
933
1000
|
}
|
|
@@ -940,28 +1007,39 @@ function resolveIncludeIdMode(includeId, globalIncludeId) {
|
|
|
940
1007
|
function shouldIncludeObjectId(mode, _isCollectionItem) {
|
|
941
1008
|
return mode === "all";
|
|
942
1009
|
}
|
|
943
|
-
function
|
|
1010
|
+
function isPlainObject(value) {
|
|
1011
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1012
|
+
}
|
|
1013
|
+
function appendQueryValue(params, key, rawValue) {
|
|
1014
|
+
if (rawValue === undefined || rawValue === null)
|
|
1015
|
+
return;
|
|
1016
|
+
if (Array.isArray(rawValue)) {
|
|
1017
|
+
const values = rawValue.filter((value) => value !== undefined && value !== null);
|
|
1018
|
+
if (values.length === 0)
|
|
1019
|
+
return;
|
|
1020
|
+
if (key.endsWith(".fields") ||
|
|
1021
|
+
key.endsWith(".exclude") ||
|
|
1022
|
+
key === "fields" ||
|
|
1023
|
+
key === "exclude") {
|
|
1024
|
+
params.set(key, values.map(String).join(","));
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
values.forEach((value) => params.append(key, String(value)));
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
params.set(key, String(rawValue));
|
|
1031
|
+
}
|
|
1032
|
+
function buildSearchParams(query, graph) {
|
|
944
1033
|
const params = new URLSearchParams();
|
|
945
|
-
if (
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
if (rawValue === undefined || rawValue === null)
|
|
949
|
-
continue;
|
|
950
|
-
if (Array.isArray(rawValue)) {
|
|
951
|
-
rawValue.forEach((value) => {
|
|
952
|
-
if (value === undefined || value === null)
|
|
953
|
-
return;
|
|
954
|
-
params.append(key, String(value));
|
|
955
|
-
});
|
|
956
|
-
continue;
|
|
1034
|
+
if (query) {
|
|
1035
|
+
for (const [key, rawValue] of Object.entries(query)) {
|
|
1036
|
+
appendQueryValue(params, key, rawValue);
|
|
957
1037
|
}
|
|
958
|
-
params.set(key, String(rawValue));
|
|
959
1038
|
}
|
|
1039
|
+
const graphParams = serializeGraphQueryOptions(graph);
|
|
1040
|
+
graphParams.forEach((value, key) => params.set(key, value));
|
|
960
1041
|
return params;
|
|
961
1042
|
}
|
|
962
|
-
function isPlainObject(value) {
|
|
963
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
964
|
-
}
|
|
965
1043
|
function cloneValue(value) {
|
|
966
1044
|
if (Array.isArray(value)) {
|
|
967
1045
|
return value.map((item) => cloneValue(item));
|
|
@@ -1109,8 +1187,73 @@ function applyFieldProjection(value, fields, exclude) {
|
|
|
1109
1187
|
}
|
|
1110
1188
|
return projected;
|
|
1111
1189
|
}
|
|
1190
|
+
function escapeGraphPointerToken(token) {
|
|
1191
|
+
return String(token).replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1192
|
+
}
|
|
1193
|
+
function buildGraphPointerPath(path) {
|
|
1194
|
+
return path.length === 0
|
|
1195
|
+
? "/"
|
|
1196
|
+
: `/${path.map((token) => escapeGraphPointerToken(token)).join("/")}`;
|
|
1197
|
+
}
|
|
1198
|
+
function buildProjectionPath(path) {
|
|
1199
|
+
return path
|
|
1200
|
+
.filter((token) => typeof token === "string")
|
|
1201
|
+
.map((token) => String(token))
|
|
1202
|
+
.join(".");
|
|
1203
|
+
}
|
|
1204
|
+
function readValueAtPath(value, path) {
|
|
1205
|
+
let current = value;
|
|
1206
|
+
for (const token of path) {
|
|
1207
|
+
if (Array.isArray(current)) {
|
|
1208
|
+
const index = Number(token);
|
|
1209
|
+
if (!Number.isInteger(index) || index < 0)
|
|
1210
|
+
return undefined;
|
|
1211
|
+
current = current[index];
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (!isPlainObject(current))
|
|
1215
|
+
return undefined;
|
|
1216
|
+
current = current[String(token)];
|
|
1217
|
+
}
|
|
1218
|
+
return current;
|
|
1219
|
+
}
|
|
1220
|
+
function buildSetOpsFromPatch(value, path = []) {
|
|
1221
|
+
if (value === undefined)
|
|
1222
|
+
return [];
|
|
1223
|
+
if (Array.isArray(value) || !isPlainObject(value)) {
|
|
1224
|
+
return [{ op: "set", path: buildGraphPointerPath(path), value }];
|
|
1225
|
+
}
|
|
1226
|
+
const entries = Object.entries(value).filter(([, child]) => child !== undefined);
|
|
1227
|
+
if (!entries.length) {
|
|
1228
|
+
return path.length
|
|
1229
|
+
? [{ op: "set", path: buildGraphPointerPath(path), value }]
|
|
1230
|
+
: [];
|
|
1231
|
+
}
|
|
1232
|
+
return entries.flatMap(([key, child]) => buildSetOpsFromPatch(child, [...path, key]));
|
|
1233
|
+
}
|
|
1234
|
+
function getModelObjectDescriptor(descriptor, modelName) {
|
|
1235
|
+
const model = descriptor.models?.[modelName];
|
|
1236
|
+
if (!model)
|
|
1237
|
+
return undefined;
|
|
1238
|
+
return asModelObjectDescriptor(modelName, descriptor);
|
|
1239
|
+
}
|
|
1240
|
+
function getDescriptorChild(descriptor, field, key) {
|
|
1241
|
+
if (!field)
|
|
1242
|
+
return undefined;
|
|
1243
|
+
if (isModelRefDescriptor(field)) {
|
|
1244
|
+
const model = getModelObjectDescriptor(descriptor, field.model);
|
|
1245
|
+
return getDescriptorChild(descriptor, model, key);
|
|
1246
|
+
}
|
|
1247
|
+
if (isArrayDescriptor(field)) {
|
|
1248
|
+
return getDescriptorChild(descriptor, field.items, key);
|
|
1249
|
+
}
|
|
1250
|
+
if (isObjectDescriptor(field)) {
|
|
1251
|
+
return field.properties[key];
|
|
1252
|
+
}
|
|
1253
|
+
return undefined;
|
|
1254
|
+
}
|
|
1112
1255
|
async function requestJson(baseUrl, apiKey, path, options) {
|
|
1113
|
-
const params = buildSearchParams(options?.query);
|
|
1256
|
+
const params = buildSearchParams(options?.query, options?.graph);
|
|
1114
1257
|
const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1115
1258
|
const response = await fetch(url, {
|
|
1116
1259
|
method: "GET",
|
|
@@ -1124,6 +1267,34 @@ async function requestJson(baseUrl, apiKey, path, options) {
|
|
|
1124
1267
|
}
|
|
1125
1268
|
return response.json();
|
|
1126
1269
|
}
|
|
1270
|
+
async function request(baseUrl, apiKey, method, path, body, options) {
|
|
1271
|
+
const params = buildSearchParams(options?.query, options?.graph);
|
|
1272
|
+
const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1273
|
+
const headers = {
|
|
1274
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
1275
|
+
};
|
|
1276
|
+
if (body !== undefined) {
|
|
1277
|
+
headers["content-type"] = "application/json";
|
|
1278
|
+
}
|
|
1279
|
+
const response = await fetch(url, {
|
|
1280
|
+
method,
|
|
1281
|
+
signal: options?.signal,
|
|
1282
|
+
headers,
|
|
1283
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1284
|
+
});
|
|
1285
|
+
if (!response.ok) {
|
|
1286
|
+
throw new Error(`Request failed for '${path}' with status ${response.status}: ${response.statusText}`);
|
|
1287
|
+
}
|
|
1288
|
+
const text = await response.text();
|
|
1289
|
+
if (!text)
|
|
1290
|
+
return undefined;
|
|
1291
|
+
try {
|
|
1292
|
+
return JSON.parse(text);
|
|
1293
|
+
}
|
|
1294
|
+
catch {
|
|
1295
|
+
return text;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1127
1298
|
function validateResult(resource, result, descriptor, includeIdMode, zodSchemas, modelZodSchemas) {
|
|
1128
1299
|
if (includeIdMode === "all")
|
|
1129
1300
|
return;
|
|
@@ -1166,13 +1337,13 @@ function attachResourceProvenanceWithOptions(resource, value, options) {
|
|
|
1166
1337
|
enableCms0ProvenanceTracking(true);
|
|
1167
1338
|
const rootId = resource.kind === "root"
|
|
1168
1339
|
? resource.key
|
|
1169
|
-
: resource.modelName ?? resource.key;
|
|
1340
|
+
: (resource.modelName ?? resource.key);
|
|
1170
1341
|
return attachCms0ProvenanceRoot(value, rootId, options);
|
|
1171
1342
|
}
|
|
1172
1343
|
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId, provenanceOptions) {
|
|
1173
1344
|
const responseMode = options?.response ?? "normalized";
|
|
1174
1345
|
const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
|
|
1175
|
-
const resolveModelRefs = options?.resolveModelRefs !== false;
|
|
1346
|
+
const resolveModelRefs = options?.graph?.resolveModelRefs !== false;
|
|
1176
1347
|
const includeProjectionPaths = normalizeProjectionPaths(options?.fields);
|
|
1177
1348
|
const excludeProjectionPaths = normalizeProjectionPaths(options?.exclude);
|
|
1178
1349
|
const shouldValidateFullResult = includeProjectionPaths.length === 0 && excludeProjectionPaths.length === 0;
|
|
@@ -1180,18 +1351,20 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1180
1351
|
(typeof options?.query?.locale === "string"
|
|
1181
1352
|
? options.query.locale
|
|
1182
1353
|
: undefined);
|
|
1183
|
-
const locale = requestedLocale ??
|
|
1184
|
-
(responseMode === "normalized" ? "all" : undefined);
|
|
1354
|
+
const locale = requestedLocale ?? (responseMode === "normalized" ? "all" : undefined);
|
|
1185
1355
|
const query = {
|
|
1186
1356
|
...(options?.query ?? {}),
|
|
1187
1357
|
};
|
|
1188
|
-
|
|
1189
|
-
|
|
1358
|
+
const graphQuery = {
|
|
1359
|
+
...options?.graph,
|
|
1360
|
+
};
|
|
1361
|
+
if (options?.fields !== undefined && graphQuery.fields === undefined) {
|
|
1362
|
+
graphQuery.fields = Array.isArray(options.fields)
|
|
1190
1363
|
? options.fields.join(",")
|
|
1191
1364
|
: options.fields;
|
|
1192
1365
|
}
|
|
1193
|
-
if (options?.exclude !== undefined &&
|
|
1194
|
-
|
|
1366
|
+
if (options?.exclude !== undefined && graphQuery.exclude === undefined) {
|
|
1367
|
+
graphQuery.exclude = Array.isArray(options.exclude)
|
|
1195
1368
|
? options.exclude.join(",")
|
|
1196
1369
|
: options.exclude;
|
|
1197
1370
|
}
|
|
@@ -1199,8 +1372,8 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1199
1372
|
if (shouldNormalize && query.raw === undefined) {
|
|
1200
1373
|
query.raw = 1;
|
|
1201
1374
|
}
|
|
1202
|
-
if (locale &&
|
|
1203
|
-
|
|
1375
|
+
if (locale && graphQuery.locale === undefined) {
|
|
1376
|
+
graphQuery.locale = locale;
|
|
1204
1377
|
}
|
|
1205
1378
|
const useResolvedEndpoint = shouldNormalize &&
|
|
1206
1379
|
!byId &&
|
|
@@ -1208,20 +1381,25 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1208
1381
|
!resource.isCollection &&
|
|
1209
1382
|
isObjectDescriptor(resource.descriptor) &&
|
|
1210
1383
|
resolveModelRefs;
|
|
1211
|
-
if (useResolvedEndpoint &&
|
|
1212
|
-
|
|
1384
|
+
if (useResolvedEndpoint &&
|
|
1385
|
+
graphQuery.resolveModelRefs === undefined) {
|
|
1386
|
+
graphQuery.resolveModelRefs = true;
|
|
1387
|
+
}
|
|
1388
|
+
if (useResolvedEndpoint && graphQuery.pageSize === undefined) {
|
|
1389
|
+
graphQuery.pageSize = "full";
|
|
1213
1390
|
}
|
|
1214
1391
|
const normalizePath = byId
|
|
1215
1392
|
? `${resource.path}/${encodeURIComponent(byId)}`
|
|
1216
1393
|
: resource.path;
|
|
1217
1394
|
const requestPath = useResolvedEndpoint
|
|
1218
|
-
? `
|
|
1395
|
+
? `_graph/${encodeURIComponent(resource.key)}`
|
|
1219
1396
|
: normalizePath;
|
|
1220
1397
|
let rawData;
|
|
1221
1398
|
if (useResolvedEndpoint) {
|
|
1222
1399
|
try {
|
|
1223
1400
|
rawData = await requestJson(baseUrl, apiKey, requestPath, {
|
|
1224
1401
|
query,
|
|
1402
|
+
graph: graphQuery,
|
|
1225
1403
|
signal: options?.signal,
|
|
1226
1404
|
});
|
|
1227
1405
|
}
|
|
@@ -1229,6 +1407,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1229
1407
|
try {
|
|
1230
1408
|
rawData = await requestJson(baseUrl, apiKey, normalizePath, {
|
|
1231
1409
|
query,
|
|
1410
|
+
graph: graphQuery,
|
|
1232
1411
|
signal: options?.signal,
|
|
1233
1412
|
});
|
|
1234
1413
|
}
|
|
@@ -1240,6 +1419,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1240
1419
|
else {
|
|
1241
1420
|
rawData = await requestJson(baseUrl, apiKey, requestPath, {
|
|
1242
1421
|
query,
|
|
1422
|
+
graph: graphQuery,
|
|
1243
1423
|
signal: options?.signal,
|
|
1244
1424
|
});
|
|
1245
1425
|
}
|
|
@@ -1304,6 +1484,80 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
1304
1484
|
}
|
|
1305
1485
|
return attachResourceProvenanceWithOptions(resource, applyFieldProjection(normalized, options?.fields, options?.exclude), provenanceOptions);
|
|
1306
1486
|
}
|
|
1487
|
+
function createConcreteGraphAccessor(input, path = [], fieldDescriptor = input.entry.descriptor) {
|
|
1488
|
+
const read = async (options) => {
|
|
1489
|
+
const projectionPath = buildProjectionPath(path);
|
|
1490
|
+
const value = await runResource(input.entry, input.descriptor, input.baseUrl, input.apiKey, input.defaultLocale, input.assetUrlBuilder, input.zodSchemas, input.modelZodSchemas, input.globalIncludeId, input.sharedModelInflightCache, projectionPath
|
|
1491
|
+
? {
|
|
1492
|
+
...options,
|
|
1493
|
+
fields: options?.fields === undefined ? projectionPath : options.fields,
|
|
1494
|
+
}
|
|
1495
|
+
: options);
|
|
1496
|
+
return projectionPath ? readValueAtPath(value, path) : value;
|
|
1497
|
+
};
|
|
1498
|
+
const mutate = async (ops) => {
|
|
1499
|
+
if (!ops.length)
|
|
1500
|
+
return;
|
|
1501
|
+
await request(input.baseUrl, input.apiKey, "POST", `_graph/${input.entry.path}/_mutate`, { ops });
|
|
1502
|
+
};
|
|
1503
|
+
const update = async (value) => {
|
|
1504
|
+
await mutate(buildSetOpsFromPatch(value, path));
|
|
1505
|
+
};
|
|
1506
|
+
const set = async (value) => {
|
|
1507
|
+
await mutate([{ op: "set", path: buildGraphPointerPath(path), value }]);
|
|
1508
|
+
};
|
|
1509
|
+
const fieldFn = read;
|
|
1510
|
+
return new Proxy(fieldFn, {
|
|
1511
|
+
get(target, property, receiver) {
|
|
1512
|
+
if (typeof property !== "string") {
|
|
1513
|
+
return Reflect.get(target, property, receiver);
|
|
1514
|
+
}
|
|
1515
|
+
if (property === "then")
|
|
1516
|
+
return undefined;
|
|
1517
|
+
if (property === "update")
|
|
1518
|
+
return update;
|
|
1519
|
+
if (property === "set")
|
|
1520
|
+
return set;
|
|
1521
|
+
if (property === "delete") {
|
|
1522
|
+
return async () => {
|
|
1523
|
+
await mutate([{ op: "delete", path: buildGraphPointerPath(path) }]);
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
if (property === "append") {
|
|
1527
|
+
return async (value) => {
|
|
1528
|
+
await mutate([
|
|
1529
|
+
{
|
|
1530
|
+
op: "insert",
|
|
1531
|
+
path: buildGraphPointerPath([...path, "-"]),
|
|
1532
|
+
value,
|
|
1533
|
+
},
|
|
1534
|
+
]);
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
if (property === "insert") {
|
|
1538
|
+
return async (index, value) => {
|
|
1539
|
+
await mutate([
|
|
1540
|
+
{
|
|
1541
|
+
op: "insert",
|
|
1542
|
+
path: buildGraphPointerPath([...path, index]),
|
|
1543
|
+
value,
|
|
1544
|
+
},
|
|
1545
|
+
]);
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
if (property === "at") {
|
|
1549
|
+
return (index) => createConcreteGraphAccessor(input, [...path, index], isArrayDescriptor(fieldDescriptor)
|
|
1550
|
+
? fieldDescriptor.items
|
|
1551
|
+
: fieldDescriptor);
|
|
1552
|
+
}
|
|
1553
|
+
const childDescriptor = getDescriptorChild(input.descriptor, fieldDescriptor, property);
|
|
1554
|
+
if (childDescriptor) {
|
|
1555
|
+
return createConcreteGraphAccessor(input, [...path, property], childDescriptor);
|
|
1556
|
+
}
|
|
1557
|
+
return Reflect.get(target, property, receiver);
|
|
1558
|
+
},
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1307
1561
|
export function createCmsClient(descriptor, config) {
|
|
1308
1562
|
const baseUrl = normalizeBaseUrl(config.apiConfig?.baseUrl);
|
|
1309
1563
|
const apiKey = config.apiConfig?.key;
|
|
@@ -1316,7 +1570,20 @@ export function createCmsClient(descriptor, config) {
|
|
|
1316
1570
|
const sharedModelInflightCache = new Map();
|
|
1317
1571
|
const buildModelAccessor = (entry) => {
|
|
1318
1572
|
const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options));
|
|
1319
|
-
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id);
|
|
1573
|
+
accessor.byId = (async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id));
|
|
1574
|
+
accessor.list = async (options) => accessor(options);
|
|
1575
|
+
accessor.get = async (id, options) => accessor.byId(id, options);
|
|
1576
|
+
accessor.create = async (value) => {
|
|
1577
|
+
const response = await request(baseUrl, apiKey, "POST", entry.path, value);
|
|
1578
|
+
return response;
|
|
1579
|
+
};
|
|
1580
|
+
accessor.update = async (id, value) => {
|
|
1581
|
+
const response = await request(baseUrl, apiKey, "POST", `_graph/${entry.path}/${encodeURIComponent(id)}/_mutate`, { ops: buildSetOpsFromPatch(value) });
|
|
1582
|
+
return response;
|
|
1583
|
+
};
|
|
1584
|
+
accessor.delete = async (id) => {
|
|
1585
|
+
await request(baseUrl, apiKey, "DELETE", `${entry.path}?id=${encodeURIComponent(id)}`);
|
|
1586
|
+
};
|
|
1320
1587
|
return accessor;
|
|
1321
1588
|
};
|
|
1322
1589
|
const modelsProxy = new Proxy({}, {
|
|
@@ -1380,7 +1647,18 @@ export function createCmsClient(descriptor, config) {
|
|
|
1380
1647
|
if (!entry) {
|
|
1381
1648
|
unknownKeyError(property, rootKeys, []);
|
|
1382
1649
|
}
|
|
1383
|
-
return
|
|
1650
|
+
return createConcreteGraphAccessor({
|
|
1651
|
+
apiKey,
|
|
1652
|
+
assetUrlBuilder,
|
|
1653
|
+
baseUrl,
|
|
1654
|
+
defaultLocale: config.defaultLocale,
|
|
1655
|
+
descriptor,
|
|
1656
|
+
entry,
|
|
1657
|
+
globalIncludeId,
|
|
1658
|
+
modelZodSchemas,
|
|
1659
|
+
sharedModelInflightCache,
|
|
1660
|
+
zodSchemas,
|
|
1661
|
+
});
|
|
1384
1662
|
},
|
|
1385
1663
|
});
|
|
1386
1664
|
return rootProxy;
|
|
@@ -1422,6 +1700,50 @@ function createLazyBrowserCmsClient(config) {
|
|
|
1422
1700
|
throw error;
|
|
1423
1701
|
}
|
|
1424
1702
|
};
|
|
1703
|
+
const getNestedAccessor = (client, rootProperty, path) => {
|
|
1704
|
+
let current = client[rootProperty];
|
|
1705
|
+
for (const token of path) {
|
|
1706
|
+
current =
|
|
1707
|
+
typeof token === "number" ? current.at(token) : current[String(token)];
|
|
1708
|
+
}
|
|
1709
|
+
return current;
|
|
1710
|
+
};
|
|
1711
|
+
const functionMemberNames = new Set([
|
|
1712
|
+
"apply",
|
|
1713
|
+
"bind",
|
|
1714
|
+
"call",
|
|
1715
|
+
"hasOwnProperty",
|
|
1716
|
+
"isPrototypeOf",
|
|
1717
|
+
"propertyIsEnumerable",
|
|
1718
|
+
"toLocaleString",
|
|
1719
|
+
"toString",
|
|
1720
|
+
"valueOf",
|
|
1721
|
+
]);
|
|
1722
|
+
const buildLazyGraphAccessor = (rootProperty, path = []) => {
|
|
1723
|
+
const accessor = (async (options) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)(options)));
|
|
1724
|
+
return new Proxy(accessor, {
|
|
1725
|
+
get(target, property, receiver) {
|
|
1726
|
+
if (typeof property !== "string") {
|
|
1727
|
+
return Reflect.get(target, property, receiver);
|
|
1728
|
+
}
|
|
1729
|
+
if (property === "then")
|
|
1730
|
+
return undefined;
|
|
1731
|
+
if (functionMemberNames.has(property)) {
|
|
1732
|
+
return Reflect.get(target, property, receiver);
|
|
1733
|
+
}
|
|
1734
|
+
if (property === "update" || property === "set" || property === "delete") {
|
|
1735
|
+
return (...args) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)[property](...args));
|
|
1736
|
+
}
|
|
1737
|
+
if (property === "append" || property === "insert") {
|
|
1738
|
+
return (...args) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)[property](...args));
|
|
1739
|
+
}
|
|
1740
|
+
if (property === "at") {
|
|
1741
|
+
return (index) => buildLazyGraphAccessor(rootProperty, [...path, index]);
|
|
1742
|
+
}
|
|
1743
|
+
return buildLazyGraphAccessor(rootProperty, [...path, property]);
|
|
1744
|
+
},
|
|
1745
|
+
});
|
|
1746
|
+
};
|
|
1425
1747
|
const buildModelAccessor = (property) => {
|
|
1426
1748
|
const accessor = (async (options) => {
|
|
1427
1749
|
return runBrowserAccessor((client) => client.models[property](options));
|
|
@@ -1429,6 +1751,21 @@ function createLazyBrowserCmsClient(config) {
|
|
|
1429
1751
|
accessor.byId = async (id, options) => {
|
|
1430
1752
|
return runBrowserAccessor((client) => client.models[property].byId(id, options));
|
|
1431
1753
|
};
|
|
1754
|
+
accessor.list = async (options) => {
|
|
1755
|
+
return runBrowserAccessor((client) => client.models[property].list(options));
|
|
1756
|
+
};
|
|
1757
|
+
accessor.get = async (id, options) => {
|
|
1758
|
+
return runBrowserAccessor((client) => client.models[property].get(id, options));
|
|
1759
|
+
};
|
|
1760
|
+
accessor.create = async (value) => {
|
|
1761
|
+
return runBrowserAccessor((client) => client.models[property].create(value));
|
|
1762
|
+
};
|
|
1763
|
+
accessor.update = async (id, value) => {
|
|
1764
|
+
return runBrowserAccessor((client) => client.models[property].update(id, value));
|
|
1765
|
+
};
|
|
1766
|
+
accessor.delete = async (id) => {
|
|
1767
|
+
return runBrowserAccessor((client) => client.models[property].delete(id));
|
|
1768
|
+
};
|
|
1432
1769
|
return accessor;
|
|
1433
1770
|
};
|
|
1434
1771
|
const modelsProxy = new Proxy({}, {
|
|
@@ -1459,9 +1796,7 @@ function createLazyBrowserCmsClient(config) {
|
|
|
1459
1796
|
if (Reflect.has(target, property)) {
|
|
1460
1797
|
return Reflect.get(target, property, receiver);
|
|
1461
1798
|
}
|
|
1462
|
-
return
|
|
1463
|
-
return runBrowserAccessor((client) => client[property](options));
|
|
1464
|
-
};
|
|
1799
|
+
return buildLazyGraphAccessor(property);
|
|
1465
1800
|
},
|
|
1466
1801
|
});
|
|
1467
1802
|
return rootProxy;
|