@cms0/cms0 0.2.3 → 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,9 +1,6 @@
1
1
  import { schemaDescriptor } from "@cms0/cms0/schema-descriptors";
2
2
  import { buildZodSchemasFromDescriptor, } from "@cms0/shared";
3
- const DEFAULT_REQUEST_CONCURRENCY = 6;
4
- const DEFAULT_REQUEST_RETRIES = 3;
5
- const DEFAULT_REQUEST_RETRY_BASE_MS = 250;
6
- const DEFAULT_REQUEST_RETRY_MAX_MS = 4000;
3
+ import { getCustomInlineTypeMetadata } from "./custom-types/registry.js";
7
4
  const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
8
5
  const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
9
6
  function normalizeUploadsPath(uploadsPath) {
@@ -64,310 +61,6 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
64
61
  url: existingUrl ?? assetUrlBuilder(filename),
65
62
  };
66
63
  }
67
- function toPlainText(value) {
68
- if (typeof value === "string")
69
- return value;
70
- if (typeof value === "number" || typeof value === "boolean")
71
- return String(value);
72
- if (!value || typeof value !== "object")
73
- return "";
74
- const node = value;
75
- if (typeof node.text === "string")
76
- return node.text;
77
- if ("value" in node)
78
- return toPlainText(node.value);
79
- if (Array.isArray(node.content)) {
80
- return node.content
81
- .map((child) => toPlainText(child))
82
- .filter(Boolean)
83
- .join(" ")
84
- .trim();
85
- }
86
- return "";
87
- }
88
- function coerceRichTextValue(value) {
89
- const source = value && typeof value === "object" ? value : {};
90
- const nested = source.value && typeof source.value === "object"
91
- ? source.value
92
- : null;
93
- const normalizedValue = source.value !== undefined ? source.value : value ?? {};
94
- const html = typeof source.html === "string"
95
- ? source.html
96
- : typeof nested?.html === "string"
97
- ? nested.html
98
- : "";
99
- return {
100
- value: normalizedValue ?? {},
101
- html,
102
- };
103
- }
104
- function normalizeLocaleTag(value) {
105
- return typeof value === "string" ? value.trim() : "";
106
- }
107
- function normalizeLocalizedMap(source, normalizeValue) {
108
- const record = source && typeof source === "object" ? source : {};
109
- const localesSource = record.locales && typeof record.locales === "object" && !Array.isArray(record.locales)
110
- ? record.locales
111
- : {};
112
- const locales = {};
113
- for (const [locale, raw] of Object.entries(localesSource)) {
114
- const localeKey = normalizeLocaleTag(locale);
115
- if (!localeKey)
116
- continue;
117
- locales[localeKey] = normalizeValue(raw);
118
- }
119
- const explicitDefault = normalizeLocaleTag(record.defaultLocale);
120
- const firstLocale = Object.keys(locales)[0] ?? "";
121
- const defaultLocale = explicitDefault || firstLocale;
122
- return { defaultLocale, locales };
123
- }
124
- function projectLocalizedByLocale(localized, locale) {
125
- if (!locale || locale === "all")
126
- return localized;
127
- const requested = normalizeLocaleTag(locale);
128
- if (!requested)
129
- return localized;
130
- const requestedValue = localized.locales[requested];
131
- if (requestedValue === undefined) {
132
- return {
133
- defaultLocale: requested,
134
- locales: {},
135
- };
136
- }
137
- return {
138
- defaultLocale: requested,
139
- locales: {
140
- [requested]: requestedValue,
141
- },
142
- };
143
- }
144
- function coerceLocalizedStringValue(value, locale, defaultLocale) {
145
- const normalized = normalizeLocalizedMap(value, (entry) => toPlainText(entry));
146
- if (!normalized.defaultLocale && defaultLocale) {
147
- normalized.defaultLocale = defaultLocale;
148
- }
149
- return projectLocalizedByLocale(normalized, locale);
150
- }
151
- function coerceLocalizedRichTextValue(value, locale, defaultLocale) {
152
- const normalized = normalizeLocalizedMap(value, (entry) => coerceRichTextValue(entry));
153
- if (!normalized.defaultLocale && defaultLocale) {
154
- normalized.defaultLocale = defaultLocale;
155
- }
156
- return projectLocalizedByLocale(normalized, locale);
157
- }
158
- function coerceCustomTypeValue(descriptor, value, options) {
159
- const customType = descriptor.customType;
160
- if (!customType)
161
- return value;
162
- if (customType === "RichText")
163
- return coerceRichTextValue(value);
164
- if (customType === "File" || customType === "Image" || customType === "Video") {
165
- return maybeAttachAssetUrl(value, options?.assetUrlBuilder ?? ((filename) => filename));
166
- }
167
- if (customType === "LocalizedString") {
168
- return coerceLocalizedStringValue(value, options?.locale, options?.defaultLocale);
169
- }
170
- if (customType === "LocalizedRichText") {
171
- return coerceLocalizedRichTextValue(value, options?.locale, options?.defaultLocale);
172
- }
173
- return value;
174
- }
175
- function isPrimitiveDescriptor(desc) {
176
- return desc.kind === "primitive";
177
- }
178
- function isModelRefDescriptor(desc) {
179
- return desc.kind === "modelRef";
180
- }
181
- function isObjectDescriptor(desc) {
182
- return desc.type === "object" && !!desc.properties;
183
- }
184
- function isArrayDescriptor(desc) {
185
- return desc.type === "array" && !!desc.items;
186
- }
187
- function asModelObjectDescriptor(modelName, descriptor) {
188
- const model = descriptor.models?.[modelName];
189
- if (!model) {
190
- throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
191
- }
192
- return {
193
- type: "object",
194
- properties: model.properties,
195
- optional: false,
196
- nullable: false,
197
- };
198
- }
199
- function normalizeBaseUrl(baseUrl) {
200
- const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
201
- if (!cleaned) {
202
- throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
203
- }
204
- return cleaned;
205
- }
206
- function resolveIncludeIdMode(includeId) {
207
- if (includeId === true)
208
- return "all";
209
- if (includeId === false)
210
- return "none";
211
- return "collections";
212
- }
213
- function shouldIncludeObjectId(mode, isCollectionItem) {
214
- if (mode === "all")
215
- return true;
216
- if (mode === "collections")
217
- return isCollectionItem;
218
- return false;
219
- }
220
- function buildSearchParams(query) {
221
- const params = new URLSearchParams();
222
- if (!query)
223
- return params;
224
- for (const [key, rawValue] of Object.entries(query)) {
225
- if (rawValue === undefined || rawValue === null)
226
- continue;
227
- if (Array.isArray(rawValue)) {
228
- rawValue.forEach((value) => {
229
- if (value === undefined || value === null)
230
- return;
231
- params.append(key, String(value));
232
- });
233
- continue;
234
- }
235
- params.set(key, String(rawValue));
236
- }
237
- return params;
238
- }
239
- function ensureCollectionEnvelope(data, path) {
240
- if (Array.isArray(data)) {
241
- return { items: data, total: data.length };
242
- }
243
- if (data && typeof data === "object" && Array.isArray(data.items)) {
244
- return {
245
- items: data.items,
246
- total: Number(data.total ?? data.items.length ?? 0),
247
- };
248
- }
249
- throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
250
- }
251
- function clampInteger(value, fallback, minimum) {
252
- if (!Number.isFinite(value))
253
- return fallback;
254
- const normalized = Math.floor(value);
255
- return normalized >= minimum ? normalized : fallback;
256
- }
257
- function normalizeRequestOptions(options) {
258
- const concurrency = clampInteger(options?.concurrency, DEFAULT_REQUEST_CONCURRENCY, 1);
259
- const retries = clampInteger(options?.retries, DEFAULT_REQUEST_RETRIES, 0);
260
- const baseDelayMs = clampInteger(options?.retryBaseMs, DEFAULT_REQUEST_RETRY_BASE_MS, 1);
261
- const maxDelayCandidate = clampInteger(options?.retryMaxMs, DEFAULT_REQUEST_RETRY_MAX_MS, 1);
262
- const maxDelayMs = Math.max(maxDelayCandidate, baseDelayMs);
263
- const modelNormalizationConcurrency = Math.max(concurrency, DEFAULT_MODEL_NORMALIZATION_CONCURRENCY);
264
- return {
265
- concurrency,
266
- retry: {
267
- retries,
268
- baseDelayMs,
269
- maxDelayMs,
270
- },
271
- modelNormalizationConcurrency,
272
- };
273
- }
274
- function createAbortError() {
275
- const error = new Error("The operation was aborted.");
276
- error.name = "AbortError";
277
- return error;
278
- }
279
- function isAbortError(error) {
280
- return !!error && typeof error === "object" && error.name === "AbortError";
281
- }
282
- function waitForDelay(ms, signal) {
283
- if (!Number.isFinite(ms) || ms <= 0) {
284
- if (signal?.aborted)
285
- return Promise.reject(createAbortError());
286
- return Promise.resolve();
287
- }
288
- return new Promise((resolve, reject) => {
289
- if (signal?.aborted) {
290
- reject(createAbortError());
291
- return;
292
- }
293
- const timer = setTimeout(() => {
294
- signal?.removeEventListener("abort", onAbort);
295
- resolve();
296
- }, ms);
297
- function onAbort() {
298
- clearTimeout(timer);
299
- signal?.removeEventListener("abort", onAbort);
300
- reject(createAbortError());
301
- }
302
- signal?.addEventListener("abort", onAbort, { once: true });
303
- });
304
- }
305
- function parseRetryAfterMs(retryAfterValue) {
306
- if (!retryAfterValue)
307
- return null;
308
- const value = retryAfterValue.trim();
309
- if (!value)
310
- return null;
311
- const seconds = Number(value);
312
- if (Number.isFinite(seconds)) {
313
- return Math.max(0, Math.round(seconds * 1000));
314
- }
315
- const at = Date.parse(value);
316
- if (Number.isNaN(at))
317
- return null;
318
- return Math.max(0, at - Date.now());
319
- }
320
- function shouldRetryStatus(status) {
321
- return status === 429 || (status >= 500 && status <= 599);
322
- }
323
- function createRequestScheduler(maxConcurrency) {
324
- let active = 0;
325
- const queue = [];
326
- const pump = () => {
327
- while (active < maxConcurrency && queue.length > 0) {
328
- const next = queue.shift();
329
- if (!next)
330
- continue;
331
- next();
332
- }
333
- };
334
- return (task, signal) => new Promise((resolve, reject) => {
335
- if (signal?.aborted) {
336
- reject(createAbortError());
337
- return;
338
- }
339
- let queued = true;
340
- let settled = false;
341
- const run = () => {
342
- if (settled)
343
- return;
344
- queued = false;
345
- active += 1;
346
- Promise.resolve()
347
- .then(task)
348
- .then(resolve, reject)
349
- .finally(() => {
350
- settled = true;
351
- active = Math.max(0, active - 1);
352
- signal?.removeEventListener("abort", onAbort);
353
- pump();
354
- });
355
- };
356
- const onAbort = () => {
357
- if (!queued || settled)
358
- return;
359
- settled = true;
360
- const index = queue.indexOf(run);
361
- if (index >= 0) {
362
- queue.splice(index, 1);
363
- }
364
- reject(createAbortError());
365
- };
366
- signal?.addEventListener("abort", onAbort, { once: true });
367
- queue.push(run);
368
- pump();
369
- });
370
- }
371
64
  async function mapWithConcurrency(items, concurrency, mapper) {
372
65
  if (items.length === 0)
373
66
  return [];
@@ -449,6 +142,43 @@ function extractModelRefId(raw, modelName, options) {
449
142
  }
450
143
  return null;
451
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
+ }
452
182
  function buildModelRefCacheKey(modelName, id, context, isCollectionItem) {
453
183
  const locale = context.options.locale ?? "";
454
184
  const defaultLocale = context.options.defaultLocale ?? "";
@@ -477,6 +207,18 @@ function extractInlineModelObject(raw, modelDescriptor) {
477
207
  return null;
478
208
  return source;
479
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
+ }
480
222
  function formatValidationError(name, error) {
481
223
  const payload = error && typeof error === "object" && "format" in error
482
224
  ? error.format()
@@ -525,6 +267,126 @@ function unknownKeyError(key, roots, models) {
525
267
  : "";
526
268
  throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
527
269
  }
270
+ function toPlainText(value) {
271
+ if (typeof value === "string")
272
+ return value;
273
+ if (typeof value === "number" || typeof value === "boolean")
274
+ return String(value);
275
+ if (!value || typeof value !== "object")
276
+ return "";
277
+ const node = value;
278
+ if (typeof node.text === "string")
279
+ return node.text;
280
+ if ("value" in node)
281
+ return toPlainText(node.value);
282
+ if (Array.isArray(node.content)) {
283
+ return node.content
284
+ .map((child) => toPlainText(child))
285
+ .filter(Boolean)
286
+ .join(" ")
287
+ .trim();
288
+ }
289
+ return "";
290
+ }
291
+ function coerceRichTextValue(value) {
292
+ const source = value && typeof value === "object" ? value : {};
293
+ const nested = source.value && typeof source.value === "object"
294
+ ? source.value
295
+ : null;
296
+ const normalizedValue = source.value !== undefined ? source.value : value ?? {};
297
+ const html = typeof source.html === "string"
298
+ ? source.html
299
+ : typeof nested?.html === "string"
300
+ ? nested.html
301
+ : "";
302
+ return {
303
+ value: normalizedValue ?? {},
304
+ html,
305
+ };
306
+ }
307
+ function normalizeLocaleTag(value) {
308
+ return typeof value === "string" ? value.trim() : "";
309
+ }
310
+ function normalizeLocalizedMap(source, normalizeValue) {
311
+ const record = source && typeof source === "object" ? source : {};
312
+ const localesSource = record.locales && typeof record.locales === "object" && !Array.isArray(record.locales)
313
+ ? record.locales
314
+ : {};
315
+ const locales = {};
316
+ for (const [locale, raw] of Object.entries(localesSource)) {
317
+ const localeKey = normalizeLocaleTag(locale);
318
+ if (!localeKey)
319
+ continue;
320
+ locales[localeKey] = normalizeValue(raw);
321
+ }
322
+ const explicitDefault = normalizeLocaleTag(record.defaultLocale);
323
+ const firstLocale = Object.keys(locales)[0] ?? "";
324
+ const defaultLocale = explicitDefault || firstLocale;
325
+ return { defaultLocale, locales };
326
+ }
327
+ function projectLocalizedByLocale(localized, locale) {
328
+ if (!locale || locale === "all")
329
+ return localized;
330
+ const requested = normalizeLocaleTag(locale);
331
+ if (!requested)
332
+ return localized;
333
+ const requestedValue = localized.locales[requested];
334
+ if (requestedValue === undefined) {
335
+ return {
336
+ defaultLocale: requested,
337
+ locales: {},
338
+ };
339
+ }
340
+ return {
341
+ defaultLocale: requested,
342
+ locales: {
343
+ [requested]: requestedValue,
344
+ },
345
+ };
346
+ }
347
+ function coerceLocalizedStringValue(value, locale, defaultLocale) {
348
+ const normalized = normalizeLocalizedMap(value, (entry) => toPlainText(entry));
349
+ if (!normalized.defaultLocale && defaultLocale) {
350
+ normalized.defaultLocale = defaultLocale;
351
+ }
352
+ return projectLocalizedByLocale(normalized, locale);
353
+ }
354
+ function coerceLocalizedRichTextValue(value, locale, defaultLocale) {
355
+ const normalized = normalizeLocalizedMap(value, (entry) => coerceRichTextValue(entry));
356
+ if (!normalized.defaultLocale && defaultLocale) {
357
+ normalized.defaultLocale = defaultLocale;
358
+ }
359
+ return projectLocalizedByLocale(normalized, locale);
360
+ }
361
+ function coerceCustomTypeValue(descriptor, value, options) {
362
+ const customType = descriptor.customType;
363
+ if (!customType)
364
+ return value;
365
+ if (customType === "RichText")
366
+ return coerceRichTextValue(value);
367
+ if (customType === "File" || customType === "Image" || customType === "Video") {
368
+ return maybeAttachAssetUrl(value, options?.assetUrlBuilder ?? ((filename) => filename));
369
+ }
370
+ if (customType === "LocalizedString") {
371
+ return coerceLocalizedStringValue(value, options?.locale, options?.defaultLocale);
372
+ }
373
+ if (customType === "LocalizedRichText") {
374
+ return coerceLocalizedRichTextValue(value, options?.locale, options?.defaultLocale);
375
+ }
376
+ return value;
377
+ }
378
+ function ensureCollectionEnvelope(data, path) {
379
+ if (Array.isArray(data)) {
380
+ return { items: data, total: data.length };
381
+ }
382
+ if (data && typeof data === "object" && Array.isArray(data.items)) {
383
+ return {
384
+ items: data.items,
385
+ total: Number(data.total ?? data.items.length ?? 0),
386
+ };
387
+ }
388
+ throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
389
+ }
528
390
  async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
529
391
  if (!id)
530
392
  return null;
@@ -672,7 +534,12 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
672
534
  return value;
673
535
  }
674
536
  const id = extractId(row);
675
- 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 };
676
543
  });
677
544
  }
678
545
  if (isModelRefDescriptor(itemDescriptor)) {
@@ -703,11 +570,16 @@ async function normalizeField(descriptor, path, raw, context, trail, isCollectio
703
570
  const value = raw && typeof raw === "object" && "value" in raw
704
571
  ? raw.value
705
572
  : raw;
706
- return coerceCustomTypeValue(descriptor, value, {
573
+ let coerced = coerceCustomTypeValue(descriptor, value, {
707
574
  locale: context.options.locale,
708
575
  defaultLocale: context.options.defaultLocale,
709
576
  assetUrlBuilder: context.options.assetUrlBuilder,
710
577
  });
578
+ if (shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)) {
579
+ const id = extractId(raw);
580
+ coerced = attachIdForPrimitiveDescriptor(descriptor, coerced, id);
581
+ }
582
+ return coerced;
711
583
  }
712
584
  if (isModelRefDescriptor(descriptor)) {
713
585
  const inlineModelDescriptor = context.modelDescriptors.get(descriptor.model);
@@ -775,71 +647,53 @@ function createResourceRegistry(descriptor) {
775
647
  modelsCaseInsensitive,
776
648
  };
777
649
  }
778
- async function requestJson(baseUrl, apiKey, path, options, runtime) {
779
- const params = buildSearchParams(options?.query);
780
- const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
781
- const execute = async () => {
782
- const retry = runtime?.retry ?? {
783
- retries: DEFAULT_REQUEST_RETRIES,
784
- baseDelayMs: DEFAULT_REQUEST_RETRY_BASE_MS,
785
- maxDelayMs: DEFAULT_REQUEST_RETRY_MAX_MS,
786
- };
787
- let attempt = 0;
788
- while (true) {
789
- let response;
790
- try {
791
- response = runtime?.scheduler
792
- ? await runtime.scheduler(() => fetch(url, {
793
- method: "GET",
794
- signal: options?.signal,
795
- headers: {
796
- ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
797
- },
798
- }), options?.signal)
799
- : await fetch(url, {
800
- method: "GET",
801
- signal: options?.signal,
802
- headers: {
803
- ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
804
- },
805
- });
806
- }
807
- catch (error) {
808
- if (isAbortError(error))
809
- throw error;
810
- if (attempt >= retry.retries)
811
- throw error;
812
- const delayMs = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);
813
- attempt += 1;
814
- await waitForDelay(delayMs, options?.signal);
815
- continue;
816
- }
817
- if (response.ok) {
818
- return response.json();
819
- }
820
- const canRetry = shouldRetryStatus(response.status) && attempt < retry.retries;
821
- if (!canRetry) {
822
- throw new Error(`Request failed for '${path}' with status ${response.status}: ${response.statusText}`);
823
- }
824
- const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
825
- const fallbackDelay = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);
826
- const delayMs = retryAfterMs ?? fallbackDelay;
827
- attempt += 1;
828
- await waitForDelay(delayMs, options?.signal);
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;
829
678
  }
830
- };
831
- const useInflightDedup = !options?.signal && !!runtime;
832
- if (!useInflightDedup || !runtime) {
833
- return execute();
679
+ params.set(key, String(rawValue));
834
680
  }
835
- const cached = runtime.inflightRequestCache.get(url);
836
- if (cached)
837
- return cached;
838
- const promise = execute().finally(() => {
839
- runtime.inflightRequestCache.delete(url);
681
+ return params;
682
+ }
683
+ async function requestJson(baseUrl, apiKey, path, options) {
684
+ const params = buildSearchParams(options?.query);
685
+ const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
686
+ const response = await fetch(url, {
687
+ method: "GET",
688
+ signal: options?.signal,
689
+ headers: {
690
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
691
+ },
840
692
  });
841
- runtime.inflightRequestCache.set(url, promise);
842
- return promise;
693
+ if (!response.ok) {
694
+ throw new Error(`Request failed for '${path}' with status ${response.status}: ${response.statusText}`);
695
+ }
696
+ return response.json();
843
697
  }
844
698
  function validateResult(resource, result, descriptor, includeIdMode, zodSchemas, modelZodSchemas) {
845
699
  if (includeIdMode === "all")
@@ -874,9 +728,9 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
874
728
  throw new Error(formatValidationError(modelName, parsed.error));
875
729
  }
876
730
  }
877
- async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options, byId) {
731
+ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId) {
878
732
  const responseMode = options?.response ?? "normalized";
879
- const includeIdMode = resolveIncludeIdMode(options?.includeId);
733
+ const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
880
734
  const resolveModelRefs = options?.resolveModelRefs !== false;
881
735
  const requestedLocale = options?.locale ??
882
736
  (typeof options?.query?.locale === "string"
@@ -900,7 +754,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
900
754
  const rawData = await requestJson(baseUrl, apiKey, resourcePath, {
901
755
  query,
902
756
  signal: options?.signal,
903
- }, requestRuntime);
757
+ });
904
758
  if (!shouldNormalize) {
905
759
  return rawData;
906
760
  }
@@ -912,7 +766,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
912
766
  requestJson: (path, requestOptions) => requestJson(baseUrl, apiKey, path, {
913
767
  ...requestOptions,
914
768
  signal: options?.signal,
915
- }, requestRuntime),
769
+ }),
916
770
  modelDescriptors,
917
771
  modelCache: new Map(),
918
772
  sharedModelInflightCache,
@@ -922,7 +776,7 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
922
776
  locale,
923
777
  defaultLocale,
924
778
  assetUrlBuilder,
925
- modelNormalizationConcurrency,
779
+ modelNormalizationConcurrency: DEFAULT_MODEL_NORMALIZATION_CONCURRENCY,
926
780
  },
927
781
  };
928
782
  if (resource.isCollection && !byId) {
@@ -958,16 +812,11 @@ export function createCmsClient(descriptor, config) {
958
812
  const rootKeys = Array.from(registry.roots.keys());
959
813
  const modelKeys = Array.from(registry.models.keys());
960
814
  const { zodSchemas, modelZodSchemas } = buildZodSchemasFromDescriptor(descriptor);
961
- const requestOptions = normalizeRequestOptions(config.requests);
962
- const requestRuntime = {
963
- scheduler: createRequestScheduler(requestOptions.concurrency),
964
- inflightRequestCache: new Map(),
965
- retry: requestOptions.retry,
966
- };
815
+ const globalIncludeId = !!config.includeId;
967
816
  const sharedModelInflightCache = new Map();
968
817
  const buildModelAccessor = (entry) => {
969
- const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options));
970
- accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, 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);
971
820
  return accessor;
972
821
  };
973
822
  const modelsProxy = new Proxy({}, {
@@ -997,10 +846,16 @@ export function createCmsClient(descriptor, config) {
997
846
  if (!entry) {
998
847
  unknownKeyError(property, rootKeys, []);
999
848
  }
1000
- return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options);
849
+ return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
1001
850
  },
1002
851
  });
1003
- 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;
1004
859
  }
1005
860
  /**
1006
861
  * Initializes the CMS SDK for a given schema.