@kozojs/core 0.3.1 → 0.3.3
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/lib/index.d.ts +657 -0
- package/lib/index.js +235 -51
- package/lib/index.js.map +1 -0
- package/lib/middleware/index.d.ts +160 -0
- package/lib/middleware/index.js +189 -16
- package/lib/middleware/index.js.map +1 -0
- package/package.json +2 -4
- package/lib/chunk-W44TTZNJ.js +0 -205
package/lib/index.js
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BadRequestError,
|
|
3
|
-
ConflictError,
|
|
4
|
-
ForbiddenError,
|
|
5
|
-
HttpError,
|
|
6
|
-
InternalServerError,
|
|
7
|
-
NotFoundError,
|
|
8
|
-
UnauthorizedError,
|
|
9
|
-
applyFileSystemRouting,
|
|
10
|
-
clearRateLimitStore,
|
|
11
|
-
cors,
|
|
12
|
-
createFileSystemRouting,
|
|
13
|
-
errorHandler,
|
|
14
|
-
logger,
|
|
15
|
-
rateLimit
|
|
16
|
-
} from "./chunk-W44TTZNJ.js";
|
|
17
|
-
|
|
18
1
|
// src/app.ts
|
|
19
2
|
import { Hono } from "hono/quick";
|
|
20
3
|
import { serve } from "@hono/node-server";
|
|
@@ -404,19 +387,19 @@ var ValidationFailedError = class extends KozoError {
|
|
|
404
387
|
return new Response(JSON.stringify(body), INIT_400);
|
|
405
388
|
}
|
|
406
389
|
};
|
|
407
|
-
var
|
|
390
|
+
var NotFoundError = class extends KozoError {
|
|
408
391
|
constructor(message = "Resource Not Found") {
|
|
409
392
|
super(message, 404, "not-found");
|
|
410
393
|
this.name = "NotFoundError";
|
|
411
394
|
}
|
|
412
395
|
};
|
|
413
|
-
var
|
|
396
|
+
var UnauthorizedError = class extends KozoError {
|
|
414
397
|
constructor(message = "Unauthorized") {
|
|
415
398
|
super(message, 401, "unauthorized");
|
|
416
399
|
this.name = "UnauthorizedError";
|
|
417
400
|
}
|
|
418
401
|
};
|
|
419
|
-
var
|
|
402
|
+
var ForbiddenError = class extends KozoError {
|
|
420
403
|
constructor(message = "Forbidden") {
|
|
421
404
|
super(message, 403, "forbidden");
|
|
422
405
|
this.name = "ForbiddenError";
|
|
@@ -517,11 +500,11 @@ function uwsWrite404(uwsRes) {
|
|
|
517
500
|
});
|
|
518
501
|
}
|
|
519
502
|
function getFreePort() {
|
|
520
|
-
return new Promise((
|
|
503
|
+
return new Promise((resolve2, reject) => {
|
|
521
504
|
const srv = netCreateServer();
|
|
522
505
|
srv.listen(0, "0.0.0.0", () => {
|
|
523
506
|
const port = srv.address().port;
|
|
524
|
-
srv.close((err) => err ? reject(err) :
|
|
507
|
+
srv.close((err) => err ? reject(err) : resolve2(port));
|
|
525
508
|
});
|
|
526
509
|
});
|
|
527
510
|
}
|
|
@@ -538,7 +521,7 @@ async function createUwsServer(opts) {
|
|
|
538
521
|
const { uws, routes } = opts;
|
|
539
522
|
const port = opts.port === 0 ? await getFreePort() : opts.port;
|
|
540
523
|
const emptyParams = Object.freeze({});
|
|
541
|
-
return new Promise((
|
|
524
|
+
return new Promise((resolve2, reject) => {
|
|
542
525
|
const uwsApp = uws.App();
|
|
543
526
|
for (const route of routes) {
|
|
544
527
|
const fn = UWS_METHOD[route.method];
|
|
@@ -606,7 +589,7 @@ async function createUwsServer(opts) {
|
|
|
606
589
|
return;
|
|
607
590
|
}
|
|
608
591
|
listenToken = token;
|
|
609
|
-
|
|
592
|
+
resolve2({
|
|
610
593
|
port,
|
|
611
594
|
server: {
|
|
612
595
|
close() {
|
|
@@ -620,8 +603,6 @@ async function createUwsServer(opts) {
|
|
|
620
603
|
|
|
621
604
|
// src/compiler.ts
|
|
622
605
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
623
|
-
import Ajv from "ajv";
|
|
624
|
-
import addFormats from "ajv-formats";
|
|
625
606
|
import fastJson from "fast-json-stringify";
|
|
626
607
|
|
|
627
608
|
// src/fast-response.ts
|
|
@@ -744,12 +725,28 @@ function fastWriteError(err, res) {
|
|
|
744
725
|
}
|
|
745
726
|
|
|
746
727
|
// src/compiler.ts
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
728
|
+
function makeZValidator(schema) {
|
|
729
|
+
const fn = function(data) {
|
|
730
|
+
const r = schema.safeParse(data);
|
|
731
|
+
fn.errors = null;
|
|
732
|
+
if (r.success) {
|
|
733
|
+
if (data !== null && typeof data === "object" && !Array.isArray(data)) {
|
|
734
|
+
const d = data;
|
|
735
|
+
const rd = r.data;
|
|
736
|
+
for (const k of Object.keys(d)) if (!(k in rd)) delete d[k];
|
|
737
|
+
Object.assign(d, rd);
|
|
738
|
+
}
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
fn.errors = r.error.issues.map((i) => ({
|
|
742
|
+
instancePath: i.path.length ? "/" + i.path.join("/") : "",
|
|
743
|
+
message: i.message
|
|
744
|
+
}));
|
|
745
|
+
return false;
|
|
746
|
+
};
|
|
747
|
+
fn.errors = null;
|
|
748
|
+
return fn;
|
|
749
|
+
}
|
|
753
750
|
function isZodSchema(schema) {
|
|
754
751
|
return typeof schema === "object" && schema !== null && "safeParse" in schema;
|
|
755
752
|
}
|
|
@@ -768,17 +765,14 @@ function toJsonBody(result) {
|
|
|
768
765
|
var SchemaCompiler = class {
|
|
769
766
|
static compile(schema) {
|
|
770
767
|
const compiled = {};
|
|
771
|
-
if (schema.body) {
|
|
772
|
-
|
|
773
|
-
compiled.validateBody = ajv.compile(jsonSchema);
|
|
768
|
+
if (schema.body && isZodSchema(schema.body)) {
|
|
769
|
+
compiled.validateBody = makeZValidator(schema.body);
|
|
774
770
|
}
|
|
775
|
-
if (schema.query) {
|
|
776
|
-
|
|
777
|
-
compiled.validateQuery = ajv.compile(jsonSchema);
|
|
771
|
+
if (schema.query && isZodSchema(schema.query)) {
|
|
772
|
+
compiled.validateQuery = makeZValidator(schema.query);
|
|
778
773
|
}
|
|
779
|
-
if (schema.params) {
|
|
780
|
-
|
|
781
|
-
compiled.validateParams = ajv.compile(jsonSchema);
|
|
774
|
+
if (schema.params && isZodSchema(schema.params)) {
|
|
775
|
+
compiled.validateParams = makeZValidator(schema.params);
|
|
782
776
|
}
|
|
783
777
|
if (schema.response) {
|
|
784
778
|
if (typeof schema.response === "object" && !isZodSchema(schema.response)) {
|
|
@@ -1875,8 +1869,8 @@ function createInflightTracker() {
|
|
|
1875
1869
|
function trackRequest(tracker) {
|
|
1876
1870
|
tracker.count++;
|
|
1877
1871
|
let resolvePromise;
|
|
1878
|
-
const promise = new Promise((
|
|
1879
|
-
resolvePromise =
|
|
1872
|
+
const promise = new Promise((resolve2) => {
|
|
1873
|
+
resolvePromise = resolve2;
|
|
1880
1874
|
});
|
|
1881
1875
|
tracker.requests.add(promise);
|
|
1882
1876
|
return () => {
|
|
@@ -1971,10 +1965,10 @@ var ShutdownManager = class {
|
|
|
1971
1965
|
});
|
|
1972
1966
|
}
|
|
1973
1967
|
const drainPromise = this.drainRequests();
|
|
1974
|
-
const timeoutPromise = new Promise((
|
|
1968
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
1975
1969
|
setTimeout(() => {
|
|
1976
1970
|
onShutdownTimeout?.(this.tracker.count);
|
|
1977
|
-
|
|
1971
|
+
resolve2();
|
|
1978
1972
|
}, timeoutMs);
|
|
1979
1973
|
});
|
|
1980
1974
|
await Promise.race([drainPromise, timeoutPromise]);
|
|
@@ -2715,23 +2709,212 @@ function generateSwaggerHtml(specUrl, title = "API Documentation") {
|
|
|
2715
2709
|
function createOpenAPIGenerator(config) {
|
|
2716
2710
|
return new OpenAPIGenerator(config);
|
|
2717
2711
|
}
|
|
2712
|
+
|
|
2713
|
+
// src/middleware/logger.ts
|
|
2714
|
+
function logger(options = {}) {
|
|
2715
|
+
const { prefix = "\u{1F310}", colorize = true } = options;
|
|
2716
|
+
return async (c, next) => {
|
|
2717
|
+
const start = Date.now();
|
|
2718
|
+
const method = c.req.method;
|
|
2719
|
+
const path = new URL(c.req.url).pathname;
|
|
2720
|
+
await next();
|
|
2721
|
+
const duration = Date.now() - start;
|
|
2722
|
+
const status = c.res.status;
|
|
2723
|
+
const statusColor = status >= 500 ? "\u{1F534}" : status >= 400 ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
2724
|
+
const log = `${prefix} ${method.padEnd(6)} ${path} ${statusColor} ${status} ${duration}ms`;
|
|
2725
|
+
console.log(log);
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// src/middleware/cors.ts
|
|
2730
|
+
import { cors as honoCors } from "hono/cors";
|
|
2731
|
+
function cors(options = {}) {
|
|
2732
|
+
return honoCors({
|
|
2733
|
+
origin: options.origin || "*",
|
|
2734
|
+
allowMethods: options.allowMethods || ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
2735
|
+
allowHeaders: options.allowHeaders || ["Content-Type", "Authorization"],
|
|
2736
|
+
exposeHeaders: options.exposeHeaders || [],
|
|
2737
|
+
maxAge: options.maxAge || 86400,
|
|
2738
|
+
credentials: options.credentials || false
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// src/middleware/rate-limit.ts
|
|
2743
|
+
var store = /* @__PURE__ */ new Map();
|
|
2744
|
+
function rateLimit(options) {
|
|
2745
|
+
const {
|
|
2746
|
+
max = 100,
|
|
2747
|
+
window = 60,
|
|
2748
|
+
keyGenerator = (c) => c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip") ?? "anonymous",
|
|
2749
|
+
message = "Too many requests"
|
|
2750
|
+
} = options;
|
|
2751
|
+
return async (c, next) => {
|
|
2752
|
+
const key = keyGenerator(c);
|
|
2753
|
+
const now = Date.now();
|
|
2754
|
+
const windowMs = window * 1e3;
|
|
2755
|
+
let record = store.get(key);
|
|
2756
|
+
if (!record || now > record.resetAt) {
|
|
2757
|
+
record = { count: 0, resetAt: now + windowMs };
|
|
2758
|
+
}
|
|
2759
|
+
record.count++;
|
|
2760
|
+
store.set(key, record);
|
|
2761
|
+
c.header("X-RateLimit-Limit", String(max));
|
|
2762
|
+
c.header("X-RateLimit-Remaining", String(Math.max(0, max - record.count)));
|
|
2763
|
+
c.header("X-RateLimit-Reset", String(Math.ceil(record.resetAt / 1e3)));
|
|
2764
|
+
if (record.count > max) {
|
|
2765
|
+
return c.json({ error: message }, 429);
|
|
2766
|
+
}
|
|
2767
|
+
await next();
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
function clearRateLimitStore() {
|
|
2771
|
+
store.clear();
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// src/middleware/error-handler.ts
|
|
2775
|
+
var HttpError = class extends Error {
|
|
2776
|
+
constructor(statusCode, message, details) {
|
|
2777
|
+
super(message);
|
|
2778
|
+
this.statusCode = statusCode;
|
|
2779
|
+
this.details = details;
|
|
2780
|
+
this.name = "HttpError";
|
|
2781
|
+
}
|
|
2782
|
+
};
|
|
2783
|
+
var BadRequestError = class extends HttpError {
|
|
2784
|
+
constructor(message = "Bad Request", details) {
|
|
2785
|
+
super(400, message, details);
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
var UnauthorizedError2 = class extends HttpError {
|
|
2789
|
+
constructor(message = "Unauthorized") {
|
|
2790
|
+
super(401, message);
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
var ForbiddenError2 = class extends HttpError {
|
|
2794
|
+
constructor(message = "Forbidden") {
|
|
2795
|
+
super(403, message);
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
var NotFoundError2 = class extends HttpError {
|
|
2799
|
+
constructor(message = "Not Found") {
|
|
2800
|
+
super(404, message);
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2803
|
+
var ConflictError = class extends HttpError {
|
|
2804
|
+
constructor(message = "Conflict", details) {
|
|
2805
|
+
super(409, message, details);
|
|
2806
|
+
}
|
|
2807
|
+
};
|
|
2808
|
+
var InternalServerError = class extends HttpError {
|
|
2809
|
+
constructor(message = "Internal Server Error") {
|
|
2810
|
+
super(500, message);
|
|
2811
|
+
}
|
|
2812
|
+
};
|
|
2813
|
+
function errorHandler() {
|
|
2814
|
+
return async (c, next) => {
|
|
2815
|
+
try {
|
|
2816
|
+
await next();
|
|
2817
|
+
} catch (err) {
|
|
2818
|
+
if (err instanceof HttpError) {
|
|
2819
|
+
return c.json({
|
|
2820
|
+
error: err.message,
|
|
2821
|
+
status: err.statusCode,
|
|
2822
|
+
...err.details ? { details: err.details } : {}
|
|
2823
|
+
}, err.statusCode);
|
|
2824
|
+
}
|
|
2825
|
+
console.error("Unhandled error:", err);
|
|
2826
|
+
return c.json({
|
|
2827
|
+
error: "Internal Server Error",
|
|
2828
|
+
status: 500
|
|
2829
|
+
}, 500);
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
// src/middleware/fileSystemRouting.ts
|
|
2835
|
+
import { readFile } from "fs/promises";
|
|
2836
|
+
import { resolve } from "path";
|
|
2837
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2838
|
+
async function readManifest(manifestPath, onMissing) {
|
|
2839
|
+
try {
|
|
2840
|
+
const raw = await readFile(manifestPath, "utf-8");
|
|
2841
|
+
return JSON.parse(raw);
|
|
2842
|
+
} catch (err) {
|
|
2843
|
+
onMissing(err instanceof Error ? err : new Error(String(err)));
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
async function importHandler(handlerPath) {
|
|
2848
|
+
try {
|
|
2849
|
+
const url = handlerPath.startsWith("file://") ? handlerPath : pathToFileURL2(handlerPath).href;
|
|
2850
|
+
const mod = await import(url);
|
|
2851
|
+
if (typeof mod.default !== "function") {
|
|
2852
|
+
console.warn(
|
|
2853
|
+
`[kozo:fsr] Skipping ${handlerPath}: no default export function`
|
|
2854
|
+
);
|
|
2855
|
+
return null;
|
|
2856
|
+
}
|
|
2857
|
+
return mod.default;
|
|
2858
|
+
} catch (err) {
|
|
2859
|
+
console.warn(
|
|
2860
|
+
`[kozo:fsr] Failed to import handler ${handlerPath}:`,
|
|
2861
|
+
err.message
|
|
2862
|
+
);
|
|
2863
|
+
return null;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
async function applyFileSystemRouting(app, options = {}) {
|
|
2867
|
+
const {
|
|
2868
|
+
manifestPath = resolve(process.cwd(), "routes-manifest.json"),
|
|
2869
|
+
verbose = false,
|
|
2870
|
+
onMissingManifest = () => {
|
|
2871
|
+
},
|
|
2872
|
+
logger: logger2 = console.log
|
|
2873
|
+
} = options;
|
|
2874
|
+
const manifest = await readManifest(manifestPath, onMissingManifest);
|
|
2875
|
+
if (!manifest) return;
|
|
2876
|
+
const log = logger2;
|
|
2877
|
+
if (verbose) {
|
|
2878
|
+
log(
|
|
2879
|
+
`
|
|
2880
|
+
\u{1F4CB} [kozo:fsr] Loading ${manifest.routes.length} route(s) from manifest
|
|
2881
|
+
`
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
for (const route of manifest.routes) {
|
|
2885
|
+
const handler = await importHandler(route.handler);
|
|
2886
|
+
if (!handler) continue;
|
|
2887
|
+
app[route.method](route.path, handler);
|
|
2888
|
+
if (verbose) {
|
|
2889
|
+
log(
|
|
2890
|
+
` ${route.method.toUpperCase().padEnd(6)} ${route.path} \u2192 ${route.handler}`
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
if (verbose) {
|
|
2895
|
+
log("");
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
function createFileSystemRouting(options = {}) {
|
|
2899
|
+
return (app) => applyFileSystemRouting(app, options);
|
|
2900
|
+
}
|
|
2718
2901
|
export {
|
|
2719
2902
|
ERROR_RESPONSES,
|
|
2720
|
-
|
|
2903
|
+
ForbiddenError,
|
|
2721
2904
|
BadRequestError as HttpBadRequestError,
|
|
2722
2905
|
ConflictError as HttpConflictError,
|
|
2723
2906
|
HttpError,
|
|
2724
|
-
|
|
2907
|
+
ForbiddenError2 as HttpForbiddenError,
|
|
2725
2908
|
InternalServerError as HttpInternalServerError,
|
|
2726
|
-
|
|
2727
|
-
|
|
2909
|
+
NotFoundError2 as HttpNotFoundError,
|
|
2910
|
+
UnauthorizedError2 as HttpUnauthorizedError,
|
|
2728
2911
|
Kozo,
|
|
2729
2912
|
KozoError,
|
|
2730
|
-
|
|
2913
|
+
NotFoundError,
|
|
2731
2914
|
OpenAPIGenerator,
|
|
2732
2915
|
SchemaCompiler,
|
|
2733
2916
|
ShutdownManager,
|
|
2734
|
-
|
|
2917
|
+
UnauthorizedError,
|
|
2735
2918
|
ValidationFailedError,
|
|
2736
2919
|
applyFileSystemRouting,
|
|
2737
2920
|
buildNativeContext,
|
|
@@ -2767,3 +2950,4 @@ export {
|
|
|
2767
2950
|
validationErrorResponse,
|
|
2768
2951
|
z
|
|
2769
2952
|
};
|
|
2953
|
+
//# sourceMappingURL=index.js.map
|