@byline/core 3.13.2 → 3.14.0
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.
|
@@ -405,6 +405,27 @@ export interface RelationField extends NonlocalizableField {
|
|
|
405
405
|
* summary. Falls back to the first text field if omitted.
|
|
406
406
|
*/
|
|
407
407
|
displayField?: string;
|
|
408
|
+
/**
|
|
409
|
+
* When `true`, the field holds an **ordered list** of target references
|
|
410
|
+
* rather than a single one. The value shape becomes an array of relation
|
|
411
|
+
* envelopes (`RelatedDocumentValue[]`), persisted as indexed `store_relation`
|
|
412
|
+
* rows (`<field>.0`, `<field>.1`, …) and reconstructed in order. The admin
|
|
413
|
+
* widget renders an add / remove / drag-reorder list of summary tiles.
|
|
414
|
+
*
|
|
415
|
+
* A given target may appear at most once (the widget dedups on
|
|
416
|
+
* `targetDocumentId`). Defaults to single-target (`false` / absent).
|
|
417
|
+
*/
|
|
418
|
+
hasMany?: boolean;
|
|
419
|
+
/**
|
|
420
|
+
* Minimum number of references required when `hasMany` is `true`. Enforced by
|
|
421
|
+
* the generated Zod schema. Ignored for single-target relations.
|
|
422
|
+
*/
|
|
423
|
+
minItems?: number;
|
|
424
|
+
/**
|
|
425
|
+
* Maximum number of references allowed when `hasMany` is `true`. Enforced by
|
|
426
|
+
* the generated Zod schema. Ignored for single-target relations.
|
|
427
|
+
*/
|
|
428
|
+
maxItems?: number;
|
|
408
429
|
}
|
|
409
430
|
export interface FileField extends NonlocalizableField {
|
|
410
431
|
type: 'file';
|
|
@@ -155,7 +155,7 @@ export const fieldToZodSchema = (field, strict = true) => {
|
|
|
155
155
|
// (recursive) and is not constrained here.
|
|
156
156
|
schema = z.any();
|
|
157
157
|
break;
|
|
158
|
-
case 'relation':
|
|
158
|
+
case 'relation': {
|
|
159
159
|
// Relation values are `RelatedDocumentValue` objects:
|
|
160
160
|
// { targetDocumentId, targetCollectionId,
|
|
161
161
|
// relationshipType?, cascadeDelete? }
|
|
@@ -165,15 +165,26 @@ export const fieldToZodSchema = (field, strict = true) => {
|
|
|
165
165
|
//
|
|
166
166
|
// Populated responses (depth > 0) skip this schema in the route layer
|
|
167
167
|
// because the tree then contains nested documents, not bare refs.
|
|
168
|
-
|
|
169
|
-
.object({
|
|
168
|
+
const relationValue = z.object({
|
|
170
169
|
targetDocumentId: z.string(),
|
|
171
170
|
targetCollectionId: z.string(),
|
|
172
171
|
relationshipType: z.string().optional(),
|
|
173
172
|
cascadeDelete: z.boolean().optional(),
|
|
174
|
-
})
|
|
175
|
-
|
|
173
|
+
});
|
|
174
|
+
if (field.hasMany) {
|
|
175
|
+
// Ordered list of relation values; `minItems` / `maxItems` bound it.
|
|
176
|
+
let arr = z.array(relationValue);
|
|
177
|
+
if (typeof field.minItems === 'number')
|
|
178
|
+
arr = arr.min(field.minItems);
|
|
179
|
+
if (typeof field.maxItems === 'number')
|
|
180
|
+
arr = arr.max(field.maxItems);
|
|
181
|
+
schema = arr;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
schema = relationValue.nullable();
|
|
185
|
+
}
|
|
176
186
|
break;
|
|
187
|
+
}
|
|
177
188
|
default:
|
|
178
189
|
schema = z.string();
|
|
179
190
|
}
|
|
@@ -317,6 +317,24 @@ function collectRelationLeaves(fields, fieldDefs, populate, acc) {
|
|
|
317
317
|
const sub = matchesPopulate(leaf.field.name, populate);
|
|
318
318
|
if (sub === undefined)
|
|
319
319
|
continue;
|
|
320
|
+
// hasMany: the value is an ordered array of relation envelopes. Expand it
|
|
321
|
+
// into one leaf ref per element, whose `parent` is the array itself and
|
|
322
|
+
// `key` is the index — so the envelope-assignment pass writes each
|
|
323
|
+
// populated result back into its array slot, in place.
|
|
324
|
+
if (leaf.field.hasMany) {
|
|
325
|
+
if (!Array.isArray(leaf.value))
|
|
326
|
+
continue;
|
|
327
|
+
const arr = leaf.value;
|
|
328
|
+
for (let i = 0; i < arr.length; i++) {
|
|
329
|
+
const item = arr[i];
|
|
330
|
+
if (!isRelatedDocumentValue(item))
|
|
331
|
+
continue;
|
|
332
|
+
if ('_resolved' in item)
|
|
333
|
+
continue;
|
|
334
|
+
acc.push({ parent: arr, key: String(i), field: leaf.field, value: item, sub });
|
|
335
|
+
}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
320
338
|
if (!isRelatedDocumentValue(leaf.value))
|
|
321
339
|
continue;
|
|
322
340
|
// Skip leaves that have already been replaced (e.g. via shared-ref
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/core",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.14.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"sharp": "^0.34.5",
|
|
80
80
|
"uuid": "^14.0.0",
|
|
81
81
|
"zod": "^4.4.3",
|
|
82
|
-
"@byline/auth": "3.
|
|
82
|
+
"@byline/auth": "3.14.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@biomejs/biome": "2.4.15",
|