@ololoepepe/controllers 0.2.38 → 0.3.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/CHANGELOG.md +54 -0
- package/README.md +182 -1
- package/dist/node/controllers/index.d.ts +3 -0
- package/dist/node/controllers/index.js +3 -0
- package/dist/node/controllers/json-controller.d.ts +17 -0
- package/dist/node/controllers/json-controller.js +28 -0
- package/dist/node/controllers/no-content-controller.d.ts +13 -0
- package/dist/node/controllers/no-content-controller.js +22 -0
- package/dist/node/controllers/string-controller.d.ts +17 -0
- package/dist/node/controllers/string-controller.js +26 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +1 -0
- package/dist/node/lib/select-status-code.d.ts +6 -0
- package/dist/node/lib/select-status-code.js +12 -0
- package/dist/node/package.json +6 -0
- package/dist/node/types.d.ts +33 -0
- package/dist/node/types.js +8 -0
- package/package.json +32 -15
- package/src/controllers/index.js +0 -5
- package/src/controllers/json-controller.js +0 -32
- package/src/controllers/no-content-controller.js +0 -26
- package/src/controllers/string-controller.js +0 -32
- package/src/index.js +0 -1
- package/src/lib/helper.js +0 -26
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - 2026-08-28
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Rewritten in TypeScript. The package now ships compiled ESM plus type declarations
|
|
15
|
+
under `dist/node/` instead of raw sources, so consumers get types without a separate
|
|
16
|
+
`@types/…` package. Runtime behaviour is unchanged.
|
|
17
|
+
- The request and the response are type parameters defaulting to Express's `Request`
|
|
18
|
+
and `Response`, so callbacks need no annotation on Express and the controllers still
|
|
19
|
+
accept any object of the right shape elsewhere.
|
|
20
|
+
- `stringController` now requires its callback to return a `string` or `undefined`.
|
|
21
|
+
Returning an object used to compile and made Express send JSON from a controller
|
|
22
|
+
whose whole purpose is text.
|
|
23
|
+
- The test suite drives a real Express app over real HTTP instead of asserting on stub
|
|
24
|
+
objects, and covers what was previously untested: failures reaching the error
|
|
25
|
+
middleware, a promise result being awaited, and `stringController` at all — its tests
|
|
26
|
+
existed but were never registered with the runner, and their assertions were wrong.
|
|
27
|
+
- Migrated from mocha and chai to the built-in `node:test` runner.
|
|
28
|
+
- ESLint config replaced with `@ololoepepe/eslint-config-typescript` on the ESLint 10
|
|
29
|
+
flat format, and the code style violations it surfaced were fixed.
|
|
30
|
+
- CI image bumped to `node:24-alpine`; the pipeline now also runs `typecheck` and
|
|
31
|
+
`build`.
|
|
32
|
+
- Dependencies updated.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `engines` field: Node.js >= 24.
|
|
37
|
+
- `exports` map, `sideEffects: false`, and per-target `package.json` in the build
|
|
38
|
+
output that remaps `#src/*.ts` to the compiled files.
|
|
39
|
+
- `npm run build` and `npm run typecheck`.
|
|
40
|
+
- Exported types: `Controller`, `ControllerCallback`, `ControllerRequest`,
|
|
41
|
+
`JsonResponse`, `NextFunction`, `NoContentResponse`, `StringResponse`.
|
|
42
|
+
- Full README, and this changelog.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
|
|
46
|
+
- The `main` field, replaced by the `exports` map. Deep imports into the package are
|
|
47
|
+
no longer possible; import the package root.
|
|
48
|
+
- `body-parser`, `chai`, `chai-http` and `mocha`, along with `.mocharc.yml` and the
|
|
49
|
+
`.ncurc.yml` that existed to hold chai and ESLint back.
|
|
50
|
+
- `.npmignore`, replaced by the `files` field.
|
|
51
|
+
- The runtime check for a non-string request method in the status code helper — the
|
|
52
|
+
type says `string`, and the only caller is Express, which always sets it.
|
|
53
|
+
- The undocumented fallback in `noContentController` where an explicit `statusCode` of
|
|
54
|
+
`0` selected the code by request method instead. `0` is not a status code.
|
package/README.md
CHANGED
|
@@ -1 +1,182 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ololoepepe/controllers
|
|
2
|
+
|
|
3
|
+
Express route handlers built from plain functions.
|
|
4
|
+
|
|
5
|
+
You write a function that takes the request and returns the data. The controller
|
|
6
|
+
sends that data, picks the status code from the HTTP method, and routes any
|
|
7
|
+
failure to your error middleware. What disappears from every route is the
|
|
8
|
+
`try`/`catch` around the handler, the `next(error)` inside it, and the decision
|
|
9
|
+
of whether this particular route answers with 200 or 201.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @ololoepepe/controllers
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node.js >= 24. Written in TypeScript — the declarations ship with the
|
|
18
|
+
package, so there is no `@types/…` to install.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import express, {type Request} from 'express';
|
|
24
|
+
import {jsonController, noContentController, stringController} from '@ololoepepe/controllers';
|
|
25
|
+
|
|
26
|
+
const app = express();
|
|
27
|
+
|
|
28
|
+
app.use(express.json());
|
|
29
|
+
|
|
30
|
+
app.get('/users', jsonController(() => listUsers()));
|
|
31
|
+
app.post('/users', jsonController(request => createUser(request.body)));
|
|
32
|
+
app.delete('/users/:id', noContentController((request: Request<{id: string}>) => deleteUser(request.params.id)));
|
|
33
|
+
app.get('/health', stringController(() => 'ok'));
|
|
34
|
+
|
|
35
|
+
app.use(errorHandler);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`GET /users` answers `200` with the array as JSON. `POST /users` answers `201`
|
|
39
|
+
with the created user. `DELETE /users/:id` answers `204` with an empty body. If
|
|
40
|
+
`createUser` throws or rejects, `errorHandler` gets the error and the client
|
|
41
|
+
never sees a hung request.
|
|
42
|
+
|
|
43
|
+
The callbacks are mostly unannotated and do not need to be: `request` and
|
|
44
|
+
`response` are Express's own `Request` and `Response` by default. The one
|
|
45
|
+
annotation above is about route parameters — see
|
|
46
|
+
[Route parameters](#route-parameters).
|
|
47
|
+
|
|
48
|
+
## The three controllers
|
|
49
|
+
|
|
50
|
+
Each one takes a callback and returns an Express handler. The callback receives
|
|
51
|
+
the request and the response, and may return a promise — the controller waits
|
|
52
|
+
for it.
|
|
53
|
+
|
|
54
|
+
### `jsonController(callback, statusCode?)`
|
|
55
|
+
|
|
56
|
+
Sends the callback's result as JSON. `undefined` is sent as `{}`; `null` is sent
|
|
57
|
+
as `null`, since only `undefined` stands for "nothing to send".
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
app.get('/settings', jsonController(async () => loadSettings()));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `stringController(callback, statusCode?)`
|
|
64
|
+
|
|
65
|
+
Sends the callback's result as text. The callback must return a `string` or
|
|
66
|
+
`undefined`; `undefined` is sent as an empty string.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
app.get('/robots.txt', stringController(() => 'User-agent: *\nDisallow:'));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `noContentController(callback, statusCode = 204)`
|
|
73
|
+
|
|
74
|
+
Runs the callback and answers with a status code and an empty body. Whatever the
|
|
75
|
+
callback returns is discarded.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
app.post('/cache/flush', noContentController(async () => flushCache()));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Status codes
|
|
82
|
+
|
|
83
|
+
`jsonController` and `stringController` choose the code from the request method
|
|
84
|
+
unless you pass one explicitly:
|
|
85
|
+
|
|
86
|
+
| Method | Status code |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `POST`, `PUT` | `201` |
|
|
89
|
+
| everything else | `200` |
|
|
90
|
+
|
|
91
|
+
`noContentController` does not look at the method: it answers `204` unless told
|
|
92
|
+
otherwise.
|
|
93
|
+
|
|
94
|
+
To override, pass the code as the second argument:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
app.post('/jobs', jsonController(request => enqueue(request.body), 202));
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Errors
|
|
101
|
+
|
|
102
|
+
Anything the callback throws or rejects with is passed to `next`, so it reaches
|
|
103
|
+
the next error-handling middleware in the chain. So is a failure of the response
|
|
104
|
+
itself — including the `ERR_HTTP_HEADERS_SENT` you get when the callback has
|
|
105
|
+
already answered on its own. Nothing is swallowed, and nothing escapes as an
|
|
106
|
+
unhandled promise rejection.
|
|
107
|
+
|
|
108
|
+
The library does not format error responses; that is the job of your error
|
|
109
|
+
middleware. [`@ololoepepe/middlewares`](https://www.npmjs.com/package/@ololoepepe/middlewares)
|
|
110
|
+
ships one.
|
|
111
|
+
|
|
112
|
+
## Route parameters
|
|
113
|
+
|
|
114
|
+
Express types `request.params` from the route string, but only when it can see
|
|
115
|
+
the handler and the route together. A controller is built before it reaches
|
|
116
|
+
`app.get`, so that link is broken and `request.params.id` falls back to Express's
|
|
117
|
+
own default, `string | string[]`.
|
|
118
|
+
|
|
119
|
+
Name the parameters on the callback to get them back:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import type {Request} from 'express';
|
|
123
|
+
|
|
124
|
+
app.get('/users/:id/posts/:postId', jsonController((request: Request<{id: string; postId: string}>) => {
|
|
125
|
+
return loadPosts(request.params.id, request.params.postId);
|
|
126
|
+
}));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Properties your own middleware puts on the request are a different matter: they
|
|
130
|
+
belong on Express's `Request` itself, through declaration merging, and then every
|
|
131
|
+
callback sees them with no annotation at all.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
declare global {
|
|
135
|
+
namespace Express {
|
|
136
|
+
interface Request {
|
|
137
|
+
currentUser: User;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
app.get('/me', jsonController(request => request.currentUser));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Types
|
|
146
|
+
|
|
147
|
+
The request and the response are type parameters, defaulting to Express's
|
|
148
|
+
`Request` and `Response`. On Express you rarely name them. Anywhere else, pass
|
|
149
|
+
your own shapes — the controllers only require what they actually use:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import {jsonController, type ControllerRequest, type JsonResponse} from '@ololoepepe/controllers';
|
|
153
|
+
|
|
154
|
+
interface TenantRequest extends ControllerRequest {
|
|
155
|
+
tenantId: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const controller = jsonController<TenantRequest, JsonResponse>(request => loadTenant(request.tenantId));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Exported types: `Controller`, `ControllerCallback`, `ControllerRequest`,
|
|
162
|
+
`JsonResponse`, `NextFunction`, `NoContentResponse`, `StringResponse`.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
| Command | What it does |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `npm run lint` | ESLint over the whole repository. |
|
|
169
|
+
| `npm run typecheck` | `tsc` over `src`, `test`, `test-support` and `scripts`, no emit. |
|
|
170
|
+
| `npm test` | The `node:test` suite; Node runs the TypeScript sources directly. |
|
|
171
|
+
| `npm run build` | Compiles `src` into `dist/node/` — the ESM and `.d.ts` that get published. |
|
|
172
|
+
|
|
173
|
+
The tests drive a real Express app over real HTTP, so they assert what a client
|
|
174
|
+
sees rather than which methods were called on a stub.
|
|
175
|
+
|
|
176
|
+
Internal imports go through the `#src/*.ts` subpath map rather than relative
|
|
177
|
+
paths. `dist/node/` gets its own `package.json` remapping `#src/*.ts` to the
|
|
178
|
+
compiled files, which is what makes those imports resolve for consumers.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
UNLICENSED — private package.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
import type { Controller, ControllerCallback, ControllerRequest, JsonResponse } from '#src/types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет его результат
|
|
5
|
+
* в виде JSON.
|
|
6
|
+
*
|
|
7
|
+
* Если `callback` вернул `undefined`, вместо него отправляется пустой объект `{}`. Если вернул
|
|
8
|
+
* промис — контроллер дожидается его результата.
|
|
9
|
+
*
|
|
10
|
+
* Код ответа по умолчанию — 201 для POST и PUT и 200 для остальных запросов.
|
|
11
|
+
*
|
|
12
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
13
|
+
* ошибок в цепочке.
|
|
14
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
15
|
+
* @param statusCode - код ответа, если нужен не тот, что выбирается по методу запроса
|
|
16
|
+
*/
|
|
17
|
+
export default function jsonController<TRequest extends ControllerRequest = Request, TResponse extends JsonResponse = Response>(callback: ControllerCallback<TRequest, TResponse>, statusCode?: number): Controller<TRequest, TResponse>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import selectStatusCode from '#src/lib/select-status-code.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет его результат
|
|
4
|
+
* в виде JSON.
|
|
5
|
+
*
|
|
6
|
+
* Если `callback` вернул `undefined`, вместо него отправляется пустой объект `{}`. Если вернул
|
|
7
|
+
* промис — контроллер дожидается его результата.
|
|
8
|
+
*
|
|
9
|
+
* Код ответа по умолчанию — 201 для POST и PUT и 200 для остальных запросов.
|
|
10
|
+
*
|
|
11
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
12
|
+
* ошибок в цепочке.
|
|
13
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
14
|
+
* @param statusCode - код ответа, если нужен не тот, что выбирается по методу запроса
|
|
15
|
+
*/
|
|
16
|
+
export default function jsonController(callback, statusCode) {
|
|
17
|
+
return async (request, response, next) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await callback(request, response);
|
|
20
|
+
// Пустым объектом подменяется только `undefined`: `null` — осмысленное тело ответа
|
|
21
|
+
const body = (result === undefined) ? {} : result;
|
|
22
|
+
response.status(statusCode ?? selectStatusCode(request.method)).json(body);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
next(error);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
import type { Controller, ControllerCallback, ControllerRequest, NoContentResponse } from '#src/types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет один только код
|
|
5
|
+
* ответа. Тело ответа остается пустым, что бы `callback` ни вернул. Если он вернул промис —
|
|
6
|
+
* контроллер дожидается его результата.
|
|
7
|
+
*
|
|
8
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
9
|
+
* ошибок в цепочке.
|
|
10
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
11
|
+
* @param statusCode - код ответа (по умолчанию 204)
|
|
12
|
+
*/
|
|
13
|
+
export default function noContentController<TRequest extends ControllerRequest = Request, TResponse extends NoContentResponse = Response>(callback: ControllerCallback<TRequest, TResponse>, statusCode?: number): Controller<TRequest, TResponse>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const NO_CONTENT = 204;
|
|
2
|
+
/**
|
|
3
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет один только код
|
|
4
|
+
* ответа. Тело ответа остается пустым, что бы `callback` ни вернул. Если он вернул промис —
|
|
5
|
+
* контроллер дожидается его результата.
|
|
6
|
+
*
|
|
7
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
8
|
+
* ошибок в цепочке.
|
|
9
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
10
|
+
* @param statusCode - код ответа (по умолчанию 204)
|
|
11
|
+
*/
|
|
12
|
+
export default function noContentController(callback, statusCode = NO_CONTENT) {
|
|
13
|
+
return async (request, response, next) => {
|
|
14
|
+
try {
|
|
15
|
+
await callback(request, response);
|
|
16
|
+
response.sendStatus(statusCode);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
next(error);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
import type { Controller, ControllerCallback, ControllerRequest, StringResponse } from '#src/types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет его результат
|
|
5
|
+
* в виде строки.
|
|
6
|
+
*
|
|
7
|
+
* Если `callback` вернул `undefined`, отправляется пустая строка. Если вернул промис —
|
|
8
|
+
* контроллер дожидается его результата.
|
|
9
|
+
*
|
|
10
|
+
* Код ответа по умолчанию — 201 для POST и PUT и 200 для остальных запросов.
|
|
11
|
+
*
|
|
12
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
13
|
+
* ошибок в цепочке.
|
|
14
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
15
|
+
* @param statusCode - код ответа, если нужен не тот, что выбирается по методу запроса
|
|
16
|
+
*/
|
|
17
|
+
export default function stringController<TRequest extends ControllerRequest = Request, TResponse extends StringResponse = Response>(callback: ControllerCallback<TRequest, TResponse, string | undefined>, statusCode?: number): Controller<TRequest, TResponse>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import selectStatusCode from '#src/lib/select-status-code.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Создает контроллер, который вызывает `callback` на каждый запрос и отправляет его результат
|
|
4
|
+
* в виде строки.
|
|
5
|
+
*
|
|
6
|
+
* Если `callback` вернул `undefined`, отправляется пустая строка. Если вернул промис —
|
|
7
|
+
* контроллер дожидается его результата.
|
|
8
|
+
*
|
|
9
|
+
* Код ответа по умолчанию — 201 для POST и PUT и 200 для остальных запросов.
|
|
10
|
+
*
|
|
11
|
+
* Ошибка `callback`, как и ошибка самой отправки ответа, передается следующему обработчику
|
|
12
|
+
* ошибок в цепочке.
|
|
13
|
+
* @param callback - функция, вызываемая на входящий запрос
|
|
14
|
+
* @param statusCode - код ответа, если нужен не тот, что выбирается по методу запроса
|
|
15
|
+
*/
|
|
16
|
+
export default function stringController(callback, statusCode) {
|
|
17
|
+
return async (request, response, next) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await callback(request, response);
|
|
20
|
+
response.status(statusCode ?? selectStatusCode(request.method)).send(result ?? '');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
next(error);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { jsonController, noContentController, stringController } from '#src/controllers/index.ts';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const CREATED = 201;
|
|
2
|
+
const OK = 200;
|
|
3
|
+
// Методы, для которых успешный ответ означает, что что-то было создано
|
|
4
|
+
const CREATING_METHODS = new Set(['POST', 'PUT']);
|
|
5
|
+
/**
|
|
6
|
+
* Возвращает код ответа, подходящий методу запроса: 201 для POST и PUT, 200 для остальных.
|
|
7
|
+
* Регистр не имеет значения.
|
|
8
|
+
* @param method - метод HTTP-запроса
|
|
9
|
+
*/
|
|
10
|
+
export default function selectStatusCode(method) {
|
|
11
|
+
return CREATING_METHODS.has(method.toUpperCase()) ? CREATED : OK;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Общие типы контроллеров.
|
|
3
|
+
*
|
|
4
|
+
* Запрос и ответ везде остаются параметрами типа. По умолчанию это `Request` и `Response` из
|
|
5
|
+
* Express, поэтому в обычном случае аннотировать ничего не нужно. Подставить можно любые объекты
|
|
6
|
+
* подходящей формы — из другого фреймворка или из теста.
|
|
7
|
+
*/
|
|
8
|
+
/** Продолжение цепочки Express. Аргумент — ошибка, если она произошла. */
|
|
9
|
+
export type NextFunction = (error?: unknown) => void;
|
|
10
|
+
/** Минимальная часть запроса, которая нужна контроллерам: по методу выбирается код ответа. */
|
|
11
|
+
export interface ControllerRequest {
|
|
12
|
+
method: string;
|
|
13
|
+
}
|
|
14
|
+
/** Ответ, умеющий отдать тело в виде JSON. */
|
|
15
|
+
export interface JsonResponse {
|
|
16
|
+
status: (statusCode: number) => {
|
|
17
|
+
json: (body: unknown) => unknown;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Ответ, умеющий отдать тело в виде строки. */
|
|
21
|
+
export interface StringResponse {
|
|
22
|
+
status: (statusCode: number) => {
|
|
23
|
+
send: (body: string) => unknown;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Ответ, умеющий отдать один только код без тела. */
|
|
27
|
+
export interface NoContentResponse {
|
|
28
|
+
sendStatus: (statusCode: number) => unknown;
|
|
29
|
+
}
|
|
30
|
+
/** Готовый обработчик Express — то, что возвращает каждый контроллер. */
|
|
31
|
+
export type Controller<TRequest, TResponse> = (request: TRequest, response: TResponse, next: NextFunction) => Promise<void>;
|
|
32
|
+
/** Функция, которую контроллер вызывает на каждый входящий запрос. */
|
|
33
|
+
export type ControllerCallback<TRequest, TResponse, TResult = unknown> = (request: TRequest, response: TResponse) => Promise<TResult> | TResult;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Общие типы контроллеров.
|
|
3
|
+
*
|
|
4
|
+
* Запрос и ответ везде остаются параметрами типа. По умолчанию это `Request` и `Response` из
|
|
5
|
+
* Express, поэтому в обычном случае аннотировать ничего не нужно. Подставить можно любые объекты
|
|
6
|
+
* подходящей формы — из другого фреймворка или из теста.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ololoepepe/controllers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Common Express controllers library",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
6
|
"imports": {
|
|
7
|
-
"#src/*.
|
|
7
|
+
"#src/*.ts": "./src/*.ts",
|
|
8
|
+
"#test-support/*.ts": "./test-support/*.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/node/index.d.ts",
|
|
13
|
+
"default": "./dist/node/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=24"
|
|
8
20
|
},
|
|
9
|
-
"type": "module",
|
|
10
21
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
22
|
+
"build": "npm run build:node",
|
|
23
|
+
"build:node": "node scripts/build-node.ts",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"prepack": "npm run build",
|
|
26
|
+
"test": "node --test",
|
|
27
|
+
"typecheck": "tsc"
|
|
13
28
|
},
|
|
14
29
|
"publishConfig": {
|
|
15
30
|
"registry": "https://registry.npmjs.com"
|
|
16
31
|
},
|
|
17
32
|
"files": [
|
|
18
|
-
"
|
|
33
|
+
"dist",
|
|
19
34
|
"package.json",
|
|
20
|
-
"README.md"
|
|
35
|
+
"README.md",
|
|
36
|
+
"CHANGELOG.md"
|
|
21
37
|
],
|
|
22
38
|
"repository": {
|
|
23
39
|
"type": "git",
|
|
@@ -29,13 +45,14 @@
|
|
|
29
45
|
"url": "https://gitlab.void-walkers.com/libs/node.js/controllers/issues"
|
|
30
46
|
},
|
|
31
47
|
"homepage": "https://gitlab.void-walkers.com/libs/node.js/controllers#README",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@types/express": "^5.0.6"
|
|
50
|
+
},
|
|
32
51
|
"devDependencies": {
|
|
33
|
-
"@ololoepepe/eslint-config": "^0.0
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"express": "^5.1.0",
|
|
39
|
-
"mocha": "^11.4.0"
|
|
52
|
+
"@ololoepepe/eslint-config-typescript": "^0.2.0",
|
|
53
|
+
"@types/node": "^26.4.0",
|
|
54
|
+
"eslint": "^10.9.1",
|
|
55
|
+
"express": "^5.2.1",
|
|
56
|
+
"typescript": "^6.0.3"
|
|
40
57
|
}
|
|
41
58
|
}
|
package/src/controllers/index.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {selectStatusCode} from '#src/lib/helper.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Создает функцию-контроллер, вызывающую переданный callback при запросах
|
|
5
|
-
* и отправляющую результат ее выполнения в виде JSON.
|
|
6
|
-
*
|
|
7
|
-
* Если функция-callback возвращает undefined, вместо этого значения подставляется пустой объект {}.
|
|
8
|
-
*
|
|
9
|
-
* Если функция-callback возвращает экземпляр Promise, контроллер будет ожидать его результат.
|
|
10
|
-
*
|
|
11
|
-
* По умолчанию HTTP-код ответа (statusCode) устанавливается в 201, если запрос POST или PUT,
|
|
12
|
-
* и в 200 в остальных случаях.
|
|
13
|
-
*
|
|
14
|
-
* При ошибке вызывается следующий middleware или контроллер в цепочке.
|
|
15
|
-
*
|
|
16
|
-
* @param {function} callback - функция, вызываемая для входящих запросов
|
|
17
|
-
* @param {number} [statusCode] - HTTP-код ответа (по умолчанию 201 для POST и PUT и 200 для остальных запросов)
|
|
18
|
-
*
|
|
19
|
-
* @return {function} - функция-контроллер
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export default (callback, statusCode) => async (req, res, next) => {
|
|
23
|
-
try {
|
|
24
|
-
const result = await callback(req, res);
|
|
25
|
-
|
|
26
|
-
const json = (result === undefined) ? {} : result;
|
|
27
|
-
|
|
28
|
-
res.status(statusCode || selectStatusCode(req.method)).json(json);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
next(err);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {selectStatusCode} from '#src/lib/helper.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Создает функцию-контроллер, вызывающую переданный callback при запросах
|
|
5
|
-
* и устанавливающую код ответа после ее выполнения. Тело ответа оставляется пустым.
|
|
6
|
-
* Если функция-callback возвращает экземпляр Promise, контроллер будет ожидать его результат.
|
|
7
|
-
*
|
|
8
|
-
* По умолчанию HTTP-код ответа (statusCode) устанавливается в 204.
|
|
9
|
-
*
|
|
10
|
-
* При ошибке вызывается следующий middleware или контроллер в цепочке.
|
|
11
|
-
*
|
|
12
|
-
* @param {function} callback - функция, вызываемая для входящих запросов
|
|
13
|
-
* @param {number} [statusCode=204] - HTTP-код ответа
|
|
14
|
-
*
|
|
15
|
-
* @return {function} - функция-контроллер
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export default (callback, statusCode = 204) => async (req, res, next) => {
|
|
19
|
-
try {
|
|
20
|
-
await callback(req, res);
|
|
21
|
-
|
|
22
|
-
res.sendStatus(statusCode || selectStatusCode(req.method));
|
|
23
|
-
} catch (err) {
|
|
24
|
-
next(err);
|
|
25
|
-
}
|
|
26
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {selectStatusCode} from '#src/lib/helper.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Создает функцию-контроллер, вызывающую переданный callback при запросах
|
|
5
|
-
* и отправляющую результат ее выполнения в виде строки.
|
|
6
|
-
*
|
|
7
|
-
* Если функция-callback возвращает undefined, вместо этого значения подставляется пустая строка.
|
|
8
|
-
*
|
|
9
|
-
* Если функция-callback возвращает экземпляр Promise, контроллер будет ожидать его результат.
|
|
10
|
-
*
|
|
11
|
-
* По умолчанию HTTP-код ответа (statusCode) устанавливается в 201, если запрос POST или PUT,
|
|
12
|
-
* и в 200 в остальных случаях.
|
|
13
|
-
*
|
|
14
|
-
* При ошибке вызывается следующий middleware или контроллер в цепочке.
|
|
15
|
-
*
|
|
16
|
-
* @param {function} callback - функция, вызываемая для входящих запросов
|
|
17
|
-
* @param {number} [statusCode] - HTTP-код ответа (по умолчанию 201 для POST и PUT и 200 для остальных запросов)
|
|
18
|
-
*
|
|
19
|
-
* @return {function} - функция-контроллер
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export default (callback, statusCode) => async (req, res, next) => {
|
|
23
|
-
try {
|
|
24
|
-
const result = await callback(req, res);
|
|
25
|
-
|
|
26
|
-
const string = (result === undefined) ? '' : result;
|
|
27
|
-
|
|
28
|
-
res.status(statusCode || selectStatusCode(req.method)).send(string);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
next(err);
|
|
31
|
-
}
|
|
32
|
-
};
|
package/src/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {jsonController, noContentController, stringController} from './controllers/index.js';
|
package/src/lib/helper.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Возвращает HTTP-код ответа в зависимости от типа запроса:
|
|
3
|
-
* 201, если запрос POST или PUT, и в 200 в остальных случаях.
|
|
4
|
-
*
|
|
5
|
-
* Не зависит от регистра.
|
|
6
|
-
*
|
|
7
|
-
* @param {string} method - тип HTTP-запроса
|
|
8
|
-
*
|
|
9
|
-
* @return {number} - HTTP-код ответа
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export function selectStatusCode(method) {
|
|
13
|
-
if (typeof method === 'string') {
|
|
14
|
-
method = method.toUpperCase();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
switch (method) {
|
|
18
|
-
case 'POST':
|
|
19
|
-
case 'PUT':
|
|
20
|
-
return 201;
|
|
21
|
-
case 'GET':
|
|
22
|
-
case 'DELETE':
|
|
23
|
-
default:
|
|
24
|
-
return 200;
|
|
25
|
-
}
|
|
26
|
-
}
|