@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
@@ -20,7 +20,7 @@ function normalizeAssetBaseUrl(baseUrl) {
20
20
  return trimmed.replace(/\/+$/, "");
21
21
  }
22
22
  function encodeFilenameForUrl(filename) {
23
- const normalized = filename.replace(/\\/g, "/").replace(/^\/+/, "").trim();
23
+ const normalized = (0, shared_1.normalizeAssetFilename)(filename);
24
24
  if (!normalized)
25
25
  return "";
26
26
  return normalized
@@ -29,11 +29,32 @@ function encodeFilenameForUrl(filename) {
29
29
  .map((segment) => encodeURIComponent(segment))
30
30
  .join("/");
31
31
  }
32
+ function resolveAssetUrlSuffix(uploadsPath, asset) {
33
+ const storageKey = asset.storageKey?.trim();
34
+ const derivedSuffix = storageKey?.startsWith("uploads/")
35
+ ? storageKey.slice("uploads/".length)
36
+ : (0, shared_1.getAssetLogicalPath)(asset.kind, asset.filename).slice("uploads/".length);
37
+ if (uploadsPath.endsWith("/uploads/images") ||
38
+ uploadsPath.endsWith("/uploads/videos") ||
39
+ uploadsPath.endsWith("/uploads/files")) {
40
+ return encodeFilenameForUrl(asset.filename);
41
+ }
42
+ if (uploadsPath !== "/uploads" && !uploadsPath.endsWith("/uploads")) {
43
+ return encodeFilenameForUrl(asset.filename);
44
+ }
45
+ return encodeFilenameForUrl(derivedSuffix);
46
+ }
32
47
  function buildAssetUrlBuilder(apiBaseUrl, assetOptions) {
33
48
  const uploadsPath = normalizeUploadsPath(assetOptions?.uploadsPath);
49
+ const customResolver = assetOptions?.resolveUrl;
34
50
  const explicitBaseUrl = normalizeAssetBaseUrl(assetOptions?.baseUrl);
35
51
  if (explicitBaseUrl) {
36
- return (filename) => `${explicitBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
52
+ return (asset) => {
53
+ const customUrl = customResolver?.(asset)?.trim();
54
+ if (customUrl)
55
+ return customUrl;
56
+ return `${explicitBaseUrl}${uploadsPath}/${resolveAssetUrlSuffix(uploadsPath, asset)}`;
57
+ };
37
58
  }
38
59
  let inferredBaseUrl = "";
39
60
  try {
@@ -42,13 +63,21 @@ function buildAssetUrlBuilder(apiBaseUrl, assetOptions) {
42
63
  catch {
43
64
  inferredBaseUrl = "";
44
65
  }
45
- return (filename) => `${inferredBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
66
+ return (asset) => {
67
+ const customUrl = customResolver?.(asset)?.trim();
68
+ if (customUrl)
69
+ return customUrl;
70
+ return `${inferredBaseUrl}${uploadsPath}/${resolveAssetUrlSuffix(uploadsPath, asset)}`;
71
+ };
46
72
  }
47
73
  function maybeAttachAssetUrl(value, assetUrlBuilder) {
48
74
  if (!value || typeof value !== "object" || Array.isArray(value))
49
75
  return value;
50
76
  const source = value;
51
- const filename = typeof source.filename === "string" ? source.filename.trim() : "";
77
+ const storageKey = typeof source.storageKey === "string" ? source.storageKey.trim() : "";
78
+ const filename = typeof source.filename === "string" && source.filename.trim().length > 0
79
+ ? source.filename.trim()
80
+ : (0, shared_1.deriveAssetFilenameFromStorageKey)(storageKey) ?? "";
52
81
  if (!filename)
53
82
  return value;
54
83
  const isAssetShape = typeof source.mimeType === "string" ||
@@ -62,9 +91,17 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
62
91
  const existingUrl = typeof source.url === "string" && source.url.trim().length > 0
63
92
  ? source.url
64
93
  : null;
94
+ const kind = (0, shared_1.deriveAssetKindFromStorageKey)(storageKey) ?? (0, shared_1.inferAssetKindFromFilename)(filename);
65
95
  return {
66
96
  ...source,
67
- url: existingUrl ?? assetUrlBuilder(filename),
97
+ filename,
98
+ ...(storageKey ? { storageKey } : {}),
99
+ url: existingUrl ??
100
+ assetUrlBuilder({
101
+ filename,
102
+ kind,
103
+ ...(storageKey ? { storageKey } : {}),
104
+ }),
68
105
  };
69
106
  }
70
107
  async function mapWithConcurrency(items, concurrency, mapper) {
@@ -90,7 +127,9 @@ async function mapWithConcurrency(items, concurrency, mapper) {
90
127
  function extractId(value) {
91
128
  if (typeof value === "string" && value.length > 0)
92
129
  return value;
93
- if (value && typeof value === "object" && typeof value.id === "string") {
130
+ if (value &&
131
+ typeof value === "object" &&
132
+ typeof value.id === "string") {
94
133
  return value.id;
95
134
  }
96
135
  return null;
@@ -180,7 +219,9 @@ function isUnionDescriptor(desc) {
180
219
  return desc.kind === "union" && Array.isArray(desc.anyOf);
181
220
  }
182
221
  function isScalarDescriptor(desc) {
183
- return isPrimitiveDescriptor(desc) || isEnumDescriptor(desc) || isUnionDescriptor(desc);
222
+ return (isPrimitiveDescriptor(desc) ||
223
+ isEnumDescriptor(desc) ||
224
+ isUnionDescriptor(desc));
184
225
  }
185
226
  function isModelRefDescriptor(desc) {
186
227
  return desc.kind === "modelRef";
@@ -194,7 +235,10 @@ function isArrayDescriptor(desc) {
194
235
  function attachIdIfObject(value, id) {
195
236
  if (!id)
196
237
  return value;
197
- if (value && typeof value === "object" && !Array.isArray(value) && !value.id) {
238
+ if (value &&
239
+ typeof value === "object" &&
240
+ !Array.isArray(value) &&
241
+ !value.id) {
198
242
  return { ...value, id };
199
243
  }
200
244
  return value;
@@ -326,11 +370,13 @@ function toPlainText(value) {
326
370
  return "";
327
371
  }
328
372
  function coerceRichTextValue(value) {
329
- const source = value && typeof value === "object" ? value : {};
373
+ const source = value && typeof value === "object"
374
+ ? value
375
+ : {};
330
376
  const nested = source.value && typeof source.value === "object"
331
377
  ? source.value
332
378
  : null;
333
- const normalizedValue = source.value !== undefined ? source.value : value ?? {};
379
+ const normalizedValue = source.value !== undefined ? source.value : (value ?? {});
334
380
  const html = typeof source.html === "string"
335
381
  ? source.html
336
382
  : typeof nested?.html === "string"
@@ -345,8 +391,12 @@ function normalizeLocaleTag(value) {
345
391
  return typeof value === "string" ? value.trim() : "";
346
392
  }
347
393
  function normalizeLocalizedMap(source, normalizeValue) {
348
- const record = source && typeof source === "object" ? source : {};
349
- const localesSource = record.locales && typeof record.locales === "object" && !Array.isArray(record.locales)
394
+ const record = source && typeof source === "object"
395
+ ? source
396
+ : {};
397
+ const localesSource = record.locales &&
398
+ typeof record.locales === "object" &&
399
+ !Array.isArray(record.locales)
350
400
  ? record.locales
351
401
  : {};
352
402
  const locales = {};
@@ -418,8 +468,11 @@ function coerceCustomTypeValue(descriptor, value, options) {
418
468
  return value;
419
469
  if (customType === "RichText")
420
470
  return coerceRichTextValue(value);
421
- if (customType === "File" || customType === "Image" || customType === "Video") {
422
- return maybeAttachAssetUrl(value, options?.assetUrlBuilder ?? ((filename) => filename));
471
+ if (customType === "File" ||
472
+ customType === "Image" ||
473
+ customType === "Video") {
474
+ return maybeAttachAssetUrl(value, options?.assetUrlBuilder ??
475
+ ((asset) => asset.storageKey ?? asset.filename));
423
476
  }
424
477
  if (customType === "LocalizedString") {
425
478
  return coerceLocalizedStringValue(value, options?.locale, options?.defaultLocale);
@@ -494,7 +547,10 @@ function decodeUnionValueFromRawField(descriptor, raw) {
494
547
  if (direct) {
495
548
  return { ...direct, rowId: extractId(raw) };
496
549
  }
497
- if (raw && typeof raw === "object" && !Array.isArray(raw) && "value" in raw) {
550
+ if (raw &&
551
+ typeof raw === "object" &&
552
+ !Array.isArray(raw) &&
553
+ "value" in raw) {
498
554
  const nested = decodeUnionEnvelope(descriptor, raw.value);
499
555
  if (nested) {
500
556
  return { ...nested, rowId: extractId(raw) };
@@ -512,17 +568,22 @@ function ensureCollectionEnvelope(data, path) {
512
568
  total: Number(data.total ?? data.items.length ?? 0),
513
569
  };
514
570
  }
571
+ if (data && typeof data === "object" && Array.isArray(data.data)) {
572
+ const pagination = data.pagination;
573
+ return {
574
+ items: data.data,
575
+ total: Number((pagination && typeof pagination === "object"
576
+ ? pagination.total
577
+ : undefined) ?? data.data.length ?? 0),
578
+ };
579
+ }
515
580
  throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
516
581
  }
517
582
  async function readCanvasModelRefCollectionIdentities(path, descriptor, context) {
518
583
  const cache = context.canvasModelRefCollectionIdentityCache;
519
584
  if (!cache)
520
585
  return null;
521
- const cacheKey = [
522
- path,
523
- descriptor.model,
524
- context.options.locale ?? "",
525
- ].join("::");
586
+ const cacheKey = [path, descriptor.model, context.options.locale ?? ""].join("::");
526
587
  const existing = cache.get(cacheKey);
527
588
  if (existing) {
528
589
  return existing;
@@ -581,7 +642,7 @@ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem
581
642
  nextTrail.add(nodeKey);
582
643
  const promise = (async () => {
583
644
  const modelPath = `models/${modelName}/${id}`;
584
- const resolvedModelPath = `_resolved/models/${encodeURIComponent(modelName)}/${encodeURIComponent(id)}`;
645
+ const resolvedModelPath = `_graph/models/${encodeURIComponent(modelName)}/${encodeURIComponent(id)}`;
585
646
  const rawModel = inlineModel ??
586
647
  (await (async () => {
587
648
  const query = {
@@ -670,7 +731,9 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
670
731
  : await context.requestJson(childPath, {
671
732
  query: {
672
733
  raw: 1,
673
- ...(context.options.locale ? { locale: context.options.locale } : {}),
734
+ ...(context.options.locale
735
+ ? { locale: context.options.locale }
736
+ : {}),
674
737
  },
675
738
  });
676
739
  const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail, inlineChildRaw !== undefined, childProjectionPathTokens);
@@ -693,7 +756,9 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
693
756
  : await context.requestJson(childPath, {
694
757
  query: {
695
758
  raw: 1,
696
- ...(context.options.locale ? { locale: context.options.locale } : {}),
759
+ ...(context.options.locale
760
+ ? { locale: context.options.locale }
761
+ : {}),
697
762
  },
698
763
  });
699
764
  const normalizedChild = await normalizeObjectField(propertyDescriptor, childPath, childRaw, context, trail, false, childProjectionPathTokens);
@@ -727,11 +792,15 @@ async function normalizeArrayField(descriptor, path, raw, context, trail, inline
727
792
  }
728
793
  const id = extractId(row);
729
794
  const valueWithId = attachIdForScalarDescriptor(itemDescriptor, normalizedValue, id);
730
- const isObjectValue = valueWithId && typeof valueWithId === "object" && !Array.isArray(valueWithId);
795
+ const isObjectValue = valueWithId &&
796
+ typeof valueWithId === "object" &&
797
+ !Array.isArray(valueWithId);
731
798
  if (!id) {
732
799
  return valueWithId;
733
800
  }
734
- return isObjectValue ? { id, ...valueWithId } : { id, value: valueWithId };
801
+ return isObjectValue
802
+ ? { id, ...valueWithId }
803
+ : { id, value: valueWithId };
735
804
  });
736
805
  }
737
806
  if (isModelRefDescriptor(itemDescriptor)) {
@@ -764,9 +833,7 @@ async function normalizeArrayField(descriptor, path, raw, context, trail, inline
764
833
  if (isObjectDescriptor(itemDescriptor)) {
765
834
  return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
766
835
  const rowId = extractId(row);
767
- const rowPath = rowId
768
- ? `${path}/${encodeURIComponent(rowId)}`
769
- : path;
836
+ const rowPath = rowId ? `${path}/${encodeURIComponent(rowId)}` : path;
770
837
  return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true, projectionPathTokens);
771
838
  });
772
839
  }
@@ -932,7 +999,7 @@ function createResourceRegistry(descriptor) {
932
999
  };
933
1000
  }
934
1001
  function normalizeBaseUrl(baseUrl) {
935
- const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
1002
+ const cleaned = baseUrl?.trim().replace(/\/+$/, "") ?? "";
936
1003
  if (!cleaned) {
937
1004
  throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
938
1005
  }
@@ -945,28 +1012,39 @@ function resolveIncludeIdMode(includeId, globalIncludeId) {
945
1012
  function shouldIncludeObjectId(mode, _isCollectionItem) {
946
1013
  return mode === "all";
947
1014
  }
948
- function buildSearchParams(query) {
1015
+ function isPlainObject(value) {
1016
+ return !!value && typeof value === "object" && !Array.isArray(value);
1017
+ }
1018
+ function appendQueryValue(params, key, rawValue) {
1019
+ if (rawValue === undefined || rawValue === null)
1020
+ return;
1021
+ if (Array.isArray(rawValue)) {
1022
+ const values = rawValue.filter((value) => value !== undefined && value !== null);
1023
+ if (values.length === 0)
1024
+ return;
1025
+ if (key.endsWith(".fields") ||
1026
+ key.endsWith(".exclude") ||
1027
+ key === "fields" ||
1028
+ key === "exclude") {
1029
+ params.set(key, values.map(String).join(","));
1030
+ return;
1031
+ }
1032
+ values.forEach((value) => params.append(key, String(value)));
1033
+ return;
1034
+ }
1035
+ params.set(key, String(rawValue));
1036
+ }
1037
+ function buildSearchParams(query, graph) {
949
1038
  const params = new URLSearchParams();
950
- if (!query)
951
- return params;
952
- for (const [key, rawValue] of Object.entries(query)) {
953
- if (rawValue === undefined || rawValue === null)
954
- continue;
955
- if (Array.isArray(rawValue)) {
956
- rawValue.forEach((value) => {
957
- if (value === undefined || value === null)
958
- return;
959
- params.append(key, String(value));
960
- });
961
- continue;
1039
+ if (query) {
1040
+ for (const [key, rawValue] of Object.entries(query)) {
1041
+ appendQueryValue(params, key, rawValue);
962
1042
  }
963
- params.set(key, String(rawValue));
964
1043
  }
1044
+ const graphParams = (0, shared_1.serializeGraphQueryOptions)(graph);
1045
+ graphParams.forEach((value, key) => params.set(key, value));
965
1046
  return params;
966
1047
  }
967
- function isPlainObject(value) {
968
- return !!value && typeof value === "object" && !Array.isArray(value);
969
- }
970
1048
  function cloneValue(value) {
971
1049
  if (Array.isArray(value)) {
972
1050
  return value.map((item) => cloneValue(item));
@@ -1114,8 +1192,73 @@ function applyFieldProjection(value, fields, exclude) {
1114
1192
  }
1115
1193
  return projected;
1116
1194
  }
1195
+ function escapeGraphPointerToken(token) {
1196
+ return String(token).replace(/~/g, "~0").replace(/\//g, "~1");
1197
+ }
1198
+ function buildGraphPointerPath(path) {
1199
+ return path.length === 0
1200
+ ? "/"
1201
+ : `/${path.map((token) => escapeGraphPointerToken(token)).join("/")}`;
1202
+ }
1203
+ function buildProjectionPath(path) {
1204
+ return path
1205
+ .filter((token) => typeof token === "string")
1206
+ .map((token) => String(token))
1207
+ .join(".");
1208
+ }
1209
+ function readValueAtPath(value, path) {
1210
+ let current = value;
1211
+ for (const token of path) {
1212
+ if (Array.isArray(current)) {
1213
+ const index = Number(token);
1214
+ if (!Number.isInteger(index) || index < 0)
1215
+ return undefined;
1216
+ current = current[index];
1217
+ continue;
1218
+ }
1219
+ if (!isPlainObject(current))
1220
+ return undefined;
1221
+ current = current[String(token)];
1222
+ }
1223
+ return current;
1224
+ }
1225
+ function buildSetOpsFromPatch(value, path = []) {
1226
+ if (value === undefined)
1227
+ return [];
1228
+ if (Array.isArray(value) || !isPlainObject(value)) {
1229
+ return [{ op: "set", path: buildGraphPointerPath(path), value }];
1230
+ }
1231
+ const entries = Object.entries(value).filter(([, child]) => child !== undefined);
1232
+ if (!entries.length) {
1233
+ return path.length
1234
+ ? [{ op: "set", path: buildGraphPointerPath(path), value }]
1235
+ : [];
1236
+ }
1237
+ return entries.flatMap(([key, child]) => buildSetOpsFromPatch(child, [...path, key]));
1238
+ }
1239
+ function getModelObjectDescriptor(descriptor, modelName) {
1240
+ const model = descriptor.models?.[modelName];
1241
+ if (!model)
1242
+ return undefined;
1243
+ return asModelObjectDescriptor(modelName, descriptor);
1244
+ }
1245
+ function getDescriptorChild(descriptor, field, key) {
1246
+ if (!field)
1247
+ return undefined;
1248
+ if (isModelRefDescriptor(field)) {
1249
+ const model = getModelObjectDescriptor(descriptor, field.model);
1250
+ return getDescriptorChild(descriptor, model, key);
1251
+ }
1252
+ if (isArrayDescriptor(field)) {
1253
+ return getDescriptorChild(descriptor, field.items, key);
1254
+ }
1255
+ if (isObjectDescriptor(field)) {
1256
+ return field.properties[key];
1257
+ }
1258
+ return undefined;
1259
+ }
1117
1260
  async function requestJson(baseUrl, apiKey, path, options) {
1118
- const params = buildSearchParams(options?.query);
1261
+ const params = buildSearchParams(options?.query, options?.graph);
1119
1262
  const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
1120
1263
  const response = await fetch(url, {
1121
1264
  method: "GET",
@@ -1129,6 +1272,34 @@ async function requestJson(baseUrl, apiKey, path, options) {
1129
1272
  }
1130
1273
  return response.json();
1131
1274
  }
1275
+ async function request(baseUrl, apiKey, method, path, body, options) {
1276
+ const params = buildSearchParams(options?.query, options?.graph);
1277
+ const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
1278
+ const headers = {
1279
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
1280
+ };
1281
+ if (body !== undefined) {
1282
+ headers["content-type"] = "application/json";
1283
+ }
1284
+ const response = await fetch(url, {
1285
+ method,
1286
+ signal: options?.signal,
1287
+ headers,
1288
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
1289
+ });
1290
+ if (!response.ok) {
1291
+ throw new Error(`Request failed for '${path}' with status ${response.status}: ${response.statusText}`);
1292
+ }
1293
+ const text = await response.text();
1294
+ if (!text)
1295
+ return undefined;
1296
+ try {
1297
+ return JSON.parse(text);
1298
+ }
1299
+ catch {
1300
+ return text;
1301
+ }
1302
+ }
1132
1303
  function validateResult(resource, result, descriptor, includeIdMode, zodSchemas, modelZodSchemas) {
1133
1304
  if (includeIdMode === "all")
1134
1305
  return;
@@ -1171,13 +1342,13 @@ function attachResourceProvenanceWithOptions(resource, value, options) {
1171
1342
  (0, provenance_1.enableCms0ProvenanceTracking)(true);
1172
1343
  const rootId = resource.kind === "root"
1173
1344
  ? resource.key
1174
- : resource.modelName ?? resource.key;
1345
+ : (resource.modelName ?? resource.key);
1175
1346
  return (0, provenance_1.attachCms0ProvenanceRoot)(value, rootId, options);
1176
1347
  }
1177
1348
  async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId, provenanceOptions) {
1178
1349
  const responseMode = options?.response ?? "normalized";
1179
1350
  const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
1180
- const resolveModelRefs = options?.resolveModelRefs !== false;
1351
+ const resolveModelRefs = options?.graph?.resolveModelRefs !== false;
1181
1352
  const includeProjectionPaths = normalizeProjectionPaths(options?.fields);
1182
1353
  const excludeProjectionPaths = normalizeProjectionPaths(options?.exclude);
1183
1354
  const shouldValidateFullResult = includeProjectionPaths.length === 0 && excludeProjectionPaths.length === 0;
@@ -1185,18 +1356,20 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1185
1356
  (typeof options?.query?.locale === "string"
1186
1357
  ? options.query.locale
1187
1358
  : undefined);
1188
- const locale = requestedLocale ??
1189
- (responseMode === "normalized" ? "all" : undefined);
1359
+ const locale = requestedLocale ?? (responseMode === "normalized" ? "all" : undefined);
1190
1360
  const query = {
1191
1361
  ...(options?.query ?? {}),
1192
1362
  };
1193
- if (options?.fields !== undefined && query.fields === undefined) {
1194
- query.fields = Array.isArray(options.fields)
1363
+ const graphQuery = {
1364
+ ...options?.graph,
1365
+ };
1366
+ if (options?.fields !== undefined && graphQuery.fields === undefined) {
1367
+ graphQuery.fields = Array.isArray(options.fields)
1195
1368
  ? options.fields.join(",")
1196
1369
  : options.fields;
1197
1370
  }
1198
- if (options?.exclude !== undefined && query.exclude === undefined) {
1199
- query.exclude = Array.isArray(options.exclude)
1371
+ if (options?.exclude !== undefined && graphQuery.exclude === undefined) {
1372
+ graphQuery.exclude = Array.isArray(options.exclude)
1200
1373
  ? options.exclude.join(",")
1201
1374
  : options.exclude;
1202
1375
  }
@@ -1204,8 +1377,8 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1204
1377
  if (shouldNormalize && query.raw === undefined) {
1205
1378
  query.raw = 1;
1206
1379
  }
1207
- if (locale && query.locale === undefined) {
1208
- query.locale = locale;
1380
+ if (locale && graphQuery.locale === undefined) {
1381
+ graphQuery.locale = locale;
1209
1382
  }
1210
1383
  const useResolvedEndpoint = shouldNormalize &&
1211
1384
  !byId &&
@@ -1213,20 +1386,25 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1213
1386
  !resource.isCollection &&
1214
1387
  isObjectDescriptor(resource.descriptor) &&
1215
1388
  resolveModelRefs;
1216
- if (useResolvedEndpoint && query.resolveModelRefs === undefined) {
1217
- query.resolveModelRefs = 1;
1389
+ if (useResolvedEndpoint &&
1390
+ graphQuery.resolveModelRefs === undefined) {
1391
+ graphQuery.resolveModelRefs = true;
1392
+ }
1393
+ if (useResolvedEndpoint && graphQuery.pageSize === undefined) {
1394
+ graphQuery.pageSize = "full";
1218
1395
  }
1219
1396
  const normalizePath = byId
1220
1397
  ? `${resource.path}/${encodeURIComponent(byId)}`
1221
1398
  : resource.path;
1222
1399
  const requestPath = useResolvedEndpoint
1223
- ? `_resolved/${encodeURIComponent(resource.key)}`
1400
+ ? `_graph/${encodeURIComponent(resource.key)}`
1224
1401
  : normalizePath;
1225
1402
  let rawData;
1226
1403
  if (useResolvedEndpoint) {
1227
1404
  try {
1228
1405
  rawData = await requestJson(baseUrl, apiKey, requestPath, {
1229
1406
  query,
1407
+ graph: graphQuery,
1230
1408
  signal: options?.signal,
1231
1409
  });
1232
1410
  }
@@ -1234,6 +1412,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1234
1412
  try {
1235
1413
  rawData = await requestJson(baseUrl, apiKey, normalizePath, {
1236
1414
  query,
1415
+ graph: graphQuery,
1237
1416
  signal: options?.signal,
1238
1417
  });
1239
1418
  }
@@ -1245,6 +1424,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1245
1424
  else {
1246
1425
  rawData = await requestJson(baseUrl, apiKey, requestPath, {
1247
1426
  query,
1427
+ graph: graphQuery,
1248
1428
  signal: options?.signal,
1249
1429
  });
1250
1430
  }
@@ -1309,6 +1489,80 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
1309
1489
  }
1310
1490
  return attachResourceProvenanceWithOptions(resource, applyFieldProjection(normalized, options?.fields, options?.exclude), provenanceOptions);
1311
1491
  }
1492
+ function createConcreteGraphAccessor(input, path = [], fieldDescriptor = input.entry.descriptor) {
1493
+ const read = async (options) => {
1494
+ const projectionPath = buildProjectionPath(path);
1495
+ 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
1496
+ ? {
1497
+ ...options,
1498
+ fields: options?.fields === undefined ? projectionPath : options.fields,
1499
+ }
1500
+ : options);
1501
+ return projectionPath ? readValueAtPath(value, path) : value;
1502
+ };
1503
+ const mutate = async (ops) => {
1504
+ if (!ops.length)
1505
+ return;
1506
+ await request(input.baseUrl, input.apiKey, "POST", `_graph/${input.entry.path}/_mutate`, { ops });
1507
+ };
1508
+ const update = async (value) => {
1509
+ await mutate(buildSetOpsFromPatch(value, path));
1510
+ };
1511
+ const set = async (value) => {
1512
+ await mutate([{ op: "set", path: buildGraphPointerPath(path), value }]);
1513
+ };
1514
+ const fieldFn = read;
1515
+ return new Proxy(fieldFn, {
1516
+ get(target, property, receiver) {
1517
+ if (typeof property !== "string") {
1518
+ return Reflect.get(target, property, receiver);
1519
+ }
1520
+ if (property === "then")
1521
+ return undefined;
1522
+ if (property === "update")
1523
+ return update;
1524
+ if (property === "set")
1525
+ return set;
1526
+ if (property === "delete") {
1527
+ return async () => {
1528
+ await mutate([{ op: "delete", path: buildGraphPointerPath(path) }]);
1529
+ };
1530
+ }
1531
+ if (property === "append") {
1532
+ return async (value) => {
1533
+ await mutate([
1534
+ {
1535
+ op: "insert",
1536
+ path: buildGraphPointerPath([...path, "-"]),
1537
+ value,
1538
+ },
1539
+ ]);
1540
+ };
1541
+ }
1542
+ if (property === "insert") {
1543
+ return async (index, value) => {
1544
+ await mutate([
1545
+ {
1546
+ op: "insert",
1547
+ path: buildGraphPointerPath([...path, index]),
1548
+ value,
1549
+ },
1550
+ ]);
1551
+ };
1552
+ }
1553
+ if (property === "at") {
1554
+ return (index) => createConcreteGraphAccessor(input, [...path, index], isArrayDescriptor(fieldDescriptor)
1555
+ ? fieldDescriptor.items
1556
+ : fieldDescriptor);
1557
+ }
1558
+ const childDescriptor = getDescriptorChild(input.descriptor, fieldDescriptor, property);
1559
+ if (childDescriptor) {
1560
+ return createConcreteGraphAccessor(input, [...path, property], childDescriptor);
1561
+ }
1562
+ return Reflect.get(target, property, receiver);
1563
+ },
1564
+ });
1565
+ }
1312
1566
  function createCmsClient(descriptor, config) {
1313
1567
  const baseUrl = normalizeBaseUrl(config.apiConfig?.baseUrl);
1314
1568
  const apiKey = config.apiConfig?.key;
@@ -1321,7 +1575,20 @@ function createCmsClient(descriptor, config) {
1321
1575
  const sharedModelInflightCache = new Map();
1322
1576
  const buildModelAccessor = (entry) => {
1323
1577
  const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options));
1324
- accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id);
1578
+ accessor.byId = (async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id));
1579
+ accessor.list = async (options) => accessor(options);
1580
+ accessor.get = async (id, options) => accessor.byId(id, options);
1581
+ accessor.create = async (value) => {
1582
+ const response = await request(baseUrl, apiKey, "POST", entry.path, value);
1583
+ return response;
1584
+ };
1585
+ accessor.update = async (id, value) => {
1586
+ const response = await request(baseUrl, apiKey, "POST", `_graph/${entry.path}/${encodeURIComponent(id)}/_mutate`, { ops: buildSetOpsFromPatch(value) });
1587
+ return response;
1588
+ };
1589
+ accessor.delete = async (id) => {
1590
+ await request(baseUrl, apiKey, "DELETE", `${entry.path}?id=${encodeURIComponent(id)}`);
1591
+ };
1325
1592
  return accessor;
1326
1593
  };
1327
1594
  const modelsProxy = new Proxy({}, {
@@ -1385,7 +1652,18 @@ function createCmsClient(descriptor, config) {
1385
1652
  if (!entry) {
1386
1653
  unknownKeyError(property, rootKeys, []);
1387
1654
  }
1388
- return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
1655
+ return createConcreteGraphAccessor({
1656
+ apiKey,
1657
+ assetUrlBuilder,
1658
+ baseUrl,
1659
+ defaultLocale: config.defaultLocale,
1660
+ descriptor,
1661
+ entry,
1662
+ globalIncludeId,
1663
+ modelZodSchemas,
1664
+ sharedModelInflightCache,
1665
+ zodSchemas,
1666
+ });
1389
1667
  },
1390
1668
  });
1391
1669
  return rootProxy;
@@ -1427,6 +1705,50 @@ function createLazyBrowserCmsClient(config) {
1427
1705
  throw error;
1428
1706
  }
1429
1707
  };
1708
+ const getNestedAccessor = (client, rootProperty, path) => {
1709
+ let current = client[rootProperty];
1710
+ for (const token of path) {
1711
+ current =
1712
+ typeof token === "number" ? current.at(token) : current[String(token)];
1713
+ }
1714
+ return current;
1715
+ };
1716
+ const functionMemberNames = new Set([
1717
+ "apply",
1718
+ "bind",
1719
+ "call",
1720
+ "hasOwnProperty",
1721
+ "isPrototypeOf",
1722
+ "propertyIsEnumerable",
1723
+ "toLocaleString",
1724
+ "toString",
1725
+ "valueOf",
1726
+ ]);
1727
+ const buildLazyGraphAccessor = (rootProperty, path = []) => {
1728
+ const accessor = (async (options) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)(options)));
1729
+ return new Proxy(accessor, {
1730
+ get(target, property, receiver) {
1731
+ if (typeof property !== "string") {
1732
+ return Reflect.get(target, property, receiver);
1733
+ }
1734
+ if (property === "then")
1735
+ return undefined;
1736
+ if (functionMemberNames.has(property)) {
1737
+ return Reflect.get(target, property, receiver);
1738
+ }
1739
+ if (property === "update" || property === "set" || property === "delete") {
1740
+ return (...args) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)[property](...args));
1741
+ }
1742
+ if (property === "append" || property === "insert") {
1743
+ return (...args) => runBrowserAccessor((client) => getNestedAccessor(client, rootProperty, path)[property](...args));
1744
+ }
1745
+ if (property === "at") {
1746
+ return (index) => buildLazyGraphAccessor(rootProperty, [...path, index]);
1747
+ }
1748
+ return buildLazyGraphAccessor(rootProperty, [...path, property]);
1749
+ },
1750
+ });
1751
+ };
1430
1752
  const buildModelAccessor = (property) => {
1431
1753
  const accessor = (async (options) => {
1432
1754
  return runBrowserAccessor((client) => client.models[property](options));
@@ -1434,6 +1756,21 @@ function createLazyBrowserCmsClient(config) {
1434
1756
  accessor.byId = async (id, options) => {
1435
1757
  return runBrowserAccessor((client) => client.models[property].byId(id, options));
1436
1758
  };
1759
+ accessor.list = async (options) => {
1760
+ return runBrowserAccessor((client) => client.models[property].list(options));
1761
+ };
1762
+ accessor.get = async (id, options) => {
1763
+ return runBrowserAccessor((client) => client.models[property].get(id, options));
1764
+ };
1765
+ accessor.create = async (value) => {
1766
+ return runBrowserAccessor((client) => client.models[property].create(value));
1767
+ };
1768
+ accessor.update = async (id, value) => {
1769
+ return runBrowserAccessor((client) => client.models[property].update(id, value));
1770
+ };
1771
+ accessor.delete = async (id) => {
1772
+ return runBrowserAccessor((client) => client.models[property].delete(id));
1773
+ };
1437
1774
  return accessor;
1438
1775
  };
1439
1776
  const modelsProxy = new Proxy({}, {
@@ -1464,9 +1801,7 @@ function createLazyBrowserCmsClient(config) {
1464
1801
  if (Reflect.has(target, property)) {
1465
1802
  return Reflect.get(target, property, receiver);
1466
1803
  }
1467
- return async (options) => {
1468
- return runBrowserAccessor((client) => client[property](options));
1469
- };
1804
+ return buildLazyGraphAccessor(property);
1470
1805
  },
1471
1806
  });
1472
1807
  return rootProxy;