@webtypen/webframez-core 0.3.103 → 0.3.105
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 +3 -2
- package/dist/Database/Model.js +1 -1
- package/dist/Datatable/Datatable.d.ts +3 -0
- package/dist/Datatable/Datatable.js +23 -17
- package/dist/Queue/Queue.js +4 -3
- package/dist/Queue/QueueExecutionContext.d.ts +7 -0
- package/dist/Queue/QueueExecutionContext.js +17 -0
- package/dist/Queue/QueueJob.d.ts +1 -0
- package/dist/Queue/QueueJob.js +21 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/datatables.md +12 -0
- package/docs/queue-workers.md +11 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.QueueWorkerCommand = void 0;
|
|
16
|
+
const QueueExecutionContext_1 = require("../Queue/QueueExecutionContext");
|
|
16
17
|
const fs_1 = __importDefault(require("fs"));
|
|
17
18
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
19
|
const path_1 = __importDefault(require("path"));
|
|
@@ -662,7 +663,7 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
662
663
|
if (!jobType) {
|
|
663
664
|
throw new Error(`QueueError: Invalid job-type ${job.jobclass}`);
|
|
664
665
|
}
|
|
665
|
-
const result = yield ErrorHandler_1.ErrorHandler.withContext({ scope: "job", operationId: jobOperationId, job: { id: String(job._id), number: job.number, jobclass: job.jobclass, worker: this.workerKey, instance: process.env.WEBFRAMEZ_QUEUE_INSTANCE, attempt: job.attempts_made } }, () => jobInstance.handle(job));
|
|
666
|
+
const result = yield ErrorHandler_1.ErrorHandler.withContext({ scope: "job", operationId: jobOperationId, job: { id: String(job._id), number: job.number, jobclass: job.jobclass, worker: this.workerKey, instance: process.env.WEBFRAMEZ_QUEUE_INSTANCE, attempt: job.attempts_made } }, () => QueueExecutionContext_1.QueueExecutionContext.run(job.execution_context, () => jobInstance.handle(job)));
|
|
666
667
|
const endedAt = new Date();
|
|
667
668
|
job.status = "finished";
|
|
668
669
|
job.ended_at = endedAt;
|
|
@@ -727,7 +728,7 @@ class QueueWorkerCommand extends ConsoleCommand_1.ConsoleCommand {
|
|
|
727
728
|
if (exhausted && (jobInstance === null || jobInstance === void 0 ? void 0 : jobInstance.onRetryExhausted) && (job.retry_exhaustion_attempts || 0) < retryPolicy.maxFinalizationAttempts) {
|
|
728
729
|
job.retry_exhaustion_attempts = (job.retry_exhaustion_attempts || 0) + 1;
|
|
729
730
|
try {
|
|
730
|
-
recovered = (yield jobInstance.onRetryExhausted(job, e)) === "finished";
|
|
731
|
+
recovered = (yield QueueExecutionContext_1.QueueExecutionContext.run(job.execution_context, () => jobInstance.onRetryExhausted(job, e))) === "finished";
|
|
731
732
|
job.retry_exhaustion_error = null;
|
|
732
733
|
}
|
|
733
734
|
catch (finalizationError) {
|
package/dist/Database/Model.js
CHANGED
|
@@ -267,7 +267,7 @@ class Model {
|
|
|
267
267
|
type: "aggregation",
|
|
268
268
|
table: collection ? collection : model ? model.__table : undefined,
|
|
269
269
|
aggregation: aggregation,
|
|
270
|
-
}, model.__connection);
|
|
270
|
+
}, model.__connection, options);
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
buildRelationship(model, foreignKey, localKey) {
|
|
@@ -37,6 +37,7 @@ export declare class Datatable {
|
|
|
37
37
|
autoApplyFilter: "begin" | "end" | null;
|
|
38
38
|
autoApplySearch: "begin" | "end" | null;
|
|
39
39
|
logAggregation: boolean;
|
|
40
|
+
collation: Record<string, any> | ((req: Request) => Record<string, any> | Promise<Record<string, any>>);
|
|
40
41
|
defaultUnwind?: string | null;
|
|
41
42
|
disablePerPageConfig: boolean;
|
|
42
43
|
selectable: boolean;
|
|
@@ -77,6 +78,8 @@ export declare class Datatable {
|
|
|
77
78
|
}[] | null>;
|
|
78
79
|
getAggregation(req: Request): Promise<any>;
|
|
79
80
|
getSubAggregation(req: Request): Promise<any>;
|
|
81
|
+
/** Filtered source for whole-result statistics; presentation and pagination are excluded. */
|
|
82
|
+
getStatisticsAggregation(req: Request): Promise<any[]>;
|
|
80
83
|
getCollection(req: Request): Promise<any>;
|
|
81
84
|
getData(req: Request, context?: DatatableColumnContext): Promise<{
|
|
82
85
|
page: number;
|
|
@@ -43,6 +43,7 @@ class Datatable {
|
|
|
43
43
|
this.autoApplyFilter = "begin";
|
|
44
44
|
this.autoApplySearch = null;
|
|
45
45
|
this.logAggregation = false;
|
|
46
|
+
this.collation = { locale: "de", strength: 2 };
|
|
46
47
|
this.defaultUnwind = null;
|
|
47
48
|
this.disablePerPageConfig = false;
|
|
48
49
|
this.selectable = false;
|
|
@@ -252,10 +253,7 @@ class Datatable {
|
|
|
252
253
|
const results = yield DBConnection_1.DBConnection.documentStore(connection)
|
|
253
254
|
.collection(yield this.getCollection(req))
|
|
254
255
|
.aggregate(aggregation, {
|
|
255
|
-
collation:
|
|
256
|
-
locale: "de",
|
|
257
|
-
strength: 2,
|
|
258
|
-
},
|
|
256
|
+
collation: typeof this.collation === "function" ? yield this.collation(req) : this.collation,
|
|
259
257
|
})
|
|
260
258
|
.toArray();
|
|
261
259
|
if (!results[0]) {
|
|
@@ -353,6 +351,17 @@ class Datatable {
|
|
|
353
351
|
return typeof this.subAggregation === "function" ? yield this.subAggregation(req, yield this.getColumnContext(req)) : this.subAggregation;
|
|
354
352
|
});
|
|
355
353
|
}
|
|
354
|
+
/** Filtered source for whole-result statistics; presentation and pagination are excluded. */
|
|
355
|
+
getStatisticsAggregation(req) {
|
|
356
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
357
|
+
return [...(yield this.getAggregation(req)) || [], ...(yield this.getSubAggregation(req)) || []]
|
|
358
|
+
.filter(stage => !stage.skipStats && !stage.$sort && stage.$skip === undefined && stage.$limit === undefined)
|
|
359
|
+
.map((_a) => {
|
|
360
|
+
var { skipStats } = _a, stage = __rest(_a, ["skipStats"]);
|
|
361
|
+
return stage;
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
}
|
|
356
365
|
getCollection(req) {
|
|
357
366
|
return __awaiter(this, void 0, void 0, function* () {
|
|
358
367
|
if (this.collection && typeof this.collection === "function") {
|
|
@@ -373,15 +382,15 @@ class Datatable {
|
|
|
373
382
|
let hasSkip = false;
|
|
374
383
|
let hasLimit = false;
|
|
375
384
|
if (aggregationDef && aggregationDef.length > 0) {
|
|
376
|
-
for (
|
|
385
|
+
for (const definition of aggregationDef) {
|
|
386
|
+
const { skipStats } = definition, entry = __rest(definition, ["skipStats"]);
|
|
377
387
|
if (entry.$skip !== undefined) {
|
|
378
388
|
hasSkip = true;
|
|
379
389
|
}
|
|
380
390
|
if (entry.$limit !== undefined) {
|
|
381
391
|
hasLimit = true;
|
|
382
392
|
}
|
|
383
|
-
if (
|
|
384
|
-
delete entry.skipStats;
|
|
393
|
+
if (skipStats) {
|
|
385
394
|
aggregation.push(entry);
|
|
386
395
|
}
|
|
387
396
|
else {
|
|
@@ -391,15 +400,15 @@ class Datatable {
|
|
|
391
400
|
}
|
|
392
401
|
}
|
|
393
402
|
if (subAggregationDef && subAggregationDef.length > 0) {
|
|
394
|
-
for (
|
|
403
|
+
for (const definition of subAggregationDef) {
|
|
404
|
+
const { skipStats } = definition, entry = __rest(definition, ["skipStats"]);
|
|
395
405
|
if (entry.$skip !== undefined) {
|
|
396
406
|
hasSkip = true;
|
|
397
407
|
}
|
|
398
408
|
if (entry.$limit !== undefined) {
|
|
399
409
|
hasLimit = true;
|
|
400
410
|
}
|
|
401
|
-
if (
|
|
402
|
-
delete entry.skipStats;
|
|
411
|
+
if (skipStats) {
|
|
403
412
|
subAggregation.push(entry);
|
|
404
413
|
}
|
|
405
414
|
else {
|
|
@@ -423,10 +432,7 @@ class Datatable {
|
|
|
423
432
|
const results = yield DBConnection_1.DBConnection.documentStore(connection)
|
|
424
433
|
.collection(yield this.getCollection(req))
|
|
425
434
|
.aggregate(aggr, {
|
|
426
|
-
collation:
|
|
427
|
-
locale: "de",
|
|
428
|
-
strength: 2,
|
|
429
|
-
},
|
|
435
|
+
collation: typeof this.collation === "function" ? yield this.collation(req) : this.collation,
|
|
430
436
|
})
|
|
431
437
|
.toArray();
|
|
432
438
|
const sumsAfter = [];
|
|
@@ -514,9 +520,9 @@ class Datatable {
|
|
|
514
520
|
const aggregation = [];
|
|
515
521
|
const aggregationDef = yield this.getAggregation(req);
|
|
516
522
|
if (aggregationDef && aggregationDef.length > 0) {
|
|
517
|
-
for (
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
for (const definition of aggregationDef) {
|
|
524
|
+
const { skipStats } = definition, entry = __rest(definition, ["skipStats"]);
|
|
525
|
+
if (skipStats) {
|
|
520
526
|
aggregation.push(entry);
|
|
521
527
|
}
|
|
522
528
|
else {
|
package/dist/Queue/Queue.js
CHANGED
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.Queue = void 0;
|
|
16
|
+
const QueueExecutionContext_1 = require("./QueueExecutionContext");
|
|
16
17
|
const ErrorHandler_1 = require("../ErrorHandling/ErrorHandler");
|
|
17
18
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
19
|
const DBConnection_1 = require("../Database/DBConnection");
|
|
@@ -29,7 +30,7 @@ class Queue {
|
|
|
29
30
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
31
|
QueueJobsRegisty_1.QueueJobsRegisty.getJobOrFail(jobclass);
|
|
31
32
|
const jobs = yield this.collection(), id = props._id || crypto_1.default.randomUUID();
|
|
32
|
-
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 });
|
|
33
|
+
yield jobs.updateOne({ _id: id }, { $setOnInsert: Object.assign(Object.assign({}, props), { execution_context: QueueExecutionContext_1.QueueExecutionContext.capture(), _id: id, jobclass, status: "pending", created_at: new Date(), not_before: null, worker: null }) }, { upsert: true });
|
|
33
34
|
return jobs.findOne({ _id: id });
|
|
34
35
|
});
|
|
35
36
|
}
|
|
@@ -126,7 +127,7 @@ class Queue {
|
|
|
126
127
|
if (job.retry_exhaustion_attempts)
|
|
127
128
|
throw new QueueRetryLimitError_1.QueueRetryLimitError("Retry exhaustion finalization pending.");
|
|
128
129
|
QueueRetryPolicy_1.QueueRetryPolicy.assertCanRun(policy, job, previousAttempts, now);
|
|
129
|
-
const result = yield ErrorHandler_1.ErrorHandler.withContext({ scope: "job", operationId: token, source: "queue.embedded", job: { id: String(job._id), number: job.number, jobclass: job.jobclass, worker: this.worker, attempt: job.attempts_made } }, () => handler.handle(Object.assign(Object.assign({}, job), { signal: controller.signal })));
|
|
130
|
+
const result = yield ErrorHandler_1.ErrorHandler.withContext({ scope: "job", operationId: token, source: "queue.embedded", job: { id: String(job._id), number: job.number, jobclass: job.jobclass, worker: this.worker, attempt: job.attempts_made } }, () => QueueExecutionContext_1.QueueExecutionContext.run(job.execution_context, () => handler.handle(Object.assign(Object.assign({}, job), { signal: controller.signal }))));
|
|
130
131
|
const endedAt = new Date();
|
|
131
132
|
const retryAt = (result === null || result === void 0 ? void 0 : result.__execute_again)
|
|
132
133
|
? QueueRetryPolicy_1.QueueRetryPolicy.rescheduleAt(policy, job, job.attempts_made, result.__execute_again, endedAt)
|
|
@@ -152,7 +153,7 @@ class Queue {
|
|
|
152
153
|
if (exhausted && (handler === null || handler === void 0 ? void 0 : handler.onRetryExhausted) && (job.retry_exhaustion_attempts || 0) < policy.maxFinalizationAttempts) {
|
|
153
154
|
job.retry_exhaustion_attempts = (job.retry_exhaustion_attempts || 0) + 1;
|
|
154
155
|
try {
|
|
155
|
-
recovered = (yield handler.onRetryExhausted(job, error)) === "finished";
|
|
156
|
+
recovered = (yield QueueExecutionContext_1.QueueExecutionContext.run(job.execution_context, () => handler.onRetryExhausted(job, error))) === "finished";
|
|
156
157
|
}
|
|
157
158
|
catch (finalizationError) {
|
|
158
159
|
yield ErrorHandler_1.ErrorHandler.report(finalizationError, { scope: "job", source: "queue.embedded.finalization", job: { id: String(job._id), jobclass: job.jobclass, worker: this.worker } });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Server-owned attribution that follows queued work. Never populate from request payloads.
|
|
2
|
+
* Consumers must reauthorize persisted identities before performing protected effects. */
|
|
3
|
+
export declare class QueueExecutionContext {
|
|
4
|
+
private static storage;
|
|
5
|
+
static capture(): Record<string, unknown> | undefined;
|
|
6
|
+
static run<T>(context: Record<string, unknown> | undefined, callback: () => T): T;
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueueExecutionContext = void 0;
|
|
4
|
+
const async_hooks_1 = require("async_hooks");
|
|
5
|
+
/** Server-owned attribution that follows queued work. Never populate from request payloads.
|
|
6
|
+
* Consumers must reauthorize persisted identities before performing protected effects. */
|
|
7
|
+
class QueueExecutionContext {
|
|
8
|
+
static capture() {
|
|
9
|
+
const value = this.storage.getStore();
|
|
10
|
+
return value ? JSON.parse(JSON.stringify(value)) : undefined;
|
|
11
|
+
}
|
|
12
|
+
static run(context, callback) {
|
|
13
|
+
return this.storage.run(context ? JSON.parse(JSON.stringify(context)) : {}, callback);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.QueueExecutionContext = QueueExecutionContext;
|
|
17
|
+
QueueExecutionContext.storage = new async_hooks_1.AsyncLocalStorage();
|
package/dist/Queue/QueueJob.d.ts
CHANGED
package/dist/Queue/QueueJob.js
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
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
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.QueueJob = void 0;
|
|
13
|
+
const QueueExecutionContext_1 = require("./QueueExecutionContext");
|
|
4
14
|
const Model_1 = require("../Database/Model");
|
|
5
15
|
class QueueJob extends Model_1.Model {
|
|
6
16
|
constructor() {
|
|
7
17
|
super(...arguments);
|
|
8
18
|
this.__table = "queue_jobs";
|
|
9
19
|
}
|
|
20
|
+
save() {
|
|
21
|
+
const _super = Object.create(null, {
|
|
22
|
+
save: { get: () => super.save }
|
|
23
|
+
});
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const context = QueueExecutionContext_1.QueueExecutionContext.capture();
|
|
26
|
+
if (context)
|
|
27
|
+
this.execution_context = context;
|
|
28
|
+
return _super.save.call(this);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
10
31
|
}
|
|
11
32
|
exports.QueueJob = QueueJob;
|
package/dist/index.d.ts
CHANGED
|
@@ -80,3 +80,4 @@ export { Field, Validates, Forms, DataBuilderFieldOptions } from "./DataBuilder/
|
|
|
80
80
|
export { DataBuilderRouteOptions } from "./DataBuilder/DataBuilderRoute";
|
|
81
81
|
export { LegacyFieldTypeAdapter } from "./DataBuilder/LegacyFieldTypeAdapter";
|
|
82
82
|
export { defaultFormFields } from "./DataBuilder/formScope";
|
|
83
|
+
export * from "./Queue/QueueExecutionContext";
|
package/dist/index.js
CHANGED
|
@@ -101,3 +101,4 @@ var LegacyFieldTypeAdapter_1 = require("./DataBuilder/LegacyFieldTypeAdapter");
|
|
|
101
101
|
Object.defineProperty(exports, "LegacyFieldTypeAdapter", { enumerable: true, get: function () { return LegacyFieldTypeAdapter_1.LegacyFieldTypeAdapter; } });
|
|
102
102
|
var formScope_1 = require("./DataBuilder/formScope");
|
|
103
103
|
Object.defineProperty(exports, "defaultFormFields", { enumerable: true, get: function () { return formScope_1.defaultFormFields; } });
|
|
104
|
+
__exportStar(require("./Queue/QueueExecutionContext"), exports);
|
package/docs/datatables.md
CHANGED
|
@@ -126,3 +126,15 @@ creates an optional column catalog from nested DataBuilder/DataSchema fields.
|
|
|
126
126
|
The `prepare(column, field, path)` callback can attach inline or lazy display
|
|
127
127
|
options, change the mapping, or return null to omit a field. Plugin installation,
|
|
128
128
|
tenant checks and relation hydration belong in the host adapter.
|
|
129
|
+
|
|
130
|
+
## Statistics and collation
|
|
131
|
+
|
|
132
|
+
`getStatisticsAggregation(req)` returns the filtered aggregation and sub-aggregation,
|
|
133
|
+
excluding stages marked `skipStats: true`, sorting and pagination. It does not mutate
|
|
134
|
+
stage definitions. Keep tenant and filter dependencies unmarked; calculate statistic-only
|
|
135
|
+
fields in the statistics service instead of depending on presentation projections.
|
|
136
|
+
|
|
137
|
+
`collation` defaults to `{ locale: "de", strength: 2 }` for compatibility. It can be
|
|
138
|
+
an object or an async request callback. Tables sorting numeric/date fields can return
|
|
139
|
+
`{ locale: "simple" }` to use matching binary indexes. Keep language collation for
|
|
140
|
+
text sorting or filters that depend on it; collation applies to the entire data query.
|
package/docs/queue-workers.md
CHANGED
|
@@ -135,3 +135,14 @@ Dedicated workers with an explicit `jobclasses` allowlist may set
|
|
|
135
135
|
`includeBackupJobs: false` to prevent the automatic addition of `BackupRunJob`.
|
|
136
136
|
This keeps long backups out of a latency-sensitive worker. Existing workers keep
|
|
137
137
|
the previous default; explicitly listed backup jobs remain allowed.
|
|
138
|
+
|
|
139
|
+
### Server-owned execution context
|
|
140
|
+
|
|
141
|
+
`QueueExecutionContext.run(context, callback)` carries JSON-serializable server-owned
|
|
142
|
+
attribution through asynchronous calls. Jobs saved via `QueueJob.save()` or
|
|
143
|
+
`Queue.enqueue()` capture it in `execution_context`; console and embedded workers
|
|
144
|
+
restore it for handlers and retry-exhaustion callbacks. Child jobs inherit it.
|
|
145
|
+
`QueueExecutionContext.capture()` returns a detached snapshot. Do not populate this
|
|
146
|
+
field from client payloads or treat it as authorization: reload the referenced
|
|
147
|
+
account and current permissions before protected effects. Raw database inserts
|
|
148
|
+
must explicitly capture the context when used instead of the queue APIs.
|
package/package.json
CHANGED