@byline/core 3.20.2 → 3.20.3
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.
|
@@ -54,6 +54,22 @@ export interface ColumnDefinition<T = any> {
|
|
|
54
54
|
className?: string;
|
|
55
55
|
formatter?: ColumnFormatter<T>;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Default sort for a collection's list view, applied when the URL carries
|
|
59
|
+
* no explicit `order`/`desc` search params. An explicit param always wins
|
|
60
|
+
* (a shared link opens exactly as sent).
|
|
61
|
+
*
|
|
62
|
+
* `field` is a top-level schema field name (e.g. `'publicationDate'`) or a
|
|
63
|
+
* document-level column (`'createdAt'`, `'updatedAt'`, `'path'`). Validated
|
|
64
|
+
* at boot by `validateAdminConfigs`. Not allowed on `orderable: true`
|
|
65
|
+
* collections — manual ordering already owns their default sort
|
|
66
|
+
* (`order_key asc`).
|
|
67
|
+
*/
|
|
68
|
+
export interface ListDefaultSort<T = any> {
|
|
69
|
+
field: keyof T | 'createdAt' | 'updatedAt' | 'path';
|
|
70
|
+
/** Defaults to `'asc'`. */
|
|
71
|
+
direction?: 'asc' | 'desc';
|
|
72
|
+
}
|
|
57
73
|
/**
|
|
58
74
|
* A single tab inside a tab set.
|
|
59
75
|
*/
|
|
@@ -181,6 +197,21 @@ export interface CollectionAdminConfig<T = any> {
|
|
|
181
197
|
group?: string;
|
|
182
198
|
/** Column definitions for the collection list view. */
|
|
183
199
|
columns?: ColumnDefinition<T>[];
|
|
200
|
+
/**
|
|
201
|
+
* Default sort for the list view when the URL carries no explicit
|
|
202
|
+
* `order`/`desc` search params — e.g. a publications library that should
|
|
203
|
+
* open newest-publication-first:
|
|
204
|
+
*
|
|
205
|
+
* ```ts
|
|
206
|
+
* defaultSort: { field: 'publicationDate', direction: 'desc' },
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* Precedence: explicit URL params → this default → `created_at desc`.
|
|
210
|
+
* Boot-validated: `field` must be a top-level schema field or a document
|
|
211
|
+
* column (`createdAt` / `updatedAt` / `path`), and the option is rejected
|
|
212
|
+
* on `orderable: true` collections (manual ordering owns their default).
|
|
213
|
+
*/
|
|
214
|
+
defaultSort?: ListDefaultSort<T>;
|
|
184
215
|
/**
|
|
185
216
|
* Column definitions for rendering this collection as a compact **item
|
|
186
217
|
* row / tile** wherever a single document is shown outside its own list —
|
|
@@ -24,6 +24,11 @@ import type { CollectionAdminConfig, CollectionDefinition } from '../@types/inde
|
|
|
24
24
|
* schema field names; groups exclude tabSets and nested groups.
|
|
25
25
|
* 6. `fields` map sanity — every key in `admin.fields` matches a top-level
|
|
26
26
|
* schema field name.
|
|
27
|
+
* 7. `defaultSort` sanity — `field` resolves to a top-level schema field
|
|
28
|
+
* or a document-level column (`createdAt` / `updatedAt` / `path`), the
|
|
29
|
+
* direction (when given) is `asc` | `desc`, and the option is rejected
|
|
30
|
+
* on `orderable: true` collections (manual ordering owns their default
|
|
31
|
+
* sort and the drag-to-reorder canonical-view check assumes it).
|
|
27
32
|
*
|
|
28
33
|
* Throws a plain `Error` (not a `BylineError`) because configuration
|
|
29
34
|
* validation runs at startup, before the logger and error registry are
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
* schema field names; groups exclude tabSets and nested groups.
|
|
24
24
|
* 6. `fields` map sanity — every key in `admin.fields` matches a top-level
|
|
25
25
|
* schema field name.
|
|
26
|
+
* 7. `defaultSort` sanity — `field` resolves to a top-level schema field
|
|
27
|
+
* or a document-level column (`createdAt` / `updatedAt` / `path`), the
|
|
28
|
+
* direction (when given) is `asc` | `desc`, and the option is rejected
|
|
29
|
+
* on `orderable: true` collections (manual ordering owns their default
|
|
30
|
+
* sort and the drag-to-reorder canonical-view check assumes it).
|
|
26
31
|
*
|
|
27
32
|
* Throws a plain `Error` (not a `BylineError`) because configuration
|
|
28
33
|
* validation runs at startup, before the logger and error registry are
|
|
@@ -57,6 +62,26 @@ function validateOne(admin, collectionsByPath) {
|
|
|
57
62
|
if ('name' in field)
|
|
58
63
|
topLevelFieldNames.add(field.name);
|
|
59
64
|
}
|
|
65
|
+
// Rule 7 — defaultSort sanity. The list read applies this spec verbatim
|
|
66
|
+
// (host list server fn → parseSort), so a bad field name would silently
|
|
67
|
+
// fall back to `created_at desc` at request time — fail loudly at boot
|
|
68
|
+
// instead.
|
|
69
|
+
if (admin.defaultSort != null) {
|
|
70
|
+
const { field, direction } = admin.defaultSort;
|
|
71
|
+
const documentColumns = new Set(['createdAt', 'updatedAt', 'path']);
|
|
72
|
+
if (collection.orderable === true) {
|
|
73
|
+
fail(`defaultSort is not allowed on an orderable collection — manual ordering owns the default sort (order_key asc).`);
|
|
74
|
+
}
|
|
75
|
+
if (typeof field !== 'string' || field.length === 0) {
|
|
76
|
+
fail(`defaultSort.field must be a non-empty string.`);
|
|
77
|
+
}
|
|
78
|
+
if (!topLevelFieldNames.has(field) && !documentColumns.has(field)) {
|
|
79
|
+
fail(`defaultSort.field "${String(field)}" is not a top-level schema field or a document column (createdAt, updatedAt, path).`);
|
|
80
|
+
}
|
|
81
|
+
if (direction != null && direction !== 'asc' && direction !== 'desc') {
|
|
82
|
+
fail(`defaultSort.direction must be 'asc' or 'desc' (got "${String(direction)}").`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
60
85
|
// Build primitive lookup tables.
|
|
61
86
|
const tabSets = admin.tabSets ?? [];
|
|
62
87
|
const rows = admin.rows ?? [];
|
|
@@ -201,6 +201,43 @@ describe('validateAdminConfigs', () => {
|
|
|
201
201
|
};
|
|
202
202
|
expect(() => validateAdminConfigs([admin], [collection])).toThrow(/not a top-level schema field/);
|
|
203
203
|
});
|
|
204
|
+
// Rule 7 — defaultSort sanity.
|
|
205
|
+
it('accepts a defaultSort on a schema field', () => {
|
|
206
|
+
const admin = {
|
|
207
|
+
...baseAdmin,
|
|
208
|
+
defaultSort: { field: 'title', direction: 'desc' },
|
|
209
|
+
};
|
|
210
|
+
expect(() => validateAdminConfigs([admin], [collection])).not.toThrow();
|
|
211
|
+
});
|
|
212
|
+
it('accepts a defaultSort on a document column', () => {
|
|
213
|
+
const admin = {
|
|
214
|
+
...baseAdmin,
|
|
215
|
+
defaultSort: { field: 'updatedAt' },
|
|
216
|
+
};
|
|
217
|
+
expect(() => validateAdminConfigs([admin], [collection])).not.toThrow();
|
|
218
|
+
});
|
|
219
|
+
it('rejects a defaultSort field that resolves to nothing', () => {
|
|
220
|
+
const admin = {
|
|
221
|
+
...baseAdmin,
|
|
222
|
+
defaultSort: { field: 'nonexistent' },
|
|
223
|
+
};
|
|
224
|
+
expect(() => validateAdminConfigs([admin], [collection])).toThrow(/defaultSort\.field "nonexistent"/);
|
|
225
|
+
});
|
|
226
|
+
it('rejects a defaultSort direction outside asc/desc', () => {
|
|
227
|
+
const admin = {
|
|
228
|
+
...baseAdmin,
|
|
229
|
+
defaultSort: { field: 'title', direction: 'sideways' },
|
|
230
|
+
};
|
|
231
|
+
expect(() => validateAdminConfigs([admin], [collection])).toThrow(/defaultSort\.direction must be 'asc' or 'desc'/);
|
|
232
|
+
});
|
|
233
|
+
it('rejects a defaultSort on an orderable collection', () => {
|
|
234
|
+
const orderableCollection = { ...collection, orderable: true };
|
|
235
|
+
const admin = {
|
|
236
|
+
...baseAdmin,
|
|
237
|
+
defaultSort: { field: 'title' },
|
|
238
|
+
};
|
|
239
|
+
expect(() => validateAdminConfigs([admin], [orderableCollection])).toThrow(/not allowed on an orderable collection/);
|
|
240
|
+
});
|
|
204
241
|
// Layout omitted — bookkeeping skipped, primitive declarations still checked.
|
|
205
242
|
it('skips placement bookkeeping when layout is omitted', () => {
|
|
206
243
|
const admin = {
|
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.20.
|
|
5
|
+
"version": "3.20.3",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"pino": "^10.3.1",
|
|
77
77
|
"sharp": "^0.35.3",
|
|
78
78
|
"zod": "^4.4.3",
|
|
79
|
-
"@byline/auth": "3.20.
|
|
79
|
+
"@byline/auth": "3.20.3"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@biomejs/biome": "2.5.2",
|