@ddd-ts-kit/http 0.7.0 → 0.9.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 +107 -4
- package/dist/controller/controller-request.d.ts +7 -1
- package/dist/controller/controller-request.d.ts.map +1 -1
- package/dist/controller/controller-routes.d.ts.map +1 -1
- package/dist/controller/controller-routes.js +22 -3
- package/dist/controller/controller-routes.js.map +1 -1
- package/dist/controller/controller-spec.d.ts +7 -0
- package/dist/controller/controller-spec.d.ts.map +1 -1
- package/dist/controller/guard-refusals.d.ts +9 -0
- package/dist/controller/guard-refusals.d.ts.map +1 -0
- package/dist/controller/guard-refusals.js +16 -0
- package/dist/controller/guard-refusals.js.map +1 -0
- package/dist/controller/guards/has-scope.d.ts +10 -0
- package/dist/controller/guards/has-scope.d.ts.map +1 -0
- package/dist/controller/guards/has-scope.js +27 -0
- package/dist/controller/guards/has-scope.js.map +1 -0
- package/dist/controller/guards/tenant-member.d.ts +9 -0
- package/dist/controller/guards/tenant-member.d.ts.map +1 -0
- package/dist/controller/guards/tenant-member.js +22 -0
- package/dist/controller/guards/tenant-member.js.map +1 -0
- package/dist/controller/http-request.d.ts +9 -3
- package/dist/controller/http-request.d.ts.map +1 -1
- package/dist/error/default-error-mapping.d.ts.map +1 -1
- package/dist/error/default-error-mapping.js +3 -0
- package/dist/error/default-error-mapping.js.map +1 -1
- package/dist/express/mount-express.d.ts +2 -0
- package/dist/express/mount-express.d.ts.map +1 -1
- package/dist/express/mount-express.js +9 -1
- package/dist/express/mount-express.js.map +1 -1
- package/dist/fastify/mount-fastify.d.ts +5 -1
- package/dist/fastify/mount-fastify.d.ts.map +1 -1
- package/dist/fastify/mount-fastify.js +26 -3
- package/dist/fastify/mount-fastify.js.map +1 -1
- package/dist/files/signed-file-routes-options.d.ts +5 -0
- package/dist/files/signed-file-routes-options.d.ts.map +1 -0
- package/dist/files/signed-file-routes-options.js +2 -0
- package/dist/files/signed-file-routes-options.js.map +1 -0
- package/dist/files/signed-file-routes.d.ts +14 -0
- package/dist/files/signed-file-routes.d.ts.map +1 -0
- package/dist/files/signed-file-routes.js +69 -0
- package/dist/files/signed-file-routes.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi/path-items.d.ts.map +1 -1
- package/dist/openapi/path-items.js +6 -8
- package/dist/openapi/path-items.js.map +1 -1
- package/dist/openapi/request-body.d.ts +6 -0
- package/dist/openapi/request-body.d.ts.map +1 -0
- package/dist/openapi/request-body.js +36 -0
- package/dist/openapi/request-body.js.map +1 -0
- package/dist/openapi/responses.d.ts.map +1 -1
- package/dist/openapi/responses.js +4 -1
- package/dist/openapi/responses.js.map +1 -1
- package/dist/pipeline/execute-options.d.ts +19 -0
- package/dist/pipeline/execute-options.d.ts.map +1 -1
- package/dist/pipeline/execute-route.d.ts.map +1 -1
- package/dist/pipeline/execute-route.js +44 -13
- package/dist/pipeline/execute-route.js.map +1 -1
- package/dist/pipeline/execution-of-request.d.ts +2 -0
- package/dist/pipeline/execution-of-request.d.ts.map +1 -1
- package/dist/pipeline/execution-of-request.js +1 -0
- package/dist/pipeline/execution-of-request.js.map +1 -1
- package/dist/pipeline/guard-refusal.d.ts +8 -0
- package/dist/pipeline/guard-refusal.d.ts.map +1 -0
- package/dist/pipeline/guard-refusal.js +2 -0
- package/dist/pipeline/guard-refusal.js.map +1 -0
- package/dist/pipeline/raw-request.d.ts +3 -0
- package/dist/pipeline/raw-request.d.ts.map +1 -1
- package/dist/pipeline/receive-upload.d.ts +12 -0
- package/dist/pipeline/receive-upload.d.ts.map +1 -0
- package/dist/pipeline/receive-upload.js +12 -0
- package/dist/pipeline/receive-upload.js.map +1 -0
- package/dist/pipeline/received-upload.d.ts +7 -0
- package/dist/pipeline/received-upload.d.ts.map +1 -0
- package/dist/pipeline/received-upload.js +2 -0
- package/dist/pipeline/received-upload.js.map +1 -0
- package/dist/pipeline/upload-reader.d.ts +9 -0
- package/dist/pipeline/upload-reader.d.ts.map +1 -0
- package/dist/pipeline/upload-reader.js +2 -0
- package/dist/pipeline/upload-reader.js.map +1 -0
- package/dist/route/header-of.d.ts +4 -0
- package/dist/route/header-of.d.ts.map +1 -0
- package/dist/route/header-of.js +6 -0
- package/dist/route/header-of.js.map +1 -0
- package/dist/route/incoming-file.d.ts +7 -0
- package/dist/route/incoming-file.d.ts.map +1 -0
- package/dist/route/incoming-file.js +2 -0
- package/dist/route/incoming-file.js.map +1 -0
- package/dist/route/request-context.d.ts +3 -0
- package/dist/route/request-context.d.ts.map +1 -1
- package/dist/route/route-input.d.ts +3 -0
- package/dist/route/route-input.d.ts.map +1 -1
- package/dist/route/route-spec.d.ts +5 -0
- package/dist/route/route-spec.d.ts.map +1 -1
- package/dist/route/upload-spec.d.ts +12 -0
- package/dist/route/upload-spec.d.ts.map +1 -0
- package/dist/route/upload-spec.js +2 -0
- package/dist/route/upload-spec.js.map +1 -0
- package/dist/tenancy/first-tenant-of.d.ts +4 -0
- package/dist/tenancy/first-tenant-of.d.ts.map +1 -0
- package/dist/tenancy/first-tenant-of.js +13 -0
- package/dist/tenancy/first-tenant-of.js.map +1 -0
- package/dist/tenancy/resolved-tenant.d.ts +22 -0
- package/dist/tenancy/resolved-tenant.d.ts.map +1 -0
- package/dist/tenancy/resolved-tenant.js +29 -0
- package/dist/tenancy/resolved-tenant.js.map +1 -0
- package/dist/tenancy/tenant-from-claim.d.ts +4 -0
- package/dist/tenancy/tenant-from-claim.d.ts.map +1 -0
- package/dist/tenancy/tenant-from-claim.js +5 -0
- package/dist/tenancy/tenant-from-claim.js.map +1 -0
- package/dist/tenancy/tenant-from-header.d.ts +4 -0
- package/dist/tenancy/tenant-from-header.d.ts.map +1 -0
- package/dist/tenancy/tenant-from-header.js +9 -0
- package/dist/tenancy/tenant-from-header.js.map +1 -0
- package/dist/tenancy/tenant-from-path.d.ts +4 -0
- package/dist/tenancy/tenant-from-path.d.ts.map +1 -0
- package/dist/tenancy/tenant-from-path.js +8 -0
- package/dist/tenancy/tenant-from-path.js.map +1 -0
- package/dist/tenancy/tenant-from-subdomain.d.ts +8 -0
- package/dist/tenancy/tenant-from-subdomain.d.ts.map +1 -0
- package/dist/tenancy/tenant-from-subdomain.js +17 -0
- package/dist/tenancy/tenant-from-subdomain.js.map +1 -0
- package/dist/tenancy/tenant-request.d.ts +10 -0
- package/dist/tenancy/tenant-request.d.ts.map +1 -0
- package/dist/tenancy/tenant-request.js +2 -0
- package/dist/tenancy/tenant-request.js.map +1 -0
- package/dist/tenancy/tenant-resolver.d.ts +8 -0
- package/dist/tenancy/tenant-resolver.d.ts.map +1 -0
- package/dist/tenancy/tenant-resolver.js +2 -0
- package/dist/tenancy/tenant-resolver.js.map +1 -0
- package/dist/tenancy/unknown-tenant-error.d.ts +6 -0
- package/dist/tenancy/unknown-tenant-error.d.ts.map +1 -0
- package/dist/tenancy/unknown-tenant-error.js +6 -0
- package/dist/tenancy/unknown-tenant-error.js.map +1 -0
- package/dist/uploads/missing-file.d.ts +4 -0
- package/dist/uploads/missing-file.d.ts.map +1 -0
- package/dist/uploads/missing-file.js +6 -0
- package/dist/uploads/missing-file.js.map +1 -0
- package/dist/uploads/multipart-limits.d.ts +7 -0
- package/dist/uploads/multipart-limits.d.ts.map +1 -0
- package/dist/uploads/multipart-limits.js +2 -0
- package/dist/uploads/multipart-limits.js.map +1 -0
- package/dist/uploads/multipart-uploads.d.ts +8 -0
- package/dist/uploads/multipart-uploads.d.ts.map +1 -0
- package/dist/uploads/multipart-uploads.js +9 -0
- package/dist/uploads/multipart-uploads.js.map +1 -0
- package/dist/uploads/read-multipart.d.ts +13 -0
- package/dist/uploads/read-multipart.d.ts.map +1 -0
- package/dist/uploads/read-multipart.js +65 -0
- package/dist/uploads/read-multipart.js.map +1 -0
- package/dist/uploads.d.ts +3 -0
- package/dist/uploads.d.ts.map +1 -0
- package/dist/uploads.js +2 -0
- package/dist/uploads.js.map +1 -0
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ const routes = controllerRoutes([PlaceOrderController], { resolve: application.r
|
|
|
87
87
|
| `Controller(spec)` | the decorator of a controller: `group`, `method`, `path` (after the prefix of the group), `operationId` (the class name without `Controller` by default), `summary`, `description`, `tags`, `params`, `query`, `headers`, `body`, `response: { status, description?, body? }`, `errors`, `guards`, `deprecated` |
|
|
88
88
|
| `RouteGroup({ parent, prefix, tags, guards, errors })` | names a set of routes on an empty class; a child inherits from its `parent` and adds |
|
|
89
89
|
| `Guard` | the interface `{ documents?, check(request: GuardRequest) }`, and the decorator of a guard class that takes dependencies |
|
|
90
|
-
| `Authenticated`, `Public`, `HasRole(...roles)` | the guards the package ships, over the `Actor` of the request: 401 to the anonymous caller, 403 without one of the roles; `Public` lets everybody in and stands alone |
|
|
90
|
+
| `Authenticated`, `Public`, `HasRole(...roles)`, `HasScope(...scopes)`, `TenantMember` | the guards the package ships, over the `Actor` and the `Session` of the request: 401 to the anonymous caller, 403 without one of the roles, without every scope named (`Session.scopes`, typed by the `SessionScopes` of the project) or outside the tenant of the request; `Public` lets everybody in and stands alone. A refusal of a guard is reported to `onGuardRefusal` |
|
|
91
91
|
| `RequestMapper<Request, Message>`, `Serializer<Result, Body>` | the interfaces of the two optional translators (`map`, `serialize`), and the decorators that declare them |
|
|
92
92
|
| `HttpRequest<{ params, query, headers, body }>` | the request as `handle`, a mapper or a guard names it, with `actor` and `context` |
|
|
93
93
|
| `controllerRoutes(controllers, { resolve, security, guards })` | the `Route` of each controller, given as a class (built by `resolve`) or as an instance; `security` names the schemes of the application (`['bearer']` by default); `guards: 'skip'` is for the spec of a controller alone |
|
|
@@ -176,6 +176,16 @@ const options: ExecuteOptions = {
|
|
|
176
176
|
};
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
+
`onGuardRefusal(refusal)` is told, and awaited, every request a guard refused before its
|
|
180
|
+
controller (`HasRole`, `HasScope`, `TenantMember`, a guard of the project, and a tenant the caller
|
|
181
|
+
does not belong to): its operation, its problem and its execution context. Those refusals reach
|
|
182
|
+
no use case, so the authorization hook of `@ddd-ts-kit/auth` never sees them;
|
|
183
|
+
`auditGuardRefusals(trail, { clock, ids })` of `@ddd-ts-kit/audit` writes each one to the audit
|
|
184
|
+
trail as a denied decision (an anonymous caller's excepted). Only a refusal is reported, a
|
|
185
|
+
`DomainError` such as `UnauthorizedError` or `ForbiddenError`: a guard that fails with another
|
|
186
|
+
error is a fault, answered 500 and not reported. A refusal of a handler is not reported there,
|
|
187
|
+
and a hook that fails does not change the answer.
|
|
188
|
+
|
|
179
189
|
`around(route, respond)` wraps the whole handling of a request, from authentication to the
|
|
180
190
|
answer, refusals included, and is told the route that was matched (`method`, `path` as
|
|
181
191
|
declared, `operationId`). It is where a tracer names a request `POST /orders/:id/lines` instead
|
|
@@ -194,7 +204,7 @@ as `context.execution`:
|
|
|
194
204
|
| `correlationId` | the incoming `x-correlation-id` when well formed (letters, digits, `._:-`, at most 128), else the request id; echoed in the `x-correlation-id` response header, on problems too, and given to `onError` |
|
|
195
205
|
| `origin` | `http` |
|
|
196
206
|
| `actor`, `session` | from what `resolvePrincipal` answered, read structurally: a `Session` gives both, an `Actor` gives the actor, anything else is the anonymous actor |
|
|
197
|
-
| `tenantId` | the tenant of the actor |
|
|
207
|
+
| `tenantId` | the tenant `resolveTenant` names, once the caller is known to belong to it; the tenant of the actor otherwise |
|
|
198
208
|
| `locale` | the locale of the session, else the preferred language of `Accept-Language` |
|
|
199
209
|
| `deadline` | arrival plus `requestTimeoutMs`, when the option is set |
|
|
200
210
|
|
|
@@ -219,6 +229,36 @@ option that says when a request arrived, the system clock by default.
|
|
|
219
229
|
Use cases still receive the actor explicitly: `new PlaceOrder(context.execution.actor, body)`,
|
|
220
230
|
or `actorOf(context.principal)` of `@ddd-ts-kit/auth`, which reads the same actor.
|
|
221
231
|
|
|
232
|
+
### The tenant of a request
|
|
233
|
+
|
|
234
|
+
A multi-tenant application says where a request names its tenant with `resolveTenant`:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { firstTenantOf, tenantFromClaim, tenantFromSubdomain } from '@ddd-ts-kit/http';
|
|
238
|
+
|
|
239
|
+
const options: ExecuteOptions = {
|
|
240
|
+
resolveTenant: firstTenantOf(tenantFromSubdomain('app.example.com'), tenantFromClaim()),
|
|
241
|
+
// or tenantFromHeader('x-tenant-id'), tenantFromPath('tenantId')
|
|
242
|
+
};
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| Resolver | Names |
|
|
246
|
+
|---|---|
|
|
247
|
+
| `tenantFromSubdomain(baseDomain)` | the one label left of the base domain in `Host` (`acme.app.example.com`); behind a proxy, have it pass the original host |
|
|
248
|
+
| `tenantFromHeader(name?)` | the value of a header, `x-tenant-id` by default |
|
|
249
|
+
| `tenantFromPath(parameter?)` | a path parameter, `tenantId` by default |
|
|
250
|
+
| `tenantFromClaim()` | the tenant of the credential, `Actor.tenantId` |
|
|
251
|
+
| `firstTenantOf(...resolvers)` | the first that names one |
|
|
252
|
+
|
|
253
|
+
The pipeline then checks the membership itself, before any guard or handler: an authenticated
|
|
254
|
+
caller that does not belong to the tenant named (its `tenantId`, or its `tenants` attribute for an
|
|
255
|
+
actor of several tenants) is answered 403, so a header or a subdomain chosen by a client never
|
|
256
|
+
opens another tenant's data. An anonymous caller keeps the tenant it addressed, for the public
|
|
257
|
+
routes of a tenant. The guard `TenantMember` puts a group of routes behind a tenant: 401 to the
|
|
258
|
+
anonymous caller, 403 to a request in no tenant or of another tenant. Platform staff do not pass
|
|
259
|
+
it: they reach tenants through an elevation of the kernel (`acrossTenants`), behind guards of their
|
|
260
|
+
own.
|
|
261
|
+
|
|
222
262
|
## In the composition root
|
|
223
263
|
|
|
224
264
|
The package binds and resolves nothing: routes are a plain list and `ExecuteOptions` a plain
|
|
@@ -494,9 +534,72 @@ one document at a time: `/docs` lists their pages); one document is served as
|
|
|
494
534
|
`openApiDocsRoutes` serves it. A generated project reads the documents from
|
|
495
535
|
`openapi.documents` of `ddd-ts-kit.config.json`, through `src/platform/http/openapi-documents.ts`.
|
|
496
536
|
|
|
537
|
+
## Uploads and files
|
|
538
|
+
|
|
539
|
+
A controller that receives a file declares it, and the declaration is the contract: the field,
|
|
540
|
+
the most it may weigh and the media types it may be, which the OpenAPI document states
|
|
541
|
+
(`multipart/form-data`, `x-max-bytes`, `x-media-types`, the 413, 415 and 422 refusals).
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
@Controller({
|
|
545
|
+
group: AppointmentRoutes,
|
|
546
|
+
method: 'POST',
|
|
547
|
+
path: '/:appointmentId/document',
|
|
548
|
+
summary: 'Attach a document to an appointment',
|
|
549
|
+
params,
|
|
550
|
+
body, // the text fields, sent before the file
|
|
551
|
+
upload: { field: 'document', maxBytes: 10_000_000, mediaTypes: ['application/pdf', 'image/*'] },
|
|
552
|
+
response: { status: 201, body: attached },
|
|
553
|
+
})
|
|
554
|
+
export class AttachDocumentController {
|
|
555
|
+
constructor(private readonly commands: CommandBus) {}
|
|
556
|
+
|
|
557
|
+
async handle({ params, upload, actor }: HttpRequest<{ params: Params; upload: IncomingFile }>) {
|
|
558
|
+
return this.commands.execute(new AttachDocument(actor, params.appointmentId, upload));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
The routes are mounted with an upload reader, `multipartUploads()` of `@ddd-ts-kit/http/uploads`
|
|
564
|
+
(on busboy, an optional peer: `npm i busboy`):
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
import { multipartUploads } from '@ddd-ts-kit/http/uploads';
|
|
568
|
+
|
|
569
|
+
mountExpress(app, routes, { ...options, uploads: multipartUploads() });
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
The file reaches `handle` while it still streams, never held whole: the command wraps it in a
|
|
573
|
+
`FileUpload` of the kernel, which `fileUploadsMiddleware` of the command bus stages before the
|
|
574
|
+
transaction opens. On its way the content is
|
|
575
|
+
counted, and refused with `FILE_TOO_LARGE` (413) beyond `maxBytes`; its first bytes must prove
|
|
576
|
+
the media type the client declared and that type must be accepted, or it is refused with
|
|
577
|
+
`UNSUPPORTED_MEDIA_TYPE` (415), before a byte reaches the storage. A request announcing a body
|
|
578
|
+
far larger than the file allowed is refused before it is read; a form without the file, 422.
|
|
579
|
+
Text fields sent after the file are not read. `fileName` is shown back to people, never used as
|
|
580
|
+
a key: the key is generated on the server.
|
|
581
|
+
|
|
582
|
+
For a large file, the client uploads straight to the storage instead: a command signs an upload
|
|
583
|
+
link (`FileStaging.uploadUrl`) and records the grant it answers on the aggregate, the client
|
|
584
|
+
sends the file to the link, and a second command confirms from that grant
|
|
585
|
+
(`FileStaging.confirm(pending)`, which checks the size and the media type on what arrived). A
|
|
586
|
+
client that drops the connection part-way fails the file stream, so a handler waiting on it
|
|
587
|
+
rejects instead of waiting forever.
|
|
588
|
+
|
|
589
|
+
`signedFileRoutes(storage, { prefix: '/files' })` answers the signed links of a storage the
|
|
590
|
+
application serves itself, the local directory of development or the memory of the tests:
|
|
591
|
+
`GET /files?token=` downloads (as an attachment, `nosniff`, `no-store`), `PUT /files?token=`
|
|
592
|
+
uploads, refused with 413 beyond the size the link was signed for. They are public, the token being the grant, and are not part of the contract. With
|
|
593
|
+
Express, mount them before `express.json()`: that parser reads a JSON body first. An object
|
|
594
|
+
store answers its own links and needs no route.
|
|
595
|
+
|
|
596
|
+
A handler may answer a stream (`Readable`) as its body: it is piped to the response, with its
|
|
597
|
+
own `content-type`.
|
|
598
|
+
|
|
497
599
|
## Limits
|
|
498
600
|
|
|
499
601
|
- A body the framework itself refuses (malformed JSON, payload too large) is answered by the
|
|
500
602
|
framework's own error handler, before the pipeline runs.
|
|
501
|
-
- Routes are JSON first. A handler may answer a string or
|
|
502
|
-
`content-type` header
|
|
603
|
+
- Routes are JSON first. A handler may answer a string, bytes or a stream by setting its own
|
|
604
|
+
`content-type` header. A request body streams only as a declared upload or a raw body
|
|
605
|
+
(`rawBody`): server-sent events and WebSockets are written natively in the framework.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Actor } from '@ddd-ts-kit/kernel';
|
|
2
2
|
import type { HandlerInput } from '../route/handler-input.js';
|
|
3
|
+
import type { IncomingFile } from '../route/incoming-file.js';
|
|
4
|
+
import type { UploadSpec } from '../route/upload-spec.js';
|
|
3
5
|
import type { ControllerSpec } from './controller-spec.js';
|
|
4
6
|
/**
|
|
5
7
|
* What `handle` receives for a given declaration: each part has the type its schema produces,
|
|
@@ -7,5 +9,9 @@ import type { ControllerSpec } from './controller-spec.js';
|
|
|
7
9
|
*/
|
|
8
10
|
export type ControllerRequest<Spec extends ControllerSpec = ControllerSpec> = HandlerInput<Spec> & {
|
|
9
11
|
readonly actor: Actor;
|
|
10
|
-
}
|
|
12
|
+
} & (Spec extends {
|
|
13
|
+
readonly upload: UploadSpec;
|
|
14
|
+
} ? {
|
|
15
|
+
readonly upload: IncomingFile;
|
|
16
|
+
} : unknown);
|
|
11
17
|
//# sourceMappingURL=controller-request.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-request.d.ts","sourceRoot":"","sources":["../../src/controller/controller-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,IAAI,SAAS,cAAc,GAAG,cAAc,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG;IACjG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB,CAAC"}
|
|
1
|
+
{"version":3,"file":"controller-request.d.ts","sourceRoot":"","sources":["../../src/controller/controller-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,IAAI,SAAS,cAAc,GAAG,cAAc,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG;IACjG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB,GAAG,CAAC,IAAI,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-routes.d.ts","sourceRoot":"","sources":["../../src/controller/controller-routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller-routes.d.ts","sourceRoot":"","sources":["../../src/controller/controller-routes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAQ9E;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,SAAS,CAAC,eAAe,GAAG,gBAAgB,CAAC,EAAE,EAC5D,OAAO,GAAE,uBAA4B,GACpC,SAAS,KAAK,EAAE,CAMlB"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { DomainError } from '@ddd-ts-kit/kernel';
|
|
1
2
|
import { assertUniqueRoutes } from '../route/assert-unique-routes.js';
|
|
3
|
+
import { guardRefusals } from './guard-refusals.js';
|
|
2
4
|
import { resolvedController } from './resolved-controller.js';
|
|
3
5
|
const DEFAULT_SECURITY = ['bearer'];
|
|
4
6
|
/**
|
|
@@ -23,19 +25,36 @@ function routeOf(resolved, options) {
|
|
|
23
25
|
...(declared.tags.length === 0 ? {} : { tags: declared.tags }),
|
|
24
26
|
...(declared.groups.length === 0 ? {} : { groups: declared.groups }),
|
|
25
27
|
...requestOf(spec),
|
|
28
|
+
...(spec.upload === undefined ? {} : { upload: spec.upload }),
|
|
26
29
|
responses: { [status]: { description: description ?? defaultDescription(body), ...bodyOf(body) } },
|
|
27
30
|
errors: [...new Set([...declared.errors, ...resolved.guards.flatMap((guard) => guard.documents?.errors ?? [])])],
|
|
28
31
|
security: securityOf(resolved.guards, options.security ?? DEFAULT_SECURITY),
|
|
29
32
|
...(spec.deprecated === true ? { deprecated: true } : {}),
|
|
30
33
|
async handle(input) {
|
|
31
|
-
|
|
32
|
-
await guard.check(guardRequest(identity, input));
|
|
33
|
-
}
|
|
34
|
+
await passedGuards(guards, guardRequest(identity, input));
|
|
34
35
|
const answer = await controller.handle({ ...input, actor: input.context.execution.actor });
|
|
35
36
|
return body === undefined ? { status } : { status, body: answer };
|
|
36
37
|
},
|
|
37
38
|
};
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Every guard, in order. A refusal one throws (a `DomainError`: `UnauthorizedError`,
|
|
42
|
+
* `ForbiddenError` or one of the project) is marked as a refusal at the edge before it goes on to
|
|
43
|
+
* the pipeline; a fault (a lookup that failed) is not, and is never audited as a decision.
|
|
44
|
+
*/
|
|
45
|
+
async function passedGuards(guards, request) {
|
|
46
|
+
try {
|
|
47
|
+
for (const guard of guards) {
|
|
48
|
+
await guard.check(request);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof DomainError) {
|
|
53
|
+
guardRefusals.mark(error);
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
function requestOf(parts) {
|
|
40
59
|
const declared = Object.entries({
|
|
41
60
|
params: parts.params,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-routes.js","sourceRoot":"","sources":["../../src/controller/controller-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"controller-routes.js","sourceRoot":"","sources":["../../src/controller/controller-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAStE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAA2B,MAAM,0BAA0B,CAAC;AAEvF,MAAM,gBAAgB,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEpC;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAA4D,EAC5D,OAAO,GAA4B,EAAE;IAErC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAExG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAE3B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,OAAO,CAAC,QAA4B,EAAE,OAAgC;IAC7E,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;IAChE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACpD,MAAM,QAAQ,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC;IAEjG,OAAO;QACL,GAAG,QAAQ;QACX,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5E,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC9D,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpE,GAAG,SAAS,CAAC,IAAI,CAAC;QAClB,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7D,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,EAAE,WAAW,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE;QAClG,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAChH,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAC;QAC3E,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,KAAK,CAAC,MAAM,CAAC,KAAiB;YAC5B,MAAM,YAAY,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;YAE1D,MAAM,MAAM,GAAY,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YAEpG,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACpE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,MAAwB,EAAE,OAAqB;IACzE,IAAI,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAqB;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IAEhD,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;AAChF,CAAC;AAED,SAAS,MAAM,CAAC,IAAiC;IAC/C,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAiC;IAC3D,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC;AACtD,CAAC;AAED,SAAS,UAAU,CAAC,MAAwB,EAAE,OAA0B;IACtE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;IAEzE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,YAAY,CACnB,QAA+D,EAC/D,KAAiB;IAEjB,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC1B,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAE7C,OAAO;QACL,GAAG,QAAQ;QACX,MAAM,EAAE,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE;QAC5F,OAAO,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;QACjC,KAAK;QACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Schema } from '@ddd-ts-kit/validation';
|
|
2
2
|
import type { HttpMethod } from '../route/http-method.js';
|
|
3
|
+
import type { UploadSpec } from '../route/upload-spec.js';
|
|
3
4
|
import type { ControllerResponse } from './controller-response.js';
|
|
4
5
|
import type { GuardReference } from './guard-reference.js';
|
|
5
6
|
import type { RouteGroupClass } from './route-group-class.js';
|
|
@@ -20,6 +21,12 @@ export interface ControllerSpec {
|
|
|
20
21
|
readonly query?: Schema<unknown>;
|
|
21
22
|
readonly headers?: Schema<unknown>;
|
|
22
23
|
readonly body?: Schema<unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* A file received as `multipart/form-data`, its field, size and media types stated here and in
|
|
26
|
+
* the contract; `handle` then reads `upload`, and `body` describes the text fields. Send the
|
|
27
|
+
* text fields before the file: what follows it is not read.
|
|
28
|
+
*/
|
|
29
|
+
readonly upload?: UploadSpec;
|
|
23
30
|
readonly response: ControllerResponse;
|
|
24
31
|
/** Domain error codes the route may answer, added to those of its group and of its guards. */
|
|
25
32
|
readonly errors?: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller-spec.d.ts","sourceRoot":"","sources":["../../src/controller/controller-spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qHAAqH;IACrH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B"}
|
|
1
|
+
{"version":3,"file":"controller-spec.d.ts","sourceRoot":"","sources":["../../src/controller/controller-spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qHAAqH;IACrH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The errors a guard threw to refuse a request, told apart from the refusals of a handler so
|
|
3
|
+
* that the pipeline can report them as refusals at the edge (`onGuardRefusal`).
|
|
4
|
+
*/
|
|
5
|
+
export declare const guardRefusals: {
|
|
6
|
+
mark(error: unknown): void;
|
|
7
|
+
has(error: unknown): boolean;
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=guard-refusals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard-refusals.d.ts","sourceRoot":"","sources":["../../src/controller/guard-refusals.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB,IAAI,QAAQ,OAAO,GAAG,IAAI;IAK1B,GAAG,QAAQ,OAAO,GAAG,OAAO;CAG7B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const refused = new WeakSet();
|
|
2
|
+
/**
|
|
3
|
+
* The errors a guard threw to refuse a request, told apart from the refusals of a handler so
|
|
4
|
+
* that the pipeline can report them as refusals at the edge (`onGuardRefusal`).
|
|
5
|
+
*/
|
|
6
|
+
export const guardRefusals = {
|
|
7
|
+
mark(error) {
|
|
8
|
+
if (typeof error === 'object' && error !== null) {
|
|
9
|
+
refused.add(error);
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
has(error) {
|
|
13
|
+
return typeof error === 'object' && error !== null && refused.has(error);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=guard-refusals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard-refusals.js","sourceRoot":"","sources":["../../src/controller/guard-refusals.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,IAAI,CAAC,KAAc;QACjB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,GAAG,CAAC,KAAc;QAChB,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3E,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SessionScope } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { Guard } from '../guard.js';
|
|
3
|
+
/**
|
|
4
|
+
* Lets through a request whose session was granted every scope named (`Session.scopes`, the
|
|
5
|
+
* `scope` claim of the token): 401 to the anonymous caller, 403 when one is missing. A scope
|
|
6
|
+
* limits the client that presents the credential (a mobile application, an integration); what
|
|
7
|
+
* the actor may do stays a permission of the use case.
|
|
8
|
+
*/
|
|
9
|
+
export declare function HasScope(...scopes: readonly SessionScope[]): Guard;
|
|
10
|
+
//# sourceMappingURL=has-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has-scope.d.ts","sourceRoot":"","sources":["../../../src/controller/guards/has-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI9E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,KAAK,CAiBlE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { hasScope, isAnonymous } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { DeclarationError } from '@ddd-ts-kit/kernel/declarations';
|
|
3
|
+
import { ForbiddenError } from '../../error/forbidden-error.js';
|
|
4
|
+
import { UnauthorizedError } from '../../error/unauthorized-error.js';
|
|
5
|
+
/**
|
|
6
|
+
* Lets through a request whose session was granted every scope named (`Session.scopes`, the
|
|
7
|
+
* `scope` claim of the token): 401 to the anonymous caller, 403 when one is missing. A scope
|
|
8
|
+
* limits the client that presents the credential (a mobile application, an integration); what
|
|
9
|
+
* the actor may do stays a permission of the use case.
|
|
10
|
+
*/
|
|
11
|
+
export function HasScope(...scopes) {
|
|
12
|
+
if (scopes.length === 0) {
|
|
13
|
+
throw new DeclarationError('HasScope() names no scope: name at least one, or use Authenticated.');
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
documents: { errors: ['UNAUTHORIZED', 'FORBIDDEN'] },
|
|
17
|
+
check({ actor, session }) {
|
|
18
|
+
if (isAnonymous(actor)) {
|
|
19
|
+
throw new UnauthorizedError('Authentication is required');
|
|
20
|
+
}
|
|
21
|
+
if (!scopes.every((scope) => hasScope(session, scope))) {
|
|
22
|
+
throw new ForbiddenError('A scope this route requires was not granted');
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=has-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has-scope.js","sourceRoot":"","sources":["../../../src/controller/guards/has-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAqB,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAG,MAA+B;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,gBAAgB,CAAC,qEAAqE,CAAC,CAAC;IACpG,CAAC;IAED,OAAO;QACL,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC,EAAE;QACpD,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;YACtB,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,CAAC,CAAC;YAC5D,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;gBACvD,MAAM,IAAI,cAAc,CAAC,6CAA6C,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Guard } from '../guard.js';
|
|
2
|
+
/**
|
|
3
|
+
* Lets through a caller that belongs to the tenant of the request: 401 to the anonymous caller,
|
|
4
|
+
* 403 to a request that names no tenant or to a caller of another tenant. The routes of tenant
|
|
5
|
+
* data stand behind it; the routes of platform staff stand behind a guard of their own, and
|
|
6
|
+
* reach tenants through an elevation, never through this one.
|
|
7
|
+
*/
|
|
8
|
+
export declare const TenantMember: Guard;
|
|
9
|
+
//# sourceMappingURL=tenant-member.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-member.d.ts","sourceRoot":"","sources":["../../../src/controller/guards/tenant-member.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,KAa1B,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isAnonymous, isTenantMember } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import { ForbiddenError } from '../../error/forbidden-error.js';
|
|
3
|
+
import { UnauthorizedError } from '../../error/unauthorized-error.js';
|
|
4
|
+
/**
|
|
5
|
+
* Lets through a caller that belongs to the tenant of the request: 401 to the anonymous caller,
|
|
6
|
+
* 403 to a request that names no tenant or to a caller of another tenant. The routes of tenant
|
|
7
|
+
* data stand behind it; the routes of platform staff stand behind a guard of their own, and
|
|
8
|
+
* reach tenants through an elevation, never through this one.
|
|
9
|
+
*/
|
|
10
|
+
export const TenantMember = {
|
|
11
|
+
documents: { errors: ['UNAUTHORIZED', 'FORBIDDEN'] },
|
|
12
|
+
check({ actor, context }) {
|
|
13
|
+
if (isAnonymous(actor)) {
|
|
14
|
+
throw new UnauthorizedError('Authentication is required');
|
|
15
|
+
}
|
|
16
|
+
const { tenantId } = context.execution;
|
|
17
|
+
if (tenantId === undefined || !isTenantMember(actor, tenantId)) {
|
|
18
|
+
throw new ForbiddenError('This route serves the members of a tenant');
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=tenant-member.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-member.js","sourceRoot":"","sources":["../../../src/controller/guards/tenant-member.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAU;IACjC,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC,EAAE;IACpD,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE;QACtB,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,iBAAiB,CAAC,4BAA4B,CAAC,CAAC;QAC5D,CAAC;QAED,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;QAEvC,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC/D,MAAM,IAAI,cAAc,CAAC,2CAA2C,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -6,22 +6,28 @@ interface Parts {
|
|
|
6
6
|
readonly query?: unknown;
|
|
7
7
|
readonly headers?: unknown;
|
|
8
8
|
readonly body?: unknown;
|
|
9
|
+
readonly upload?: unknown;
|
|
9
10
|
}
|
|
10
11
|
type Part<Given, Name extends keyof Parts, Default> = Given extends {
|
|
11
12
|
readonly [K in Name]: infer T;
|
|
12
13
|
} ? T : Default;
|
|
13
14
|
/**
|
|
14
15
|
* The request as a controller, a mapper or a guard of the project names it: the parts it reads,
|
|
15
|
-
* typed by hand, `HttpRequest<{ params: CancelAppointmentParams; body: CancelAppointmentBody }
|
|
16
|
+
* typed by hand, `HttpRequest<{ params: CancelAppointmentParams; body: CancelAppointmentBody }>`, and
|
|
17
|
+
* `upload: IncomingFile` for a controller that declares an upload.
|
|
16
18
|
* The decorator of the controller checks that its schemas produce those types.
|
|
17
19
|
*/
|
|
18
|
-
export
|
|
20
|
+
export type HttpRequest<Given extends Parts = Parts> = {
|
|
19
21
|
readonly params: Part<Given, 'params', Record<string, string>>;
|
|
20
22
|
readonly query: Part<Given, 'query', Record<string, unknown>>;
|
|
21
23
|
readonly headers: Part<Given, 'headers', RawHeaders>;
|
|
22
24
|
readonly body: Part<Given, 'body', unknown>;
|
|
23
25
|
readonly actor: Actor;
|
|
24
26
|
readonly context: RequestContext;
|
|
25
|
-
}
|
|
27
|
+
} & (Given extends {
|
|
28
|
+
readonly upload: infer File;
|
|
29
|
+
} ? {
|
|
30
|
+
readonly upload: File;
|
|
31
|
+
} : unknown);
|
|
26
32
|
export {};
|
|
27
33
|
//# sourceMappingURL=http-request.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-request.d.ts","sourceRoot":"","sources":["../../src/controller/http-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,UAAU,KAAK;IACb,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"http-request.d.ts","sourceRoot":"","sources":["../../src/controller/http-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,UAAU,KAAK;IACb,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI,SAAS,MAAM,KAAK,EAAE,OAAO,IAAI,KAAK,SAAS;IAAE,QAAQ,EAAE,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC;CAAE,GAAG,CAAC,GAAG,OAAO,CAAC;AAEpH;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,KAAK,GAAG,KAAK,IAAI;IACrD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;CAClC,GAAG,CAAC,KAAK,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-error-mapping.d.ts","sourceRoot":"","sources":["../../src/error/default-error-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,eAAO,MAAM,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"default-error-mapping.d.ts","sourceRoot":"","sources":["../../src/error/default-error-mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,eAAO,MAAM,qBAAqB,EAAE,YAUnC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-error-mapping.js","sourceRoot":"","sources":["../../src/error/default-error-mapping.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,iBAAiB,EAAE,GAAG;IACtB,SAAS,EAAE,GAAG;IACd,QAAQ,EAAE,GAAG;IACb,mBAAmB,EAAE,GAAG;IACxB,YAAY,EAAE,GAAG;IACjB,SAAS,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"default-error-mapping.js","sourceRoot":"","sources":["../../src/error/default-error-mapping.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,iBAAiB,EAAE,GAAG;IACtB,SAAS,EAAE,GAAG;IACd,QAAQ,EAAE,GAAG;IACb,mBAAmB,EAAE,GAAG;IACxB,YAAY,EAAE,GAAG;IACjB,SAAS,EAAE,GAAG;IACd,cAAc,EAAE,GAAG;IACnB,cAAc,EAAE,GAAG;IACnB,sBAAsB,EAAE,GAAG;CAC5B,CAAC"}
|
|
@@ -5,6 +5,8 @@ export type ExpressMountOptions = ExecuteOptions;
|
|
|
5
5
|
/**
|
|
6
6
|
* Registers the routes on an Express 5 application or router. A JSON body parser
|
|
7
7
|
* (`express.json()`) must be installed before, otherwise bodies reach validation as `undefined`.
|
|
8
|
+
* An upload and a raw body are read from the request stream, which that parser leaves alone
|
|
9
|
+
* unless the request is JSON. A body that is a stream is piped to the response.
|
|
8
10
|
*/
|
|
9
11
|
export declare function mountExpress(target: IRouter, routes: readonly Route[], options?: ExpressMountOptions): void;
|
|
10
12
|
//# sourceMappingURL=mount-express.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-express.d.ts","sourceRoot":"","sources":["../../src/express/mount-express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-express.d.ts","sourceRoot":"","sources":["../../src/express/mount-express.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAKrE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAMjD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAQ/G"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
1
3
|
import { executeRoute } from '../pipeline/execute-route.js';
|
|
2
4
|
import { serializeBody } from '../pipeline/serialize-body.js';
|
|
3
5
|
import { assertUniqueRoutes } from '../route/assert-unique-routes.js';
|
|
4
6
|
/**
|
|
5
7
|
* Registers the routes on an Express 5 application or router. A JSON body parser
|
|
6
8
|
* (`express.json()`) must be installed before, otherwise bodies reach validation as `undefined`.
|
|
9
|
+
* An upload and a raw body are read from the request stream, which that parser leaves alone
|
|
10
|
+
* unless the request is JSON. A body that is a stream is piped to the response.
|
|
7
11
|
*/
|
|
8
12
|
export function mountExpress(target, routes, options = {}) {
|
|
9
13
|
assertUniqueRoutes(routes);
|
|
@@ -27,11 +31,15 @@ function handlerOf(route, options) {
|
|
|
27
31
|
return async (request, response) => {
|
|
28
32
|
const { params, query, headers } = request;
|
|
29
33
|
const body = request.body;
|
|
30
|
-
const result = await executeRoute(route, { params, query, headers, body, raw: request }, options);
|
|
34
|
+
const result = await executeRoute(route, { params, query, headers, body, raw: request, stream: request }, options);
|
|
31
35
|
response.status(result.status);
|
|
32
36
|
for (const [name, value] of Object.entries(result.headers)) {
|
|
33
37
|
response.setHeader(name, value);
|
|
34
38
|
}
|
|
39
|
+
if (result.body instanceof Readable) {
|
|
40
|
+
await pipeline(result.body, response).catch(() => response.destroy());
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
35
43
|
response.end(serializeBody(result));
|
|
36
44
|
};
|
|
37
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-express.js","sourceRoot":"","sources":["../../src/express/mount-express.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-express.js","sourceRoot":"","sources":["../../src/express/mount-express.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { FastifyInstance } from 'fastify';
|
|
2
2
|
import type { Route } from '../route/route.js';
|
|
3
3
|
import type { FastifyMountOptions } from './fastify-mount-options.js';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Registers the routes on a Fastify 5 instance. Validation stays in the shared pipeline. The
|
|
6
|
+
* routes that read their body as a stream (an upload, a raw body) are registered in a context of
|
|
7
|
+
* their own, where no body parser runs: the rest of the instance keeps its parsers.
|
|
8
|
+
*/
|
|
5
9
|
export declare function mountFastify(instance: FastifyInstance, routes: readonly Route[], options?: FastifyMountOptions): void;
|
|
6
10
|
//# sourceMappingURL=mount-fastify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-fastify.d.ts","sourceRoot":"","sources":["../../src/fastify/mount-fastify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-fastify.d.ts","sourceRoot":"","sources":["../../src/fastify/mount-fastify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAsBN"}
|
|
@@ -1,17 +1,40 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
import { executeRoute } from '../pipeline/execute-route.js';
|
|
2
3
|
import { serializeBody } from '../pipeline/serialize-body.js';
|
|
3
4
|
import { assertUniqueRoutes } from '../route/assert-unique-routes.js';
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Registers the routes on a Fastify 5 instance. Validation stays in the shared pipeline. The
|
|
7
|
+
* routes that read their body as a stream (an upload, a raw body) are registered in a context of
|
|
8
|
+
* their own, where no body parser runs: the rest of the instance keeps its parsers.
|
|
9
|
+
*/
|
|
5
10
|
export function mountFastify(instance, routes, options = {}) {
|
|
6
11
|
assertUniqueRoutes(routes);
|
|
12
|
+
const streamed = routes.filter(readsItsStream);
|
|
13
|
+
register(instance, routes.filter((route) => !readsItsStream(route)), options);
|
|
14
|
+
if (streamed.length > 0) {
|
|
15
|
+
void instance.register(async (unparsed) => {
|
|
16
|
+
unparsed.removeAllContentTypeParsers();
|
|
17
|
+
unparsed.addContentTypeParser('*', (_request, _payload, done) => {
|
|
18
|
+
done(null);
|
|
19
|
+
});
|
|
20
|
+
register(unparsed, streamed, options);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function readsItsStream(route) {
|
|
25
|
+
return route.upload !== undefined || route.rawBody === true;
|
|
26
|
+
}
|
|
27
|
+
function register(instance, routes, options) {
|
|
7
28
|
for (const route of routes) {
|
|
8
29
|
instance.route({
|
|
9
30
|
method: route.method,
|
|
10
31
|
url: route.path,
|
|
11
32
|
handler: async (request, reply) => {
|
|
12
33
|
const { params, query, headers, body } = request;
|
|
13
|
-
const
|
|
14
|
-
|
|
34
|
+
const raw = { params, query, headers, body, raw: request, stream: request.raw };
|
|
35
|
+
const result = await executeRoute(route, raw, options);
|
|
36
|
+
const sent = result.body instanceof Readable ? result.body : serializeBody(result);
|
|
37
|
+
return reply.code(result.status).headers(result.headers).send(sent);
|
|
15
38
|
},
|
|
16
39
|
});
|
|
17
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-fastify.js","sourceRoot":"","sources":["../../src/fastify/mount-fastify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mount-fastify.js","sourceRoot":"","sources":["../../src/fastify/mount-fastify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAItE;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,QAAyB,EACzB,MAAwB,EACxB,OAAO,GAAwB,EAAE;IAEjC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAE3B,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAE/C,QAAQ,CACN,QAAQ,EACR,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAChD,OAAO,CACR,CAAC;IAEF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,KAAK,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YACxC,QAAQ,CAAC,2BAA2B,EAAE,CAAC;YAEvC,QAAQ,CAAC,oBAAoB,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBAC9D,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,CAAC,CAAC,CAAC;YAEH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAY;IAClC,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,QAAQ,CAAC,QAAyB,EAAE,MAAwB,EAAE,OAA4B;IACjG,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC;YACb,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,EAAE,KAAK,CAAC,IAAI;YACf,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;gBAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;gBACjD,MAAM,GAAG,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;gBAChF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBACvD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,YAAY,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBAEnF,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtE,CAAC;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signed-file-routes-options.d.ts","sourceRoot":"","sources":["../../src/files/signed-file-routes-options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|