@decohere/http 0.0.1-dev.126 → 0.0.1-dev.5
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 +2 -2
- package/package.json +4 -13
- package/src/app/app.ts +147 -0
- package/src/app/module.ts +19 -0
- package/src/auth/ability.ts +56 -0
- package/src/auth/auth.config.ts +34 -0
- package/src/auth/auth.extensions.ts +115 -0
- package/src/auth/auth.types.ts +124 -0
- package/src/auth/casl-authorization.service.ts +62 -0
- package/{types/src/auth/principal-provider.d.ts → src/auth/principal-provider.ts} +13 -5
- package/src/auth/safe-outbound-url.ts +117 -0
- package/src/auth/token-service.ts +192 -0
- package/src/builder/builder.ts +118 -0
- package/{es6/src/const.js → src/const.ts} +25 -17
- package/src/health/health.controller.ts +34 -0
- package/src/health/health.extensions.ts +53 -0
- package/src/health/readiness.service.ts +33 -0
- package/{types/src/health/readiness.types.d.ts → src/health/readiness.types.ts} +4 -8
- package/src/hosting/hosted-service-host.ts +102 -0
- package/src/hosting/hosted-service.ts +31 -0
- package/src/http/http.compression.ts +38 -0
- package/src/http/http.extensions.ts +244 -0
- package/{types/src/http/http.options.store.d.ts → src/http/http.options.store.ts} +11 -1
- package/src/http/http.options.ts +41 -0
- package/{types/src/http/http.security.d.ts → src/http/http.security.ts} +16 -10
- package/src/http/http.static.ts +359 -0
- package/{es6/src/index.js → src/index.ts} +1 -0
- package/src/openapi/index.ts +22 -0
- package/src/openapi/openapi.generator.ts +78 -0
- package/src/openapi/openapi.types.ts +60 -0
- package/src/openapi/request-schema.ts +155 -0
- package/src/openapi/request-validator.ts +95 -0
- package/src/providers/fastify.provider.ts +454 -0
- package/src/routing/anvil.types.ts +150 -0
- package/src/routing/decorators/argument.decorator.ts +33 -0
- package/src/routing/decorators/auth.decorator.ts +79 -0
- package/src/routing/decorators/controller.decorator.ts +9 -0
- package/src/routing/decorators/field.decorator.ts +15 -0
- package/src/routing/decorators/request.decorator.ts +34 -0
- package/src/routing/decorators/response.decorator.ts +18 -0
- package/src/routing/decorators/route.decorator.ts +52 -0
- package/src/routing/filters/exception-filter-host.ts +90 -0
- package/src/routing/filters/exception-filter.ts +29 -0
- package/src/routing/middleware/http-access-log.middleware.ts +36 -0
- package/src/routing/middleware/middleware.ts +26 -0
- package/src/routing/middleware/request-id.ts +18 -0
- package/src/routing/path.util.ts +38 -0
- package/src/routing/pipes/pipes.ts +132 -0
- package/src/routing/services/route-argument-resolver.ts +92 -0
- package/src/routing/services/route-registry.ts +229 -0
- package/src/routing/services/router.ts +166 -0
- package/src/sse/sse.decorator.ts +41 -0
- package/src/sse/sse.encoder.ts +86 -0
- package/src/sse/sse.types.ts +47 -0
- package/src/sse/sse.wire.ts +34 -0
- package/src/testing/boot-module.ts +63 -0
- package/src/testing/sse-reader.ts +120 -0
- package/tests/app.hosted-services.spec.ts +292 -0
- package/tests/app.shutdown.integration.spec.ts +160 -0
- package/tests/app.spec.ts +132 -0
- package/tests/auth.ability.spec.ts +48 -0
- package/tests/auth.decorators.spec.ts +20 -0
- package/tests/auth.fail-closed.pentest.spec.ts +77 -0
- package/tests/auth.integration.spec.ts +234 -0
- package/tests/auth.jwks-cache.spec.ts +52 -0
- package/tests/auth.missing-auth.pentest.spec.ts +56 -0
- package/tests/auth.oidc-ssrf.pentest.spec.ts +72 -0
- package/tests/auth.oidc.integration.spec.ts +161 -0
- package/tests/auth.token-service.spec.ts +251 -0
- package/tests/builder.spec.ts +12 -0
- package/tests/health.integration.spec.ts +111 -0
- package/tests/helpers/listen-ephemeral.ts +13 -0
- package/tests/helpers/local-oidc-server.ts +100 -0
- package/tests/http.access-log.integration.spec.ts +111 -0
- package/tests/http.boolean-query.integration.spec.ts +59 -0
- package/tests/http.compression.integration.spec.ts +604 -0
- package/tests/http.date-query.integration.spec.ts +71 -0
- package/tests/http.global-prefix.integration.spec.ts +284 -0
- package/tests/http.number-query.integration.spec.ts +65 -0
- package/tests/http.pino-roll-hmr.integration.spec.ts +79 -0
- package/tests/http.pipeline-order.integration.spec.ts +95 -0
- package/tests/http.request-cleanup.integration.spec.ts +57 -0
- package/tests/http.request-id-scope.pentest.spec.ts +50 -0
- package/tests/http.security.integration.spec.ts +106 -0
- package/tests/http.static.integration.spec.ts +847 -0
- package/tests/http.static.spec.ts +111 -0
- package/tests/http.surface.integration.spec.ts +167 -0
- package/tests/openapi.integration.spec.ts +193 -0
- package/tests/openapi.validator-cache.integration.spec.ts +55 -0
- package/tests/routing.decorators.spec.ts +15 -0
- package/tests/routing.registry.spec.ts +44 -0
- package/tests/routing.resolver.spec.ts +92 -0
- package/tests/sse.integration.spec.ts +421 -0
- package/tests/sse.wire.spec.ts +33 -0
- package/LICENSE +0 -15
- package/commonjs/src/app/app.js +0 -166
- package/commonjs/src/app/index.js +0 -19
- package/commonjs/src/app/module.js +0 -17
- package/commonjs/src/auth/ability.js +0 -54
- package/commonjs/src/auth/auth.config.js +0 -23
- package/commonjs/src/auth/auth.extensions.js +0 -106
- package/commonjs/src/auth/auth.types.js +0 -65
- package/commonjs/src/auth/casl-authorization.service.js +0 -36
- package/commonjs/src/auth/index.js +0 -23
- package/commonjs/src/auth/principal-provider.js +0 -19
- package/commonjs/src/auth/safe-outbound-url.js +0 -102
- package/commonjs/src/auth/token-service.js +0 -151
- package/commonjs/src/builder/builder.js +0 -95
- package/commonjs/src/builder/index.js +0 -18
- package/commonjs/src/const.js +0 -87
- package/commonjs/src/health/health.controller.js +0 -75
- package/commonjs/src/health/health.extensions.js +0 -50
- package/commonjs/src/health/index.js +0 -21
- package/commonjs/src/health/readiness.service.js +0 -67
- package/commonjs/src/health/readiness.types.js +0 -7
- package/commonjs/src/hosting/hosted-service-host.js +0 -88
- package/commonjs/src/hosting/hosted-service.js +0 -11
- package/commonjs/src/hosting/index.js +0 -19
- package/commonjs/src/http/http.compression.js +0 -27
- package/commonjs/src/http/http.extensions.js +0 -269
- package/commonjs/src/http/http.options.js +0 -26
- package/commonjs/src/http/http.options.store.js +0 -20
- package/commonjs/src/http/http.security.js +0 -27
- package/commonjs/src/http/http.static.js +0 -248
- package/commonjs/src/http/index.js +0 -22
- package/commonjs/src/index.js +0 -27
- package/commonjs/src/openapi/index.js +0 -45
- package/commonjs/src/openapi/openapi.generator.js +0 -103
- package/commonjs/src/openapi/openapi.types.js +0 -4
- package/commonjs/src/openapi/request-schema.js +0 -187
- package/commonjs/src/openapi/request-validator.js +0 -85
- package/commonjs/src/providers/fastify.provider.js +0 -391
- package/commonjs/src/providers/index.js +0 -18
- package/commonjs/src/routing/anvil.types.js +0 -99
- package/commonjs/src/routing/decorators/argument.decorator.js +0 -49
- package/commonjs/src/routing/decorators/auth.decorator.js +0 -70
- package/commonjs/src/routing/decorators/controller.decorator.js +0 -16
- package/commonjs/src/routing/decorators/field.decorator.js +0 -25
- package/commonjs/src/routing/decorators/index.js +0 -24
- package/commonjs/src/routing/decorators/request.decorator.js +0 -30
- package/commonjs/src/routing/decorators/response.decorator.js +0 -34
- package/commonjs/src/routing/decorators/route.decorator.js +0 -67
- package/commonjs/src/routing/filters/exception-filter-host.js +0 -145
- package/commonjs/src/routing/filters/exception-filter.js +0 -28
- package/commonjs/src/routing/index.js +0 -25
- package/commonjs/src/routing/middleware/http-access-log.middleware.js +0 -47
- package/commonjs/src/routing/middleware/middleware.js +0 -20
- package/commonjs/src/routing/middleware/request-id.js +0 -30
- package/commonjs/src/routing/path.util.js +0 -51
- package/commonjs/src/routing/pipes/index.js +0 -18
- package/commonjs/src/routing/pipes/pipes.js +0 -145
- package/commonjs/src/routing/services/index.js +0 -18
- package/commonjs/src/routing/services/route-argument-resolver.js +0 -77
- package/commonjs/src/routing/services/route-registry.js +0 -194
- package/commonjs/src/routing/services/router.js +0 -158
- package/commonjs/src/sse/index.js +0 -26
- package/commonjs/src/sse/sse.decorator.js +0 -42
- package/commonjs/src/sse/sse.encoder.js +0 -73
- package/commonjs/src/sse/sse.types.js +0 -36
- package/commonjs/src/sse/sse.wire.js +0 -40
- package/commonjs/src/testing/boot-module.js +0 -38
- package/commonjs/src/testing/index.js +0 -20
- package/commonjs/src/testing/sse-reader.js +0 -98
- package/es6/src/app/app.js +0 -148
- package/es6/src/app/index.js +0 -2
- package/es6/src/app/module.js +0 -7
- package/es6/src/auth/ability.js +0 -45
- package/es6/src/auth/auth.config.js +0 -19
- package/es6/src/auth/auth.extensions.js +0 -83
- package/es6/src/auth/auth.types.js +0 -35
- package/es6/src/auth/casl-authorization.service.js +0 -40
- package/es6/src/auth/index.js +0 -6
- package/es6/src/auth/principal-provider.js +0 -14
- package/es6/src/auth/safe-outbound-url.js +0 -99
- package/es6/src/auth/token-service.js +0 -141
- package/es6/src/builder/builder.js +0 -85
- package/es6/src/builder/index.js +0 -1
- package/es6/src/health/health.controller.js +0 -65
- package/es6/src/health/health.extensions.js +0 -40
- package/es6/src/health/index.js +0 -4
- package/es6/src/health/readiness.service.js +0 -57
- package/es6/src/health/readiness.types.js +0 -4
- package/es6/src/hosting/hosted-service-host.js +0 -81
- package/es6/src/hosting/hosted-service.js +0 -8
- package/es6/src/hosting/index.js +0 -2
- package/es6/src/http/http.compression.js +0 -9
- package/es6/src/http/http.extensions.js +0 -259
- package/es6/src/http/http.options.js +0 -16
- package/es6/src/http/http.options.store.js +0 -13
- package/es6/src/http/http.security.js +0 -9
- package/es6/src/http/http.static.js +0 -224
- package/es6/src/http/index.js +0 -5
- package/es6/src/openapi/index.js +0 -3
- package/es6/src/openapi/openapi.generator.js +0 -82
- package/es6/src/openapi/openapi.types.js +0 -1
- package/es6/src/openapi/request-schema.js +0 -160
- package/es6/src/openapi/request-validator.js +0 -71
- package/es6/src/providers/fastify.provider.js +0 -376
- package/es6/src/providers/index.js +0 -1
- package/es6/src/routing/anvil.types.js +0 -57
- package/es6/src/routing/decorators/argument.decorator.js +0 -28
- package/es6/src/routing/decorators/auth.decorator.js +0 -58
- package/es6/src/routing/decorators/controller.decorator.js +0 -6
- package/es6/src/routing/decorators/field.decorator.js +0 -15
- package/es6/src/routing/decorators/index.js +0 -7
- package/es6/src/routing/decorators/request.decorator.js +0 -24
- package/es6/src/routing/decorators/response.decorator.js +0 -16
- package/es6/src/routing/decorators/route.decorator.js +0 -37
- package/es6/src/routing/filters/exception-filter-host.js +0 -127
- package/es6/src/routing/filters/exception-filter.js +0 -10
- package/es6/src/routing/index.js +0 -8
- package/es6/src/routing/middleware/http-access-log.middleware.js +0 -37
- package/es6/src/routing/middleware/middleware.js +0 -10
- package/es6/src/routing/middleware/request-id.js +0 -14
- package/es6/src/routing/path.util.js +0 -33
- package/es6/src/routing/pipes/index.js +0 -1
- package/es6/src/routing/pipes/pipes.js +0 -118
- package/es6/src/routing/services/index.js +0 -1
- package/es6/src/routing/services/route-argument-resolver.js +0 -67
- package/es6/src/routing/services/route-registry.js +0 -184
- package/es6/src/routing/services/router.js +0 -148
- package/es6/src/sse/index.js +0 -2
- package/es6/src/sse/sse.decorator.js +0 -31
- package/es6/src/sse/sse.encoder.js +0 -67
- package/es6/src/sse/sse.types.js +0 -22
- package/es6/src/sse/sse.wire.js +0 -30
- package/es6/src/testing/boot-module.js +0 -35
- package/es6/src/testing/index.js +0 -2
- package/es6/src/testing/sse-reader.js +0 -91
- package/types/src/app/app.d.ts +0 -41
- package/types/src/app/module.d.ts +0 -15
- package/types/src/auth/ability.d.ts +0 -19
- package/types/src/auth/auth.config.d.ts +0 -28
- package/types/src/auth/auth.extensions.d.ts +0 -53
- package/types/src/auth/auth.types.d.ts +0 -73
- package/types/src/auth/casl-authorization.service.d.ts +0 -28
- package/types/src/auth/safe-outbound-url.d.ts +0 -9
- package/types/src/auth/token-service.d.ts +0 -31
- package/types/src/builder/builder.d.ts +0 -36
- package/types/src/const.d.ts +0 -32
- package/types/src/health/health.controller.d.ts +0 -16
- package/types/src/health/health.extensions.d.ts +0 -31
- package/types/src/health/readiness.service.d.ts +0 -9
- package/types/src/hosting/hosted-service-host.d.ts +0 -37
- package/types/src/hosting/hosted-service.d.ts +0 -31
- package/types/src/http/http.compression.d.ts +0 -32
- package/types/src/http/http.extensions.d.ts +0 -79
- package/types/src/http/http.options.d.ts +0 -39
- package/types/src/http/http.static.d.ts +0 -160
- package/types/src/index.d.ts +0 -10
- package/types/src/openapi/index.d.ts +0 -6
- package/types/src/openapi/openapi.generator.d.ts +0 -6
- package/types/src/openapi/openapi.types.d.ts +0 -58
- package/types/src/openapi/request-schema.d.ts +0 -21
- package/types/src/openapi/request-validator.d.ts +0 -9
- package/types/src/providers/fastify.provider.d.ts +0 -56
- package/types/src/routing/anvil.types.d.ts +0 -119
- package/types/src/routing/decorators/argument.decorator.d.ts +0 -11
- package/types/src/routing/decorators/auth.decorator.d.ts +0 -30
- package/types/src/routing/decorators/controller.decorator.d.ts +0 -3
- package/types/src/routing/decorators/field.decorator.d.ts +0 -2
- package/types/src/routing/decorators/request.decorator.d.ts +0 -8
- package/types/src/routing/decorators/response.decorator.d.ts +0 -4
- package/types/src/routing/decorators/route.decorator.d.ts +0 -7
- package/types/src/routing/filters/exception-filter-host.d.ts +0 -19
- package/types/src/routing/filters/exception-filter.d.ts +0 -16
- package/types/src/routing/middleware/http-access-log.middleware.d.ts +0 -8
- package/types/src/routing/middleware/middleware.d.ts +0 -10
- package/types/src/routing/middleware/request-id.d.ts +0 -6
- package/types/src/routing/path.util.d.ts +0 -9
- package/types/src/routing/pipes/pipes.d.ts +0 -21
- package/types/src/routing/services/route-argument-resolver.d.ts +0 -10
- package/types/src/routing/services/route-registry.d.ts +0 -20
- package/types/src/routing/services/router.d.ts +0 -22
- package/types/src/sse/sse.decorator.d.ts +0 -17
- package/types/src/sse/sse.encoder.d.ts +0 -16
- package/types/src/sse/sse.types.d.ts +0 -37
- package/types/src/sse/sse.wire.d.ts +0 -9
- package/types/src/testing/boot-module.d.ts +0 -34
- package/types/src/testing/sse-reader.d.ts +0 -29
- /package/{types/src/app/index.d.ts → src/app/index.ts} +0 -0
- /package/{types/src/auth/index.d.ts → src/auth/index.ts} +0 -0
- /package/{types/src/builder/index.d.ts → src/builder/index.ts} +0 -0
- /package/{types/src/health/index.d.ts → src/health/index.ts} +0 -0
- /package/{types/src/hosting/index.d.ts → src/hosting/index.ts} +0 -0
- /package/{types/src/http/index.d.ts → src/http/index.ts} +0 -0
- /package/{types/src/providers/index.d.ts → src/providers/index.ts} +0 -0
- /package/{types/src/routing/decorators/index.d.ts → src/routing/decorators/index.ts} +0 -0
- /package/{types/src/routing/index.d.ts → src/routing/index.ts} +0 -0
- /package/{types/src/routing/pipes/index.d.ts → src/routing/pipes/index.ts} +0 -0
- /package/{types/src/routing/services/index.d.ts → src/routing/services/index.ts} +0 -0
- /package/{types/src/sse/index.d.ts → src/sse/index.ts} +0 -0
- /package/{types/src/testing/index.d.ts → src/testing/index.ts} +0 -0
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
function _ts_param(paramIndex, decorator) {
|
|
11
|
-
return function(target, key) {
|
|
12
|
-
decorator(target, key, paramIndex);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
16
|
-
import { getArgumentMeta, getFields, getFunctionArgs, getFunctions, getMeta, isConstructable, isPrimitiveOrDateType } from "@decohere/core/reflection";
|
|
17
|
-
import { HttpOptionsToken, MetadataKeys } from "../../const.js";
|
|
18
|
-
import { InjectedParts, RouteConfigurationError } from "../anvil.types.js";
|
|
19
|
-
import { applyGlobalPrefix, joinRoutePath, normalizePrefix } from "../path.util.js";
|
|
20
|
-
import { BooleanPipe, DatePipe, JsonPipe, NumberPipe } from "../pipes/pipes.js";
|
|
21
|
-
export class RouteRegistry {
|
|
22
|
-
routeMap = new Map();
|
|
23
|
-
httpOptions;
|
|
24
|
-
constructor(httpOptions){
|
|
25
|
-
this.httpOptions = httpOptions;
|
|
26
|
-
}
|
|
27
|
-
addController(controller) {
|
|
28
|
-
const funcs = getFunctions(controller);
|
|
29
|
-
for (const func of funcs){
|
|
30
|
-
this.addControllerRoute(controller, func);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
get routes() {
|
|
34
|
-
return this.routeMap.values();
|
|
35
|
-
}
|
|
36
|
-
getRoute(key) {
|
|
37
|
-
return this.routeMap.get(key);
|
|
38
|
-
}
|
|
39
|
-
addControllerRoute(controller, prop) {
|
|
40
|
-
const func = prop.value;
|
|
41
|
-
const handler = func.name;
|
|
42
|
-
const route = getMeta(MetadataKeys.route, func);
|
|
43
|
-
if (route == null) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const globalPrefix = this.takesGlobalPrefix(controller) ? normalizePrefix(this.httpOptions?.globalPrefix) : "";
|
|
47
|
-
const controllerPrefix = this.getControllerPrefix(controller);
|
|
48
|
-
const path = applyGlobalPrefix(globalPrefix, joinRoutePath(controllerPrefix, route.path));
|
|
49
|
-
const { method } = route;
|
|
50
|
-
const key = `${method}|${path}`;
|
|
51
|
-
const existing = this.routeMap.get(key);
|
|
52
|
-
if (existing != null) {
|
|
53
|
-
// Last one used to win silently, leaving the other handler unreachable.
|
|
54
|
-
throw new RouteConfigurationError(`Duplicate route ${method.toUpperCase()} ${path}: ${existing.controller.name}.${existing.handler} and ${controller.name}.${handler}`);
|
|
55
|
-
}
|
|
56
|
-
const routeArgs = this.getRouteArguments(controller, func);
|
|
57
|
-
const httpCode = getMeta(MetadataKeys.httpCode, func);
|
|
58
|
-
const headers = getMeta(MetadataKeys.headers, func);
|
|
59
|
-
const sse = getMeta(MetadataKeys.sse, func);
|
|
60
|
-
this.routeMap.set(key, {
|
|
61
|
-
key,
|
|
62
|
-
controller,
|
|
63
|
-
handler,
|
|
64
|
-
path,
|
|
65
|
-
method,
|
|
66
|
-
routeArgs,
|
|
67
|
-
httpCode,
|
|
68
|
-
headers,
|
|
69
|
-
sse
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/** `@Controller({ globalPrefix: false })` opts out, unless the app put it back under. */ takesGlobalPrefix(controller) {
|
|
73
|
-
const meta = getMeta(MetadataKeys.controller, controller.prototype);
|
|
74
|
-
return meta?.globalPrefix !== false || this.httpOptions?.underGlobalPrefix.has(controller) === true;
|
|
75
|
-
}
|
|
76
|
-
getControllerPrefix(controller) {
|
|
77
|
-
const meta = getMeta(MetadataKeys.controller, controller.prototype);
|
|
78
|
-
if (meta?.prefix) {
|
|
79
|
-
return normalizePrefix(meta.prefix);
|
|
80
|
-
}
|
|
81
|
-
const controllerName = controller.name.toLowerCase().replace("controller", "");
|
|
82
|
-
return controllerName !== "" ? `/${controllerName}` : "";
|
|
83
|
-
}
|
|
84
|
-
getRouteArguments(controller, handler) {
|
|
85
|
-
const types = getFunctionArgs(controller.prototype, handler) ?? [];
|
|
86
|
-
const args = [];
|
|
87
|
-
for (const [argumentIndex, argumentType] of types.entries()){
|
|
88
|
-
const argMeta = getArgumentMeta(MetadataKeys.parameter, handler, argumentIndex);
|
|
89
|
-
if (argMeta == null && !isConstructable(argumentType)) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
args.push(this.getRouteArgument(argumentType, argumentIndex, handler));
|
|
93
|
-
}
|
|
94
|
-
return args;
|
|
95
|
-
}
|
|
96
|
-
getRouteArgument(argumentType, argumentIndex, handler) {
|
|
97
|
-
const argMeta = getArgumentMeta(MetadataKeys.parameter, handler, argumentIndex);
|
|
98
|
-
// @CurrentUser() / interface params are Object (or other non-DTO types) — no field map.
|
|
99
|
-
if (isInjectedPart(argMeta?.argumentLocation) || !isConstructable(argumentType)) {
|
|
100
|
-
return {
|
|
101
|
-
...argMeta,
|
|
102
|
-
pipes: [],
|
|
103
|
-
argumentIndex,
|
|
104
|
-
argumentType: isConstructable(argumentType) ? argumentType : Object,
|
|
105
|
-
fieldMap: new Map()
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
const pipes = argMeta != null ? this.getDefaultPipes(argumentType) : [];
|
|
109
|
-
const fieldMap = this.getFields(argumentType, argumentIndex, argMeta);
|
|
110
|
-
const routeArg = {
|
|
111
|
-
...argMeta,
|
|
112
|
-
pipes,
|
|
113
|
-
argumentIndex,
|
|
114
|
-
argumentType,
|
|
115
|
-
fieldMap
|
|
116
|
-
};
|
|
117
|
-
return routeArg;
|
|
118
|
-
}
|
|
119
|
-
getFields(argumentType, argumentIndex, argMeta) {
|
|
120
|
-
const inst = new argumentType();
|
|
121
|
-
const fields = getFields(inst);
|
|
122
|
-
const fieldMap = getMeta(MetadataKeys.fieldMap, argumentType.prototype) ?? new Map();
|
|
123
|
-
for (const field of fields){
|
|
124
|
-
const fieldKey = field.key;
|
|
125
|
-
const fieldMeta = fieldMap.get(field.key);
|
|
126
|
-
if (fieldMeta && fieldMeta.fieldLocation == null && argMeta?.argumentLocation == null) {
|
|
127
|
-
throw new RouteConfigurationError(`field ${fieldMeta.fieldKey} must specify a location because location was not specified for the route argument at idx ${argumentIndex}`);
|
|
128
|
-
}
|
|
129
|
-
const enriched = this.enrichField(field, fieldMeta, argMeta);
|
|
130
|
-
fieldMap.set(fieldKey, enriched);
|
|
131
|
-
}
|
|
132
|
-
return fieldMap;
|
|
133
|
-
}
|
|
134
|
-
enrichField(field, fieldMeta, argMeta) {
|
|
135
|
-
const fieldLocation = fieldMeta?.fieldLocation ?? argMeta.argumentLocation;
|
|
136
|
-
const pipes = this.getDefaultPipes(fieldMeta?.fieldType);
|
|
137
|
-
return {
|
|
138
|
-
fieldKey: field.key,
|
|
139
|
-
mappingKey: field.key,
|
|
140
|
-
...fieldMeta,
|
|
141
|
-
fieldLocation,
|
|
142
|
-
pipes
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
getDefaultPipes(argumentType) {
|
|
146
|
-
if (argumentType == null) {
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
let pipe;
|
|
150
|
-
if (!isPrimitiveOrDateType(argumentType)) {
|
|
151
|
-
pipe = JsonPipe;
|
|
152
|
-
return [
|
|
153
|
-
pipe
|
|
154
|
-
];
|
|
155
|
-
}
|
|
156
|
-
switch(argumentType.name){
|
|
157
|
-
case "Number":
|
|
158
|
-
pipe = NumberPipe;
|
|
159
|
-
break;
|
|
160
|
-
case "Boolean":
|
|
161
|
-
pipe = BooleanPipe;
|
|
162
|
-
break;
|
|
163
|
-
case "Date":
|
|
164
|
-
pipe = DatePipe;
|
|
165
|
-
break;
|
|
166
|
-
default:
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
return [
|
|
170
|
-
pipe
|
|
171
|
-
];
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
RouteRegistry = _ts_decorate([
|
|
175
|
-
injectable(),
|
|
176
|
-
_ts_param(0, inject(HttpOptionsToken)),
|
|
177
|
-
_ts_metadata("design:type", Function),
|
|
178
|
-
_ts_metadata("design:paramtypes", [
|
|
179
|
-
typeof HttpOptions === "undefined" ? Object : HttpOptions
|
|
180
|
-
])
|
|
181
|
-
], RouteRegistry);
|
|
182
|
-
function isInjectedPart(location) {
|
|
183
|
-
return location != null && InjectedParts.includes(location);
|
|
184
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
function _ts_param(paramIndex, decorator) {
|
|
11
|
-
return function(target, key) {
|
|
12
|
-
decorator(target, key, paramIndex);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
import { LifetimeScopeToken } from "@decohere/crucible";
|
|
16
|
-
import { inject, injectable, optional } from "@decohere/crucible/decorators";
|
|
17
|
-
import { scribe } from "@decohere/scribe";
|
|
18
|
-
import { AuthorizationServiceToken, HttpOptionsToken, IPrincipalProviderToken, RouteRegistryToken } from "../../const.js";
|
|
19
|
-
import { validateRequest } from "../../openapi/request-validator.js";
|
|
20
|
-
import { SseResponse } from "../../sse/sse.types.js";
|
|
21
|
-
import { RoutingError } from "../anvil.types.js";
|
|
22
|
-
import { getAuthorizeOptions, isAllowAnonymous } from "../decorators/auth.decorator.js";
|
|
23
|
-
import { composeMiddleware } from "../middleware/middleware.js";
|
|
24
|
-
import { RouteArgumentResolve } from "./route-argument-resolver.js";
|
|
25
|
-
import { RouteRegistry } from "./route-registry.js";
|
|
26
|
-
export class Router {
|
|
27
|
-
argResolver = new RouteArgumentResolve();
|
|
28
|
-
lifetimeScope;
|
|
29
|
-
routeRegistry;
|
|
30
|
-
authService;
|
|
31
|
-
httpOptions;
|
|
32
|
-
logger = scribe.logger(Router.name);
|
|
33
|
-
constructor(routeRegistry, lifetimeScope, authService, httpOptions){
|
|
34
|
-
this.routeRegistry = routeRegistry;
|
|
35
|
-
this.lifetimeScope = lifetimeScope;
|
|
36
|
-
this.authService = authService;
|
|
37
|
-
this.httpOptions = httpOptions;
|
|
38
|
-
this.assertAuthConfiguredForProtectedRoutes();
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Without an authorization service nothing checks tokens, so a route marked @Authorize
|
|
42
|
-
* would be served to anyone. Refuse to start instead of failing open.
|
|
43
|
-
*/ assertAuthConfiguredForProtectedRoutes() {
|
|
44
|
-
if (this.authService != null) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const protectedRoutes = [
|
|
48
|
-
...this.routeRegistry.routes
|
|
49
|
-
].filter(({ controller, handler })=>getAuthorizeOptions(controller.prototype, controller.prototype[handler])).map(({ controller, handler })=>`${controller.name}.${String(handler)}`);
|
|
50
|
-
if (protectedRoutes.length > 0) {
|
|
51
|
-
throw new RoutingError(`Routes use @Authorize but auth is not configured, so they would be open to anyone: ${protectedRoutes.join(", ")}. Add .withAuth() to the builder, or remove @Authorize.`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
async route(routeKey, httpRequest) {
|
|
55
|
-
const routeConfig = this.routeRegistry.getRoute(routeKey);
|
|
56
|
-
if (!routeConfig) {
|
|
57
|
-
throw new RoutingError(`Route was not configured for key ${routeKey}`);
|
|
58
|
-
}
|
|
59
|
-
const { controller, handler, routeArgs } = routeConfig;
|
|
60
|
-
// Unnamed: the request id comes from the client, so it is not unique.
|
|
61
|
-
// Disposed before we respond, so async cleanup (e.g. rolling back a DB transaction)
|
|
62
|
-
// finishes first. An SSE route is the exception: its scope lives as long as the stream.
|
|
63
|
-
const scope = this.lifetimeScope.beginScope();
|
|
64
|
-
let scopeHandedOff = false;
|
|
65
|
-
// Runs inside the middleware chain so rejections (401/403/400) reach middleware such as
|
|
66
|
-
// the access log. Auth comes first so anonymous callers learn nothing about the body.
|
|
67
|
-
const handle = async ()=>{
|
|
68
|
-
const principal = await this.authorize(routeConfig, httpRequest);
|
|
69
|
-
if (this.httpOptions?.openapi?.validateRequests !== false && this.httpOptions?.openapi) {
|
|
70
|
-
validateRequest(routeConfig, httpRequest);
|
|
71
|
-
}
|
|
72
|
-
const provider = scope.resolve(IPrincipalProviderToken);
|
|
73
|
-
provider.setPrincipal(principal);
|
|
74
|
-
const controllerInstance = scope.resolve(controller);
|
|
75
|
-
const func = controllerInstance[handler];
|
|
76
|
-
const argValues = this.argResolver.resolveArguments(httpRequest, routeArgs, provider);
|
|
77
|
-
try {
|
|
78
|
-
const result = await func.apply(controllerInstance, argValues);
|
|
79
|
-
if (routeConfig.sse == null) {
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
if (typeof result?.[Symbol.asyncIterator] !== "function") {
|
|
83
|
-
throw new RoutingError(`${controller.name}.${String(handler)} is marked @Sse() so it must return an AsyncIterable (an async generator method works)`);
|
|
84
|
-
}
|
|
85
|
-
scopeHandedOff = true;
|
|
86
|
-
return new SseResponse(result, ()=>scope[Symbol.asyncDispose]());
|
|
87
|
-
} catch (error) {
|
|
88
|
-
this.logger.error(error.message, error.stack);
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
try {
|
|
93
|
-
const middlewares = this.resolveMiddlewares(scope);
|
|
94
|
-
if (middlewares.length === 0) {
|
|
95
|
-
return await handle();
|
|
96
|
-
}
|
|
97
|
-
return await composeMiddleware(middlewares, handle, {
|
|
98
|
-
request: httpRequest,
|
|
99
|
-
route: routeConfig
|
|
100
|
-
});
|
|
101
|
-
} catch (error) {
|
|
102
|
-
// A middleware failed after the handler returned: nobody will consume the stream.
|
|
103
|
-
scopeHandedOff = false;
|
|
104
|
-
throw error;
|
|
105
|
-
} finally{
|
|
106
|
-
if (!scopeHandedOff) {
|
|
107
|
-
await scope[Symbol.asyncDispose]();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
resolveMiddlewares(scope) {
|
|
112
|
-
const types = this.httpOptions?.middlewareTypes ?? [];
|
|
113
|
-
return types.map((type)=>scope.resolve(type));
|
|
114
|
-
}
|
|
115
|
-
async authorize(routeConfig, httpRequest) {
|
|
116
|
-
// Optional DI may resolve as null when auth is not registered.
|
|
117
|
-
if (this.authService == null) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const { controller, handler } = routeConfig;
|
|
121
|
-
const handlerFn = controller.prototype[handler];
|
|
122
|
-
if (isAllowAnonymous(controller.prototype, handlerFn)) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const authOptions = getAuthorizeOptions(controller.prototype, handlerFn);
|
|
126
|
-
if (authOptions === undefined) {
|
|
127
|
-
// Auth is enabled but the route has no @Authorize — require a valid
|
|
128
|
-
// token (fail closed) without checking a specific ability.
|
|
129
|
-
return this.authService.authenticateRequest(httpRequest);
|
|
130
|
-
}
|
|
131
|
-
return this.authService.authorizeRequest(httpRequest, authOptions.action, authOptions.subject);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
Router = _ts_decorate([
|
|
135
|
-
injectable(),
|
|
136
|
-
_ts_param(0, inject(RouteRegistryToken)),
|
|
137
|
-
_ts_param(1, inject(LifetimeScopeToken)),
|
|
138
|
-
_ts_param(2, inject(AuthorizationServiceToken)),
|
|
139
|
-
_ts_param(2, optional()),
|
|
140
|
-
_ts_param(3, inject(HttpOptionsToken)),
|
|
141
|
-
_ts_metadata("design:type", Function),
|
|
142
|
-
_ts_metadata("design:paramtypes", [
|
|
143
|
-
typeof RouteRegistry === "undefined" ? Object : RouteRegistry,
|
|
144
|
-
typeof LifetimeScope === "undefined" ? Object : LifetimeScope,
|
|
145
|
-
typeof IAuthorizationService === "undefined" ? Object : IAuthorizationService,
|
|
146
|
-
typeof HttpOptions === "undefined" ? Object : HttpOptions
|
|
147
|
-
])
|
|
148
|
-
], Router);
|
package/es6/src/sse/index.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { defineArgumentMeta, defineMeta } from "@decohere/core/reflection";
|
|
2
|
-
import { MetadataKeys } from "../const.js";
|
|
3
|
-
/**
|
|
4
|
-
* Mark a route as a server-sent event stream. The method returns an `AsyncIterable` of
|
|
5
|
-
* {@link SseMessage}s (an `async *` method is the usual shape) and the HTTP package writes it as
|
|
6
|
-
* `text/event-stream`. Auth, validation and middleware run first, exactly as on any route,
|
|
7
|
-
* so an unauthorised caller gets a normal 401/403 and no event is ever sent.
|
|
8
|
-
*
|
|
9
|
-
* Combine with `@Get()` (EventSource only issues GET).
|
|
10
|
-
*/ export function Sse(options = {}) {
|
|
11
|
-
return (_target, _methodName, descriptor)=>{
|
|
12
|
-
defineMeta(MetadataKeys.sse, options, descriptor.value);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function injected(location) {
|
|
16
|
-
return ()=>(target, propertyKey, parameterIndex)=>{
|
|
17
|
-
const fn = target[propertyKey];
|
|
18
|
-
const meta = {
|
|
19
|
-
argumentIndex: parameterIndex,
|
|
20
|
-
argumentLocation: location,
|
|
21
|
-
mappingKey: undefined,
|
|
22
|
-
pipes: []
|
|
23
|
-
};
|
|
24
|
-
defineArgumentMeta(MetadataKeys.parameter, meta, fn, parameterIndex);
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Inject an `AbortSignal` that aborts when the client disconnects or the app stops. Pass it to
|
|
29
|
-
* whatever the producer waits on (timers, subscriptions, queries) so it stops promptly.
|
|
30
|
-
*/ export const Signal = injected("signal");
|
|
31
|
-
/** Inject the `Last-Event-ID` header the browser sends when it reconnects, or `undefined`. */ export const LastEventId = injected("lastEventId");
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { scribe } from "@decohere/scribe";
|
|
2
|
-
import { sseWire } from "./sse.wire.js";
|
|
3
|
-
const logger = scribe.logger("SseEncoder");
|
|
4
|
-
/**
|
|
5
|
-
* Turn the handler's events into SSE text. Writes an opening comment so the browser sees the
|
|
6
|
-
* response headers at once, a heartbeat whenever the producer is quiet, and stops as soon as the
|
|
7
|
-
* signal aborts, without waiting for a producer that is stuck.
|
|
8
|
-
*/ export async function* encodeSse(source, options) {
|
|
9
|
-
const { signal, heartbeatMs } = options;
|
|
10
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
11
|
-
let onAbort;
|
|
12
|
-
const abortStep = new Promise((resolve)=>{
|
|
13
|
-
onAbort = ()=>resolve({
|
|
14
|
-
kind: "abort"
|
|
15
|
-
});
|
|
16
|
-
if (signal.aborted) onAbort();
|
|
17
|
-
else signal.addEventListener("abort", onAbort, {
|
|
18
|
-
once: true
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
let pending;
|
|
22
|
-
try {
|
|
23
|
-
yield sseWire.formatComment("open");
|
|
24
|
-
while(!signal.aborted){
|
|
25
|
-
pending ??= iterator.next().then((result)=>({
|
|
26
|
-
kind: "item",
|
|
27
|
-
result
|
|
28
|
-
}));
|
|
29
|
-
let timer;
|
|
30
|
-
const steps = [
|
|
31
|
-
pending,
|
|
32
|
-
abortStep
|
|
33
|
-
];
|
|
34
|
-
if (heartbeatMs > 0) {
|
|
35
|
-
steps.push(new Promise((resolve)=>{
|
|
36
|
-
timer = setTimeout(()=>resolve({
|
|
37
|
-
kind: "heartbeat"
|
|
38
|
-
}), heartbeatMs);
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
41
|
-
let step;
|
|
42
|
-
try {
|
|
43
|
-
step = await Promise.race(steps);
|
|
44
|
-
} finally{
|
|
45
|
-
clearTimeout(timer);
|
|
46
|
-
}
|
|
47
|
-
if (step.kind === "abort") return;
|
|
48
|
-
if (step.kind === "heartbeat") {
|
|
49
|
-
yield sseWire.formatComment("heartbeat");
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
pending = undefined;
|
|
53
|
-
if (step.result.done) return;
|
|
54
|
-
yield sseWire.formatMessage(step.result.value);
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
// Headers are already sent, so the only honest thing left is to log and end the stream.
|
|
58
|
-
if (!signal.aborted) logger.error("SSE producer failed", error);
|
|
59
|
-
} finally{
|
|
60
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
61
|
-
// If the producer has not answered its last `next()`, `return()` waits behind it. That is
|
|
62
|
-
// why handlers must pass the signal to whatever they wait on.
|
|
63
|
-
options.onProducerStopped(Promise.resolve().then(()=>iterator.return?.()).then(()=>undefined, (error)=>{
|
|
64
|
-
if (!signal.aborted) logger.error("SSE producer failed while stopping", error);
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
}
|
package/es6/src/sse/sse.types.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One server-sent event. A bare `string` yielded by a handler is shorthand for `{ data }`.
|
|
3
|
-
*
|
|
4
|
-
* `data` is written as-is when it is a string and as JSON otherwise.
|
|
5
|
-
*/ export const DEFAULT_SSE_HEARTBEAT_MS = 15_000;
|
|
6
|
-
/**
|
|
7
|
-
* What the router hands the HTTP provider for an `@Sse()` route. The request-scoped services the
|
|
8
|
-
* handler uses must stay alive for as long as the stream runs, so the provider calls `release()`
|
|
9
|
-
* once the stream is over, instead of the router disposing them as soon as the handler returns.
|
|
10
|
-
*/ export class SseResponse {
|
|
11
|
-
events;
|
|
12
|
-
onRelease;
|
|
13
|
-
released;
|
|
14
|
-
constructor(events, onRelease){
|
|
15
|
-
this.events = events;
|
|
16
|
-
this.onRelease = onRelease;
|
|
17
|
-
}
|
|
18
|
-
/** Safe to call more than once. */ release() {
|
|
19
|
-
this.released ??= this.onRelease();
|
|
20
|
-
return this.released;
|
|
21
|
-
}
|
|
22
|
-
}
|
package/es6/src/sse/sse.wire.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** Line breaks would end a field early, so a client-controlled `id` could inject extra fields. */ function singleLine(value) {
|
|
2
|
-
return value.replace(/[\r\n]+/g, " ");
|
|
3
|
-
}
|
|
4
|
-
function toMessage(item) {
|
|
5
|
-
if (typeof item === "object" && item !== null && "data" in item) {
|
|
6
|
-
return item;
|
|
7
|
-
}
|
|
8
|
-
return {
|
|
9
|
-
data: item
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
/** Encode one event as the text a browser's `EventSource` parses. */ function formatMessage(item) {
|
|
13
|
-
const { data, event, id, retry } = toMessage(item);
|
|
14
|
-
let frame = "";
|
|
15
|
-
if (id != null) frame += `id: ${singleLine(String(id))}\n`;
|
|
16
|
-
if (event != null) frame += `event: ${singleLine(event)}\n`;
|
|
17
|
-
if (retry != null && Number.isFinite(retry)) frame += `retry: ${Math.trunc(retry)}\n`;
|
|
18
|
-
const text = typeof data === "string" ? data : JSON.stringify(data) ?? "";
|
|
19
|
-
for (const line of text.split(/\r\n|\r|\n/)){
|
|
20
|
-
frame += `data: ${line}\n`;
|
|
21
|
-
}
|
|
22
|
-
return `${frame}\n`;
|
|
23
|
-
}
|
|
24
|
-
/** A comment line: ignored by `EventSource`, but it is traffic on the wire. */ function formatComment(text) {
|
|
25
|
-
return `: ${singleLine(text)}\n\n`;
|
|
26
|
-
}
|
|
27
|
-
export const sseWire = {
|
|
28
|
-
formatMessage,
|
|
29
|
-
formatComment
|
|
30
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Builder } from "../builder/builder.js";
|
|
2
|
-
import "../http/http.extensions.js";
|
|
3
|
-
import { FastifyProvider } from "../providers/fastify.provider.js";
|
|
4
|
-
import { readEventStream } from "./sse-reader.js";
|
|
5
|
-
/**
|
|
6
|
-
* Boot the given controllers as a real app on a free loopback port, so a test can call it over
|
|
7
|
-
* HTTP. The app runs the full pipeline: routing, auth, middleware, and the real provider.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* await using api = await bootModule({ controllers: [PriceController] });
|
|
11
|
-
* const { events } = await api.readEvents("/prices/stream", { count: 3 });
|
|
12
|
-
*/ export async function bootModule(options) {
|
|
13
|
-
let builder = Builder.create().withHttp(options.provider ?? FastifyProvider).withControllers(...options.controllers);
|
|
14
|
-
if (options.services) builder = builder.withServices(options.services);
|
|
15
|
-
options.configure?.(builder);
|
|
16
|
-
const app = await builder.build();
|
|
17
|
-
await app.start({
|
|
18
|
-
port: 0,
|
|
19
|
-
host: "127.0.0.1",
|
|
20
|
-
shutdownSignals: false
|
|
21
|
-
});
|
|
22
|
-
const baseUrl = app.addresses[0];
|
|
23
|
-
if (baseUrl == null) {
|
|
24
|
-
await app.dispose();
|
|
25
|
-
throw new Error("app.start({ port: 0 }) advertised no IPv4 address");
|
|
26
|
-
}
|
|
27
|
-
const stop = ()=>app.dispose();
|
|
28
|
-
return {
|
|
29
|
-
app,
|
|
30
|
-
baseUrl,
|
|
31
|
-
readEvents: (path, readOptions)=>readEventStream(`${baseUrl}${path}`, readOptions),
|
|
32
|
-
stop,
|
|
33
|
-
[Symbol.asyncDispose]: stop
|
|
34
|
-
};
|
|
35
|
-
}
|
package/es6/src/testing/index.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/** Incremental parser for the `text/event-stream` format. */ class SseParser {
|
|
2
|
-
buffer = "";
|
|
3
|
-
data = [];
|
|
4
|
-
current = {
|
|
5
|
-
data: ""
|
|
6
|
-
};
|
|
7
|
-
hasFields = false;
|
|
8
|
-
events = [];
|
|
9
|
-
comments = [];
|
|
10
|
-
push(chunk) {
|
|
11
|
-
this.buffer += chunk;
|
|
12
|
-
let newline;
|
|
13
|
-
while((newline = this.buffer.search(/\r\n|\r|\n/)) !== -1){
|
|
14
|
-
const line = this.buffer.slice(0, newline);
|
|
15
|
-
const eol = this.buffer.slice(newline).startsWith("\r\n") ? 2 : 1;
|
|
16
|
-
this.buffer = this.buffer.slice(newline + eol);
|
|
17
|
-
this.line(line);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
line(line) {
|
|
21
|
-
if (line === "") {
|
|
22
|
-
if (this.hasFields) {
|
|
23
|
-
this.events.push({
|
|
24
|
-
...this.current,
|
|
25
|
-
data: this.data.join("\n")
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
this.data = [];
|
|
29
|
-
this.current = {
|
|
30
|
-
data: ""
|
|
31
|
-
};
|
|
32
|
-
this.hasFields = false;
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (line.startsWith(":")) {
|
|
36
|
-
this.comments.push(line.slice(1).trim());
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const colon = line.indexOf(":");
|
|
40
|
-
const field = colon === -1 ? line : line.slice(0, colon);
|
|
41
|
-
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
42
|
-
if (value.startsWith(" ")) value = value.slice(1);
|
|
43
|
-
this.hasFields = true;
|
|
44
|
-
if (field === "data") this.data.push(value);
|
|
45
|
-
else if (field === "event") this.current.event = value;
|
|
46
|
-
else if (field === "id") this.current.id = value;
|
|
47
|
-
else if (field === "retry") this.current.retry = Number(value);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* GET `url` as an `EventSource` would and collect events until `count` have arrived or the
|
|
52
|
-
* server ends the stream. A non-200 answer (401, 403, ...) returns at once with no events.
|
|
53
|
-
*/ export async function readEventStream(url, options = {}) {
|
|
54
|
-
const count = options.count ?? 1;
|
|
55
|
-
const abort = new AbortController();
|
|
56
|
-
const timer = setTimeout(()=>abort.abort(new Error(`No ${count} events from ${url} in time`)), options.timeoutMs ?? 5000);
|
|
57
|
-
try {
|
|
58
|
-
const response = await fetch(url, {
|
|
59
|
-
headers: {
|
|
60
|
-
accept: "text/event-stream",
|
|
61
|
-
...options.headers
|
|
62
|
-
},
|
|
63
|
-
signal: abort.signal
|
|
64
|
-
});
|
|
65
|
-
const parser = new SseParser();
|
|
66
|
-
let ended = false;
|
|
67
|
-
if (response.status === 200 && response.body != null) {
|
|
68
|
-
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
69
|
-
while(parser.events.length < count){
|
|
70
|
-
const { done, value } = await reader.read();
|
|
71
|
-
if (done) {
|
|
72
|
-
ended = true;
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
parser.push(value);
|
|
76
|
-
}
|
|
77
|
-
await reader.cancel();
|
|
78
|
-
} else {
|
|
79
|
-
await response.body?.cancel();
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
status: response.status,
|
|
83
|
-
headers: response.headers,
|
|
84
|
-
events: parser.events.slice(0, count),
|
|
85
|
-
comments: parser.comments,
|
|
86
|
-
ended
|
|
87
|
-
};
|
|
88
|
-
} finally{
|
|
89
|
-
clearTimeout(timer);
|
|
90
|
-
}
|
|
91
|
-
}
|
package/types/src/app/app.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type LifetimeScope } from "@decohere/crucible";
|
|
2
|
-
import { type IConfigurationService } from "@decohere/core/configuration";
|
|
3
|
-
import type { HostedServiceHost } from "../hosting/hosted-service-host.js";
|
|
4
|
-
import type { HttpProvider, StartResponse } from "../routing/anvil.types.js";
|
|
5
|
-
export declare class ApiConfiguration {
|
|
6
|
-
port: number;
|
|
7
|
-
}
|
|
8
|
-
export type StartOptions = {
|
|
9
|
-
port: number;
|
|
10
|
-
host?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Signals that stop the app gracefully: stop accepting connections, let in-flight
|
|
13
|
-
* requests finish, stop hosted services, then dispose services. Default SIGTERM and SIGINT; `false` to
|
|
14
|
-
* handle signals yourself.
|
|
15
|
-
*/
|
|
16
|
-
shutdownSignals?: readonly NodeJS.Signals[] | false;
|
|
17
|
-
/** How long a signalled shutdown may take before the process exits with code 1. Default 10s. */
|
|
18
|
-
shutdownTimeoutMs?: number;
|
|
19
|
-
};
|
|
20
|
-
export declare class App {
|
|
21
|
-
private readonly container;
|
|
22
|
-
private configurationService?;
|
|
23
|
-
private httpProvider?;
|
|
24
|
-
private hostedServices?;
|
|
25
|
-
private _addresses;
|
|
26
|
-
private logger;
|
|
27
|
-
private disposing;
|
|
28
|
-
private disposingContainer;
|
|
29
|
-
private signalHandler;
|
|
30
|
-
private shutdownSignals;
|
|
31
|
-
constructor(container: LifetimeScope, configurationService?: IConfigurationService, httpProvider?: HttpProvider, hostedServices?: HostedServiceHost);
|
|
32
|
-
get addresses(): readonly string[];
|
|
33
|
-
start(options?: StartOptions): Promise<StartResponse>;
|
|
34
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
35
|
-
/** Stop the app. Calling it again (or from a signal) returns the same shutdown. */
|
|
36
|
-
dispose(): Promise<void>;
|
|
37
|
-
private stop;
|
|
38
|
-
private stopOnSignals;
|
|
39
|
-
private removeSignalHandlers;
|
|
40
|
-
private getPort;
|
|
41
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ContainerBuilder } from "@decohere/crucible";
|
|
2
|
-
import type { Constructable } from "@decohere/core/reflection";
|
|
3
|
-
export type ModuleDefinition = {
|
|
4
|
-
name: string;
|
|
5
|
-
entities?: Constructable[];
|
|
6
|
-
repositories?: Constructable[];
|
|
7
|
-
registerServices?(containerBuilder: ContainerBuilder): void;
|
|
8
|
-
};
|
|
9
|
-
export declare abstract class Module {
|
|
10
|
-
abstract name: string;
|
|
11
|
-
constructor(data: Partial<Module>);
|
|
12
|
-
readonly entities?: readonly Constructable[];
|
|
13
|
-
readonly repositories?: readonly Constructable[];
|
|
14
|
-
abstract registerServices?(containerBuilder: ContainerBuilder): void;
|
|
15
|
-
}
|