@cms0/cms0 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/custom-types/registry.cjs +14 -1
- package/dist/cjs/index-old.cjs +1016 -0
- package/dist/cjs/index.cjs +324 -206
- package/dist/esm/custom-types/registry.js +12 -0
- package/dist/esm/index-old.js +1012 -0
- package/dist/esm/index.js +324 -206
- 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 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -4,6 +4,8 @@ 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 registry_js_1 = require("./custom-types/registry.cjs");
|
|
8
|
+
const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
|
|
7
9
|
const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
|
|
8
10
|
function normalizeUploadsPath(uploadsPath) {
|
|
9
11
|
const raw = uploadsPath?.trim() || "/uploads";
|
|
@@ -63,6 +65,212 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
|
|
|
63
65
|
url: existingUrl ?? assetUrlBuilder(filename),
|
|
64
66
|
};
|
|
65
67
|
}
|
|
68
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
69
|
+
if (items.length === 0)
|
|
70
|
+
return [];
|
|
71
|
+
const limit = Math.max(1, Math.floor(concurrency));
|
|
72
|
+
if (limit >= items.length) {
|
|
73
|
+
return Promise.all(items.map((item, index) => mapper(item, index)));
|
|
74
|
+
}
|
|
75
|
+
const results = new Array(items.length);
|
|
76
|
+
let nextIndex = 0;
|
|
77
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
78
|
+
while (true) {
|
|
79
|
+
const current = nextIndex++;
|
|
80
|
+
if (current >= items.length)
|
|
81
|
+
break;
|
|
82
|
+
results[current] = await mapper(items[current], current);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
await Promise.all(workers);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
function extractId(value) {
|
|
89
|
+
if (typeof value === "string" && value.length > 0)
|
|
90
|
+
return value;
|
|
91
|
+
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
92
|
+
return value.id;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function capitalize(input) {
|
|
97
|
+
return input ? input[0].toUpperCase() + input.slice(1) : input;
|
|
98
|
+
}
|
|
99
|
+
function lowerFirst(input) {
|
|
100
|
+
return input ? input[0].toLowerCase() + input.slice(1) : input;
|
|
101
|
+
}
|
|
102
|
+
function camelCaseModelIdKey(modelName) {
|
|
103
|
+
return `${lowerFirst(modelName)}Id`;
|
|
104
|
+
}
|
|
105
|
+
function snakeCaseModelIdKey(modelName) {
|
|
106
|
+
const snake = modelName
|
|
107
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
108
|
+
.replace(/\s+/g, "_")
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
return `${snake}_id`;
|
|
111
|
+
}
|
|
112
|
+
function extractModelRefId(raw, modelName, options) {
|
|
113
|
+
if (typeof raw === "string")
|
|
114
|
+
return raw;
|
|
115
|
+
if (!raw || typeof raw !== "object")
|
|
116
|
+
return null;
|
|
117
|
+
const object = raw;
|
|
118
|
+
const prop = options?.propertyName ?? "";
|
|
119
|
+
const propertyCandidates = [
|
|
120
|
+
prop ? `${prop}Id` : "",
|
|
121
|
+
prop
|
|
122
|
+
? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
|
|
123
|
+
: "",
|
|
124
|
+
].filter(Boolean);
|
|
125
|
+
const modelCandidates = [
|
|
126
|
+
camelCaseModelIdKey(modelName),
|
|
127
|
+
snakeCaseModelIdKey(modelName),
|
|
128
|
+
];
|
|
129
|
+
// Prefer property-specific FKs (e.g., logoId) over model-level defaults
|
|
130
|
+
// (e.g., imageId) when multiple refs share the same model in one object row.
|
|
131
|
+
const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
|
|
132
|
+
for (const key of candidates) {
|
|
133
|
+
const found = extractId(object[key]);
|
|
134
|
+
if (found)
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
|
|
138
|
+
if (allowObjectIdFallback) {
|
|
139
|
+
const keys = Object.keys(object);
|
|
140
|
+
const hasOnlyId = keys.length === 1 && keys[0] === "id";
|
|
141
|
+
if (hasOnlyId) {
|
|
142
|
+
const byId = extractId(object);
|
|
143
|
+
if (byId)
|
|
144
|
+
return byId;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
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
|
+
}
|
|
186
|
+
function buildModelRefCacheKey(modelName, id, context, isCollectionItem) {
|
|
187
|
+
const locale = context.options.locale ?? "";
|
|
188
|
+
const defaultLocale = context.options.defaultLocale ?? "";
|
|
189
|
+
const includeIdMode = context.options.includeIdMode;
|
|
190
|
+
const refsMode = context.options.resolveModelRefs ? "resolve" : "ids";
|
|
191
|
+
const collectionFlag = isCollectionItem ? "collection" : "single";
|
|
192
|
+
return [
|
|
193
|
+
modelName,
|
|
194
|
+
id,
|
|
195
|
+
locale,
|
|
196
|
+
defaultLocale,
|
|
197
|
+
includeIdMode,
|
|
198
|
+
refsMode,
|
|
199
|
+
collectionFlag,
|
|
200
|
+
].join("|");
|
|
201
|
+
}
|
|
202
|
+
function extractInlineModelObject(raw, modelDescriptor) {
|
|
203
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
204
|
+
return null;
|
|
205
|
+
if (!isObjectDescriptor(modelDescriptor))
|
|
206
|
+
return null;
|
|
207
|
+
const source = raw;
|
|
208
|
+
const propertyKeys = Object.keys(modelDescriptor.properties ?? {});
|
|
209
|
+
const hasModelField = propertyKeys.some((key) => key in source);
|
|
210
|
+
if (!hasModelField)
|
|
211
|
+
return null;
|
|
212
|
+
return source;
|
|
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
|
+
}
|
|
226
|
+
function formatValidationError(name, error) {
|
|
227
|
+
const payload = error && typeof error === "object" && "format" in error
|
|
228
|
+
? error.format()
|
|
229
|
+
: error;
|
|
230
|
+
return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
|
|
231
|
+
}
|
|
232
|
+
function levenshtein(a, b) {
|
|
233
|
+
if (a === b)
|
|
234
|
+
return 0;
|
|
235
|
+
if (!a.length)
|
|
236
|
+
return b.length;
|
|
237
|
+
if (!b.length)
|
|
238
|
+
return a.length;
|
|
239
|
+
const rows = a.length + 1;
|
|
240
|
+
const cols = b.length + 1;
|
|
241
|
+
const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
242
|
+
for (let i = 0; i < rows; i++)
|
|
243
|
+
matrix[i][0] = i;
|
|
244
|
+
for (let j = 0; j < cols; j++)
|
|
245
|
+
matrix[0][j] = j;
|
|
246
|
+
for (let i = 1; i < rows; i++) {
|
|
247
|
+
for (let j = 1; j < cols; j++) {
|
|
248
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
249
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return matrix[rows - 1][cols - 1];
|
|
253
|
+
}
|
|
254
|
+
function unknownKeyError(key, roots, models) {
|
|
255
|
+
const candidates = [...roots, ...models];
|
|
256
|
+
const normalized = key.toLowerCase();
|
|
257
|
+
const suggestions = candidates
|
|
258
|
+
.map((candidate) => ({
|
|
259
|
+
candidate,
|
|
260
|
+
distance: levenshtein(normalized, candidate.toLowerCase()),
|
|
261
|
+
}))
|
|
262
|
+
.sort((a, b) => a.distance - b.distance)
|
|
263
|
+
.slice(0, 3)
|
|
264
|
+
.map((entry) => entry.candidate);
|
|
265
|
+
const suggestionText = suggestions.length
|
|
266
|
+
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
267
|
+
: "";
|
|
268
|
+
const rootsText = `Available roots: [${roots.join(", ")}]`;
|
|
269
|
+
const modelsText = models.length
|
|
270
|
+
? `, models (under data.models.*): [${models.join(", ")}]`
|
|
271
|
+
: "";
|
|
272
|
+
throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
|
|
273
|
+
}
|
|
66
274
|
function toPlainText(value) {
|
|
67
275
|
if (typeof value === "string")
|
|
68
276
|
return value;
|
|
@@ -171,70 +379,6 @@ function coerceCustomTypeValue(descriptor, value, options) {
|
|
|
171
379
|
}
|
|
172
380
|
return value;
|
|
173
381
|
}
|
|
174
|
-
function isPrimitiveDescriptor(desc) {
|
|
175
|
-
return desc.kind === "primitive";
|
|
176
|
-
}
|
|
177
|
-
function isModelRefDescriptor(desc) {
|
|
178
|
-
return desc.kind === "modelRef";
|
|
179
|
-
}
|
|
180
|
-
function isObjectDescriptor(desc) {
|
|
181
|
-
return desc.type === "object" && !!desc.properties;
|
|
182
|
-
}
|
|
183
|
-
function isArrayDescriptor(desc) {
|
|
184
|
-
return desc.type === "array" && !!desc.items;
|
|
185
|
-
}
|
|
186
|
-
function asModelObjectDescriptor(modelName, descriptor) {
|
|
187
|
-
const model = descriptor.models?.[modelName];
|
|
188
|
-
if (!model) {
|
|
189
|
-
throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
type: "object",
|
|
193
|
-
properties: model.properties,
|
|
194
|
-
optional: false,
|
|
195
|
-
nullable: false,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function normalizeBaseUrl(baseUrl) {
|
|
199
|
-
const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
200
|
-
if (!cleaned) {
|
|
201
|
-
throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
|
|
202
|
-
}
|
|
203
|
-
return cleaned;
|
|
204
|
-
}
|
|
205
|
-
function resolveIncludeIdMode(includeId) {
|
|
206
|
-
if (includeId === true)
|
|
207
|
-
return "all";
|
|
208
|
-
if (includeId === false)
|
|
209
|
-
return "none";
|
|
210
|
-
return "collections";
|
|
211
|
-
}
|
|
212
|
-
function shouldIncludeObjectId(mode, isCollectionItem) {
|
|
213
|
-
if (mode === "all")
|
|
214
|
-
return true;
|
|
215
|
-
if (mode === "collections")
|
|
216
|
-
return isCollectionItem;
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
function buildSearchParams(query) {
|
|
220
|
-
const params = new URLSearchParams();
|
|
221
|
-
if (!query)
|
|
222
|
-
return params;
|
|
223
|
-
for (const [key, rawValue] of Object.entries(query)) {
|
|
224
|
-
if (rawValue === undefined || rawValue === null)
|
|
225
|
-
continue;
|
|
226
|
-
if (Array.isArray(rawValue)) {
|
|
227
|
-
rawValue.forEach((value) => {
|
|
228
|
-
if (value === undefined || value === null)
|
|
229
|
-
return;
|
|
230
|
-
params.append(key, String(value));
|
|
231
|
-
});
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
params.set(key, String(rawValue));
|
|
235
|
-
}
|
|
236
|
-
return params;
|
|
237
|
-
}
|
|
238
382
|
function ensureCollectionEnvelope(data, path) {
|
|
239
383
|
if (Array.isArray(data)) {
|
|
240
384
|
return { items: data, total: data.length };
|
|
@@ -247,121 +391,12 @@ function ensureCollectionEnvelope(data, path) {
|
|
|
247
391
|
}
|
|
248
392
|
throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
|
|
249
393
|
}
|
|
250
|
-
function
|
|
251
|
-
if (typeof value === "string" && value.length > 0)
|
|
252
|
-
return value;
|
|
253
|
-
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
254
|
-
return value.id;
|
|
255
|
-
}
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
function capitalize(input) {
|
|
259
|
-
return input ? input[0].toUpperCase() + input.slice(1) : input;
|
|
260
|
-
}
|
|
261
|
-
function lowerFirst(input) {
|
|
262
|
-
return input ? input[0].toLowerCase() + input.slice(1) : input;
|
|
263
|
-
}
|
|
264
|
-
function camelCaseModelIdKey(modelName) {
|
|
265
|
-
return `${lowerFirst(modelName)}Id`;
|
|
266
|
-
}
|
|
267
|
-
function snakeCaseModelIdKey(modelName) {
|
|
268
|
-
const snake = modelName
|
|
269
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
270
|
-
.replace(/\s+/g, "_")
|
|
271
|
-
.toLowerCase();
|
|
272
|
-
return `${snake}_id`;
|
|
273
|
-
}
|
|
274
|
-
function extractModelRefId(raw, modelName, options) {
|
|
275
|
-
if (typeof raw === "string")
|
|
276
|
-
return raw;
|
|
277
|
-
if (!raw || typeof raw !== "object")
|
|
278
|
-
return null;
|
|
279
|
-
const object = raw;
|
|
280
|
-
const prop = options?.propertyName ?? "";
|
|
281
|
-
const propertyCandidates = [
|
|
282
|
-
prop ? `${prop}Id` : "",
|
|
283
|
-
prop
|
|
284
|
-
? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
|
|
285
|
-
: "",
|
|
286
|
-
].filter(Boolean);
|
|
287
|
-
const modelCandidates = [
|
|
288
|
-
camelCaseModelIdKey(modelName),
|
|
289
|
-
snakeCaseModelIdKey(modelName),
|
|
290
|
-
];
|
|
291
|
-
// Prefer property-specific FKs (e.g., logoId) over model-level defaults
|
|
292
|
-
// (e.g., imageId) when multiple refs share the same model in one object row.
|
|
293
|
-
const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
|
|
294
|
-
for (const key of candidates) {
|
|
295
|
-
const found = extractId(object[key]);
|
|
296
|
-
if (found)
|
|
297
|
-
return found;
|
|
298
|
-
}
|
|
299
|
-
const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
|
|
300
|
-
if (allowObjectIdFallback) {
|
|
301
|
-
const keys = Object.keys(object);
|
|
302
|
-
const hasOnlyId = keys.length === 1 && keys[0] === "id";
|
|
303
|
-
if (hasOnlyId) {
|
|
304
|
-
const byId = extractId(object);
|
|
305
|
-
if (byId)
|
|
306
|
-
return byId;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
function formatValidationError(name, error) {
|
|
312
|
-
const payload = error && typeof error === "object" && "format" in error
|
|
313
|
-
? error.format()
|
|
314
|
-
: error;
|
|
315
|
-
return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
|
|
316
|
-
}
|
|
317
|
-
function levenshtein(a, b) {
|
|
318
|
-
if (a === b)
|
|
319
|
-
return 0;
|
|
320
|
-
if (!a.length)
|
|
321
|
-
return b.length;
|
|
322
|
-
if (!b.length)
|
|
323
|
-
return a.length;
|
|
324
|
-
const rows = a.length + 1;
|
|
325
|
-
const cols = b.length + 1;
|
|
326
|
-
const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
327
|
-
for (let i = 0; i < rows; i++)
|
|
328
|
-
matrix[i][0] = i;
|
|
329
|
-
for (let j = 0; j < cols; j++)
|
|
330
|
-
matrix[0][j] = j;
|
|
331
|
-
for (let i = 1; i < rows; i++) {
|
|
332
|
-
for (let j = 1; j < cols; j++) {
|
|
333
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
334
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return matrix[rows - 1][cols - 1];
|
|
338
|
-
}
|
|
339
|
-
function unknownKeyError(key, roots, models) {
|
|
340
|
-
const candidates = [...roots, ...models];
|
|
341
|
-
const normalized = key.toLowerCase();
|
|
342
|
-
const suggestions = candidates
|
|
343
|
-
.map((candidate) => ({
|
|
344
|
-
candidate,
|
|
345
|
-
distance: levenshtein(normalized, candidate.toLowerCase()),
|
|
346
|
-
}))
|
|
347
|
-
.sort((a, b) => a.distance - b.distance)
|
|
348
|
-
.slice(0, 3)
|
|
349
|
-
.map((entry) => entry.candidate);
|
|
350
|
-
const suggestionText = suggestions.length
|
|
351
|
-
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
352
|
-
: "";
|
|
353
|
-
const rootsText = `Available roots: [${roots.join(", ")}]`;
|
|
354
|
-
const modelsText = models.length
|
|
355
|
-
? `, models (under data.models.*): [${models.join(", ")}]`
|
|
356
|
-
: "";
|
|
357
|
-
throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
|
|
358
|
-
}
|
|
359
|
-
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem) {
|
|
394
|
+
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
|
|
360
395
|
if (!id)
|
|
361
396
|
return null;
|
|
362
397
|
if (!context.options.resolveModelRefs)
|
|
363
398
|
return id;
|
|
364
|
-
const nodeKey =
|
|
399
|
+
const nodeKey = buildModelRefCacheKey(modelName, id, context, isCollectionItem);
|
|
365
400
|
if (trail.has(nodeKey)) {
|
|
366
401
|
return shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)
|
|
367
402
|
? { id }
|
|
@@ -371,25 +406,37 @@ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem
|
|
|
371
406
|
if (!modelDescriptor) {
|
|
372
407
|
return id;
|
|
373
408
|
}
|
|
409
|
+
const inlineModel = extractInlineModelObject(inlineValue, modelDescriptor);
|
|
374
410
|
const cached = context.modelCache.get(nodeKey);
|
|
375
411
|
if (cached) {
|
|
376
412
|
return cached;
|
|
377
413
|
}
|
|
414
|
+
const sharedInflight = context.sharedModelInflightCache.get(nodeKey);
|
|
415
|
+
if (sharedInflight) {
|
|
416
|
+
context.modelCache.set(nodeKey, sharedInflight);
|
|
417
|
+
return sharedInflight;
|
|
418
|
+
}
|
|
378
419
|
const nextTrail = new Set(trail);
|
|
379
420
|
nextTrail.add(nodeKey);
|
|
380
421
|
const promise = (async () => {
|
|
381
|
-
const rawModel =
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
422
|
+
const rawModel = inlineModel ??
|
|
423
|
+
(await context.requestJson(`models/${modelName}/${id}`, {
|
|
424
|
+
query: {
|
|
425
|
+
raw: 1,
|
|
426
|
+
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
427
|
+
},
|
|
428
|
+
}));
|
|
387
429
|
return normalizeField(modelDescriptor, `models/${modelName}/${encodeURIComponent(id)}`, rawModel, context, nextTrail, isCollectionItem);
|
|
388
|
-
})()
|
|
430
|
+
})();
|
|
431
|
+
context.modelCache.set(nodeKey, promise);
|
|
432
|
+
context.sharedModelInflightCache.set(nodeKey, promise);
|
|
433
|
+
promise
|
|
434
|
+
.then(() => undefined, () => {
|
|
389
435
|
context.modelCache.delete(nodeKey);
|
|
390
|
-
|
|
436
|
+
})
|
|
437
|
+
.finally(() => {
|
|
438
|
+
context.sharedModelInflightCache.delete(nodeKey);
|
|
391
439
|
});
|
|
392
|
-
context.modelCache.set(nodeKey, promise);
|
|
393
440
|
return promise;
|
|
394
441
|
}
|
|
395
442
|
function missingModelRefValue(descriptor) {
|
|
@@ -412,10 +459,19 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
412
459
|
continue;
|
|
413
460
|
}
|
|
414
461
|
if (isModelRefDescriptor(propertyDescriptor)) {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
462
|
+
const inlineValue = source[propertyName];
|
|
463
|
+
const inlineModelDescriptor = context.modelDescriptors.get(propertyDescriptor.model);
|
|
464
|
+
const inlineModel = inlineModelDescriptor
|
|
465
|
+
? extractInlineModelObject(inlineValue, inlineModelDescriptor)
|
|
466
|
+
: null;
|
|
467
|
+
const refId = extractModelRefId(inlineValue, propertyDescriptor.model, {
|
|
468
|
+
allowObjectIdFallback: true,
|
|
469
|
+
}) ??
|
|
470
|
+
(inlineModel ? extractId(inlineModel) : null) ??
|
|
471
|
+
extractModelRefId(source, propertyDescriptor.model, {
|
|
472
|
+
propertyName,
|
|
473
|
+
allowObjectIdFallback: false,
|
|
474
|
+
});
|
|
419
475
|
if (!refId) {
|
|
420
476
|
const missing = missingModelRefValue(propertyDescriptor);
|
|
421
477
|
if (missing !== undefined) {
|
|
@@ -423,7 +479,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
423
479
|
}
|
|
424
480
|
continue;
|
|
425
481
|
}
|
|
426
|
-
output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false);
|
|
482
|
+
output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false, inlineValue);
|
|
427
483
|
continue;
|
|
428
484
|
}
|
|
429
485
|
if (isArrayDescriptor(propertyDescriptor)) {
|
|
@@ -482,26 +538,34 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
|
|
|
482
538
|
return value;
|
|
483
539
|
}
|
|
484
540
|
const id = extractId(row);
|
|
485
|
-
|
|
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 };
|
|
486
547
|
});
|
|
487
548
|
}
|
|
488
549
|
if (isModelRefDescriptor(itemDescriptor)) {
|
|
489
|
-
const
|
|
550
|
+
const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
|
|
551
|
+
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
552
|
+
const inlineModel = inlineModelDescriptor
|
|
553
|
+
? extractInlineModelObject(row, inlineModelDescriptor)
|
|
554
|
+
: null;
|
|
490
555
|
const refId = extractModelRefId(row, itemDescriptor.model, {
|
|
491
556
|
allowObjectIdFallback: false,
|
|
492
|
-
});
|
|
493
|
-
return normalizeModelRef(itemDescriptor.model, refId, context, trail, true);
|
|
494
|
-
})
|
|
495
|
-
return resolved;
|
|
557
|
+
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
558
|
+
return normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
|
|
559
|
+
});
|
|
496
560
|
}
|
|
497
561
|
if (isObjectDescriptor(itemDescriptor)) {
|
|
498
|
-
return
|
|
562
|
+
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
499
563
|
const rowId = extractId(row);
|
|
500
564
|
const rowPath = rowId
|
|
501
565
|
? `${path}/${encodeURIComponent(rowId)}`
|
|
502
566
|
: path;
|
|
503
567
|
return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true);
|
|
504
|
-
})
|
|
568
|
+
});
|
|
505
569
|
}
|
|
506
570
|
return envelope.items;
|
|
507
571
|
}
|
|
@@ -510,18 +574,29 @@ async function normalizeField(descriptor, path, raw, context, trail, isCollectio
|
|
|
510
574
|
const value = raw && typeof raw === "object" && "value" in raw
|
|
511
575
|
? raw.value
|
|
512
576
|
: raw;
|
|
513
|
-
|
|
577
|
+
let coerced = coerceCustomTypeValue(descriptor, value, {
|
|
514
578
|
locale: context.options.locale,
|
|
515
579
|
defaultLocale: context.options.defaultLocale,
|
|
516
580
|
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
517
581
|
});
|
|
582
|
+
if (shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)) {
|
|
583
|
+
const id = extractId(raw);
|
|
584
|
+
coerced = attachIdForPrimitiveDescriptor(descriptor, coerced, id);
|
|
585
|
+
}
|
|
586
|
+
return coerced;
|
|
518
587
|
}
|
|
519
588
|
if (isModelRefDescriptor(descriptor)) {
|
|
520
|
-
const
|
|
589
|
+
const inlineModelDescriptor = context.modelDescriptors.get(descriptor.model);
|
|
590
|
+
const inlineModel = inlineModelDescriptor
|
|
591
|
+
? extractInlineModelObject(raw, inlineModelDescriptor)
|
|
592
|
+
: null;
|
|
593
|
+
const refId = extractModelRefId(raw, descriptor.model, {
|
|
594
|
+
allowObjectIdFallback: true,
|
|
595
|
+
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
521
596
|
if (!refId) {
|
|
522
597
|
return missingModelRefValue(descriptor);
|
|
523
598
|
}
|
|
524
|
-
return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem);
|
|
599
|
+
return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem, raw);
|
|
525
600
|
}
|
|
526
601
|
if (isArrayDescriptor(descriptor)) {
|
|
527
602
|
const normalized = await normalizeArrayField(descriptor, path, raw, context, trail);
|
|
@@ -576,6 +651,39 @@ function createResourceRegistry(descriptor) {
|
|
|
576
651
|
modelsCaseInsensitive,
|
|
577
652
|
};
|
|
578
653
|
}
|
|
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;
|
|
682
|
+
}
|
|
683
|
+
params.set(key, String(rawValue));
|
|
684
|
+
}
|
|
685
|
+
return params;
|
|
686
|
+
}
|
|
579
687
|
async function requestJson(baseUrl, apiKey, path, options) {
|
|
580
688
|
const params = buildSearchParams(options?.query);
|
|
581
689
|
const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
@@ -624,9 +732,9 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
|
|
|
624
732
|
throw new Error(formatValidationError(modelName, parsed.error));
|
|
625
733
|
}
|
|
626
734
|
}
|
|
627
|
-
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, byId) {
|
|
735
|
+
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId) {
|
|
628
736
|
const responseMode = options?.response ?? "normalized";
|
|
629
|
-
const includeIdMode = resolveIncludeIdMode(options?.includeId);
|
|
737
|
+
const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
|
|
630
738
|
const resolveModelRefs = options?.resolveModelRefs !== false;
|
|
631
739
|
const requestedLocale = options?.locale ??
|
|
632
740
|
(typeof options?.query?.locale === "string"
|
|
@@ -665,12 +773,14 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
665
773
|
}),
|
|
666
774
|
modelDescriptors,
|
|
667
775
|
modelCache: new Map(),
|
|
776
|
+
sharedModelInflightCache,
|
|
668
777
|
options: {
|
|
669
778
|
includeIdMode,
|
|
670
779
|
resolveModelRefs,
|
|
671
780
|
locale,
|
|
672
781
|
defaultLocale,
|
|
673
782
|
assetUrlBuilder,
|
|
783
|
+
modelNormalizationConcurrency: DEFAULT_MODEL_NORMALIZATION_CONCURRENCY,
|
|
674
784
|
},
|
|
675
785
|
};
|
|
676
786
|
if (resource.isCollection && !byId) {
|
|
@@ -706,9 +816,11 @@ function createCmsClient(descriptor, config) {
|
|
|
706
816
|
const rootKeys = Array.from(registry.roots.keys());
|
|
707
817
|
const modelKeys = Array.from(registry.models.keys());
|
|
708
818
|
const { zodSchemas, modelZodSchemas } = (0, shared_1.buildZodSchemasFromDescriptor)(descriptor);
|
|
819
|
+
const globalIncludeId = !!config.includeId;
|
|
820
|
+
const sharedModelInflightCache = new Map();
|
|
709
821
|
const buildModelAccessor = (entry) => {
|
|
710
|
-
const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options));
|
|
711
|
-
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, id);
|
|
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);
|
|
712
824
|
return accessor;
|
|
713
825
|
};
|
|
714
826
|
const modelsProxy = new Proxy({}, {
|
|
@@ -738,10 +850,16 @@ function createCmsClient(descriptor, config) {
|
|
|
738
850
|
if (!entry) {
|
|
739
851
|
unknownKeyError(property, rootKeys, []);
|
|
740
852
|
}
|
|
741
|
-
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options);
|
|
853
|
+
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
|
|
742
854
|
},
|
|
743
855
|
});
|
|
744
|
-
|
|
856
|
+
const client = Object.assign(rootProxy, {
|
|
857
|
+
models: modelsProxy,
|
|
858
|
+
locales: config.locales ?? [],
|
|
859
|
+
defaultLocale: config.defaultLocale,
|
|
860
|
+
includeIdDefault: globalIncludeId,
|
|
861
|
+
});
|
|
862
|
+
return client;
|
|
745
863
|
}
|
|
746
864
|
/**
|
|
747
865
|
* Initializes the CMS SDK for a given schema.
|
|
@@ -59,6 +59,15 @@ export const customInlineDescriptors = {
|
|
|
59
59
|
LocalizedString: localizedStringDescriptor,
|
|
60
60
|
LocalizedRichText: localizedRichTextDescriptor,
|
|
61
61
|
};
|
|
62
|
+
export const customInlineTypeMetadata = {
|
|
63
|
+
RichText: {},
|
|
64
|
+
LocalizedString: {
|
|
65
|
+
includeId: { mapLikeFields: ["locales"] },
|
|
66
|
+
},
|
|
67
|
+
LocalizedRichText: {
|
|
68
|
+
includeId: { mapLikeFields: ["locales"] },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
62
71
|
export const customTypeDescriptors = {
|
|
63
72
|
...customModelDescriptors,
|
|
64
73
|
...customInlineDescriptors,
|
|
@@ -66,6 +75,9 @@ export const customTypeDescriptors = {
|
|
|
66
75
|
export const customTypeNames = new Set(Object.keys(customTypeDescriptors));
|
|
67
76
|
export const customModelTypeNames = new Set(Object.keys(customModelDescriptors));
|
|
68
77
|
export const customInlineTypeNames = new Set(Object.keys(customInlineDescriptors));
|
|
78
|
+
export function getCustomInlineTypeMetadata(name) {
|
|
79
|
+
return customInlineTypeMetadata[name];
|
|
80
|
+
}
|
|
69
81
|
export function resolveCustomTypeDependencies(names) {
|
|
70
82
|
const resolved = new Set();
|
|
71
83
|
const queue = Array.from(names);
|