@webtypen/webframez-core 0.3.64 → 0.3.66

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/README.md CHANGED
@@ -32,7 +32,8 @@ node_modules/@webtypen/webframez-core/agents/webframez_rules.md
32
32
  ```
33
33
 
34
34
  Die Regeln beschreiben unter anderem:
35
- - bevorzugte Nutzung von Model.objectId(...) statt eigener ObjectId-Resolver
35
+ - verbindliche Nutzung konkreter Model-Klassen fuer fachliche Datenzugriffe statt Collection-Wrappern
36
+ - Nutzung von Model.objectId(...) statt eigener ObjectId-Resolver
36
37
  - bevorzugte Nutzung der vorhandenen Helper wie StringFunctions, NumericFunctions und DateFunctions
37
38
  - Verwendung der Webframez-Abstraktionen fuer Request, Response, Middleware, Storage, Datatables und Routes
38
39
 
@@ -49,6 +50,8 @@ Uebernimm fuer alle Webframez-bezogenen Implementierungen die Konventionen aus:
49
50
  ./node_modules/@webtypen/webframez-core/agents/webframez_rules.md
50
51
 
51
52
  Insbesondere gilt:
53
+ - pro persistierter Entitaet eine Model-Klasse unter app/Models anlegen; Controller, Services und Screens greifen ueber diese Klassen zu
54
+ - keine DatabaseService.collection(...)-Wrapper oder eigene Cursor-/JSON-Persistenzschichten implementieren; fehlende atomare Operationen im Treiber ergaenzen
52
55
  - fuer ObjectId-Konvertierungen Model.objectId(...) oder die passende Model-Instanzmethode verwenden
53
56
  - vorhandene Webframez-Helper und Facades bevorzugen statt neue Utilities oder Resolver anzulegen
54
57
  - fuer HTTP-, Routing-, Middleware-, Storage- und Datatable-Code die Webframez-APIs und Konventionen beibehalten
@@ -790,3 +793,24 @@ export const handler = (event: any, context: any) => {
790
793
  });
791
794
  };
792
795
  ```
796
+
797
+
798
+ ## Inline text and icons
799
+
800
+ `InlineTextDefinition` is a JSON-safe text contract exported by Webframez Core and
801
+ structurally compatible with Native Design. Strings and existing locale maps stay
802
+ valid. Arrays preserve order and explicit whitespace:
803
+
804
+ ```ts
805
+ [{ type: "icon", name: "ubuntu" }, " Ubuntu 24.04"]
806
+ ```
807
+
808
+ Suite screen titles/subtitles, Card/CardHeader, Alert and Text accept this content
809
+ (`SuiteTextDefinition`). In React, use `<InlineText value={content} />` in other
810
+ text slots; `ScreenTitle` also accepts the array directly. On React Native, place
811
+ InlineText inside Text. Icons use the registered Native Design Icon component.
812
+ Built-in names include `linux`, `linux-pinguin` and `ubuntu` (native OS icons use
813
+ a penguin glyph fallback). Set `accessibilityLabel` only when the icon adds meaning
814
+ not already present in adjacent text. Optional `size` is a positive number.
815
+ Content is rendered as text and icons, never HTML. Plain navigation labels retain
816
+ the text representation; their separate `icon` property controls navigation icons.
@@ -43,9 +43,13 @@ Vermeide Importe aus internen Pfaden wie src/..., dist/... oder einzelnen Deep-I
43
43
  1. Fuer ObjectId-Konvertierungen immer Model.objectId(...) oder die Instanzmethode objectId(...) verwenden, wenn man sich im Kontext eines Models befindet.
44
44
  2. Keine redundanten resolveObjectId, parseObjectId, normalizeObjectId, toObjectId oder aehnlichen Hilfsmethoden erzeugen, wenn objectId(...) ausreicht.
45
45
  3. Wenn kein konkretes Model passt, ist DBConnection.objectId(...) die zweite Wahl. Ein projektspezifischer Resolver ist nicht die erste Wahl.
46
- 4. Fuer Standardabfragen bevorzugt die vorhandenen Model-Methoden verwenden: where(...), orderBy(...), first(), get(), paginate(), aggregate().
47
- 5. Beziehungen ueber hasOne(...), hasMany(...) und hasManyArray(...) abbilden statt manuelle Lade- oder Resolve-Methoden pro Beziehung zu schreiben.
48
- 6. Model-Klassen sollen die Webframez-Konventionen fuer __table, __primaryKey und Mapping respektieren.
46
+ 4. Fachliche Datenzugriffe muessen ueber konkrete Model-Klassen erfolgen: pro persistierter Entitaet eine Klasse unter app/Models mit __table und gegebenenfalls __primaryKey / __connection. Models von Anfang an anlegen und in Controllern, Services, Screens und Formular-Hooks verwenden. Keine DatabaseService.collection(...)-Wrapper und keine eigenen Collection-/Cursor-/JSON-Persistenzschichten im Projekt.
47
+ 5. Fuer Abfragen die Model-Methoden where(...), orderBy(...), first(), get(), paginate(), aggregate() verwenden. Fuer Einzelobjekte save(), update() und delete() verwenden. Vorbelegte IDs bei neuen Objekten beachten: Core Model.save() behandelt einen vorhandenen Primaerschluessel als Update; eine dokumentierte Insert-/create-Methode des Treibers nutzen, wenn die ID vor dem Insert feststeht.
48
+ 6. Falls eine notwendige Faehigkeit fehlt (z.B. atomare bedingte Updates, Upserts, Projektionen oder Bulk-Inserts), diese generisch im Datenbanktreiber bzw. Framework ergaenzen. Keine Read-Modify-Save-Schleifen fuer Token-Rotation, Zaehler oder einmalige Codes; Filter und Aenderung muessen atomar ausgefuehrt werden.
49
+ 7. Direkte DBConnection-/Treiberzugriffe sind auf Verbindungsverwaltung, Migrationen und Framework-Adapter begrenzt. Wenn ein bestehender Framework-Controller einen Datenbank-Handle verlangt, diesen nur an der Adaptergrenze weiterreichen. Geschaeftslogik bleibt bei Models.
50
+ 8. Model-Mapping, Projekt-/Tenantfilter, versteckte Felder, Datentypen und bestehende Daten beim Wechsel des Treibers testen. Raw-Aggregationen liefern Datenobjekte; normale Model-Abfragen liefern Model-Instanzen.
51
+ 9. Beziehungen ueber hasOne(...), hasMany(...) und hasManyArray(...) abbilden statt manuelle Lade- oder Resolve-Methoden pro Beziehung zu schreiben.
52
+ 10. Model-Klassen sollen die Webframez-Konventionen fuer __table, __primaryKey und Mapping respektieren.
49
53
 
50
54
  Bevorzugt:
51
55
 
@@ -234,3 +238,13 @@ Wenn Codex zwischen Eigenbau und Framework-Funktion waehlen muss, gilt standardm
234
238
  3. Erst zuletzt eine neue Abstraktion einfuehren.
235
239
 
236
240
  Wenn eine neue Abstraktion doch noetig ist, muss im Code klar erkennbar sein, warum die vorhandene Webframez-Funktion nicht ausreicht.
241
+
242
+ ## Suite navigation in generated projects
243
+
244
+ Use actual router Link components for navigation, with href and browser modifiers
245
+ preserved. Keep the Suite provider, renderer and AdminLayout mounted within a
246
+ project; never key them by pathname or hide them behind per-page context loading.
247
+ Key only changing page content, and reset the provider at project/account boundaries.
248
+ Configure Native Design's `suite.navigation.linkComponent` at the application
249
+ boundary. Keep server schemas serializable and authorization checks on every API.
250
+ Verify persistent layout state during navigation, not just HTTP status codes.
@@ -8,6 +8,8 @@ export type DataBuilderSchema = {
8
8
  afterSave?: any;
9
9
  beforeDelete?: any;
10
10
  afterDelete?: any;
11
+ /** Override persistence for nested/model-backed records; authorization and hooks still run. */
12
+ deleteHandler?: (element: any, req: Request) => Promise<void>;
11
13
  getAggregation?: any;
12
14
  events?: {
13
15
  [key: string]: any;
@@ -553,7 +553,10 @@ class DataBuilder {
553
553
  catch (e) {
554
554
  throw e;
555
555
  }
556
- yield db.collection(collection).deleteOne({ _id: element._id });
556
+ if (typeof type.schema.deleteHandler === "function")
557
+ yield type.schema.deleteHandler(element, req);
558
+ else
559
+ yield db.collection(collection).deleteOne({ _id: element._id });
557
560
  try {
558
561
  if (typeof type.schema.afterDelete === "function") {
559
562
  yield type.schema.afterDelete(element, req);
@@ -68,12 +68,14 @@ class DBConnectionFacade {
68
68
  }
69
69
  runQuery(query, options) {
70
70
  return __awaiter(this, void 0, void 0, function* () {
71
- const connection = yield this.getConnection();
71
+ const mapping = query.modelMapping;
72
+ const model = typeof mapping === "function" ? new mapping() : mapping;
73
+ const connection = yield this.getConnection(model === null || model === void 0 ? void 0 : model.__connection);
72
74
  const data = yield connection.driver.handleQueryBuilder(connection.client, query);
73
75
  if (options && options.raw) {
74
76
  return data;
75
77
  }
76
- if (!data) {
78
+ if (data === null || data === undefined) {
77
79
  return null;
78
80
  }
79
81
  // @ToDo: Model-Mapping
@@ -299,15 +299,16 @@ class Model {
299
299
  primaryKey: this.__primaryKey,
300
300
  filter: { [this.__primaryKey]: this[this.__primaryKey] },
301
301
  data: this.getModelData(),
302
- });
302
+ }, this.__connection);
303
303
  }
304
304
  else {
305
305
  // Insert
306
306
  status = yield DBConnection_1.DBConnection.execute({
307
307
  type: "insertOne",
308
308
  table: this.__table,
309
+ primaryKey: this.__primaryKey,
309
310
  data: this.getModelData(),
310
- });
311
+ }, this.__connection);
311
312
  }
312
313
  // onModelSave
313
314
  const driver = yield DBConnection_1.DBConnection.getDriver(this.__connection);
@@ -329,7 +330,7 @@ class Model {
329
330
  primaryKey: this.__primaryKey,
330
331
  filter: { [this.__primaryKey]: this[this.__primaryKey] },
331
332
  data: updateData,
332
- });
333
+ }, this.__connection);
333
334
  });
334
335
  }
335
336
  /**
@@ -1,4 +1,14 @@
1
1
  import { Request } from "../Router/Request";
2
+ export interface DatatableModelSelectorConfig {
3
+ value?: string;
4
+ label?: string;
5
+ subtitle?: string;
6
+ }
7
+ export interface NormalizedDatatableModelSelectorConfig {
8
+ value: string;
9
+ label: string;
10
+ subtitle?: string;
11
+ }
2
12
  export declare class Datatable {
3
13
  collection: string | Function;
4
14
  aggregation: {
@@ -26,6 +36,7 @@ export declare class Datatable {
26
36
  defaultUnwind?: string | null;
27
37
  disablePerPageConfig: boolean;
28
38
  selectable: boolean;
39
+ modelSelector: boolean | DatatableModelSelectorConfig;
29
40
  selectableFunctions: {
30
41
  [key: string]: {
31
42
  label: string;
@@ -38,6 +49,8 @@ export declare class Datatable {
38
49
  };
39
50
  } | null;
40
51
  getInit(req: Request): Promise<any>;
52
+ getModelSelectorConfig(): NormalizedDatatableModelSelectorConfig | null;
53
+ getModelSelectorPreview(req: Request): Promise<any>;
41
54
  getSelectableFunctionsDef(req: Request): Promise<{
42
55
  label: string;
43
56
  icon: string | undefined;
@@ -8,12 +8,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
12
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
24
  };
14
25
  Object.defineProperty(exports, "__esModule", { value: true });
15
26
  exports.Datatable = void 0;
16
27
  const moment_1 = __importDefault(require("moment"));
28
+ const mongodb_1 = require("mongodb");
17
29
  const DBConnection_1 = require("../Database/DBConnection");
18
30
  const NumericFunctions_1 = require("../Functions/NumericFunctions");
19
31
  class Datatable {
@@ -32,6 +44,7 @@ class Datatable {
32
44
  this.defaultUnwind = null;
33
45
  this.disablePerPageConfig = false;
34
46
  this.selectable = false;
47
+ this.modelSelector = false;
35
48
  this.selectableFunctions = null;
36
49
  }
37
50
  getInit(req) {
@@ -41,6 +54,10 @@ class Datatable {
41
54
  selectable: this.selectable ? true : false,
42
55
  selectableFunctions: yield this.getSelectableFunctionsDef(req),
43
56
  };
57
+ const modelSelector = this.getModelSelectorConfig();
58
+ if (modelSelector) {
59
+ out.modelSelector = modelSelector;
60
+ }
44
61
  const promises = [];
45
62
  promises.push(new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
46
63
  out.filter = yield this.getFilter(req);
@@ -78,6 +95,73 @@ class Datatable {
78
95
  return out;
79
96
  });
80
97
  }
98
+ getModelSelectorConfig() {
99
+ if (!this.modelSelector) {
100
+ return null;
101
+ }
102
+ const config = this.modelSelector === true ? {} : this.modelSelector;
103
+ const subtitle = typeof config.subtitle === "string" && config.subtitle.trim() !== "" ? config.subtitle : undefined;
104
+ return Object.assign({ value: typeof config.value === "string" && config.value.trim() !== "" ? config.value.trim() : "_id", label: typeof config.label === "string" && config.label.trim() !== "" ? config.label.trim() : "_id" }, (subtitle ? { subtitle: subtitle.trim() } : {}));
105
+ }
106
+ getModelSelectorPreview(req) {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ const config = this.getModelSelectorConfig();
109
+ if (!config) {
110
+ throw new Error("Datatable is not enabled for model selection.");
111
+ }
112
+ const rawValue = req.body.value;
113
+ if (rawValue === undefined || rawValue === null || rawValue === "") {
114
+ return null;
115
+ }
116
+ let value = rawValue;
117
+ if (config.value === "_id") {
118
+ if (rawValue instanceof mongodb_1.ObjectId) {
119
+ value = rawValue;
120
+ }
121
+ else if (typeof rawValue === "string" && mongodb_1.ObjectId.isValid(rawValue)) {
122
+ value = new mongodb_1.ObjectId(rawValue);
123
+ }
124
+ else {
125
+ return null;
126
+ }
127
+ }
128
+ const normalizeStages = (stages) => (stages !== null && stages !== void 0 ? stages : []).map((stage) => {
129
+ if (!stage || typeof stage !== "object" || !Object.prototype.hasOwnProperty.call(stage, "skipStats")) {
130
+ return stage;
131
+ }
132
+ const { skipStats: _skipStats } = stage, normalized = __rest(stage, ["skipStats"]);
133
+ return normalized;
134
+ });
135
+ const aggregation = [
136
+ ...normalizeStages(yield this.getAggregation(req)),
137
+ ...normalizeStages(yield this.getSubAggregation(req)),
138
+ { $match: { [config.value]: value } },
139
+ { $limit: 1 },
140
+ ];
141
+ const connection = yield DBConnection_1.DBConnection.getConnection();
142
+ const results = yield connection.client
143
+ .db(null)
144
+ .collection(yield this.getCollection(req))
145
+ .aggregate(aggregation, {
146
+ collation: {
147
+ locale: "de",
148
+ strength: 2,
149
+ },
150
+ })
151
+ .toArray();
152
+ if (!results[0]) {
153
+ return null;
154
+ }
155
+ let result = results[0];
156
+ if (this.onRow) {
157
+ result = yield this.onRow(result);
158
+ }
159
+ if (this.onAttributes) {
160
+ result.__cellattr = yield this.onAttributes(result);
161
+ }
162
+ return result;
163
+ });
164
+ }
81
165
  getSelectableFunctionsDef(req) {
82
166
  return __awaiter(this, void 0, void 0, function* () {
83
167
  if (!this.selectableFunctions) {
@@ -43,6 +43,18 @@ class DatatableController extends Controller_1.Controller {
43
43
  });
44
44
  }
45
45
  const table = new tableClass();
46
+ if (req.body.mode === "model-selector-preview") {
47
+ if (!table.getModelSelectorConfig()) {
48
+ return res.status(403).send({
49
+ status: "error",
50
+ message: "Datatable is not enabled for model selection.",
51
+ });
52
+ }
53
+ return res.send({
54
+ status: "success",
55
+ data: yield table.getModelSelectorPreview(req),
56
+ });
57
+ }
46
58
  if (req.body.type === "stats" || req.body.mode === "stats") {
47
59
  return res.send({
48
60
  status: "success",
@@ -0,0 +1,9 @@
1
+ /** JSON-safe inline content, structurally shared with Native Design/Webframez Core. */
2
+ export type InlineTextIcon = {
3
+ type: "icon";
4
+ name: string;
5
+ /** Omit for decorative icons next to equivalent text. */
6
+ accessibilityLabel?: string;
7
+ size?: number;
8
+ };
9
+ export type InlineTextDefinition = string | Record<string, string> | readonly (string | Record<string, string> | InlineTextIcon)[];
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -8,3 +8,5 @@ export type DueAutomationExecution = {
8
8
  };
9
9
  export declare function getDueAutomationExecutions(executions: any[], since: any, until: any, timezone?: string, identifier?: string | null): DueAutomationExecution[];
10
10
  export declare function matchesAutomationExecutions(executions: any[] | undefined, date: Date | string | undefined, timezone?: string): boolean;
11
+ /** Next wall-clock automation occurrence, preserving the scheduler's timezone/DST semantics. */
12
+ export declare function nextAutomationExecution(executions: any[], after: Date | string, timezone?: string): Date | null;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.matchesAutomationExecutions = exports.getDueAutomationExecutions = void 0;
6
+ exports.nextAutomationExecution = exports.matchesAutomationExecutions = exports.getDueAutomationExecutions = void 0;
7
7
  const moment_timezone_1 = __importDefault(require("moment-timezone"));
8
8
  const DateFunctions_1 = require("../Functions/DateFunctions");
9
9
  function pushIfDue(out, entry, since, until) {
@@ -124,3 +124,19 @@ function matchesAutomationExecutions(executions, date, timezone = "Europe/Berlin
124
124
  return getDueAutomationExecutions(executions, since, until, timezone).some((execution) => execution.dueAt.isSame(dueAt));
125
125
  }
126
126
  exports.matchesAutomationExecutions = matchesAutomationExecutions;
127
+ /** Next wall-clock automation occurrence, preserving the scheduler's timezone/DST semantics. */
128
+ function nextAutomationExecution(executions, after, timezone = "Europe/Berlin") {
129
+ const since = (0, moment_timezone_1.default)(after).tz(timezone);
130
+ if (!since.isValid() || !moment_timezone_1.default.tz.zone(timezone))
131
+ throw new Error("Invalid automation time or timezone");
132
+ // Short windows avoid materializing months of minute/hourly occurrences just to find one.
133
+ const windows = [[1, "minutes"], [1, "hours"], [1, "days"], [8, "days"], [2, "months"]];
134
+ for (const [amount, unit] of windows) {
135
+ const due = getDueAutomationExecutions(executions, since, since.clone().add(amount, unit), timezone);
136
+ const first = due.reduce((best, entry) => !best || entry.dueAt.isBefore(best.dueAt) ? entry : best, null);
137
+ if (first)
138
+ return first.dueAt.toDate();
139
+ }
140
+ return null;
141
+ }
142
+ exports.nextAutomationExecution = nextAutomationExecution;
@@ -0,0 +1,13 @@
1
+ /** Embedded worker for applications whose database must stay in one process (e.g. DuckDB).
2
+ * Uses the same queue_jobs records and BaseQueueJob handlers as console workers. */
3
+ export declare class Queue {
4
+ private static timer;
5
+ private static running;
6
+ private static abort;
7
+ private static worker;
8
+ private static collection;
9
+ static enqueue(jobclass: string, props?: Record<string, any>): Promise<any>;
10
+ static start(jobclasses: string[], intervalMs?: number): void;
11
+ static stop(): Promise<void>;
12
+ static runOnce(jobclasses: string[]): Promise<void>;
13
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.Queue = void 0;
16
+ const crypto_1 = __importDefault(require("crypto"));
17
+ const DBConnection_1 = require("../Database/DBConnection");
18
+ const QueueJobsRegisty_1 = require("./QueueJobsRegisty");
19
+ /** Embedded worker for applications whose database must stay in one process (e.g. DuckDB).
20
+ * Uses the same queue_jobs records and BaseQueueJob handlers as console workers. */
21
+ class Queue {
22
+ static collection() {
23
+ return __awaiter(this, void 0, void 0, function* () { return (yield DBConnection_1.DBConnection.getConnection()).client.db(null).collection("queue_jobs"); });
24
+ }
25
+ static enqueue(jobclass, props = {}) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ QueueJobsRegisty_1.QueueJobsRegisty.getJobOrFail(jobclass);
28
+ const jobs = yield this.collection(), id = props._id || crypto_1.default.randomUUID();
29
+ yield jobs.updateOne({ _id: id }, { $setOnInsert: Object.assign(Object.assign({}, props), { _id: id, jobclass, status: "pending", created_at: new Date(), not_before: null, worker: null }) }, { upsert: true });
30
+ return jobs.findOne({ _id: id });
31
+ });
32
+ }
33
+ static start(jobclasses, intervalMs = 1000) {
34
+ if (this.timer)
35
+ return;
36
+ const tick = () => { if (!this.running)
37
+ this.running = this.runOnce(jobclasses).catch(() => { }).finally(() => { this.running = undefined; }); };
38
+ this.timer = setInterval(tick, intervalMs);
39
+ this.timer.unref();
40
+ tick();
41
+ }
42
+ static stop() {
43
+ var _a;
44
+ return __awaiter(this, void 0, void 0, function* () { if (this.timer)
45
+ clearInterval(this.timer); this.timer = undefined; (_a = this.abort) === null || _a === void 0 ? void 0 : _a.abort(); yield this.running; });
46
+ }
47
+ static runOnce(jobclasses) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ const jobs = yield this.collection(), now = new Date();
50
+ yield jobs.updateMany({ status: "running", embedded_queue: true, lease_until: { $lt: now } }, { $set: { status: "pending", worker: null, recovered: true } });
51
+ const token = crypto_1.default.randomUUID();
52
+ const claimed = yield jobs.findOneAndUpdate({ status: "pending", jobclass: { $in: jobclasses }, $or: [{ not_before: null }, { not_before: { $lte: now } }] }, { $set: { status: "running", worker: this.worker, embedded_queue: true, lease_token: token, lease_until: new Date(Date.now() + 60000), started_at: now } }, { sort: { priority: -1, created_at: 1 }, returnDocument: "after" });
53
+ const job = (claimed === null || claimed === void 0 ? void 0 : claimed._id) ? claimed : claimed === null || claimed === void 0 ? void 0 : claimed.value;
54
+ if (!job)
55
+ return;
56
+ const filter = { _id: job._id, lease_token: token, status: "running" };
57
+ const controller = new AbortController();
58
+ this.abort = controller;
59
+ const heartbeat = setInterval(() => { void jobs.updateOne(filter, { $set: { lease_until: new Date(Date.now() + 60000) } }).then((result) => { if (!result.matchedCount)
60
+ controller.abort(); }).catch(() => controller.abort()); }, 15000);
61
+ heartbeat.unref();
62
+ try {
63
+ const Handler = QueueJobsRegisty_1.QueueJobsRegisty.getJobOrFail(job.jobclass), handler = new Handler();
64
+ const result = yield handler.handle(Object.assign(Object.assign({}, job), { signal: controller.signal }));
65
+ yield jobs.updateOne(filter, { $set: (result === null || result === void 0 ? void 0 : result.__execute_again) ? { status: "pending", not_before: result.__execute_again, worker: null } : { status: "finished", ended_at: new Date(), worker: null } });
66
+ }
67
+ catch (error) {
68
+ // Handler owns log redaction. Do not persist raw exception output or credentials here.
69
+ yield jobs.updateOne(filter, { $set: { status: "failed", ended_at: new Date(), worker: null, error: "Queue job failed. See the domain status for details." } });
70
+ }
71
+ finally {
72
+ clearInterval(heartbeat);
73
+ if (this.abort === controller)
74
+ this.abort = undefined;
75
+ }
76
+ });
77
+ }
78
+ }
79
+ exports.Queue = Queue;
80
+ Queue.worker = "embedded-" + crypto_1.default.randomUUID();
@@ -67,8 +67,8 @@ export declare class Response {
67
67
  * @param mimeType
68
68
  * @returns void
69
69
  */
70
- stream(req: Request, filepath: string, filename: string, mimeType: string): Promise<unknown>;
71
- download(filepath: string, options?: any): Promise<unknown>;
70
+ stream(req: Request, filepath: string, filename: string, mimeType: string): Promise<Response | undefined>;
71
+ download(filepath: string, options?: any): Promise<void>;
72
72
  end(): this;
73
73
  registerEvent(eventKey: string, func: any): Promise<void>;
74
74
  /**
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.Response = void 0;
16
16
  const fs_1 = __importDefault(require("fs"));
17
+ const promises_1 = require("stream/promises");
17
18
  const path_1 = __importDefault(require("path"));
18
19
  const mime_types_1 = __importDefault(require("mime-types"));
19
20
  class Response {
@@ -129,83 +130,54 @@ class Response {
129
130
  * @returns void
130
131
  */
131
132
  stream(req, filepath, filename, mimeType) {
132
- var _a;
133
+ var _a, _b;
133
134
  return __awaiter(this, void 0, void 0, function* () {
134
- const stats = fs_1.default.statSync(filepath);
135
- const range = ((_a = req.req) === null || _a === void 0 ? void 0 : _a.headers.range) || "";
135
+ if (!this.res)
136
+ throw new Error("Native Response-Objekt nicht verfügbar");
137
+ const stats = yield fs_1.default.promises.stat(filepath);
136
138
  const total = stats.size;
137
- let start = undefined;
138
- let end = undefined;
139
+ const range = String(((_a = req.headers) === null || _a === void 0 ? void 0 : _a.range) || ((_b = req.message) === null || _b === void 0 ? void 0 : _b.headers.range) || "");
140
+ let start = 0, end = total - 1;
139
141
  if (range) {
140
- var parts = range.replace(/bytes=/, "").split("-");
141
- var partialstart = parts[0];
142
- var partialend = parts[1];
143
- start = parseInt(partialstart, 10);
144
- end = partialend ? parseInt(partialend, 10) : total - 1;
145
- var chunksize = end - start + 1;
146
- this.status(206);
147
- this.header("Content-Range", "bytes " + start + "-" + end + "/" + total);
148
- this.header("Accept-Ranges", "bytes");
149
- this.header("Content-Length", chunksize.toString());
150
- this.header("Content-Type", mimeType);
151
- this.header("Content-Disposition", `attachment; filename="${filename}"`);
152
- }
153
- else {
154
- this.status(200);
155
- this.header("Accept-Ranges", "bytes");
156
- this.header("Content-Length", stats.size.toString());
157
- this.header("Content-Type", mimeType);
158
- this.header("Content-Disposition", `attachment; filename="${filename}"`);
159
- }
160
- // @ts-ignore
161
- var readStream = fs_1.default.createReadStream(filepath, { start: start, end: end });
162
- readStream.on("error", (streamError) => {
163
- console.error("WebframezStreamError:", streamError);
164
- });
165
- readStream.on("end", () => { });
166
- // Prüfe ob res.res verfügbar ist
167
- if (!this.res) {
168
- throw new Error("Native Response-Objekt nicht verfügbar");
142
+ const match = /^bytes=(\d*)-(\d*)$/.exec(range);
143
+ if (!match || (!match[1] && !match[2])) {
144
+ return this.status(416).header("Content-Range", `bytes */${total}`).end();
145
+ }
146
+ if (!match[1]) {
147
+ const suffix = Number(match[2]);
148
+ start = Math.max(0, total - suffix);
149
+ if (suffix === 0)
150
+ start = total;
151
+ }
152
+ else {
153
+ start = Number(match[1]);
154
+ end = match[2] ? Math.min(Number(match[2]), total - 1) : total - 1;
155
+ }
156
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start >= total || end < start) {
157
+ return this.status(416).header("Content-Range", `bytes */${total}`).end();
158
+ }
159
+ this.status(206).header("Content-Range", `bytes ${start}-${end}/${total}`);
169
160
  }
170
- readStream.pipe(this.res);
171
- return new Promise((resolve) => {
172
- readStream.on("end", resolve);
173
- readStream.on("error", resolve);
174
- });
161
+ this.header("Accept-Ranges", "bytes");
162
+ this.header("Content-Length", String(range ? end - start + 1 : total));
163
+ this.header("Content-Type", mimeType);
164
+ this.header("Content-Disposition", `attachment; filename="${filename.replace(/["\r\n\\]/g, "_")}"`);
165
+ if (total === 0)
166
+ return this.end();
167
+ yield (0, promises_1.pipeline)(fs_1.default.createReadStream(filepath, { start, end }), this.res);
175
168
  });
176
169
  }
177
170
  download(filepath, options) {
178
171
  return __awaiter(this, void 0, void 0, function* () {
179
- return new Promise((resolve, reject) => {
180
- if (!this.res) {
181
- return reject("Cannot download: Original response object is missing");
182
- }
183
- if (!fs_1.default.existsSync(filepath)) {
184
- return reject("Cannot download: File '" + filepath + "' does not exist");
185
- }
186
- let mimeType = options && options.contentType ? options.contentType : "application/octet-stream";
187
- if (options && options.inline) {
188
- let tempMime = mime_types_1.default.lookup(filepath);
189
- if (tempMime) {
190
- mimeType = tempMime;
191
- }
192
- }
193
- const stats = fs_1.default.lstatSync(filepath);
194
- this.header("Content-Disposition", (options && options.inline ? "inline" : "attachment") +
195
- "; filename=" +
196
- (options && options.filename ? options.filename : path_1.default.basename(filepath)));
197
- this.header("Content-Type", mimeType);
198
- this.header("Content-Length", stats.size.toString());
199
- const readStream = fs_1.default.createReadStream(filepath);
200
- readStream.pipe(this.res);
201
- readStream.on("close", () => {
202
- resolve();
203
- });
204
- readStream.on("error", (streamErr) => {
205
- console.error(streamErr);
206
- reject(streamErr);
207
- });
208
- });
172
+ if (!this.res)
173
+ throw new Error("Cannot download: Original response object is missing");
174
+ const stats = yield fs_1.default.promises.stat(filepath);
175
+ const mimeType = (options === null || options === void 0 ? void 0 : options.contentType) || ((options === null || options === void 0 ? void 0 : options.inline) && mime_types_1.default.lookup(filepath)) || "application/octet-stream";
176
+ const filename = String((options === null || options === void 0 ? void 0 : options.filename) || path_1.default.basename(filepath)).replace(/["\r\n\\]/g, "_");
177
+ this.header("Content-Disposition", `${(options === null || options === void 0 ? void 0 : options.inline) ? "inline" : "attachment"}; filename="${filename}"`);
178
+ this.header("Content-Type", mimeType);
179
+ this.header("Content-Length", String(stats.size));
180
+ yield (0, promises_1.pipeline)(fs_1.default.createReadStream(filepath), this.res);
209
181
  });
210
182
  }
211
183
  end() {
package/dist/index.d.ts CHANGED
@@ -61,3 +61,5 @@ export * from "./Notifications/NotificationService";
61
61
  export * from "./Notifications/NotificationsOutputChannel";
62
62
  export * from "./Notifications/NotificationsOutputChannelsJob";
63
63
  export * from "./info";
64
+ export type { InlineTextDefinition, InlineTextIcon } from "./Presentation/InlineText";
65
+ export * from "./Queue/Queue";
package/dist/index.js CHANGED
@@ -77,3 +77,4 @@ __exportStar(require("./Notifications/NotificationService"), exports);
77
77
  __exportStar(require("./Notifications/NotificationsOutputChannel"), exports);
78
78
  __exportStar(require("./Notifications/NotificationsOutputChannelsJob"), exports);
79
79
  __exportStar(require("./info"), exports);
80
+ __exportStar(require("./Queue/Queue"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.64",
3
+ "version": "0.3.66",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",