@webtypen/webframez-core 0.3.103 → 0.3.104

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.
@@ -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) {
@@ -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();
@@ -6,4 +6,5 @@ export declare class QueueJob extends Model {
6
6
  _notification?: DatabaseId;
7
7
  notification?: Notification;
8
8
  __table: string;
9
+ save(): Promise<any>;
9
10
  }
@@ -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);
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.103",
3
+ "version": "0.3.104",
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",