@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.
- package/README.md +215 -0
- package/dist/Ai/AiTypes.d.ts +10 -0
- package/dist/Ai/AiTypes.js +2 -0
- package/dist/Api/ApiFunction.d.ts +27 -0
- package/dist/Api/ApiFunction.js +45 -0
- package/dist/Api/ApiFunctionUtils.d.ts +61 -0
- package/dist/Api/ApiFunctionUtils.js +274 -0
- package/dist/Api/ApiScope.d.ts +12 -0
- package/dist/Api/ApiScope.js +26 -0
- package/dist/Api/ApiScopeRegistry.d.ts +17 -0
- package/dist/Api/ApiScopeRegistry.js +131 -0
- package/dist/Api/ApiTypes.d.ts +33 -0
- package/dist/Api/ApiTypes.js +2 -0
- package/dist/Auth/AuthTypes.d.ts +1 -0
- package/dist/Auth/AuthTypes.js +2 -0
- package/dist/Auth/UserAuth.d.ts +54 -0
- package/dist/Auth/UserAuth.js +368 -0
- package/dist/Auth/UserPermissions.d.ts +13 -0
- package/dist/Auth/UserPermissions.js +56 -0
- package/dist/Auth/index.d.ts +3 -0
- package/dist/Auth/index.js +19 -0
- package/dist/Backup/BackupManager.d.ts +28 -0
- package/dist/Backup/BackupManager.js +341 -0
- package/dist/Backup/BackupOutputDrivers.d.ts +12 -0
- package/dist/Backup/BackupOutputDrivers.js +25 -0
- package/dist/Backup/BackupTypes.d.ts +107 -0
- package/dist/Backup/BackupTypes.js +2 -0
- package/dist/Backup/Jobs/BackupRunJob.d.ts +4 -0
- package/dist/Backup/Jobs/BackupRunJob.js +34 -0
- package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.d.ts +10 -0
- package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.js +2 -0
- package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.d.ts +12 -0
- package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.js +163 -0
- package/dist/Backup/Utils/BackupPathUtils.d.ts +8 -0
- package/dist/Backup/Utils/BackupPathUtils.js +35 -0
- package/dist/Backup/Utils/GlobMatcher.d.ts +1 -0
- package/dist/Backup/Utils/GlobMatcher.js +63 -0
- package/dist/Backup/Utils/ZipWriter.d.ts +4 -0
- package/dist/Backup/Utils/ZipWriter.js +120 -0
- package/dist/BaseKernelConsole.d.ts +4 -0
- package/dist/BaseKernelWeb.d.ts +6 -0
- package/dist/BaseKernelWeb.js +1 -0
- package/dist/Cli.js +1 -0
- package/dist/Commands/BackupCleanupCommand.d.ts +6 -0
- package/dist/Commands/BackupCleanupCommand.js +54 -0
- package/dist/Commands/BackupListCommand.d.ts +6 -0
- package/dist/Commands/BackupListCommand.js +39 -0
- package/dist/Commands/BackupQueueCommand.d.ts +6 -0
- package/dist/Commands/BackupQueueCommand.js +54 -0
- package/dist/Commands/BackupRunCommand.d.ts +6 -0
- package/dist/Commands/BackupRunCommand.js +59 -0
- package/dist/Commands/ConsoleCommand.js +30 -0
- package/dist/Commands/QueueWorkerCommand.d.ts +4 -0
- package/dist/Commands/QueueWorkerCommand.js +103 -11
- package/dist/ConsoleApplication.d.ts +2 -0
- package/dist/ConsoleApplication.js +43 -0
- package/dist/Database/BaseDBDriver.d.ts +1 -0
- package/dist/Database/BaseDBDriver.js +5 -0
- package/dist/Facades/Storage.d.ts +1 -0
- package/dist/Facades/StorageDrivers/BaseStorageDriver.d.ts +1 -0
- package/dist/Facades/StorageDrivers/LocalStorageDriver.d.ts +1 -0
- package/dist/Hooks/WebframezHooks.d.ts +38 -0
- package/dist/Hooks/WebframezHooks.js +85 -0
- package/dist/Http/HttpKernelHandlerRegistry.d.ts +20 -0
- package/dist/Http/HttpKernelHandlerRegistry.js +46 -0
- package/dist/LambdaApplication.js +36 -1
- package/dist/Mcp/McpTypes.d.ts +15 -0
- package/dist/Mcp/McpTypes.js +2 -0
- package/dist/Modules/ModuleProvider.d.ts +8 -1
- package/dist/Modules/ModuleProvider.js +3 -1
- package/dist/Modules/ModulesLoader.d.ts +6 -1
- package/dist/Modules/ModulesLoader.js +18 -2
- package/dist/Router/Route.d.ts +8 -0
- package/dist/Router/Route.js +10 -0
- package/dist/Router/Router.d.ts +6 -0
- package/dist/Router/Router.js +199 -8
- package/dist/WebApplication.js +81 -3
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- 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,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
|
+
}
|