@sdk-it/hono 0.8.2 → 0.10.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @sdk-it/hono
2
2
 
3
- Hono framework integration for SDK-IT that provides type-safe request validation and standardized response handling.
3
+ Hono framework integration for SDK-IT that provides type-safe request validation and semantic response handling.
4
4
 
5
5
  To learn more about SDK code generation, see the [TypeScript Doc](../typescript/readme.md)
6
6
 
@@ -22,7 +22,7 @@ The validator middleware offers type-safe request validation using [Zod](https:/
22
22
  > For openapi generation to work correctly, you must use the `validate` middleware for each route.
23
23
 
24
24
  ```typescript
25
- import { validate } from '@sdk-it/hono';
25
+ import { validate } from '@sdk-it/hono/runtime';
26
26
 
27
27
  app.post(
28
28
  '/books',
@@ -82,36 +82,40 @@ app.post(
82
82
 
83
83
  ### Response Helper
84
84
 
85
- The output function provides a clean API for sending HTTP responses with proper status codes and content types. It automatically handles JSON serialization and content type headers.
85
+ The output function provides a clean API for sending HTTP responses with proper status codes and content types.
86
+
87
+ The `output` utility builds on hono's `context.body`.
86
88
 
87
89
  > ![NOTE]
88
90
  > You don't necessarily need to use this function for OpenAPI generation, but it provides a clean and consistent way to send responses.
89
91
 
90
92
  ```typescript
91
- import { createOutput } from '@sdk-it/hono';
93
+ import { createOutput } from '@sdk-it/hono/runtime';
92
94
 
93
- const output = createOutput(() => c);
95
+ app.post('/users', (c) => {
96
+ const output = createOutput(() => c);
94
97
 
95
- // Success responses
96
- output.ok({ data: 'success' });
97
- output.accepted({ status: 'processing' });
98
+ // Success responses
99
+ return output.ok({ data: 'success' });
100
+ return output.accepted({ status: 'processing' });
98
101
 
99
- // Error responses
100
- output.badRequest({ error: 'Invalid input' });
101
- output.unauthorized({ error: 'Not authenticated' });
102
- output.forbidden({ error: 'Not authorized' });
103
- output.notImplemented({ error: 'Coming soon' });
102
+ // Error responses
103
+ return output.badRequest({ error: 'Invalid input' });
104
+ return output.unauthorized({ error: 'Not authenticated' });
105
+ return output.forbidden({ error: 'Not authorized' });
106
+ return output.notImplemented({ error: 'Coming soon' });
104
107
 
105
- // Redirects
106
- output.redirect('/new-location');
108
+ // Redirects
109
+ return output.redirect('/new-location');
107
110
 
108
- // Custom headers
109
- output.ok({ data: 'success' }, { 'Cache-Control': 'max-age=3600' });
111
+ // Custom headers
112
+ return output.ok({ data: 'success' }, { 'Cache-Control': 'max-age=3600' });
113
+ });
110
114
  ```
111
115
 
112
116
  ## OpenAPI Generation
113
117
 
114
- SDK-IT relies on the aforementioned primitives and JSDoc tags to correctly infer each route specification.
118
+ SDK-IT relies on the `validator` middleware and JSDoc to correctly infer each route specification.
115
119
 
116
120
  Consider the following example:
117
121
 
@@ -120,7 +124,7 @@ Consider the following example:
120
124
  ```typescript
121
125
  import z from 'zod';
122
126
 
123
- import { validate } from '@sdk-it/hono';
127
+ import { validate } from '@sdk-it/hono/runtime';
124
128
 
125
129
  const app = new Hono();
126
130
 
@@ -143,8 +147,13 @@ app.get(
143
147
  );
144
148
  ```
145
149
 
150
+ > ![TIP]
151
+ > Instead of using `createOutput` fn, you can use [context-storage](https://hono.dev/docs/middleware/builtin/context-storage) middleware and then import the global `output` object from `@sdk-it/generic`.
152
+
146
153
  - Use the generate fn to create an OpenAPI spec from your routes.
147
154
 
155
+ <small>filename: openapi.ts</small>
156
+
148
157
  ```typescript
149
158
  import { join } from 'node:path';
150
159
 
@@ -178,6 +187,19 @@ await generate(spec, {
178
187
  });
179
188
  ```
180
189
 
190
+ - Run the script
191
+
192
+ ```bash
193
+ # using recent versions of node
194
+ node --experimental-strip-types ./openapi.ts
195
+
196
+ # using node < 22
197
+ npx tsx ./openapi.ts
198
+
199
+ # using bun
200
+ node --experimental-strip-types ./openapi.ts
201
+ ```
202
+
181
203
  > [!TIP]
182
204
  > See [typescript](../typescript/README.md) for more info.
183
205
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,2 @@
1
1
  export * from './lib/response-analyzer.ts';
2
- export * from './lib/runtime/output.ts';
3
- export * from './lib/runtime/validator.ts';
4
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // packages/hono/src/lib/response-analyzer.ts
2
- import debug from "debug";
3
2
  import ts from "typescript";
4
- var logger = debug("@sdk-it/hono");
3
+ import {
4
+ $types
5
+ } from "@sdk-it/core";
5
6
  var handlerVisitor = (callback, contextVarName) => {
6
7
  return (node) => {
7
8
  if (ts.isReturnStatement(node) && node.expression) {
@@ -46,182 +47,62 @@ function resolveStatusCode(node) {
46
47
  }
47
48
  throw new Error(`Could not resolve status code`);
48
49
  }
49
- function responseAnalyzer(handler, deriver) {
50
+ function defaultResponseAnalyzer(handler, deriver) {
50
51
  return toResponses(handler, deriver);
51
52
  }
52
-
53
- // packages/hono/src/lib/runtime/output.ts
54
- import { getContext } from "hono/context-storage";
55
- function send(context, value, status, headers) {
56
- if (value === void 0 || value === null) {
57
- return context.body(null, status, headers);
58
- }
59
- const responseHeaders = { ...headers };
60
- responseHeaders["Content-Type"] ??= "application/json";
61
- if (responseHeaders["Content-Type"].includes("application/json")) {
62
- return context.body(JSON.stringify(value), status, responseHeaders);
63
- }
64
- return context.body(value, status, responseHeaders);
65
- }
66
- function createOutput(contextFn) {
67
- return {
68
- nocontent() {
69
- const context = contextFn();
70
- return context.body(null, 204, {});
71
- },
72
- ok(value, headers) {
73
- return send(contextFn(), value, 200, headers);
74
- },
75
- created(valueOrUri, value, headers) {
76
- if (typeof valueOrUri === "string") {
77
- return send(contextFn(), value, 201, {
78
- Location: valueOrUri,
79
- ...headers || {}
80
- });
81
- } else {
82
- return send(contextFn(), valueOrUri, 201, headers);
53
+ function streamText(handler, deriver) {
54
+ return [
55
+ {
56
+ contentType: "text/plain",
57
+ headers: [{ "Transfer-Encoding": ["chunked"] }],
58
+ statusCode: "200",
59
+ response: {
60
+ optional: false,
61
+ kind: "primitive",
62
+ [$types]: ["string"]
83
63
  }
84
- },
85
- redirect(uri, statusCode) {
86
- const context = contextFn();
87
- return context.redirect(
88
- uri.toString(),
89
- statusCode ?? void 0
90
- );
91
- },
92
- attachment(buffer, filename, mimeType) {
93
- const context = contextFn();
94
- return context.body(buffer, 200, {
95
- "Content-Type": mimeType,
96
- "Content-Disposition": `attachment; filename="${filename}"`,
97
- "Content-Length": buffer.length.toString()
98
- });
99
- },
100
- badRequest(value, headers) {
101
- return send(contextFn(), value, 400, headers);
102
- },
103
- unauthorized(value, headers) {
104
- return send(contextFn(), value, 401, headers);
105
- },
106
- forbidden(value, headers) {
107
- return send(contextFn(), value, 403, headers);
108
- },
109
- notFound(value, headers) {
110
- return send(contextFn(), value, 404, headers);
111
- },
112
- notImplemented(value, headers) {
113
- return send(contextFn(), value, 501, headers);
114
- },
115
- accepted(value, headers) {
116
- return send(contextFn(), value, 202, headers);
117
- },
118
- conflict(value, headers) {
119
- return send(contextFn(), value, 409, headers);
120
- },
121
- unprocessableEntity(value, headers) {
122
- return send(contextFn(), value, 422, headers);
123
- },
124
- internalServerError(value, headers) {
125
- return send(contextFn(), value, 500, headers);
126
- },
127
- serviceUnavailable(value, headers) {
128
- return send(contextFn(), value, 503, headers);
129
64
  }
130
- };
65
+ ];
131
66
  }
132
- var output = createOutput(() => getContext());
133
-
134
- // packages/hono/src/lib/runtime/validator.ts
135
- import { createMiddleware } from "hono/factory";
136
- import { HTTPException } from "hono/http-exception";
137
- import z from "zod";
138
- var validate = (selector) => {
139
- return createMiddleware(async (c, next) => {
140
- const contentType = c.req.header("content-type") ?? "";
141
- let body = null;
142
- switch (contentType) {
143
- case "application/json":
144
- body = await c.req.json();
145
- break;
146
- case "application/x-www-form-urlencoded":
147
- body = await c.req.parseBody();
148
- break;
149
- default:
150
- body = {};
67
+ function stream(handler, deriver) {
68
+ return [
69
+ {
70
+ contentType: "application/octet-stream",
71
+ headers: [],
72
+ statusCode: "200",
73
+ response: {
74
+ optional: false,
75
+ kind: "primitive",
76
+ [$types]: ["string"]
77
+ }
151
78
  }
152
- const payload = {
153
- body,
154
- query: c.req.query(),
155
- queries: c.req.queries(),
156
- params: c.req.param(),
157
- headers: Object.fromEntries(
158
- Object.entries(c.req.header()).map(([k, v]) => [k, v ?? ""])
159
- )
160
- };
161
- const config = selector(payload);
162
- const schema = z.object(
163
- Object.entries(config).reduce(
164
- (acc, [key, value]) => {
165
- acc[key] = value.against;
166
- return acc;
167
- },
168
- {}
169
- )
170
- );
171
- const input = Object.entries(config).reduce(
172
- (acc, [key, value]) => {
173
- acc[key] = value.select;
174
- return acc;
175
- },
176
- {}
177
- );
178
- const parsed = parse(schema, input);
179
- c.set("input", parsed);
180
- await next();
181
- });
182
- };
183
- function parse(schema, input) {
184
- const result = schema.safeParse(input);
185
- if (!result.success) {
186
- const error = new HTTPException(400, {
187
- message: "Validation failed",
188
- cause: {
189
- code: "api/validation-failed",
190
- details: result.error.flatten((issue) => ({
191
- message: issue.message,
192
- code: issue.code,
193
- fatel: issue.fatal,
194
- path: issue.path.join(".")
195
- })).fieldErrors
79
+ ];
80
+ }
81
+ var httpException = (handler, deriver, node) => {
82
+ if (ts.isNewExpression(node)) {
83
+ const [status, options] = node.arguments ?? [];
84
+ return [
85
+ {
86
+ contentType: "application/json",
87
+ headers: [],
88
+ statusCode: resolveStatusCode(status),
89
+ response: options ? deriver.serializeNode(options) : void 0
196
90
  }
197
- });
198
- throw error;
91
+ ];
199
92
  }
200
- return result.data;
201
- }
202
- var openapi = validate;
203
- var consume = (contentType) => {
204
- return createMiddleware(async (context, next) => {
205
- const clientContentType = context.req.header("Content-Type");
206
- if (clientContentType !== contentType) {
207
- throw new HTTPException(415, {
208
- message: "Unsupported Media Type",
209
- cause: {
210
- code: "api/unsupported-media-type",
211
- details: `Expected content type: ${contentType}, but got: ${clientContentType}`
212
- }
213
- });
214
- }
215
- await next();
216
- });
93
+ return [];
94
+ };
95
+ var responseAnalyzer = {
96
+ default: defaultResponseAnalyzer,
97
+ streamText,
98
+ stream,
99
+ "throw.new.HTTPException": httpException
217
100
  };
218
101
  export {
219
- consume,
220
- createOutput,
221
- openapi,
222
- output,
223
- parse,
102
+ defaultResponseAnalyzer,
103
+ httpException,
224
104
  responseAnalyzer,
225
- validate
105
+ stream,
106
+ streamText
226
107
  };
227
108
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/lib/response-analyzer.ts", "../src/lib/runtime/output.ts", "../src/lib/runtime/validator.ts"],
4
- "sourcesContent": ["import debug from 'debug';\nimport ts from 'typescript';\n\nimport type { ResponseItem, TypeDeriver } from '@sdk-it/core';\n\nconst logger = debug('@sdk-it/hono');\n\nconst handlerVisitor: (\n on: (\n node: ts.Node | undefined,\n statusCode: ts.Node | undefined,\n headers: ts.Node | undefined,\n contentType: string,\n ) => void,\n contextVarName: string,\n) => ts.Visitor = (callback, contextVarName) => {\n return (node: ts.Node) => {\n if (ts.isReturnStatement(node) && node.expression) {\n if (ts.isCallExpression(node.expression)) {\n if (ts.isPropertyAccessExpression(node.expression.expression)) {\n const propAccess = node.expression.expression;\n if (\n ts.isIdentifier(propAccess.expression) &&\n propAccess.expression.text === contextVarName\n ) {\n const [body, statusCode, headers] = node.expression.arguments;\n let contentType = 'application/json';\n const callerMethod = propAccess.name.text;\n if (callerMethod === 'body') {\n contentType = 'application/octet-stream';\n }\n if (!body) {\n contentType = 'empty';\n }\n callback(body, statusCode, headers, contentType);\n }\n }\n // if (ts.isIdentifier(node.expression.expression)) {\n // console.log('streamText');\n // if (node.expression.expression.text === 'streamText') {\n // callback(undefined, undefined, undefined, 'text/plain');\n // }\n // }\n }\n }\n return ts.forEachChild(node, handlerVisitor(callback, contextVarName));\n };\n};\n\nfunction toResponses(handler: ts.ArrowFunction, deriver: TypeDeriver) {\n const contextVarName = handler.parameters[0].name.getText();\n const responsesList: ResponseItem[] = [];\n const visit = handlerVisitor((node, statusCode, headers, contentType) => {\n responsesList.push({\n headers: headers ? Object.keys(deriver.serializeNode(headers)) : [],\n contentType,\n statusCode: statusCode ? resolveStatusCode(statusCode) : '200',\n response: node ? deriver.serializeNode(node) : undefined,\n });\n }, contextVarName);\n visit(handler.body);\n return responsesList;\n}\n\nfunction resolveStatusCode(node: ts.Node) {\n if (ts.isNumericLiteral(node)) {\n return node.text;\n }\n throw new Error(`Could not resolve status code`);\n}\n\nexport function responseAnalyzer(\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n) {\n return toResponses(handler, deriver);\n}\n", "import type { Context } from 'hono';\nimport { getContext } from 'hono/context-storage';\nimport type {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from 'hono/utils/http-status';\n\ntype Data = any;\nfunction send(\n context: Context,\n value: Data | undefined | null,\n status: ContentfulStatusCode,\n headers?: Readonly<Record<string, string>>,\n) {\n if (value === undefined || value === null) {\n return context.body(null, status, headers);\n }\n const responseHeaders = { ...headers };\n responseHeaders['Content-Type'] ??= 'application/json';\n if (responseHeaders['Content-Type'].includes('application/json')) {\n return context.body(JSON.stringify(value), status, responseHeaders);\n }\n return context.body(value, status, responseHeaders);\n}\n\nexport function createOutput(contextFn: () => Context) {\n return {\n nocontent() {\n const context = contextFn();\n return context.body(null, 204, {});\n },\n ok(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 200, headers);\n },\n created(\n valueOrUri: string | Data,\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n if (typeof valueOrUri === 'string') {\n // If no content is provided, we send null\n return send(contextFn(), value, 201, {\n Location: valueOrUri,\n ...(headers || {}),\n });\n } else {\n // valueOrUri is the data\n return send(contextFn(), valueOrUri, 201, headers);\n }\n },\n redirect(uri: string | URL, statusCode?: unknown) {\n const context = contextFn();\n return context.redirect(\n uri.toString(),\n (statusCode as RedirectStatusCode) ?? undefined,\n );\n },\n attachment(buffer: Buffer, filename: string, mimeType: string) {\n const context = contextFn();\n // https://github.com/honojs/hono/issues/3720\n return context.body(buffer as never, 200, {\n 'Content-Type': mimeType,\n 'Content-Disposition': `attachment; filename=\"${filename}\"`,\n 'Content-Length': buffer.length.toString(),\n });\n },\n badRequest(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 400, headers);\n },\n unauthorized(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 401, headers);\n },\n forbidden(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 403, headers);\n },\n notFound(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 404, headers);\n },\n notImplemented(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 501, headers);\n },\n accepted(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 202, headers);\n },\n conflict(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 409, headers);\n },\n unprocessableEntity(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 422, headers);\n },\n internalServerError(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 500, headers);\n },\n serviceUnavailable(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 503, headers);\n },\n };\n}\n\nexport const output = createOutput(() => getContext());\n", "/* eslint-disable no-unused-private-class-members */\nimport type { MiddlewareHandler, ValidationTargets } from 'hono';\nimport { createMiddleware } from 'hono/factory';\nimport { HTTPException } from 'hono/http-exception';\nimport z from 'zod';\n\ntype ValidatorConfig = Record<\n string,\n { select: unknown; against: z.ZodTypeAny; }\n>;\n\ntype ExtractInput<T extends ValidatorConfig> = {\n [K in keyof T]: z.infer<T[K]['against']>;\n};\n\ntype HasUndefined<T> = undefined extends T ? true : false;\n\ntype InferTarget<\n T extends ValidatorConfig,\n S,\n Target extends keyof ValidationTargets,\n> = {\n [K in keyof T as T[K]['select'] extends S ? K : never]: HasUndefined<\n z.infer<T[K]['against']>\n > extends true\n ? z.infer<T[K]['against']> | undefined\n : z.infer<T[K]['against']> extends ValidationTargets[Target]\n ? z.infer<T[K]['against']>\n : z.infer<T[K]['against']>;\n };\n\ntype InferIn<T extends ValidatorConfig> = (\n keyof InferTarget<T, QuerySelect | QueriesSelect, 'query'> extends never\n ? never\n : { query: InferTarget<T, QuerySelect | QueriesSelect, 'query'>; }) &\n (keyof InferTarget<T, BodySelect, 'json'> extends never\n ? never\n : { json: InferTarget<T, BodySelect, 'json'>; }) &\n (keyof InferTarget<T, ParamsSelect, 'param'> extends never\n ? never\n : { param: InferTarget<T, ParamsSelect, 'param'>; }) &\n (keyof InferTarget<T, HeadersSelect, 'header'> extends never\n ? never\n : { header: InferTarget<T, HeadersSelect, 'header'>; }) &\n (keyof InferTarget<T, CookieSelect, 'cookie'> extends never\n ? never\n : { form: InferTarget<T, CookieSelect, 'cookie'>; });\n\n// Marker classes\nclass BodySelect {\n #private = 0;\n}\nclass QuerySelect {\n #private = 0;\n}\nclass QueriesSelect {\n #private = 0;\n}\nclass ParamsSelect {\n #private = 0;\n}\nclass HeadersSelect {\n #private = 0;\n}\nclass CookieSelect {\n #private = 0;\n}\n\nexport const validate = <T extends ValidatorConfig>(\n selector: (payload: {\n body: Record<string, BodySelect>;\n query: Record<string, QuerySelect>;\n queries: Record<string, QueriesSelect>;\n params: Record<string, ParamsSelect>;\n headers: Record<string, HeadersSelect>;\n }) => T,\n): MiddlewareHandler<\n {\n Variables: {\n input: ExtractInput<T>;\n };\n },\n string,\n { in: InferIn<T>; }\n> => {\n return createMiddleware<{\n Variables: {\n input: ExtractInput<T>;\n };\n }>(async (c, next) => {\n const contentType = c.req.header('content-type') ?? '';\n let body: unknown = null;\n\n switch (contentType) {\n case 'application/json':\n body = await c.req.json();\n break;\n case 'application/x-www-form-urlencoded':\n body = await c.req.parseBody();\n break;\n default:\n body = {};\n }\n\n const payload = {\n body,\n query: c.req.query(),\n queries: c.req.queries(),\n params: c.req.param(),\n headers: Object.fromEntries(\n Object.entries(c.req.header()).map(([k, v]) => [k, v ?? '']),\n ),\n };\n\n const config = selector(payload as never);\n const schema = z.object(\n Object.entries(config).reduce(\n (acc, [key, value]) => {\n acc[key] = value.against;\n return acc;\n },\n {} as Record<string, z.ZodTypeAny>,\n ),\n );\n\n const input = Object.entries(config).reduce(\n (acc, [key, value]) => {\n acc[key] = value.select;\n return acc;\n },\n {} as Record<string, unknown>,\n );\n\n const parsed = parse(schema, input);\n c.set('input', parsed as ExtractInput<T>);\n await next();\n });\n};\n\nexport function parse<T extends z.ZodRawShape>(\n schema: z.ZodObject<T>,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const error = new HTTPException(400, {\n message: 'Validation failed',\n cause: {\n code: 'api/validation-failed',\n details: result.error.flatten((issue) => ({\n message: issue.message,\n code: issue.code,\n fatel: issue.fatal,\n path: issue.path.join('.'),\n })).fieldErrors,\n },\n });\n throw error;\n }\n return result.data;\n}\n\nexport const openapi = validate;\n\nexport const consume = (\n contentType: 'application/json' | 'application/x-www-form-urlencoded',\n) => {\n return createMiddleware(async (context, next) => {\n const clientContentType = context.req.header('Content-Type');\n if (clientContentType !== contentType) {\n throw new HTTPException(415, {\n message: 'Unsupported Media Type',\n cause: {\n code: 'api/unsupported-media-type',\n details: `Expected content type: ${contentType}, but got: ${clientContentType}`,\n },\n });\n }\n await next();\n });\n};\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/lib/response-analyzer.ts"],
4
+ "sourcesContent": ["import ts from 'typescript';\n\nimport {\n $types,\n type NaunceResponseAnalyzerFn,\n type ResponseItem,\n type TypeDeriver,\n} from '@sdk-it/core';\n\nconst handlerVisitor: (\n on: (\n node: ts.Node | undefined,\n statusCode: ts.Node | undefined,\n headers: ts.Node | undefined,\n contentType: string,\n ) => void,\n contextVarName: string,\n) => ts.Visitor = (callback, contextVarName) => {\n return (node: ts.Node) => {\n if (ts.isReturnStatement(node) && node.expression) {\n if (ts.isCallExpression(node.expression)) {\n if (ts.isPropertyAccessExpression(node.expression.expression)) {\n const propAccess = node.expression.expression;\n if (\n ts.isIdentifier(propAccess.expression) &&\n propAccess.expression.text === contextVarName\n ) {\n const [body, statusCode, headers] = node.expression.arguments;\n let contentType = 'application/json';\n const callerMethod = propAccess.name.text;\n if (callerMethod === 'body') {\n contentType = 'application/octet-stream';\n }\n if (!body) {\n contentType = 'empty';\n }\n callback(body, statusCode, headers, contentType);\n }\n }\n // if (ts.isIdentifier(node.expression.expression)) {\n // console.log('streamText');\n // if (node.expression.expression.text === 'streamText') {\n // callback(undefined, undefined, undefined, 'text/plain');\n // }\n // }\n }\n }\n return ts.forEachChild(node, handlerVisitor(callback, contextVarName));\n };\n};\n\nfunction toResponses(handler: ts.ArrowFunction, deriver: TypeDeriver) {\n const contextVarName = handler.parameters[0].name.getText();\n const responsesList: ResponseItem[] = [];\n const visit = handlerVisitor((node, statusCode, headers, contentType) => {\n responsesList.push({\n headers: headers ? Object.keys(deriver.serializeNode(headers)) : [],\n contentType,\n statusCode: statusCode ? resolveStatusCode(statusCode) : '200',\n response: node ? deriver.serializeNode(node) : undefined,\n });\n }, contextVarName);\n visit(handler.body);\n return responsesList;\n}\n\nfunction resolveStatusCode(node: ts.Node) {\n if (ts.isNumericLiteral(node)) {\n return node.text;\n }\n throw new Error(`Could not resolve status code`);\n}\n\nexport function defaultResponseAnalyzer(\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n) {\n return toResponses(handler, deriver);\n}\n\nexport function streamText(\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n): ResponseItem[] {\n return [\n {\n contentType: 'text/plain',\n headers: [{ 'Transfer-Encoding': ['chunked'] }],\n statusCode: '200',\n response: {\n optional: false,\n kind: 'primitive',\n [$types]: ['string'],\n },\n },\n ];\n}\n\nexport function stream(\n handler: ts.ArrowFunction,\n deriver: TypeDeriver,\n): ResponseItem[] {\n return [\n {\n contentType: 'application/octet-stream',\n headers: [],\n statusCode: '200',\n response: {\n optional: false,\n kind: 'primitive',\n [$types]: ['string'],\n },\n },\n ];\n}\n\nexport const httpException: NaunceResponseAnalyzerFn = (\n handler,\n deriver,\n node,\n) => {\n if (ts.isNewExpression(node)) {\n const [status, options] = node.arguments ?? [];\n // if (!ts.isObjectLiteralExpression(options)) {\n // return [];\n // }\n // const properties = options.properties.reduce<Record<string, string>>(\n // (acc, prop) => {\n // if (ts.isPropertyAssignment(prop)) {\n // const key = prop.name.getText();\n // if (ts.isLiteralExpression(prop.initializer)) {\n // acc[key] = prop.initializer.text;\n // } else {\n // acc[key] = prop.initializer.getText();\n // }\n // }\n // return acc;\n // },\n // {},\n // );\n return [\n {\n contentType: 'application/json',\n headers: [],\n statusCode: resolveStatusCode(status),\n response: options ? deriver.serializeNode(options) : undefined,\n },\n ];\n }\n return [];\n};\n\nexport const responseAnalyzer = {\n default: defaultResponseAnalyzer,\n streamText,\n stream,\n 'throw.new.HTTPException': httpException,\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,13 @@
1
1
  import ts from 'typescript';
2
- import type { ResponseItem, TypeDeriver } from '@sdk-it/core';
3
- export declare function responseAnalyzer(handler: ts.ArrowFunction, deriver: TypeDeriver): ResponseItem[];
2
+ import { type NaunceResponseAnalyzerFn, type ResponseItem, type TypeDeriver } from '@sdk-it/core';
3
+ export declare function defaultResponseAnalyzer(handler: ts.ArrowFunction, deriver: TypeDeriver): ResponseItem[];
4
+ export declare function streamText(handler: ts.ArrowFunction, deriver: TypeDeriver): ResponseItem[];
5
+ export declare function stream(handler: ts.ArrowFunction, deriver: TypeDeriver): ResponseItem[];
6
+ export declare const httpException: NaunceResponseAnalyzerFn;
7
+ export declare const responseAnalyzer: {
8
+ default: typeof defaultResponseAnalyzer;
9
+ streamText: typeof streamText;
10
+ stream: typeof stream;
11
+ 'throw.new.HTTPException': NaunceResponseAnalyzerFn;
12
+ };
4
13
  //# sourceMappingURL=response-analyzer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response-analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/response-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAoE9D,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,kBAGrB"}
1
+ {"version":3,"file":"response-analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/response-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,YAAY,EACjB,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAkEtB,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,kBAGrB;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,GACnB,YAAY,EAAE,CAahB;AAED,wBAAgB,MAAM,CACpB,OAAO,EAAE,EAAE,CAAC,aAAa,EACzB,OAAO,EAAE,WAAW,GACnB,YAAY,EAAE,CAahB;AAED,eAAO,MAAM,aAAa,EAAE,wBAkC3B,CAAC;AAEF,eAAO,MAAM,gBAAgB;;;;;CAK5B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './output.ts';
2
+ export * from './validator.ts';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/runtime/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,174 @@
1
+ // packages/hono/src/lib/runtime/output.ts
2
+ import { getContext } from "hono/context-storage";
3
+ function send(context, value, status, headers) {
4
+ if (value === void 0 || value === null) {
5
+ return context.body(null, status, headers);
6
+ }
7
+ const responseHeaders = { ...headers };
8
+ responseHeaders["Content-Type"] ??= "application/json";
9
+ if (responseHeaders["Content-Type"].includes("application/json")) {
10
+ return context.body(JSON.stringify(value), status, responseHeaders);
11
+ }
12
+ return context.body(value, status, responseHeaders);
13
+ }
14
+ function createOutput(contextFn) {
15
+ return {
16
+ nocontent() {
17
+ const context = contextFn();
18
+ return context.body(null, 204, {});
19
+ },
20
+ ok(value, headers) {
21
+ return send(contextFn(), value, 200, headers);
22
+ },
23
+ created(valueOrUri, value, headers) {
24
+ if (typeof valueOrUri === "string") {
25
+ return send(contextFn(), value, 201, {
26
+ Location: valueOrUri,
27
+ ...headers || {}
28
+ });
29
+ } else {
30
+ return send(contextFn(), valueOrUri, 201, headers);
31
+ }
32
+ },
33
+ redirect(uri, statusCode) {
34
+ const context = contextFn();
35
+ return context.redirect(
36
+ uri.toString(),
37
+ statusCode ?? void 0
38
+ );
39
+ },
40
+ attachment(buffer, filename, mimeType) {
41
+ const context = contextFn();
42
+ return context.body(buffer, 200, {
43
+ "Content-Type": mimeType,
44
+ "Content-Disposition": `attachment; filename="${filename}"`,
45
+ "Content-Length": buffer.length.toString()
46
+ });
47
+ },
48
+ badRequest(value, headers) {
49
+ return send(contextFn(), value, 400, headers);
50
+ },
51
+ unauthorized(value, headers) {
52
+ return send(contextFn(), value, 401, headers);
53
+ },
54
+ forbidden(value, headers) {
55
+ return send(contextFn(), value, 403, headers);
56
+ },
57
+ notFound(value, headers) {
58
+ return send(contextFn(), value, 404, headers);
59
+ },
60
+ notImplemented(value, headers) {
61
+ return send(contextFn(), value, 501, headers);
62
+ },
63
+ accepted(value, headers) {
64
+ return send(contextFn(), value, 202, headers);
65
+ },
66
+ conflict(value, headers) {
67
+ return send(contextFn(), value, 409, headers);
68
+ },
69
+ unprocessableEntity(value, headers) {
70
+ return send(contextFn(), value, 422, headers);
71
+ },
72
+ internalServerError(value, headers) {
73
+ return send(contextFn(), value, 500, headers);
74
+ },
75
+ serviceUnavailable(value, headers) {
76
+ return send(contextFn(), value, 503, headers);
77
+ }
78
+ };
79
+ }
80
+ var output = createOutput(() => getContext());
81
+
82
+ // packages/hono/src/lib/runtime/validator.ts
83
+ import { createMiddleware } from "hono/factory";
84
+ import { HTTPException } from "hono/http-exception";
85
+ import z from "zod";
86
+ var validate = (selector) => {
87
+ return createMiddleware(async (c, next) => {
88
+ const contentType = c.req.header("content-type") ?? "";
89
+ let body = null;
90
+ switch (contentType) {
91
+ case "application/json":
92
+ body = await c.req.json();
93
+ break;
94
+ case "application/x-www-form-urlencoded":
95
+ body = await c.req.parseBody();
96
+ break;
97
+ default:
98
+ body = {};
99
+ }
100
+ const payload = {
101
+ body,
102
+ query: c.req.query(),
103
+ queries: c.req.queries(),
104
+ params: c.req.param(),
105
+ headers: Object.fromEntries(
106
+ Object.entries(c.req.header()).map(([k, v]) => [k, v ?? ""])
107
+ )
108
+ };
109
+ const config = selector(payload);
110
+ const schema = z.object(
111
+ Object.entries(config).reduce(
112
+ (acc, [key, value]) => {
113
+ acc[key] = value.against;
114
+ return acc;
115
+ },
116
+ {}
117
+ )
118
+ );
119
+ const input = Object.entries(config).reduce(
120
+ (acc, [key, value]) => {
121
+ acc[key] = value.select;
122
+ return acc;
123
+ },
124
+ {}
125
+ );
126
+ const parsed = parse(schema, input);
127
+ c.set("input", parsed);
128
+ await next();
129
+ });
130
+ };
131
+ function parse(schema, input) {
132
+ const result = schema.safeParse(input);
133
+ if (!result.success) {
134
+ const error = new HTTPException(400, {
135
+ message: "Validation failed",
136
+ cause: {
137
+ code: "api/validation-failed",
138
+ details: result.error.flatten((issue) => ({
139
+ message: issue.message,
140
+ code: issue.code,
141
+ fatel: issue.fatal,
142
+ path: issue.path.join(".")
143
+ })).fieldErrors
144
+ }
145
+ });
146
+ throw error;
147
+ }
148
+ return result.data;
149
+ }
150
+ var openapi = validate;
151
+ var consume = (contentType) => {
152
+ return createMiddleware(async (context, next) => {
153
+ const clientContentType = context.req.header("Content-Type");
154
+ if (clientContentType !== contentType) {
155
+ throw new HTTPException(415, {
156
+ message: "Unsupported Media Type",
157
+ cause: {
158
+ code: "api/unsupported-media-type",
159
+ details: `Expected content type: ${contentType}, but got: ${clientContentType}`
160
+ }
161
+ });
162
+ }
163
+ await next();
164
+ });
165
+ };
166
+ export {
167
+ consume,
168
+ createOutput,
169
+ openapi,
170
+ output,
171
+ parse,
172
+ validate
173
+ };
174
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/runtime/output.ts", "../../../src/lib/runtime/validator.ts"],
4
+ "sourcesContent": ["import type { Context } from 'hono';\nimport { getContext } from 'hono/context-storage';\nimport type {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from 'hono/utils/http-status';\n\ntype Data = any;\nfunction send(\n context: Context,\n value: Data | undefined | null,\n status: ContentfulStatusCode,\n headers?: Readonly<Record<string, string>>,\n) {\n if (value === undefined || value === null) {\n return context.body(null, status, headers);\n }\n const responseHeaders = { ...headers };\n responseHeaders['Content-Type'] ??= 'application/json';\n if (responseHeaders['Content-Type'].includes('application/json')) {\n return context.body(JSON.stringify(value), status, responseHeaders);\n }\n return context.body(value, status, responseHeaders);\n}\n\nexport function createOutput(contextFn: () => Context) {\n return {\n nocontent() {\n const context = contextFn();\n return context.body(null, 204, {});\n },\n ok(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 200, headers);\n },\n created(\n valueOrUri: string | Data,\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n if (typeof valueOrUri === 'string') {\n // If no content is provided, we send null\n return send(contextFn(), value, 201, {\n Location: valueOrUri,\n ...(headers || {}),\n });\n } else {\n // valueOrUri is the data\n return send(contextFn(), valueOrUri, 201, headers);\n }\n },\n redirect(uri: string | URL, statusCode?: unknown) {\n const context = contextFn();\n return context.redirect(\n uri.toString(),\n (statusCode as RedirectStatusCode) ?? undefined,\n );\n },\n attachment(buffer: Buffer, filename: string, mimeType: string) {\n const context = contextFn();\n // https://github.com/honojs/hono/issues/3720\n return context.body(buffer as never, 200, {\n 'Content-Type': mimeType,\n 'Content-Disposition': `attachment; filename=\"${filename}\"`,\n 'Content-Length': buffer.length.toString(),\n });\n },\n badRequest(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 400, headers);\n },\n unauthorized(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 401, headers);\n },\n forbidden(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 403, headers);\n },\n notFound(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 404, headers);\n },\n notImplemented(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 501, headers);\n },\n accepted(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 202, headers);\n },\n conflict(value: Data | undefined | null, headers?: Record<string, string>) {\n return send(contextFn(), value, 409, headers);\n },\n unprocessableEntity(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 422, headers);\n },\n internalServerError(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 500, headers);\n },\n serviceUnavailable(\n value: Data | undefined | null,\n headers?: Record<string, string>,\n ) {\n return send(contextFn(), value, 503, headers);\n },\n };\n}\n\nexport const output = createOutput(() => getContext());\n", "/* eslint-disable no-unused-private-class-members */\nimport type { MiddlewareHandler, ValidationTargets } from 'hono';\nimport { createMiddleware } from 'hono/factory';\nimport { HTTPException } from 'hono/http-exception';\nimport z from 'zod';\n\ntype ValidatorConfig = Record<\n string,\n { select: unknown; against: z.ZodTypeAny; }\n>;\n\ntype ExtractInput<T extends ValidatorConfig> = {\n [K in keyof T]: z.infer<T[K]['against']>;\n};\n\ntype HasUndefined<T> = undefined extends T ? true : false;\n\ntype InferTarget<\n T extends ValidatorConfig,\n S,\n Target extends keyof ValidationTargets,\n> = {\n [K in keyof T as T[K]['select'] extends S ? K : never]: HasUndefined<\n z.infer<T[K]['against']>\n > extends true\n ? z.infer<T[K]['against']> | undefined\n : z.infer<T[K]['against']> extends ValidationTargets[Target]\n ? z.infer<T[K]['against']>\n : z.infer<T[K]['against']>;\n };\n\ntype InferIn<T extends ValidatorConfig> = (\n keyof InferTarget<T, QuerySelect | QueriesSelect, 'query'> extends never\n ? never\n : { query: InferTarget<T, QuerySelect | QueriesSelect, 'query'>; }) &\n (keyof InferTarget<T, BodySelect, 'json'> extends never\n ? never\n : { json: InferTarget<T, BodySelect, 'json'>; }) &\n (keyof InferTarget<T, ParamsSelect, 'param'> extends never\n ? never\n : { param: InferTarget<T, ParamsSelect, 'param'>; }) &\n (keyof InferTarget<T, HeadersSelect, 'header'> extends never\n ? never\n : { header: InferTarget<T, HeadersSelect, 'header'>; }) &\n (keyof InferTarget<T, CookieSelect, 'cookie'> extends never\n ? never\n : { form: InferTarget<T, CookieSelect, 'cookie'>; });\n\n// Marker classes\nclass BodySelect {\n #private = 0;\n}\nclass QuerySelect {\n #private = 0;\n}\nclass QueriesSelect {\n #private = 0;\n}\nclass ParamsSelect {\n #private = 0;\n}\nclass HeadersSelect {\n #private = 0;\n}\nclass CookieSelect {\n #private = 0;\n}\n\nexport const validate = <T extends ValidatorConfig>(\n selector: (payload: {\n body: Record<string, BodySelect>;\n query: Record<string, QuerySelect>;\n queries: Record<string, QueriesSelect>;\n params: Record<string, ParamsSelect>;\n headers: Record<string, HeadersSelect>;\n }) => T,\n): MiddlewareHandler<\n {\n Variables: {\n input: ExtractInput<T>;\n };\n },\n string,\n { in: InferIn<T>; }\n> => {\n return createMiddleware<{\n Variables: {\n input: ExtractInput<T>;\n };\n }>(async (c, next) => {\n const contentType = c.req.header('content-type') ?? '';\n let body: unknown = null;\n\n switch (contentType) {\n case 'application/json':\n body = await c.req.json();\n break;\n case 'application/x-www-form-urlencoded':\n body = await c.req.parseBody();\n break;\n default:\n body = {};\n }\n\n const payload = {\n body,\n query: c.req.query(),\n queries: c.req.queries(),\n params: c.req.param(),\n headers: Object.fromEntries(\n Object.entries(c.req.header()).map(([k, v]) => [k, v ?? '']),\n ),\n };\n\n const config = selector(payload as never);\n const schema = z.object(\n Object.entries(config).reduce(\n (acc, [key, value]) => {\n acc[key] = value.against;\n return acc;\n },\n {} as Record<string, z.ZodTypeAny>,\n ),\n );\n\n const input = Object.entries(config).reduce(\n (acc, [key, value]) => {\n acc[key] = value.select;\n return acc;\n },\n {} as Record<string, unknown>,\n );\n\n const parsed = parse(schema, input);\n c.set('input', parsed as ExtractInput<T>);\n await next();\n });\n};\n\nexport function parse<T extends z.ZodRawShape>(\n schema: z.ZodObject<T>,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const error = new HTTPException(400, {\n message: 'Validation failed',\n cause: {\n code: 'api/validation-failed',\n details: result.error.flatten((issue) => ({\n message: issue.message,\n code: issue.code,\n fatel: issue.fatal,\n path: issue.path.join('.'),\n })).fieldErrors,\n },\n });\n throw error;\n }\n return result.data;\n}\n\nexport const openapi = validate;\n\nexport const consume = (\n contentType: 'application/json' | 'application/x-www-form-urlencoded',\n) => {\n return createMiddleware(async (context, next) => {\n const clientContentType = context.req.header('Content-Type');\n if (clientContentType !== contentType) {\n throw new HTTPException(415, {\n message: 'Unsupported Media Type',\n cause: {\n code: 'api/unsupported-media-type',\n details: `Expected content type: ${contentType}, but got: ${clientContentType}`,\n },\n });\n }\n await next();\n });\n};\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdk-it/hono",
3
- "version": "0.8.2",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -14,6 +14,11 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js",
16
16
  "default": "./dist/index.js"
17
+ },
18
+ "./runtime": {
19
+ "types": "./dist/lib/runtime/index.d.ts",
20
+ "import": "./dist/lib/runtime/index.js",
21
+ "default": "./dist/lib/runtime/index.js"
17
22
  }
18
23
  },
19
24
  "files": [
@@ -21,8 +26,7 @@
21
26
  "!**/*.tsbuildinfo"
22
27
  ],
23
28
  "dependencies": {
24
- "@sdk-it/core": "0.8.2",
25
- "debug": "^4.4.0",
29
+ "@sdk-it/core": "0.10.0",
26
30
  "hono": "^4.7.4",
27
31
  "typescript": "^5.7.2",
28
32
  "zod": "^3.24.2"