@webtypen/webframez-core 0.3.29 → 0.3.30

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 CHANGED
@@ -1,207 +1,422 @@
1
- # webtypen webframez Docs
1
+ # @webtypen/webframez-core
2
2
 
3
- ## Controller
3
+ TypeScript-first backend framework core for Node.js.
4
4
 
5
- ### Register a controller
5
+ This README reflects the current API in this repository and focuses on:
6
+ - Routing
7
+ - Datatables
8
+ - DataBuilder
9
+ - Console Commands
10
+ - Queue and Jobs
6
11
 
7
- A new controller is registered via the `controller` attribute in `app/Kernel.ts`. Just add a new entry for the controller to the object. The key of the object is the unique name of the controller, this is used again when connecting routes.
12
+ ## Requirements
8
13
 
9
- The controllers are initialized once when the web service is started. When calling a route, a single controller instance is created per request and thrown away after the request finishes.
14
+ - Node.js `>= 20` (recommended)
15
+ - TypeScript for development
10
16
 
11
- ## Routing
17
+ ## Installation
12
18
 
13
- ### Register a route
19
+ ```bash
20
+ npm i @webtypen/webframez-core
21
+ ```
14
22
 
15
- Routes are managed via the app/routes.ts. Routes can be registered and configured with the Route Facade. Routes can be associated strictly with a request method (GET, POST, PUT, DELETE) or all request methods (ANY):
23
+ ## Basic Web Setup
16
24
 
17
25
  ```ts
18
- Route.get("/", "TestController@test");
19
- Route.post("/test2", "TestController@test");
20
- Route.put("/test2", "TestController@test");
21
- Route.delete("/test2", "TestController@test");
22
- Route.any("/test2", "TestController@test");
26
+ import { BaseKernelWeb, Request, Response, Route, WebApplication } from "@webtypen/webframez-core";
27
+
28
+ class Kernel extends BaseKernelWeb {
29
+ static controller = {
30
+ TestController: class {
31
+ async index(req: Request, res: Response) {
32
+ return res.send({ status: "ok" });
33
+ }
34
+ }
35
+ };
36
+
37
+ static middleware = {
38
+ auth: async (next: Function, reject: Function, req: Request, res: Response) => {
39
+ const isAllowed = true;
40
+ if (!isAllowed) {
41
+ return reject(new Error("Unauthorized"));
42
+ }
43
+ next(true);
44
+ }
45
+ };
46
+ }
47
+
48
+ const app = new WebApplication();
49
+ app.boot({
50
+ kernel: Kernel,
51
+ port: 3000,
52
+ basename: null,
53
+ routesFunction: () => {
54
+ Route.get("/", "TestController@index");
55
+ }
56
+ });
23
57
  ```
24
58
 
25
- The URL path is specified in the first parameter. The second parameter connects the route to a controller method. A previously registered controller can be connected using the syntax `[CONTROLLER]@[METHODE]`.
59
+ `WebApplication.boot(...)` supports:
60
+ - `kernel`
61
+ - `port`
62
+ - `basename`
63
+ - `routesFunction`
64
+ - `modules`
65
+ - `config`
66
+ - `datatables`
67
+ - `jobs`
68
+ - `mode`
69
+ - `onBoot`
26
70
 
27
- Alternatively, a function can be specified directly or a previously imported one can be used:
71
+ ## Routing
72
+
73
+ ### Register Routes
28
74
 
29
75
  ```ts
30
- // Directly
31
- Route.get("/", (req: Request, res: Response) => res.send({ status: "success" }));
76
+ Route.get("/", "HomeController@index");
77
+ Route.post("/login", "AuthController@login");
78
+ Route.put("/users/:id", "UserController@update");
79
+ Route.delete("/users/:id", "UserController@delete");
80
+ ```
81
+
82
+ You can also register inline handlers:
32
83
 
33
- // Imported
34
- import { myFunction } from "./myfunction";
35
- Route.get("/", myFunction);
84
+ ```ts
85
+ Route.get("/health", (req: Request, res: Response) => {
86
+ return res.send({ status: "ok" });
87
+ });
36
88
  ```
37
89
 
38
- ### Register a middleware
90
+ ### Path Parameters and Wildcards
39
91
 
40
- Middleware functions are also registered in `app/Kernel.ts.` A middleware function can be associated with one or more routes. When calling these routes, the middleware function is called before executing the route logic. In this function, the request can be continued or aborted, for example, authorization rules can be implemented and easily reused for multiple routes.
92
+ Supported path patterns:
93
+ - Required parameter: `/:id`
94
+ - Optional parameter: `/:id?`
95
+ - Single wildcard: `/*`
96
+ - Catch-all wildcard: `/**`
41
97
 
42
- Middleware functions are usually created under `app/Middleware`. A middleware function is called with the parameters (next, abort, req and res):
98
+ Matched values are available in `req.params`.
43
99
 
44
- - `next`: Function that signals when called that the request may be continued
45
- - `abort`: Function that aborts the request when called. An HTTP status code can be specified in the first parameter and a request body (usually text or JSON) in the second.
46
- - `req`: The Request-Object
47
- - `res`: The Response-Object
100
+ ### Route Groups
48
101
 
49
- Alternatively, middleware functions can also be defined directly in Kernel.ts:
102
+ ```ts
103
+ Route.group({ prefix: "/admin", middleware: ["auth"] }, () => {
104
+ Route.get("/dashboard", "AdminController@dashboard");
105
+
106
+ Route.group({ prefix: "/users" }, () => {
107
+ Route.get("/:id", "AdminUserController@details");
108
+ });
109
+ });
110
+ ```
111
+
112
+ ### Route Middleware
113
+
114
+ Attach middleware by key via route options:
50
115
 
51
116
  ```ts
52
- static middleware: { [key: string]: any } = {
53
- auth: async (next: any, abort: any, req: Request, res: Response) => {
54
- console.log("auth-middleware");
55
- next();
56
- },
57
- };
117
+ Route.get("/me", "AccountController@me", { middleware: ["auth"] });
58
118
  ```
59
119
 
60
- A route can be connected to several middleware functions, which are called one after the other but before executing the route logic.
120
+ Middleware signature:
61
121
 
62
- ### Connect a route with a middleware
122
+ ```ts
123
+ (next, reject, req, res) => {
124
+ // allow:
125
+ next(true);
63
126
 
64
- When registering a route, a configuration object can be passed in the third parameter. This can contain an array with middleware keys in the `middleware` parameter. The middlewares specified here are executed prior to executing the route logic when invoking this route. If a specified middleware aborts the request, it will not be executed:
127
+ // abort with error:
128
+ // reject(new Error("Forbidden"));
129
+ }
130
+ ```
131
+
132
+ ### Extend the Route Facade
133
+
134
+ You can add custom route registration helpers:
65
135
 
66
136
  ```ts
67
- Route.get("/auth-data", "AuthController@authData", { middleware: ["auth"] });
137
+ Route.extend("jsonGet", (route) => {
138
+ return (path: string, component: any, options?: any) => {
139
+ route.get(path, async (req: Request, res: Response) => {
140
+ res.header("Content-Type", "application/json");
141
+ return component(req, res);
142
+ }, options);
143
+ };
144
+ });
145
+
146
+ (Route as any).jsonGet("/x", (req: Request, res: Response) => res.send({ ok: true }));
68
147
  ```
69
148
 
70
- ### Group routes
149
+ ## Request and Response
150
+
151
+ ### Request
71
152
 
72
- With the grouping of routes, the code of the application can be significantly reduced and kept clear. Here's how the following configurations can be used with a multiple route definition:
153
+ `Request` includes:
154
+ - `method`, `url`, `headers`, `rawHeaders`
155
+ - `body`, `bodyPlain`
156
+ - `query`, `queryRaw`
157
+ - `params`
158
+ - `files`
159
+ - `message` (native `IncomingMessage`)
73
160
 
74
- - `prefix`: A route path prefix used for all routes in this group:
161
+ ### Response
162
+
163
+ Common helpers:
75
164
 
76
165
  ```ts
77
- Route.group({ prefix: "/admin", () => {
78
- Route.get("/dashboard", "AdminDashboardController@data");
79
- // Path for this route would be: /admin/dashboard
80
- }});
166
+ res.status(201);
167
+ res.header("X-Test", "1");
168
+ res.send({ status: "success" });
81
169
  ```
82
170
 
83
- - `middleware`: An array of middleware-keys used for all routes in this group:
171
+ Also available:
172
+ - `sendCsv(...)`
173
+ - `download(filepath, options?)`
174
+ - `stream(req, filepath, filename, mimeType)`
175
+ - `registerEvent("after", fn)`
176
+
177
+ ## Datatables
178
+
179
+ Use `Datatable` + `DatatableRegistry` + `DatatableController`.
180
+
181
+ ### Define a Datatable
84
182
 
85
183
  ```ts
86
- Route.group({ middleware: ["auth"], () => {
87
- // Route with middleware auth
88
- Route.get("/auth-data", "AuthController@authData");
184
+ import { Datatable, Request } from "@webtypen/webframez-core";
185
+
186
+ export class UsersTable extends Datatable {
187
+ collection = "users";
188
+ perPage = 25;
89
189
 
90
- // Route with middleware auth and admin
91
- Route.get("/dashboard", "AdminDashboardController@data", { middleware: ["admin"] });
92
- }});
190
+ columns = {
191
+ name: { label: "Name" },
192
+ email: { label: "E-Mail" }
193
+ };
194
+
195
+ filter = {
196
+ name: { type: "text", mapping: "name" }
197
+ };
198
+
199
+ aggregation = async (req: Request) => {
200
+ return [{ $sort: { created_at: -1 } }];
201
+ };
202
+ }
203
+ ```
204
+
205
+ ### Register Datatables
206
+
207
+ ```ts
208
+ app.boot({
209
+ // ...
210
+ datatables: {
211
+ users: UsersTable
212
+ }
213
+ });
93
214
  ```
94
215
 
95
- The configuration parameters of a route group can of course be combined with each other.
216
+ ### Datatable Endpoints
96
217
 
97
- Nesting of the route groups is also possible:
218
+ Use `DatatableController` in your routes:
98
219
 
99
220
  ```ts
100
- Route.group({ prefix: "/test" }, () => {
101
- Route.get("/", "TestController@test"); // Path: /test/
102
- Route.get("/test2", "TestController@test"); // Path: /test2/
221
+ import { DatatableController, Route } from "@webtypen/webframez-core";
103
222
 
104
- Route.group({ middleware: ["auth"], prefix: "/auth" }, () => {
105
- Route.get("/data", "AuthController@data"); // Path: /test/auth/data; Middleware: auth
106
- });
107
- });
223
+ Route.post("/api/datatable", "DatatableController@restApi");
224
+ Route.post("/api/datatable/export", "DatatableController@tableExport");
108
225
  ```
109
226
 
110
- ## Database
227
+ `restApi` supports:
228
+ - init requests (`init_request`)
229
+ - paginated data
230
+ - selectable functions (`apiFunction`)
111
231
 
112
- ### Connect
232
+ `tableExport` uses your table `exports` definition.
113
233
 
114
- The application can handle multiple database connections, but one is defined as the default connection and is automatically used by the system if models and database requests are not explicitly executed over another connection.
234
+ ## DataBuilder
115
235
 
116
- The connections are configured in the `config/database.ts` file. Each connection is given a unique key in the `connections` object and uses a driver. The following drivers are currently available:
236
+ Use `DataBuilder` + `DataBuilderController` for schema-driven CRUD flows.
117
237
 
118
- - MongoDB Driver
238
+ ### Define and Register Types
119
239
 
120
- Depending on the driver, the database connection must be configured with different parameters.
240
+ ```ts
241
+ import { DataBuilder } from "@webtypen/webframez-core";
242
+
243
+ const builder = new DataBuilder();
244
+
245
+ builder.registerType({
246
+ key: "user",
247
+ singular: "User",
248
+ plural: "Users",
249
+ schema: {
250
+ version: "1.0.0",
251
+ collection: "users",
252
+ fields: {
253
+ email: { type: "text", required: true, unique: { match: {} } },
254
+ age: { type: "integer" }
255
+ }
256
+ }
257
+ });
258
+ ```
259
+
260
+ You can also use:
261
+ - `registerModelType(...)`
262
+ - `registerFieldType(...)`
121
263
 
122
- **MongoDB Connection example:**
264
+ ### Expose the REST API
123
265
 
124
266
  ```ts
125
- export default {
126
- defaultConnection: "default",
127
- connections: {
128
- ["default"]: {
129
- driver: "mongodb",
130
- url: "mongodb://...",
131
- },
132
- },
133
- };
267
+ import { DataBuilderController, Route } from "@webtypen/webframez-core";
268
+
269
+ class MyDataBuilderController extends DataBuilderController {
270
+ constructor() {
271
+ super(builder);
272
+ }
273
+ }
274
+
275
+ // Register controller in kernel, then:
276
+ Route.post("/api/builder", "MyDataBuilderController@restApi");
134
277
  ```
135
278
 
136
- ### Model
137
-
138
- Models are usually placed under `app/Models`. Each model class inherits from `Model` and must store the table / collection name, in which the models data sets are stored, in the `__table` attribute.
139
-
140
- ```ts
141
- import { Model, hasMany, QueryBuilder } from "@webtypen/webframez-core";
142
- import { File } from "./File";
143
- import { Session } from "./Session";
144
- import { CustomerGroups } from "./CustomerGroups";
145
-
146
- export class User extends Model {
147
- __table = "users";
148
-
149
- /**
150
- * @param File Dependency Model
151
- * @param foreignKey
152
- * @param localKey (optional)
153
- * @param queryBuilderFunction (optional) customize the query
154
- **/
155
- @hasOne(() => File, "_user_avatar", "_id", (query: QueryBuilder) => {
156
- query.where("status", "=", "uploaded");
157
- })
158
- avatar!: () => File;
159
-
160
- /**
161
- * @param Session Dependency Model
162
- * @param foreignKey
163
- * @param localKey (optional)
164
- * @param queryBuilderFunction (optional) customize the query
165
- **/
166
- @hasMany(() => Session, "_user")
167
- sessions!: () => Session[];
168
-
169
- /**
170
- * @param CustomerGroups Dependency Model
171
- * @param foreignKey
172
- * @param localKey (optional)
173
- * @param queryBuilderFunction (optional) customize the query
174
- **/
175
- @hasManyArray(() => CustomerGroups, "_groups")
176
- groups!: () => CustomerGroups[];
279
+ `__builder_rest_api` actions:
280
+ - `type`
281
+ - `details`
282
+ - `details-newdata`
283
+ - `save`
284
+ - `delete`
285
+ - `api-autocomplete`
286
+
287
+ ## Console Commands
288
+
289
+ Use `ConsoleApplication` with `BaseKernelConsole`.
290
+
291
+ ```ts
292
+ import { BaseKernelConsole, ConsoleApplication, ConsoleCommand } from "@webtypen/webframez-core";
293
+
294
+ class HelloCommand extends ConsoleCommand {
295
+ static signature = "hello";
296
+ static description = "Print a greeting";
297
+
298
+ async handle() {
299
+ this.success("Hello world");
300
+ }
301
+ }
302
+
303
+ class KernelConsole extends BaseKernelConsole {
304
+ static commands = [HelloCommand];
177
305
  }
306
+
307
+ const app = new ConsoleApplication();
308
+ app.boot({
309
+ kernel: KernelConsole,
310
+ config: {}
311
+ });
178
312
  ```
179
313
 
180
- ### Load a model
314
+ `ConsoleCommand` helpers include:
315
+ - `getArguments()`, `getOptions()`, `getOption(...)`
316
+ - `ask(...)`
317
+ - `write`, `writeln`, `info`, `warning`, `success`, `error`
318
+ - progress helpers (`progress`, `progressIncrement`, `progressFinish`)
319
+
320
+ ## Queue and Jobs
321
+
322
+ Queue workers run through console commands and use `queue_jobs` storage.
323
+
324
+ ### Create a Job
181
325
 
182
326
  ```ts
183
- import { User } from "../Models/User";
327
+ import { BaseQueueJob } from "@webtypen/webframez-core";
328
+
329
+ export class SendMailJob extends BaseQueueJob {
330
+ async handle(job: any) {
331
+ this.log("Sending mail", job.payload);
332
+ // do work...
333
+
334
+ // optional delayed re-run:
335
+ // return this.executeAgain(5, "minutes");
336
+ }
337
+ }
338
+ ```
184
339
 
185
- const users = await User.where("is_active", "=", true).get();
186
- const testUser = await User.where("email", "=", "test@test.de").where("is_active", "=", true).first();
340
+ Create a new queue entry:
341
+
342
+ ```ts
343
+ await SendMailJob.create({
344
+ payload: { userId: "..." },
345
+ priority: 5,
346
+ worker: null
347
+ });
187
348
  ```
188
349
 
189
- #### Dependency Injection
350
+ ### Register Jobs
190
351
 
191
352
  ```ts
192
- const sessions = await testUser.session();
193
- const avatarUrl = await testUser.avatar()?.url;
194
- const customerGroups = await testUser.groups();
353
+ app.boot({
354
+ // ConsoleApplication or WebApplication
355
+ jobs: [SendMailJob]
356
+ });
195
357
  ```
196
358
 
197
- ##### Disable cache / force dependency-reload:
359
+ ### Queue Config
360
+
361
+ Configure workers in `config.queue`:
198
362
 
199
363
  ```ts
200
- const refreshedData = await testUser.session({ force: true });
364
+ export default {
365
+ workers: {
366
+ default: {
367
+ is_active: true,
368
+ jobclasses: ["SendMailJob"],
369
+ automation: [
370
+ {
371
+ jobclass: "SendMailJob",
372
+ executions: [
373
+ ["every_x_mins", 15],
374
+ ["daily", "08:00"],
375
+ ["every_hour", 5],
376
+ ["mondays", "09:30"]
377
+ ]
378
+ }
379
+ ]
380
+ }
381
+ }
382
+ };
201
383
  ```
202
384
 
203
- ##### Get dependency-query:
385
+ ### Built-in Queue Commands
386
+
387
+ - `queue:start`
388
+ - `queue:status`
389
+ - `queue:stop`
390
+ - `queue:log`
391
+ - `queue:worker`
392
+ - `queue:worker:autorestart`
393
+
394
+ Additional built-in command:
395
+ - `build`
396
+
397
+ ## Modules
398
+
399
+ You can load module providers via `modules` in `WebApplication.boot(...)`.
400
+
401
+ A module provider can:
402
+ - register controllers/middleware
403
+ - define `boot()`
404
+ - define `routes()`
405
+
406
+ ## Lambda Mode
407
+
408
+ `LambdaApplication` uses the same router in AWS Lambda mode:
204
409
 
205
410
  ```ts
206
- const oldSessions = await testUser.session({ query: true }).where("date", "<", moment().subtract(30, "days").format("YYYY-MM-DD")).get();
411
+ import { LambdaApplication } from "@webtypen/webframez-core";
412
+
413
+ const app = new LambdaApplication();
414
+ export const handler = (event: any, context: any) => {
415
+ return app.boot(event, context, {
416
+ kernel: Kernel,
417
+ routesFunction: () => {
418
+ Route.get("/status", (req, res) => res.send({ ok: true }));
419
+ }
420
+ });
421
+ };
207
422
  ```
@@ -1,6 +1,16 @@
1
+ type RouteRegistrationOptions = {
2
+ [key: string]: any;
3
+ };
4
+ type RouteExtensionFactory = (route: RouteFacade) => (...args: any[]) => any;
1
5
  export declare class RouteFacade {
2
6
  tempGroupPrefix: string | null;
3
7
  tempGroupMiddleware: string[] | null;
8
+ private registerWithGroupContext;
9
+ /**
10
+ * Register a custom route-method on the Route facade.
11
+ * Allows optional packages to add framework-specific route helpers.
12
+ */
13
+ extend(name: string, factory: RouteExtensionFactory): this;
4
14
  /**
5
15
  * Register a GET-Method
6
16
  *
@@ -8,9 +18,7 @@ export declare class RouteFacade {
8
18
  * @param component
9
19
  * @param options
10
20
  */
11
- get(path: string, component: any, options?: {
12
- [key: string]: any;
13
- }): void;
21
+ get(path: string, component: any, options?: RouteRegistrationOptions): void;
14
22
  /**
15
23
  * Register a POST-Method
16
24
  *
@@ -18,9 +26,7 @@ export declare class RouteFacade {
18
26
  * @param component
19
27
  * @param options
20
28
  */
21
- post(path: string, component: any, options?: {
22
- [key: string]: any;
23
- }): void;
29
+ post(path: string, component: any, options?: RouteRegistrationOptions): void;
24
30
  /**
25
31
  * Register a PUT-Method
26
32
  *
@@ -28,9 +34,7 @@ export declare class RouteFacade {
28
34
  * @param component
29
35
  * @param options
30
36
  */
31
- put(path: string, component: any, options?: {
32
- [key: string]: any;
33
- }): void;
37
+ put(path: string, component: any, options?: RouteRegistrationOptions): void;
34
38
  /**
35
39
  * Register a DELETE-Method
36
40
  *
@@ -38,11 +42,10 @@ export declare class RouteFacade {
38
42
  * @param component
39
43
  * @param options
40
44
  */
41
- delete(path: string, component: any, options?: {
42
- [key: string]: any;
43
- }): void;
45
+ delete(path: string, component: any, options?: RouteRegistrationOptions): void;
44
46
  group(config: {
45
47
  [key: string]: any;
46
48
  }, children: any): void;
47
49
  }
48
50
  export declare const Route: RouteFacade;
51
+ export {};
@@ -6,6 +6,32 @@ class RouteFacade {
6
6
  constructor() {
7
7
  this.tempGroupPrefix = null;
8
8
  this.tempGroupMiddleware = null;
9
+ this.registerWithGroupContext = (method, path, component, options) => {
10
+ Router_1.Router.register(method, (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path, component, options || this.tempGroupMiddleware
11
+ ? Object.assign(Object.assign({}, (options ? options : {})), { middleware: this.tempGroupMiddleware || (options && options.middleware)
12
+ ? [
13
+ ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
14
+ ...(options && options.middleware ? options.middleware : []),
15
+ ]
16
+ : null }) : {});
17
+ };
18
+ }
19
+ /**
20
+ * Register a custom route-method on the Route facade.
21
+ * Allows optional packages to add framework-specific route helpers.
22
+ */
23
+ extend(name, factory) {
24
+ if (!name || name.trim() === "") {
25
+ throw new Error("Route.extend requires a non-empty method name");
26
+ }
27
+ if (name in this && typeof this[name] === "function") {
28
+ throw new Error("Route method '" + name + "' already exists");
29
+ }
30
+ if (!factory || typeof factory !== "function") {
31
+ throw new Error("Route.extend requires a factory function");
32
+ }
33
+ this[name] = factory(this);
34
+ return this;
9
35
  }
10
36
  /**
11
37
  * Register a GET-Method
@@ -15,13 +41,7 @@ class RouteFacade {
15
41
  * @param options
16
42
  */
17
43
  get(path, component, options) {
18
- Router_1.Router.register("GET", (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path, component, options || this.tempGroupMiddleware
19
- ? Object.assign(Object.assign({}, (options ? options : {})), { middleware: this.tempGroupMiddleware || (options && options.middleware)
20
- ? [
21
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
22
- ...(options && options.middleware ? options.middleware : []),
23
- ]
24
- : null }) : {});
44
+ this.registerWithGroupContext("GET", path, component, options);
25
45
  }
26
46
  /**
27
47
  * Register a POST-Method
@@ -31,13 +51,7 @@ class RouteFacade {
31
51
  * @param options
32
52
  */
33
53
  post(path, component, options) {
34
- Router_1.Router.register("POST", (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path, component, options || this.tempGroupMiddleware
35
- ? Object.assign(Object.assign({}, (options ? options : {})), { middleware: this.tempGroupMiddleware || (options && options.middleware)
36
- ? [
37
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
38
- ...(options && options.middleware ? options.middleware : []),
39
- ]
40
- : null }) : {});
54
+ this.registerWithGroupContext("POST", path, component, options);
41
55
  }
42
56
  /**
43
57
  * Register a PUT-Method
@@ -47,13 +61,7 @@ class RouteFacade {
47
61
  * @param options
48
62
  */
49
63
  put(path, component, options) {
50
- Router_1.Router.register("PUT", (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path, component, options || this.tempGroupMiddleware
51
- ? Object.assign(Object.assign({}, (options ? options : {})), { middleware: this.tempGroupMiddleware || (options && options.middleware)
52
- ? [
53
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
54
- ...(options && options.middleware ? options.middleware : []),
55
- ]
56
- : null }) : {});
64
+ this.registerWithGroupContext("PUT", path, component, options);
57
65
  }
58
66
  /**
59
67
  * Register a DELETE-Method
@@ -63,13 +71,7 @@ class RouteFacade {
63
71
  * @param options
64
72
  */
65
73
  delete(path, component, options) {
66
- Router_1.Router.register("DELETE", (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path, component, options || this.tempGroupMiddleware
67
- ? Object.assign(Object.assign({}, (options ? options : {})), { middleware: this.tempGroupMiddleware || (options && options.middleware)
68
- ? [
69
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
70
- ...(options && options.middleware ? options.middleware : []),
71
- ]
72
- : null }) : {});
74
+ this.registerWithGroupContext("DELETE", path, component, options);
73
75
  }
74
76
  group(config, children) {
75
77
  // Handle group prefix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.29",
3
+ "version": "0.3.30",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",
@@ -1,19 +1,22 @@
1
1
  import { Router } from "./Router";
2
2
 
3
+ type RouteRegistrationOptions = { [key: string]: any };
4
+ type RouteRegistrationHandler = (
5
+ method: string,
6
+ path: string,
7
+ component: any,
8
+ options?: RouteRegistrationOptions
9
+ ) => void;
10
+
11
+ type RouteExtensionFactory = (route: RouteFacade) => (...args: any[]) => any;
12
+
3
13
  export class RouteFacade {
4
14
  tempGroupPrefix: string | null = null;
5
15
  tempGroupMiddleware: string[] | null = null;
6
16
 
7
- /**
8
- * Register a GET-Method
9
- *
10
- * @param path
11
- * @param component
12
- * @param options
13
- */
14
- get(path: string, component: any, options?: { [key: string]: any }) {
17
+ private registerWithGroupContext: RouteRegistrationHandler = (method, path, component, options) => {
15
18
  Router.register(
16
- "GET",
19
+ method,
17
20
  (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
18
21
  component,
19
22
  options || this.tempGroupMiddleware
@@ -29,6 +32,38 @@ export class RouteFacade {
29
32
  }
30
33
  : {}
31
34
  );
35
+ };
36
+
37
+ /**
38
+ * Register a custom route-method on the Route facade.
39
+ * Allows optional packages to add framework-specific route helpers.
40
+ */
41
+ extend(name: string, factory: RouteExtensionFactory) {
42
+ if (!name || name.trim() === "") {
43
+ throw new Error("Route.extend requires a non-empty method name");
44
+ }
45
+
46
+ if (name in this && typeof (this as any)[name] === "function") {
47
+ throw new Error("Route method '" + name + "' already exists");
48
+ }
49
+
50
+ if (!factory || typeof factory !== "function") {
51
+ throw new Error("Route.extend requires a factory function");
52
+ }
53
+
54
+ (this as any)[name] = factory(this);
55
+ return this;
56
+ }
57
+
58
+ /**
59
+ * Register a GET-Method
60
+ *
61
+ * @param path
62
+ * @param component
63
+ * @param options
64
+ */
65
+ get(path: string, component: any, options?: RouteRegistrationOptions) {
66
+ this.registerWithGroupContext("GET", path, component, options);
32
67
  }
33
68
 
34
69
  /**
@@ -38,24 +73,8 @@ export class RouteFacade {
38
73
  * @param component
39
74
  * @param options
40
75
  */
41
- post(path: string, component: any, options?: { [key: string]: any }) {
42
- Router.register(
43
- "POST",
44
- (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
45
- component,
46
- options || this.tempGroupMiddleware
47
- ? {
48
- ...(options ? options : {}),
49
- middleware:
50
- this.tempGroupMiddleware || (options && options.middleware)
51
- ? [
52
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
53
- ...(options && options.middleware ? options.middleware : []),
54
- ]
55
- : null,
56
- }
57
- : {}
58
- );
76
+ post(path: string, component: any, options?: RouteRegistrationOptions) {
77
+ this.registerWithGroupContext("POST", path, component, options);
59
78
  }
60
79
 
61
80
  /**
@@ -65,24 +84,8 @@ export class RouteFacade {
65
84
  * @param component
66
85
  * @param options
67
86
  */
68
- put(path: string, component: any, options?: { [key: string]: any }) {
69
- Router.register(
70
- "PUT",
71
- (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
72
- component,
73
- options || this.tempGroupMiddleware
74
- ? {
75
- ...(options ? options : {}),
76
- middleware:
77
- this.tempGroupMiddleware || (options && options.middleware)
78
- ? [
79
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
80
- ...(options && options.middleware ? options.middleware : []),
81
- ]
82
- : null,
83
- }
84
- : {}
85
- );
87
+ put(path: string, component: any, options?: RouteRegistrationOptions) {
88
+ this.registerWithGroupContext("PUT", path, component, options);
86
89
  }
87
90
 
88
91
  /**
@@ -92,30 +95,15 @@ export class RouteFacade {
92
95
  * @param component
93
96
  * @param options
94
97
  */
95
- delete(path: string, component: any, options?: { [key: string]: any }) {
96
- Router.register(
97
- "DELETE",
98
- (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
99
- component,
100
- options || this.tempGroupMiddleware
101
- ? {
102
- ...(options ? options : {}),
103
- middleware:
104
- this.tempGroupMiddleware || (options && options.middleware)
105
- ? [
106
- ...(this.tempGroupMiddleware ? this.tempGroupMiddleware : []),
107
- ...(options && options.middleware ? options.middleware : []),
108
- ]
109
- : null,
110
- }
111
- : {}
112
- );
98
+ delete(path: string, component: any, options?: RouteRegistrationOptions) {
99
+ this.registerWithGroupContext("DELETE", path, component, options);
113
100
  }
114
101
 
115
102
  group(config: { [key: string]: any }, children: any) {
116
103
  // Handle group prefix
117
104
  const tempGroupPrefixBefore = this.tempGroupPrefix ? this.tempGroupPrefix + "" : null;
118
- const tempGroupMiddlewareBefore = this.tempGroupMiddleware && this.tempGroupMiddleware.length > 0 ? this.tempGroupMiddleware : null;
105
+ const tempGroupMiddlewareBefore =
106
+ this.tempGroupMiddleware && this.tempGroupMiddleware.length > 0 ? this.tempGroupMiddleware : null;
119
107
  if (config && config.prefix && config.prefix.trim() !== "") {
120
108
  this.tempGroupPrefix = (this.tempGroupPrefix ? this.tempGroupPrefix : "") + config.prefix;
121
109
  }