@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.
- package/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +21 -19
- package/CHANGELOG.md +34 -0
- package/README.md +2 -2
- package/dist/codegen-operation.d.ts +1 -1
- package/dist/codegen-operation.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -8
- package/dist/index.js.map +1 -1
- package/dist/server-framework-fastify.d.ts +8 -0
- package/dist/server-framework-fastify.d.ts.map +1 -0
- package/dist/server-framework-koa.d.ts.map +1 -1
- package/dist/server-framework.d.ts +8 -1
- package/dist/server-framework.d.ts.map +1 -1
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/src/codegen-operation.ts +49 -5
- package/src/index.ts +1 -0
- package/src/server-framework-fastify.ts +128 -0
- package/src/server-framework-koa.ts +2 -0
- package/src/server-framework.ts +11 -1
- package/tests/codegen-operation-framework.test.ts +44 -0
- package/tests/codegen-operation.test.ts +45 -0
- package/tests/server-framework-fastify.test.ts +148 -0
- package/tests/server-framework.test.ts +1 -0
|
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
|
|
|
5
5
|
[34mCLI[39m tsup v8.5.1
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
9
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m230.12 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m542.08 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 359ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
13
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 3932ms
|
|
13
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m11.34 KB[39m
|
|
@@ -3,39 +3,41 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/packages/plugin-typescript[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/codegen-operation.test.ts [2m([22m[
|
|
7
|
-
[32m✓[39m tests/codegen-contract.test.ts [2m([22m[2m147 tests[22m[2m)[22m[32m
|
|
8
|
-
[32m✓[39m tests/codegen-sdk.test.ts [2m([22m[2m173 tests[22m[2m)[22m[32m
|
|
9
|
-
[32m✓[39m tests/codegen-plain-types.test.ts [2m([22m[2m67 tests[22m[2m)[22m[32m
|
|
10
|
-
[32m✓[39m tests/codegen-server.test.ts [2m([22m[2m39 tests[22m[2m)[22m[32m
|
|
11
|
-
[32m✓[39m tests/pipeline.test.ts [2m([22m[2m34 tests[22m[2m)[22m[33m
|
|
12
|
-
[32m✓[39m tests/codegen-mcp.test.ts [2m([22m[2m33 tests[22m[2m)[22m[32m
|
|
13
|
-
[32m✓[39m tests/
|
|
14
|
-
[32m✓[39m tests/
|
|
6
|
+
[32m✓[39m tests/codegen-operation.test.ts [2m([22m[2m166 tests[22m[2m)[22m[32m 190[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/codegen-contract.test.ts [2m([22m[2m147 tests[22m[2m)[22m[32m 182[2mms[22m[39m
|
|
8
|
+
[32m✓[39m tests/codegen-sdk.test.ts [2m([22m[2m173 tests[22m[2m)[22m[32m 228[2mms[22m[39m
|
|
9
|
+
[32m✓[39m tests/codegen-plain-types.test.ts [2m([22m[2m67 tests[22m[2m)[22m[32m 39[2mms[22m[39m
|
|
10
|
+
[32m✓[39m tests/codegen-server.test.ts [2m([22m[2m39 tests[22m[2m)[22m[32m 129[2mms[22m[39m
|
|
11
|
+
[32m✓[39m tests/pipeline.test.ts [2m([22m[2m34 tests[22m[2m)[22m[33m 542[2mms[22m[39m
|
|
12
|
+
[32m✓[39m tests/codegen-mcp.test.ts [2m([22m[2m33 tests[22m[2m)[22m[32m 52[2mms[22m[39m
|
|
13
|
+
[32m✓[39m tests/server-framework-fastify.test.ts [2m([22m[2m25 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
|
14
|
+
[32m✓[39m tests/codegen-operation-framework.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
15
|
+
[32m✓[39m tests/server-framework-koa.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
15
16
|
[32m✓[39m tests/ts-render.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
16
|
-
[32m✓[39m tests/
|
|
17
|
-
[32m✓[39m tests/
|
|
17
|
+
[32m✓[39m tests/server-framework.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
18
|
+
[32m✓[39m tests/escaping-security.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 20[2mms[22m[39m
|
|
18
19
|
|
|
19
|
-
[2m Test Files [22m [1m[
|
|
20
|
-
[2m Tests [22m [1m[
|
|
21
|
-
[2m Start at [22m
|
|
22
|
-
[2m Duration [22m
|
|
20
|
+
[2m Test Files [22m [1m[32m13 passed[39m[22m[90m (13)[39m
|
|
21
|
+
[2m Tests [22m [1m[32m744 passed[39m[22m[90m (744)[39m
|
|
22
|
+
[2m Start at [22m 19:51:10
|
|
23
|
+
[2m Duration [22m 9.04s[2m (transform 3.75s, setup 0ms, import 16.64s, tests 1.45s, environment 3ms)[22m
|
|
23
24
|
|
|
24
25
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
25
26
|
-------------------|---------|----------|---------|---------|-------------------
|
|
26
27
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
27
28
|
-------------------|---------|----------|---------|---------|-------------------
|
|
28
|
-
All files | 84.
|
|
29
|
-
src | 84.
|
|
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.
|
|
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 | ...
|
|
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
|
|
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` (`
|
|
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;
|
|
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. */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,iBAAiB,EASpB,MAAM,mBAAmB,CAAC;AAiC3B,OAAO,EAAkD,KAAK,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjH,OAAO,EACH,sBAAsB,EACtB,6BAA6B,EAC7B,sBAAsB,EACtB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;
|
|
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
|
-
|
|
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) =>
|
|
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,
|