@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/cjs/custom-types/registry.cjs +14 -1
- package/dist/cjs/index-old.cjs +1016 -0
- package/dist/cjs/index.cjs +242 -387
- package/dist/esm/custom-types/registry.js +12 -0
- package/dist/esm/index-old.js +1012 -0
- package/dist/esm/index.js +242 -387
- package/dist/types/custom-types/registry.d.ts +8 -0
- package/dist/types/custom-types/registry.d.ts.map +1 -1
- package/dist/types/index-old.d.ts +151 -0
- package/dist/types/index-old.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -8
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
|
|
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
|
-
|
|
842
|
-
|
|
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,
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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
|
|
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,
|
|
970
|
-
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder,
|
|
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,
|
|
849
|
+
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
|
|
1001
850
|
},
|
|
1002
851
|
});
|
|
1003
|
-
|
|
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.
|