@webtypen/webframez-core 0.3.38 → 0.3.40
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 +153 -0
- package/dist/Api/ApiFunction.d.ts +27 -0
- package/dist/Api/ApiFunction.js +45 -0
- package/dist/Api/ApiFunctionUtils.d.ts +79 -0
- package/dist/Api/ApiFunctionUtils.js +351 -0
- package/dist/Api/ApiScope.d.ts +15 -0
- package/dist/Api/ApiScope.js +31 -0
- package/dist/Api/ApiScopeRegistry.d.ts +17 -0
- package/dist/Api/ApiScopeRegistry.js +130 -0
- package/dist/Api/ApiScopesGroup.d.ts +10 -0
- package/dist/Api/ApiScopesGroup.js +24 -0
- package/dist/Api/ApiTypes.d.ts +35 -0
- package/dist/Api/ApiTypes.js +2 -0
- package/dist/BaseKernelWeb.d.ts +2 -0
- package/dist/BaseKernelWeb.js +1 -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/Mcp/McpTypes.d.ts +1 -1
- package/dist/Modules/ModuleProvider.d.ts +2 -0
- package/dist/Modules/ModuleProvider.js +1 -0
- 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 +17 -0
- package/dist/WebApplication.js +5 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -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,156 @@ 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
|
+
### Group ApiScopes
|
|
301
|
+
|
|
302
|
+
Use `ApiScopesGroup` to share middleware across multiple scopes:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
import { ApiScope, ApiScopesGroup, Request, Response } from "@webtypen/webframez-core";
|
|
306
|
+
|
|
307
|
+
class BackofficeApiScope extends ApiScope {
|
|
308
|
+
key = "backoffice";
|
|
309
|
+
apiBasePath = "/api/backoffice";
|
|
310
|
+
functions = [CurrentUserFunction];
|
|
311
|
+
|
|
312
|
+
async middleware(_req: Request, _res: Response, _abort: (message?: any, status?: number) => never) {
|
|
313
|
+
return {
|
|
314
|
+
scope: "backoffice"
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async groupMiddleware(_req: Request, _res: Response, _abort: (message?: any, status?: number) => never) {
|
|
319
|
+
return {
|
|
320
|
+
reachedThroughGroup: true
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class BackofficeApiScopesGroup extends ApiScopesGroup {
|
|
326
|
+
key = "backoffice-group";
|
|
327
|
+
apiScopes = [BackofficeApiScope];
|
|
328
|
+
|
|
329
|
+
async middleware(req: Request, _res: Response, abort: (message?: any, status?: number) => never) {
|
|
330
|
+
if (!req.headers.authorization) {
|
|
331
|
+
return abort("Unauthorized", 401);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
user: { id: "..." }
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
When a scope is reached through a group, middleware runs in this order:
|
|
342
|
+
|
|
343
|
+
```text
|
|
344
|
+
ApiScopesGroup.middleware()
|
|
345
|
+
ApiScope.middleware()
|
|
346
|
+
ApiScope.groupMiddleware()
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The context objects are merged in that order, so later scope-specific values can override group values. `groupMiddleware()` only runs when the scope was registered through an `ApiScopesGroup`.
|
|
350
|
+
|
|
351
|
+
### Register ApiScopes
|
|
352
|
+
|
|
353
|
+
Register scopes on the web kernel:
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { BaseKernelWeb } from "@webtypen/webframez-core";
|
|
357
|
+
|
|
358
|
+
class Kernel extends BaseKernelWeb {
|
|
359
|
+
static apiScopes = [BackofficeApiScope, BackofficeApiScopesGroup];
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Modules can register scopes as instance properties:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
import { ModuleProvider } from "@webtypen/webframez-core";
|
|
367
|
+
|
|
368
|
+
class BackofficeModuleProvider extends ModuleProvider {
|
|
369
|
+
apiScopes = [BackofficeApiScope, BackofficeApiScopesGroup];
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Params and Validation
|
|
374
|
+
|
|
375
|
+
GET functions validate params from `req.query`; all other request methods validate params from `req.body`.
|
|
376
|
+
|
|
377
|
+
Supported descriptor types include:
|
|
378
|
+
- `string`
|
|
379
|
+
- `number` / `float`
|
|
380
|
+
- `integer` / `int`
|
|
381
|
+
- `boolean` / `bool`
|
|
382
|
+
- `ObjectId`
|
|
383
|
+
- `option`
|
|
384
|
+
- `array`
|
|
385
|
+
- `object`
|
|
386
|
+
|
|
387
|
+
Defaults are applied before required checks. Unknown types are passed through unchanged but still respect `required` and `default`.
|
|
388
|
+
|
|
236
389
|
## Datatables
|
|
237
390
|
|
|
238
391
|
Use `Datatable` + `DatatableRegistry` + `DatatableController`.
|
|
@@ -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,79 @@
|
|
|
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 { ApiScopesGroup } from "./ApiScopesGroup";
|
|
8
|
+
import { ApiFunctionClass, ApiFunctionParamDefinition, ApiFunctionParamsDefinition, ApiScopeClass, ApiScopeRegistrationClass, ApiScopesGroupClass } from "./ApiTypes";
|
|
9
|
+
export type ApiScopeRegistrationEntry = {
|
|
10
|
+
scopeClass: ApiScopeClass;
|
|
11
|
+
groupClass?: ApiScopesGroupClass;
|
|
12
|
+
};
|
|
13
|
+
export declare class ApiFunctionRuntimeError extends Error {
|
|
14
|
+
statusCode: number;
|
|
15
|
+
constructor(message: string, statusCode?: number);
|
|
16
|
+
}
|
|
17
|
+
export declare function normalizeApiPath(value: string): string;
|
|
18
|
+
export declare function joinApiPath(...parts: Array<string | null | undefined>): string;
|
|
19
|
+
export declare function getApiErrorMessage(error: any): any;
|
|
20
|
+
export declare function isPlainApiObject(value: any): boolean;
|
|
21
|
+
export declare function normalizeApiFunctionProvider(provider: any): ApiFunctionClass[];
|
|
22
|
+
export declare function getApiFunctionClasses(scope: ApiScope): ApiFunctionClass[];
|
|
23
|
+
export declare function normalizeApiScopesGroupProvider(provider: any): ApiScopeClass[];
|
|
24
|
+
export declare function isApiScopesGroupInstance(value: any): value is ApiScopesGroup;
|
|
25
|
+
export declare function instantiateApiScopesGroup(GroupClass: ApiScopesGroupClass): ApiScopesGroup;
|
|
26
|
+
export declare function collectApiScopeRegistrations(registrations: ApiScopeRegistrationClass[]): ApiScopeRegistrationEntry[];
|
|
27
|
+
export declare function runApiScopeMiddleware(scope: ApiScope, req: Request, res: Response): Promise<{
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function runApiScopesGroupMiddleware(group: ApiScopesGroup, req: Request, res: Response): Promise<{
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function runApiScopeGroupMiddleware(scope: ApiScope, req: Request, res: Response): Promise<{
|
|
34
|
+
[key: string]: any;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function runApiScopeRegistrationMiddleware(registration: ApiScopeRegistrationEntry, req: Request, res: Response): Promise<{
|
|
37
|
+
[x: string]: any;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function validateApiFunctionParams(definitions: ApiFunctionParamsDefinition, source: {
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
}): {
|
|
42
|
+
[key: string]: any;
|
|
43
|
+
};
|
|
44
|
+
export declare function coerceApiFunctionParam(key: string, value: any, definition: ApiFunctionParamDefinition): any;
|
|
45
|
+
export declare function apiFunctionParamsToJsonSchema(definitions: ApiFunctionParamsDefinition): {
|
|
46
|
+
type: string;
|
|
47
|
+
properties: {
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
};
|
|
50
|
+
required: string[];
|
|
51
|
+
additionalProperties: boolean;
|
|
52
|
+
};
|
|
53
|
+
export declare function apiFunctionParamToJsonSchema(definition: ApiFunctionParamDefinition): {
|
|
54
|
+
type: string;
|
|
55
|
+
pattern?: undefined;
|
|
56
|
+
items?: undefined;
|
|
57
|
+
enum?: undefined;
|
|
58
|
+
} | {
|
|
59
|
+
type: string;
|
|
60
|
+
pattern: string;
|
|
61
|
+
items?: undefined;
|
|
62
|
+
enum?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
type: string;
|
|
65
|
+
items: {};
|
|
66
|
+
pattern?: undefined;
|
|
67
|
+
enum?: undefined;
|
|
68
|
+
} | {
|
|
69
|
+
enum: any[];
|
|
70
|
+
type?: undefined;
|
|
71
|
+
pattern?: undefined;
|
|
72
|
+
items?: undefined;
|
|
73
|
+
};
|
|
74
|
+
export declare function toApiFunctionHttpResult(res: Response, result: any): Response;
|
|
75
|
+
export declare function toApiFunctionPayload(result: any): any;
|
|
76
|
+
export declare function createApiFrameworkRequest(req: IncomingMessage): Request;
|
|
77
|
+
export declare function assertUniqueApiFunctionKeys(scope: ApiScope, functions: ApiFunctionClass[]): void;
|
|
78
|
+
export declare function instantiateApiScope(ScopeClass: ApiScopeClass): ApiScope;
|
|
79
|
+
export declare function instantiateApiFunction(FunctionClass: ApiFunctionClass): ApiFunction;
|
|
@@ -0,0 +1,351 @@
|
|
|
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.runApiScopeRegistrationMiddleware = exports.runApiScopeGroupMiddleware = exports.runApiScopesGroupMiddleware = exports.runApiScopeMiddleware = exports.collectApiScopeRegistrations = exports.instantiateApiScopesGroup = exports.isApiScopesGroupInstance = exports.normalizeApiScopesGroupProvider = 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
|
+
const ApiScopesGroup_1 = require("./ApiScopesGroup");
|
|
16
|
+
class ApiFunctionRuntimeError extends Error {
|
|
17
|
+
constructor(message, statusCode = 500) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.ApiFunctionRuntimeError = ApiFunctionRuntimeError;
|
|
23
|
+
function normalizeApiPath(value) {
|
|
24
|
+
const trimmed = value && value.trim() !== "" ? value.trim() : "/";
|
|
25
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
|
|
26
|
+
const collapsed = withSlash.replace(/\/+/g, "/");
|
|
27
|
+
return collapsed.length > 1 ? collapsed.replace(/\/+$/, "") : collapsed;
|
|
28
|
+
}
|
|
29
|
+
exports.normalizeApiPath = normalizeApiPath;
|
|
30
|
+
function joinApiPath(...parts) {
|
|
31
|
+
const cleaned = parts
|
|
32
|
+
.filter((part) => typeof part === "string" && part.trim() !== "")
|
|
33
|
+
.map((part) => part.trim().replace(/^\/+|\/+$/g, ""))
|
|
34
|
+
.filter((part) => part !== "");
|
|
35
|
+
return normalizeApiPath("/" + cleaned.join("/"));
|
|
36
|
+
}
|
|
37
|
+
exports.joinApiPath = joinApiPath;
|
|
38
|
+
function getApiErrorMessage(error) {
|
|
39
|
+
if (error && typeof error === "string" && error.trim() !== "") {
|
|
40
|
+
return error.trim();
|
|
41
|
+
}
|
|
42
|
+
if (error && error.message && typeof error.message === "string" && error.message.trim() !== "") {
|
|
43
|
+
return error.message.trim();
|
|
44
|
+
}
|
|
45
|
+
if (error && error.toString && error.toString().trim() !== "") {
|
|
46
|
+
return error.toString().trim();
|
|
47
|
+
}
|
|
48
|
+
return "Internal Server Error";
|
|
49
|
+
}
|
|
50
|
+
exports.getApiErrorMessage = getApiErrorMessage;
|
|
51
|
+
function isPlainApiObject(value) {
|
|
52
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Buffer);
|
|
53
|
+
}
|
|
54
|
+
exports.isPlainApiObject = isPlainApiObject;
|
|
55
|
+
function normalizeApiFunctionProvider(provider) {
|
|
56
|
+
if (typeof provider === "function") {
|
|
57
|
+
const resolved = provider();
|
|
58
|
+
if (resolved && typeof resolved.then === "function") {
|
|
59
|
+
throw new Error("Async ApiScope.functions providers are not supported during web boot");
|
|
60
|
+
}
|
|
61
|
+
return Array.isArray(resolved) ? resolved : [];
|
|
62
|
+
}
|
|
63
|
+
return Array.isArray(provider) ? provider : [];
|
|
64
|
+
}
|
|
65
|
+
exports.normalizeApiFunctionProvider = normalizeApiFunctionProvider;
|
|
66
|
+
function getApiFunctionClasses(scope) {
|
|
67
|
+
const functions = normalizeApiFunctionProvider(scope.functions);
|
|
68
|
+
assertUniqueApiFunctionKeys(scope, functions);
|
|
69
|
+
return functions;
|
|
70
|
+
}
|
|
71
|
+
exports.getApiFunctionClasses = getApiFunctionClasses;
|
|
72
|
+
function createApiScopeMiddlewareAbort() {
|
|
73
|
+
const abort = (message, status = 403) => {
|
|
74
|
+
throw new ApiFunctionRuntimeError(getApiErrorMessage(message || "Request aborted"), status);
|
|
75
|
+
};
|
|
76
|
+
return abort;
|
|
77
|
+
}
|
|
78
|
+
function normalizeApiScopesGroupProvider(provider) {
|
|
79
|
+
if (typeof provider === "function") {
|
|
80
|
+
const resolved = provider();
|
|
81
|
+
if (resolved && typeof resolved.then === "function") {
|
|
82
|
+
throw new Error("Async ApiScopesGroup.apiScopes providers are not supported during web boot");
|
|
83
|
+
}
|
|
84
|
+
return Array.isArray(resolved) ? resolved : [];
|
|
85
|
+
}
|
|
86
|
+
return Array.isArray(provider) ? provider : [];
|
|
87
|
+
}
|
|
88
|
+
exports.normalizeApiScopesGroupProvider = normalizeApiScopesGroupProvider;
|
|
89
|
+
function isApiScopesGroupInstance(value) {
|
|
90
|
+
return value instanceof ApiScopesGroup_1.ApiScopesGroup;
|
|
91
|
+
}
|
|
92
|
+
exports.isApiScopesGroupInstance = isApiScopesGroupInstance;
|
|
93
|
+
function instantiateApiScopesGroup(GroupClass) {
|
|
94
|
+
return new GroupClass();
|
|
95
|
+
}
|
|
96
|
+
exports.instantiateApiScopesGroup = instantiateApiScopesGroup;
|
|
97
|
+
function collectApiScopeRegistrations(registrations) {
|
|
98
|
+
const entries = [];
|
|
99
|
+
for (const RegistrationClass of registrations || []) {
|
|
100
|
+
if (typeof RegistrationClass !== "function") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const registration = new RegistrationClass();
|
|
104
|
+
if (isApiScopesGroupInstance(registration)) {
|
|
105
|
+
for (const ScopeClass of normalizeApiScopesGroupProvider(registration.apiScopes)) {
|
|
106
|
+
if (typeof ScopeClass === "function") {
|
|
107
|
+
entries.push({
|
|
108
|
+
scopeClass: ScopeClass,
|
|
109
|
+
groupClass: RegistrationClass,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
entries.push({
|
|
116
|
+
scopeClass: RegistrationClass,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return entries;
|
|
120
|
+
}
|
|
121
|
+
exports.collectApiScopeRegistrations = collectApiScopeRegistrations;
|
|
122
|
+
function runApiScopeMiddleware(scope, req, res) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
const abort = createApiScopeMiddlewareAbort();
|
|
125
|
+
const result = yield scope.middleware(req, res, abort);
|
|
126
|
+
return isPlainApiObject(result) ? result : {};
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
exports.runApiScopeMiddleware = runApiScopeMiddleware;
|
|
130
|
+
function runApiScopesGroupMiddleware(group, req, res) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
const abort = createApiScopeMiddlewareAbort();
|
|
133
|
+
const result = yield group.middleware(req, res, abort);
|
|
134
|
+
return isPlainApiObject(result) ? result : {};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
exports.runApiScopesGroupMiddleware = runApiScopesGroupMiddleware;
|
|
138
|
+
function runApiScopeGroupMiddleware(scope, req, res) {
|
|
139
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
const abort = createApiScopeMiddlewareAbort();
|
|
141
|
+
const result = yield scope.groupMiddleware(req, res, abort);
|
|
142
|
+
return isPlainApiObject(result) ? result : {};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
exports.runApiScopeGroupMiddleware = runApiScopeGroupMiddleware;
|
|
146
|
+
function runApiScopeRegistrationMiddleware(registration, req, res) {
|
|
147
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
const groupContext = registration.groupClass
|
|
149
|
+
? yield runApiScopesGroupMiddleware(instantiateApiScopesGroup(registration.groupClass), req, res)
|
|
150
|
+
: {};
|
|
151
|
+
const scope = instantiateApiScope(registration.scopeClass);
|
|
152
|
+
const scopeContext = yield runApiScopeMiddleware(scope, req, res);
|
|
153
|
+
const groupScopeContext = registration.groupClass ? yield runApiScopeGroupMiddleware(scope, req, res) : {};
|
|
154
|
+
return Object.assign(Object.assign(Object.assign({}, groupContext), scopeContext), groupScopeContext);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
exports.runApiScopeRegistrationMiddleware = runApiScopeRegistrationMiddleware;
|
|
158
|
+
function validateApiFunctionParams(definitions, source) {
|
|
159
|
+
const params = {};
|
|
160
|
+
const input = isPlainApiObject(source) ? source : {};
|
|
161
|
+
for (const key of Object.keys(definitions || {})) {
|
|
162
|
+
const definition = definitions[key] || {};
|
|
163
|
+
const hasValue = input[key] !== undefined && input[key] !== null && input[key] !== "";
|
|
164
|
+
const value = hasValue ? input[key] : definition.default;
|
|
165
|
+
if ((value === undefined || value === null || value === "") && definition.required) {
|
|
166
|
+
throw new ApiFunctionRuntimeError(`Missing required parameter "${key}"`, 400);
|
|
167
|
+
}
|
|
168
|
+
if (value === undefined || value === null || value === "") {
|
|
169
|
+
params[key] = value;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
params[key] = coerceApiFunctionParam(key, value, definition);
|
|
173
|
+
}
|
|
174
|
+
return params;
|
|
175
|
+
}
|
|
176
|
+
exports.validateApiFunctionParams = validateApiFunctionParams;
|
|
177
|
+
function coerceApiFunctionParam(key, value, definition) {
|
|
178
|
+
const type = (definition.type || "string").toLowerCase();
|
|
179
|
+
if (type === "string") {
|
|
180
|
+
return typeof value === "string" ? value : value.toString();
|
|
181
|
+
}
|
|
182
|
+
if (type === "number" || type === "float") {
|
|
183
|
+
const parsed = typeof value === "number" ? value : parseFloat(value);
|
|
184
|
+
if (isNaN(parsed)) {
|
|
185
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a number`, 400);
|
|
186
|
+
}
|
|
187
|
+
return parsed;
|
|
188
|
+
}
|
|
189
|
+
if (type === "integer" || type === "int") {
|
|
190
|
+
const parsed = typeof value === "number" ? value : parseInt(value, 10);
|
|
191
|
+
if (isNaN(parsed)) {
|
|
192
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an integer`, 400);
|
|
193
|
+
}
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
if (type === "boolean" || type === "bool") {
|
|
197
|
+
if (typeof value === "boolean") {
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
const normalized = value.toString().toLowerCase();
|
|
201
|
+
if (["true", "1", "yes", "on"].includes(normalized)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (["false", "0", "no", "off"].includes(normalized)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a boolean`, 400);
|
|
208
|
+
}
|
|
209
|
+
if (type === "objectid") {
|
|
210
|
+
const stringValue = value.toString();
|
|
211
|
+
if (!/^[a-fA-F0-9]{24}$/.test(stringValue)) {
|
|
212
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be a valid ObjectId`, 400);
|
|
213
|
+
}
|
|
214
|
+
return stringValue;
|
|
215
|
+
}
|
|
216
|
+
if (type === "option") {
|
|
217
|
+
const allowedValues = Array.isArray(definition.options) ? definition.options.map((option) => option.value) : [];
|
|
218
|
+
if (allowedValues.length > 0 && !allowedValues.includes(value)) {
|
|
219
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be one of: ${allowedValues.join(", ")}`, 400);
|
|
220
|
+
}
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
if (type === "array") {
|
|
224
|
+
if (Array.isArray(value)) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
if (typeof value === "string") {
|
|
228
|
+
try {
|
|
229
|
+
const parsed = JSON.parse(value);
|
|
230
|
+
if (Array.isArray(parsed)) {
|
|
231
|
+
return parsed;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (_error) { }
|
|
235
|
+
}
|
|
236
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an array`, 400);
|
|
237
|
+
}
|
|
238
|
+
if (type === "object") {
|
|
239
|
+
if (isPlainApiObject(value)) {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "string") {
|
|
243
|
+
try {
|
|
244
|
+
const parsed = JSON.parse(value);
|
|
245
|
+
if (isPlainApiObject(parsed)) {
|
|
246
|
+
return parsed;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (_error) { }
|
|
250
|
+
}
|
|
251
|
+
throw new ApiFunctionRuntimeError(`Parameter "${key}" must be an object`, 400);
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
exports.coerceApiFunctionParam = coerceApiFunctionParam;
|
|
256
|
+
function apiFunctionParamsToJsonSchema(definitions) {
|
|
257
|
+
const properties = {};
|
|
258
|
+
const required = [];
|
|
259
|
+
for (const key of Object.keys(definitions || {})) {
|
|
260
|
+
const definition = definitions[key] || {};
|
|
261
|
+
const schema = apiFunctionParamToJsonSchema(definition);
|
|
262
|
+
if (definition.description) {
|
|
263
|
+
schema.description = definition.description;
|
|
264
|
+
}
|
|
265
|
+
if (definition.default !== undefined) {
|
|
266
|
+
schema.default = definition.default;
|
|
267
|
+
}
|
|
268
|
+
if (definition.required && definition.default === undefined) {
|
|
269
|
+
required.push(key);
|
|
270
|
+
}
|
|
271
|
+
properties[key] = schema;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
type: "object",
|
|
275
|
+
properties,
|
|
276
|
+
required,
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
exports.apiFunctionParamsToJsonSchema = apiFunctionParamsToJsonSchema;
|
|
281
|
+
function apiFunctionParamToJsonSchema(definition) {
|
|
282
|
+
const type = (definition.type || "string").toLowerCase();
|
|
283
|
+
if (type === "number" || type === "float")
|
|
284
|
+
return { type: "number" };
|
|
285
|
+
if (type === "integer" || type === "int")
|
|
286
|
+
return { type: "integer" };
|
|
287
|
+
if (type === "boolean" || type === "bool")
|
|
288
|
+
return { type: "boolean" };
|
|
289
|
+
if (type === "objectid")
|
|
290
|
+
return { type: "string", pattern: "^[a-fA-F0-9]{24}$" };
|
|
291
|
+
if (type === "array")
|
|
292
|
+
return { type: "array", items: {} };
|
|
293
|
+
if (type === "object")
|
|
294
|
+
return { type: "object" };
|
|
295
|
+
if (type === "option" && Array.isArray(definition.options) && definition.options.length > 0) {
|
|
296
|
+
return { enum: definition.options.map((option) => option.value) };
|
|
297
|
+
}
|
|
298
|
+
return { type: "string" };
|
|
299
|
+
}
|
|
300
|
+
exports.apiFunctionParamToJsonSchema = apiFunctionParamToJsonSchema;
|
|
301
|
+
function toApiFunctionHttpResult(res, result) {
|
|
302
|
+
if (result instanceof ApiFunction_1.ApiFunctionResponse) {
|
|
303
|
+
res.status(result.status);
|
|
304
|
+
return res.send(result.getData());
|
|
305
|
+
}
|
|
306
|
+
return res.send(result);
|
|
307
|
+
}
|
|
308
|
+
exports.toApiFunctionHttpResult = toApiFunctionHttpResult;
|
|
309
|
+
function toApiFunctionPayload(result) {
|
|
310
|
+
return result instanceof ApiFunction_1.ApiFunctionResponse ? result.getData() : result;
|
|
311
|
+
}
|
|
312
|
+
exports.toApiFunctionPayload = toApiFunctionPayload;
|
|
313
|
+
function createApiFrameworkRequest(req) {
|
|
314
|
+
const parsedUrl = new URL(req.url || "/", "http://webframez.local");
|
|
315
|
+
const request = new Request_1.Request();
|
|
316
|
+
request.message = req;
|
|
317
|
+
request.headers = req.headers;
|
|
318
|
+
request.rawHeaders = req.rawHeaders;
|
|
319
|
+
request.url = req.url || "";
|
|
320
|
+
request.method = req.method || "";
|
|
321
|
+
request.socket = req.socket;
|
|
322
|
+
request.query = Object.fromEntries(parsedUrl.searchParams.entries());
|
|
323
|
+
request.queryRaw = parsedUrl.search ? parsedUrl.search.substring(1) : "";
|
|
324
|
+
request.pathname = parsedUrl.pathname;
|
|
325
|
+
request.body = {};
|
|
326
|
+
request.bodyPlain = "";
|
|
327
|
+
return request;
|
|
328
|
+
}
|
|
329
|
+
exports.createApiFrameworkRequest = createApiFrameworkRequest;
|
|
330
|
+
function assertUniqueApiFunctionKeys(scope, functions) {
|
|
331
|
+
const seen = {};
|
|
332
|
+
for (const FunctionClass of functions) {
|
|
333
|
+
const func = new FunctionClass();
|
|
334
|
+
if (!func.key || func.key.trim() === "") {
|
|
335
|
+
throw new Error(`ApiFunction in scope "${scope.key}" is missing a key`);
|
|
336
|
+
}
|
|
337
|
+
if (seen[func.key]) {
|
|
338
|
+
throw new Error(`Duplicate ApiFunction key "${func.key}" in scope "${scope.key}"`);
|
|
339
|
+
}
|
|
340
|
+
seen[func.key] = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
exports.assertUniqueApiFunctionKeys = assertUniqueApiFunctionKeys;
|
|
344
|
+
function instantiateApiScope(ScopeClass) {
|
|
345
|
+
return new ScopeClass();
|
|
346
|
+
}
|
|
347
|
+
exports.instantiateApiScope = instantiateApiScope;
|
|
348
|
+
function instantiateApiFunction(FunctionClass) {
|
|
349
|
+
return new FunctionClass();
|
|
350
|
+
}
|
|
351
|
+
exports.instantiateApiFunction = instantiateApiFunction;
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
groupMiddleware(_req: Request, _res: Response, _abort: ApiScopeMiddlewareAbort): Promise<{
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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.ApiScope = void 0;
|
|
13
|
+
class ApiScope {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.key = "unique_scope_key";
|
|
16
|
+
this.apiBasePath = "/api";
|
|
17
|
+
this.mcpEndpoint = null;
|
|
18
|
+
this.functions = [];
|
|
19
|
+
}
|
|
20
|
+
middleware(_req, _res, _abort) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
return {};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
groupMiddleware(_req, _res, _abort) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
return {};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ApiScope = ApiScope;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiScopeRegistrationEntry } from "./ApiFunctionUtils";
|
|
2
|
+
type ApiScopeRuntimeContext = {
|
|
3
|
+
kernel?: any;
|
|
4
|
+
modulesLoader?: any;
|
|
5
|
+
};
|
|
6
|
+
declare class ApiScopeRegistryFacade {
|
|
7
|
+
private context;
|
|
8
|
+
configure(context?: ApiScopeRuntimeContext): this;
|
|
9
|
+
collect(context?: ApiScopeRuntimeContext): ApiScopeRegistrationEntry[];
|
|
10
|
+
register(context?: ApiScopeRuntimeContext): this;
|
|
11
|
+
private registerHttpRoutes;
|
|
12
|
+
private registerRouteMethod;
|
|
13
|
+
private handleHttpFunction;
|
|
14
|
+
private assertUniqueApiPaths;
|
|
15
|
+
}
|
|
16
|
+
export declare const ApiScopeRegistry: ApiScopeRegistryFacade;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,130 @@
|
|
|
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.ApiScopeRegistry = void 0;
|
|
13
|
+
const Route_1 = require("../Router/Route");
|
|
14
|
+
const ApiFunctionUtils_1 = require("./ApiFunctionUtils");
|
|
15
|
+
function getModuleInstances(modulesLoader) {
|
|
16
|
+
if (!modulesLoader) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
if (typeof modulesLoader.getLoadedModuleInstances === "function") {
|
|
20
|
+
return modulesLoader.getLoadedModuleInstances();
|
|
21
|
+
}
|
|
22
|
+
if (typeof modulesLoader.getLoadedModules === "function") {
|
|
23
|
+
const modules = modulesLoader.getLoadedModules();
|
|
24
|
+
return Object.keys(modules || {}).map((key) => modules[key]);
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
class ApiScopeRegistryFacade {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.context = {};
|
|
31
|
+
}
|
|
32
|
+
configure(context) {
|
|
33
|
+
this.context = Object.assign(Object.assign({}, (this.context || {})), (context || {}));
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
collect(context) {
|
|
37
|
+
const runtimeContext = Object.assign(Object.assign({}, (this.context || {})), (context || {}));
|
|
38
|
+
const scopes = [];
|
|
39
|
+
const kernelScopes = runtimeContext.kernel && Array.isArray(runtimeContext.kernel.apiScopes) ? runtimeContext.kernel.apiScopes : [];
|
|
40
|
+
scopes.push(...kernelScopes);
|
|
41
|
+
for (const mod of getModuleInstances(runtimeContext.modulesLoader)) {
|
|
42
|
+
const moduleScopes = Array.isArray(mod && mod.apiScopes)
|
|
43
|
+
? mod.apiScopes
|
|
44
|
+
: Array.isArray(mod && mod.constructor && mod.constructor.apiScopes)
|
|
45
|
+
? mod.constructor.apiScopes
|
|
46
|
+
: [];
|
|
47
|
+
scopes.push(...moduleScopes);
|
|
48
|
+
}
|
|
49
|
+
return (0, ApiFunctionUtils_1.collectApiScopeRegistrations)(scopes);
|
|
50
|
+
}
|
|
51
|
+
register(context) {
|
|
52
|
+
this.configure(context);
|
|
53
|
+
const scopes = this.collect(context);
|
|
54
|
+
this.assertUniqueApiPaths(scopes);
|
|
55
|
+
for (const registration of scopes) {
|
|
56
|
+
this.registerHttpRoutes(registration);
|
|
57
|
+
}
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
registerHttpRoutes(registration) {
|
|
61
|
+
const scope = (0, ApiFunctionUtils_1.instantiateApiScope)(registration.scopeClass);
|
|
62
|
+
if (!scope.apiBasePath) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const FunctionClass of (0, ApiFunctionUtils_1.getApiFunctionClasses)(scope)) {
|
|
66
|
+
const func = (0, ApiFunctionUtils_1.instantiateApiFunction)(FunctionClass);
|
|
67
|
+
if (func.requestMethod === null) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const path = (0, ApiFunctionUtils_1.joinApiPath)(scope.apiBasePath, func.key);
|
|
71
|
+
const handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
return yield this.handleHttpFunction(registration, FunctionClass, req, res);
|
|
73
|
+
});
|
|
74
|
+
this.registerRouteMethod(func.requestMethod, path, handler);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
registerRouteMethod(method, path, handler) {
|
|
78
|
+
if (!method) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const methodName = method.toLowerCase();
|
|
82
|
+
const routeMethod = Route_1.Route[methodName];
|
|
83
|
+
if (!routeMethod || typeof routeMethod !== "function") {
|
|
84
|
+
throw new Error(`ApiFunction request method "${method}" is not supported by Route`);
|
|
85
|
+
}
|
|
86
|
+
routeMethod.bind(Route_1.Route)(path, handler);
|
|
87
|
+
}
|
|
88
|
+
handleHttpFunction(registration, FunctionClass, req, res) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
const func = (0, ApiFunctionUtils_1.instantiateApiFunction)(FunctionClass);
|
|
91
|
+
try {
|
|
92
|
+
const context = yield (0, ApiFunctionUtils_1.runApiScopeRegistrationMiddleware)(registration, req, res);
|
|
93
|
+
const paramsSource = (func.requestMethod || req.method).toUpperCase() === "GET" ? req.query : req.body;
|
|
94
|
+
const params = (0, ApiFunctionUtils_1.validateApiFunctionParams)(func.params || {}, paramsSource || {});
|
|
95
|
+
const result = yield func.handle({ context, params, request: req, response: res });
|
|
96
|
+
return (0, ApiFunctionUtils_1.toApiFunctionHttpResult)(res, result);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const statusCode = error && error.statusCode && !isNaN(parseInt(error.statusCode)) ? parseInt(error.statusCode) : 500;
|
|
100
|
+
return res.status(statusCode).send({
|
|
101
|
+
status: "error",
|
|
102
|
+
message: (0, ApiFunctionUtils_1.getApiErrorMessage)(error),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
assertUniqueApiPaths(scopes) {
|
|
108
|
+
const seen = {};
|
|
109
|
+
for (const registration of scopes) {
|
|
110
|
+
const scope = (0, ApiFunctionUtils_1.instantiateApiScope)(registration.scopeClass);
|
|
111
|
+
if (!scope.apiBasePath) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const FunctionClass of (0, ApiFunctionUtils_1.getApiFunctionClasses)(scope)) {
|
|
115
|
+
const func = (0, ApiFunctionUtils_1.instantiateApiFunction)(FunctionClass);
|
|
116
|
+
if (func.requestMethod === null) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const method = func.requestMethod || "GET";
|
|
120
|
+
const path = (0, ApiFunctionUtils_1.joinApiPath)(scope.apiBasePath, func.key);
|
|
121
|
+
const lookupKey = `${method}:${path}`;
|
|
122
|
+
if (seen[lookupKey]) {
|
|
123
|
+
throw new Error(`Duplicate ApiFunction route "${lookupKey}"`);
|
|
124
|
+
}
|
|
125
|
+
seen[lookupKey] = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.ApiScopeRegistry = new ApiScopeRegistryFacade();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Request } from "../Router/Request";
|
|
2
|
+
import { Response } from "../Router/Response";
|
|
3
|
+
import { ApiScopeClass, ApiScopeMiddlewareAbort } from "./ApiTypes";
|
|
4
|
+
export declare class ApiScopesGroup {
|
|
5
|
+
key: string;
|
|
6
|
+
apiScopes: Array<ApiScopeClass> | (() => Array<ApiScopeClass>);
|
|
7
|
+
middleware(_req: Request, _res: Response, _abort: ApiScopeMiddlewareAbort): Promise<{
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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.ApiScopesGroup = void 0;
|
|
13
|
+
class ApiScopesGroup {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.key = "unique_api_scopes_group_key";
|
|
16
|
+
this.apiScopes = [];
|
|
17
|
+
}
|
|
18
|
+
middleware(_req, _res, _abort) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
return {};
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ApiScopesGroup = ApiScopesGroup;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Request } from "../Router/Request";
|
|
2
|
+
import { Response } from "../Router/Response";
|
|
3
|
+
export type ApiFunctionRequestMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | null;
|
|
4
|
+
export type ApiFunctionResponseType = "response" | "confirm" | "ask";
|
|
5
|
+
export type ApiScopeFunctionContext = {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
export type ApiFunctionParamOption = {
|
|
9
|
+
value: any;
|
|
10
|
+
label?: string;
|
|
11
|
+
};
|
|
12
|
+
export type ApiFunctionParamDefinition = {
|
|
13
|
+
type?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
default?: any;
|
|
16
|
+
options?: ApiFunctionParamOption[];
|
|
17
|
+
description?: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
};
|
|
20
|
+
export type ApiFunctionParamsDefinition = {
|
|
21
|
+
[key: string]: ApiFunctionParamDefinition;
|
|
22
|
+
};
|
|
23
|
+
export type ApiFunctionClass = new (...args: any[]) => any;
|
|
24
|
+
export type ApiScopeClass = new (...args: any[]) => any;
|
|
25
|
+
export type ApiScopesGroupClass = new (...args: any[]) => any;
|
|
26
|
+
export type ApiScopeRegistrationClass = ApiScopeClass | ApiScopesGroupClass;
|
|
27
|
+
export type ApiScopeMiddlewareAbort = (message?: any, status?: number) => never;
|
|
28
|
+
export type ApiFunctionRequest = {
|
|
29
|
+
context: ApiScopeFunctionContext;
|
|
30
|
+
params: {
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
};
|
|
33
|
+
request?: Request;
|
|
34
|
+
response?: Response;
|
|
35
|
+
};
|
package/dist/BaseKernelWeb.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Controller } from "./Controller/Controller";
|
|
2
2
|
import { KernelAiRegistrations } from "./Ai/AiTypes";
|
|
3
3
|
import { KernelMcpRegistrations } from "./Mcp/McpTypes";
|
|
4
|
+
import { ApiScopeRegistrationClass } from "./Api/ApiTypes";
|
|
4
5
|
export declare class BaseKernelWeb {
|
|
5
6
|
static controller: {
|
|
6
7
|
[key: string]: Controller;
|
|
@@ -8,6 +9,7 @@ export declare class BaseKernelWeb {
|
|
|
8
9
|
static middleware: {
|
|
9
10
|
[key: string]: any;
|
|
10
11
|
};
|
|
12
|
+
static apiScopes: ApiScopeRegistrationClass[];
|
|
11
13
|
static mcp?: KernelMcpRegistrations;
|
|
12
14
|
static ai?: KernelAiRegistrations;
|
|
13
15
|
}
|
package/dist/BaseKernelWeb.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { BaseStorageDriver, StorageDeleteOptions, StorageMkdirOptions, StorageReadDirOptions } from "./BaseStorageDriver";
|
|
3
4
|
import { Request } from "../../Router/Request";
|
|
4
5
|
export declare class LocalStorageDriver implements BaseStorageDriver {
|
package/dist/Mcp/McpTypes.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KernelAiRegistrations } from "../Ai/AiTypes";
|
|
2
|
+
import { ApiScopeRegistrationClass } from "../Api/ApiTypes";
|
|
2
3
|
import { KernelMcpRegistrations } from "../Mcp/McpTypes";
|
|
3
4
|
export declare class ModuleProvider {
|
|
4
5
|
static key: string;
|
|
@@ -8,6 +9,7 @@ export declare class ModuleProvider {
|
|
|
8
9
|
middleware: {
|
|
9
10
|
[key: string]: any;
|
|
10
11
|
};
|
|
12
|
+
apiScopes: ApiScopeRegistrationClass[];
|
|
11
13
|
commands: any[];
|
|
12
14
|
mcp?: KernelMcpRegistrations;
|
|
13
15
|
ai?: KernelAiRegistrations;
|
package/dist/Router/Route.d.ts
CHANGED
|
@@ -37,6 +37,14 @@ export declare class RouteFacade {
|
|
|
37
37
|
* @param options
|
|
38
38
|
*/
|
|
39
39
|
put(path: string, component: any, options?: RouteRegistrationOptions): void;
|
|
40
|
+
/**
|
|
41
|
+
* Register a PATCH-Method
|
|
42
|
+
*
|
|
43
|
+
* @param path
|
|
44
|
+
* @param component
|
|
45
|
+
* @param options
|
|
46
|
+
*/
|
|
47
|
+
patch(path: string, component: any, options?: RouteRegistrationOptions): void;
|
|
40
48
|
/**
|
|
41
49
|
* Register a DELETE-Method
|
|
42
50
|
*
|
package/dist/Router/Route.js
CHANGED
|
@@ -79,6 +79,16 @@ class RouteFacade {
|
|
|
79
79
|
put(path, component, options) {
|
|
80
80
|
this.registerWithGroupContext("PUT", path, component, options);
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Register a PATCH-Method
|
|
84
|
+
*
|
|
85
|
+
* @param path
|
|
86
|
+
* @param component
|
|
87
|
+
* @param options
|
|
88
|
+
*/
|
|
89
|
+
patch(path, component, options) {
|
|
90
|
+
this.registerWithGroupContext("PATCH", path, component, options);
|
|
91
|
+
}
|
|
82
92
|
/**
|
|
83
93
|
* Register a DELETE-Method
|
|
84
94
|
*
|
package/dist/Router/Router.d.ts
CHANGED
package/dist/Router/Router.js
CHANGED
|
@@ -47,6 +47,10 @@ class RouterFacade {
|
|
|
47
47
|
* PUT Route Store
|
|
48
48
|
*/
|
|
49
49
|
this.routesPUT = {};
|
|
50
|
+
/**
|
|
51
|
+
* PATCH Route Store
|
|
52
|
+
*/
|
|
53
|
+
this.routesPATCH = {};
|
|
50
54
|
/**
|
|
51
55
|
* DELETE Route Store
|
|
52
56
|
*/
|
|
@@ -66,6 +70,7 @@ class RouterFacade {
|
|
|
66
70
|
this.routesGET = {};
|
|
67
71
|
this.routesPOST = {};
|
|
68
72
|
this.routesPUT = {};
|
|
73
|
+
this.routesPATCH = {};
|
|
69
74
|
this.routesDELETE = {};
|
|
70
75
|
// Load routes
|
|
71
76
|
if (options.routesFunction) {
|
|
@@ -136,6 +141,12 @@ class RouterFacade {
|
|
|
136
141
|
}
|
|
137
142
|
this.routesPUT[path].push(routeConf);
|
|
138
143
|
}
|
|
144
|
+
else if (type === "PATCH") {
|
|
145
|
+
if (!this.routesPATCH[path]) {
|
|
146
|
+
this.routesPATCH[path] = [];
|
|
147
|
+
}
|
|
148
|
+
this.routesPATCH[path].push(routeConf);
|
|
149
|
+
}
|
|
139
150
|
else if (type === "DELETE") {
|
|
140
151
|
if (!this.routesDELETE[path]) {
|
|
141
152
|
this.routesDELETE[path] = [];
|
|
@@ -167,6 +178,9 @@ class RouterFacade {
|
|
|
167
178
|
else if (req.method === "PUT") {
|
|
168
179
|
return this.dissolveRoute(this.routesPUT, cleanUrl, req);
|
|
169
180
|
}
|
|
181
|
+
else if (req.method === "PATCH") {
|
|
182
|
+
return this.dissolveRoute(this.routesPATCH, cleanUrl, req);
|
|
183
|
+
}
|
|
170
184
|
else if (req.method === "DELETE") {
|
|
171
185
|
return this.dissolveRoute(this.routesDELETE, cleanUrl, req);
|
|
172
186
|
}
|
|
@@ -180,6 +194,9 @@ class RouterFacade {
|
|
|
180
194
|
else if (req.headers["access-control-request-method"] === "PUT") {
|
|
181
195
|
return this.dissolveRoute(this.routesPUT, cleanUrl, req);
|
|
182
196
|
}
|
|
197
|
+
else if (req.headers["access-control-request-method"] === "PATCH") {
|
|
198
|
+
return this.dissolveRoute(this.routesPATCH, cleanUrl, req);
|
|
199
|
+
}
|
|
183
200
|
else if (req.headers["access-control-request-method"] === "DELETE") {
|
|
184
201
|
return this.dissolveRoute(this.routesDELETE, cleanUrl, req);
|
|
185
202
|
}
|
package/dist/WebApplication.js
CHANGED
|
@@ -23,6 +23,7 @@ const ModulesLoader_1 = require("./Modules/ModulesLoader");
|
|
|
23
23
|
const ErrorHandler_1 = require("./ErrorHandling/ErrorHandler");
|
|
24
24
|
const WebframezHooks_1 = require("./Hooks/WebframezHooks");
|
|
25
25
|
const HttpKernelHandlerRegistry_1 = require("./Http/HttpKernelHandlerRegistry");
|
|
26
|
+
const ApiScopeRegistry_1 = require("./Api/ApiScopeRegistry");
|
|
26
27
|
// import { SigNozTelemetry } from "./Telemetry/SigNozTelemetry";
|
|
27
28
|
class WebApplication {
|
|
28
29
|
constructor() {
|
|
@@ -72,6 +73,10 @@ class WebApplication {
|
|
|
72
73
|
tempDir: options && options.tempDir ? options.tempDir : null,
|
|
73
74
|
});
|
|
74
75
|
this.modulesLoader.initRoutes();
|
|
76
|
+
ApiScopeRegistry_1.ApiScopeRegistry.register({
|
|
77
|
+
kernel: options && options.kernel ? options.kernel : null,
|
|
78
|
+
modulesLoader: this.modulesLoader,
|
|
79
|
+
});
|
|
75
80
|
if (options && options.datatables) {
|
|
76
81
|
DatatableRegistry_1.DatatableRegistry.registerMany(options.datatables);
|
|
77
82
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export * from "./Commands/ConsoleOutputHelper";
|
|
|
40
40
|
export * from "./Modules/ModuleProvider";
|
|
41
41
|
export * from "./ErrorHandling/ErrorHandler";
|
|
42
42
|
export * from "./Hooks/WebframezHooks";
|
|
43
|
+
export * from "./Api/ApiTypes";
|
|
44
|
+
export * from "./Api/ApiFunction";
|
|
45
|
+
export * from "./Api/ApiScope";
|
|
46
|
+
export * from "./Api/ApiScopesGroup";
|
|
47
|
+
export * from "./Api/ApiFunctionUtils";
|
|
48
|
+
export * from "./Api/ApiScopeRegistry";
|
|
43
49
|
export * from "./Ai/AiTypes";
|
|
44
50
|
export * from "./Mcp/McpTypes";
|
|
45
51
|
export * from "./Http/HttpKernelHandlerRegistry";
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,12 @@ __exportStar(require("./Commands/ConsoleOutputHelper"), exports);
|
|
|
56
56
|
__exportStar(require("./Modules/ModuleProvider"), exports);
|
|
57
57
|
__exportStar(require("./ErrorHandling/ErrorHandler"), exports);
|
|
58
58
|
__exportStar(require("./Hooks/WebframezHooks"), exports);
|
|
59
|
+
__exportStar(require("./Api/ApiTypes"), exports);
|
|
60
|
+
__exportStar(require("./Api/ApiFunction"), exports);
|
|
61
|
+
__exportStar(require("./Api/ApiScope"), exports);
|
|
62
|
+
__exportStar(require("./Api/ApiScopesGroup"), exports);
|
|
63
|
+
__exportStar(require("./Api/ApiFunctionUtils"), exports);
|
|
64
|
+
__exportStar(require("./Api/ApiScopeRegistry"), exports);
|
|
59
65
|
__exportStar(require("./Ai/AiTypes"), exports);
|
|
60
66
|
__exportStar(require("./Mcp/McpTypes"), exports);
|
|
61
67
|
__exportStar(require("./Http/HttpKernelHandlerRegistry"), exports);
|
package/package.json
CHANGED