@contractkit/plugin-typescript 0.35.0 → 0.36.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.
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
5
5
  CLI tsup v8.5.1
6
6
  CLI Target: esnext
7
7
  ESM Build start
8
- ESM dist/index.js 225.18 KB
9
- ESM dist/index.js.map 531.21 KB
10
- ESM ⚡️ Build success in 386ms
8
+ ESM dist/index.js 230.12 KB
9
+ ESM dist/index.js.map 542.08 KB
10
+ ESM ⚡️ Build success in 359ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 3500ms
13
- DTS dist/index.d.ts 10.53 KB
12
+ DTS ⚡️ Build success in 3932ms
13
+ DTS dist/index.d.ts 11.34 KB
@@ -3,39 +3,41 @@ $ vitest run --coverage
3
3
   RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/packages/plugin-typescript
4
4
  Coverage enabled with v8
5
5
 
6
- ✓ tests/codegen-operation.test.ts (161 tests) 207ms
7
- ✓ tests/codegen-contract.test.ts (147 tests) 155ms
8
- ✓ tests/codegen-sdk.test.ts (173 tests) 256ms
9
- ✓ tests/codegen-plain-types.test.ts (67 tests) 54ms
10
- ✓ tests/codegen-server.test.ts (39 tests) 147ms
11
- ✓ tests/pipeline.test.ts (34 tests) 433ms
12
- ✓ tests/codegen-mcp.test.ts (33 tests) 29ms
13
- ✓ tests/codegen-operation-framework.test.ts (8 tests) 6ms
14
- ✓ tests/server-framework-koa.test.ts (19 tests) 18ms
6
+ ✓ tests/codegen-operation.test.ts (166 tests) 190ms
7
+ ✓ tests/codegen-contract.test.ts (147 tests) 182ms
8
+ ✓ tests/codegen-sdk.test.ts (173 tests) 228ms
9
+ ✓ tests/codegen-plain-types.test.ts (67 tests) 39ms
10
+ ✓ tests/codegen-server.test.ts (39 tests) 129ms
11
+ ✓ tests/pipeline.test.ts (34 tests) 542ms
12
+ ✓ tests/codegen-mcp.test.ts (33 tests) 52ms
13
+ ✓ tests/server-framework-fastify.test.ts (25 tests) 11ms
14
+ ✓ tests/codegen-operation-framework.test.ts (14 tests) 9ms
15
+ ✓ tests/server-framework-koa.test.ts (19 tests) 22ms
15
16
  ✓ tests/ts-render.test.ts (5 tests) 10ms
16
- ✓ tests/escaping-security.test.ts (18 tests) 22ms
17
- ✓ tests/server-framework.test.ts (4 tests) 7ms
17
+ ✓ tests/server-framework.test.ts (4 tests) 13ms
18
+ ✓ tests/escaping-security.test.ts (18 tests) 20ms
18
19
 
19
-  Test Files  12 passed (12)
20
-  Tests  708 passed (708)
21
-  Start at  18:00:17
22
-  Duration  7.85s (transform 4.21s, setup 0ms, import 14.10s, tests 1.34s, environment 1ms)
20
+  Test Files  13 passed (13)
21
+  Tests  744 passed (744)
22
+  Start at  19:51:10
23
+  Duration  9.04s (transform 3.75s, setup 0ms, import 16.64s, tests 1.45s, environment 3ms)
23
24
 
24
25
   % Coverage report from v8
25
26
  -------------------|---------|----------|---------|---------|-------------------
26
27
  File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
27
28
  -------------------|---------|----------|---------|---------|-------------------
28
- All files | 84.59 | 78.42 | 87.93 | 87.3 |
29
- src | 84.42 | 78.12 | 87.61 | 87.16 |
29
+ All files | 84.73 | 78.53 | 88.26 | 87.45 |
30
+ src | 84.57 | 78.23 | 87.97 | 87.3 |
30
31
  ...n-contract.ts | 90.13 | 85.89 | 90.51 | 90.9 | ...1166,1171-1172
31
32
  codegen-mcp.ts | 83.44 | 75.46 | 84.21 | 86.61 | ...01,306-307,392
32
- ...-operation.ts | 83.27 | 80.62 | 80.82 | 85.33 | ...1070,1075-1076
33
+ ...-operation.ts | 83.68 | 80.85 | 81.08 | 85.74 | ...1114,1119-1120
33
34
  ...lain-types.ts | 91.7 | 83.8 | 96.96 | 94.57 | ...79,204,316,322
34
35
  ...gen-revive.ts | 59.23 | 52.54 | 78.37 | 65.28 | ...85-316,398-404
35
36
  codegen-sdk.ts | 88.46 | 80.47 | 90.55 | 91.36 | ...1891,1895-1896
36
37
  ...al-runtime.ts | 100 | 100 | 100 | 100 |
37
- index.ts | 77.23 | 65.81 | 83.6 | 81.28 | ...1152,1168-1187
38
+ index.ts | 77.23 | 65.81 | 83.6 | 81.28 | ...1153,1169-1188
38
39
  path-utils.ts | 70.5 | 52.27 | 100 | 73.72 | ...75-178,226-241
40
+ ...rk-fastify.ts | 100 | 100 | 100 | 100 |
39
41
  ...mework-koa.ts | 100 | 100 | 100 | 100 |
40
42
  ...-framework.ts | 100 | 100 | 100 | 100 |
41
43
  ts-render.ts | 86.04 | 88.23 | 80 | 90.14 | 103,191,224-232
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @contractkit/contractkit-plugin-typescript
2
2
 
3
+ ## 0.36.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 18263e2: Generate Fastify routers with `server.framework: "fastify"`.
8
+
9
+ ```json
10
+ "server": { "framework": "fastify", "baseDir": "apps/api/" }
11
+ ```
12
+
13
+ Output targets `@maroonedsoftware/fastify`, whose exported surface mirrors the Koa one, so a contract compiles to the same handlers either way:
14
+
15
+ ```ts
16
+ BillingRouter.post('/payments', requirePolicy(), bodyParserMiddleware(['json']), async (request, reply) => {
17
+ const body = await parseAndValidate(request.parsedBody, PaymentInput);
18
+
19
+ const service = request.container.get(PaymentService);
20
+ const result: Payment = await service.create(body);
21
+
22
+ reply.status(200);
23
+ reply.type('application/json');
24
+ return reply.send(result);
25
+ });
26
+ ```
27
+
28
+ Three differences are worth knowing before you switch:
29
+ - **The request is the context.** Handlers take `(request, reply)`, and params, query, headers, the DI container and the parsed body all hang off `request`. Fastify's own `request.body` is never populated, because ServerKit parses lazily per route.
30
+ - **Responses are returned, not assigned.** A handler that neither returns a body nor calls `send` leaves the request hanging, so a bodyless 204 emits an explicit `return reply.send();` where Koa emits nothing at all.
31
+ - **Content type comes from `requestMediaType(request)`.** Fastify has no accessor that strips the parameters off the header, and a raw `application/json; charset=utf-8` matches none of the MIME literals an operation declares.
32
+
33
+ `koa` remains the default and its output is unchanged. The generated `mcp.router.ts` follows the setting, using `reply.hijack()` where the Koa mount sets `ctx.respond = false`.
34
+
35
+ Separately, a path parameter whose name collides with an identifier the handler already binds is now renamed. `/threads/{reply}` emitted `const { reply } = ...` inside `async (request, reply) => {`, a redeclaration under `tsc` and a temporal-dead-zone `ReferenceError` at runtime; the same held on Koa for a parameter named `ctx`, and on both for one named after a generator local such as `body` or `query`. Only the local binding moves, so the wire name, the schema key and the route placeholder are untouched.
36
+
3
37
  ## 0.35.0
4
38
 
5
39
  ### Minor Changes
package/README.md CHANGED
@@ -57,7 +57,7 @@ Generates server router files from `operation` declarations and optionally type
57
57
  | Option | Type | Default | Description |
58
58
  |---|---|---|---|
59
59
  | `baseDir` | `string` | `rootDir` | Base directory for output files |
60
- | `framework` | `string` | `"koa"` | HTTP framework the routers target. Only `koa` is supported today. |
60
+ | `framework` | `string` | `"koa"` | HTTP framework the routers target: `"koa"` or `"fastify"`. Also selects the `mcp.router.ts` flavour. |
61
61
  | `zod` | `boolean` | `false` | Emit Zod schemas in `output.types` instead of plain interfaces |
62
62
  | `output.routes` | `string` | — | Path template for router files |
63
63
  | `output.types` | `string` | — | Path template for type/schema files |
@@ -132,7 +132,7 @@ Contracts without visibility modifiers generate a single `Model` schema.
132
132
 
133
133
  ### Router shape (from `operation`)
134
134
 
135
- Each operation file generates one router, targeting the framework named by `framework` (`koa` today). Request bodies and path/query params are validated against the Zod schemas (when `zod: true`) or plain types. Handlers are expected to be exported from the service module specified by `servicePathTemplate`.
135
+ Each operation file generates one router, targeting the framework named by `framework`. On Koa a handler is `async ctx => {}` writing `ctx.status` / `ctx.type` / `ctx.body`; on Fastify it is `async (request, reply) => {}` reading `request.parsedBody` and returning `reply.send(...)`. Validation, service dispatch and the response shape are identical either way. Request bodies and path/query params are validated against the Zod schemas (when `zod: true`) or plain types. Handlers are expected to be exported from the service module specified by `servicePathTemplate`.
136
136
 
137
137
  Responses are only type-annotated by default. With `validateResponses: true` (which requires `zod: true`) the service's return value is re-parsed against its declared response schema and the parsed value is written to `ctx.body`, so a service returning a shape the contract does not allow fails with a 500 instead of shipping it. See [docs/config.md](../../docs/config.md#validateresponses) for the caveats — notably that models using `format(input=…)`/`format(output=…)` are skipped.
138
138
 
@@ -74,7 +74,7 @@ export declare function inferService(op: OpOperationNode, route: OpRouteNode, fi
74
74
  *
75
75
  * @returns The rendered argument list, or an empty string when the method takes no arguments.
76
76
  */
77
- export declare function buildArgs(route: OpRouteNode, op: OpOperationNode): string;
77
+ export declare function buildArgs(route: OpRouteNode, op: OpOperationNode, bindings?: Map<string, string>): string;
78
78
  /**
79
79
  * Derive the PascalCase base name used for router, service, and type names from a contract file path.
80
80
  *
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-operation.d.ts","sourceRoot":"","sources":["../src/codegen-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EACV,WAAW,EACX,eAAe,EAGf,gBAAgB,EAInB,MAAM,mBAAmB,CAAC;AAa3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AA0BvD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,gBAAgB,GAAG,OAAO,CA0E5F;AAID,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,qFAAqF;IACrF,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAKD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAgHnF;AAuTD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAY7H;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,eAAe,GAAG,MAAM,CAqBzE;AAgbD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWnD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ/E"}
1
+ {"version":3,"file":"codegen-operation.d.ts","sourceRoot":"","sources":["../src/codegen-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,UAAU,EACV,WAAW,EACX,eAAe,EAGf,gBAAgB,EAInB,MAAM,mBAAmB,CAAC;AAa3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,sGAAsG;AACtG,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAsDvD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,gBAAgB,GAAG,OAAO,CA0E5F;AAID,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,qFAAqF;IACrF,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAKD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAgHnF;AA0TD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAY7H;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAuBzG;AA2bD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWnD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ/E"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { ContractKitPlugin } from '@contractkit/core';
2
2
  import { type ServerFrameworkName } from './server-framework.js';
3
3
  export { SERVER_FRAMEWORK_NAMES, DEFAULT_SERVER_FRAMEWORK_NAME, resolveServerFramework, SERVER_FRAMEWORKS, type ServerFramework, type ServerFrameworkName, } from './server-framework.js';
4
4
  export { KOA_SERVER_FRAMEWORK } from './server-framework-koa.js';
5
+ export { FASTIFY_SERVER_FRAMEWORK } from './server-framework-fastify.js';
5
6
  /** Server output: routers, and the type or Zod schema files they import. */
6
7
  export interface ServerConfig {
7
8
  /** Directory (relative to rootDir) where server files are written. Default: rootDir. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1476,6 +1476,9 @@ var KOA_SERVER_FRAMEWORK = {
1476
1476
  pathParam(identifier) {
1477
1477
  return `:${identifier}`;
1478
1478
  },
1479
+ handlerLocals: [
1480
+ "ctx"
1481
+ ],
1479
1482
  routeOpen(routerName, method, path, middlewares) {
1480
1483
  const middlewareStr = middlewares.length > 0 ? `, ${middlewares.join(", ")},` : ",";
1481
1484
  return `${routerName}.${method}('${path}'${middlewareStr} async ctx => {`;
@@ -1557,6 +1560,31 @@ export function mountMcp(router: ReturnType<typeof ServerKitRouter>): void {
1557
1560
  };
1558
1561
 
1559
1562
  // src/codegen-operation.ts
1563
+ var GENERATOR_HANDLER_LOCALS = [
1564
+ "service",
1565
+ "result",
1566
+ "body",
1567
+ "multipartBody",
1568
+ "params",
1569
+ "query",
1570
+ "headers"
1571
+ ];
1572
+ function bindPathParams(nodes, handlerLocals) {
1573
+ const reserved = /* @__PURE__ */ new Set([
1574
+ ...handlerLocals,
1575
+ ...GENERATOR_HANDLER_LOCALS
1576
+ ]);
1577
+ const taken = /* @__PURE__ */ new Set();
1578
+ const bindings = /* @__PURE__ */ new Map();
1579
+ for (const node of nodes) {
1580
+ let local = toIdentifier(node.name);
1581
+ while (reserved.has(local) || taken.has(local)) local += "_";
1582
+ taken.add(local);
1583
+ bindings.set(node.name, local);
1584
+ }
1585
+ return bindings;
1586
+ }
1587
+ __name(bindPathParams, "bindPathParams");
1560
1588
  function bodyParserToken(contentType) {
1561
1589
  switch (classifyContentType(contentType)) {
1562
1590
  case "urlencoded":
@@ -1763,7 +1791,8 @@ function generateHandler(route, op, root, options) {
1763
1791
  middlewares.push(framework.middleware.signature(sigArgs));
1764
1792
  }
1765
1793
  lines.push(framework.routeOpen(deriveRouterName(file), method, path, middlewares));
1766
- lines.push(...generateParamValidation(route.params, "params", framework.request.params, route.paramsMode ?? "strict", "", modelsWithInput));
1794
+ const pathBindings = route.params?.kind === "params" ? bindPathParams(route.params.nodes, framework.handlerLocals) : void 0;
1795
+ lines.push(...generateParamValidation(route.params, "params", framework.request.params, route.paramsMode ?? "strict", "", modelsWithInput, pathBindings));
1767
1796
  lines.push(...generateParamValidation(op.query, "query", framework.request.query, op.queryMode ?? "strict", "", modelsWithInput));
1768
1797
  lines.push(...generateParamValidation(op.headers, "headers", framework.request.headers, op.headersMode ?? "strip", "", modelsWithInput));
1769
1798
  if (hasBody && op.request) {
@@ -1795,7 +1824,7 @@ function generateHandler(route, op, root, options) {
1795
1824
  }
1796
1825
  const emitted = emittedResponses(op);
1797
1826
  const serviceParts = inferService(op, route, file);
1798
- const call = `await service.${serviceParts.methodName}(${buildArgs(route, op)})`;
1827
+ const call = `await service.${serviceParts.methodName}(${buildArgs(route, op, pathBindings)})`;
1799
1828
  if (emitted.length > 1) {
1800
1829
  lines.push(...generateMultiStatusResult(emitted, serviceParts.className, call, options));
1801
1830
  } else {
@@ -2002,11 +2031,11 @@ function inferMethodName(method, path) {
2002
2031
  }
2003
2032
  }
2004
2033
  __name(inferMethodName, "inferMethodName");
2005
- function buildArgs(route, op) {
2034
+ function buildArgs(route, op, bindings) {
2006
2035
  const args = [];
2007
2036
  if (route.params) {
2008
2037
  if (route.params.kind === "params") {
2009
- args.push(...route.params.nodes.map((p) => toIdentifier(p.name)));
2038
+ args.push(...route.params.nodes.map((p) => bindings?.get(p.name) ?? toIdentifier(p.name)));
2010
2039
  } else {
2011
2040
  args.push("params");
2012
2041
  }
@@ -2126,7 +2155,7 @@ function responseBodyExpr(value, schema) {
2126
2155
  return schema ? `await parseAndValidate(${value}, ${schema}, 500)` : value;
2127
2156
  }
2128
2157
  __name(responseBodyExpr, "responseBodyExpr");
2129
- function generateParamValidation(source, kind, sourceExpr, mode, suffix = "", modelsWithInput) {
2158
+ function generateParamValidation(source, kind, sourceExpr, mode, suffix = "", modelsWithInput, bindings) {
2130
2159
  if (!source) return [];
2131
2160
  const lines = [];
2132
2161
  const isQuery = kind === "query";
@@ -2138,7 +2167,11 @@ function generateParamValidation(source, kind, sourceExpr, mode, suffix = "", mo
2138
2167
  } else if (source.kind === "params") {
2139
2168
  if (source.nodes.length > 0) {
2140
2169
  const bind = /* @__PURE__ */ __name((name) => isPathParams ? toIdentifier(name) : name, "bind");
2141
- const lhs = isPathParams ? `{ ${source.nodes.map((p) => bind(p.name)).join(", ")} }` : kind;
2170
+ const lhs = isPathParams ? `{ ${source.nodes.map((p) => {
2171
+ const wire = bind(p.name);
2172
+ const local = bindings?.get(p.name) ?? wire;
2173
+ return wire === local ? wire : `${wire}: ${local}`;
2174
+ }).join(", ")} }` : kind;
2142
2175
  lines.push(` const ${lhs} = await parseAndValidate(`);
2143
2176
  lines.push(` ${sourceExpr},`);
2144
2177
  lines.push(` ${modeToWrapper(mode)}({`);
@@ -4215,13 +4248,131 @@ function renderOutputField(field, outputCase, modelsWithOutput, target) {
4215
4248
  }
4216
4249
  __name(renderOutputField, "renderOutputField");
4217
4250
 
4251
+ // src/server-framework-fastify.ts
4252
+ var FASTIFY_RUNTIME_MODULE = "@maroonedsoftware/fastify";
4253
+ var FASTIFY_RUNTIME_SYMBOLS = [
4254
+ "ServerKitRouter",
4255
+ "bodyParserMiddleware",
4256
+ "requirePolicy",
4257
+ "requireSignature",
4258
+ "requestMediaType"
4259
+ ];
4260
+ var FASTIFY_SERVER_FRAMEWORK = {
4261
+ name: "fastify",
4262
+ imports(uses) {
4263
+ const symbols = FASTIFY_RUNTIME_SYMBOLS.filter(uses);
4264
+ return symbols.length > 0 ? [
4265
+ `import { ${symbols.join(", ")} } from '${FASTIFY_RUNTIME_MODULE}';`
4266
+ ] : [];
4267
+ },
4268
+ routerDeclaration(routerName) {
4269
+ return `export const ${routerName} = ServerKitRouter();`;
4270
+ },
4271
+ pathParam(identifier) {
4272
+ return `:${identifier}`;
4273
+ },
4274
+ handlerLocals: [
4275
+ "request",
4276
+ "reply"
4277
+ ],
4278
+ routeOpen(routerName, method, path, middlewares) {
4279
+ const middlewareStr = middlewares.length > 0 ? `, ${middlewares.join(", ")},` : ",";
4280
+ return `${routerName}.${method}('${path}'${middlewareStr} async (request, reply) => {`;
4281
+ },
4282
+ routeClose() {
4283
+ return [
4284
+ "});"
4285
+ ];
4286
+ },
4287
+ middleware: {
4288
+ policy(args) {
4289
+ return `requirePolicy(${args})`;
4290
+ },
4291
+ bodyParser(tokensExpr) {
4292
+ return `bodyParserMiddleware([${tokensExpr}])`;
4293
+ },
4294
+ signature(args) {
4295
+ return `requireSignature(${args})`;
4296
+ }
4297
+ },
4298
+ request: {
4299
+ params: "request.params",
4300
+ query: "request.query",
4301
+ headers: "request.headers",
4302
+ // ServerKit parses lazily per route, so Fastify's own `request.body` is never populated.
4303
+ parsedBody: "request.parsedBody",
4304
+ // A call, not a property: the raw header carries `; charset=utf-8`, which would match none of
4305
+ // the declared MIME literals the generated switch compares against.
4306
+ contentType: "requestMediaType(request)"
4307
+ },
4308
+ resolveService(className) {
4309
+ return `request.container.get(${className})`;
4310
+ },
4311
+ response: {
4312
+ status(expr) {
4313
+ return `reply.status(${expr});`;
4314
+ },
4315
+ header(name, valueExpr) {
4316
+ return `reply.header('${name}', ${valueExpr});`;
4317
+ },
4318
+ type(expr) {
4319
+ return `reply.type(${expr});`;
4320
+ },
4321
+ send(bodyExpr) {
4322
+ return bodyExpr === void 0 ? [
4323
+ "return reply.send();"
4324
+ ] : [
4325
+ `return reply.send(${bodyExpr});`
4326
+ ];
4327
+ },
4328
+ caseEnd() {
4329
+ return [];
4330
+ }
4331
+ },
4332
+ mcpRouter({ path }) {
4333
+ return `import { type ServerKitRouterType, bodyParserMiddleware, requireSignature, requestHeader } from '${FASTIFY_RUNTIME_MODULE}';
4334
+ import { McpDispatcher, createMcpRequestContext, MCP_AUTH_POLICY } from '@maroonedsoftware/mcp';
4335
+
4336
+ /** Mount the MCP endpoint onto a ServerKit router. Bind \`registerMcpTools\` to the \`McpToolHandlerMap\` token. */
4337
+ export function mountMcp(router: ServerKitRouterType): void {
4338
+ router.post('${path}', bodyParserMiddleware(['json']), requireSignature('mcp', { policy: MCP_AUTH_POLICY }), async (request, reply) => {
4339
+ const dispatcher = request.container.get(McpDispatcher);
4340
+ const context = createMcpRequestContext({ requestId: request.requestId, logger: request.logger });
4341
+ if (dispatcher.sessionMode === 'stateful') {
4342
+ // Fastify's equivalent of Koa's \`ctx.respond = false\`: the dispatcher writes the raw
4343
+ // response itself, and the request scope is disposed on the raw socket close instead.
4344
+ reply.hijack();
4345
+ await dispatcher.dispatchStateful(
4346
+ {
4347
+ req: request.raw,
4348
+ res: reply.raw,
4349
+ body: request.parsedBody,
4350
+ // \`requestHeader\` returns '' for an absent header; the session id is optional.
4351
+ sessionId: requestHeader(request, 'mcp-session-id') || undefined,
4352
+ },
4353
+ context,
4354
+ );
4355
+ return;
4356
+ }
4357
+ const response = await dispatcher.dispatch(JSON.parse(String(request.rawBody)), context);
4358
+ if (response) return reply.send(response);
4359
+ reply.status(202); // a notification \u2014 nothing to return
4360
+ return reply.send();
4361
+ });
4362
+ }
4363
+ `;
4364
+ }
4365
+ };
4366
+
4218
4367
  // src/server-framework.ts
4219
4368
  var SERVER_FRAMEWORK_NAMES = [
4220
- "koa"
4369
+ "koa",
4370
+ "fastify"
4221
4371
  ];
4222
4372
  var DEFAULT_SERVER_FRAMEWORK_NAME = "koa";
4223
4373
  var SERVER_FRAMEWORKS = {
4224
- koa: KOA_SERVER_FRAMEWORK
4374
+ koa: KOA_SERVER_FRAMEWORK,
4375
+ fastify: FASTIFY_SERVER_FRAMEWORK
4225
4376
  };
4226
4377
  function resolveServerFramework(name) {
4227
4378
  const resolved = name ?? DEFAULT_SERVER_FRAMEWORK_NAME;
@@ -5809,6 +5960,7 @@ function stableSubConfig(config) {
5809
5960
  __name(stableSubConfig, "stableSubConfig");
5810
5961
  export {
5811
5962
  DEFAULT_SERVER_FRAMEWORK_NAME,
5963
+ FASTIFY_SERVER_FRAMEWORK,
5812
5964
  KOA_SERVER_FRAMEWORK,
5813
5965
  SERVER_FRAMEWORKS,
5814
5966
  SERVER_FRAMEWORK_NAMES,