@rexeus/typeweaver-server 0.10.2 → 0.10.4

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.
@@ -10,10 +10,15 @@ export function poweredBy(options?: PoweredByOptions) {
10
10
 
11
11
  return defineMiddleware(async (_ctx, next) => {
12
12
  const response = await next();
13
+ const responseHeaders = Object.fromEntries(
14
+ Object.entries(response.header ?? {}).filter(
15
+ ([headerName]) => headerName.toLowerCase() !== "x-powered-by"
16
+ )
17
+ );
13
18
 
14
19
  return {
15
20
  ...response,
16
- header: { ...response.header, "x-powered-by": value },
21
+ header: { ...responseHeaders, "x-powered-by": value },
17
22
  } satisfies IHttpResponse;
18
23
  });
19
24
  }
@@ -1,29 +1,29 @@
1
1
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
+ import { omitHeaders, readSingletonHeader } from "./header.js";
3
4
 
4
5
  export type RequestIdOptions = {
5
6
  readonly headerName?: string;
6
7
  readonly generator?: () => string;
7
8
  };
8
9
 
10
+ const isValidRequestId = (value: string | undefined): value is string =>
11
+ value !== undefined && value.length > 0 && !/[\r\n]/.test(value);
12
+
9
13
  export function requestId(options?: RequestIdOptions) {
10
14
  const headerName = (options?.headerName ?? "x-request-id").toLowerCase();
11
15
  const generator = options?.generator ?? (() => crypto.randomUUID());
12
16
 
13
17
  return defineMiddleware<{ requestId: string }>(async (ctx, next) => {
14
- const existing = ctx.request.header?.[headerName];
15
- const id =
16
- typeof existing === "string"
17
- ? existing
18
- : Array.isArray(existing)
19
- ? (existing[0] ?? generator())
20
- : generator();
18
+ const existing = readSingletonHeader(ctx.request.header, headerName);
19
+ const id = isValidRequestId(existing) ? existing : generator();
21
20
 
22
21
  const response = await next({ requestId: id });
22
+ const header = omitHeaders(response.header, [headerName]);
23
23
 
24
24
  return {
25
25
  ...response,
26
- header: { ...response.header, [headerName]: id },
26
+ header: { ...header, [headerName]: id },
27
27
  } satisfies IHttpResponse;
28
28
  });
29
29
  }
@@ -2,55 +2,70 @@ import { pathMatcher } from "../PathMatcher.js";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
3
  import type { TypedMiddleware } from "../TypedMiddleware.js";
4
4
 
5
+ type NoProvidedKeys<TProvides extends Record<string, unknown>> = [
6
+ keyof TProvides,
7
+ ] extends [never]
8
+ ? unknown
9
+ : never;
10
+
5
11
  /**
6
12
  * Restricts a middleware to only run on paths matching the given patterns.
7
13
  *
8
14
  * Accepts the same pattern syntax as {@link pathMatcher}: exact (`"/users"`),
9
15
  * prefix (`"/api/*"`), and parameterized (`"/users/:id"`).
10
16
  *
11
- * Only accepts non-state middleware (`TypedMiddleware<{}, {}>`) to preserve
17
+ * Only accepts non-state-providing middleware to preserve
12
18
  * TypeWeaver's compile-time state guarantees — skipping a state-providing
13
19
  * middleware would leave downstream consumers with missing state.
20
+ * Any upstream state requirements declared by the wrapped middleware are
21
+ * preserved on the returned middleware descriptor.
14
22
  *
15
23
  * @example
16
24
  * ```typescript
17
25
  * app.use(scoped(["/api/*"], cors({ origin: "https://app.com" })));
18
26
  * ```
19
27
  */
20
- export function scoped(
28
+ export function scoped<
29
+ TProvides extends Record<string, unknown>,
30
+ TRequires extends Record<string, unknown>,
31
+ >(
21
32
  paths: readonly string[],
22
- middleware: TypedMiddleware<{}, {}>
23
- ): TypedMiddleware<{}, {}> {
33
+ middleware: TypedMiddleware<TProvides, TRequires> & NoProvidedKeys<TProvides>
34
+ ): TypedMiddleware<TProvides, TRequires> {
24
35
  const matchers = paths.map(pathMatcher);
25
36
 
26
- return defineMiddleware(async (ctx, next) => {
37
+ return defineMiddleware<{}, TRequires>(async (ctx, next) => {
27
38
  if (!matchers.some(match => match(ctx.request.path))) {
28
39
  return next();
29
40
  }
30
41
  return middleware.handler(ctx, next);
31
- });
42
+ }) as TypedMiddleware<TProvides, TRequires>;
32
43
  }
33
44
 
34
45
  /**
35
46
  * Runs a middleware on all paths *except* those matching the given patterns.
36
47
  *
37
- * The inverse of {@link scoped}. Same pattern syntax and type constraint.
48
+ * The inverse of {@link scoped}. Same pattern syntax and type constraints,
49
+ * including preservation of wrapped middleware state requirements.
38
50
  *
39
51
  * @example
40
52
  * ```typescript
41
53
  * app.use(except(["/health", "/ready"], logger()));
42
54
  * ```
43
55
  */
44
- export function except(
56
+ export function except<
57
+ TProvides extends Record<string, unknown>,
58
+ TRequires extends Record<string, unknown>,
59
+ >(
45
60
  paths: readonly string[],
46
- middleware: TypedMiddleware<{}, {}>
47
- ): TypedMiddleware<{}, {}> {
61
+ middleware: TypedMiddleware<TProvides, TRequires> & NoProvidedKeys<TProvides>
62
+ ): TypedMiddleware<TProvides, TRequires> {
48
63
  const matchers = paths.map(pathMatcher);
49
64
 
50
- return defineMiddleware(async (ctx, next) => {
65
+ return defineMiddleware<{}, TRequires>(async (ctx, next) => {
51
66
  if (matchers.some(match => match(ctx.request.path))) {
52
67
  return next();
53
68
  }
54
69
  return middleware.handler(ctx, next);
55
- });
70
+ }) as TypedMiddleware<TProvides, TRequires>;
56
71
  }
@@ -1,5 +1,6 @@
1
1
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
+ import { omitHeaders } from "./header.js";
3
4
 
4
5
  export type SecureHeadersOptions = {
5
6
  readonly contentTypeOptions?: string | false;
@@ -48,10 +49,11 @@ export function secureHeaders(options?: SecureHeadersOptions) {
48
49
 
49
50
  return defineMiddleware(async (_ctx, next) => {
50
51
  const response = await next();
52
+ const header = omitHeaders(response.header, Object.keys(headers));
51
53
 
52
54
  return {
53
55
  ...response,
54
- header: { ...headers, ...response.header },
56
+ header: { ...header, ...headers },
55
57
  } satisfies IHttpResponse;
56
58
  });
57
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "description": "Generates a lightweight, dependency-free server with built-in routing and middleware from your API definitions. Powered by Typeweaver.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,15 +47,18 @@
47
47
  },
48
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
49
49
  "peerDependencies": {
50
- "@rexeus/typeweaver-core": "^0.10.2",
51
- "@rexeus/typeweaver-gen": "^0.10.2"
50
+ "@rexeus/typeweaver-core": "^0.10.4",
51
+ "@rexeus/typeweaver-gen": "^0.10.4"
52
52
  },
53
53
  "devDependencies": {
54
54
  "get-port": "^7.2.0",
55
55
  "test-utils": "file:../test-utils",
56
56
  "tsx": "^4.21.0",
57
- "@rexeus/typeweaver-core": "^0.10.2",
58
- "@rexeus/typeweaver-gen": "^0.10.2"
57
+ "@rexeus/typeweaver-core": "^0.10.4",
58
+ "@rexeus/typeweaver-gen": "^0.10.4"
59
+ },
60
+ "dependencies": {
61
+ "polycase": "^1.1.0"
59
62
  },
60
63
  "scripts": {
61
64
  "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",