@kozojs/core 0.2.2
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 +254 -0
- package/lib/index.d.ts +135 -0
- package/lib/index.js +694 -0
- package/lib/index.js.map +1 -0
- package/lib/middleware/index.d.ts +79 -0
- package/lib/middleware/index.js +135 -0
- package/lib/middleware/index.js.map +1 -0
- package/package.json +76 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
// src/app.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { serve } from "@hono/node-server";
|
|
4
|
+
import Ajv3 from "ajv";
|
|
5
|
+
import addFormats3 from "ajv-formats";
|
|
6
|
+
|
|
7
|
+
// src/client-generator.ts
|
|
8
|
+
function generateMethodName(method, path) {
|
|
9
|
+
const cleanPath = path.replace(/^\/+|\/+$/g, "");
|
|
10
|
+
const withParams = cleanPath.replace(/:(\w+)/g, "By$1");
|
|
11
|
+
const safeName = withParams.replace(/[\/\-\.]/g, "_").replace(/[^\w]/g, "");
|
|
12
|
+
if (method.toLowerCase() !== "get") {
|
|
13
|
+
return method.toLowerCase() + safeName.charAt(0).toUpperCase() + safeName.slice(1);
|
|
14
|
+
}
|
|
15
|
+
return safeName || "index";
|
|
16
|
+
}
|
|
17
|
+
function extractPathParams(path) {
|
|
18
|
+
const matches = path.match(/:(\w+)/g);
|
|
19
|
+
return matches ? matches.map((m) => m.slice(1)) : [];
|
|
20
|
+
}
|
|
21
|
+
function generateTypedClient(routes, options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
includeValidation = true,
|
|
24
|
+
baseUrl = "",
|
|
25
|
+
validateByDefault = false,
|
|
26
|
+
defaultHeaders = {}
|
|
27
|
+
} = options;
|
|
28
|
+
const imports = [];
|
|
29
|
+
const typeDefinitions = [];
|
|
30
|
+
const schemaExports = [];
|
|
31
|
+
const methodImplementations = [];
|
|
32
|
+
if (includeValidation) {
|
|
33
|
+
imports.push(`import { z } from 'zod';`);
|
|
34
|
+
}
|
|
35
|
+
let code = `// Auto-generated Kozo Client
|
|
36
|
+
`;
|
|
37
|
+
code += `// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
38
|
+
`;
|
|
39
|
+
code += `// DO NOT EDIT - Changes will be overwritten
|
|
40
|
+
|
|
41
|
+
`;
|
|
42
|
+
const schemaVars = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const route of routes) {
|
|
44
|
+
const methodName = generateMethodName(route.method, route.path);
|
|
45
|
+
const pathParams = extractPathParams(route.path);
|
|
46
|
+
let paramsType = "void";
|
|
47
|
+
let bodyType = "void";
|
|
48
|
+
let queryType = "void";
|
|
49
|
+
let responseType = "unknown";
|
|
50
|
+
if (pathParams.length > 0) {
|
|
51
|
+
paramsType = `{ ${pathParams.map((p) => `${p}: string`).join("; ")} }`;
|
|
52
|
+
}
|
|
53
|
+
if (route.zodSchemas?.body || route.schema.body) {
|
|
54
|
+
const schemaVarName = `${capitalize(methodName)}BodySchema`;
|
|
55
|
+
schemaVars.set(`${methodName}_body`, schemaVarName);
|
|
56
|
+
bodyType = `z.infer<typeof ${schemaVarName}>`;
|
|
57
|
+
if (includeValidation) {
|
|
58
|
+
schemaExports.push(`// @ts-ignore - Schema defined at runtime
|
|
59
|
+
export const ${schemaVarName}: any = null;`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (route.zodSchemas?.query || route.schema.query) {
|
|
63
|
+
const schemaVarName = `${capitalize(methodName)}QuerySchema`;
|
|
64
|
+
schemaVars.set(`${methodName}_query`, schemaVarName);
|
|
65
|
+
queryType = `z.infer<typeof ${schemaVarName}>`;
|
|
66
|
+
if (includeValidation) {
|
|
67
|
+
schemaExports.push(`// @ts-ignore - Schema defined at runtime
|
|
68
|
+
export const ${schemaVarName}: any = null;`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (route.zodSchemas?.response || route.schema.response) {
|
|
72
|
+
const schemaVarName = `${capitalize(methodName)}ResponseSchema`;
|
|
73
|
+
schemaVars.set(`${methodName}_response`, schemaVarName);
|
|
74
|
+
responseType = `z.infer<typeof ${schemaVarName}>`;
|
|
75
|
+
if (includeValidation) {
|
|
76
|
+
schemaExports.push(`// @ts-ignore - Schema defined at runtime
|
|
77
|
+
export const ${schemaVarName}: any = null;`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (bodyType !== "void" && !bodyType.includes("z.infer")) {
|
|
81
|
+
typeDefinitions.push(`export type ${capitalize(methodName)}Body = ${bodyType};`);
|
|
82
|
+
}
|
|
83
|
+
if (queryType !== "void" && !queryType.includes("z.infer")) {
|
|
84
|
+
typeDefinitions.push(`export type ${capitalize(methodName)}Query = ${queryType};`);
|
|
85
|
+
}
|
|
86
|
+
if (!responseType.includes("z.infer")) {
|
|
87
|
+
typeDefinitions.push(`export type ${capitalize(methodName)}Response = ${responseType};`);
|
|
88
|
+
}
|
|
89
|
+
const args = [];
|
|
90
|
+
if (paramsType !== "void") args.push(`params: ${paramsType}`);
|
|
91
|
+
if (bodyType !== "void") args.push(`body: ${bodyType}`);
|
|
92
|
+
if (queryType !== "void") args.push(`query?: ${queryType}`);
|
|
93
|
+
const argsStr = args.join(", ");
|
|
94
|
+
const returnType = `Promise<${responseType}>`;
|
|
95
|
+
let methodBody = ` async ${methodName}(${argsStr}): ${returnType} {
|
|
96
|
+
`;
|
|
97
|
+
if (includeValidation && bodyType !== "void") {
|
|
98
|
+
const schemaVar = schemaVars.get(`${methodName}_body`);
|
|
99
|
+
if (schemaVar) {
|
|
100
|
+
methodBody += ` if (this.validateRequests && ${schemaVar}) {
|
|
101
|
+
`;
|
|
102
|
+
methodBody += ` ${schemaVar}.parse(body);
|
|
103
|
+
`;
|
|
104
|
+
methodBody += ` }
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let urlExpression = `\`\${this.baseUrl}${route.path}\``;
|
|
109
|
+
if (pathParams.length > 0) {
|
|
110
|
+
const pathWithParams = route.path.replace(/:(\w+)/g, "${params.$1}");
|
|
111
|
+
urlExpression = `\`\${this.baseUrl}${pathWithParams}\``;
|
|
112
|
+
}
|
|
113
|
+
methodBody += ` let url = ${urlExpression};
|
|
114
|
+
`;
|
|
115
|
+
if (queryType !== "void") {
|
|
116
|
+
methodBody += ` if (query) {
|
|
117
|
+
`;
|
|
118
|
+
methodBody += ` const queryString = new URLSearchParams(query as any).toString();
|
|
119
|
+
`;
|
|
120
|
+
methodBody += ` url += \`?\${queryString}\`;
|
|
121
|
+
`;
|
|
122
|
+
methodBody += ` }
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
methodBody += ` const options: RequestInit = {
|
|
126
|
+
`;
|
|
127
|
+
methodBody += ` method: '${route.method.toUpperCase()}',
|
|
128
|
+
`;
|
|
129
|
+
methodBody += ` headers: { ...this.defaultHeaders, 'Content-Type': 'application/json' },
|
|
130
|
+
`;
|
|
131
|
+
if (bodyType !== "void") {
|
|
132
|
+
methodBody += ` body: JSON.stringify(body),
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
methodBody += ` };
|
|
136
|
+
`;
|
|
137
|
+
methodBody += ` const response = await fetch(url, options);
|
|
138
|
+
`;
|
|
139
|
+
methodBody += ` if (!response.ok) {
|
|
140
|
+
`;
|
|
141
|
+
methodBody += ` throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
142
|
+
`;
|
|
143
|
+
methodBody += ` }
|
|
144
|
+
`;
|
|
145
|
+
methodBody += ` return response.json();
|
|
146
|
+
`;
|
|
147
|
+
methodBody += ` }
|
|
148
|
+
`;
|
|
149
|
+
methodImplementations.push(methodBody);
|
|
150
|
+
}
|
|
151
|
+
if (imports.length > 0) {
|
|
152
|
+
code += imports.join("\n") + "\n\n";
|
|
153
|
+
}
|
|
154
|
+
if (typeDefinitions.length > 0) {
|
|
155
|
+
code += "// Type Definitions\n";
|
|
156
|
+
code += typeDefinitions.join("\n") + "\n\n";
|
|
157
|
+
}
|
|
158
|
+
if (includeValidation && schemaExports.length > 0) {
|
|
159
|
+
code += "// Zod Schemas\n";
|
|
160
|
+
code += schemaExports.join("\n") + "\n\n";
|
|
161
|
+
}
|
|
162
|
+
code += `export interface KozoClientOptions {
|
|
163
|
+
`;
|
|
164
|
+
code += ` baseUrl?: string;
|
|
165
|
+
`;
|
|
166
|
+
code += ` validateRequests?: boolean;
|
|
167
|
+
`;
|
|
168
|
+
code += ` defaultHeaders?: Record<string, string>;
|
|
169
|
+
`;
|
|
170
|
+
code += `}
|
|
171
|
+
|
|
172
|
+
`;
|
|
173
|
+
code += `export class KozoClient {
|
|
174
|
+
`;
|
|
175
|
+
code += ` private baseUrl: string;
|
|
176
|
+
`;
|
|
177
|
+
code += ` private validateRequests: boolean;
|
|
178
|
+
`;
|
|
179
|
+
code += ` private defaultHeaders: Record<string, string>;
|
|
180
|
+
|
|
181
|
+
`;
|
|
182
|
+
code += ` constructor(options: KozoClientOptions = {}) {
|
|
183
|
+
`;
|
|
184
|
+
code += ` this.baseUrl = options.baseUrl || '${baseUrl}';
|
|
185
|
+
`;
|
|
186
|
+
code += ` this.validateRequests = options.validateRequests ?? ${validateByDefault};
|
|
187
|
+
`;
|
|
188
|
+
code += ` this.defaultHeaders = options.defaultHeaders || ${JSON.stringify(defaultHeaders)};
|
|
189
|
+
`;
|
|
190
|
+
code += ` }
|
|
191
|
+
|
|
192
|
+
`;
|
|
193
|
+
code += methodImplementations.join("\n");
|
|
194
|
+
code += `}
|
|
195
|
+
|
|
196
|
+
`;
|
|
197
|
+
code += `export default KozoClient;
|
|
198
|
+
`;
|
|
199
|
+
return code;
|
|
200
|
+
}
|
|
201
|
+
function capitalize(str) {
|
|
202
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/optimizations/serializer.ts
|
|
206
|
+
import fastJson from "fast-json-stringify";
|
|
207
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
208
|
+
function isZodSchema(schema) {
|
|
209
|
+
return typeof schema === "object" && schema !== null && "safeParse" in schema;
|
|
210
|
+
}
|
|
211
|
+
var ResponseSerializer = class {
|
|
212
|
+
serializers = /* @__PURE__ */ new Map();
|
|
213
|
+
fallback = JSON.stringify;
|
|
214
|
+
compile(routeId, schema) {
|
|
215
|
+
try {
|
|
216
|
+
let jsonSchema;
|
|
217
|
+
const isZod = isZodSchema(schema);
|
|
218
|
+
if (isZod) {
|
|
219
|
+
jsonSchema = zodToJsonSchema(schema, { $refStrategy: "none", target: "jsonSchema7" });
|
|
220
|
+
if (process.env.KOZO_DEBUG_SCHEMA === "true") {
|
|
221
|
+
console.log(`
|
|
222
|
+
[DIAGNOSTIC] Schema for ${routeId}:`);
|
|
223
|
+
console.log(JSON.stringify(jsonSchema, null, 2));
|
|
224
|
+
const schemaStr = JSON.stringify(jsonSchema);
|
|
225
|
+
const hasAnyOf = schemaStr.includes("anyOf");
|
|
226
|
+
const hasOneOf = schemaStr.includes("oneOf");
|
|
227
|
+
const hasAllOf = schemaStr.includes("allOf");
|
|
228
|
+
if (hasAnyOf || hasOneOf || hasAllOf) {
|
|
229
|
+
console.warn(`\u26A0\uFE0F Complex schema detected for ${routeId}:`, { hasAnyOf, hasOneOf, hasAllOf });
|
|
230
|
+
console.warn(" This may reduce fast-json-stringify performance");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
jsonSchema = schema;
|
|
235
|
+
}
|
|
236
|
+
const stringify = fastJson(jsonSchema);
|
|
237
|
+
this.serializers.set(routeId, stringify);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error(`Failed to compile serializer for ${routeId}`, err);
|
|
240
|
+
this.serializers.set(routeId, this.fallback);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Deprecated: for backward compatibility
|
|
244
|
+
compileFromZod(routeId, zodSchema) {
|
|
245
|
+
this.compile(routeId, zodSchema);
|
|
246
|
+
}
|
|
247
|
+
// Deprecated: for backward compatibility
|
|
248
|
+
compileFromJsonSchema(routeId, schema) {
|
|
249
|
+
this.compile(routeId, schema);
|
|
250
|
+
}
|
|
251
|
+
serialize(routeId, data) {
|
|
252
|
+
const serializer = this.serializers.get(routeId);
|
|
253
|
+
return serializer ? serializer(data) : this.fallback(data);
|
|
254
|
+
}
|
|
255
|
+
has(routeId) {
|
|
256
|
+
return this.serializers.has(routeId);
|
|
257
|
+
}
|
|
258
|
+
getSerializer(routeId) {
|
|
259
|
+
return this.serializers.get(routeId);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var commonSchemas = {
|
|
263
|
+
health: {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
status: { type: "string" },
|
|
267
|
+
timestamp: { type: "number" }
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
error: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: {
|
|
273
|
+
error: { type: "string" },
|
|
274
|
+
status: { type: "number" }
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
success: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
success: { type: "boolean" },
|
|
281
|
+
data: {}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var globalSerializer = new ResponseSerializer();
|
|
286
|
+
globalSerializer.compile("__health", commonSchemas.health);
|
|
287
|
+
globalSerializer.compile("__error", commonSchemas.error);
|
|
288
|
+
|
|
289
|
+
// src/optimizations/validator.ts
|
|
290
|
+
import Ajv from "ajv";
|
|
291
|
+
import addFormats from "ajv-formats";
|
|
292
|
+
import { zodToJsonSchema as zodToJsonSchema2 } from "zod-to-json-schema";
|
|
293
|
+
function isZodSchema2(schema) {
|
|
294
|
+
return typeof schema === "object" && schema !== null && "safeParse" in schema;
|
|
295
|
+
}
|
|
296
|
+
var HybridValidator = class {
|
|
297
|
+
ajv;
|
|
298
|
+
validators = /* @__PURE__ */ new Map();
|
|
299
|
+
constructor() {
|
|
300
|
+
this.ajv = new Ajv({
|
|
301
|
+
allErrors: false,
|
|
302
|
+
// Stop at first error (faster)
|
|
303
|
+
coerceTypes: true,
|
|
304
|
+
// Auto-coerce strings to numbers etc
|
|
305
|
+
removeAdditional: true
|
|
306
|
+
// Strip unknown properties
|
|
307
|
+
});
|
|
308
|
+
addFormats(this.ajv);
|
|
309
|
+
}
|
|
310
|
+
compile(routeId, schema) {
|
|
311
|
+
try {
|
|
312
|
+
let jsonSchema;
|
|
313
|
+
if (isZodSchema2(schema)) {
|
|
314
|
+
jsonSchema = zodToJsonSchema2(schema, { $refStrategy: "none", target: "jsonSchema7" });
|
|
315
|
+
} else {
|
|
316
|
+
jsonSchema = schema;
|
|
317
|
+
}
|
|
318
|
+
const validate = this.ajv.compile(jsonSchema);
|
|
319
|
+
this.validators.set(routeId, (data) => {
|
|
320
|
+
const valid = validate(data);
|
|
321
|
+
if (valid) {
|
|
322
|
+
return { valid: true, data };
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
valid: false,
|
|
326
|
+
errors: validate.errors?.map((e) => `${e.instancePath} ${e.message}`) || ["Validation failed"]
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
} catch (err) {
|
|
330
|
+
if (isZodSchema2(schema)) {
|
|
331
|
+
this.validators.set(routeId, (data) => {
|
|
332
|
+
const result = schema.safeParse(data);
|
|
333
|
+
if (result.success) {
|
|
334
|
+
return { valid: true, data: result.data };
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
valid: false,
|
|
338
|
+
errors: result.error.issues.map((i) => `${i.path.join(".")} ${i.message}`)
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
console.error(`Failed to compile validator for ${routeId}`, err);
|
|
343
|
+
this.validators.set(routeId, () => ({ valid: false, errors: ["Internal Server Error: Validator compilation failed"] }));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Deprecated: kept for backward compatibility if needed, but redirects to compile
|
|
348
|
+
compileFromZod(routeId, zodSchema) {
|
|
349
|
+
this.compile(routeId, zodSchema);
|
|
350
|
+
}
|
|
351
|
+
validate(routeId, data) {
|
|
352
|
+
const validator = this.validators.get(routeId);
|
|
353
|
+
if (!validator) {
|
|
354
|
+
return { valid: true, data };
|
|
355
|
+
}
|
|
356
|
+
return validator(data);
|
|
357
|
+
}
|
|
358
|
+
// For production: throws on invalid (faster than returning errors)
|
|
359
|
+
validateOrThrow(routeId, data) {
|
|
360
|
+
const result = this.validate(routeId, data);
|
|
361
|
+
if (!result.valid) {
|
|
362
|
+
const error = new Error("Validation failed");
|
|
363
|
+
error.errors = result.errors;
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
return result.data;
|
|
367
|
+
}
|
|
368
|
+
has(routeId) {
|
|
369
|
+
return this.validators.has(routeId);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
var globalValidator = new HybridValidator();
|
|
373
|
+
|
|
374
|
+
// src/compiler.ts
|
|
375
|
+
import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
|
|
376
|
+
import Ajv2 from "ajv";
|
|
377
|
+
import addFormats2 from "ajv-formats";
|
|
378
|
+
import fastJson2 from "fast-json-stringify";
|
|
379
|
+
var ajv = new Ajv2({
|
|
380
|
+
removeAdditional: "all",
|
|
381
|
+
useDefaults: true,
|
|
382
|
+
coerceTypes: true
|
|
383
|
+
});
|
|
384
|
+
addFormats2(ajv);
|
|
385
|
+
function isZodSchema3(schema) {
|
|
386
|
+
return typeof schema === "object" && schema !== null && "safeParse" in schema;
|
|
387
|
+
}
|
|
388
|
+
var SchemaCompiler = class {
|
|
389
|
+
static compile(schema) {
|
|
390
|
+
const compiled = {};
|
|
391
|
+
if (schema.body) {
|
|
392
|
+
const jsonSchema = isZodSchema3(schema.body) ? zodToJsonSchema3(schema.body, { target: "jsonSchema7" }) : schema.body;
|
|
393
|
+
compiled.validateBody = ajv.compile(jsonSchema);
|
|
394
|
+
}
|
|
395
|
+
if (schema.query) {
|
|
396
|
+
const jsonSchema = isZodSchema3(schema.query) ? zodToJsonSchema3(schema.query, { target: "jsonSchema7" }) : schema.query;
|
|
397
|
+
compiled.validateQuery = ajv.compile(jsonSchema);
|
|
398
|
+
}
|
|
399
|
+
if (schema.params) {
|
|
400
|
+
const jsonSchema = isZodSchema3(schema.params) ? zodToJsonSchema3(schema.params, { target: "jsonSchema7" }) : schema.params;
|
|
401
|
+
compiled.validateParams = ajv.compile(jsonSchema);
|
|
402
|
+
}
|
|
403
|
+
if (schema.response) {
|
|
404
|
+
if (typeof schema.response === "object" && !isZodSchema3(schema.response)) {
|
|
405
|
+
const keys = Object.keys(schema.response);
|
|
406
|
+
const isStatusMap = keys.length > 0 && keys.every((k) => !isNaN(Number(k)));
|
|
407
|
+
if (isStatusMap) {
|
|
408
|
+
const responseSchemas = schema.response;
|
|
409
|
+
if (responseSchemas[200]) {
|
|
410
|
+
const jsonSchema = isZodSchema3(responseSchemas[200]) ? zodToJsonSchema3(responseSchemas[200], { target: "jsonSchema7" }) : responseSchemas[200];
|
|
411
|
+
compiled.serialize = fastJson2(jsonSchema);
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
compiled.serialize = fastJson2(schema.response);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
const responseSchema = schema.response;
|
|
418
|
+
const jsonSchema = zodToJsonSchema3(responseSchema, { target: "jsonSchema7" });
|
|
419
|
+
compiled.serialize = fastJson2(jsonSchema);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return compiled;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
var RESPONSE_INIT_200 = { status: 200, headers: { "Content-Type": "application/json" } };
|
|
426
|
+
var RESPONSE_INIT_400 = { status: 400, headers: { "Content-Type": "application/json" } };
|
|
427
|
+
var RESPONSE_INIT_500 = { status: 500, headers: { "Content-Type": "application/json" } };
|
|
428
|
+
var ERROR_INVALID = '{"error":"Validation failed"}';
|
|
429
|
+
var ERROR_INTERNAL = '{"error":"Internal server error"}';
|
|
430
|
+
function compileRouteHandler(userHandler, schema, services, compiled) {
|
|
431
|
+
const bodyValidator = compiled.validateBody;
|
|
432
|
+
const queryValidator = compiled.validateQuery;
|
|
433
|
+
const paramsValidator = compiled.validateParams;
|
|
434
|
+
const serializer = compiled.serialize;
|
|
435
|
+
const needsBody = !!bodyValidator;
|
|
436
|
+
const needsQuery = !!queryValidator;
|
|
437
|
+
const needsParams = !!paramsValidator;
|
|
438
|
+
const needsSerialize = !!serializer;
|
|
439
|
+
if (!needsBody && !needsQuery && !needsParams) {
|
|
440
|
+
if (needsSerialize) {
|
|
441
|
+
return (c) => {
|
|
442
|
+
c.services = services;
|
|
443
|
+
const result = userHandler(c);
|
|
444
|
+
if (result instanceof Promise) {
|
|
445
|
+
return result.then((res) => {
|
|
446
|
+
if (res instanceof Response) return res;
|
|
447
|
+
return new Response(serializer(res), RESPONSE_INIT_200);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (result instanceof Response) return result;
|
|
451
|
+
return new Response(serializer(result), RESPONSE_INIT_200);
|
|
452
|
+
};
|
|
453
|
+
} else {
|
|
454
|
+
return (c) => {
|
|
455
|
+
c.services = services;
|
|
456
|
+
const result = userHandler(c);
|
|
457
|
+
if (result instanceof Promise) {
|
|
458
|
+
return result.then((res) => {
|
|
459
|
+
if (res instanceof Response) return res;
|
|
460
|
+
return new Response(JSON.stringify(res), RESPONSE_INIT_200);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (result instanceof Response) return result;
|
|
464
|
+
return new Response(JSON.stringify(result), RESPONSE_INIT_200);
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!needsBody) {
|
|
469
|
+
if (needsSerialize) {
|
|
470
|
+
return (c) => {
|
|
471
|
+
const ctx = c;
|
|
472
|
+
ctx.services = services;
|
|
473
|
+
if (needsParams) {
|
|
474
|
+
const params = c.req.param();
|
|
475
|
+
if (!paramsValidator(params)) {
|
|
476
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
477
|
+
}
|
|
478
|
+
ctx.params = params;
|
|
479
|
+
}
|
|
480
|
+
if (needsQuery) {
|
|
481
|
+
const query = c.req.query();
|
|
482
|
+
if (!queryValidator(query)) {
|
|
483
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
484
|
+
}
|
|
485
|
+
ctx.query = query;
|
|
486
|
+
}
|
|
487
|
+
const result = userHandler(c);
|
|
488
|
+
if (result instanceof Promise) {
|
|
489
|
+
return result.then((res) => {
|
|
490
|
+
if (res instanceof Response) return res;
|
|
491
|
+
return new Response(serializer(res), RESPONSE_INIT_200);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
if (result instanceof Response) return result;
|
|
495
|
+
return new Response(serializer(result), RESPONSE_INIT_200);
|
|
496
|
+
};
|
|
497
|
+
} else {
|
|
498
|
+
return (c) => {
|
|
499
|
+
const ctx = c;
|
|
500
|
+
ctx.services = services;
|
|
501
|
+
if (needsParams) {
|
|
502
|
+
const params = c.req.param();
|
|
503
|
+
if (!paramsValidator(params)) {
|
|
504
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
505
|
+
}
|
|
506
|
+
ctx.params = params;
|
|
507
|
+
}
|
|
508
|
+
if (needsQuery) {
|
|
509
|
+
const query = c.req.query();
|
|
510
|
+
if (!queryValidator(query)) {
|
|
511
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
512
|
+
}
|
|
513
|
+
ctx.query = query;
|
|
514
|
+
}
|
|
515
|
+
const result = userHandler(c);
|
|
516
|
+
if (result instanceof Promise) {
|
|
517
|
+
return result.then((res) => {
|
|
518
|
+
if (res instanceof Response) return res;
|
|
519
|
+
return new Response(JSON.stringify(res), RESPONSE_INIT_200);
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (result instanceof Response) return result;
|
|
523
|
+
return new Response(JSON.stringify(result), RESPONSE_INIT_200);
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (needsSerialize) {
|
|
528
|
+
return async (c) => {
|
|
529
|
+
try {
|
|
530
|
+
const ctx = c;
|
|
531
|
+
ctx.services = services;
|
|
532
|
+
if (needsParams) {
|
|
533
|
+
const params = c.req.param();
|
|
534
|
+
if (!paramsValidator(params)) {
|
|
535
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
536
|
+
}
|
|
537
|
+
ctx.params = params;
|
|
538
|
+
}
|
|
539
|
+
if (needsQuery) {
|
|
540
|
+
const query = c.req.query();
|
|
541
|
+
if (!queryValidator(query)) {
|
|
542
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
543
|
+
}
|
|
544
|
+
ctx.query = query;
|
|
545
|
+
}
|
|
546
|
+
if (needsBody) {
|
|
547
|
+
const rawBody = await c.req.json().catch(() => ({}));
|
|
548
|
+
if (!bodyValidator(rawBody)) {
|
|
549
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
550
|
+
}
|
|
551
|
+
ctx.body = rawBody;
|
|
552
|
+
}
|
|
553
|
+
const result = await userHandler(c);
|
|
554
|
+
if (result instanceof Response) return result;
|
|
555
|
+
return new Response(serializer(result), RESPONSE_INIT_200);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
console.error("[Kozo] Handler error:", err);
|
|
558
|
+
return new Response(ERROR_INTERNAL, RESPONSE_INIT_500);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
} else {
|
|
562
|
+
return async (c) => {
|
|
563
|
+
try {
|
|
564
|
+
const ctx = c;
|
|
565
|
+
ctx.services = services;
|
|
566
|
+
if (needsParams) {
|
|
567
|
+
const params = c.req.param();
|
|
568
|
+
if (!paramsValidator(params)) {
|
|
569
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
570
|
+
}
|
|
571
|
+
ctx.params = params;
|
|
572
|
+
}
|
|
573
|
+
if (needsQuery) {
|
|
574
|
+
const query = c.req.query();
|
|
575
|
+
if (!queryValidator(query)) {
|
|
576
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
577
|
+
}
|
|
578
|
+
ctx.query = query;
|
|
579
|
+
}
|
|
580
|
+
if (needsBody) {
|
|
581
|
+
const rawBody = await c.req.json().catch(() => ({}));
|
|
582
|
+
if (!bodyValidator(rawBody)) {
|
|
583
|
+
return new Response(ERROR_INVALID, RESPONSE_INIT_400);
|
|
584
|
+
}
|
|
585
|
+
ctx.body = rawBody;
|
|
586
|
+
}
|
|
587
|
+
const result = await userHandler(c);
|
|
588
|
+
if (result instanceof Response) return result;
|
|
589
|
+
return new Response(JSON.stringify(result), RESPONSE_INIT_200);
|
|
590
|
+
} catch (err) {
|
|
591
|
+
console.error("[Kozo] Handler error:", err);
|
|
592
|
+
return new Response(ERROR_INTERNAL, RESPONSE_INIT_500);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/app.ts
|
|
599
|
+
var ajv2 = new Ajv3({
|
|
600
|
+
removeAdditional: "all",
|
|
601
|
+
useDefaults: true,
|
|
602
|
+
coerceTypes: true
|
|
603
|
+
});
|
|
604
|
+
addFormats3(ajv2);
|
|
605
|
+
var isBun = typeof Bun !== "undefined";
|
|
606
|
+
if (isBun) {
|
|
607
|
+
try {
|
|
608
|
+
if (typeof Bun !== "undefined" && Bun.json) {
|
|
609
|
+
globalThis.JSON = {
|
|
610
|
+
parse: Bun.json.parse,
|
|
611
|
+
stringify: Bun.json.stringify
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
} catch (e) {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
var Kozo = class {
|
|
618
|
+
app;
|
|
619
|
+
services;
|
|
620
|
+
routes = [];
|
|
621
|
+
constructor(config = {}) {
|
|
622
|
+
this.app = new Hono();
|
|
623
|
+
this.services = config.services || {};
|
|
624
|
+
}
|
|
625
|
+
// Plugin system
|
|
626
|
+
use(plugin) {
|
|
627
|
+
plugin.install(this);
|
|
628
|
+
return this;
|
|
629
|
+
}
|
|
630
|
+
generateClient(baseUrlOrOptions) {
|
|
631
|
+
const options = typeof baseUrlOrOptions === "string" ? { baseUrl: baseUrlOrOptions, includeValidation: false } : baseUrlOrOptions || {};
|
|
632
|
+
const routeInfos = this.routes.map((r) => ({
|
|
633
|
+
method: r.method,
|
|
634
|
+
path: r.path,
|
|
635
|
+
schema: r.schema
|
|
636
|
+
}));
|
|
637
|
+
return generateTypedClient(routeInfos, options);
|
|
638
|
+
}
|
|
639
|
+
get(path, schema, handler) {
|
|
640
|
+
this.register("get", path, schema, handler);
|
|
641
|
+
}
|
|
642
|
+
post(path, schema, handler) {
|
|
643
|
+
this.register("post", path, schema, handler);
|
|
644
|
+
}
|
|
645
|
+
put(path, schema, handler) {
|
|
646
|
+
this.register("put", path, schema, handler);
|
|
647
|
+
}
|
|
648
|
+
patch(path, schema, handler) {
|
|
649
|
+
this.register("patch", path, schema, handler);
|
|
650
|
+
}
|
|
651
|
+
delete(path, schema, handler) {
|
|
652
|
+
this.register("delete", path, schema, handler);
|
|
653
|
+
}
|
|
654
|
+
register(method, path, schema, handler) {
|
|
655
|
+
this.routes.push({ method, path, schema });
|
|
656
|
+
const compiled = SchemaCompiler.compile(schema);
|
|
657
|
+
const optimizedHandler = compileRouteHandler(
|
|
658
|
+
handler,
|
|
659
|
+
schema,
|
|
660
|
+
this.services,
|
|
661
|
+
compiled
|
|
662
|
+
);
|
|
663
|
+
this.app[method](path, optimizedHandler);
|
|
664
|
+
}
|
|
665
|
+
async listen(port) {
|
|
666
|
+
const finalPort = port || 3e3;
|
|
667
|
+
serve({
|
|
668
|
+
fetch: this.app.fetch,
|
|
669
|
+
port: finalPort
|
|
670
|
+
});
|
|
671
|
+
console.log(`\u{1F680} Kozo server listening on http://localhost:${finalPort}`);
|
|
672
|
+
}
|
|
673
|
+
getApp() {
|
|
674
|
+
return this.app;
|
|
675
|
+
}
|
|
676
|
+
get fetch() {
|
|
677
|
+
return this.app.fetch;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
function createKozo(config) {
|
|
681
|
+
return new Kozo(config);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/index.ts
|
|
685
|
+
import { z } from "zod";
|
|
686
|
+
export {
|
|
687
|
+
Kozo,
|
|
688
|
+
SchemaCompiler,
|
|
689
|
+
compileRouteHandler,
|
|
690
|
+
createKozo,
|
|
691
|
+
generateTypedClient,
|
|
692
|
+
z
|
|
693
|
+
};
|
|
694
|
+
//# sourceMappingURL=index.js.map
|