@webtypen/webframez-core 0.3.37 → 0.3.39

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.
Files changed (80) hide show
  1. package/README.md +215 -0
  2. package/dist/Ai/AiTypes.d.ts +10 -0
  3. package/dist/Ai/AiTypes.js +2 -0
  4. package/dist/Api/ApiFunction.d.ts +27 -0
  5. package/dist/Api/ApiFunction.js +45 -0
  6. package/dist/Api/ApiFunctionUtils.d.ts +61 -0
  7. package/dist/Api/ApiFunctionUtils.js +274 -0
  8. package/dist/Api/ApiScope.d.ts +12 -0
  9. package/dist/Api/ApiScope.js +26 -0
  10. package/dist/Api/ApiScopeRegistry.d.ts +17 -0
  11. package/dist/Api/ApiScopeRegistry.js +131 -0
  12. package/dist/Api/ApiTypes.d.ts +33 -0
  13. package/dist/Api/ApiTypes.js +2 -0
  14. package/dist/Auth/AuthTypes.d.ts +1 -0
  15. package/dist/Auth/AuthTypes.js +2 -0
  16. package/dist/Auth/UserAuth.d.ts +54 -0
  17. package/dist/Auth/UserAuth.js +368 -0
  18. package/dist/Auth/UserPermissions.d.ts +13 -0
  19. package/dist/Auth/UserPermissions.js +56 -0
  20. package/dist/Auth/index.d.ts +3 -0
  21. package/dist/Auth/index.js +19 -0
  22. package/dist/Backup/BackupManager.d.ts +28 -0
  23. package/dist/Backup/BackupManager.js +341 -0
  24. package/dist/Backup/BackupOutputDrivers.d.ts +12 -0
  25. package/dist/Backup/BackupOutputDrivers.js +25 -0
  26. package/dist/Backup/BackupTypes.d.ts +107 -0
  27. package/dist/Backup/BackupTypes.js +2 -0
  28. package/dist/Backup/Jobs/BackupRunJob.d.ts +4 -0
  29. package/dist/Backup/Jobs/BackupRunJob.js +34 -0
  30. package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.d.ts +10 -0
  31. package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.js +2 -0
  32. package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.d.ts +12 -0
  33. package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.js +163 -0
  34. package/dist/Backup/Utils/BackupPathUtils.d.ts +8 -0
  35. package/dist/Backup/Utils/BackupPathUtils.js +35 -0
  36. package/dist/Backup/Utils/GlobMatcher.d.ts +1 -0
  37. package/dist/Backup/Utils/GlobMatcher.js +63 -0
  38. package/dist/Backup/Utils/ZipWriter.d.ts +4 -0
  39. package/dist/Backup/Utils/ZipWriter.js +120 -0
  40. package/dist/BaseKernelConsole.d.ts +4 -0
  41. package/dist/BaseKernelWeb.d.ts +6 -0
  42. package/dist/BaseKernelWeb.js +1 -0
  43. package/dist/Cli.js +1 -0
  44. package/dist/Commands/BackupCleanupCommand.d.ts +6 -0
  45. package/dist/Commands/BackupCleanupCommand.js +54 -0
  46. package/dist/Commands/BackupListCommand.d.ts +6 -0
  47. package/dist/Commands/BackupListCommand.js +39 -0
  48. package/dist/Commands/BackupQueueCommand.d.ts +6 -0
  49. package/dist/Commands/BackupQueueCommand.js +54 -0
  50. package/dist/Commands/BackupRunCommand.d.ts +6 -0
  51. package/dist/Commands/BackupRunCommand.js +59 -0
  52. package/dist/Commands/ConsoleCommand.js +30 -0
  53. package/dist/Commands/QueueWorkerCommand.d.ts +4 -0
  54. package/dist/Commands/QueueWorkerCommand.js +103 -11
  55. package/dist/ConsoleApplication.d.ts +2 -0
  56. package/dist/ConsoleApplication.js +43 -0
  57. package/dist/Database/BaseDBDriver.d.ts +1 -0
  58. package/dist/Database/BaseDBDriver.js +5 -0
  59. package/dist/Facades/Storage.d.ts +1 -0
  60. package/dist/Facades/StorageDrivers/BaseStorageDriver.d.ts +1 -0
  61. package/dist/Facades/StorageDrivers/LocalStorageDriver.d.ts +1 -0
  62. package/dist/Hooks/WebframezHooks.d.ts +38 -0
  63. package/dist/Hooks/WebframezHooks.js +85 -0
  64. package/dist/Http/HttpKernelHandlerRegistry.d.ts +20 -0
  65. package/dist/Http/HttpKernelHandlerRegistry.js +46 -0
  66. package/dist/LambdaApplication.js +36 -1
  67. package/dist/Mcp/McpTypes.d.ts +15 -0
  68. package/dist/Mcp/McpTypes.js +2 -0
  69. package/dist/Modules/ModuleProvider.d.ts +8 -1
  70. package/dist/Modules/ModuleProvider.js +3 -1
  71. package/dist/Modules/ModulesLoader.d.ts +6 -1
  72. package/dist/Modules/ModulesLoader.js +18 -2
  73. package/dist/Router/Route.d.ts +8 -0
  74. package/dist/Router/Route.js +10 -0
  75. package/dist/Router/Router.d.ts +6 -0
  76. package/dist/Router/Router.js +199 -8
  77. package/dist/WebApplication.js +81 -3
  78. package/dist/index.d.ts +16 -0
  79. package/dist/index.js +16 -0
  80. package/package.json +1 -1
package/README.md CHANGED
@@ -4,6 +4,7 @@ TypeScript-first backend framework core for Node.js.
4
4
 
5
5
  This README reflects the current API in this repository and focuses on:
6
6
  - Routing
7
+ - ApiScopes and ApiFunctions
7
8
  - Datatables
8
9
  - DataBuilder
9
10
  - Console Commands
@@ -103,6 +104,7 @@ app.boot({
103
104
  - `modules`
104
105
  - `config`
105
106
  - `datatables`
107
+ - `apiScopes` on the kernel or module providers
106
108
  - `jobs`
107
109
  - `mode`
108
110
  - `onBoot`
@@ -116,6 +118,7 @@ Route.get("/", "HomeController@index");
116
118
  Route.post("/login", "AuthController@login");
117
119
  Route.put("/users/:id", "UserController@update");
118
120
  Route.delete("/users/:id", "UserController@delete");
121
+ Route.patch("/users/:id", "UserController@patch");
119
122
  ```
120
123
 
121
124
  You can also register inline handlers:
@@ -233,6 +236,105 @@ Also available:
233
236
  - `stream(req, filepath, filename, mimeType)`
234
237
  - `registerEvent("after", fn)`
235
238
 
239
+ ## ApiScopes and ApiFunctions
240
+
241
+ ApiScopes group callable ApiFunctions and can automatically expose them as normal HTTP routes. MCP exposure is intentionally not implemented in core; install `@webtypen/webframez-ai` and use `APIFunctionsMCPServer` when ApiFunctions should be exposed as MCP tools.
242
+
243
+ ### Define ApiFunctions
244
+
245
+ ```ts
246
+ import { ApiFunction, ApiFunctionRequest, ApiFunctionResponse } from "@webtypen/webframez-core";
247
+
248
+ class CurrentUserFunction extends ApiFunction {
249
+ key = "current-user";
250
+ description = "Returns the current user.";
251
+ requestMethod = "GET";
252
+ params = {
253
+ includePermissions: { type: "boolean", default: false }
254
+ };
255
+
256
+ async handle(apiRequest: ApiFunctionRequest) {
257
+ return new ApiFunctionResponse({
258
+ user: apiRequest.context.user,
259
+ includePermissions: apiRequest.params.includePermissions
260
+ });
261
+ }
262
+ }
263
+ ```
264
+
265
+ `requestMethod` may be `"GET"`, `"POST"`, `"PUT"`, `"PATCH"`, `"DELETE"` or `null`.
266
+ Functions with `requestMethod = null` are not registered as normal HTTP routes, but they can still be used by higher-level integrations such as `APIFunctionsMCPServer` in `@webtypen/webframez-ai`.
267
+
268
+ ### Define ApiScopes
269
+
270
+ ```ts
271
+ import { ApiScope, Request, Response } from "@webtypen/webframez-core";
272
+
273
+ class BackofficeApiScope extends ApiScope {
274
+ key = "backoffice";
275
+ apiBasePath = "/api/backoffice";
276
+ functions = [CurrentUserFunction];
277
+
278
+ async middleware(req: Request, _res: Response, abort: (message?: any, status?: number) => never) {
279
+ if (!req.headers.authorization) {
280
+ return abort("Unauthorized", 401);
281
+ }
282
+
283
+ return {
284
+ user: { id: "..." }
285
+ };
286
+ }
287
+ }
288
+ ```
289
+
290
+ For every function with a non-null request method, core registers:
291
+
292
+ ```text
293
+ apiBasePath + "/" + function.key
294
+ ```
295
+
296
+ Example: `GET /api/backoffice/current-user`.
297
+
298
+ The scope middleware runs before the function. Its return value is passed to the function as `apiRequest.context`. Calling `abort(message, status)` stops execution and returns an error response with that status.
299
+
300
+ ### Register ApiScopes
301
+
302
+ Register scopes on the web kernel:
303
+
304
+ ```ts
305
+ import { BaseKernelWeb } from "@webtypen/webframez-core";
306
+
307
+ class Kernel extends BaseKernelWeb {
308
+ static apiScopes = [BackofficeApiScope];
309
+ }
310
+ ```
311
+
312
+ Modules can register scopes as instance properties:
313
+
314
+ ```ts
315
+ import { ModuleProvider } from "@webtypen/webframez-core";
316
+
317
+ class BackofficeModuleProvider extends ModuleProvider {
318
+ apiScopes = [BackofficeApiScope];
319
+ }
320
+ ```
321
+
322
+ ### Params and Validation
323
+
324
+ GET functions validate params from `req.query`; all other request methods validate params from `req.body`.
325
+
326
+ Supported descriptor types include:
327
+ - `string`
328
+ - `number` / `float`
329
+ - `integer` / `int`
330
+ - `boolean` / `bool`
331
+ - `ObjectId`
332
+ - `option`
333
+ - `array`
334
+ - `object`
335
+
336
+ Defaults are applied before required checks. Unknown types are passed through unchanged but still respect `required` and `default`.
337
+
236
338
  ## Datatables
237
339
 
238
340
  Use `Datatable` + `DatatableRegistry` + `DatatableController`.
@@ -453,6 +555,119 @@ export default {
453
555
  Additional built-in command:
454
556
  - `build`
455
557
 
558
+ ## Backups
559
+
560
+ Backups are configured through `config.backup`, usually by importing `config/backup.ts` next to your database and queue configs. Core ships the local filesystem output driver and a generic adapter registry.
561
+
562
+ ```ts
563
+ export default {
564
+ defaults: {
565
+ workDir: "storage/backups/.work",
566
+ outputDir: "storage/backups",
567
+ filename: "{key}_{date}_{time}",
568
+ zip: true,
569
+ cleanupWorkDir: true,
570
+ retention: {
571
+ keepLast: 10,
572
+ maxAgeDays: 30,
573
+ runAfterBackup: true
574
+ }
575
+ },
576
+ types: {
577
+ daily_full: {
578
+ is_active: true,
579
+ files: [
580
+ {
581
+ from: "storage",
582
+ to: "storage",
583
+ include: ["**/*"],
584
+ exclude: ["queue/**", "backups/**"]
585
+ }
586
+ ],
587
+ databases: [
588
+ {
589
+ connection: "default",
590
+ to: "database/default",
591
+ options: {}
592
+ }
593
+ ],
594
+ outputs: [
595
+ {
596
+ driver: "local",
597
+ path: "storage/backups/daily",
598
+ retention: {
599
+ keepLast: 14,
600
+ maxAgeDays: 60,
601
+ runAfterBackup: true
602
+ }
603
+ }
604
+ ],
605
+ automation: {
606
+ worker: "worker_1",
607
+ executions: [["daily", "02:00"]],
608
+ priority: 10
609
+ }
610
+ }
611
+ }
612
+ };
613
+ ```
614
+
615
+ Built-in backup commands:
616
+ - `backup:list`
617
+ - `backup:run <key> [--dry-run] [--channel=local]`
618
+ - `backup:queue <key> [--worker=worker_1] [--priority=10]`
619
+ - `backup:cleanup <key> [--channel=local] [--dry-run]`
620
+
621
+ Remote outputs are optional packages and must be registered by the application before the backup config is used:
622
+
623
+ ```ts
624
+ import { BackupOutputDrivers } from "@webtypen/webframez-core";
625
+ import { GoogleDriveBackupOutputDriver } from "@webtypen/webframez-backups-googledrive";
626
+ import { OneDriveBackupOutputDriver } from "@webtypen/webframez-backups-onedrive";
627
+ import { SftpBackupOutputDriver } from "@webtypen/webframez-backups-sftp";
628
+ import { FtpBackupOutputDriver } from "@webtypen/webframez-backups-ftp";
629
+
630
+ BackupOutputDrivers.register("google_drive", GoogleDriveBackupOutputDriver);
631
+ BackupOutputDrivers.register("one_drive", OneDriveBackupOutputDriver);
632
+ BackupOutputDrivers.register("sftp", SftpBackupOutputDriver);
633
+ BackupOutputDrivers.register("ftp", FtpBackupOutputDriver);
634
+
635
+ export default {
636
+ types: {
637
+ daily_google_drive: {
638
+ is_active: true,
639
+ files: [{ from: "storage", to: "storage", exclude: ["backups/**", "queue/**"] }],
640
+ outputs: [
641
+ {
642
+ driver: "google_drive",
643
+ folderId: process.env.GOOGLE_DRIVE_BACKUP_FOLDER_ID,
644
+ auth: {
645
+ keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS
646
+ },
647
+ retention: {
648
+ keepLast: 30,
649
+ maxAgeDays: 90,
650
+ runAfterBackup: true
651
+ }
652
+ }
653
+ ],
654
+ automation: {
655
+ worker: "worker_1",
656
+ executions: [["daily", "03:00"], ["daily", "12:00"]]
657
+ }
658
+ }
659
+ }
660
+ };
661
+ ```
662
+
663
+ Available adapter packages:
664
+ - `@webtypen/webframez-backups-googledrive`
665
+ - `@webtypen/webframez-backups-onedrive`
666
+ - `@webtypen/webframez-backups-sftp`
667
+ - `@webtypen/webframez-backups-ftp`
668
+
669
+ Database backups are delegated to the configured database driver via `backup(client, options)`. Implement that hook in your driver to write dump files into `options.targetDir`.
670
+
456
671
  ## Modules
457
672
 
458
673
  You can load module providers via `modules` in `WebApplication.boot(...)`.
@@ -0,0 +1,10 @@
1
+ export type KernelAiAgentRegistration = any;
2
+ export type KernelAiApiRegistrations = {
3
+ prefix?: string;
4
+ middleware?: string[];
5
+ [key: string]: any;
6
+ };
7
+ export type KernelAiRegistrations = {
8
+ agents?: KernelAiAgentRegistration[];
9
+ api?: KernelAiApiRegistrations;
10
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ import { ApiFunctionParamsDefinition, ApiFunctionRequest, ApiFunctionRequestMethod, ApiFunctionResponseType } from "./ApiTypes";
2
+ export declare class ApiFunctionResponse {
3
+ data: {
4
+ [key: string]: any;
5
+ };
6
+ status: number;
7
+ responseType: ApiFunctionResponseType;
8
+ getData(): {
9
+ [key: string]: any;
10
+ };
11
+ constructor(data: {
12
+ [key: string]: any;
13
+ }, options?: {
14
+ status?: number;
15
+ responseType?: ApiFunctionResponseType;
16
+ });
17
+ }
18
+ export declare class ApiFunction {
19
+ key: string;
20
+ description: string;
21
+ requestMethod: ApiFunctionRequestMethod;
22
+ mcpDisabled: boolean;
23
+ params: ApiFunctionParamsDefinition;
24
+ handle(apiRequest: ApiFunctionRequest): Promise<ApiFunctionResponse | {
25
+ [key: string]: any;
26
+ }>;
27
+ }
@@ -0,0 +1,45 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ApiFunction = exports.ApiFunctionResponse = void 0;
13
+ class ApiFunctionResponse {
14
+ getData() {
15
+ return this.data;
16
+ }
17
+ constructor(data, options) {
18
+ var _a, _b;
19
+ this.data = {};
20
+ this.status = 200;
21
+ this.responseType = "response";
22
+ this.data = data;
23
+ this.status = (_a = options === null || options === void 0 ? void 0 : options.status) !== null && _a !== void 0 ? _a : 200;
24
+ this.responseType = (_b = options === null || options === void 0 ? void 0 : options.responseType) !== null && _b !== void 0 ? _b : "response";
25
+ }
26
+ }
27
+ exports.ApiFunctionResponse = ApiFunctionResponse;
28
+ class ApiFunction {
29
+ constructor() {
30
+ this.key = "unnamed-function";
31
+ this.description = "A unnamed example function";
32
+ this.requestMethod = "POST";
33
+ this.mcpDisabled = false;
34
+ this.params = {
35
+ elementId: { type: "ObjectId", required: true },
36
+ mode: { type: "option", options: [{ value: "all" }, { value: "pending" }], default: "all" },
37
+ };
38
+ }
39
+ handle(apiRequest) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ return new ApiFunctionResponse({ status: "Api is running ..." });
42
+ });
43
+ }
44
+ }
45
+ exports.ApiFunction = ApiFunction;
@@ -0,0 +1,61 @@
1
+ /// <reference types="node" />
2
+ import { IncomingMessage } from "http";
3
+ import { Request } from "../Router/Request";
4
+ import { Response } from "../Router/Response";
5
+ import { ApiFunction } from "./ApiFunction";
6
+ import { ApiScope } from "./ApiScope";
7
+ import { ApiFunctionClass, ApiFunctionParamDefinition, ApiFunctionParamsDefinition, ApiScopeClass } from "./ApiTypes";
8
+ export declare class ApiFunctionRuntimeError extends Error {
9
+ statusCode: number;
10
+ constructor(message: string, statusCode?: number);
11
+ }
12
+ export declare function normalizeApiPath(value: string): string;
13
+ export declare function joinApiPath(...parts: Array<string | null | undefined>): string;
14
+ export declare function getApiErrorMessage(error: any): any;
15
+ export declare function isPlainApiObject(value: any): boolean;
16
+ export declare function normalizeApiFunctionProvider(provider: any): ApiFunctionClass[];
17
+ export declare function getApiFunctionClasses(scope: ApiScope): ApiFunctionClass[];
18
+ export declare function runApiScopeMiddleware(scope: ApiScope, req: Request, res: Response): Promise<{
19
+ [key: string]: any;
20
+ }>;
21
+ export declare function validateApiFunctionParams(definitions: ApiFunctionParamsDefinition, source: {
22
+ [key: string]: any;
23
+ }): {
24
+ [key: string]: any;
25
+ };
26
+ export declare function coerceApiFunctionParam(key: string, value: any, definition: ApiFunctionParamDefinition): any;
27
+ export declare function apiFunctionParamsToJsonSchema(definitions: ApiFunctionParamsDefinition): {
28
+ type: string;
29
+ properties: {
30
+ [key: string]: any;
31
+ };
32
+ required: string[];
33
+ additionalProperties: boolean;
34
+ };
35
+ export declare function apiFunctionParamToJsonSchema(definition: ApiFunctionParamDefinition): {
36
+ type: string;
37
+ pattern?: undefined;
38
+ items?: undefined;
39
+ enum?: undefined;
40
+ } | {
41
+ type: string;
42
+ pattern: string;
43
+ items?: undefined;
44
+ enum?: undefined;
45
+ } | {
46
+ type: string;
47
+ items: {};
48
+ pattern?: undefined;
49
+ enum?: undefined;
50
+ } | {
51
+ enum: any[];
52
+ type?: undefined;
53
+ pattern?: undefined;
54
+ items?: undefined;
55
+ };
56
+ export declare function toApiFunctionHttpResult(res: Response, result: any): Response;
57
+ export declare function toApiFunctionPayload(result: any): any;
58
+ export declare function createApiFrameworkRequest(req: IncomingMessage): Request;
59
+ export declare function assertUniqueApiFunctionKeys(scope: ApiScope, functions: ApiFunctionClass[]): void;
60
+ export declare function instantiateApiScope(ScopeClass: ApiScopeClass): ApiScope;
61
+ export declare function instantiateApiFunction(FunctionClass: ApiFunctionClass): ApiFunction;
@@ -0,0 +1,274 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.instantiateApiFunction = exports.instantiateApiScope = exports.assertUniqueApiFunctionKeys = exports.createApiFrameworkRequest = exports.toApiFunctionPayload = exports.toApiFunctionHttpResult = exports.apiFunctionParamToJsonSchema = exports.apiFunctionParamsToJsonSchema = exports.coerceApiFunctionParam = exports.validateApiFunctionParams = exports.runApiScopeMiddleware = exports.getApiFunctionClasses = exports.normalizeApiFunctionProvider = exports.isPlainApiObject = exports.getApiErrorMessage = exports.joinApiPath = exports.normalizeApiPath = exports.ApiFunctionRuntimeError = void 0;
13
+ const Request_1 = require("../Router/Request");
14
+ const ApiFunction_1 = require("./ApiFunction");
15
+ class ApiFunctionRuntimeError extends Error {
16
+ constructor(message, statusCode = 500) {
17
+ super(message);
18
+ this.statusCode = statusCode;
19
+ }
20
+ }
21
+ exports.ApiFunctionRuntimeError = ApiFunctionRuntimeError;
22
+ function normalizeApiPath(value) {
23
+ const trimmed = value && value.trim() !== "" ? value.trim() : "/";
24
+ const withSlash = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
25
+ const collapsed = withSlash.replace(/\/+/g, "/");
26
+ return collapsed.length > 1 ? collapsed.replace(/\/+$/, "") : collapsed;
27
+ }
28
+ exports.normalizeApiPath = normalizeApiPath;
29
+ function joinApiPath(...parts) {
30
+ const cleaned = parts
31
+ .filter((part) => typeof part === "string" && part.trim() !== "")
32
+ .map((part) => part.trim().replace(/^\/+|\/+$/g, ""))
33
+ .filter((part) => part !== "");
34
+ return normalizeApiPath("/" + cleaned.join("/"));
35
+ }
36
+ exports.joinApiPath = joinApiPath;
37
+ function getApiErrorMessage(error) {
38
+ if (error && typeof error === "string" && error.trim() !== "") {
39
+ return error.trim();
40
+ }
41
+ if (error && error.message && typeof error.message === "string" && error.message.trim() !== "") {
42
+ return error.message.trim();
43
+ }
44
+ if (error && error.toString && error.toString().trim() !== "") {
45
+ return error.toString().trim();
46
+ }
47
+ return "Internal Server Error";
48
+ }
49
+ exports.getApiErrorMessage = getApiErrorMessage;
50
+ function isPlainApiObject(value) {
51
+ return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Buffer);
52
+ }
53
+ exports.isPlainApiObject = isPlainApiObject;
54
+ function normalizeApiFunctionProvider(provider) {
55
+ if (typeof provider === "function") {
56
+ const resolved = provider();
57
+ if (resolved && typeof resolved.then === "function") {
58
+ throw new Error("Async ApiScope.functions providers are not supported during web boot");
59
+ }
60
+ return Array.isArray(resolved) ? resolved : [];
61
+ }
62
+ return Array.isArray(provider) ? provider : [];
63
+ }
64
+ exports.normalizeApiFunctionProvider = normalizeApiFunctionProvider;
65
+ function getApiFunctionClasses(scope) {
66
+ const functions = normalizeApiFunctionProvider(scope.functions);
67
+ assertUniqueApiFunctionKeys(scope, functions);
68
+ return functions;
69
+ }
70
+ exports.getApiFunctionClasses = getApiFunctionClasses;
71
+ function runApiScopeMiddleware(scope, req, res) {
72
+ return __awaiter(this, void 0, void 0, function* () {
73
+ const abort = (message, status = 403) => {
74
+ throw new ApiFunctionRuntimeError(getApiErrorMessage(message || "Request aborted"), status);
75
+ };
76
+ const result = yield scope.middleware(req, res, abort);
77
+ return isPlainApiObject(result) ? result : {};
78
+ });
79
+ }
80
+ exports.runApiScopeMiddleware = runApiScopeMiddleware;
81
+ function validateApiFunctionParams(definitions, source) {
82
+ const params = {};
83
+ const input = isPlainApiObject(source) ? source : {};
84
+ for (const key of Object.keys(definitions || {})) {
85
+ const definition = definitions[key] || {};
86
+ const hasValue = input[key] !== undefined && input[key] !== null && input[key] !== "";
87
+ const value = hasValue ? input[key] : definition.default;
88
+ if ((value === undefined || value === null || value === "") && definition.required) {
89
+ throw new ApiFunctionRuntimeError(`Missing required parameter "${key}"`, 400);
90
+ }
91
+ if (value === undefined || value === null || value === "") {
92
+ params[key] = value;
93
+ continue;
94
+ }
95
+ params[key] = coerceApiFunctionParam(key, value, definition);
96
+ }
97
+ return params;
98
+ }
99
+ exports.validateApiFunctionParams = validateApiFunctionParams;
100
+ function coerceApiFunctionParam(key, value, definition) {
101
+ const type = (definition.type || "string").toLowerCase();
102
+ if (type === "string") {
103
+ return typeof value === "string" ? value : value.toString();
104
+ }
105
+ if (type === "number" || type === "float") {
106
+ const parsed = typeof value === "number" ? value : parseFloat(value);
107
+ if (isNaN(parsed)) {
108
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a number`, 400);
109
+ }
110
+ return parsed;
111
+ }
112
+ if (type === "integer" || type === "int") {
113
+ const parsed = typeof value === "number" ? value : parseInt(value, 10);
114
+ if (isNaN(parsed)) {
115
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an integer`, 400);
116
+ }
117
+ return parsed;
118
+ }
119
+ if (type === "boolean" || type === "bool") {
120
+ if (typeof value === "boolean") {
121
+ return value;
122
+ }
123
+ const normalized = value.toString().toLowerCase();
124
+ if (["true", "1", "yes", "on"].includes(normalized)) {
125
+ return true;
126
+ }
127
+ if (["false", "0", "no", "off"].includes(normalized)) {
128
+ return false;
129
+ }
130
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a boolean`, 400);
131
+ }
132
+ if (type === "objectid") {
133
+ const stringValue = value.toString();
134
+ if (!/^[a-fA-F0-9]{24}$/.test(stringValue)) {
135
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a valid ObjectId`, 400);
136
+ }
137
+ return stringValue;
138
+ }
139
+ if (type === "option") {
140
+ const allowedValues = Array.isArray(definition.options) ? definition.options.map((option) => option.value) : [];
141
+ if (allowedValues.length > 0 && !allowedValues.includes(value)) {
142
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be one of: ${allowedValues.join(", ")}`, 400);
143
+ }
144
+ return value;
145
+ }
146
+ if (type === "array") {
147
+ if (Array.isArray(value)) {
148
+ return value;
149
+ }
150
+ if (typeof value === "string") {
151
+ try {
152
+ const parsed = JSON.parse(value);
153
+ if (Array.isArray(parsed)) {
154
+ return parsed;
155
+ }
156
+ }
157
+ catch (_error) { }
158
+ }
159
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an array`, 400);
160
+ }
161
+ if (type === "object") {
162
+ if (isPlainApiObject(value)) {
163
+ return value;
164
+ }
165
+ if (typeof value === "string") {
166
+ try {
167
+ const parsed = JSON.parse(value);
168
+ if (isPlainApiObject(parsed)) {
169
+ return parsed;
170
+ }
171
+ }
172
+ catch (_error) { }
173
+ }
174
+ throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an object`, 400);
175
+ }
176
+ return value;
177
+ }
178
+ exports.coerceApiFunctionParam = coerceApiFunctionParam;
179
+ function apiFunctionParamsToJsonSchema(definitions) {
180
+ const properties = {};
181
+ const required = [];
182
+ for (const key of Object.keys(definitions || {})) {
183
+ const definition = definitions[key] || {};
184
+ const schema = apiFunctionParamToJsonSchema(definition);
185
+ if (definition.description) {
186
+ schema.description = definition.description;
187
+ }
188
+ if (definition.default !== undefined) {
189
+ schema.default = definition.default;
190
+ }
191
+ if (definition.required && definition.default === undefined) {
192
+ required.push(key);
193
+ }
194
+ properties[key] = schema;
195
+ }
196
+ return {
197
+ type: "object",
198
+ properties,
199
+ required,
200
+ additionalProperties: false,
201
+ };
202
+ }
203
+ exports.apiFunctionParamsToJsonSchema = apiFunctionParamsToJsonSchema;
204
+ function apiFunctionParamToJsonSchema(definition) {
205
+ const type = (definition.type || "string").toLowerCase();
206
+ if (type === "number" || type === "float")
207
+ return { type: "number" };
208
+ if (type === "integer" || type === "int")
209
+ return { type: "integer" };
210
+ if (type === "boolean" || type === "bool")
211
+ return { type: "boolean" };
212
+ if (type === "objectid")
213
+ return { type: "string", pattern: "^[a-fA-F0-9]{24}$" };
214
+ if (type === "array")
215
+ return { type: "array", items: {} };
216
+ if (type === "object")
217
+ return { type: "object" };
218
+ if (type === "option" && Array.isArray(definition.options) && definition.options.length > 0) {
219
+ return { enum: definition.options.map((option) => option.value) };
220
+ }
221
+ return { type: "string" };
222
+ }
223
+ exports.apiFunctionParamToJsonSchema = apiFunctionParamToJsonSchema;
224
+ function toApiFunctionHttpResult(res, result) {
225
+ if (result instanceof ApiFunction_1.ApiFunctionResponse) {
226
+ res.status(result.status);
227
+ return res.send(result.getData());
228
+ }
229
+ return res.send(result);
230
+ }
231
+ exports.toApiFunctionHttpResult = toApiFunctionHttpResult;
232
+ function toApiFunctionPayload(result) {
233
+ return result instanceof ApiFunction_1.ApiFunctionResponse ? result.getData() : result;
234
+ }
235
+ exports.toApiFunctionPayload = toApiFunctionPayload;
236
+ function createApiFrameworkRequest(req) {
237
+ const parsedUrl = new URL(req.url || "/", "http://webframez.local");
238
+ const request = new Request_1.Request();
239
+ request.message = req;
240
+ request.headers = req.headers;
241
+ request.rawHeaders = req.rawHeaders;
242
+ request.url = req.url || "";
243
+ request.method = req.method || "";
244
+ request.socket = req.socket;
245
+ request.query = Object.fromEntries(parsedUrl.searchParams.entries());
246
+ request.queryRaw = parsedUrl.search ? parsedUrl.search.substring(1) : "";
247
+ request.pathname = parsedUrl.pathname;
248
+ request.body = {};
249
+ request.bodyPlain = "";
250
+ return request;
251
+ }
252
+ exports.createApiFrameworkRequest = createApiFrameworkRequest;
253
+ function assertUniqueApiFunctionKeys(scope, functions) {
254
+ const seen = {};
255
+ for (const FunctionClass of functions) {
256
+ const func = new FunctionClass();
257
+ if (!func.key || func.key.trim() === "") {
258
+ throw new Error(`ApiFunction in scope "${scope.key}" is missing a key`);
259
+ }
260
+ if (seen[func.key]) {
261
+ throw new Error(`Duplicate ApiFunction key "${func.key}" in scope "${scope.key}"`);
262
+ }
263
+ seen[func.key] = true;
264
+ }
265
+ }
266
+ exports.assertUniqueApiFunctionKeys = assertUniqueApiFunctionKeys;
267
+ function instantiateApiScope(ScopeClass) {
268
+ return new ScopeClass();
269
+ }
270
+ exports.instantiateApiScope = instantiateApiScope;
271
+ function instantiateApiFunction(FunctionClass) {
272
+ return new FunctionClass();
273
+ }
274
+ exports.instantiateApiFunction = instantiateApiFunction;
@@ -0,0 +1,12 @@
1
+ import { Request } from "../Router/Request";
2
+ import { Response } from "../Router/Response";
3
+ import { ApiFunctionClass, ApiScopeMiddlewareAbort } from "./ApiTypes";
4
+ export declare class ApiScope {
5
+ key: string;
6
+ apiBasePath: string | null;
7
+ mcpEndpoint: string | null;
8
+ functions: Array<ApiFunctionClass> | (() => Array<ApiFunctionClass>);
9
+ middleware(_req: Request, _res: Response, _abort: ApiScopeMiddlewareAbort): Promise<{
10
+ [key: string]: any;
11
+ }>;
12
+ }