@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/esm/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { schemaDescriptor } from "@cms0/cms0/schema-descriptors";
|
|
2
2
|
import { buildZodSchemasFromDescriptor, } from "@cms0/shared";
|
|
3
|
+
import { getCustomInlineTypeMetadata } from "./custom-types/registry.js";
|
|
4
|
+
const DEFAULT_MODEL_NORMALIZATION_CONCURRENCY = 8;
|
|
3
5
|
const COLLECTION_SHAPE_ERROR = "Invalid collection response. Expected array or { items, total }.";
|
|
4
6
|
function normalizeUploadsPath(uploadsPath) {
|
|
5
7
|
const raw = uploadsPath?.trim() || "/uploads";
|
|
@@ -59,6 +61,212 @@ function maybeAttachAssetUrl(value, assetUrlBuilder) {
|
|
|
59
61
|
url: existingUrl ?? assetUrlBuilder(filename),
|
|
60
62
|
};
|
|
61
63
|
}
|
|
64
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
65
|
+
if (items.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const limit = Math.max(1, Math.floor(concurrency));
|
|
68
|
+
if (limit >= items.length) {
|
|
69
|
+
return Promise.all(items.map((item, index) => mapper(item, index)));
|
|
70
|
+
}
|
|
71
|
+
const results = new Array(items.length);
|
|
72
|
+
let nextIndex = 0;
|
|
73
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
74
|
+
while (true) {
|
|
75
|
+
const current = nextIndex++;
|
|
76
|
+
if (current >= items.length)
|
|
77
|
+
break;
|
|
78
|
+
results[current] = await mapper(items[current], current);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
await Promise.all(workers);
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
function extractId(value) {
|
|
85
|
+
if (typeof value === "string" && value.length > 0)
|
|
86
|
+
return value;
|
|
87
|
+
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
88
|
+
return value.id;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function capitalize(input) {
|
|
93
|
+
return input ? input[0].toUpperCase() + input.slice(1) : input;
|
|
94
|
+
}
|
|
95
|
+
function lowerFirst(input) {
|
|
96
|
+
return input ? input[0].toLowerCase() + input.slice(1) : input;
|
|
97
|
+
}
|
|
98
|
+
function camelCaseModelIdKey(modelName) {
|
|
99
|
+
return `${lowerFirst(modelName)}Id`;
|
|
100
|
+
}
|
|
101
|
+
function snakeCaseModelIdKey(modelName) {
|
|
102
|
+
const snake = modelName
|
|
103
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
104
|
+
.replace(/\s+/g, "_")
|
|
105
|
+
.toLowerCase();
|
|
106
|
+
return `${snake}_id`;
|
|
107
|
+
}
|
|
108
|
+
function extractModelRefId(raw, modelName, options) {
|
|
109
|
+
if (typeof raw === "string")
|
|
110
|
+
return raw;
|
|
111
|
+
if (!raw || typeof raw !== "object")
|
|
112
|
+
return null;
|
|
113
|
+
const object = raw;
|
|
114
|
+
const prop = options?.propertyName ?? "";
|
|
115
|
+
const propertyCandidates = [
|
|
116
|
+
prop ? `${prop}Id` : "",
|
|
117
|
+
prop
|
|
118
|
+
? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
|
|
119
|
+
: "",
|
|
120
|
+
].filter(Boolean);
|
|
121
|
+
const modelCandidates = [
|
|
122
|
+
camelCaseModelIdKey(modelName),
|
|
123
|
+
snakeCaseModelIdKey(modelName),
|
|
124
|
+
];
|
|
125
|
+
// Prefer property-specific FKs (e.g., logoId) over model-level defaults
|
|
126
|
+
// (e.g., imageId) when multiple refs share the same model in one object row.
|
|
127
|
+
const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
|
|
128
|
+
for (const key of candidates) {
|
|
129
|
+
const found = extractId(object[key]);
|
|
130
|
+
if (found)
|
|
131
|
+
return found;
|
|
132
|
+
}
|
|
133
|
+
const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
|
|
134
|
+
if (allowObjectIdFallback) {
|
|
135
|
+
const keys = Object.keys(object);
|
|
136
|
+
const hasOnlyId = keys.length === 1 && keys[0] === "id";
|
|
137
|
+
if (hasOnlyId) {
|
|
138
|
+
const byId = extractId(object);
|
|
139
|
+
if (byId)
|
|
140
|
+
return byId;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function isPrimitiveDescriptor(desc) {
|
|
146
|
+
return desc.kind === "primitive";
|
|
147
|
+
}
|
|
148
|
+
function isModelRefDescriptor(desc) {
|
|
149
|
+
return desc.kind === "modelRef";
|
|
150
|
+
}
|
|
151
|
+
function isObjectDescriptor(desc) {
|
|
152
|
+
return desc.type === "object" && !!desc.properties;
|
|
153
|
+
}
|
|
154
|
+
function isArrayDescriptor(desc) {
|
|
155
|
+
return desc.type === "array" && !!desc.items;
|
|
156
|
+
}
|
|
157
|
+
function attachIdIfObject(value, id) {
|
|
158
|
+
if (!id)
|
|
159
|
+
return value;
|
|
160
|
+
if (value && typeof value === "object" && !Array.isArray(value) && !value.id) {
|
|
161
|
+
return { ...value, id };
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function attachIdForPrimitiveDescriptor(descriptor, value, id) {
|
|
166
|
+
const withId = attachIdIfObject(value, id);
|
|
167
|
+
if (!withId || typeof withId !== "object" || Array.isArray(withId)) {
|
|
168
|
+
return withId;
|
|
169
|
+
}
|
|
170
|
+
const customType = descriptor.customType;
|
|
171
|
+
if (typeof customType !== "string") {
|
|
172
|
+
return withId;
|
|
173
|
+
}
|
|
174
|
+
const metadata = getCustomInlineTypeMetadata(customType);
|
|
175
|
+
if (!metadata?.includeId?.mapLikeFields?.length) {
|
|
176
|
+
return withId;
|
|
177
|
+
}
|
|
178
|
+
// Map-like objects (e.g., locales) are data containers, not entity rows.
|
|
179
|
+
// Keep them untouched by id propagation.
|
|
180
|
+
return { ...withId };
|
|
181
|
+
}
|
|
182
|
+
function buildModelRefCacheKey(modelName, id, context, isCollectionItem) {
|
|
183
|
+
const locale = context.options.locale ?? "";
|
|
184
|
+
const defaultLocale = context.options.defaultLocale ?? "";
|
|
185
|
+
const includeIdMode = context.options.includeIdMode;
|
|
186
|
+
const refsMode = context.options.resolveModelRefs ? "resolve" : "ids";
|
|
187
|
+
const collectionFlag = isCollectionItem ? "collection" : "single";
|
|
188
|
+
return [
|
|
189
|
+
modelName,
|
|
190
|
+
id,
|
|
191
|
+
locale,
|
|
192
|
+
defaultLocale,
|
|
193
|
+
includeIdMode,
|
|
194
|
+
refsMode,
|
|
195
|
+
collectionFlag,
|
|
196
|
+
].join("|");
|
|
197
|
+
}
|
|
198
|
+
function extractInlineModelObject(raw, modelDescriptor) {
|
|
199
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
200
|
+
return null;
|
|
201
|
+
if (!isObjectDescriptor(modelDescriptor))
|
|
202
|
+
return null;
|
|
203
|
+
const source = raw;
|
|
204
|
+
const propertyKeys = Object.keys(modelDescriptor.properties ?? {});
|
|
205
|
+
const hasModelField = propertyKeys.some((key) => key in source);
|
|
206
|
+
if (!hasModelField)
|
|
207
|
+
return null;
|
|
208
|
+
return source;
|
|
209
|
+
}
|
|
210
|
+
function asModelObjectDescriptor(modelName, descriptor) {
|
|
211
|
+
const model = descriptor.models?.[modelName];
|
|
212
|
+
if (!model) {
|
|
213
|
+
throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: model.properties,
|
|
218
|
+
optional: false,
|
|
219
|
+
nullable: false,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function formatValidationError(name, error) {
|
|
223
|
+
const payload = error && typeof error === "object" && "format" in error
|
|
224
|
+
? error.format()
|
|
225
|
+
: error;
|
|
226
|
+
return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
|
|
227
|
+
}
|
|
228
|
+
function levenshtein(a, b) {
|
|
229
|
+
if (a === b)
|
|
230
|
+
return 0;
|
|
231
|
+
if (!a.length)
|
|
232
|
+
return b.length;
|
|
233
|
+
if (!b.length)
|
|
234
|
+
return a.length;
|
|
235
|
+
const rows = a.length + 1;
|
|
236
|
+
const cols = b.length + 1;
|
|
237
|
+
const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
238
|
+
for (let i = 0; i < rows; i++)
|
|
239
|
+
matrix[i][0] = i;
|
|
240
|
+
for (let j = 0; j < cols; j++)
|
|
241
|
+
matrix[0][j] = j;
|
|
242
|
+
for (let i = 1; i < rows; i++) {
|
|
243
|
+
for (let j = 1; j < cols; j++) {
|
|
244
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
245
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return matrix[rows - 1][cols - 1];
|
|
249
|
+
}
|
|
250
|
+
function unknownKeyError(key, roots, models) {
|
|
251
|
+
const candidates = [...roots, ...models];
|
|
252
|
+
const normalized = key.toLowerCase();
|
|
253
|
+
const suggestions = candidates
|
|
254
|
+
.map((candidate) => ({
|
|
255
|
+
candidate,
|
|
256
|
+
distance: levenshtein(normalized, candidate.toLowerCase()),
|
|
257
|
+
}))
|
|
258
|
+
.sort((a, b) => a.distance - b.distance)
|
|
259
|
+
.slice(0, 3)
|
|
260
|
+
.map((entry) => entry.candidate);
|
|
261
|
+
const suggestionText = suggestions.length
|
|
262
|
+
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
263
|
+
: "";
|
|
264
|
+
const rootsText = `Available roots: [${roots.join(", ")}]`;
|
|
265
|
+
const modelsText = models.length
|
|
266
|
+
? `, models (under data.models.*): [${models.join(", ")}]`
|
|
267
|
+
: "";
|
|
268
|
+
throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
|
|
269
|
+
}
|
|
62
270
|
function toPlainText(value) {
|
|
63
271
|
if (typeof value === "string")
|
|
64
272
|
return value;
|
|
@@ -167,70 +375,6 @@ function coerceCustomTypeValue(descriptor, value, options) {
|
|
|
167
375
|
}
|
|
168
376
|
return value;
|
|
169
377
|
}
|
|
170
|
-
function isPrimitiveDescriptor(desc) {
|
|
171
|
-
return desc.kind === "primitive";
|
|
172
|
-
}
|
|
173
|
-
function isModelRefDescriptor(desc) {
|
|
174
|
-
return desc.kind === "modelRef";
|
|
175
|
-
}
|
|
176
|
-
function isObjectDescriptor(desc) {
|
|
177
|
-
return desc.type === "object" && !!desc.properties;
|
|
178
|
-
}
|
|
179
|
-
function isArrayDescriptor(desc) {
|
|
180
|
-
return desc.type === "array" && !!desc.items;
|
|
181
|
-
}
|
|
182
|
-
function asModelObjectDescriptor(modelName, descriptor) {
|
|
183
|
-
const model = descriptor.models?.[modelName];
|
|
184
|
-
if (!model) {
|
|
185
|
-
throw new Error(`Unknown model '${modelName}' in schema descriptor.`);
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
type: "object",
|
|
189
|
-
properties: model.properties,
|
|
190
|
-
optional: false,
|
|
191
|
-
nullable: false,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function normalizeBaseUrl(baseUrl) {
|
|
195
|
-
const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
196
|
-
if (!cleaned) {
|
|
197
|
-
throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
|
|
198
|
-
}
|
|
199
|
-
return cleaned;
|
|
200
|
-
}
|
|
201
|
-
function resolveIncludeIdMode(includeId) {
|
|
202
|
-
if (includeId === true)
|
|
203
|
-
return "all";
|
|
204
|
-
if (includeId === false)
|
|
205
|
-
return "none";
|
|
206
|
-
return "collections";
|
|
207
|
-
}
|
|
208
|
-
function shouldIncludeObjectId(mode, isCollectionItem) {
|
|
209
|
-
if (mode === "all")
|
|
210
|
-
return true;
|
|
211
|
-
if (mode === "collections")
|
|
212
|
-
return isCollectionItem;
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
function buildSearchParams(query) {
|
|
216
|
-
const params = new URLSearchParams();
|
|
217
|
-
if (!query)
|
|
218
|
-
return params;
|
|
219
|
-
for (const [key, rawValue] of Object.entries(query)) {
|
|
220
|
-
if (rawValue === undefined || rawValue === null)
|
|
221
|
-
continue;
|
|
222
|
-
if (Array.isArray(rawValue)) {
|
|
223
|
-
rawValue.forEach((value) => {
|
|
224
|
-
if (value === undefined || value === null)
|
|
225
|
-
return;
|
|
226
|
-
params.append(key, String(value));
|
|
227
|
-
});
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
params.set(key, String(rawValue));
|
|
231
|
-
}
|
|
232
|
-
return params;
|
|
233
|
-
}
|
|
234
378
|
function ensureCollectionEnvelope(data, path) {
|
|
235
379
|
if (Array.isArray(data)) {
|
|
236
380
|
return { items: data, total: data.length };
|
|
@@ -243,121 +387,12 @@ function ensureCollectionEnvelope(data, path) {
|
|
|
243
387
|
}
|
|
244
388
|
throw new Error(`${COLLECTION_SHAPE_ERROR} Path: '${path}'.`);
|
|
245
389
|
}
|
|
246
|
-
function
|
|
247
|
-
if (typeof value === "string" && value.length > 0)
|
|
248
|
-
return value;
|
|
249
|
-
if (value && typeof value === "object" && typeof value.id === "string") {
|
|
250
|
-
return value.id;
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
function capitalize(input) {
|
|
255
|
-
return input ? input[0].toUpperCase() + input.slice(1) : input;
|
|
256
|
-
}
|
|
257
|
-
function lowerFirst(input) {
|
|
258
|
-
return input ? input[0].toLowerCase() + input.slice(1) : input;
|
|
259
|
-
}
|
|
260
|
-
function camelCaseModelIdKey(modelName) {
|
|
261
|
-
return `${lowerFirst(modelName)}Id`;
|
|
262
|
-
}
|
|
263
|
-
function snakeCaseModelIdKey(modelName) {
|
|
264
|
-
const snake = modelName
|
|
265
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
266
|
-
.replace(/\s+/g, "_")
|
|
267
|
-
.toLowerCase();
|
|
268
|
-
return `${snake}_id`;
|
|
269
|
-
}
|
|
270
|
-
function extractModelRefId(raw, modelName, options) {
|
|
271
|
-
if (typeof raw === "string")
|
|
272
|
-
return raw;
|
|
273
|
-
if (!raw || typeof raw !== "object")
|
|
274
|
-
return null;
|
|
275
|
-
const object = raw;
|
|
276
|
-
const prop = options?.propertyName ?? "";
|
|
277
|
-
const propertyCandidates = [
|
|
278
|
-
prop ? `${prop}Id` : "",
|
|
279
|
-
prop
|
|
280
|
-
? `${prop.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase()}_id`
|
|
281
|
-
: "",
|
|
282
|
-
].filter(Boolean);
|
|
283
|
-
const modelCandidates = [
|
|
284
|
-
camelCaseModelIdKey(modelName),
|
|
285
|
-
snakeCaseModelIdKey(modelName),
|
|
286
|
-
];
|
|
287
|
-
// Prefer property-specific FKs (e.g., logoId) over model-level defaults
|
|
288
|
-
// (e.g., imageId) when multiple refs share the same model in one object row.
|
|
289
|
-
const candidates = Array.from(new Set([...propertyCandidates, ...modelCandidates, "value"]));
|
|
290
|
-
for (const key of candidates) {
|
|
291
|
-
const found = extractId(object[key]);
|
|
292
|
-
if (found)
|
|
293
|
-
return found;
|
|
294
|
-
}
|
|
295
|
-
const allowObjectIdFallback = options?.allowObjectIdFallback ?? true;
|
|
296
|
-
if (allowObjectIdFallback) {
|
|
297
|
-
const keys = Object.keys(object);
|
|
298
|
-
const hasOnlyId = keys.length === 1 && keys[0] === "id";
|
|
299
|
-
if (hasOnlyId) {
|
|
300
|
-
const byId = extractId(object);
|
|
301
|
-
if (byId)
|
|
302
|
-
return byId;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
function formatValidationError(name, error) {
|
|
308
|
-
const payload = error && typeof error === "object" && "format" in error
|
|
309
|
-
? error.format()
|
|
310
|
-
: error;
|
|
311
|
-
return `Invalid data received for '${name}': ${JSON.stringify(payload)}`;
|
|
312
|
-
}
|
|
313
|
-
function levenshtein(a, b) {
|
|
314
|
-
if (a === b)
|
|
315
|
-
return 0;
|
|
316
|
-
if (!a.length)
|
|
317
|
-
return b.length;
|
|
318
|
-
if (!b.length)
|
|
319
|
-
return a.length;
|
|
320
|
-
const rows = a.length + 1;
|
|
321
|
-
const cols = b.length + 1;
|
|
322
|
-
const matrix = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
323
|
-
for (let i = 0; i < rows; i++)
|
|
324
|
-
matrix[i][0] = i;
|
|
325
|
-
for (let j = 0; j < cols; j++)
|
|
326
|
-
matrix[0][j] = j;
|
|
327
|
-
for (let i = 1; i < rows; i++) {
|
|
328
|
-
for (let j = 1; j < cols; j++) {
|
|
329
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
330
|
-
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return matrix[rows - 1][cols - 1];
|
|
334
|
-
}
|
|
335
|
-
function unknownKeyError(key, roots, models) {
|
|
336
|
-
const candidates = [...roots, ...models];
|
|
337
|
-
const normalized = key.toLowerCase();
|
|
338
|
-
const suggestions = candidates
|
|
339
|
-
.map((candidate) => ({
|
|
340
|
-
candidate,
|
|
341
|
-
distance: levenshtein(normalized, candidate.toLowerCase()),
|
|
342
|
-
}))
|
|
343
|
-
.sort((a, b) => a.distance - b.distance)
|
|
344
|
-
.slice(0, 3)
|
|
345
|
-
.map((entry) => entry.candidate);
|
|
346
|
-
const suggestionText = suggestions.length
|
|
347
|
-
? ` Did you mean: ${suggestions.join(", ")}?`
|
|
348
|
-
: "";
|
|
349
|
-
const rootsText = `Available roots: [${roots.join(", ")}]`;
|
|
350
|
-
const modelsText = models.length
|
|
351
|
-
? `, models (under data.models.*): [${models.join(", ")}]`
|
|
352
|
-
: "";
|
|
353
|
-
throw new Error(`Unknown schema key '${key}'. ${rootsText}${modelsText}.${suggestionText}`);
|
|
354
|
-
}
|
|
355
|
-
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem) {
|
|
390
|
+
async function normalizeModelRef(modelName, id, context, trail, isCollectionItem, inlineValue) {
|
|
356
391
|
if (!id)
|
|
357
392
|
return null;
|
|
358
393
|
if (!context.options.resolveModelRefs)
|
|
359
394
|
return id;
|
|
360
|
-
const nodeKey =
|
|
395
|
+
const nodeKey = buildModelRefCacheKey(modelName, id, context, isCollectionItem);
|
|
361
396
|
if (trail.has(nodeKey)) {
|
|
362
397
|
return shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)
|
|
363
398
|
? { id }
|
|
@@ -367,25 +402,37 @@ async function normalizeModelRef(modelName, id, context, trail, isCollectionItem
|
|
|
367
402
|
if (!modelDescriptor) {
|
|
368
403
|
return id;
|
|
369
404
|
}
|
|
405
|
+
const inlineModel = extractInlineModelObject(inlineValue, modelDescriptor);
|
|
370
406
|
const cached = context.modelCache.get(nodeKey);
|
|
371
407
|
if (cached) {
|
|
372
408
|
return cached;
|
|
373
409
|
}
|
|
410
|
+
const sharedInflight = context.sharedModelInflightCache.get(nodeKey);
|
|
411
|
+
if (sharedInflight) {
|
|
412
|
+
context.modelCache.set(nodeKey, sharedInflight);
|
|
413
|
+
return sharedInflight;
|
|
414
|
+
}
|
|
374
415
|
const nextTrail = new Set(trail);
|
|
375
416
|
nextTrail.add(nodeKey);
|
|
376
417
|
const promise = (async () => {
|
|
377
|
-
const rawModel =
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
418
|
+
const rawModel = inlineModel ??
|
|
419
|
+
(await context.requestJson(`models/${modelName}/${id}`, {
|
|
420
|
+
query: {
|
|
421
|
+
raw: 1,
|
|
422
|
+
...(context.options.locale ? { locale: context.options.locale } : {}),
|
|
423
|
+
},
|
|
424
|
+
}));
|
|
383
425
|
return normalizeField(modelDescriptor, `models/${modelName}/${encodeURIComponent(id)}`, rawModel, context, nextTrail, isCollectionItem);
|
|
384
|
-
})()
|
|
426
|
+
})();
|
|
427
|
+
context.modelCache.set(nodeKey, promise);
|
|
428
|
+
context.sharedModelInflightCache.set(nodeKey, promise);
|
|
429
|
+
promise
|
|
430
|
+
.then(() => undefined, () => {
|
|
385
431
|
context.modelCache.delete(nodeKey);
|
|
386
|
-
|
|
432
|
+
})
|
|
433
|
+
.finally(() => {
|
|
434
|
+
context.sharedModelInflightCache.delete(nodeKey);
|
|
387
435
|
});
|
|
388
|
-
context.modelCache.set(nodeKey, promise);
|
|
389
436
|
return promise;
|
|
390
437
|
}
|
|
391
438
|
function missingModelRefValue(descriptor) {
|
|
@@ -408,10 +455,19 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
408
455
|
continue;
|
|
409
456
|
}
|
|
410
457
|
if (isModelRefDescriptor(propertyDescriptor)) {
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
458
|
+
const inlineValue = source[propertyName];
|
|
459
|
+
const inlineModelDescriptor = context.modelDescriptors.get(propertyDescriptor.model);
|
|
460
|
+
const inlineModel = inlineModelDescriptor
|
|
461
|
+
? extractInlineModelObject(inlineValue, inlineModelDescriptor)
|
|
462
|
+
: null;
|
|
463
|
+
const refId = extractModelRefId(inlineValue, propertyDescriptor.model, {
|
|
464
|
+
allowObjectIdFallback: true,
|
|
465
|
+
}) ??
|
|
466
|
+
(inlineModel ? extractId(inlineModel) : null) ??
|
|
467
|
+
extractModelRefId(source, propertyDescriptor.model, {
|
|
468
|
+
propertyName,
|
|
469
|
+
allowObjectIdFallback: false,
|
|
470
|
+
});
|
|
415
471
|
if (!refId) {
|
|
416
472
|
const missing = missingModelRefValue(propertyDescriptor);
|
|
417
473
|
if (missing !== undefined) {
|
|
@@ -419,7 +475,7 @@ async function normalizeObjectField(descriptor, path, raw, context, trail, isCol
|
|
|
419
475
|
}
|
|
420
476
|
continue;
|
|
421
477
|
}
|
|
422
|
-
output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false);
|
|
478
|
+
output[propertyName] = await normalizeModelRef(propertyDescriptor.model, refId, context, trail, false, inlineValue);
|
|
423
479
|
continue;
|
|
424
480
|
}
|
|
425
481
|
if (isArrayDescriptor(propertyDescriptor)) {
|
|
@@ -478,26 +534,34 @@ async function normalizeArrayField(descriptor, path, raw, context, trail) {
|
|
|
478
534
|
return value;
|
|
479
535
|
}
|
|
480
536
|
const id = extractId(row);
|
|
481
|
-
|
|
537
|
+
const valueWithId = attachIdForPrimitiveDescriptor(itemDescriptor, value, id);
|
|
538
|
+
const isObjectValue = valueWithId && typeof valueWithId === "object" && !Array.isArray(valueWithId);
|
|
539
|
+
if (!id) {
|
|
540
|
+
return valueWithId;
|
|
541
|
+
}
|
|
542
|
+
return isObjectValue ? { id, ...valueWithId } : { id, value: valueWithId };
|
|
482
543
|
});
|
|
483
544
|
}
|
|
484
545
|
if (isModelRefDescriptor(itemDescriptor)) {
|
|
485
|
-
const
|
|
546
|
+
const inlineModelDescriptor = context.modelDescriptors.get(itemDescriptor.model);
|
|
547
|
+
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
548
|
+
const inlineModel = inlineModelDescriptor
|
|
549
|
+
? extractInlineModelObject(row, inlineModelDescriptor)
|
|
550
|
+
: null;
|
|
486
551
|
const refId = extractModelRefId(row, itemDescriptor.model, {
|
|
487
552
|
allowObjectIdFallback: false,
|
|
488
|
-
});
|
|
489
|
-
return normalizeModelRef(itemDescriptor.model, refId, context, trail, true);
|
|
490
|
-
})
|
|
491
|
-
return resolved;
|
|
553
|
+
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
554
|
+
return normalizeModelRef(itemDescriptor.model, refId, context, trail, true, row);
|
|
555
|
+
});
|
|
492
556
|
}
|
|
493
557
|
if (isObjectDescriptor(itemDescriptor)) {
|
|
494
|
-
return
|
|
558
|
+
return mapWithConcurrency(envelope.items, context.options.modelNormalizationConcurrency, async (row) => {
|
|
495
559
|
const rowId = extractId(row);
|
|
496
560
|
const rowPath = rowId
|
|
497
561
|
? `${path}/${encodeURIComponent(rowId)}`
|
|
498
562
|
: path;
|
|
499
563
|
return normalizeObjectField(itemDescriptor, rowPath, row, context, trail, true);
|
|
500
|
-
})
|
|
564
|
+
});
|
|
501
565
|
}
|
|
502
566
|
return envelope.items;
|
|
503
567
|
}
|
|
@@ -506,18 +570,29 @@ async function normalizeField(descriptor, path, raw, context, trail, isCollectio
|
|
|
506
570
|
const value = raw && typeof raw === "object" && "value" in raw
|
|
507
571
|
? raw.value
|
|
508
572
|
: raw;
|
|
509
|
-
|
|
573
|
+
let coerced = coerceCustomTypeValue(descriptor, value, {
|
|
510
574
|
locale: context.options.locale,
|
|
511
575
|
defaultLocale: context.options.defaultLocale,
|
|
512
576
|
assetUrlBuilder: context.options.assetUrlBuilder,
|
|
513
577
|
});
|
|
578
|
+
if (shouldIncludeObjectId(context.options.includeIdMode, isCollectionItem)) {
|
|
579
|
+
const id = extractId(raw);
|
|
580
|
+
coerced = attachIdForPrimitiveDescriptor(descriptor, coerced, id);
|
|
581
|
+
}
|
|
582
|
+
return coerced;
|
|
514
583
|
}
|
|
515
584
|
if (isModelRefDescriptor(descriptor)) {
|
|
516
|
-
const
|
|
585
|
+
const inlineModelDescriptor = context.modelDescriptors.get(descriptor.model);
|
|
586
|
+
const inlineModel = inlineModelDescriptor
|
|
587
|
+
? extractInlineModelObject(raw, inlineModelDescriptor)
|
|
588
|
+
: null;
|
|
589
|
+
const refId = extractModelRefId(raw, descriptor.model, {
|
|
590
|
+
allowObjectIdFallback: true,
|
|
591
|
+
}) ?? (inlineModel ? extractId(inlineModel) : null);
|
|
517
592
|
if (!refId) {
|
|
518
593
|
return missingModelRefValue(descriptor);
|
|
519
594
|
}
|
|
520
|
-
return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem);
|
|
595
|
+
return normalizeModelRef(descriptor.model, refId, context, trail, isCollectionItem, raw);
|
|
521
596
|
}
|
|
522
597
|
if (isArrayDescriptor(descriptor)) {
|
|
523
598
|
const normalized = await normalizeArrayField(descriptor, path, raw, context, trail);
|
|
@@ -572,6 +647,39 @@ function createResourceRegistry(descriptor) {
|
|
|
572
647
|
modelsCaseInsensitive,
|
|
573
648
|
};
|
|
574
649
|
}
|
|
650
|
+
function normalizeBaseUrl(baseUrl) {
|
|
651
|
+
const cleaned = baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
652
|
+
if (!cleaned) {
|
|
653
|
+
throw new Error("cms0: apiConfig.baseUrl is required to consume API resources.");
|
|
654
|
+
}
|
|
655
|
+
return cleaned;
|
|
656
|
+
}
|
|
657
|
+
function resolveIncludeIdMode(includeId, globalIncludeId) {
|
|
658
|
+
const enabled = includeId ?? globalIncludeId ?? false;
|
|
659
|
+
return enabled ? "all" : "none";
|
|
660
|
+
}
|
|
661
|
+
function shouldIncludeObjectId(mode, _isCollectionItem) {
|
|
662
|
+
return mode === "all";
|
|
663
|
+
}
|
|
664
|
+
function buildSearchParams(query) {
|
|
665
|
+
const params = new URLSearchParams();
|
|
666
|
+
if (!query)
|
|
667
|
+
return params;
|
|
668
|
+
for (const [key, rawValue] of Object.entries(query)) {
|
|
669
|
+
if (rawValue === undefined || rawValue === null)
|
|
670
|
+
continue;
|
|
671
|
+
if (Array.isArray(rawValue)) {
|
|
672
|
+
rawValue.forEach((value) => {
|
|
673
|
+
if (value === undefined || value === null)
|
|
674
|
+
return;
|
|
675
|
+
params.append(key, String(value));
|
|
676
|
+
});
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
params.set(key, String(rawValue));
|
|
680
|
+
}
|
|
681
|
+
return params;
|
|
682
|
+
}
|
|
575
683
|
async function requestJson(baseUrl, apiKey, path, options) {
|
|
576
684
|
const params = buildSearchParams(options?.query);
|
|
577
685
|
const url = `${baseUrl}/${path}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
@@ -620,9 +728,9 @@ function validateResult(resource, result, descriptor, includeIdMode, zodSchemas,
|
|
|
620
728
|
throw new Error(formatValidationError(modelName, parsed.error));
|
|
621
729
|
}
|
|
622
730
|
}
|
|
623
|
-
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, byId) {
|
|
731
|
+
async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, byId) {
|
|
624
732
|
const responseMode = options?.response ?? "normalized";
|
|
625
|
-
const includeIdMode = resolveIncludeIdMode(options?.includeId);
|
|
733
|
+
const includeIdMode = resolveIncludeIdMode(options?.includeId, globalIncludeId);
|
|
626
734
|
const resolveModelRefs = options?.resolveModelRefs !== false;
|
|
627
735
|
const requestedLocale = options?.locale ??
|
|
628
736
|
(typeof options?.query?.locale === "string"
|
|
@@ -661,12 +769,14 @@ async function runResource(resource, descriptor, baseUrl, apiKey, defaultLocale,
|
|
|
661
769
|
}),
|
|
662
770
|
modelDescriptors,
|
|
663
771
|
modelCache: new Map(),
|
|
772
|
+
sharedModelInflightCache,
|
|
664
773
|
options: {
|
|
665
774
|
includeIdMode,
|
|
666
775
|
resolveModelRefs,
|
|
667
776
|
locale,
|
|
668
777
|
defaultLocale,
|
|
669
778
|
assetUrlBuilder,
|
|
779
|
+
modelNormalizationConcurrency: DEFAULT_MODEL_NORMALIZATION_CONCURRENCY,
|
|
670
780
|
},
|
|
671
781
|
};
|
|
672
782
|
if (resource.isCollection && !byId) {
|
|
@@ -702,9 +812,11 @@ export function createCmsClient(descriptor, config) {
|
|
|
702
812
|
const rootKeys = Array.from(registry.roots.keys());
|
|
703
813
|
const modelKeys = Array.from(registry.models.keys());
|
|
704
814
|
const { zodSchemas, modelZodSchemas } = buildZodSchemasFromDescriptor(descriptor);
|
|
815
|
+
const globalIncludeId = !!config.includeId;
|
|
816
|
+
const sharedModelInflightCache = new Map();
|
|
705
817
|
const buildModelAccessor = (entry) => {
|
|
706
|
-
const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options));
|
|
707
|
-
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options, id);
|
|
818
|
+
const accessor = (async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options));
|
|
819
|
+
accessor.byId = async (id, options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options, id);
|
|
708
820
|
return accessor;
|
|
709
821
|
};
|
|
710
822
|
const modelsProxy = new Proxy({}, {
|
|
@@ -734,10 +846,16 @@ export function createCmsClient(descriptor, config) {
|
|
|
734
846
|
if (!entry) {
|
|
735
847
|
unknownKeyError(property, rootKeys, []);
|
|
736
848
|
}
|
|
737
|
-
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, options);
|
|
849
|
+
return async (options) => runResource(entry, descriptor, baseUrl, apiKey, config.defaultLocale, assetUrlBuilder, zodSchemas, modelZodSchemas, globalIncludeId, sharedModelInflightCache, options);
|
|
738
850
|
},
|
|
739
851
|
});
|
|
740
|
-
|
|
852
|
+
const client = Object.assign(rootProxy, {
|
|
853
|
+
models: modelsProxy,
|
|
854
|
+
locales: config.locales ?? [],
|
|
855
|
+
defaultLocale: config.defaultLocale,
|
|
856
|
+
includeIdDefault: globalIncludeId,
|
|
857
|
+
});
|
|
858
|
+
return client;
|
|
741
859
|
}
|
|
742
860
|
/**
|
|
743
861
|
* Initializes the CMS SDK for a given schema.
|
|
@@ -27,6 +27,13 @@ export declare const customInlineDescriptors: {
|
|
|
27
27
|
customType?: string;
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
|
+
export type CustomInlineTypeName = keyof typeof customInlineDescriptors;
|
|
31
|
+
export type CustomInlineTypeMetadata = {
|
|
32
|
+
includeId?: {
|
|
33
|
+
mapLikeFields?: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export declare const customInlineTypeMetadata: Record<CustomInlineTypeName, CustomInlineTypeMetadata>;
|
|
30
37
|
export declare const customTypeDescriptors: {
|
|
31
38
|
readonly RichText: {
|
|
32
39
|
kind?: "primitive";
|
|
@@ -57,5 +64,6 @@ export type CustomTypeName = keyof typeof customTypeDescriptors;
|
|
|
57
64
|
export declare const customTypeNames: Set<string>;
|
|
58
65
|
export declare const customModelTypeNames: Set<string>;
|
|
59
66
|
export declare const customInlineTypeNames: Set<string>;
|
|
67
|
+
export declare function getCustomInlineTypeMetadata(name: string): CustomInlineTypeMetadata | undefined;
|
|
60
68
|
export declare function resolveCustomTypeDependencies(names: Iterable<string>): Set<string>;
|
|
61
69
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/custom-types/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA6DpF,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;CAI1B,CAAC;AAEX,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;CAGxB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,qBAAqB,CAAC;AAEhE,eAAO,MAAM,eAAe,aAAsD,CAAC;AACnF,eAAO,MAAM,oBAAoB,aAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwBlF"}
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/custom-types/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA6DpF,eAAO,MAAM,sBAAsB;;;;CAIzB,CAAC;AAEX,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;CAI1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,MAAM,OAAO,uBAAuB,CAAC;AAExE,MAAM,MAAM,wBAAwB,GAAG;IACrC,SAAS,CAAC,EAAE;QAEV,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACnC,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,oBAAoB,EACpB,wBAAwB,CASzB,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;CAGxB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,qBAAqB,CAAC;AAEhE,eAAO,MAAM,eAAe,aAAsD,CAAC;AACnF,eAAO,MAAM,oBAAoB,aAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,aAEjC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,SAAS,CAEtC;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAwBlF"}
|