@cms0/cms0 0.2.2 → 0.2.4

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.
@@ -4,6 +4,8 @@ exports.createCmsClient = createCmsClient;
4
4
  exports.cms0 = cms0;
5
5
  const schema_descriptors_1 = require("@cms0/cms0/schema-descriptors");
6
6
  const shared_1 = require("@cms0/shared");
7
+ const registry_js_1 = require("./custom-types/registry.cjs");
8
+ const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
7
9
  const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
8
10
  function normalizeUploadsPath(uploadsPath) {
9
11
  const raw = uploadsPath?.trim() || "/uploads";
@@ -63,6 +65,212 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
63
65
  url: existingUrl ?? assetUrlBuilder(filename),
64
66
  };
65
67
  }
68
+ async function mapWithConcurrency(items, concurrency, mapper) {
69
+ if (items.length === 0)
70
+ return [];
71
+ const limit = Math.max(1, Math.floor(concurrency));
72
+ if (limit >= items.length) {
73
+ return Promise.all(items.map((item, index) => mapper(item, index)));
74
+ }
75
+ const results = new Array(items.length);
76
+ let nextIndex = 0;
77
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
78
+ while (true) {
79
+ const current = nextIndex++;
80
+ if (current >= items.length)
81
+ break;
82
+ results[current] = await mapper(items[current], current);
83
+ }
84
+ });
85
+ await Promise.all(workers);
86
+ return results;
87
+ }
88
+ function extractId(value) {
89
+ if (typeof value === "string" && value.length > 0)
90
+ return value;
91
+ if (value && typeof value === "object" && typeof value.id === "string") {
92
+ return value.id;
93
+ }
94
+ return null;
95
+ }
96
+ function capitalize(input) {
97
+ return input ? input[0].toUpperCase() + input.slice(1) : input;
98
+ }
99
+ function lowerFirst(input) {
100
+ return input ? input[0].toLowerCase() + input.slice(1) : input;
101
+ }
102
+ function camelCaseModelIdKey(modelName) {
103
+ return `${lowerFirst(modelName)}Id`;
104
+ }
105
+ function snakeCaseModelIdKey(modelName) {
106
+ const snake = modelName
107
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
108
+ .replace(/\s+/g, "_")
109
+ .toLowerCase();
110
+ return `${snake}_id`;
111
+ }
112
+ function extractModelRefId(raw, modelName, options) {
113
+ if (typeof raw === "string")
114
+ return raw;
115
+ if (!raw || typeof raw !== "object")
116
+ return null;
117
+ const object = raw;
118
+ const prop = options?.propertyName ?? "";
119
+ const propertyCandidates = [
120
+ prop ? `${prop}Id` : "",
121
+ prop
122
+ ? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
123
+ : "",
124
+ ].filter(Boolean);
125
+ const modelCandidates = [
126
+ camelCaseModelIdKey(modelName),
127
+ snakeCaseModelIdKey(modelName),
128
+ ];
129
+ // Prefer property-specific FKs (e.g., logoId) over model-level defaults
130
+ // (e.g., imageId) when multiple refs share the same model in one object row.
131
+ const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
132
+ for (const key of candidates) {
133
+ const found = extractId(object[key]);
134
+ if (found)
135
+ return found;
136
+ }
137
+ const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
138
+ if (allowObjectIdFallback) {
139
+ const keys = Object.keys(object);
140
+ const hasOnlyId = keys.length === 1 && keys[0] === "id";
141
+ if (hasOnlyId) {
142
+ const byId = extractId(object);
143
+ if (byId)
144
+ return byId;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+ function isPrimitiveDescriptor(desc) {
150
+ return desc.kind === "primitive";
151
+ }
152
+ function isModelRefDescriptor(desc) {
153
+ return desc.kind === "modelRef";
154
+ }
155
+ function isObjectDescriptor(desc) {
156
+ return desc.type === "object" && !!desc.properties;
157
+ }
158
+ function isArrayDescriptor(desc) {
159
+ return desc.type === "array" && !!desc.items;
160
+ }
161
+ function attachIdIfObject(value, id) {
162
+ if (!id)
163
+ return value;
164
+ if (value && typeof value === "object" && !Array.isArray(value) && !value.id) {
165
+ return { ...value, id };
166
+ }
167
+ return value;
168
+ }
169
+ function attachIdForPrimitiveDescriptor(descriptor, value, id) {
170
+ const withId = attachIdIfObject(value, id);
171
+ if (!withId || typeof withId !== "object" || Array.isArray(withId)) {
172
+ return withId;
173
+ }
174
+ const customType = descriptor.customType;
175
+ if (typeof customType !== "string") {
176
+ return withId;
177
+ }
178
+ const metadata = (0, registry_js_1.getCustomInlineTypeMetadata)(customType);
179
+ if (!metadata?.includeId?.mapLikeFields?.length) {
180
+ return withId;
181
+ }
182
+ // Map-like objects (e.g., locales) are data containers, not entity rows.
183
+ // Keep them untouched by id propagation.
184
+ return { ...withId };
185
+ }
186
+ function buildModelRefCacheKey(modelName, id, context, isCollectionItem) {
187
+ const locale = context.options.locale ?? "";
188
+ const defaultLocale = context.options.defaultLocale ?? "";
189
+ const includeIdMode = context.options.includeIdMode;
190
+ const refsMode = context.options.resolveModelRefs ? "resolve" : "ids";
191
+ const collectionFlag = isCollectionItem ? "collection" : "single";
192
+ return [
193
+ modelName,
194
+ id,
195
+ locale,
196
+ defaultLocale,
197
+ includeIdMode,
198
+ refsMode,
199
+ collectionFlag,
200
+ ].join("|");
201
+ }
202
+ function extractInlineModelObject(raw, modelDescriptor) {
203
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
204
+ return null;
205
+ if (!isObjectDescriptor(modelDescriptor))
206
+ return null;
207
+ const source = raw;
208
+ const propertyKeys = Object.keys(modelDescriptor.properties ?? {});
209
+ const hasModelField = propertyKeys.some((key) => key in source);
210
+ if (!hasModelField)
211
+ return null;
212
+ return source;
213
+ }
214
+ function asModelObjectDescriptor(modelName, descriptor) {
215
+ const model = descriptor.models?.[modelName];
216
+ if (!model) {
217
+ throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
218
+ }
219
+ return {
220
+ type: "object",
221
+ properties: model.properties,
222
+ optional: false,
223
+ nullable: false,
224
+ };
225
+ }
226
+ function formatValidationError(name, error) {
227
+ const payload = error && typeof error === "object" && "format" in error
228
+ ? error.format()
229
+ : error;
230
+ return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
231
+ }
232
+ function levenshtein(a, b) {
233
+ if (a === b)
234
+ return 0;
235
+ if (!a.length)
236
+ return b.length;
237
+ if (!b.length)
238
+ return a.length;
239
+ const rows = a.length + 1;
240
+ const cols = b.length + 1;
241
+ const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
242
+ for (let i = 0; i < rows; i++)
243
+ matrix[i][0] = i;
244
+ for (let j = 0; j < cols; j++)
245
+ matrix[0][j] = j;
246
+ for (let i = 1; i < rows; i++) {
247
+ for (let j = 1; j < cols; j++) {
248
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
249
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
250
+ }
251
+ }
252
+ return matrix[rows - 1][cols - 1];
253
+ }
254
+ function unknownKeyError(key, roots, models) {
255
+ const candidates = [...roots, ...models];
256
+ const normalized = key.toLowerCase();
257
+ const suggestions = candidates
258
+ .map((candidate) => ({
259
+ candidate,
260
+ distance: levenshtein(normalized, candidate.toLowerCase()),
261
+ }))
262
+ .sort((a, b) => a.distance - b.distance)
263
+ .slice(0, 3)
264
+ .map((entry) => entry.candidate);
265
+ const suggestionText = suggestions.length
266
+ ? ` Did you mean: ${suggestions.join(", ")}?`
267
+ : "";
268
+ const rootsText = `Available roots: [${roots.join(", ")}]`;
269
+ const modelsText = models.length
270
+ ? `, models (under data.models.*): [${models.join(", ")}]`
271
+ : "";
272
+ throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
273
+ }
66
274
  function toPlainText(value) {
67
275
  if (typeof value === "string")
68
276
  return value;
@@ -171,70 +379,6 @@ function coerceCustomTypeValue(descriptor, value, options) {
171
379
  }
172
380
  return value;
173
381
  }
174
- function isPrimitiveDescriptor(desc) {
175
- return desc.kind === "primitive";
176
- }
177
- function isModelRefDescriptor(desc) {
178
- return desc.kind === "modelRef";
179
- }
180
- function isObjectDescriptor(desc) {
181
- return desc.type === "object" && !!desc.properties;
182
- }
183
- function isArrayDescriptor(desc) {
184
- return desc.type === "array" && !!desc.items;
185
- }
186
- function asModelObjectDescriptor(modelName, descriptor) {
187
- const model = descriptor.models?.[modelName];
188
- if (!model) {
189
- throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
190
- }
191
- return {
192
- type: "object",
193
- properties: model.properties,
194
- optional: false,
195
- nullable: false,
196
- };
197
- }
198
- function normalizeBaseUrl(baseUrl) {
199
- const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
200
- if (!cleaned) {
201
- throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
202
- }
203
- return cleaned;
204
- }
205
- function resolveIncludeIdMode(includeId) {
206
- if (includeId === true)
207
- return "all";
208
- if (includeId === false)
209
- return "none";
210
- return "collections";
211
- }
212
- function shouldIncludeObjectId(mode, isCollectionItem) {
213
- if (mode === "all")
214
- return true;
215
- if (mode === "collections")
216
- return isCollectionItem;
217
- return false;
218
- }
219
- function buildSearchParams(query) {
220
- const params = new URLSearchParams();
221
- if (!query)
222
- return params;
223
- for (const [key, rawValue] of Object.entries(query)) {
224
- if (rawValue === undefined || rawValue === null)
225
- continue;
226
- if (Array.isArray(rawValue)) {
227
- rawValue.forEach((value) => {
228
- if (value === undefined || value === null)
229
- return;
230
- params.append(key, String(value));
231
- });
232
- continue;
233
- }
234
- params.set(key, String(rawValue));
235
- }
236
- return params;
237
- }
238
382
  function ensureCollectionEnvelope(data, path) {
239
383
  if (Array.isArray(data)) {
240
384
  return { items: data, total: data.length };
@@ -247,121 +391,12 @@ function ensureCollectionEnvelope(data, path) {
247
391
  }
248
392
  throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
249
393
  }
250
- function extractId(value) {
251
- if (typeof value === "string" && value.length > 0)
252
- return value;
253
- if (value && typeof value === "object" && typeof value.id === "string") {
254
- return value.id;
255
- }
256
- return null;
257
- }
258
- function capitalize(input) {
259
- return input ? input[0].toUpperCase() + input.slice(1) : input;
260
- }
261
- function lowerFirst(input) {
262
- return input ? input[0].toLowerCase() + input.slice(1) : input;
263
- }
264
- function camelCaseModelIdKey(modelName) {
265
- return `${lowerFirst(modelName)}Id`;
266
- }
267
- function snakeCaseModelIdKey(modelName) {
268
- const snake = modelName
269
- .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
270
- .replace(/\s+/g, "_")
271
- .toLowerCase();
272
- return `${snake}_id`;
273
- }
274
- function extractModelRefId(raw, modelName, options) {
275
- if (typeof raw === "string")
276
- return raw;
277
- if (!raw || typeof raw !== "object")
278
- return null;
279
- const object = raw;
280
- const prop = options?.propertyName ?? "";
281
- const propertyCandidates = [
282
- prop ? `${prop}Id` : "",
283
- prop
284
- ? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
285
- : "",
286
- ].filter(Boolean);
287
- const modelCandidates = [
288
- camelCaseModelIdKey(modelName),
289
- snakeCaseModelIdKey(modelName),
290
- ];
291
- // Prefer property-specific FKs (e.g., logoId) over model-level defaults
292
- // (e.g., imageId) when multiple refs share the same model in one object row.
293
- const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
294
- for (const key of candidates) {
295
- const found = extractId(object[key]);
296
- if (found)
297
- return found;
298
- }
299
- const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
300
- if (allowObjectIdFallback) {
301
- const keys = Object.keys(object);
302
- const hasOnlyId = keys.length === 1 && keys[0] === "id";
303
- if (hasOnlyId) {
304
- const byId = extractId(object);
305
- if (byId)
306
- return byId;
307
- }
308
- }
309
- return null;
310
- }
311
- function formatValidationError(name, error) {
312
- const payload = error && typeof error === "object" && "format" in error
313
- ? error.format()
314
- : error;
315
- return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
316
- }
317
- function levenshtein(a, b) {
318
- if (a === b)
319
- return 0;
320
- if (!a.length)
321
- return b.length;
322
- if (!b.length)
323
- return a.length;
324
- const rows = a.length + 1;
325
- const cols = b.length + 1;
326
- const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
327
- for (let i = 0; i < rows; i++)
328
- matrix[i][0] = i;
329
- for (let j = 0; j < cols; j++)
330
- matrix[0][j] = j;
331
- for (let i = 1; i < rows; i++) {
332
- for (let j = 1; j < cols; j++) {
333
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
334
- matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
335
- }
336
- }
337
- return matrix[rows - 1][cols - 1];
338
- }
339
- function unknownKeyError(key, roots, models) {
340
- const candidates = [...roots, ...models];
341
- const normalized = key.toLowerCase();
342
- const suggestions = candidates
343
- .map((candidate) => ({
344
- candidate,
345
- distance: levenshtein(normalized, candidate.toLowerCase()),
346
- }))
347
- .sort((a, b) => a.distance - b.distance)
348
- .slice(0, 3)
349
- .map((entry) => entry.candidate);
350
- const suggestionText = suggestions.length
351
- ? ` Did you mean: ${suggestions.join(", ")}?`
352
- : "";
353
- const rootsText = `Available roots: [${roots.join(", ")}]`;
354
- const modelsText = models.length
355
- ? `, models (under data.models.*): [${models.join(", ")}]`
356
- : "";
357
- throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
358
- }
359
- async function normalizeModelRef(modelName, id, context, trail, isCollectionItem) {
394
+ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
360
395
  if (!id)
361
396
  return null;
362
397
  if (!context.options.resolveModelRefs)
363
398
  return id;
364
- const nodeKey = `${modelName}:${id}`;
399
+ const nodeKey = buildModelRefCacheKey(modelName, id, context, isCollectionItem);
365
400
  if (trail.has(nodeKey)) {
366
401
  return shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)
367
402
  ? { id }
@@ -371,25 +406,37 @@ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem
371
406
  if (!modelDescriptor) {
372
407
  return id;
373
408
  }
409
+ const inlineModel = extractInlineModelObject(inlineValue, modelDescriptor);
374
410
  const cached = context.modelCache.get(nodeKey);
375
411
  if (cached) {
376
412
  return cached;
377
413
  }
414
+ const sharedInflight = context.sharedModelInflightCache.get(nodeKey);
415
+ if (sharedInflight) {
416
+ context.modelCache.set(nodeKey, sharedInflight);
417
+ return sharedInflight;
418
+ }
378
419
  const nextTrail = new Set(trail);
379
420
  nextTrail.add(nodeKey);
380
421
  const promise = (async () => {
381
- const rawModel = await context.requestJson(`models/${modelName}/${id}`, {
382
- query: {
383
- raw: 1,
384
- ...(context.options.locale ? { locale: context.options.locale } : {}),
385
- },
386
- });
422
+ const rawModel = inlineModel ??
423
+ (await context.requestJson(`models/${modelName}/${id}`, {
424
+ query: {
425
+ raw: 1,
426
+ ...(context.options.locale ? { locale: context.options.locale } : {}),
427
+ },
428
+ }));
387
429
  return normalizeField(modelDescriptor, `models/${modelName}/${encodeURIComponent(id)}`, rawModel, context, nextTrail, isCollectionItem);
388
- })().catch((error) => {
430
+ })();
431
+ context.modelCache.set(nodeKey, promise);
432
+ context.sharedModelInflightCache.set(nodeKey, promise);
433
+ promise
434
+ .then(() => undefined, () => {
389
435
  context.modelCache.delete(nodeKey);
390
- throw error;
436
+ })
437
+ .finally(() => {
438
+ context.sharedModelInflightCache.delete(nodeKey);
391
439
  });
392
- context.modelCache.set(nodeKey, promise);
393
440
  return promise;
394
441
  }
395
442
  function missingModelRefValue(descriptor) {
@@ -412,10 +459,19 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
412
459
  continue;
413
460
  }
414
461
  if (isModelRefDescriptor(propertyDescriptor)) {
415
- const refId = extractModelRefId(source, propertyDescriptor.model, {
416
- propertyName,
417
- allowObjectIdFallback: false,
418
- });
462
+ const inlineValue = source[propertyName];
463
+ const inlineModelDescriptor = context.modelDescriptors.get(propertyDescriptor.model);
464
+ const inlineModel = inlineModelDescriptor
465
+ ? extractInlineModelObject(inlineValue, inlineModelDescriptor)
466
+ : null;
467
+ const refId = extractModelRefId(inlineValue, propertyDescriptor.model, {
468
+ allowObjectIdFallback: true,
469
+ }) ??
470
+ (inlineModel ? extractId(inlineModel) : null) ??
471
+ extractModelRefId(source, propertyDescriptor.model, {
472
+ propertyName,
473
+ allowObjectIdFallback: false,
474
+ });
419
475
  if (!refId) {
420
476
  const missing = missingModelRefValue(propertyDescriptor);
421
477
  if (missing !== undefined) {
@@ -423,7 +479,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
423
479
  }
424
480
  continue;
425
481
  }
426
- output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false);
482
+ output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false, inlineValue);
427
483
  continue;
428
484
  }
429
485
  if (isArrayDescriptor(propertyDescriptor)) {
@@ -482,26 +538,34 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
482
538
  return value;
483
539
  }
484
540
  const id = extractId(row);
485
- return id ? { id, value } : value;
541
+ const valueWithId = attachIdForPrimitiveDescriptor(itemDescriptor, value, id);
542
+ const isObjectValue = valueWithId && typeof valueWithId === "object" && !Array.isArray(valueWithId);
543
+ if (!id) {
544
+ return valueWithId;
545
+ }
546
+ return isObjectValue ? { id, ...valueWithId } : { id, value: valueWithId };
486
547
  });
487
548
  }
488
549
  if (isModelRefDescriptor(itemDescriptor)) {
489
- const resolved = await Promise.all(envelope.items.map(async (row) => {
550
+ const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
551
+ return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
552
+ const inlineModel = inlineModelDescriptor
553
+ ? extractInlineModelObject(row, inlineModelDescriptor)
554
+ : null;
490
555
  const refId = extractModelRefId(row, itemDescriptor.model, {
491
556
  allowObjectIdFallback: false,
492
- });
493
- return normalizeModelRef(itemDescriptor.model, refId, context, trail, true);
494
- }));
495
- return resolved;
557
+ }) ?? (inlineModel ? extractId(inlineModel) : null);
558
+ return normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
559
+ });
496
560
  }
497
561
  if (isObjectDescriptor(itemDescriptor)) {
498
- return Promise.all(envelope.items.map(async (row) => {
562
+ return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
499
563
  const rowId = extractId(row);
500
564
  const rowPath = rowId
501
565
  ? `${path}/${encodeURIComponent(rowId)}`
502
566
  : path;
503
567
  return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true);
504
- }));
568
+ });
505
569
  }
506
570
  return envelope.items;
507
571
  }
@@ -510,18 +574,29 @@ async function normalizeField(descriptor, path, raw, context, trail, isCollectio
510
574
  const value = raw && typeof raw === "object" && "value" in raw
511
575
  ? raw.value
512
576
  : raw;
513
- return coerceCustomTypeValue(descriptor, value, {
577
+ let coerced = coerceCustomTypeValue(descriptor, value, {
514
578
  locale: context.options.locale,
515
579
  defaultLocale: context.options.defaultLocale,
516
580
  assetUrlBuilder: context.options.assetUrlBuilder,
517
581
  });
582
+ if (shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)) {
583
+ const id = extractId(raw);
584
+ coerced = attachIdForPrimitiveDescriptor(descriptor, coerced, id);
585
+ }
586
+ return coerced;
518
587
  }
519
588
  if (isModelRefDescriptor(descriptor)) {
520
- const refId = extractModelRefId(raw, descriptor.model);
589
+ const inlineModelDescriptor = context.modelDescriptors.get(descriptor.model);
590
+ const inlineModel = inlineModelDescriptor
591
+ ? extractInlineModelObject(raw, inlineModelDescriptor)
592
+ : null;
593
+ const refId = extractModelRefId(raw, descriptor.model, {
594
+ allowObjectIdFallback: true,
595
+ }) ?? (inlineModel ? extractId(inlineModel) : null);
521
596
  if (!refId) {
522
597
  return missingModelRefValue(descriptor);
523
598
  }
524
- return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem);
599
+ return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem, raw);
525
600
  }
526
601
  if (isArrayDescriptor(descriptor)) {
527
602
  const normalized = await normalizeArrayField(descriptor, path, raw, context, trail);
@@ -576,6 +651,39 @@ function createResourceRegistry(descriptor) {
576
651
  modelsCaseInsensitive,
577
652
  };
578
653
  }
654
+ function normalizeBaseUrl(baseUrl) {
655
+ const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
656
+ if (!cleaned) {
657
+ throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
658
+ }
659
+ return cleaned;
660
+ }
661
+ function resolveIncludeIdMode(includeId, globalIncludeId) {
662
+ const enabled = includeId ?? globalIncludeId ?? false;
663
+ return enabled ? "all" : "none";
664
+ }
665
+ function shouldIncludeObjectId(mode, _isCollectionItem) {
666
+ return mode === "all";
667
+ }
668
+ function buildSearchParams(query) {
669
+ const params = new URLSearchParams();
670
+ if (!query)
671
+ return params;
672
+ for (const [key, rawValue] of Object.entries(query)) {
673
+ if (rawValue === undefined || rawValue === null)
674
+ continue;
675
+ if (Array.isArray(rawValue)) {
676
+ rawValue.forEach((value) => {
677
+ if (value === undefined || value === null)
678
+ return;
679
+ params.append(key, String(value));
680
+ });
681
+ continue;
682
+ }
683
+ params.set(key, String(rawValue));
684
+ }
685
+ return params;
686
+ }
579
687
  async function requestJson(baseUrl, apiKey, path, options) {
580
688
  const params = buildSearchParams(options?.query);
581
689
  const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
@@ -624,9 +732,9 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
624
732
  throw new Error(formatValidationError(modelName, parsed.error));
625
733
  }
626
734
  }
627
- async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, byId) {
735
+ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId) {
628
736
  const responseMode = options?.response ?? "normalized";
629
- const includeIdMode = resolveIncludeIdMode(options?.includeId);
737
+ const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
630
738
  const resolveModelRefs = options?.resolveModelRefs !== false;
631
739
  const requestedLocale = options?.locale ??
632
740
  (typeof options?.query?.locale === "string"
@@ -665,12 +773,14 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
665
773
  }),
666
774
  modelDescriptors,
667
775
  modelCache: new Map(),
776
+ sharedModelInflightCache,
668
777
  options: {
669
778
  includeIdMode,
670
779
  resolveModelRefs,
671
780
  locale,
672
781
  defaultLocale,
673
782
  assetUrlBuilder,
783
+ modelNormalizationConcurrency: DEFAULT_MODEL_NORMALIZATION_CONCURRENCY,
674
784
  },
675
785
  };
676
786
  if (resource.isCollection && !byId) {
@@ -706,9 +816,11 @@ function createCmsClient(descriptor, config) {
706
816
  const rootKeys = Array.from(registry.roots.keys());
707
817
  const modelKeys = Array.from(registry.models.keys());
708
818
  const { zodSchemas, modelZodSchemas } = (0, shared_1.buildZodSchemasFromDescriptor)(descriptor);
819
+ const globalIncludeId = !!config.includeId;
820
+ const sharedModelInflightCache = new Map();
709
821
  const buildModelAccessor = (entry) => {
710
- const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options));
711
- accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, id);
822
+ const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options));
823
+ accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id);
712
824
  return accessor;
713
825
  };
714
826
  const modelsProxy = new Proxy({}, {
@@ -738,10 +850,16 @@ function createCmsClient(descriptor, config) {
738
850
  if (!entry) {
739
851
  unknownKeyError(property, rootKeys, []);
740
852
  }
741
- return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options);
853
+ return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
742
854
  },
743
855
  });
744
- return rootProxy;
856
+ const client = Object.assign(rootProxy, {
857
+ models: modelsProxy,
858
+ locales: config.locales ?? [],
859
+ defaultLocale: config.defaultLocale,
860
+ includeIdDefault: globalIncludeId,
861
+ });
862
+ return client;
745
863
  }
746
864
  /**
747
865
  * Initializes the CMS SDK for a given schema.
@@ -59,6 +59,15 @@ export const customInlineDescriptors = {
59
59
  LocalizedString: localizedStringDescriptor,
60
60
  LocalizedRichText: localizedRichTextDescriptor,
61
61
  };
62
+ export const customInlineTypeMetadata = {
63
+ RichText: {},
64
+ LocalizedString: {
65
+ includeId: { mapLikeFields: ["locales"] },
66
+ },
67
+ LocalizedRichText: {
68
+ includeId: { mapLikeFields: ["locales"] },
69
+ },
70
+ };
62
71
  export const customTypeDescriptors = {
63
72
  ...customModelDescriptors,
64
73
  ...customInlineDescriptors,
@@ -66,6 +75,9 @@ export const customTypeDescriptors = {
66
75
  export const customTypeNames = new Set(Object.keys(customTypeDescriptors));
67
76
  export const customModelTypeNames = new Set(Object.keys(customModelDescriptors));
68
77
  export const customInlineTypeNames = new Set(Object.keys(customInlineDescriptors));
78
+ export function getCustomInlineTypeMetadata(name) {
79
+ return customInlineTypeMetadata[name];
80
+ }
69
81
  export function resolveCustomTypeDependencies(names) {
70
82
  const resolved = new Set();
71
83
  const queue = Array.from(names);