@webtypen/webframez-core 0.3.101 → 0.3.103
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/Commands/QueueWorkerCommand.js +1 -1
- package/dist/Datatable/Datatable.d.ts +3 -0
- package/dist/Datatable/Datatable.js +36 -7
- package/dist/Datatable/DatatableColumnPreferences.d.ts +10 -0
- package/dist/Datatable/DatatableColumnPreferences.js +37 -3
- package/dist/Datatable/DatatableController.js +6 -4
- package/dist/Datatable/DatatableSchemaColumns.d.ts +8 -0
- package/dist/Datatable/DatatableSchemaColumns.js +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/datatables.md +25 -0
- package/docs/queue-workers.md +5 -0
- package/package.json +1 -1
|
@@ -371,7 +371,7 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
371
371
|
return null;
|
|
372
372
|
}
|
|
373
373
|
const jobclasses = [...this.workerConfig.jobclasses];
|
|
374
|
-
if (Config_1.Config.get("backup") && !jobclasses.includes("BackupRunJob")) {
|
|
374
|
+
if (this.workerConfig.includeBackupJobs !== false && Config_1.Config.get("backup") && !jobclasses.includes("BackupRunJob")) {
|
|
375
375
|
jobclasses.push("BackupRunJob");
|
|
376
376
|
}
|
|
377
377
|
return jobclasses;
|
|
@@ -54,6 +54,9 @@ export declare class Datatable {
|
|
|
54
54
|
} | null;
|
|
55
55
|
getInit(req: Request): Promise<any>;
|
|
56
56
|
private loadColumnState;
|
|
57
|
+
/** Options may be declared inline or loaded from an authenticated host callback. */
|
|
58
|
+
getColumnDisplayOptions(req: Request, key: string): Promise<any>;
|
|
59
|
+
private resolveDisplayOptions;
|
|
57
60
|
private getColumnState;
|
|
58
61
|
saveColumnPreferences(req: Request, columns: unknown): Promise<any>;
|
|
59
62
|
resetColumnPreferences(req: Request): Promise<any>;
|
|
@@ -64,13 +64,13 @@ class Datatable {
|
|
|
64
64
|
this.getFilter(req), this.getColumnState(req), this.getOnPressLink(req),
|
|
65
65
|
]);
|
|
66
66
|
out.filter = filter;
|
|
67
|
-
out.columns = Object.entries(state.visible).map(([key, column]) => (Object.assign(Object.assign({}, column), { key })));
|
|
68
|
-
out.availableColumns = Object.entries(state.all).map(([key, column]) => (Object.assign(Object.assign({}, column), { key })));
|
|
67
|
+
out.columns = Object.entries(state.visible).map(([key, column]) => (Object.assign(Object.assign({}, column), { displayOptions: typeof column.displayOptions === "function" ? true : column.displayOptions, key })));
|
|
68
|
+
out.availableColumns = Object.entries(state.all).map(([key, column]) => (Object.assign(Object.assign({}, column), { displayOptions: typeof column.displayOptions === "function" ? true : column.displayOptions, key })));
|
|
69
69
|
out.columnPreferences = Boolean(state.scope) && Object.keys(state.all).length > 0;
|
|
70
70
|
out.columnSelection = out.columns.map((column) => {
|
|
71
|
-
var _a
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
var _a;
|
|
72
|
+
const saved = (_a = state.preferences) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.key === column.key);
|
|
73
|
+
return Object.assign(Object.assign(Object.assign({ key: column.key }, ((saved === null || saved === void 0 ? void 0 : saved.width) ? { width: saved.width } : {})), ((saved === null || saved === void 0 ? void 0 : saved.title) ? { title: saved.title } : {})), ((saved === null || saved === void 0 ? void 0 : saved.displayField) && saved.displayField === column.displayField ? { displayField: saved.displayField } : {}));
|
|
74
74
|
});
|
|
75
75
|
out.onPressLink = onPressLink;
|
|
76
76
|
if (this.exports) {
|
|
@@ -97,7 +97,35 @@ class Datatable {
|
|
|
97
97
|
const store = new DatatablePreferencesStore_1.DatatablePreferencesStore(config || {});
|
|
98
98
|
const scope = config === false ? null : yield store.scope(req);
|
|
99
99
|
const preferences = scope ? yield store.load(scope) : null;
|
|
100
|
-
|
|
100
|
+
const savedKeys = Array.isArray(preferences) ? preferences.map(entry => entry === null || entry === void 0 ? void 0 : entry.key).filter(key => Object.prototype.hasOwnProperty.call(all, key)) : [];
|
|
101
|
+
const selected = savedKeys.length ? savedKeys : Object.keys(all).filter(key => !all[key].hidden);
|
|
102
|
+
const resolved = yield this.resolveDisplayOptions(req, all, selected);
|
|
103
|
+
return { all, visible: DatatableColumnPreferences_1.DatatableColumnPreferences.apply(resolved, preferences), preferences, store, scope };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Options may be declared inline or loaded from an authenticated host callback. */
|
|
107
|
+
getColumnDisplayOptions(req, key) {
|
|
108
|
+
var _a;
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
const all = (_a = yield this.getColumns(req)) !== null && _a !== void 0 ? _a : {};
|
|
111
|
+
if (typeof key !== "string" || !Object.prototype.hasOwnProperty.call(all, key)) {
|
|
112
|
+
throw Object.assign(new Error("Unbekannte Spalte."), { status: 400 });
|
|
113
|
+
}
|
|
114
|
+
const resolved = yield this.resolveDisplayOptions(req, all, [key]);
|
|
115
|
+
return resolved[key].displayOptions || [];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
resolveDisplayOptions(req, columns, keys) {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
const resolved = Object.assign({}, columns);
|
|
121
|
+
yield Promise.all(Array.from(new Set(keys)).filter(key => typeof key === "string" && Object.prototype.hasOwnProperty.call(columns, key)).map((key) => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
const column = columns[key];
|
|
123
|
+
if (typeof column.displayOptions === "function") {
|
|
124
|
+
const options = yield column.displayOptions(req, key);
|
|
125
|
+
resolved[key] = Object.assign(Object.assign({}, column), { displayOptions: Array.isArray(options) ? options : [] });
|
|
126
|
+
}
|
|
127
|
+
})));
|
|
128
|
+
return resolved;
|
|
101
129
|
});
|
|
102
130
|
}
|
|
103
131
|
getColumnState(req) {
|
|
@@ -115,7 +143,8 @@ class Datatable {
|
|
|
115
143
|
throw Object.assign(new Error("Spalteneinstellungen sind für diesen Benutzer nicht verfügbar."), { status: 403 });
|
|
116
144
|
let preferences;
|
|
117
145
|
try {
|
|
118
|
-
|
|
146
|
+
const resolved = yield this.resolveDisplayOptions(req, state.all, Array.isArray(columns) ? columns.map(entry => entry === null || entry === void 0 ? void 0 : entry.key) : []);
|
|
147
|
+
preferences = DatatableColumnPreferences_1.DatatableColumnPreferences.validate(columns, resolved);
|
|
119
148
|
}
|
|
120
149
|
catch (error) {
|
|
121
150
|
throw Object.assign(error, { status: 400 });
|
|
@@ -10,6 +10,15 @@ export interface DatatableColumnPreference {
|
|
|
10
10
|
key: string;
|
|
11
11
|
/** Omit to retain the current backend default. */
|
|
12
12
|
width?: DatatableColumnWidth;
|
|
13
|
+
title?: string;
|
|
14
|
+
displayField?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface DatatableDisplayOption {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
plain?: boolean;
|
|
21
|
+
options?: any[];
|
|
13
22
|
}
|
|
14
23
|
export interface DatatableColumnPreferencesConfig {
|
|
15
24
|
collection?: string;
|
|
@@ -26,5 +35,6 @@ export interface DatatableColumnContext {
|
|
|
26
35
|
}
|
|
27
36
|
export declare class DatatableColumnPreferences {
|
|
28
37
|
static validate(value: unknown, columns: Record<string, any>): DatatableColumnPreference[];
|
|
38
|
+
static display(definition: any, selected?: string): any;
|
|
29
39
|
static apply(columns: Record<string, any>, preferences: unknown): Record<string, any>;
|
|
30
40
|
}
|
|
@@ -13,6 +13,19 @@ class DatatableColumnPreferences {
|
|
|
13
13
|
}
|
|
14
14
|
seen.add(entry.key);
|
|
15
15
|
const preference = { key: entry.key };
|
|
16
|
+
if (entry.title !== undefined) {
|
|
17
|
+
if (typeof entry.title !== "string" || entry.title.length > 160)
|
|
18
|
+
throw new Error("Ungültiger Spaltentitel (maximal 160 Zeichen).");
|
|
19
|
+
if (entry.title.trim())
|
|
20
|
+
preference.title = entry.title.trim();
|
|
21
|
+
}
|
|
22
|
+
if (entry.displayField !== undefined && entry.displayField !== "") {
|
|
23
|
+
if (typeof entry.displayField !== "string" || !Array.isArray(columns[entry.key].displayOptions)
|
|
24
|
+
|| !columns[entry.key].displayOptions.some((option) => option.value === entry.displayField)) {
|
|
25
|
+
throw new Error("Unzulässiges Anzeigefeld für diese Spalte.");
|
|
26
|
+
}
|
|
27
|
+
preference.displayField = entry.displayField;
|
|
28
|
+
}
|
|
16
29
|
if (entry.width !== undefined) {
|
|
17
30
|
const width = entry.width;
|
|
18
31
|
if ((width === null || width === void 0 ? void 0 : width.mode) === "auto") {
|
|
@@ -29,11 +42,28 @@ class DatatableColumnPreferences {
|
|
|
29
42
|
return preference;
|
|
30
43
|
});
|
|
31
44
|
}
|
|
45
|
+
static display(definition, selected) {
|
|
46
|
+
const column = Object.assign({}, definition);
|
|
47
|
+
const options = Array.isArray(column.displayOptions) ? column.displayOptions : [];
|
|
48
|
+
const option = options.find(entry => entry.value === (selected || column.displayField)) || options[0];
|
|
49
|
+
if (option) {
|
|
50
|
+
column.displayField = option.value;
|
|
51
|
+
for (const key of ["type", "plain", "options"])
|
|
52
|
+
if (option[key] !== undefined)
|
|
53
|
+
column[key] = option[key];
|
|
54
|
+
}
|
|
55
|
+
return column;
|
|
56
|
+
}
|
|
32
57
|
static apply(columns, preferences) {
|
|
33
|
-
const defaults = () => Object.fromEntries(Object.entries(columns).filter(([, column]) => column.hidden !== true));
|
|
58
|
+
const defaults = () => Object.fromEntries(Object.entries(columns).filter(([, column]) => column.hidden !== true).map(([key, column]) => [key, this.display(column)]));
|
|
34
59
|
if (!Array.isArray(preferences))
|
|
35
60
|
return defaults();
|
|
36
|
-
const available = preferences.filter(entry => entry && typeof entry.key === "string" && Object.prototype.hasOwnProperty.call(columns, entry.key))
|
|
61
|
+
const available = preferences.filter(entry => entry && typeof entry.key === "string" && Object.prototype.hasOwnProperty.call(columns, entry.key)).map(entry => {
|
|
62
|
+
const options = columns[entry.key].displayOptions;
|
|
63
|
+
// Removed target fields fall back independently; retain the rest of the layout.
|
|
64
|
+
return entry.displayField && (!Array.isArray(options) || !options.some((option) => option.value === entry.displayField))
|
|
65
|
+
? Object.assign(Object.assign({}, entry), { displayField: undefined }) : entry;
|
|
66
|
+
});
|
|
37
67
|
if (!available.length)
|
|
38
68
|
return defaults();
|
|
39
69
|
let validated;
|
|
@@ -44,7 +74,11 @@ class DatatableColumnPreferences {
|
|
|
44
74
|
return defaults();
|
|
45
75
|
}
|
|
46
76
|
return Object.fromEntries(validated.map(preference => {
|
|
47
|
-
const column =
|
|
77
|
+
const column = this.display(columns[preference.key], preference.displayField);
|
|
78
|
+
if (preference.title) {
|
|
79
|
+
column.title = preference.title;
|
|
80
|
+
column.label = preference.title;
|
|
81
|
+
}
|
|
48
82
|
if (preference.width) {
|
|
49
83
|
delete column.width;
|
|
50
84
|
delete column.minWidth;
|
|
@@ -40,11 +40,13 @@ class DatatableController extends Controller_1.Controller {
|
|
|
40
40
|
if (req.body.init_request || req.body.mode === "columns-get") {
|
|
41
41
|
return res.send({ status: "success", data: yield table.getInit(req) });
|
|
42
42
|
}
|
|
43
|
-
if (
|
|
43
|
+
if (["columns-save", "columns-reset", "columns-options"].includes(req.body.mode)) {
|
|
44
44
|
try {
|
|
45
|
-
const data = req.body.mode === "columns-
|
|
46
|
-
? yield table.
|
|
47
|
-
:
|
|
45
|
+
const data = req.body.mode === "columns-options"
|
|
46
|
+
? yield table.getColumnDisplayOptions(req, req.body.column)
|
|
47
|
+
: req.body.mode === "columns-save"
|
|
48
|
+
? yield table.saveColumnPreferences(req, req.body.columns)
|
|
49
|
+
: yield table.resetColumnPreferences(req);
|
|
48
50
|
return res.send({ status: "success", data });
|
|
49
51
|
}
|
|
50
52
|
catch (error) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Converts DataBuilder/DataSchema field definitions into an extensible column catalog. */
|
|
2
|
+
export declare class DatatableSchemaColumns {
|
|
3
|
+
static fromSchema(schema: Record<string, any>, options?: {
|
|
4
|
+
prefix?: string;
|
|
5
|
+
labelPrefix?: string;
|
|
6
|
+
prepare?: (column: Record<string, any>, field: any, path: string) => Record<string, any> | null;
|
|
7
|
+
}): Record<string, any>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatatableSchemaColumns = void 0;
|
|
4
|
+
/** Converts DataBuilder/DataSchema field definitions into an extensible column catalog. */
|
|
5
|
+
class DatatableSchemaColumns {
|
|
6
|
+
static fromSchema(schema, options = {}) {
|
|
7
|
+
const columns = {};
|
|
8
|
+
for (const [key, field] of Object.entries(schema || {})) {
|
|
9
|
+
if (!field || /[.$]/.test(key) || ["__proto__", "constructor", "prototype"].includes(key)
|
|
10
|
+
|| field.secret || field.writeOnly || field.type === "password")
|
|
11
|
+
continue;
|
|
12
|
+
const mapping = options.prefix ? `${options.prefix}.${key}` : key;
|
|
13
|
+
const label = [options.labelPrefix, field.label || key].filter(Boolean).join(" · ");
|
|
14
|
+
if (field.type === "object") {
|
|
15
|
+
Object.assign(columns, this.fromSchema(field.schema, Object.assign(Object.assign({}, options), { prefix: mapping, labelPrefix: label })));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const types = { integer: "number", float: "number", number: "number", boolean: "boolean", date: "date", datetime: "datetime", option: "option", currency_cent: "currency", currency: "currency" };
|
|
19
|
+
const column = Object.assign(Object.assign({ label, mapping, type: types[field.type] || "string", width: 170, hidden: true, sortable: !["array", "file", "filemanager_file", "relation"].includes(field.type) && !field.multi }, (field.type === "currency" ? { plain: true } : {})), (field.options ? { options: field.options } : {}));
|
|
20
|
+
const prepared = options.prepare ? options.prepare(column, field, mapping) : column;
|
|
21
|
+
if (prepared)
|
|
22
|
+
columns[mapping] = prepared;
|
|
23
|
+
}
|
|
24
|
+
return columns;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.DatatableSchemaColumns = DatatableSchemaColumns;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./DataBuilder/DataBuilder";
|
|
|
29
29
|
export * from "./DataBuilder/DataBuilderController";
|
|
30
30
|
export * from "./Datatable/Datatable";
|
|
31
31
|
export * from "./Datatable/DatatableColumnPreferences";
|
|
32
|
+
export * from "./Datatable/DatatableSchemaColumns";
|
|
32
33
|
export * from "./Datatable/DatatablePreferencesStore";
|
|
33
34
|
export * from "./Datatable/DatatableRegistry";
|
|
34
35
|
export * from "./Datatable/DatatableController";
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ __exportStar(require("./DataBuilder/DataBuilder"), exports);
|
|
|
46
46
|
__exportStar(require("./DataBuilder/DataBuilderController"), exports);
|
|
47
47
|
__exportStar(require("./Datatable/Datatable"), exports);
|
|
48
48
|
__exportStar(require("./Datatable/DatatableColumnPreferences"), exports);
|
|
49
|
+
__exportStar(require("./Datatable/DatatableSchemaColumns"), exports);
|
|
49
50
|
__exportStar(require("./Datatable/DatatablePreferencesStore"), exports);
|
|
50
51
|
__exportStar(require("./Datatable/DatatableRegistry"), exports);
|
|
51
52
|
__exportStar(require("./Datatable/DatatableController"), exports);
|
package/docs/datatables.md
CHANGED
|
@@ -101,3 +101,28 @@ mappings, active filter/search/sort fields and sums, as well as model-selector
|
|
|
101
101
|
preview fields. This avoids breaking a sort/filter on a hidden column. Dependencies
|
|
102
102
|
inside custom expressions, custom filters, exports or other calculated columns
|
|
103
103
|
remain the table author's responsibility.
|
|
104
|
+
|
|
105
|
+
## Relation display fields and custom titles
|
|
106
|
+
|
|
107
|
+
A column can declare `displayOptions: [{ value: "name", label: "Name", type: "string" }]`
|
|
108
|
+
or an async `displayOptions(req, columnKey)` callback. The callback receives the
|
|
109
|
+
normal authenticated request. Initialization advertises lazy options with `true`;
|
|
110
|
+
`mode: "columns-options", column: "key"` resolves them when the editor needs them.
|
|
111
|
+
Only visible columns resolve options during normal loading. `displayField` selects
|
|
112
|
+
the backend default; otherwise the first option is used.
|
|
113
|
+
|
|
114
|
+
Saved preferences accept `title` (up to 160 characters) and `displayField` in
|
|
115
|
+
addition to width. Empty values restore the backend defaults. An unknown display
|
|
116
|
+
field is rejected on save; if a previously saved field disappears, loading falls
|
|
117
|
+
back to the default while preserving the other layout settings.
|
|
118
|
+
|
|
119
|
+
Core validates the selection and exposes it through `context.columns[key].displayField`;
|
|
120
|
+
the host performs the relation lookup and writes the value to the column's stable
|
|
121
|
+
`mapping`. Options can supply `type`, `plain` and `options` for typed rendering.
|
|
122
|
+
Never interpret an unchecked client field as a database projection.
|
|
123
|
+
|
|
124
|
+
`DatatableSchemaColumns.fromSchema(schema, { prefix, labelPrefix, prepare })`
|
|
125
|
+
creates an optional column catalog from nested DataBuilder/DataSchema fields.
|
|
126
|
+
The `prepare(column, field, path)` callback can attach inline or lazy display
|
|
127
|
+
options, change the mapping, or return null to omit a field. Plugin installation,
|
|
128
|
+
tenant checks and relation hydration belong in the host adapter.
|
package/docs/queue-workers.md
CHANGED
|
@@ -130,3 +130,8 @@ independent bounded pool using the same atomic claims and renewable leases as
|
|
|
130
130
|
`Queue.runOnce`. `await worker.stop({drain: true})` stops new claims and waits for
|
|
131
131
|
active handlers; `worker.stop()` aborts that pool's handlers. Pools do not share
|
|
132
132
|
abort controllers. Host-wide resource limits remain the application's concern.
|
|
133
|
+
|
|
134
|
+
Dedicated workers with an explicit `jobclasses` allowlist may set
|
|
135
|
+
`includeBackupJobs: false` to prevent the automatic addition of `BackupRunJob`.
|
|
136
|
+
This keeps long backups out of a latency-sensitive worker. Existing workers keep
|
|
137
|
+
the previous default; explicitly listed backup jobs remain allowed.
|
package/package.json
CHANGED