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