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