@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.
Files changed (33) hide show
  1. package/README.md +11 -0
  2. package/dist/cjs/custom-types/registry.cjs +6 -0
  3. package/dist/cjs/index.cjs +400 -65
  4. package/dist/cjs/libs/cli/config-loader.cjs +45 -4
  5. package/dist/cjs/libs/cli/publisher.cjs +24 -11
  6. package/dist/esm/custom-types/registry.js +6 -0
  7. package/dist/esm/index.js +401 -66
  8. package/dist/esm/libs/cli/config-loader.js +45 -4
  9. package/dist/esm/libs/cli/publisher.js +24 -11
  10. package/dist/types/custom-types/index.d.ts +2 -1
  11. package/dist/types/custom-types/index.d.ts.map +1 -1
  12. package/dist/types/custom-types/registry.d.ts.map +1 -1
  13. package/dist/types/index.d.ts +131 -7
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/libs/cli/config-loader.d.ts.map +1 -1
  16. package/dist/types/libs/cli/publisher.d.ts.map +1 -1
  17. package/package.json +13 -9
  18. package/dist/cjs/index-old-1.cjs +0 -866
  19. package/dist/cjs/index-old.cjs +0 -1016
  20. package/dist/cjs/libs/cli/descriptor-builder.cjs +0 -273
  21. package/dist/cjs/utils/index.cjs +0 -2
  22. package/dist/esm/index-old-1.js +0 -862
  23. package/dist/esm/index-old.js +0 -1012
  24. package/dist/esm/libs/cli/descriptor-builder.js +0 -268
  25. package/dist/esm/utils/index.js +0 -1
  26. package/dist/types/index-old-1.d.ts +0 -175
  27. package/dist/types/index-old-1.d.ts.map +0 -1
  28. package/dist/types/index-old.d.ts +0 -151
  29. package/dist/types/index-old.d.ts.map +0 -1
  30. package/dist/types/libs/cli/descriptor-builder.d.ts +0 -5
  31. package/dist/types/libs/cli/descriptor-builder.d.ts.map +0 -1
  32. package/dist/types/utils/index.d.ts +0 -2
  33. 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.replace(/\\/g, "/").replace(/^\/+/, "").trim();
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 (filename) => `${explicitBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
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 (filename) => `${inferredBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
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 filename = typeof source.filename === "string" ? source.filename.trim() : "";
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
- url: existingUrl ?? assetUrlBuilder(filename),
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 && typeof value === "object" && typeof value.id === "string") {
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) || isEnumDescriptor(desc) || isUnionDescriptor(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 && typeof value === "object" && !Array.isArray(value) && !value.id) {
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" ? value : {};
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" ? source : {};
344
- const localesSource = record.locales && typeof record.locales === "object" && !Array.isArray(record.locales)
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" || customType === "Image" || customType === "Video") {
417
- return maybeAttachAssetUrl(value, options?.assetUrlBuilder ?? ((filename) => filename));
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 && typeof raw === "object" && !Array.isArray(raw) && "value" in 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 = `_resolved/models/${encodeURIComponent(modelName)}/${encodeURIComponent(id)}`;
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 ? { locale: 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 ? { locale: 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 && typeof valueWithId === "object" && !Array.isArray(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 ? { id, ...valueWithId } : { id, value: valueWithId };
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 buildSearchParams(query) {
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 (!query)
946
- return params;
947
- for (const [key, rawValue] of Object.entries(query)) {
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
- if (options?.fields !== undefined && query.fields === undefined) {
1189
- query.fields = Array.isArray(options.fields)
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 && query.exclude === undefined) {
1194
- query.exclude = Array.isArray(options.exclude)
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 && query.locale === undefined) {
1203
- query.locale = locale;
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 && query.resolveModelRefs === undefined) {
1212
- query.resolveModelRefs = 1;
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
- ? `_resolved/${encodeURIComponent(resource.key)}`
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 async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
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 async (options) => {
1463
- return runBrowserAccessor((client) => client[property](options));
1464
- };
1799
+ return buildLazyGraphAccessor(property);
1465
1800
  },
1466
1801
  });
1467
1802
  return rootProxy;