@webtypen/webframez-core 0.3.85 → 0.3.93
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 +59 -0
- package/build-cache.d.ts +1 -0
- package/build-cache.js +30 -0
- package/dist/Ai/AiTypes.d.ts +4 -1
- package/dist/Api/ApiScopeRegistry.js +4 -2
- package/dist/BuildApplication.d.ts +2 -0
- package/dist/BuildApplication.js +47 -7
- package/dist/Commands/ConsoleCommand.d.ts +1 -0
- package/dist/Commands/ConsoleCommand.js +6 -0
- package/dist/Commands/QueueWorkerCommand.js +199 -148
- package/dist/ConsoleApplication.d.ts +1 -0
- package/dist/ConsoleApplication.js +12 -0
- package/dist/Database/Model.d.ts +7 -0
- package/dist/Database/Model.js +20 -0
- package/dist/ErrorHandling/ErrorHandler.d.ts +38 -2
- package/dist/ErrorHandling/ErrorHandler.js +52 -2
- package/dist/LambdaApplication.d.ts +2 -7
- package/dist/LambdaApplication.js +23 -2
- package/dist/Queue/BaseQueueJob.d.ts +6 -0
- package/dist/Queue/BaseQueueJob.js +10 -0
- package/dist/Queue/Queue.d.ts +12 -2
- package/dist/Queue/Queue.js +133 -24
- package/dist/Queue/QueueRetryLimitError.d.ts +4 -0
- package/dist/Queue/QueueRetryLimitError.js +11 -0
- package/dist/Queue/QueueRetryPolicy.d.ts +35 -0
- package/dist/Queue/QueueRetryPolicy.js +105 -0
- package/dist/Router/Response.js +8 -3
- package/dist/Router/Router.d.ts +5 -8
- package/dist/Router/Router.js +26 -14
- package/dist/WebApplication.d.ts +1 -0
- package/dist/WebApplication.js +14 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/docs/errors.md +34 -0
- package/docs/queue-workers.md +123 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ This README reflects the current API in this repository and focuses on:
|
|
|
9
9
|
- DataBuilder
|
|
10
10
|
- Console Commands
|
|
11
11
|
- Queue and Jobs
|
|
12
|
+
- Structured error reporting and execution context
|
|
12
13
|
- Session, browser and cross-instance authentication
|
|
13
14
|
|
|
14
15
|
## Requirements
|
|
@@ -680,6 +681,26 @@ app.boot({
|
|
|
680
681
|
- `write`, `writeln`, `info`, `warning`, `success`, `error`
|
|
681
682
|
- progress helpers (`progress`, `progressIncrement`, `progressFinish`)
|
|
682
683
|
|
|
684
|
+
## Error reporting and execution context (0.3.88+)
|
|
685
|
+
|
|
686
|
+
`ErrorHandler.onReport(listener)` observes reported errors independently of the
|
|
687
|
+
existing logging handlers and returns an unsubscribe function. Replacing handlers
|
|
688
|
+
with `setHandler` / `setHandlers` does not remove these subscriptions.
|
|
689
|
+
`ErrorHandler.withContext(context, callback)` propagates request, command and queue
|
|
690
|
+
context through asynchronous work; a distinct `operationId` separates executions
|
|
691
|
+
and prevents duplicate reports of the same Error within one execution.
|
|
692
|
+
|
|
693
|
+
Core reports errors at Router/middleware, ApiFunction, application boot, Console
|
|
694
|
+
and queue boundaries. Unexpected HTTP failures receive a generic 500 response;
|
|
695
|
+
failed console commands set a nonzero exit code. Error events include application
|
|
696
|
+
type, process identity and the available route/API/command/job/worker context.
|
|
697
|
+
|
|
698
|
+
Use `@webtypen/webframez-errors` for redaction, bounded durable buffering and
|
|
699
|
+
Server Manager delivery. Core itself does not implement Manager authentication or
|
|
700
|
+
transport. The SDK excludes expected errors, including HTTP 4xx, by default.
|
|
701
|
+
See [Error reporting and execution context](docs/errors.md) for the subscription
|
|
702
|
+
API, explicit reporting and the distinction between error events and job status.
|
|
703
|
+
|
|
683
704
|
## Queue and Jobs
|
|
684
705
|
|
|
685
706
|
Queue workers run through console commands and use `queue_jobs` storage.
|
|
@@ -745,6 +766,29 @@ export default {
|
|
|
745
766
|
};
|
|
746
767
|
```
|
|
747
768
|
|
|
769
|
+
### Retry limits and long-running polling
|
|
770
|
+
|
|
771
|
+
Console workers and the embedded `Queue` share finite limits. Defaults are
|
|
772
|
+
`maxAttempts: 100`, `maxErrorRetries: 3`, `maxAgeSeconds: 86400` and
|
|
773
|
+
`maxFinalizationAttempts: 3`; error retries require `error.retryable === true`.
|
|
774
|
+
**Every execution counts toward maxAttempts, including successful status polls
|
|
775
|
+
that return executeAgain().** Polling does not consume maxErrorRetries.
|
|
776
|
+
|
|
777
|
+
Configure `queue.retry`, `static retry`, `queue.jobs[JobClass].retry` or the job's
|
|
778
|
+
asynchronous `retryPolicy(job)` override. A policy can define delay stages and an
|
|
779
|
+
absolute `deadlineAt`; both the job age and that deadline are enforced. Stages set
|
|
780
|
+
minimum delays and also cap attempts at their summed attempt count.
|
|
781
|
+
|
|
782
|
+
Long-running provider workflows such as SEPA settlement need an application-owned
|
|
783
|
+
policy covering their settlement window. A generic 24-hour / 100-execution default
|
|
784
|
+
is not a payment settlement deadline. Existing histories count after upgrades, so
|
|
785
|
+
an older polling job can exhaust a newly introduced limit before its next handler
|
|
786
|
+
call. Updating a policy does not reactivate failed jobs or reset their histories.
|
|
787
|
+
|
|
788
|
+
See [Managed workers, retry policies and safe recovery](docs/queue-workers.md)
|
|
789
|
+
for precedence, stages, finalization hooks and recovery without repeating external
|
|
790
|
+
side effects. Core never automatically creates, cancels or retries a payment.
|
|
791
|
+
|
|
748
792
|
### Built-in Queue Commands
|
|
749
793
|
|
|
750
794
|
- `queue:start`
|
|
@@ -938,3 +982,18 @@ Lifecycle hooks skip event normalization when no listeners are registered. Use
|
|
|
938
982
|
Registered handlers retain their ordered, awaited execution and snapshot behavior.
|
|
939
983
|
The router applies this check before building telemetry payloads. Middleware keeps
|
|
940
984
|
its existing `next`/`reject` callback contract.
|
|
985
|
+
|
|
986
|
+
### Persistent build caches
|
|
987
|
+
|
|
988
|
+
Set `build.cacheDirectory` or `WEBFRAMEZ_BUILD_CACHE_DIRECTORY` to opt into
|
|
989
|
+
incremental TypeScript compilation. Compiler output and build information live
|
|
990
|
+
outside the published build. Resolved configuration, compiler version and source
|
|
991
|
+
file list invalidate the cache; failed compilation discards it and leaves the
|
|
992
|
+
previous successful release intact. Each directory requires an exclusive build
|
|
993
|
+
owner. Linux runtime copies use independent reflinks where supported.
|
|
994
|
+
|
|
995
|
+
Metro consumers can call
|
|
996
|
+
`require('@webtypen/webframez-core/build-cache').configureMetroCache(config)`.
|
|
997
|
+
It applies `WEBFRAMEZ_BUILD_MAX_WORKERS` and `WEBFRAMEZ_METRO_CACHE_DIRECTORY`,
|
|
998
|
+
preserving Expo's FileStore implementation and supporting old/new file-map option
|
|
999
|
+
names. Do not pass Expo `--clear` on normal cached builds.
|
package/build-cache.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function configureMetroCache<T extends {cacheStores?: any; maxWorkers?: number}>(config:T, env?:Record<string,string | undefined>):T;
|
package/build-cache.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
|
|
4
|
+
/** Preserve the platform's FileStore subclass (including Expo CSS handling). */
|
|
5
|
+
function configureMetroCache(config, env = process.env) {
|
|
6
|
+
if (env.WEBFRAMEZ_BUILD_MAX_WORKERS !== undefined) {
|
|
7
|
+
const workers = Number(env.WEBFRAMEZ_BUILD_MAX_WORKERS);
|
|
8
|
+
if (!Number.isInteger(workers) || workers < 1 || workers > 64) throw new Error("Invalid WEBFRAMEZ_BUILD_MAX_WORKERS");
|
|
9
|
+
config.maxWorkers = workers;
|
|
10
|
+
}
|
|
11
|
+
if (!env.WEBFRAMEZ_METRO_CACHE_DIRECTORY) return config;
|
|
12
|
+
const root = path.resolve(env.WEBFRAMEZ_METRO_CACHE_DIRECTORY);
|
|
13
|
+
const relocate = stores => stores.map((store, index) => {
|
|
14
|
+
// Leave remote and custom stores untouched; their constructors need other options.
|
|
15
|
+
if (!store || typeof store._root !== "string") return store;
|
|
16
|
+
return new store.constructor({root:path.join(root, `transform-${index}`)});
|
|
17
|
+
});
|
|
18
|
+
const stores = config.cacheStores;
|
|
19
|
+
config.cacheStores = typeof stores === "function" ? cache => relocate(stores(cache)) : relocate(stores || []);
|
|
20
|
+
// Some Metro releases accept these options at runtime but reject them in
|
|
21
|
+
// their schema. Only extend a field exposed by the caller/default config.
|
|
22
|
+
const field = "fileMapCacheDirectory" in config ? "fileMapCacheDirectory" : "hasteMapCacheDirectory" in config ? "hasteMapCacheDirectory" : null;
|
|
23
|
+
if (field) {
|
|
24
|
+
config[field] = path.join(root, "file-map");
|
|
25
|
+
fs.mkdirSync(config[field], {recursive:true});
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {configureMetroCache};
|
package/dist/Ai/AiTypes.d.ts
CHANGED
|
@@ -4,7 +4,10 @@ export type KernelAiApiRegistrations = {
|
|
|
4
4
|
middleware?: string[];
|
|
5
5
|
[key: string]: any;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
/** Optional packages augment this map without introducing a Core -> AI dependency. */
|
|
8
|
+
export interface KernelAiExtensions {
|
|
9
|
+
}
|
|
10
|
+
export type KernelAiRegistrations = KernelAiExtensions & {
|
|
8
11
|
agents?: KernelAiAgentRegistration[];
|
|
9
12
|
api?: KernelAiApiRegistrations;
|
|
10
13
|
};
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ApiScopeRegistry = void 0;
|
|
13
|
+
const ErrorHandler_1 = require("../ErrorHandling/ErrorHandler");
|
|
13
14
|
const Route_1 = require("../Router/Route");
|
|
14
15
|
const ApiFunctionUtils_1 = require("./ApiFunctionUtils");
|
|
15
16
|
function getModuleInstances(modulesLoader) {
|
|
@@ -92,14 +93,15 @@ class ApiScopeRegistryFacade {
|
|
|
92
93
|
const context = yield (0, ApiFunctionUtils_1.runApiScopeRegistrationMiddleware)(registration, req, res);
|
|
93
94
|
const paramsSource = (func.requestMethod || req.method).toUpperCase() === "GET" ? req.query : req.body;
|
|
94
95
|
const params = (0, ApiFunctionUtils_1.validateApiFunctionParams)(func.params || {}, paramsSource || {});
|
|
95
|
-
const result = yield func.handle({ context, params, request: req, response: res });
|
|
96
|
+
const result = yield ErrorHandler_1.ErrorHandler.withContext({ api: { scope: registration.scopeClass.name, function: func.key } }, () => func.handle({ context, params, request: req, response: res }));
|
|
96
97
|
return (0, ApiFunctionUtils_1.toApiFunctionHttpResult)(res, result);
|
|
97
98
|
}
|
|
98
99
|
catch (error) {
|
|
100
|
+
yield ErrorHandler_1.ErrorHandler.report(error, { scope: "controller", source: "api.function", api: { scope: registration.scopeClass.name, function: func.key } });
|
|
99
101
|
const statusCode = error && error.statusCode && !isNaN(parseInt(error.statusCode)) ? parseInt(error.statusCode) : 500;
|
|
100
102
|
return res.status(statusCode).send({
|
|
101
103
|
status: "error",
|
|
102
|
-
message: (
|
|
104
|
+
message: ErrorHandler_1.ErrorHandler.publicMessage(error, statusCode),
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
});
|
|
@@ -21,6 +21,7 @@ export type WebframezBuildPlugin = {
|
|
|
21
21
|
export type WebframezBuildConfig = {
|
|
22
22
|
dir?: string;
|
|
23
23
|
tsconfig?: string;
|
|
24
|
+
cacheDirectory?: string;
|
|
24
25
|
entry?: string;
|
|
25
26
|
runtimeFiles?: WebframezBuildRuntimeFile[];
|
|
26
27
|
prunePaths?: string[];
|
|
@@ -59,6 +60,7 @@ export declare class BuildApplication {
|
|
|
59
60
|
private createContext;
|
|
60
61
|
private callHook;
|
|
61
62
|
private runTypescriptBuild;
|
|
63
|
+
private copyTree;
|
|
62
64
|
private copyRuntimeFiles;
|
|
63
65
|
private validateCoreBuild;
|
|
64
66
|
private promoteBuild;
|
package/dist/BuildApplication.js
CHANGED
|
@@ -17,6 +17,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
17
17
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
18
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
19
|
const node_child_process_1 = require("node:child_process");
|
|
20
|
+
const node_util_1 = require("node:util");
|
|
21
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
22
|
+
const exec = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
20
23
|
function timestamp() {
|
|
21
24
|
return new Date().toISOString().replace(/[-:TZ.]/g, "");
|
|
22
25
|
}
|
|
@@ -117,9 +120,46 @@ class BuildApplication {
|
|
|
117
120
|
});
|
|
118
121
|
}
|
|
119
122
|
runTypescriptBuild(context) {
|
|
120
|
-
var _a;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
var _a, _b;
|
|
124
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
const tsconfig = node_path_1.default.resolve(this.projectRoot, ((_a = this.config.build) === null || _a === void 0 ? void 0 : _a.tsconfig) || "tsconfig.json");
|
|
126
|
+
const configuredCache = ((_b = this.config.build) === null || _b === void 0 ? void 0 : _b.cacheDirectory) || process.env.WEBFRAMEZ_BUILD_CACHE_DIRECTORY;
|
|
127
|
+
if (!configuredCache)
|
|
128
|
+
return this.runOrFail(context, "tsc", ["-p", tsconfig, "--outDir", this.stagingDir]);
|
|
129
|
+
const root = node_path_1.default.resolve(this.projectRoot, configuredCache);
|
|
130
|
+
const scope = node_crypto_1.default.createHash("sha256").update(node_path_1.default.relative(root, tsconfig)).digest("hex").slice(0, 24);
|
|
131
|
+
const cache = node_path_1.default.join(root, "typescript", scope);
|
|
132
|
+
// Resolved options and the complete source list invalidate deleted/renamed outputs.
|
|
133
|
+
const { stdout: config } = yield exec(this.resolveBinary("tsc"), ["-p", tsconfig, "--outDir", node_path_1.default.join(cache, "output"), "--showConfig"], { cwd: this.projectRoot, maxBuffer: 8 * 1024 * 1024 });
|
|
134
|
+
const { stdout: version } = yield exec(this.resolveBinary("tsc"), ["--version"], { cwd: this.projectRoot });
|
|
135
|
+
const key = node_crypto_1.default.createHash("sha256").update(config).update(version).digest("hex");
|
|
136
|
+
const previous = yield promises_1.default.readFile(node_path_1.default.join(cache, "key"), "utf8").catch(() => "");
|
|
137
|
+
if (previous !== key)
|
|
138
|
+
yield promises_1.default.rm(cache, { recursive: true, force: true });
|
|
139
|
+
const output = node_path_1.default.join(cache, "output");
|
|
140
|
+
yield promises_1.default.mkdir(output, { recursive: true });
|
|
141
|
+
try {
|
|
142
|
+
yield this.runOrFail(context, "tsc", ["-p", tsconfig, "--outDir", output, "--incremental", "--tsBuildInfoFile", node_path_1.default.join(cache, "build.tsbuildinfo")]);
|
|
143
|
+
yield this.copyTree(output, this.stagingDir);
|
|
144
|
+
yield promises_1.default.writeFile(node_path_1.default.join(cache, "key"), key);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
yield promises_1.default.rm(cache, { recursive: true, force: true });
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
copyTree(source, target) {
|
|
153
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
154
|
+
if (process.platform === "linux") {
|
|
155
|
+
yield promises_1.default.mkdir(target, { recursive: true });
|
|
156
|
+
// Reflinks are independent copies; never share writable hardlinks with dependencies.
|
|
157
|
+
yield exec("cp", ["-a", "--reflink=auto", "--", source + "/.", target], { maxBuffer: 65536 });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
yield promises_1.default.cp(source, target, { recursive: true, dereference: false, mode: node_fs_1.default.constants.COPYFILE_FICLONE });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
123
163
|
}
|
|
124
164
|
copyRuntimeFiles() {
|
|
125
165
|
var _a, _b;
|
|
@@ -143,10 +183,10 @@ class BuildApplication {
|
|
|
143
183
|
}
|
|
144
184
|
yield promises_1.default.rm(targetPath, { recursive: true, force: true });
|
|
145
185
|
yield promises_1.default.mkdir(node_path_1.default.dirname(targetPath), { recursive: true });
|
|
146
|
-
yield promises_1.default.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
186
|
+
if ((yield promises_1.default.lstat(sourcePath)).isDirectory())
|
|
187
|
+
yield this.copyTree(sourcePath, targetPath);
|
|
188
|
+
else
|
|
189
|
+
yield promises_1.default.cp(sourcePath, targetPath, { dereference: false, mode: node_fs_1.default.constants.COPYFILE_FICLONE });
|
|
150
190
|
}
|
|
151
191
|
for (const prunePath of ((_b = this.config.build) === null || _b === void 0 ? void 0 : _b.prunePaths) || []) {
|
|
152
192
|
yield promises_1.default.rm(node_path_1.default.resolve(this.stagingDir, prunePath), { recursive: true, force: true });
|
|
@@ -34,6 +34,11 @@ class ConsoleCommand {
|
|
|
34
34
|
return __awaiter(this, void 0, void 0, function* () { });
|
|
35
35
|
}
|
|
36
36
|
handleSystem() {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
return ErrorHandler_1.ErrorHandler.withContext({ applicationType: "ConsoleApplication", scope: "command", operationId: WebframezHooks_1.WebframezHooks.createOperationId("command"), command: { signature: this.constructor.signature, className: this.constructor.name } }, () => this.handleSystemScoped());
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
handleSystemScoped() {
|
|
37
42
|
return __awaiter(this, void 0, void 0, function* () {
|
|
38
43
|
const commandSignature = String(this.constructor.signature || this.constructor.name || "command");
|
|
39
44
|
const operationId = WebframezHooks_1.WebframezHooks.createOperationId("command");
|
|
@@ -62,6 +67,7 @@ class ConsoleCommand {
|
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
69
|
catch (e) {
|
|
70
|
+
process.exitCode = process.exitCode || 1;
|
|
65
71
|
yield WebframezHooks_1.WebframezHooks.emit("console.command.error", {
|
|
66
72
|
operationId,
|
|
67
73
|
name: commandSignature,
|