@cms0/cms0 0.2.20 → 0.2.22
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/README.md +11 -0
- package/dist/cjs/custom-types/registry.cjs +6 -0
- package/dist/cjs/index.cjs +400 -65
- package/dist/cjs/libs/cli/config-loader.cjs +45 -4
- package/dist/cjs/libs/cli/publisher.cjs +24 -11
- package/dist/esm/custom-types/registry.js +6 -0
- package/dist/esm/index.js +401 -66
- package/dist/esm/libs/cli/config-loader.js +45 -4
- package/dist/esm/libs/cli/publisher.js +24 -11
- package/dist/types/custom-types/index.d.ts +2 -1
- package/dist/types/custom-types/index.d.ts.map +1 -1
- package/dist/types/custom-types/registry.d.ts.map +1 -1
- package/dist/types/index.d.ts +131 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/libs/cli/config-loader.d.ts.map +1 -1
- package/dist/types/libs/cli/publisher.d.ts.map +1 -1
- package/package.json +13 -9
- package/dist/cjs/index-old-1.cjs +0 -866
- package/dist/cjs/index-old.cjs +0 -1016
- package/dist/cjs/libs/cli/descriptor-builder.cjs +0 -273
- package/dist/cjs/utils/index.cjs +0 -2
- package/dist/esm/index-old-1.js +0 -862
- package/dist/esm/index-old.js +0 -1012
- package/dist/esm/libs/cli/descriptor-builder.js +0 -268
- package/dist/esm/utils/index.js +0 -1
- package/dist/types/index-old-1.d.ts +0 -175
- package/dist/types/index-old-1.d.ts.map +0 -1
- package/dist/types/index-old.d.ts +0 -151
- package/dist/types/index-old.d.ts.map +0 -1
- package/dist/types/libs/cli/descriptor-builder.d.ts +0 -5
- package/dist/types/libs/cli/descriptor-builder.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts +0 -2
- package/dist/types/utils/index.d.ts.map +0 -1
package/dist/cjs/index-old.cjs
DELETED
|
@@ -1,1016 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createCmsClient = createCmsClient;
|
|
4
|
-
exports.cms0 = cms0;
|
|
5
|
-
const schema_descriptors_1 = require("@cms0/cms0/schema-descriptors");
|
|
6
|
-
const shared_1 = require("@cms0/shared");
|
|
7
|
-
const DEFAULT_REQUEST_CONCURRENCY = 6;
|
|
8
|
-
const DEFAULT_REQUEST_RETRIES = 3;
|
|
9
|
-
const DEFAULT_REQUEST_RETRY_BASE_MS = 250;
|
|
10
|
-
const DEFAULT_REQUEST_RETRY_MAX_MS = 4000;
|
|
11
|
-
const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
|
|
12
|
-
const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
|
|
13
|
-
function normalizeUploadsPath(uploadsPath) {
|
|
14
|
-
const raw = uploadsPath?.trim() || "/uploads";
|
|
15
|
-
const withLeadingSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
16
|
-
const normalized = withLeadingSlash.replace(/\/+$/, "");
|
|
17
|
-
return normalized || "/uploads";
|
|
18
|
-
}
|
|
19
|
-
function normalizeAssetBaseUrl(baseUrl) {
|
|
20
|
-
const trimmed = baseUrl?.trim() ?? "";
|
|
21
|
-
return trimmed.replace(/\/+$/, "");
|
|
22
|
-
}
|
|
23
|
-
function encodeFilenameForUrl(filename) {
|
|
24
|
-
const normalized = filename.replace(/\\/g, "/").replace(/^\/+/, "").trim();
|
|
25
|
-
if (!normalized)
|
|
26
|
-
return "";
|
|
27
|
-
return normalized
|
|
28
|
-
.split("/")
|
|
29
|
-
.filter(Boolean)
|
|
30
|
-
.map((segment) => encodeURIComponent(segment))
|
|
31
|
-
.join("/");
|
|
32
|
-
}
|
|
33
|
-
function buildAssetUrlBuilder(apiBaseUrl, assetOptions) {
|
|
34
|
-
const uploadsPath = normalizeUploadsPath(assetOptions?.uploadsPath);
|
|
35
|
-
const explicitBaseUrl = normalizeAssetBaseUrl(assetOptions?.baseUrl);
|
|
36
|
-
if (explicitBaseUrl) {
|
|
37
|
-
return (filename) => `${explicitBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
|
|
38
|
-
}
|
|
39
|
-
let inferredBaseUrl = "";
|
|
40
|
-
try {
|
|
41
|
-
inferredBaseUrl = new URL(apiBaseUrl).origin;
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
inferredBaseUrl = "";
|
|
45
|
-
}
|
|
46
|
-
return (filename) => `${inferredBaseUrl}${uploadsPath}/${encodeFilenameForUrl(filename)}`;
|
|
47
|
-
}
|
|
48
|
-
function maybeAttachAssetUrl(value, assetUrlBuilder) {
|
|
49
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50
|
-
return value;
|
|
51
|
-
const source = value;
|
|
52
|
-
const filename = typeof source.filename === "string" ? source.filename.trim() : "";
|
|
53
|
-
if (!filename)
|
|
54
|
-
return value;
|
|
55
|
-
const isAssetShape = typeof source.mimeType === "string" ||
|
|
56
|
-
typeof source.extension === "string" ||
|
|
57
|
-
typeof source.size === "number" ||
|
|
58
|
-
typeof source.width === "number" ||
|
|
59
|
-
typeof source.height === "number" ||
|
|
60
|
-
typeof source.length === "number";
|
|
61
|
-
if (!isAssetShape)
|
|
62
|
-
return value;
|
|
63
|
-
const existingUrl = typeof source.url === "string" && source.url.trim().length > 0
|
|
64
|
-
? source.url
|
|
65
|
-
: null;
|
|
66
|
-
return {
|
|
67
|
-
...source,
|
|
68
|
-
url: existingUrl ?? assetUrlBuilder(filename),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
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
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
376
|
-
if (items.length === 0)
|
|
377
|
-
return [];
|
|
378
|
-
const limit = Math.max(1, Math.floor(concurrency));
|
|
379
|
-
if (limit >= items.length) {
|
|
380
|
-
return Promise.all(items.map((item, index) => mapper(item, index)));
|
|
381
|
-
}
|
|
382
|
-
const results = new Array(items.length);
|
|
383
|
-
let nextIndex = 0;
|
|
384
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
385
|
-
while (true) {
|
|
386
|
-
const current = nextIndex++;
|
|
387
|
-
if (current >= items.length)
|
|
388
|
-
break;
|
|
389
|
-
results[current] = await mapper(items[current], current);
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
await Promise.all(workers);
|
|
393
|
-
return results;
|
|
394
|
-
}
|
|
395
|
-
function extractId(value) {
|
|
396
|
-
if (typeof value === "string" && value.length > 0)
|
|
397
|
-
return value;
|
|
398
|
-
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
399
|
-
return value.id;
|
|
400
|
-
}
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
function capitalize(input) {
|
|
404
|
-
return input ? input[0].toUpperCase() + input.slice(1) : input;
|
|
405
|
-
}
|
|
406
|
-
function lowerFirst(input) {
|
|
407
|
-
return input ? input[0].toLowerCase() + input.slice(1) : input;
|
|
408
|
-
}
|
|
409
|
-
function camelCaseModelIdKey(modelName) {
|
|
410
|
-
return `${lowerFirst(modelName)}Id`;
|
|
411
|
-
}
|
|
412
|
-
function snakeCaseModelIdKey(modelName) {
|
|
413
|
-
const snake = modelName
|
|
414
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
415
|
-
.replace(/\s+/g, "_")
|
|
416
|
-
.toLowerCase();
|
|
417
|
-
return `${snake}_id`;
|
|
418
|
-
}
|
|
419
|
-
function extractModelRefId(raw, modelName, options) {
|
|
420
|
-
if (typeof raw === "string")
|
|
421
|
-
return raw;
|
|
422
|
-
if (!raw || typeof raw !== "object")
|
|
423
|
-
return null;
|
|
424
|
-
const object = raw;
|
|
425
|
-
const prop = options?.propertyName ?? "";
|
|
426
|
-
const propertyCandidates = [
|
|
427
|
-
prop ? `${prop}Id` : "",
|
|
428
|
-
prop
|
|
429
|
-
? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
|
|
430
|
-
: "",
|
|
431
|
-
].filter(Boolean);
|
|
432
|
-
const modelCandidates = [
|
|
433
|
-
camelCaseModelIdKey(modelName),
|
|
434
|
-
snakeCaseModelIdKey(modelName),
|
|
435
|
-
];
|
|
436
|
-
// Prefer property-specific FKs (e.g., logoId) over model-level defaults
|
|
437
|
-
// (e.g., imageId) when multiple refs share the same model in one object row.
|
|
438
|
-
const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
|
|
439
|
-
for (const key of candidates) {
|
|
440
|
-
const found = extractId(object[key]);
|
|
441
|
-
if (found)
|
|
442
|
-
return found;
|
|
443
|
-
}
|
|
444
|
-
const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
|
|
445
|
-
if (allowObjectIdFallback) {
|
|
446
|
-
const keys = Object.keys(object);
|
|
447
|
-
const hasOnlyId = keys.length === 1 && keys[0] === "id";
|
|
448
|
-
if (hasOnlyId) {
|
|
449
|
-
const byId = extractId(object);
|
|
450
|
-
if (byId)
|
|
451
|
-
return byId;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
function buildModelRefCacheKey(modelName, id, context, isCollectionItem) {
|
|
457
|
-
const locale = context.options.locale ?? "";
|
|
458
|
-
const defaultLocale = context.options.defaultLocale ?? "";
|
|
459
|
-
const includeIdMode = context.options.includeIdMode;
|
|
460
|
-
const refsMode = context.options.resolveModelRefs ? "resolve" : "ids";
|
|
461
|
-
const collectionFlag = isCollectionItem ? "collection" : "single";
|
|
462
|
-
return [
|
|
463
|
-
modelName,
|
|
464
|
-
id,
|
|
465
|
-
locale,
|
|
466
|
-
defaultLocale,
|
|
467
|
-
includeIdMode,
|
|
468
|
-
refsMode,
|
|
469
|
-
collectionFlag,
|
|
470
|
-
].join("|");
|
|
471
|
-
}
|
|
472
|
-
function extractInlineModelObject(raw, modelDescriptor) {
|
|
473
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
474
|
-
return null;
|
|
475
|
-
if (!isObjectDescriptor(modelDescriptor))
|
|
476
|
-
return null;
|
|
477
|
-
const source = raw;
|
|
478
|
-
const propertyKeys = Object.keys(modelDescriptor.properties ?? {});
|
|
479
|
-
const hasModelField = propertyKeys.some((key) => key in source);
|
|
480
|
-
if (!hasModelField)
|
|
481
|
-
return null;
|
|
482
|
-
return source;
|
|
483
|
-
}
|
|
484
|
-
function formatValidationError(name, error) {
|
|
485
|
-
const payload = error && typeof error === "object" && "format" in error
|
|
486
|
-
? error.format()
|
|
487
|
-
: error;
|
|
488
|
-
return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
|
|
489
|
-
}
|
|
490
|
-
function levenshtein(a, b) {
|
|
491
|
-
if (a === b)
|
|
492
|
-
return 0;
|
|
493
|
-
if (!a.length)
|
|
494
|
-
return b.length;
|
|
495
|
-
if (!b.length)
|
|
496
|
-
return a.length;
|
|
497
|
-
const rows = a.length + 1;
|
|
498
|
-
const cols = b.length + 1;
|
|
499
|
-
const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
500
|
-
for (let i = 0; i < rows; i++)
|
|
501
|
-
matrix[i][0] = i;
|
|
502
|
-
for (let j = 0; j < cols; j++)
|
|
503
|
-
matrix[0][j] = j;
|
|
504
|
-
for (let i = 1; i < rows; i++) {
|
|
505
|
-
for (let j = 1; j < cols; j++) {
|
|
506
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
507
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return matrix[rows - 1][cols - 1];
|
|
511
|
-
}
|
|
512
|
-
function unknownKeyError(key, roots, models) {
|
|
513
|
-
const candidates = [...roots, ...models];
|
|
514
|
-
const normalized = key.toLowerCase();
|
|
515
|
-
const suggestions = candidates
|
|
516
|
-
.map((candidate) => ({
|
|
517
|
-
candidate,
|
|
518
|
-
distance: levenshtein(normalized, candidate.toLowerCase()),
|
|
519
|
-
}))
|
|
520
|
-
.sort((a, b) => a.distance - b.distance)
|
|
521
|
-
.slice(0, 3)
|
|
522
|
-
.map((entry) => entry.candidate);
|
|
523
|
-
const suggestionText = suggestions.length
|
|
524
|
-
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
525
|
-
: "";
|
|
526
|
-
const rootsText = `Available roots: [${roots.join(", ")}]`;
|
|
527
|
-
const modelsText = models.length
|
|
528
|
-
? `, models (under data.models.*): [${models.join(", ")}]`
|
|
529
|
-
: "";
|
|
530
|
-
throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
|
|
531
|
-
}
|
|
532
|
-
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
|
|
533
|
-
if (!id)
|
|
534
|
-
return null;
|
|
535
|
-
if (!context.options.resolveModelRefs)
|
|
536
|
-
return id;
|
|
537
|
-
const nodeKey = buildModelRefCacheKey(modelName, id, context, isCollectionItem);
|
|
538
|
-
if (trail.has(nodeKey)) {
|
|
539
|
-
return shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)
|
|
540
|
-
? { id }
|
|
541
|
-
: id;
|
|
542
|
-
}
|
|
543
|
-
const modelDescriptor = context.modelDescriptors.get(modelName);
|
|
544
|
-
if (!modelDescriptor) {
|
|
545
|
-
return id;
|
|
546
|
-
}
|
|
547
|
-
const inlineModel = extractInlineModelObject(inlineValue, modelDescriptor);
|
|
548
|
-
const cached = context.modelCache.get(nodeKey);
|
|
549
|
-
if (cached) {
|
|
550
|
-
return cached;
|
|
551
|
-
}
|
|
552
|
-
const sharedInflight = context.sharedModelInflightCache.get(nodeKey);
|
|
553
|
-
if (sharedInflight) {
|
|
554
|
-
context.modelCache.set(nodeKey, sharedInflight);
|
|
555
|
-
return sharedInflight;
|
|
556
|
-
}
|
|
557
|
-
const nextTrail = new Set(trail);
|
|
558
|
-
nextTrail.add(nodeKey);
|
|
559
|
-
const promise = (async () => {
|
|
560
|
-
const rawModel = inlineModel ??
|
|
561
|
-
(await context.requestJson(`models/${modelName}/${id}`, {
|
|
562
|
-
query: {
|
|
563
|
-
raw: 1,
|
|
564
|
-
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
565
|
-
},
|
|
566
|
-
}));
|
|
567
|
-
return normalizeField(modelDescriptor, `models/${modelName}/${encodeURIComponent(id)}`, rawModel, context, nextTrail, isCollectionItem);
|
|
568
|
-
})();
|
|
569
|
-
context.modelCache.set(nodeKey, promise);
|
|
570
|
-
context.sharedModelInflightCache.set(nodeKey, promise);
|
|
571
|
-
promise
|
|
572
|
-
.then(() => undefined, () => {
|
|
573
|
-
context.modelCache.delete(nodeKey);
|
|
574
|
-
})
|
|
575
|
-
.finally(() => {
|
|
576
|
-
context.sharedModelInflightCache.delete(nodeKey);
|
|
577
|
-
});
|
|
578
|
-
return promise;
|
|
579
|
-
}
|
|
580
|
-
function missingModelRefValue(descriptor) {
|
|
581
|
-
if (descriptor.nullable)
|
|
582
|
-
return null;
|
|
583
|
-
if (descriptor.optional)
|
|
584
|
-
return undefined;
|
|
585
|
-
return null;
|
|
586
|
-
}
|
|
587
|
-
async function normalizeObjectField(descriptor, path, raw, context, trail, isCollectionItem) {
|
|
588
|
-
const source = raw && typeof raw === "object" ? raw : {};
|
|
589
|
-
const output = {};
|
|
590
|
-
for (const [propertyName, propertyDescriptor] of Object.entries(descriptor.properties)) {
|
|
591
|
-
if (isPrimitiveDescriptor(propertyDescriptor)) {
|
|
592
|
-
output[propertyName] = coerceCustomTypeValue(propertyDescriptor, source[propertyName], {
|
|
593
|
-
locale: context.options.locale,
|
|
594
|
-
defaultLocale: context.options.defaultLocale,
|
|
595
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
596
|
-
});
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if (isModelRefDescriptor(propertyDescriptor)) {
|
|
600
|
-
const inlineValue = source[propertyName];
|
|
601
|
-
const inlineModelDescriptor = context.modelDescriptors.get(propertyDescriptor.model);
|
|
602
|
-
const inlineModel = inlineModelDescriptor
|
|
603
|
-
? extractInlineModelObject(inlineValue, inlineModelDescriptor)
|
|
604
|
-
: null;
|
|
605
|
-
const refId = extractModelRefId(inlineValue, propertyDescriptor.model, {
|
|
606
|
-
allowObjectIdFallback: true,
|
|
607
|
-
}) ??
|
|
608
|
-
(inlineModel ? extractId(inlineModel) : null) ??
|
|
609
|
-
extractModelRefId(source, propertyDescriptor.model, {
|
|
610
|
-
propertyName,
|
|
611
|
-
allowObjectIdFallback: false,
|
|
612
|
-
});
|
|
613
|
-
if (!refId) {
|
|
614
|
-
const missing = missingModelRefValue(propertyDescriptor);
|
|
615
|
-
if (missing !== undefined) {
|
|
616
|
-
output[propertyName] = missing;
|
|
617
|
-
}
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false, inlineValue);
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
if (isArrayDescriptor(propertyDescriptor)) {
|
|
624
|
-
const childPath = `${path}/${propertyName}`;
|
|
625
|
-
const childRaw = await context.requestJson(childPath, {
|
|
626
|
-
query: {
|
|
627
|
-
raw: 1,
|
|
628
|
-
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
629
|
-
},
|
|
630
|
-
});
|
|
631
|
-
const normalizedChild = await normalizeArrayField(propertyDescriptor, childPath, childRaw, context, trail);
|
|
632
|
-
output[propertyName] = coerceCustomTypeValue(propertyDescriptor, normalizedChild, {
|
|
633
|
-
locale: context.options.locale,
|
|
634
|
-
defaultLocale: context.options.defaultLocale,
|
|
635
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
636
|
-
});
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
if (isObjectDescriptor(propertyDescriptor)) {
|
|
640
|
-
const childPath = `${path}/${propertyName}`;
|
|
641
|
-
const childRaw = await context.requestJson(childPath, {
|
|
642
|
-
query: {
|
|
643
|
-
raw: 1,
|
|
644
|
-
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
645
|
-
},
|
|
646
|
-
});
|
|
647
|
-
const normalizedChild = await normalizeObjectField(propertyDescriptor, childPath, childRaw, context, trail, false);
|
|
648
|
-
output[propertyName] = coerceCustomTypeValue(propertyDescriptor, normalizedChild, {
|
|
649
|
-
locale: context.options.locale,
|
|
650
|
-
defaultLocale: context.options.defaultLocale,
|
|
651
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
if (shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)) {
|
|
656
|
-
const rowId = extractId(source);
|
|
657
|
-
if (rowId)
|
|
658
|
-
output.id = rowId;
|
|
659
|
-
}
|
|
660
|
-
if (typeof source.url === "string" &&
|
|
661
|
-
source.url.trim().length > 0 &&
|
|
662
|
-
typeof output.filename === "string") {
|
|
663
|
-
output.url = source.url;
|
|
664
|
-
}
|
|
665
|
-
return maybeAttachAssetUrl(output, context.options.assetUrlBuilder);
|
|
666
|
-
}
|
|
667
|
-
async function normalizeArrayField(descriptor, path, raw, context, trail) {
|
|
668
|
-
const envelope = ensureCollectionEnvelope(raw, path);
|
|
669
|
-
const itemDescriptor = descriptor.items;
|
|
670
|
-
if (isPrimitiveDescriptor(itemDescriptor)) {
|
|
671
|
-
return envelope.items.map((row) => {
|
|
672
|
-
const value = row && typeof row === "object" && "value" in row
|
|
673
|
-
? row.value
|
|
674
|
-
: row;
|
|
675
|
-
if (context.options.includeIdMode !== "all") {
|
|
676
|
-
return value;
|
|
677
|
-
}
|
|
678
|
-
const id = extractId(row);
|
|
679
|
-
return id ? { id, value } : value;
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (isModelRefDescriptor(itemDescriptor)) {
|
|
683
|
-
const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
|
|
684
|
-
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
685
|
-
const inlineModel = inlineModelDescriptor
|
|
686
|
-
? extractInlineModelObject(row, inlineModelDescriptor)
|
|
687
|
-
: null;
|
|
688
|
-
const refId = extractModelRefId(row, itemDescriptor.model, {
|
|
689
|
-
allowObjectIdFallback: false,
|
|
690
|
-
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
691
|
-
return normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
if (isObjectDescriptor(itemDescriptor)) {
|
|
695
|
-
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
696
|
-
const rowId = extractId(row);
|
|
697
|
-
const rowPath = rowId
|
|
698
|
-
? `${path}/${encodeURIComponent(rowId)}`
|
|
699
|
-
: path;
|
|
700
|
-
return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true);
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
return envelope.items;
|
|
704
|
-
}
|
|
705
|
-
async function normalizeField(descriptor, path, raw, context, trail, isCollectionItem) {
|
|
706
|
-
if (isPrimitiveDescriptor(descriptor)) {
|
|
707
|
-
const value = raw && typeof raw === "object" && "value" in raw
|
|
708
|
-
? raw.value
|
|
709
|
-
: raw;
|
|
710
|
-
return coerceCustomTypeValue(descriptor, value, {
|
|
711
|
-
locale: context.options.locale,
|
|
712
|
-
defaultLocale: context.options.defaultLocale,
|
|
713
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
if (isModelRefDescriptor(descriptor)) {
|
|
717
|
-
const inlineModelDescriptor = context.modelDescriptors.get(descriptor.model);
|
|
718
|
-
const inlineModel = inlineModelDescriptor
|
|
719
|
-
? extractInlineModelObject(raw, inlineModelDescriptor)
|
|
720
|
-
: null;
|
|
721
|
-
const refId = extractModelRefId(raw, descriptor.model, {
|
|
722
|
-
allowObjectIdFallback: true,
|
|
723
|
-
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
724
|
-
if (!refId) {
|
|
725
|
-
return missingModelRefValue(descriptor);
|
|
726
|
-
}
|
|
727
|
-
return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem, raw);
|
|
728
|
-
}
|
|
729
|
-
if (isArrayDescriptor(descriptor)) {
|
|
730
|
-
const normalized = await normalizeArrayField(descriptor, path, raw, context, trail);
|
|
731
|
-
return coerceCustomTypeValue(descriptor, normalized, {
|
|
732
|
-
locale: context.options.locale,
|
|
733
|
-
defaultLocale: context.options.defaultLocale,
|
|
734
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
if (isObjectDescriptor(descriptor)) {
|
|
738
|
-
const normalized = await normalizeObjectField(descriptor, path, raw, context, trail, isCollectionItem);
|
|
739
|
-
return coerceCustomTypeValue(descriptor, normalized, {
|
|
740
|
-
locale: context.options.locale,
|
|
741
|
-
defaultLocale: context.options.defaultLocale,
|
|
742
|
-
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
return raw;
|
|
746
|
-
}
|
|
747
|
-
function createResourceRegistry(descriptor) {
|
|
748
|
-
const roots = new Map();
|
|
749
|
-
const rootsCaseInsensitive = new Map();
|
|
750
|
-
for (const [key, rootDescriptor] of Object.entries(descriptor.roots ?? {})) {
|
|
751
|
-
const entry = {
|
|
752
|
-
kind: "root",
|
|
753
|
-
key,
|
|
754
|
-
path: key,
|
|
755
|
-
descriptor: rootDescriptor,
|
|
756
|
-
isCollection: isArrayDescriptor(rootDescriptor),
|
|
757
|
-
};
|
|
758
|
-
roots.set(key, entry);
|
|
759
|
-
rootsCaseInsensitive.set(key.toLowerCase(), entry);
|
|
760
|
-
}
|
|
761
|
-
const models = new Map();
|
|
762
|
-
const modelsCaseInsensitive = new Map();
|
|
763
|
-
for (const modelName of Object.keys(descriptor.models ?? {})) {
|
|
764
|
-
const entry = {
|
|
765
|
-
kind: "model",
|
|
766
|
-
key: modelName,
|
|
767
|
-
path: `models/${modelName}`,
|
|
768
|
-
descriptor: asModelObjectDescriptor(modelName, descriptor),
|
|
769
|
-
isCollection: true,
|
|
770
|
-
modelName,
|
|
771
|
-
};
|
|
772
|
-
models.set(modelName, entry);
|
|
773
|
-
modelsCaseInsensitive.set(modelName.toLowerCase(), entry);
|
|
774
|
-
}
|
|
775
|
-
return {
|
|
776
|
-
roots,
|
|
777
|
-
rootsCaseInsensitive,
|
|
778
|
-
models,
|
|
779
|
-
modelsCaseInsensitive,
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
async function requestJson(baseUrl, apiKey, path, options, runtime) {
|
|
783
|
-
const params = buildSearchParams(options?.query);
|
|
784
|
-
const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
785
|
-
const execute = async () => {
|
|
786
|
-
const retry = runtime?.retry ?? {
|
|
787
|
-
retries: DEFAULT_REQUEST_RETRIES,
|
|
788
|
-
baseDelayMs: DEFAULT_REQUEST_RETRY_BASE_MS,
|
|
789
|
-
maxDelayMs: DEFAULT_REQUEST_RETRY_MAX_MS,
|
|
790
|
-
};
|
|
791
|
-
let attempt = 0;
|
|
792
|
-
while (true) {
|
|
793
|
-
let response;
|
|
794
|
-
try {
|
|
795
|
-
response = runtime?.scheduler
|
|
796
|
-
? await runtime.scheduler(() => fetch(url, {
|
|
797
|
-
method: "GET",
|
|
798
|
-
signal: options?.signal,
|
|
799
|
-
headers: {
|
|
800
|
-
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
801
|
-
},
|
|
802
|
-
}), options?.signal)
|
|
803
|
-
: await fetch(url, {
|
|
804
|
-
method: "GET",
|
|
805
|
-
signal: options?.signal,
|
|
806
|
-
headers: {
|
|
807
|
-
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
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);
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
const useInflightDedup = !options?.signal && !!runtime;
|
|
836
|
-
if (!useInflightDedup || !runtime) {
|
|
837
|
-
return execute();
|
|
838
|
-
}
|
|
839
|
-
const cached = runtime.inflightRequestCache.get(url);
|
|
840
|
-
if (cached)
|
|
841
|
-
return cached;
|
|
842
|
-
const promise = execute().finally(() => {
|
|
843
|
-
runtime.inflightRequestCache.delete(url);
|
|
844
|
-
});
|
|
845
|
-
runtime.inflightRequestCache.set(url, promise);
|
|
846
|
-
return promise;
|
|
847
|
-
}
|
|
848
|
-
function validateResult(resource, result, descriptor, includeIdMode, zodSchemas, modelZodSchemas) {
|
|
849
|
-
if (includeIdMode === "all")
|
|
850
|
-
return;
|
|
851
|
-
if (resource.kind === "root") {
|
|
852
|
-
const schema = zodSchemas[resource.key];
|
|
853
|
-
if (!schema)
|
|
854
|
-
return;
|
|
855
|
-
const parsed = schema.safeParse(result);
|
|
856
|
-
if (!parsed.success) {
|
|
857
|
-
throw new Error(formatValidationError(resource.key, parsed.error));
|
|
858
|
-
}
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
const modelName = resource.modelName;
|
|
862
|
-
if (!modelName)
|
|
863
|
-
return;
|
|
864
|
-
const modelSchema = modelZodSchemas[modelName];
|
|
865
|
-
if (!modelSchema)
|
|
866
|
-
return;
|
|
867
|
-
if (Array.isArray(result)) {
|
|
868
|
-
const parsed = modelSchema.array().safeParse(result);
|
|
869
|
-
if (!parsed.success) {
|
|
870
|
-
throw new Error(formatValidationError(modelName, parsed.error));
|
|
871
|
-
}
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
if (result === null || result === undefined)
|
|
875
|
-
return;
|
|
876
|
-
const parsed = modelSchema.safeParse(result);
|
|
877
|
-
if (!parsed.success) {
|
|
878
|
-
throw new Error(formatValidationError(modelName, parsed.error));
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options, byId) {
|
|
882
|
-
const responseMode = options?.response ?? "normalized";
|
|
883
|
-
const includeIdMode = resolveIncludeIdMode(options?.includeId);
|
|
884
|
-
const resolveModelRefs = options?.resolveModelRefs !== false;
|
|
885
|
-
const requestedLocale = options?.locale ??
|
|
886
|
-
(typeof options?.query?.locale === "string"
|
|
887
|
-
? options.query.locale
|
|
888
|
-
: undefined);
|
|
889
|
-
const locale = requestedLocale ??
|
|
890
|
-
(responseMode === "normalized" ? "all" : undefined);
|
|
891
|
-
const query = {
|
|
892
|
-
...(options?.query ?? {}),
|
|
893
|
-
};
|
|
894
|
-
const shouldNormalize = responseMode !== "raw";
|
|
895
|
-
if (shouldNormalize && query.raw === undefined) {
|
|
896
|
-
query.raw = 1;
|
|
897
|
-
}
|
|
898
|
-
if (locale && query.locale === undefined) {
|
|
899
|
-
query.locale = locale;
|
|
900
|
-
}
|
|
901
|
-
const resourcePath = byId
|
|
902
|
-
? `${resource.path}/${encodeURIComponent(byId)}`
|
|
903
|
-
: resource.path;
|
|
904
|
-
const rawData = await requestJson(baseUrl, apiKey, resourcePath, {
|
|
905
|
-
query,
|
|
906
|
-
signal: options?.signal,
|
|
907
|
-
}, requestRuntime);
|
|
908
|
-
if (!shouldNormalize) {
|
|
909
|
-
return rawData;
|
|
910
|
-
}
|
|
911
|
-
const modelDescriptors = new Map();
|
|
912
|
-
for (const modelName of Object.keys(descriptor.models ?? {})) {
|
|
913
|
-
modelDescriptors.set(modelName, asModelObjectDescriptor(modelName, descriptor));
|
|
914
|
-
}
|
|
915
|
-
const context = {
|
|
916
|
-
requestJson: (path, requestOptions) => requestJson(baseUrl, apiKey, path, {
|
|
917
|
-
...requestOptions,
|
|
918
|
-
signal: options?.signal,
|
|
919
|
-
}, requestRuntime),
|
|
920
|
-
modelDescriptors,
|
|
921
|
-
modelCache: new Map(),
|
|
922
|
-
sharedModelInflightCache,
|
|
923
|
-
options: {
|
|
924
|
-
includeIdMode,
|
|
925
|
-
resolveModelRefs,
|
|
926
|
-
locale,
|
|
927
|
-
defaultLocale,
|
|
928
|
-
assetUrlBuilder,
|
|
929
|
-
modelNormalizationConcurrency,
|
|
930
|
-
},
|
|
931
|
-
};
|
|
932
|
-
if (resource.isCollection && !byId) {
|
|
933
|
-
const envelope = ensureCollectionEnvelope(rawData, resource.path);
|
|
934
|
-
const descriptorForCollection = resource.kind === "root" && isArrayDescriptor(resource.descriptor)
|
|
935
|
-
? resource.descriptor
|
|
936
|
-
: {
|
|
937
|
-
type: "array",
|
|
938
|
-
items: resource.descriptor,
|
|
939
|
-
optional: false,
|
|
940
|
-
nullable: false,
|
|
941
|
-
};
|
|
942
|
-
const normalizedItems = await normalizeArrayField(descriptorForCollection, resource.path, envelope, context, new Set());
|
|
943
|
-
if (responseMode === "envelope") {
|
|
944
|
-
validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
945
|
-
return {
|
|
946
|
-
items: normalizedItems,
|
|
947
|
-
total: envelope.total,
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
validateResult(resource, normalizedItems, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
951
|
-
return normalizedItems;
|
|
952
|
-
}
|
|
953
|
-
const normalized = await normalizeField(resource.descriptor, resourcePath, rawData, context, new Set(), false);
|
|
954
|
-
validateResult(resource, normalized, descriptor, includeIdMode, zodSchemas, modelZodSchemas);
|
|
955
|
-
return normalized;
|
|
956
|
-
}
|
|
957
|
-
function createCmsClient(descriptor, config) {
|
|
958
|
-
const baseUrl = normalizeBaseUrl(config.apiConfig?.baseUrl);
|
|
959
|
-
const apiKey = config.apiConfig?.key;
|
|
960
|
-
const assetUrlBuilder = buildAssetUrlBuilder(baseUrl, config.assets);
|
|
961
|
-
const registry = createResourceRegistry(descriptor);
|
|
962
|
-
const rootKeys = Array.from(registry.roots.keys());
|
|
963
|
-
const modelKeys = Array.from(registry.models.keys());
|
|
964
|
-
const { zodSchemas, modelZodSchemas } = (0, shared_1.buildZodSchemasFromDescriptor)(descriptor);
|
|
965
|
-
const requestOptions = normalizeRequestOptions(config.requests);
|
|
966
|
-
const requestRuntime = {
|
|
967
|
-
scheduler: createRequestScheduler(requestOptions.concurrency),
|
|
968
|
-
inflightRequestCache: new Map(),
|
|
969
|
-
retry: requestOptions.retry,
|
|
970
|
-
};
|
|
971
|
-
const sharedModelInflightCache = new Map();
|
|
972
|
-
const buildModelAccessor = (entry) => {
|
|
973
|
-
const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options));
|
|
974
|
-
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options, id);
|
|
975
|
-
return accessor;
|
|
976
|
-
};
|
|
977
|
-
const modelsProxy = new Proxy({}, {
|
|
978
|
-
get(_target, property) {
|
|
979
|
-
if (typeof property !== "string")
|
|
980
|
-
return undefined;
|
|
981
|
-
if (property === "then")
|
|
982
|
-
return undefined;
|
|
983
|
-
const modelEntry = registry.models.get(property) ??
|
|
984
|
-
registry.modelsCaseInsensitive.get(property.toLowerCase());
|
|
985
|
-
if (!modelEntry) {
|
|
986
|
-
unknownKeyError(property, [], modelKeys);
|
|
987
|
-
}
|
|
988
|
-
return buildModelAccessor(modelEntry);
|
|
989
|
-
},
|
|
990
|
-
});
|
|
991
|
-
const rootProxy = new Proxy({}, {
|
|
992
|
-
get(_target, property) {
|
|
993
|
-
if (typeof property !== "string")
|
|
994
|
-
return undefined;
|
|
995
|
-
if (property === "then")
|
|
996
|
-
return undefined;
|
|
997
|
-
if (property === "models")
|
|
998
|
-
return modelsProxy;
|
|
999
|
-
const entry = registry.roots.get(property) ??
|
|
1000
|
-
registry.rootsCaseInsensitive.get(property.toLowerCase());
|
|
1001
|
-
if (!entry) {
|
|
1002
|
-
unknownKeyError(property, rootKeys, []);
|
|
1003
|
-
}
|
|
1004
|
-
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, requestRuntime, sharedModelInflightCache, requestOptions.modelNormalizationConcurrency, zodSchemas, modelZodSchemas, options);
|
|
1005
|
-
},
|
|
1006
|
-
});
|
|
1007
|
-
return rootProxy;
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Initializes the CMS SDK for a given schema.
|
|
1011
|
-
* @param config configuration including API base URL and API key.
|
|
1012
|
-
* @returns typed accessors for root resources and model resources.
|
|
1013
|
-
*/
|
|
1014
|
-
function cms0(config) {
|
|
1015
|
-
return createCmsClient(schema_descriptors_1.schemaDescriptor, config);
|
|
1016
|
-
}
|