@lakutata/entrypoint 3.0.0-beta.6 → 3.1.0
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 +39 -26
- package/README.zh-CN.md +182 -0
- package/dist/cjs/Entrypoint.d.ts +13 -7
- package/dist/cjs/Entrypoint.js +12 -6
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.d.ts +3 -2
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.js +3 -2
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.js +2 -1
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +3 -2
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +8 -2
- package/dist/cjs/adapter/express/Responders.d.ts +2 -1
- package/dist/cjs/adapter/express/Responders.js +4 -2
- package/dist/cjs/adapter/express/StaticUI.d.ts +2 -1
- package/dist/cjs/adapter/express/StaticUI.js +2 -1
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +4 -1
- package/dist/cjs/adapter/fastify/Responders.d.ts +2 -1
- package/dist/cjs/adapter/fastify/Responders.js +4 -2
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +4 -3
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +10 -22
- package/dist/cjs/adapter/http/HTTPServiceProxy.d.ts +190 -0
- package/dist/cjs/adapter/http/HTTPServiceProxy.js +371 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +11 -7
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +15 -26
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.js +12 -5
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +2 -1
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.js +2 -1
- package/dist/cjs/channel/BuiltinChannels.js +4 -2
- package/dist/cjs/channel/EntrypointAdapter.d.ts +2 -2
- package/dist/cjs/decorators/HTTPAction.d.ts +5 -5
- package/dist/cjs/decorators/Schedule.d.ts +4 -2
- package/dist/cjs/decorators/Schedule.js +3 -2
- package/dist/cjs/decorators/ServiceAction.d.ts +12 -8
- package/dist/cjs/decorators/http/DELETE.d.ts +5 -5
- package/dist/cjs/decorators/http/GET.d.ts +7 -7
- package/dist/cjs/decorators/http/HEAD.d.ts +9 -9
- package/dist/cjs/decorators/http/OPTIONS.d.ts +8 -7
- package/dist/cjs/decorators/http/PATCH.d.ts +5 -5
- package/dist/cjs/decorators/http/POST.d.ts +5 -5
- package/dist/cjs/decorators/http/PUT.d.ts +7 -7
- package/dist/cjs/exceptions/AccessDeniedException.d.ts +7 -1
- package/dist/cjs/exceptions/AccessDeniedException.js +8 -3
- package/dist/cjs/exceptions/EntrypointStoppedException.d.ts +2 -1
- package/dist/cjs/exceptions/EntrypointStoppedException.js +2 -1
- package/dist/cjs/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.js +4 -0
- package/dist/cjs/exceptions/ServiceUnavailableException.d.ts +15 -0
- package/dist/cjs/exceptions/ServiceUnavailableException.js +18 -0
- package/dist/cjs/exports/Entrypoint.d.ts +3 -0
- package/dist/cjs/exports/Entrypoint.js +6 -1
- package/dist/cjs/lib/AccessControl.d.ts +2 -1
- package/dist/cjs/lib/AccessControl.js +2 -1
- package/dist/cjs/lib/AccessControlRule.d.ts +2 -1
- package/dist/cjs/lib/AccessControlRule.js +2 -1
- package/dist/cjs/lib/ExplicitStatus.d.ts +11 -0
- package/dist/cjs/lib/ExplicitStatus.js +22 -0
- package/dist/cjs/lib/Middleware.d.ts +2 -1
- package/dist/cjs/lib/Middleware.js +2 -1
- package/dist/cjs/lib/Preflight.d.ts +8 -0
- package/dist/cjs/lib/Preflight.js +13 -0
- package/dist/cjs/lib/ReadBody.d.ts +9 -0
- package/dist/cjs/lib/ReadBody.js +25 -0
- package/dist/cjs/lib/pattern/FlattenPattern.d.ts +14 -0
- package/dist/cjs/lib/pattern/FlattenPattern.js +59 -0
- package/dist/esm/Entrypoint.js +12 -6
- package/dist/esm/adapter/commander/CommanderCLIEntrypoint.js +3 -2
- package/dist/esm/adapter/electron/ElectronIPCServiceEntrypoint.js +2 -1
- package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +8 -2
- package/dist/esm/adapter/express/Responders.js +4 -2
- package/dist/esm/adapter/express/StaticUI.js +2 -1
- package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +4 -1
- package/dist/esm/adapter/fastify/Responders.js +4 -2
- package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +9 -21
- package/dist/esm/adapter/http/HTTPServiceProxy.js +366 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +14 -25
- package/dist/esm/adapter/schedule/ScheduleEntrypoint.js +12 -5
- package/dist/esm/adapter/socket.io/SocketIOServiceEntrypoint.js +2 -1
- package/dist/esm/channel/BuiltinChannels.js +4 -2
- package/dist/esm/decorators/Schedule.js +3 -2
- package/dist/esm/exceptions/AccessDeniedException.js +8 -3
- package/dist/esm/exceptions/EntrypointStoppedException.js +2 -1
- package/dist/esm/exceptions/InvalidActionGroupException.js +4 -0
- package/dist/esm/exceptions/ServiceUnavailableException.js +14 -0
- package/dist/esm/exports/Entrypoint.js +3 -0
- package/dist/esm/lib/AccessControl.js +2 -1
- package/dist/esm/lib/AccessControlRule.js +2 -1
- package/dist/esm/lib/ExplicitStatus.js +18 -0
- package/dist/esm/lib/Middleware.js +2 -1
- package/dist/esm/lib/Preflight.js +10 -0
- package/dist/esm/lib/ReadBody.js +22 -0
- package/dist/esm/lib/pattern/FlattenPattern.js +55 -0
- package/dist/types/Entrypoint.d.ts +13 -7
- package/dist/types/adapter/commander/CommanderCLIEntrypoint.d.ts +3 -2
- package/dist/types/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +2 -1
- package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +3 -2
- package/dist/types/adapter/express/Responders.d.ts +2 -1
- package/dist/types/adapter/express/StaticUI.d.ts +2 -1
- package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +2 -1
- package/dist/types/adapter/fastify/Responders.d.ts +2 -1
- package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +4 -3
- package/dist/types/adapter/http/HTTPServiceProxy.d.ts +190 -0
- package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +11 -7
- package/dist/types/adapter/schedule/ScheduleEntrypoint.d.ts +2 -1
- package/dist/types/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +2 -1
- package/dist/types/channel/EntrypointAdapter.d.ts +2 -2
- package/dist/types/decorators/HTTPAction.d.ts +5 -5
- package/dist/types/decorators/Schedule.d.ts +4 -2
- package/dist/types/decorators/ServiceAction.d.ts +12 -8
- package/dist/types/decorators/http/DELETE.d.ts +5 -5
- package/dist/types/decorators/http/GET.d.ts +7 -7
- package/dist/types/decorators/http/HEAD.d.ts +9 -9
- package/dist/types/decorators/http/OPTIONS.d.ts +8 -7
- package/dist/types/decorators/http/PATCH.d.ts +5 -5
- package/dist/types/decorators/http/POST.d.ts +5 -5
- package/dist/types/decorators/http/PUT.d.ts +7 -7
- package/dist/types/exceptions/AccessDeniedException.d.ts +7 -1
- package/dist/types/exceptions/EntrypointStoppedException.d.ts +2 -1
- package/dist/types/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/types/exceptions/ServiceUnavailableException.d.ts +15 -0
- package/dist/types/exports/Entrypoint.d.ts +3 -0
- package/dist/types/lib/AccessControl.d.ts +2 -1
- package/dist/types/lib/AccessControlRule.d.ts +2 -1
- package/dist/types/lib/ExplicitStatus.d.ts +11 -0
- package/dist/types/lib/Middleware.d.ts +2 -1
- package/dist/types/lib/Preflight.d.ts +8 -0
- package/dist/types/lib/ReadBody.d.ts +9 -0
- package/dist/types/lib/pattern/FlattenPattern.d.ts +14 -0
- package/package.json +35 -4
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# @lakutata/entrypoint
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
method can be an HTTP route, a CLI command, a service action (invoked over HTTP, Socket.IO, JSON-RPC, gRPC, NATS or
|
|
5
|
-
Electron IPC) and a scheduled job at the same time; its input is validated by its DTO, checked by the access control and
|
|
6
|
-
passed through the middlewares the same way whatever the transport.
|
|
3
|
+
English | [中文](README.zh-CN.md)
|
|
7
4
|
|
|
8
|
-
The
|
|
9
|
-
|
|
10
|
-
and
|
|
5
|
+
The entrypoint of the lakutata framework: controllers and their actions, served by transports. One controller method
|
|
6
|
+
can be an HTTP route, a CLI command, a service action (called over HTTP, Socket.IO, JSON-RPC, gRPC, NATS or Electron
|
|
7
|
+
IPC) and a scheduled job at the same time. Whatever the transport, a request goes through the same middlewares, its
|
|
8
|
+
input is validated by the same DTO, and the same access control applies.
|
|
9
|
+
|
|
10
|
+
The umbrella package `lakutata` re-exports it as `lakutata/com/entrypoint` (the component, controllers, contexts and
|
|
11
|
+
results), `lakutata/decorator/ctrl` (the action decorators) and `lakutata/adapter/*` (the transports), and presets the
|
|
12
|
+
`entrypoint` component in every application.
|
|
11
13
|
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
@@ -15,7 +17,7 @@ and presets the `entrypoint` component of the applications.
|
|
|
15
17
|
npm install lakutata
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
The transports
|
|
20
|
+
The transports rely on optional peer dependencies, which you install only when you use them:
|
|
19
21
|
|
|
20
22
|
| Transport | Subpath | Install |
|
|
21
23
|
|---|---|---|
|
|
@@ -71,7 +73,7 @@ class OrderController extends Controller {
|
|
|
71
73
|
return input
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
//Every 5 minutes (with {every: '5m', exclusive: true} and a mutex component
|
|
76
|
+
//Every 5 minutes (with {every: '5m', exclusive: true} and a mutex component, in one instance at a time)
|
|
75
77
|
@Schedule('5m')
|
|
76
78
|
public async report(): Promise<void> {
|
|
77
79
|
}
|
|
@@ -93,15 +95,18 @@ Application.run(() => ({
|
|
|
93
95
|
}))
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
###
|
|
98
|
+
### Inside an action
|
|
97
99
|
|
|
98
|
-
- `this.context` is the context
|
|
99
|
-
`CLIContext` (
|
|
100
|
-
- `this.signal` is aborted when the request is:
|
|
101
|
-
|
|
102
|
-
-
|
|
100
|
+
- `this.context` is the request's context: `HTTPContext` (with `request`, `response`, `route`, `method`),
|
|
101
|
+
`CLIContext` (with `command`), `ServiceContext` (with `clientId`) or `ScheduleContext`.
|
|
102
|
+
- `this.signal` is aborted when the request is aborted: the client goes away, the deadline passes or the entrypoint
|
|
103
|
+
stops.
|
|
104
|
+
- A controller is created for each request (`@Scoped`), and the request-scoped objects it injects are shared within
|
|
105
|
+
that request.
|
|
106
|
+
- The returned value is the response. `Result.status(201, data, headers)`, `Result.file(path)` and `Result.events()`
|
|
103
107
|
(Server-Sent Events) control the HTTP response.
|
|
104
|
-
-
|
|
108
|
+
- Over HTTP (Express, Fastify), an exception is answered with its `statusCode` and the JSON `{errno, errMsg}`, and
|
|
109
|
+
invalid input with 400.
|
|
105
110
|
|
|
106
111
|
### Middlewares and access control
|
|
107
112
|
|
|
@@ -109,8 +114,9 @@ Application.run(() => ({
|
|
|
109
114
|
import {Application} from 'lakutata'
|
|
110
115
|
import {buildEntrypoints, Controller, type ActionInvocation, type Next} from 'lakutata/com/entrypoint'
|
|
111
116
|
import {GET, UseMiddlewares} from 'lakutata/decorator/ctrl'
|
|
117
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
112
118
|
|
|
113
|
-
//Runs around the action
|
|
119
|
+
//Runs around the action, before input validation and access control
|
|
114
120
|
async function timing(invocation: ActionInvocation, next: Next): Promise<unknown> {
|
|
115
121
|
const started: number = Date.now()
|
|
116
122
|
try {
|
|
@@ -120,6 +126,7 @@ async function timing(invocation: ActionInvocation, next: Next): Promise<unknown
|
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
|
|
129
|
+
//For the actions of this controller (buildEntrypoints({middlewares: [timing]}) would run it for every action)
|
|
123
130
|
@UseMiddlewares(timing)
|
|
124
131
|
class HealthController extends Controller {
|
|
125
132
|
@GET('/health')
|
|
@@ -132,13 +139,14 @@ Application.run(() => ({
|
|
|
132
139
|
id: 'health.app',
|
|
133
140
|
name: 'Health',
|
|
134
141
|
components: {
|
|
135
|
-
entrypoint: buildEntrypoints({controllers: [HealthController],
|
|
136
|
-
}
|
|
142
|
+
entrypoint: buildEntrypoints({controllers: [HealthController], http: expressHTTPEntrypoint({port: 3000})})
|
|
143
|
+
},
|
|
144
|
+
bootstrap: ['entrypoint']
|
|
137
145
|
}))
|
|
138
146
|
```
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
(`AccessControlRule` subclasses, created for each request)
|
|
148
|
+
Access control: an action declared with `{acl: true}` is checked by the rules given to `buildEntrypoints({rules})`
|
|
149
|
+
(`AccessControlRule` subclasses, created for each request). A rule that resolves to `false` denies the request with
|
|
142
150
|
`AccessDeniedException` (HTTP 403).
|
|
143
151
|
|
|
144
152
|
## API
|
|
@@ -152,11 +160,12 @@ The access control: an action declared with `{acl: true}` is checked by the rule
|
|
|
152
160
|
| `AccessControlRule`, `AccessControl` | `lakutata/com/entrypoint` | The access control |
|
|
153
161
|
| `Middleware`, `ActionInvocation`, `Next` | `lakutata/com/entrypoint` | The middlewares |
|
|
154
162
|
| `httpServiceEntrypoint`, `scheduleEntrypoint` | `lakutata/com/entrypoint` | The service over HTTP, the schedules |
|
|
163
|
+
| `HTTPServiceProxy`, `buildHTTPServiceProxy` | `lakutata/com/entrypoint` | The client of a service over HTTP: its actions and its events |
|
|
155
164
|
| `Cron`, `parseDuration` | `lakutata/com/entrypoint` | Cron expressions and durations |
|
|
156
165
|
| `HTTPAction`, `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS` | `lakutata/decorator/ctrl` | HTTP routes |
|
|
157
166
|
| `CLIAction`, `ServiceAction`, `Schedule` | `lakutata/decorator/ctrl` | Commands, service actions, scheduled jobs |
|
|
158
167
|
| `Prefix`, `UseMiddlewares` | `lakutata/decorator/ctrl` | A prefix for the routes, commands and patterns of a controller; its middlewares |
|
|
159
|
-
| `expressHTTPEntrypoint`, `fastifyHTTPEntrypoint` | `lakutata/adapter/express`, `lakutata/adapter/fastify` | HTTP transports (with
|
|
168
|
+
| `expressHTTPEntrypoint`, `fastifyHTTPEntrypoint` | `lakutata/adapter/express`, `lakutata/adapter/fastify` | HTTP transports (`staticUI` with Express) |
|
|
160
169
|
| `commanderCLIEntrypoint` | `lakutata/adapter/commander` | CLI transport |
|
|
161
170
|
| `socketIOServiceEntrypoint`, `jsonRPCServiceEntrypoint`, `electronIPCServiceEntrypoint` | `lakutata/adapter/*` | Service transports |
|
|
162
171
|
| `EntrypointAdapter`, `ChannelRuntime` | `@lakutata/entrypoint/channel` | Writing a transport or a channel |
|
|
@@ -172,12 +181,16 @@ The typings document each declaration with examples.
|
|
|
172
181
|
| `ControllerActionNotFoundException`, `RouteNotFoundException` | No action matches the request (HTTP 404) |
|
|
173
182
|
| `EntrypointStoppedException` | The entrypoint is stopping (HTTP 503) |
|
|
174
183
|
| `DuplicateActionNameException` | Two actions of a channel have the same name |
|
|
175
|
-
| `InvalidPrefixException`, `InvalidMiddlewareException`, `InvalidChannelException`, `InvalidEntrypointAdapterException` | The declarations of the controllers or the options are invalid |
|
|
184
|
+
| `InvalidActionGroupException`, `InvalidPrefixException`, `InvalidMiddlewareException`, `InvalidChannelException`, `InvalidEntrypointAdapterException` | The declarations of the controllers or the options are invalid |
|
|
176
185
|
| `ServiceInvokeException` | A remote service action failed (its error's properties copied) |
|
|
186
|
+
| `ServiceUnavailableException` | A service called by an `HTTPServiceProxy` gave no response (HTTP 503) |
|
|
177
187
|
|
|
178
188
|
## See also
|
|
179
189
|
|
|
180
|
-
- The
|
|
181
|
-
|
|
182
|
-
-
|
|
190
|
+
- The guides on the website: [Controllers and Actions](https://lakutata.com/guide/Controllers) and
|
|
191
|
+
[Entrypoint](https://lakutata.com/guide/Entrypoint).
|
|
192
|
+
- The guides in the `lakutata` package (`doc/en`, Chinese in `doc/zh`): `Controllers.md`, `Entrypoint.md`,
|
|
193
|
+
`Middlewares.md`, `HTTP.md` (the HTTP adapters), `ServiceCalls.md` (the service transports), `CLI.md`, `Schedules.md`.
|
|
194
|
+
- `@lakutata/core`: DTOs, dependency injection and the application.
|
|
195
|
+
- `@lakutata/mutex`: `@Synchronized` actions and `exclusive` schedules.
|
|
183
196
|
- `@lakutata/grpc`, `@lakutata/nats`: the service transports over gRPC and NATS.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @lakutata/entrypoint
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
lakutata 框架的入口:控制器及其动作,通过各种传输方式对外提供。一个控制器方法可以同时是 HTTP 路由、命令行命令、服务动作(通过 HTTP、Socket.IO、JSON-RPC、gRPC、NATS 或 Electron IPC 调用)和定时任务。无论通过哪种传输方式,请求都经过相同的中间件,输入由相同的 DTO 校验,适用的访问控制也相同。
|
|
6
|
+
|
|
7
|
+
`lakutata` 包把它重新导出为 `lakutata/com/entrypoint`(组件、控制器、上下文和结果)、`lakutata/decorator/ctrl`(动作装饰器)和 `lakutata/adapter/*`(各种传输方式),并在每个应用中预置 `entrypoint` 组件。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
npm install lakutata
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
各传输方式所需的包都是可选的 peer 依赖,用到时才需要安装:
|
|
16
|
+
|
|
17
|
+
| 传输方式 | 子路径 | 需要安装 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 基于 Express 4/5 的 HTTP | `lakutata/adapter/express` | `express` |
|
|
20
|
+
| 基于 Fastify 5 的 HTTP | `lakutata/adapter/fastify` | `fastify` |
|
|
21
|
+
| 基于 Commander 的命令行 | `lakutata/adapter/commander` | `commander`(提示输入还需要 `@inquirer/prompts`) |
|
|
22
|
+
| 基于 Socket.IO 的服务调用 | `lakutata/adapter/socket.io` | `socket.io` |
|
|
23
|
+
| 基于 JSON-RPC 2.0 的服务调用(HTTP、WebSocket、stdio) | `lakutata/adapter/jsonrpc` | 使用 WebSocket 时需要 `ws` |
|
|
24
|
+
| 基于 Electron IPC 的服务调用 | `lakutata/adapter/electron` | (Electron) |
|
|
25
|
+
| 基于 HTTP 的服务调用(`POST /`,事件通过 SSE 推送) | `lakutata/com/entrypoint` | 无 |
|
|
26
|
+
| 定时任务(`@Schedule`) | `lakutata/com/entrypoint` | 无 |
|
|
27
|
+
| 基于 gRPC / NATS 的服务调用 | `@lakutata/grpc/service` / `@lakutata/nats` | 它们各自的包 |
|
|
28
|
+
|
|
29
|
+
## 用法
|
|
30
|
+
|
|
31
|
+
### 控制器与传输方式
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import {Application, DTO} from 'lakutata'
|
|
35
|
+
import {buildEntrypoints, Controller, scheduleEntrypoint} from 'lakutata/com/entrypoint'
|
|
36
|
+
import {CLIAction, GET, POST, Schedule, ServiceAction} from 'lakutata/decorator/ctrl'
|
|
37
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
38
|
+
import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
|
|
39
|
+
import {socketIOServiceEntrypoint} from 'lakutata/adapter/socket.io'
|
|
40
|
+
import {Expect} from 'lakutata/decorator/dto'
|
|
41
|
+
|
|
42
|
+
class GetOrderDTO extends DTO {
|
|
43
|
+
//路由参数和查询参数都是字符串:strict(false) 会转换它们
|
|
44
|
+
@Expect(DTO.Number().integer().strict(false).required())
|
|
45
|
+
public id: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class CreateOrderDTO extends DTO {
|
|
49
|
+
@Expect(DTO.String().required())
|
|
50
|
+
public product: string
|
|
51
|
+
|
|
52
|
+
@Expect(DTO.Number().integer().min(1).default(1))
|
|
53
|
+
public quantity: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class OrderController extends Controller {
|
|
57
|
+
//GET /orders/42、命令 "order --id 42"、服务请求 {cmd: 'order.get', id: 42}
|
|
58
|
+
@GET('/orders/:id', GetOrderDTO)
|
|
59
|
+
@CLIAction('order', GetOrderDTO)
|
|
60
|
+
@ServiceAction({cmd: 'order.get'}, GetOrderDTO)
|
|
61
|
+
public async get(input: GetOrderDTO): Promise<{id: number}> {
|
|
62
|
+
return {id: input.id}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//带 JSON 请求体的 POST /orders;返回值就是响应(JSON)
|
|
66
|
+
@POST('/orders', CreateOrderDTO)
|
|
67
|
+
public async create(input: CreateOrderDTO): Promise<{product: string, quantity: number}> {
|
|
68
|
+
return input
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//每 5 分钟执行一次(使用 {every: '5m', exclusive: true} 并配置锁组件时,同一时间只在一个实例中执行)
|
|
72
|
+
@Schedule('5m')
|
|
73
|
+
public async report(): Promise<void> {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Application.run(() => ({
|
|
78
|
+
id: 'orders.app',
|
|
79
|
+
name: 'Orders',
|
|
80
|
+
components: {
|
|
81
|
+
entrypoint: buildEntrypoints({
|
|
82
|
+
controllers: [OrderController],
|
|
83
|
+
http: expressHTTPEntrypoint({port: 8080}),
|
|
84
|
+
cli: commanderCLIEntrypoint(),
|
|
85
|
+
service: socketIOServiceEntrypoint({port: 8081}),
|
|
86
|
+
schedule: scheduleEntrypoint()
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
bootstrap: ['entrypoint']
|
|
90
|
+
}))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 在动作中
|
|
94
|
+
|
|
95
|
+
- `this.context` 是请求的上下文:`HTTPContext`(带有 `request`、`response`、`route`、`method`)、`CLIContext`(带有 `command`)、`ServiceContext`(带有 `clientId`)或 `ScheduleContext`。
|
|
96
|
+
- 请求被中止时,`this.signal` 也会被中止:客户端离开、截止时间已过,或者入口停止。
|
|
97
|
+
- 每个请求都会创建一个控制器(`@Scoped`),它注入的请求作用域对象在这个请求内共享。
|
|
98
|
+
- 返回值就是响应。`Result.status(201, data, headers)`、`Result.file(path)` 和 `Result.events()`(Server-Sent Events)用来控制 HTTP 响应。
|
|
99
|
+
- 通过 HTTP(Express、Fastify)调用时,抛出的异常会以它的 `statusCode` 和 JSON `{errno, errMsg}` 返回,输入不合法时返回 400。
|
|
100
|
+
|
|
101
|
+
### 中间件与访问控制
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import {Application} from 'lakutata'
|
|
105
|
+
import {buildEntrypoints, Controller, type ActionInvocation, type Next} from 'lakutata/com/entrypoint'
|
|
106
|
+
import {GET, UseMiddlewares} from 'lakutata/decorator/ctrl'
|
|
107
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
108
|
+
|
|
109
|
+
//在动作前后运行,早于输入校验和访问控制
|
|
110
|
+
async function timing(invocation: ActionInvocation, next: Next): Promise<unknown> {
|
|
111
|
+
const started: number = Date.now()
|
|
112
|
+
try {
|
|
113
|
+
return await next()
|
|
114
|
+
} finally {
|
|
115
|
+
console.log(`${invocation.context.type} took ${Date.now() - started} ms`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//作用于这个控制器的动作(写在 buildEntrypoints({middlewares: [timing]}) 中则作用于所有动作)
|
|
120
|
+
@UseMiddlewares(timing)
|
|
121
|
+
class HealthController extends Controller {
|
|
122
|
+
@GET('/health')
|
|
123
|
+
public async health(): Promise<string> {
|
|
124
|
+
return 'ok'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Application.run(() => ({
|
|
129
|
+
id: 'health.app',
|
|
130
|
+
name: 'Health',
|
|
131
|
+
components: {
|
|
132
|
+
entrypoint: buildEntrypoints({controllers: [HealthController], http: expressHTTPEntrypoint({port: 3000})})
|
|
133
|
+
},
|
|
134
|
+
bootstrap: ['entrypoint']
|
|
135
|
+
}))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
访问控制:以 `{acl: true}` 声明的动作,由传给 `buildEntrypoints({rules})` 的规则检查(`AccessControlRule` 的子类,每个请求创建一次)。某条规则的结果为 `false` 时,请求被拒绝,抛出 `AccessDeniedException`(HTTP 403)。
|
|
139
|
+
|
|
140
|
+
## API
|
|
141
|
+
|
|
142
|
+
| 导出 | 子路径 | 说明 |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `Entrypoint`、`buildEntrypoints` | `lakutata/com/entrypoint` | 组件及其选项:控制器、传输方式、中间件、规则、分组 |
|
|
145
|
+
| `Controller` | `lakutata/com/entrypoint` | 控制器的基类(`context`、`signal`) |
|
|
146
|
+
| `HTTPContext`、`CLIContext`、`ServiceContext`、`ScheduleContext`、`BaseContext`、`ContextType` | `lakutata/com/entrypoint` | 请求的上下文 |
|
|
147
|
+
| `Result`、`StatusResult`、`FileResult`、`EventsResult` | `lakutata/com/entrypoint` | 控制 HTTP 响应的结果 |
|
|
148
|
+
| `AccessControlRule`、`AccessControl` | `lakutata/com/entrypoint` | 访问控制 |
|
|
149
|
+
| `Middleware`、`ActionInvocation`、`Next` | `lakutata/com/entrypoint` | 中间件 |
|
|
150
|
+
| `httpServiceEntrypoint`、`scheduleEntrypoint` | `lakutata/com/entrypoint` | 基于 HTTP 的服务调用;定时任务 |
|
|
151
|
+
| `HTTPServiceProxy`、`buildHTTPServiceProxy` | `lakutata/com/entrypoint` | 基于 HTTP 的服务的客户端:调用它的动作,接收它推送的事件 |
|
|
152
|
+
| `Cron`、`parseDuration` | `lakutata/com/entrypoint` | cron 表达式和时长 |
|
|
153
|
+
| `HTTPAction`、`GET`、`POST`、`PUT`、`PATCH`、`DELETE`、`HEAD`、`OPTIONS` | `lakutata/decorator/ctrl` | HTTP 路由 |
|
|
154
|
+
| `CLIAction`、`ServiceAction`、`Schedule` | `lakutata/decorator/ctrl` | 命令、服务动作、定时任务 |
|
|
155
|
+
| `Prefix`、`UseMiddlewares` | `lakutata/decorator/ctrl` | 为控制器的路由、命令和模式加上前缀;控制器的中间件 |
|
|
156
|
+
| `expressHTTPEntrypoint`、`fastifyHTTPEntrypoint` | `lakutata/adapter/express`、`lakutata/adapter/fastify` | HTTP 传输方式(Express 支持 `staticUI`) |
|
|
157
|
+
| `commanderCLIEntrypoint` | `lakutata/adapter/commander` | 命令行传输方式 |
|
|
158
|
+
| `socketIOServiceEntrypoint`、`jsonRPCServiceEntrypoint`、`electronIPCServiceEntrypoint` | `lakutata/adapter/*` | 服务调用的传输方式 |
|
|
159
|
+
| `EntrypointAdapter`、`ChannelRuntime` | `@lakutata/entrypoint/channel` | 编写适配器或新的传输方式 |
|
|
160
|
+
|
|
161
|
+
类型声明中的每一项都附有说明和示例。
|
|
162
|
+
|
|
163
|
+
## 错误
|
|
164
|
+
|
|
165
|
+
| 异常 | 什么时候抛出 |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `InvalidValueException`(来自 `lakutata`) | 输入不符合动作的 DTO(HTTP 400) |
|
|
168
|
+
| `AccessDeniedException` | 规则拒绝了请求(HTTP 403) |
|
|
169
|
+
| `ControllerActionNotFoundException`、`RouteNotFoundException` | 没有与请求匹配的动作(HTTP 404) |
|
|
170
|
+
| `EntrypointStoppedException` | 入口正在停止(HTTP 503) |
|
|
171
|
+
| `DuplicateActionNameException` | 同一传输方式中有两个同名的动作 |
|
|
172
|
+
| `InvalidActionGroupException`、`InvalidPrefixException`、`InvalidMiddlewareException`、`InvalidChannelException`、`InvalidEntrypointAdapterException` | 控制器的声明或选项不合法 |
|
|
173
|
+
| `ServiceInvokeException` | 远程服务动作失败(带有原错误的各个属性) |
|
|
174
|
+
| `ServiceUnavailableException` | `HTTPServiceProxy` 调用的服务没有给出响应(HTTP 503) |
|
|
175
|
+
|
|
176
|
+
## 另见
|
|
177
|
+
|
|
178
|
+
- 网站上的指南:[控制器与动作](https://lakutata.com/zh/guide/Controllers)、[Entrypoint](https://lakutata.com/zh/guide/Entrypoint)。
|
|
179
|
+
- `lakutata` 包中的指南(`doc/zh`):`Controllers.md`、`Entrypoint.md`、`Middlewares.md`、`HTTP.md`(HTTP 适配器)、`ServiceCalls.md`(服务调用的传输方式)、`CLI.md`、`Schedules.md`。
|
|
180
|
+
- `@lakutata/core`:DTO、依赖注入和应用。
|
|
181
|
+
- `@lakutata/mutex`:`@Synchronized` 动作和 `exclusive` 定时任务。
|
|
182
|
+
- `@lakutata/grpc`、`@lakutata/nats`:基于 gRPC 和 NATS 的服务调用传输方式。
|
package/dist/cjs/Entrypoint.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export type ServiceEntrypoint = (module: Module, handler: ServiceEntrypointHandl
|
|
|
60
60
|
export type CLIMap = Map<string, JSONSchema>;
|
|
61
61
|
/**
|
|
62
62
|
* The HTTP actions given to an {@link HTTPEntrypoint}: each route (with its controller prefix) mapped to the set of
|
|
63
|
-
* its HTTP methods (`GET`, `
|
|
63
|
+
* its HTTP methods, as declared (`get`, `post`… from `@GET`, `@POST`…; `@HTTPAction` keeps the case it is given).
|
|
64
64
|
*/
|
|
65
65
|
export type HTTPRouteMap<HTTPMethods = string> = Map<string, Set<HTTPMethods>>;
|
|
66
66
|
/**
|
|
@@ -303,7 +303,8 @@ export interface EntrypointActions<T extends BaseActionInfo> {
|
|
|
303
303
|
* if (cliMap.has(command)) console.log(await handler(new CLIContext({command: command, data: {}})))
|
|
304
304
|
* })
|
|
305
305
|
* })
|
|
306
|
-
* }
|
|
306
|
+
* },
|
|
307
|
+
* bootstrap: ['entrypoint']
|
|
307
308
|
* }))
|
|
308
309
|
* ```
|
|
309
310
|
*/
|
|
@@ -335,8 +336,9 @@ export declare const buildCLIEntrypoint: (entrypoint: CLIEntrypoint) => CLIEntry
|
|
|
335
336
|
* controllers: [HealthController],
|
|
336
337
|
* http: buildHTTPEntrypoint((module, routeMap, handler, registerDestroy): void => {
|
|
337
338
|
* const server: Server = createServer(async (request, response): Promise<void> => {
|
|
339
|
+
* //The route map holds the routes and the methods as declared: '/health', 'get'
|
|
338
340
|
* const route: string = (request.url ?? '/').split('?')[0]
|
|
339
|
-
* const method: string = request.method ?? 'GET'
|
|
341
|
+
* const method: string = (request.method ?? 'GET').toLowerCase()
|
|
340
342
|
* if (!routeMap.get(route)?.has(method)) {
|
|
341
343
|
* response.writeHead(404).end()
|
|
342
344
|
* return
|
|
@@ -353,7 +355,8 @@ export declare const buildCLIEntrypoint: (entrypoint: CLIEntrypoint) => CLIEntry
|
|
|
353
355
|
* registerDestroy((): Promise<void> => new Promise((resolve): void => void server.close((): void => resolve())))
|
|
354
356
|
* })
|
|
355
357
|
* })
|
|
356
|
-
* }
|
|
358
|
+
* },
|
|
359
|
+
* bootstrap: ['entrypoint']
|
|
357
360
|
* }))
|
|
358
361
|
* ```
|
|
359
362
|
*/
|
|
@@ -394,7 +397,8 @@ export declare const buildHTTPEntrypoint: (entrypoint: HTTPEntrypoint) => HTTPEn
|
|
|
394
397
|
* registerDestroy((): void => void bus.off('request', listener))
|
|
395
398
|
* })
|
|
396
399
|
* })
|
|
397
|
-
* }
|
|
400
|
+
* },
|
|
401
|
+
* bootstrap: ['entrypoint']
|
|
398
402
|
* }))
|
|
399
403
|
* ```
|
|
400
404
|
*/
|
|
@@ -444,7 +448,8 @@ export declare const buildServiceEntrypoint: (entrypoint: ServiceEntrypoint) =>
|
|
|
444
448
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
445
449
|
* schedule: scheduleEntrypoint()
|
|
446
450
|
* })
|
|
447
|
-
* }
|
|
451
|
+
* },
|
|
452
|
+
* bootstrap: ['entrypoint']
|
|
448
453
|
* }))
|
|
449
454
|
* ```
|
|
450
455
|
*/
|
|
@@ -531,7 +536,8 @@ type StartedAdapter = {
|
|
|
531
536
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
532
537
|
* service: socketIOServiceEntrypoint({port: 3001})
|
|
533
538
|
* })
|
|
534
|
-
* }
|
|
539
|
+
* },
|
|
540
|
+
* bootstrap: ['entrypoint']
|
|
535
541
|
* }))
|
|
536
542
|
* .onLaunched(async (app: Application): Promise<void> => {
|
|
537
543
|
* const entrypoint: Entrypoint = await app.getObject<Entrypoint>('entrypoint')
|
package/dist/cjs/Entrypoint.js
CHANGED
|
@@ -67,7 +67,8 @@ Object.defineProperty(exports, "ScheduleContext", { enumerable: true, get: funct
|
|
|
67
67
|
* if (cliMap.has(command)) console.log(await handler(new CLIContext({command: command, data: {}})))
|
|
68
68
|
* })
|
|
69
69
|
* })
|
|
70
|
-
* }
|
|
70
|
+
* },
|
|
71
|
+
* bootstrap: ['entrypoint']
|
|
71
72
|
* }))
|
|
72
73
|
* ```
|
|
73
74
|
*/
|
|
@@ -100,8 +101,9 @@ exports.buildCLIEntrypoint = buildCLIEntrypoint;
|
|
|
100
101
|
* controllers: [HealthController],
|
|
101
102
|
* http: buildHTTPEntrypoint((module, routeMap, handler, registerDestroy): void => {
|
|
102
103
|
* const server: Server = createServer(async (request, response): Promise<void> => {
|
|
104
|
+
* //The route map holds the routes and the methods as declared: '/health', 'get'
|
|
103
105
|
* const route: string = (request.url ?? '/').split('?')[0]
|
|
104
|
-
* const method: string = request.method ?? 'GET'
|
|
106
|
+
* const method: string = (request.method ?? 'GET').toLowerCase()
|
|
105
107
|
* if (!routeMap.get(route)?.has(method)) {
|
|
106
108
|
* response.writeHead(404).end()
|
|
107
109
|
* return
|
|
@@ -118,7 +120,8 @@ exports.buildCLIEntrypoint = buildCLIEntrypoint;
|
|
|
118
120
|
* registerDestroy((): Promise<void> => new Promise((resolve): void => void server.close((): void => resolve())))
|
|
119
121
|
* })
|
|
120
122
|
* })
|
|
121
|
-
* }
|
|
123
|
+
* },
|
|
124
|
+
* bootstrap: ['entrypoint']
|
|
122
125
|
* }))
|
|
123
126
|
* ```
|
|
124
127
|
*/
|
|
@@ -160,7 +163,8 @@ exports.buildHTTPEntrypoint = buildHTTPEntrypoint;
|
|
|
160
163
|
* registerDestroy((): void => void bus.off('request', listener))
|
|
161
164
|
* })
|
|
162
165
|
* })
|
|
163
|
-
* }
|
|
166
|
+
* },
|
|
167
|
+
* bootstrap: ['entrypoint']
|
|
164
168
|
* }))
|
|
165
169
|
* ```
|
|
166
170
|
*/
|
|
@@ -211,7 +215,8 @@ exports.buildServiceEntrypoint = buildServiceEntrypoint;
|
|
|
211
215
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
212
216
|
* schedule: scheduleEntrypoint()
|
|
213
217
|
* })
|
|
214
|
-
* }
|
|
218
|
+
* },
|
|
219
|
+
* bootstrap: ['entrypoint']
|
|
215
220
|
* }))
|
|
216
221
|
* ```
|
|
217
222
|
*/
|
|
@@ -282,7 +287,8 @@ const LEGACY_CHANNELS = new Set(['cli', 'http', 'service']);
|
|
|
282
287
|
* http: expressHTTPEntrypoint({port: 3000}),
|
|
283
288
|
* service: socketIOServiceEntrypoint({port: 3001})
|
|
284
289
|
* })
|
|
285
|
-
* }
|
|
290
|
+
* },
|
|
291
|
+
* bootstrap: ['entrypoint']
|
|
286
292
|
* }))
|
|
287
293
|
* .onLaunched(async (app: Application): Promise<void> => {
|
|
288
294
|
* const entrypoint: Entrypoint = await app.getObject<Entrypoint>('entrypoint')
|
|
@@ -173,7 +173,7 @@ export type CommanderCLIEntrypointOptions = {
|
|
|
173
173
|
* @CLIAction('greet <name>', GreetDTO)
|
|
174
174
|
* public async greet(input: GreetDTO): Promise<string> {
|
|
175
175
|
* const text: string = `Hello, ${input.name}`
|
|
176
|
-
* return (input.loud ? text.toUpperCase() : text).
|
|
176
|
+
* return Array(input.times ?? 1).fill(input.loud ? text.toUpperCase() : text).join('\n')
|
|
177
177
|
* }
|
|
178
178
|
* }
|
|
179
179
|
*
|
|
@@ -185,7 +185,8 @@ export type CommanderCLIEntrypointOptions = {
|
|
|
185
185
|
* controllers: [GreetController],
|
|
186
186
|
* cli: commanderCLIEntrypoint({bin: 'tool', description: 'A greeting tool', version: '1.0.0'})
|
|
187
187
|
* })
|
|
188
|
-
* }
|
|
188
|
+
* },
|
|
189
|
+
* bootstrap: ['entrypoint']
|
|
189
190
|
* })
|
|
190
191
|
* ```
|
|
191
192
|
*/
|
|
@@ -196,7 +196,7 @@ function errorMessage(error) {
|
|
|
196
196
|
* @CLIAction('greet <name>', GreetDTO)
|
|
197
197
|
* public async greet(input: GreetDTO): Promise<string> {
|
|
198
198
|
* const text: string = `Hello, ${input.name}`
|
|
199
|
-
* return (input.loud ? text.toUpperCase() : text).
|
|
199
|
+
* return Array(input.times ?? 1).fill(input.loud ? text.toUpperCase() : text).join('\n')
|
|
200
200
|
* }
|
|
201
201
|
* }
|
|
202
202
|
*
|
|
@@ -208,7 +208,8 @@ function errorMessage(error) {
|
|
|
208
208
|
* controllers: [GreetController],
|
|
209
209
|
* cli: commanderCLIEntrypoint({bin: 'tool', description: 'A greeting tool', version: '1.0.0'})
|
|
210
210
|
* })
|
|
211
|
-
* }
|
|
211
|
+
* },
|
|
212
|
+
* bootstrap: ['entrypoint']
|
|
212
213
|
* })
|
|
213
214
|
* ```
|
|
214
215
|
*/
|
|
@@ -187,7 +187,8 @@ export declare function ipcEventOfContext<Event extends IPCInvokeEvent = IPCInvo
|
|
|
187
187
|
* controllers: [EchoController],
|
|
188
188
|
* service: electronIPCServiceEntrypoint()
|
|
189
189
|
* })
|
|
190
|
-
* }
|
|
190
|
+
* },
|
|
191
|
+
* bootstrap: ['entrypoint']
|
|
191
192
|
* })
|
|
192
193
|
*
|
|
193
194
|
* //In the preload script of a window
|
|
@@ -20,7 +20,7 @@ export type ExpressHookContext = {
|
|
|
20
20
|
*/
|
|
21
21
|
readonly server: Server;
|
|
22
22
|
/**
|
|
23
|
-
* Each action route mapped to its HTTP methods,
|
|
23
|
+
* Each action route mapped to its HTTP methods, as declared (`'/users/:id'` → `{'get', 'delete'}`).
|
|
24
24
|
*/
|
|
25
25
|
readonly routes: Map<string, Set<string>>;
|
|
26
26
|
/**
|
|
@@ -166,7 +166,8 @@ export type ExpressHTTPEntrypointOptions = {
|
|
|
166
166
|
* fallback: staticUI('./ui/dist')
|
|
167
167
|
* })
|
|
168
168
|
* })
|
|
169
|
-
* }
|
|
169
|
+
* },
|
|
170
|
+
* bootstrap: ['entrypoint']
|
|
170
171
|
* })
|
|
171
172
|
* ```
|
|
172
173
|
*/
|
|
@@ -12,6 +12,7 @@ const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
|
12
12
|
const Result_js_1 = require("../../lib/Result.js");
|
|
13
13
|
const RouteNotFoundException_js_1 = require("../../exceptions/RouteNotFoundException.js");
|
|
14
14
|
const Connections_js_1 = require("../../lib/Connections.js");
|
|
15
|
+
const ExplicitStatus_js_1 = require("../../lib/ExplicitStatus.js");
|
|
15
16
|
const Responders_js_1 = require("./Responders.js");
|
|
16
17
|
/**
|
|
17
18
|
* The first segments of the routes
|
|
@@ -100,7 +101,8 @@ function eventFrame(data, event, id) {
|
|
|
100
101
|
* fallback: staticUI('./ui/dist')
|
|
101
102
|
* })
|
|
102
103
|
* })
|
|
103
|
-
* }
|
|
104
|
+
* },
|
|
105
|
+
* bootstrap: ['entrypoint']
|
|
104
106
|
* })
|
|
105
107
|
* ```
|
|
106
108
|
*/
|
|
@@ -199,6 +201,7 @@ function expressHTTPEntrypoint(options = {}) {
|
|
|
199
201
|
return await sendFile(result, request, response);
|
|
200
202
|
if (result instanceof Result_js_1.StatusResult) {
|
|
201
203
|
response.status(result.status).set(result.headers);
|
|
204
|
+
(0, ExplicitStatus_js_1.markExplicitStatus)(response);
|
|
202
205
|
return respond.success(response, result.data, request);
|
|
203
206
|
}
|
|
204
207
|
respond.success(response, result, request);
|
|
@@ -257,7 +260,10 @@ function expressHTTPEntrypoint(options = {}) {
|
|
|
257
260
|
await options.before?.(app, { module: runtime.module, server: server, routes: routes, actionPrefixes: actionPrefixes, isActionPath: isActionPath });
|
|
258
261
|
if (options.body !== false)
|
|
259
262
|
app.use(express_1.default.json({ limit: options.body?.limit ?? '1mb' }), express_1.default.urlencoded({ extended: true, limit: options.body?.limit ?? '1mb' }));
|
|
260
|
-
|
|
263
|
+
//The HEAD actions first: express answers a HEAD request with the first GET route of its path when no HEAD route
|
|
264
|
+
//comes before it
|
|
265
|
+
const isHead = (action) => String(action.address.method).toLowerCase() === 'head';
|
|
266
|
+
for (const action of [...runtime.actions.filter(isHead), ...runtime.actions.filter((action) => !isHead(action))]) {
|
|
261
267
|
const method = String(action.address.method).toLowerCase();
|
|
262
268
|
const register = app[method];
|
|
263
269
|
if (typeof register !== 'function')
|
|
@@ -6,6 +6,7 @@ const HTTPErrors_js_1 = require("../../lib/HTTPErrors.js");
|
|
|
6
6
|
Object.defineProperty(exports, "describeError", { enumerable: true, get: function () { return HTTPErrors_js_1.describeError; } });
|
|
7
7
|
Object.defineProperty(exports, "statusOfError", { enumerable: true, get: function () { return HTTPErrors_js_1.statusOfError; } });
|
|
8
8
|
const Envelope_js_1 = require("../../lib/Envelope.js");
|
|
9
|
+
const ExplicitStatus_js_1 = require("../../lib/ExplicitStatus.js");
|
|
9
10
|
/**
|
|
10
11
|
* The default responder of {@link expressHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
11
12
|
* status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
|
|
@@ -14,7 +15,7 @@ const Envelope_js_1 = require("../../lib/Envelope.js");
|
|
|
14
15
|
exports.JSONResponder = {
|
|
15
16
|
success: (response, data) => {
|
|
16
17
|
if (data === undefined) {
|
|
17
|
-
response.status(response.statusCode === 200 ? 204 : response.statusCode).end();
|
|
18
|
+
response.status(response.statusCode === 200 && !(0, ExplicitStatus_js_1.hasExplicitStatus)(response) ? 204 : response.statusCode).end();
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
response.json(data);
|
|
@@ -63,7 +64,8 @@ exports.JSONResponder = {
|
|
|
63
64
|
* })
|
|
64
65
|
* })
|
|
65
66
|
* })
|
|
66
|
-
* }
|
|
67
|
+
* },
|
|
68
|
+
* bootstrap: ['entrypoint']
|
|
67
69
|
* })
|
|
68
70
|
* ```
|
|
69
71
|
*/
|