typespec-hono 0.1.0 → 0.2.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.
@@ -1,25 +1,36 @@
1
1
  import type { Context, Env, Input, MiddlewareHandler } from "hono";
2
- import type { ResponseArm } from "typespec-http-zod/runtime";
2
+ import type { ZodType } from "zod";
3
3
  /**
4
- * ⚠️ **`ResponseArm` and `armFor` live in `typespec-http-zod` and are re-exported here.**
4
+ * One arm of an operation's declared response set, as the document publishes it.
5
5
  *
6
- * The library is what emits the response arms `schemas.gen.ts` declares them and annotates them
7
- * with `satisfies readonly ResponseArm[]` so the type belongs to the package that produces it, and
8
- * the rule for reading an array containing `4XX` and `default` belongs beside it. A consumer serving
9
- * those validators from Express needs both and should not depend on a Hono emitter to get them.
6
+ * Declared here rather than imported from `typespec-http-zod`, so that this module and the copy the
7
+ * emitter writes beside the generated code are both free of any package import at run time. The two
8
+ * declarations are structurally identical, which is all TypeScript requires, and
9
+ * `test/runtime-parity.test.ts` asserts that this `armFor` answers identically to the library's for
10
+ * every shape of arm list.
11
+ */
12
+ export interface ResponseArm {
13
+ readonly status: number | "default" | `${1 | 2 | 3 | 4 | 5}XX`;
14
+ readonly schema: ZodType | undefined;
15
+ readonly when?: {
16
+ readonly property: string;
17
+ readonly value: boolean | string;
18
+ };
19
+ }
20
+ /**
21
+ * The arm that applies to a status, preferring an exact code, then its `NXX` range, then `default`.
10
22
  *
11
- * Re-exported rather than merely available, so an application still has ONE runtime import and the
12
- * `runtime-module` substitution keeps working unchanged: a module an app points that option at has to
13
- * supply every name the generated files reference, and they reference these.
23
+ * That order is the document's own: OpenAPI resolves a response the same way, so an application
24
+ * choosing an arm by hand would have to re-derive this and could get it wrong differently.
14
25
  */
15
- export { armFor, type ResponseArm } from "typespec-http-zod/runtime";
26
+ export declare function armFor(arms: readonly ResponseArm[], status: number): ResponseArm | undefined;
16
27
  /**
17
28
  * One acceptable combination of credentials, exactly as OpenAPI's `security` states it: scheme id to
18
29
  * the scopes that scheme requires. Every entry in one requirement must be satisfied TOGETHER, and
19
30
  * satisfying ANY ONE requirement authorises the caller.
20
31
  *
21
- * ⚠️ **Declared here rather than beside the code that derives it**, because `./runtime` is what a
22
- * running server imports and must stay free of every build-time dependency a packaging arm asserts
32
+ * **Declared here rather than beside the code that derives it**, because `./runtime` is what a
33
+ * running server imports and must stay free of every build-time dependency, a packaging arm asserts
23
34
  * it names no `@typespec/*` package at all. An application should not drag a compiler into its
24
35
  * Worker to read one type.
25
36
  */
@@ -27,8 +38,8 @@ export type SecurityRequirement = Readonly<Record<string, readonly string[]>>;
27
38
  /**
28
39
  * The contract between the GENERATED server and the app that mounts it.
29
40
  *
30
- * ⚠️ **This exists because "here is a data table, write your own router" is not a deliverable.**
31
- * The emitter used to produce `GENERATED_ROUTES` one array of plain objects and every consumer
41
+ * **This exists because "here is a data table, write your own router" is not a deliverable.**
42
+ * The emitter used to produce `GENERATED_ROUTES` (one array of plain objects) and every consumer
32
43
  * had to hand-write a loop that interpreted it at run time. In this repository that loop is 220
33
44
  * lines, it sits outside every oracle the emitter is judged by, and it carries a cast that exists
34
45
  * *only* because iterating a homogeneous array throws away the per-operation types the emitter knew:
@@ -37,14 +48,14 @@ export type SecurityRequirement = Readonly<Record<string, readonly string[]>>;
37
48
  * assertion invented to put the guarantee back.
38
49
  *
39
50
  * What is left for the app to supply is genuinely app-specific: how a request becomes a caller's
40
- * context, and how a result becomes a response. Everything else routing, validation, which
41
- * validator applies to which target, what status each arm answers is generated.
51
+ * context, and how a result becomes a response. Everything else, routing, validation, which
52
+ * validator applies to which target, what status each arm answers, is generated.
42
53
  */
43
54
  /**
44
55
  * How an operation's return value is wrapped.
45
56
  *
46
57
  * Identity by default, so an operation may simply return its value. An app with a result envelope
47
- * points `runtime-module` at its own module and re-declares this as, say, `ServiceResult<T>` which
58
+ * points `runtime-module` at its own module and re-declares this as, say, `ServiceResult<T>`, which
48
59
  * is what keeps the generated `Operations` interface concretely typed end to end instead of falling
49
60
  * back to `unknown` and reintroducing the cast this whole change exists to delete.
50
61
  */
@@ -52,11 +63,11 @@ export type Result<T> = T;
52
63
  /**
53
64
  * The Hono environment the generated server mounts on, and the caller context its operations take.
54
65
  *
55
- * ⚠️ **Concrete on purpose.** Making `registerRoutes` generic over the environment does not work:
66
+ * **Concrete on purpose.** Making `registerRoutes` generic over the environment does not work:
56
67
  * Hono narrows `Context` per route and its conditional types cannot reduce
57
- * `IfAnyThenEmptyObject<E extends Env ? …>` while `E` is an unbound parameter, so nothing the app
58
- * supplies is ever assignable and every call site needs a cast. Naming the types here instead an
59
- * app points `runtime-module` at its own module and re-declares them keeps every generated call
68
+ * `IfAnyThenEmptyObject<E extends Env ? ...>` while `E` is an unbound parameter, so nothing the app
69
+ * supplies is ever assignable and every call site needs a cast. Naming the types here instead, an
70
+ * app points `runtime-module` at its own module and re-declares them, keeps every generated call
60
71
  * site concrete and cast-free. Identity defaults, so an app with neither can ignore both.
61
72
  */
62
73
  export type AppEnv = Env;
@@ -64,56 +75,101 @@ export type Ctx = unknown;
64
75
  /** Anything an operation may hand back: the value, or a promise of it. */
65
76
  export type Awaitable<T> = T | Promise<T>;
66
77
  /**
67
- * Pick the media type to serve, per RFC 9110 §12.5.1.
78
+ * Pick the media type to serve, per RFC 9110 section 12.5.1.
68
79
  *
69
- * ⚠️ **In the runtime rather than in {@link RouteDeps}, deliberately.** The test for admitting
80
+ * **In the runtime rather than in {@link RouteDeps}, deliberately.** The test for admitting
70
81
  * anything to `deps` is *the generated code cannot proceed without an answer*, and this is not that:
71
82
  * which media types an operation offers is a contract fact the emitter reads from the document, and
72
83
  * how `Accept` selects among them is specified by the RFC. Both sides are derivable, so an app that
73
- * had to supply this would be re-implementing the standard and could get it wrong differently from
84
+ * had to supply this would be re-implementing the standard, and could get it wrong differently from
74
85
  * everybody else.
75
86
  *
76
87
  * The rules that matter, and that a naive `includes()` gets wrong:
77
- * - **absent or empty `Accept` means anything is acceptable** serve the first offer;
88
+ * - **absent or empty `Accept` means anything is acceptable**, serve the first offer;
78
89
  * - **`q=0` is a REFUSAL**, not a weak preference, so a range scoring zero can never be chosen;
79
- * - **specificity breaks ties before quality does**: `text/plain` beats `text/*` beats `*​/*` at
80
- * equal `q`, which is why the score is a pair and not a number;
90
+ * - **specificity breaks ties before quality does**: an exact type beats a subtype wildcard
91
+ * (`text/*`), which beats the fully wildcard range, at equal `q`. That is why the score is a
92
+ * pair rather than a number. The fully wildcard range is not written literally here because it
93
+ * would close this comment;
81
94
  * - parameters after the media range (`;charset=utf-8`) are not part of the match.
82
95
  *
83
- * Returns `undefined` when nothing offered is acceptable the caller answers 406, and the
96
+ * Returns `undefined` when nothing offered is acceptable. The caller answers 406, and the
84
97
  * difference between "no preference" and "no acceptable option" is exactly what that turns on.
85
98
  */
86
99
  export declare function selectContentType(accept: string | undefined, offered: readonly string[]): string | undefined;
100
+ /**
101
+ * Let only a real HEAD request through.
102
+ *
103
+ * Hono rewrites HEAD to GET before matching, so a `@head` operation has to be registered under GET
104
+ * to be reachable at all. Where the document declares no GET on that path, this keeps the
105
+ * registration honest: a GET gets the 404 it would have got if the route had never been registered,
106
+ * and only a HEAD reaches the validators and the handler. `c.req.method` still reads `HEAD` after the
107
+ * rewrite, which is what makes the distinction possible.
108
+ *
109
+ * In the runtime rather than in {@link RouteDeps} on the usual test: the generated code can proceed
110
+ * without asking the app anything. Which verbs the document declares is a contract fact, and the
111
+ * answer for a verb it does not declare is the same 404 any unrouted request already gets, through
112
+ * whatever `app.notFound()` the application has set.
113
+ *
114
+ * A plain middleware rather than `except()` from `hono/combine`, because `except` wraps the final
115
+ * handler and erases its response type, and Hono's RPC client derives its whole surface from that
116
+ * type. Measured: `hc<typeof app>` resolved a wrapped route's body to `unknown`.
117
+ */
118
+ export declare const headOnly: MiddlewareHandler;
119
+ /**
120
+ * Apply the validator that parses the media type the request actually carries.
121
+ *
122
+ * A route may declare several request media types needing different parsers -- `addPet` in the
123
+ * Swagger Petstore accepts JSON, XML and urlencoded on one path. `zValidator`'s target is fixed when
124
+ * the server is generated; which parser applies is decided by the caller's `Content-Type` when the
125
+ * request arrives. Those are different times, and only the second one has the answer.
126
+ *
127
+ * **Before this, one target was chosen for the whole route and everything else was rejected.** A
128
+ * form-encoded body to a route declaring JSON first was handed to `c.req.json()` and answered 400,
129
+ * with no diagnostic anywhere. The status looked like the caller's fault and was not.
130
+ *
131
+ * Parameters after the media type (`; charset=utf-8`, `; boundary=...`) are not part of the match,
132
+ * which matters because a multipart request always carries a boundary.
133
+ *
134
+ * A `Content-Type` matching nothing declared falls through to the first validator, which reproduces
135
+ * the previous behaviour exactly for that case: the body fails to parse and `deps.invalid` answers.
136
+ * No status is invented here that the document does not describe.
137
+ */
138
+ export declare function byContentType<E extends Env>(validators: readonly (readonly [
139
+ mediaType: string,
140
+ target: string,
141
+ validator: MiddlewareHandler<E>
142
+ ])[]): MiddlewareHandler<E>;
87
143
  /**
88
144
  * What the app provides. One object, passed once, rather than a module the generated file imports by
89
- * path a generated server that hard-codes `../../backend.js` is only usable by the project it was
145
+ * path. A generated server that hard-codes `../../backend.js` is only usable by the project it was
90
146
  * generated in, and this one has to be usable by any.
91
147
  *
92
- * ⚠️ **The hooks are generic over Hono's path and input parameters, deliberately.** Hono narrows
93
- * `Context` per route by the literal path, and by whatever the validators on that route produced
148
+ * **The hooks are generic over Hono's path and input parameters, deliberately.** Hono narrows
149
+ * `Context` per route, by the literal path, and by whatever the validators on that route produced,
94
150
  * so a hook typed against a single `Context<E>` is not assignable at any real call site. Making the
95
151
  * hooks generic lets the app write functions that ignore both, without a cast anywhere.
96
152
  *
97
- * ⚠️ **`E` and `C` are PARAMETERS, and they have to be.** The defaults keep the bare `RouteDeps` the
153
+ * **`E` and `C` are PARAMETERS, and they have to be.** The defaults keep the bare `RouteDeps` the
98
154
  * generated server writes working for an app that substitutes nothing. An app that substitutes
99
- * anything binds them once `export type RouteDeps = BaseRouteDeps<AppEnv, Ctx>` in the module it
100
- * points `runtime-module` at and every hook is then typed against its own environment and its own
155
+ * anything binds them once, `export type RouteDeps = BaseRouteDeps<AppEnv, Ctx>` in the module it
156
+ * points `runtime-module` at, and every hook is then typed against its own environment and its own
101
157
  * caller context.
102
158
  *
103
159
  * Re-exporting this interface unparameterised instead does not work, and the reason is not obvious:
104
160
  * **Hono's `Context` is INVARIANT in its environment**, because `Context.set` takes `E` as an
105
- * argument. So `Context<AppEnv, …>` is not assignable to `Context<Env, …>` however plain the
161
+ * argument. So `Context<AppEnv, ...>` is not assignable to `Context<Env, ...>` however plain the
106
162
  * substituted environment is, and every generated `deps.*` call site fails. Separately, `context`
107
- * would keep returning the identity `Ctx` `unknown` which the app's own handlers then reject.
163
+ * would keep returning the identity `Ctx` (`unknown`) which the app's own handlers then reject.
108
164
  * Measured before this was parameterised: **19 errors on a four-operation service.**
109
165
  */
110
166
  export interface RouteDeps<E extends Env = AppEnv, C = Ctx> {
111
167
  /**
112
168
  * The gate the DOCUMENT publishes, as middleware.
113
169
  *
114
- * ⚠️ **Which scopes an operation demands is a contract fact; how a token is verified is not.**
115
- * `@useAuth(OAuth2Auth<…>)` reaches OpenAPI as `security` per operation, so the requirement is
116
- * generated and this implements the check the same split as `context` and `respond`. Emitted
170
+ * **Which scopes an operation demands is a contract fact; how a token is verified is not.**
171
+ * `@useAuth(OAuth2Auth<...>)` reaches OpenAPI as `security` per operation, so the requirement is
172
+ * generated and this implements the check. The same split as `context` and `respond`. Emitted
117
173
  * only where the operation declares scopes, which is why an internal surface with none is
118
174
  * unaffected.
119
175
  *
@@ -121,25 +177,25 @@ export interface RouteDeps<E extends Env = AppEnv, C = Ctx> {
121
177
  * to scopes while the document published eleven, so a surface mounted with its gate silently
122
178
  * dropped.
123
179
  *
124
- * ⚠️ **It receives the document's REQUIREMENTS, not a flat list of scopes, and that is the second
125
- * half of the same defect.** `@useAuth(BearerAuth)` publishes `security: [{ "BearerAuth": [] }]`
126
- * no scopes so a scopes-only gate was emitted for OAuth2 and for nothing else. Bearer, api-key
180
+ * **It receives the document's REQUIREMENTS, not a flat list of scopes, and that is the second
181
+ * half of the same defect.** `@useAuth(BearerAuth)` publishes `security: [{ "BearerAuth": [] }]`
182
+ * with no scopes, so a scopes-only gate was emitted for OAuth2 and for nothing else. Bearer, api-key
127
183
  * and basic, which is most services, carried no gate at all and rested entirely on `context`
128
184
  * returning null. An app whose `context` read a cookie would serve a route the document says needs
129
185
  * a bearer token.
130
186
  *
131
187
  * Satisfying ANY ONE requirement authorises the caller, and every scheme WITHIN a requirement must
132
- * be satisfied together which is exactly what an array of OpenAPI `security` objects means.
188
+ * be satisfied together, which is exactly what an array of OpenAPI `security` objects means.
133
189
  */
134
190
  readonly authorize: (requirements: readonly SecurityRequirement[]) => MiddlewareHandler<E>;
135
191
  /**
136
192
  * The caller's context, or `null` when there is none to establish.
137
193
  *
138
194
  * `authentication` is what the DOCUMENT says, and only that: `"none"` where the operation
139
- * declares `@useAuth(NoAuth)` `security: []` in OpenAPI and `"required"` otherwise. Deciding
195
+ * declares `@useAuth(NoAuth)` (`security: []` in OpenAPI) and `"required"` otherwise. Deciding
140
196
  * it at generation time is the point: the gate the document publishes is the gate that runs.
141
197
  *
142
- * ⚠️ **It used to be `"none" | "account" | "resource"`, and the last two were an invention.** They
198
+ * **It used to be `"none" | "account" | "resource"`, and the last two were an invention.** They
143
199
  * were chosen by whether the path had parameters, which no OpenAPI keyword expresses and which
144
200
  * merely happened to fit the first consumer. A generated server enforcing a rule derived from
145
201
  * nothing published is the defect class this emitter exists to remove, so it is gone. An app that
@@ -149,7 +205,7 @@ export interface RouteDeps<E extends Env = AppEnv, C = Ctx> {
149
205
  /** The response when `context` returns `null`. */
150
206
  readonly noContext: <P extends string, I extends Input>(c: Context<E, P, I>) => Response;
151
207
  /**
152
- * The response when the caller's `Accept` matches nothing the operation offers a 406.
208
+ * The response when the caller's `Accept` matches nothing the operation offers, a 406.
153
209
  *
154
210
  * Emitted only on routes where the document declares more than one media type for a status, so
155
211
  * a service without content negotiation never sees it. Same shape of hook as {@link noContext}
@@ -1,34 +1,34 @@
1
1
  /**
2
- * ⚠️ **`ResponseArm` and `armFor` live in `typespec-http-zod` and are re-exported here.**
2
+ * The arm that applies to a status, preferring an exact code, then its `NXX` range, then `default`.
3
3
  *
4
- * The library is what emits the response arms `schemas.gen.ts` declares them and annotates them
5
- * with `satisfies readonly ResponseArm[]` so the type belongs to the package that produces it, and
6
- * the rule for reading an array containing `4XX` and `default` belongs beside it. A consumer serving
7
- * those validators from Express needs both and should not depend on a Hono emitter to get them.
8
- *
9
- * Re-exported rather than merely available, so an application still has ONE runtime import and the
10
- * `runtime-module` substitution keeps working unchanged: a module an app points that option at has to
11
- * supply every name the generated files reference, and they reference these.
4
+ * That order is the document's own: OpenAPI resolves a response the same way, so an application
5
+ * choosing an arm by hand would have to re-derive this and could get it wrong differently.
12
6
  */
13
- export { armFor } from "typespec-http-zod/runtime";
7
+ export function armFor(arms, status) {
8
+ return (arms.find((arm) => arm.status === status) ??
9
+ arms.find((arm) => arm.status === `${Math.floor(status / 100)}XX`) ??
10
+ arms.find((arm) => arm.status === "default"));
11
+ }
14
12
  /**
15
- * Pick the media type to serve, per RFC 9110 §12.5.1.
13
+ * Pick the media type to serve, per RFC 9110 section 12.5.1.
16
14
  *
17
- * ⚠️ **In the runtime rather than in {@link RouteDeps}, deliberately.** The test for admitting
15
+ * **In the runtime rather than in {@link RouteDeps}, deliberately.** The test for admitting
18
16
  * anything to `deps` is *the generated code cannot proceed without an answer*, and this is not that:
19
17
  * which media types an operation offers is a contract fact the emitter reads from the document, and
20
18
  * how `Accept` selects among them is specified by the RFC. Both sides are derivable, so an app that
21
- * had to supply this would be re-implementing the standard and could get it wrong differently from
19
+ * had to supply this would be re-implementing the standard, and could get it wrong differently from
22
20
  * everybody else.
23
21
  *
24
22
  * The rules that matter, and that a naive `includes()` gets wrong:
25
- * - **absent or empty `Accept` means anything is acceptable** serve the first offer;
23
+ * - **absent or empty `Accept` means anything is acceptable**, serve the first offer;
26
24
  * - **`q=0` is a REFUSAL**, not a weak preference, so a range scoring zero can never be chosen;
27
- * - **specificity breaks ties before quality does**: `text/plain` beats `text/*` beats `*​/*` at
28
- * equal `q`, which is why the score is a pair and not a number;
25
+ * - **specificity breaks ties before quality does**: an exact type beats a subtype wildcard
26
+ * (`text/*`), which beats the fully wildcard range, at equal `q`. That is why the score is a
27
+ * pair rather than a number. The fully wildcard range is not written literally here because it
28
+ * would close this comment;
29
29
  * - parameters after the media range (`;charset=utf-8`) are not part of the match.
30
30
  *
31
- * Returns `undefined` when nothing offered is acceptable the caller answers 406, and the
31
+ * Returns `undefined` when nothing offered is acceptable. The caller answers 406, and the
32
32
  * difference between "no preference" and "no acceptable option" is exactly what that turns on.
33
33
  */
34
34
  export function selectContentType(accept, offered) {
@@ -61,3 +61,49 @@ export function selectContentType(accept, offered) {
61
61
  }
62
62
  return best?.type;
63
63
  }
64
+ /**
65
+ * Let only a real HEAD request through.
66
+ *
67
+ * Hono rewrites HEAD to GET before matching, so a `@head` operation has to be registered under GET
68
+ * to be reachable at all. Where the document declares no GET on that path, this keeps the
69
+ * registration honest: a GET gets the 404 it would have got if the route had never been registered,
70
+ * and only a HEAD reaches the validators and the handler. `c.req.method` still reads `HEAD` after the
71
+ * rewrite, which is what makes the distinction possible.
72
+ *
73
+ * In the runtime rather than in {@link RouteDeps} on the usual test: the generated code can proceed
74
+ * without asking the app anything. Which verbs the document declares is a contract fact, and the
75
+ * answer for a verb it does not declare is the same 404 any unrouted request already gets, through
76
+ * whatever `app.notFound()` the application has set.
77
+ *
78
+ * A plain middleware rather than `except()` from `hono/combine`, because `except` wraps the final
79
+ * handler and erases its response type, and Hono's RPC client derives its whole surface from that
80
+ * type. Measured: `hc<typeof app>` resolved a wrapped route's body to `unknown`.
81
+ */
82
+ export const headOnly = async (c, next) => c.req.method === "HEAD" ? next() : c.notFound();
83
+ /**
84
+ * Apply the validator that parses the media type the request actually carries.
85
+ *
86
+ * A route may declare several request media types needing different parsers -- `addPet` in the
87
+ * Swagger Petstore accepts JSON, XML and urlencoded on one path. `zValidator`'s target is fixed when
88
+ * the server is generated; which parser applies is decided by the caller's `Content-Type` when the
89
+ * request arrives. Those are different times, and only the second one has the answer.
90
+ *
91
+ * **Before this, one target was chosen for the whole route and everything else was rejected.** A
92
+ * form-encoded body to a route declaring JSON first was handed to `c.req.json()` and answered 400,
93
+ * with no diagnostic anywhere. The status looked like the caller's fault and was not.
94
+ *
95
+ * Parameters after the media type (`; charset=utf-8`, `; boundary=...`) are not part of the match,
96
+ * which matters because a multipart request always carries a boundary.
97
+ *
98
+ * A `Content-Type` matching nothing declared falls through to the first validator, which reproduces
99
+ * the previous behaviour exactly for that case: the body fails to parse and `deps.invalid` answers.
100
+ * No status is invented here that the document does not describe.
101
+ */
102
+ export function byContentType(validators) {
103
+ return async (c, next) => {
104
+ const declared = (c.req.header("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
105
+ const matched = validators.find(([mediaType]) => mediaType.toLowerCase() === declared);
106
+ const [, , validator] = matched ?? validators[0];
107
+ return validator(c, next);
108
+ };
109
+ }
@@ -4,21 +4,21 @@ import type { SecurityRequirement } from "./runtime.js";
4
4
  /**
5
5
  * What the DOCUMENT says a caller must satisfy, in the shape the document says it.
6
6
  *
7
- * ⚠️ **The scheme was being thrown away, and only "is a caller needed" survived.** `@useAuth(BearerAuth)`
7
+ * **The scheme was being thrown away, and only "is a caller needed" survived.** `@useAuth(BearerAuth)`
8
8
  * reaches OpenAPI as `security: [{ "BearerAuth": [] }]`, and this emitter reduced that to
9
- * `deps.context(c, "required")`. A gate was emitted ONLY when the scheme carried scopes so for
9
+ * `deps.context(c, "required")`. A gate was emitted ONLY when the scheme carried scopes, so for
10
10
  * bearer, api-key and basic, which is the common case, nothing carried which scheme at all. An
11
11
  * application whose `context` read a cookie would happily serve a route the document says needs a
12
12
  * bearer token, and nothing anywhere would notice.
13
13
  *
14
- * ⚠️ **Passed through, never enforced here.** Which credentials satisfy a scheme is the application's
14
+ * **Passed through, never enforced here.** Which credentials satisfy a scheme is the application's
15
15
  * business and could not be anything else; which schemes an operation ACCEPTS is a contract fact and
16
16
  * is now generated. That is the same split as `context` and `respond`, applied to the half that was
17
17
  * missing.
18
18
  */
19
19
  export type { SecurityRequirement } from "./runtime.js";
20
20
  /**
21
- * The requirements an operation declares. Satisfying **any one** of them authorises the caller
21
+ * The requirements an operation declares. Satisfying **any one** of them authorises the caller,
22
22
  * which is what an array of `security` objects means in OpenAPI, and why this is a list of lists
23
23
  * rather than a flat set of scopes.
24
24
  *
@@ -1,6 +1,6 @@
1
1
  import { getAuthenticationForOperation } from "@typespec/http";
2
2
  /**
3
- * The requirements an operation declares. Satisfying **any one** of them authorises the caller
3
+ * The requirements an operation declares. Satisfying **any one** of them authorises the caller,
4
4
  * which is what an array of `security` objects means in OpenAPI, and why this is a list of lists
5
5
  * rather than a flat set of scopes.
6
6
  *
@@ -14,7 +14,7 @@ export function securityFor(program, operation) {
14
14
  let anonymous = false;
15
15
  for (const scheme of option.schemes) {
16
16
  /**
17
- * ⚠️ **`NoAuth` inside an option means that option needs nothing**, which is how a spec says
17
+ * **`NoAuth` inside an option means that option needs nothing**, which is how a spec says
18
18
  * "authentication is optional here". It is not a scheme to demand, and emitting it as one
19
19
  * would refuse every anonymous caller the document permits.
20
20
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * What TypeSpec's compiler loads for this library.
3
3
  *
4
- * ⚠️ **There is deliberately no `$decorators` export**, here or in `typespec-http-zod`. Four once
4
+ * **There is deliberately no `$decorators` export**, here or in `typespec-http-zod`. Four once
5
5
  * existed and every one let a spec state something `@typespec/openapi3` could not publish, so the
6
6
  * emitted validator enforced a rule no caller reading the contract could see. A decorator is not a
7
7
  * convenience; it is a second contract.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * What TypeSpec's compiler loads for this library.
3
3
  *
4
- * ⚠️ **There is deliberately no `$decorators` export**, here or in `typespec-http-zod`. Four once
4
+ * **There is deliberately no `$decorators` export**, here or in `typespec-http-zod`. Four once
5
5
  * existed and every one let a spec state something `@typespec/openapi3` could not publish, so the
6
6
  * emitted validator enforced a rule no caller reading the contract could see. A decorator is not a
7
7
  * convenience; it is a second contract.
package/lib/main.tsp CHANGED
@@ -1,3 +1,3 @@
1
- // The emitter library itself one diagnostic and its options schema. There are no decorators; see
1
+ // The emitter library itself. One diagnostic and its options schema. There are no decorators; see
2
2
  // `src/tsp-index.ts` for why, and `typespec-http-zod` for the rest of the options this accepts.
3
3
  import "../dist/src/tsp-index.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typespec-hono",
3
- "version": "0.1.0",
4
- "description": "TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.",
3
+ "version": "0.2.0",
4
+ "description": "TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition, agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
7
7
  "codegen",
@@ -21,9 +21,10 @@
21
21
  "url": "git+https://github.com/bison-digital/typespec-hono.git"
22
22
  },
23
23
  "files": [
24
- "lib/*.tsp",
24
+ "!dist/test/**",
25
25
  "dist/**",
26
- "!dist/test/**"
26
+ "lib/*.tsp",
27
+ "src/runtime.ts"
27
28
  ],
28
29
  "type": "module",
29
30
  "main": "dist/src/index.js",
@@ -43,7 +44,7 @@
43
44
  "provenance": true
44
45
  },
45
46
  "dependencies": {
46
- "typespec-http-zod": "^0.1.0"
47
+ "typespec-http-zod": "^0.2.0"
47
48
  },
48
49
  "devDependencies": {
49
50
  "@hono/zod-openapi": "^1.4.0",