@decohere/http 0.0.1-dev.125 → 0.0.1-dev.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,7 +58,7 @@ Builder.create()
58
58
 
59
59
  **`exclude`.** Patterns for paths the mount never answers, neither a file nor the fallback (the request gets the JSON `404`), for anything the defaults get wrong. They are globs ([picomatch](https://github.com/micromatch/picomatch), `dot` on) matched against the full request path after decoding it and collapsing repeated slashes, not relative to the prefix: `*` is any characters within one segment, `**` crosses segments, `?` is one character. `"/docs/**"` covers `/docs`, `/docs/` and everything below.
60
60
 
61
- Why this shape, and what is left (running the UI's own build is [#381](https://github.com/typesmiths/decohere/issues/381)): [ADR 0007](../../docs/adr/0007-serving-a-built-ui-from-http.md).
61
+ Why this shape, and what is left (running the UI's own build): [ADR 0007](../../docs/adr/0007-serving-a-built-ui-from-http.md).
62
62
 
63
63
  ## Global prefix
64
64
 
@@ -113,7 +113,7 @@ What to know about the plugin's behaviour:
113
113
  - **A `304`** for a file can carry the `Content-Encoding` header of the `200` it stands for. It has no body.
114
114
  - **Helmet, CORS and the rate limit** are unaffected, and an error reply (a `401`, a `400` with a large body) is compressed like any other.
115
115
  - **BREACH.** Compressing a response served over HTTPS that contains both a secret and input an attacker controls can leak the secret to someone who can watch response sizes. It mostly concerns HTML that reflects input next to a CSRF token. A JSON API that authenticates with a bearer token in a header is low risk, because the token is not in the body. If you render such pages, mask per-response secrets, do not reflect input beside them, or turn compression off for them.
116
- - **Pre-compressed files.** Serving `.br`/`.gz` files built ahead of time is a possible later step ([#642](https://github.com/typesmiths/decohere/issues/642)); today static files are compressed on the fly.
116
+ - **Pre-compressed files.** Serving `.br`/`.gz` files built ahead of time is a possible later step; today static files are compressed on the fly.
117
117
  - **Testing.** Node's `fetch` negotiates and decodes compression itself. To see what is on the wire, use `node:http` and read the body undecoded.
118
118
 
119
119
  Why this shape: [ADR 0008](../../docs/adr/0008-response-compression-in-http.md).
@@ -18,7 +18,7 @@ class TokenService {
18
18
  principalMapper;
19
19
  /** Lazily resolved JWKS set when using OIDC discovery. */ discoveryJwks;
20
20
  discoveryIssuer;
21
- /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys (#499). */ configuredJwks;
21
+ /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys. */ configuredJwks;
22
22
  constructor(config, principalMapper){
23
23
  if ((0, _authtypes.isDevStaticConfig)(config)) {
24
24
  (0, _authtypes.assertDevStaticAllowed)();
@@ -41,7 +41,7 @@ const ajv = new _ajv.Ajv({
41
41
  });
42
42
  /**
43
43
  * Compiled once per route and reused. Compiling per request was slow, and AJV keeps every
44
- * schema it compiles, so memory grew with traffic (#498).
44
+ * schema it compiles, so memory grew with traffic.
45
45
  */ const validatorsByRoute = new WeakMap();
46
46
  function getValidators(route) {
47
47
  let validators = validatorsByRoute.get(route);
@@ -132,7 +132,7 @@ class JsonPipe extends Pipe {
132
132
  }
133
133
  }
134
134
  class NumberPipe extends Pipe {
135
- /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined` (#503). */ transform(value, _context) {
135
+ /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined`. */ transform(value, _context) {
136
136
  const text = typeof value === "number" ? null : String(value).trim();
137
137
  const number = text == null ? value : text === "" ? Number.NaN : Number(text);
138
138
  if (!Number.isFinite(number)) {
@@ -60,7 +60,7 @@ class RouteRegistry {
60
60
  const key = `${method}|${path}`;
61
61
  const existing = this.routeMap.get(key);
62
62
  if (existing != null) {
63
- // Last one used to win silently, leaving the other handler unreachable (#503).
63
+ // Last one used to win silently, leaving the other handler unreachable.
64
64
  throw new _anviltypes.RouteConfigurationError(`Duplicate route ${method.toUpperCase()} ${path}: ${existing.controller.name}.${existing.handler} and ${controller.name}.${handler}`);
65
65
  }
66
66
  const routeArgs = this.getRouteArguments(controller, func);
@@ -49,7 +49,7 @@ class Router {
49
49
  }
50
50
  /**
51
51
  * Without an authorization service nothing checks tokens, so a route marked @Authorize
52
- * would be served to anyone. Refuse to start instead of failing open (#500).
52
+ * would be served to anyone. Refuse to start instead of failing open.
53
53
  */ assertAuthConfiguredForProtectedRoutes() {
54
54
  if (this.authService != null) {
55
55
  return;
@@ -67,13 +67,13 @@ class Router {
67
67
  throw new _anviltypes.RoutingError(`Route was not configured for key ${routeKey}`);
68
68
  }
69
69
  const { controller, handler, routeArgs } = routeConfig;
70
- // Unnamed: the request id comes from the client, so it is not unique (#497).
70
+ // Unnamed: the request id comes from the client, so it is not unique.
71
71
  // Disposed before we respond, so async cleanup (e.g. rolling back a DB transaction)
72
72
  // finishes first. An SSE route is the exception: its scope lives as long as the stream.
73
73
  const scope = this.lifetimeScope.beginScope();
74
74
  let scopeHandedOff = false;
75
75
  // Runs inside the middleware chain so rejections (401/403/400) reach middleware such as
76
- // the access log. Auth comes first so anonymous callers learn nothing about the body (#501).
76
+ // the access log. Auth comes first so anonymous callers learn nothing about the body.
77
77
  const handle = async ()=>{
78
78
  const principal = await this.authorize(routeConfig, httpRequest);
79
79
  if (this.httpOptions?.openapi?.validateRequests !== false && this.httpOptions?.openapi) {
@@ -8,7 +8,7 @@ export class TokenService {
8
8
  principalMapper;
9
9
  /** Lazily resolved JWKS set when using OIDC discovery. */ discoveryJwks;
10
10
  discoveryIssuer;
11
- /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys (#499). */ configuredJwks;
11
+ /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys. */ configuredJwks;
12
12
  constructor(config, principalMapper){
13
13
  if (isDevStaticConfig(config)) {
14
14
  assertDevStaticAllowed();
@@ -27,7 +27,7 @@ const ajv = new Ajv({
27
27
  });
28
28
  /**
29
29
  * Compiled once per route and reused. Compiling per request was slow, and AJV keeps every
30
- * schema it compiles, so memory grew with traffic (#498).
30
+ * schema it compiles, so memory grew with traffic.
31
31
  */ const validatorsByRoute = new WeakMap();
32
32
  function getValidators(route) {
33
33
  let validators = validatorsByRoute.get(route);
@@ -105,7 +105,7 @@ export class JsonPipe extends Pipe {
105
105
  }
106
106
  }
107
107
  export class NumberPipe extends Pipe {
108
- /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined` (#503). */ transform(value, _context) {
108
+ /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined`. */ transform(value, _context) {
109
109
  const text = typeof value === "number" ? null : String(value).trim();
110
110
  const number = text == null ? value : text === "" ? Number.NaN : Number(text);
111
111
  if (!Number.isFinite(number)) {
@@ -50,7 +50,7 @@ export class RouteRegistry {
50
50
  const key = `${method}|${path}`;
51
51
  const existing = this.routeMap.get(key);
52
52
  if (existing != null) {
53
- // Last one used to win silently, leaving the other handler unreachable (#503).
53
+ // Last one used to win silently, leaving the other handler unreachable.
54
54
  throw new RouteConfigurationError(`Duplicate route ${method.toUpperCase()} ${path}: ${existing.controller.name}.${existing.handler} and ${controller.name}.${handler}`);
55
55
  }
56
56
  const routeArgs = this.getRouteArguments(controller, func);
@@ -39,7 +39,7 @@ export class Router {
39
39
  }
40
40
  /**
41
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 (#500).
42
+ * would be served to anyone. Refuse to start instead of failing open.
43
43
  */ assertAuthConfiguredForProtectedRoutes() {
44
44
  if (this.authService != null) {
45
45
  return;
@@ -57,13 +57,13 @@ export class Router {
57
57
  throw new RoutingError(`Route was not configured for key ${routeKey}`);
58
58
  }
59
59
  const { controller, handler, routeArgs } = routeConfig;
60
- // Unnamed: the request id comes from the client, so it is not unique (#497).
60
+ // Unnamed: the request id comes from the client, so it is not unique.
61
61
  // Disposed before we respond, so async cleanup (e.g. rolling back a DB transaction)
62
62
  // finishes first. An SSE route is the exception: its scope lives as long as the stream.
63
63
  const scope = this.lifetimeScope.beginScope();
64
64
  let scopeHandedOff = false;
65
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 (#501).
66
+ // the access log. Auth comes first so anonymous callers learn nothing about the body.
67
67
  const handle = async ()=>{
68
68
  const principal = await this.authorize(routeConfig, httpRequest);
69
69
  if (this.httpOptions?.openapi?.validateRequests !== false && this.httpOptions?.openapi) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/http",
3
- "version": "0.0.1-dev.125",
3
+ "version": "0.0.1-dev.127",
4
4
  "description": "Web API and microservice platform built on Fastify with decorator-based routing",
5
5
  "keywords": [
6
6
  "typescript",
@@ -56,9 +56,9 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@casl/ability": "^6.8.1",
59
- "@decohere/core": "0.0.1-dev.125",
60
- "@decohere/crucible": "0.0.1-dev.125",
61
- "@decohere/scribe": "0.0.1-dev.125",
59
+ "@decohere/core": "0.0.1-dev.127",
60
+ "@decohere/crucible": "0.0.1-dev.127",
61
+ "@decohere/scribe": "0.0.1-dev.127",
62
62
  "@fastify/compress": "^9.2.1",
63
63
  "@fastify/cors": "^11.3.0",
64
64
  "@fastify/helmet": "^13.1.0",
@@ -7,7 +7,7 @@ export declare class TokenService {
7
7
  /** Lazily resolved JWKS set when using OIDC discovery. */
8
8
  private discoveryJwks?;
9
9
  private discoveryIssuer?;
10
- /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys (#499). */
10
+ /** Remote key set for `jwksUrl` mode, created once so jose can cache the keys. */
11
11
  private configuredJwks?;
12
12
  constructor(config: TokenServiceConfig, principalMapper?: PrincipalMapper);
13
13
  verifyRequest(request: HttpRequest): Promise<Principal>;
@@ -16,6 +16,6 @@ export declare class JsonPipe extends Pipe<object> {
16
16
  private reviver;
17
17
  }
18
18
  export declare class NumberPipe extends Pipe<number> {
19
- /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined` (#503). */
19
+ /** Finite numbers only. Blank or non-numeric input is a 400, not `undefined`. */
20
20
  transform(value: unknown, _context?: unknown): number;
21
21
  }
@@ -13,7 +13,7 @@ export declare class Router {
13
13
  constructor(routeRegistry: RouteRegistry, lifetimeScope: LifetimeScope, authService?: IAuthorizationService, httpOptions?: HttpOptions);
14
14
  /**
15
15
  * Without an authorization service nothing checks tokens, so a route marked @Authorize
16
- * would be served to anyone. Refuse to start instead of failing open (#500).
16
+ * would be served to anyone. Refuse to start instead of failing open.
17
17
  */
18
18
  private assertAuthConfiguredForProtectedRoutes;
19
19
  route(routeKey: string, httpRequest: HttpRequest): Promise<any>;