@kozojs/core 0.3.0 → 0.3.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/lib/index.d.ts +652 -0
- package/lib/index.js +207 -34
- 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 +9 -2
- 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() {
|
|
@@ -1875,8 +1858,8 @@ function createInflightTracker() {
|
|
|
1875
1858
|
function trackRequest(tracker) {
|
|
1876
1859
|
tracker.count++;
|
|
1877
1860
|
let resolvePromise;
|
|
1878
|
-
const promise = new Promise((
|
|
1879
|
-
resolvePromise =
|
|
1861
|
+
const promise = new Promise((resolve2) => {
|
|
1862
|
+
resolvePromise = resolve2;
|
|
1880
1863
|
});
|
|
1881
1864
|
tracker.requests.add(promise);
|
|
1882
1865
|
return () => {
|
|
@@ -1971,10 +1954,10 @@ var ShutdownManager = class {
|
|
|
1971
1954
|
});
|
|
1972
1955
|
}
|
|
1973
1956
|
const drainPromise = this.drainRequests();
|
|
1974
|
-
const timeoutPromise = new Promise((
|
|
1957
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
1975
1958
|
setTimeout(() => {
|
|
1976
1959
|
onShutdownTimeout?.(this.tracker.count);
|
|
1977
|
-
|
|
1960
|
+
resolve2();
|
|
1978
1961
|
}, timeoutMs);
|
|
1979
1962
|
});
|
|
1980
1963
|
await Promise.race([drainPromise, timeoutPromise]);
|
|
@@ -2715,23 +2698,212 @@ function generateSwaggerHtml(specUrl, title = "API Documentation") {
|
|
|
2715
2698
|
function createOpenAPIGenerator(config) {
|
|
2716
2699
|
return new OpenAPIGenerator(config);
|
|
2717
2700
|
}
|
|
2701
|
+
|
|
2702
|
+
// src/middleware/logger.ts
|
|
2703
|
+
function logger(options = {}) {
|
|
2704
|
+
const { prefix = "\u{1F310}", colorize = true } = options;
|
|
2705
|
+
return async (c, next) => {
|
|
2706
|
+
const start = Date.now();
|
|
2707
|
+
const method = c.req.method;
|
|
2708
|
+
const path = new URL(c.req.url).pathname;
|
|
2709
|
+
await next();
|
|
2710
|
+
const duration = Date.now() - start;
|
|
2711
|
+
const status = c.res.status;
|
|
2712
|
+
const statusColor = status >= 500 ? "\u{1F534}" : status >= 400 ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
2713
|
+
const log = `${prefix} ${method.padEnd(6)} ${path} ${statusColor} ${status} ${duration}ms`;
|
|
2714
|
+
console.log(log);
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// src/middleware/cors.ts
|
|
2719
|
+
import { cors as honoCors } from "hono/cors";
|
|
2720
|
+
function cors(options = {}) {
|
|
2721
|
+
return honoCors({
|
|
2722
|
+
origin: options.origin || "*",
|
|
2723
|
+
allowMethods: options.allowMethods || ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
2724
|
+
allowHeaders: options.allowHeaders || ["Content-Type", "Authorization"],
|
|
2725
|
+
exposeHeaders: options.exposeHeaders || [],
|
|
2726
|
+
maxAge: options.maxAge || 86400,
|
|
2727
|
+
credentials: options.credentials || false
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// src/middleware/rate-limit.ts
|
|
2732
|
+
var store = /* @__PURE__ */ new Map();
|
|
2733
|
+
function rateLimit(options) {
|
|
2734
|
+
const {
|
|
2735
|
+
max = 100,
|
|
2736
|
+
window = 60,
|
|
2737
|
+
keyGenerator = (c) => c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip") ?? "anonymous",
|
|
2738
|
+
message = "Too many requests"
|
|
2739
|
+
} = options;
|
|
2740
|
+
return async (c, next) => {
|
|
2741
|
+
const key = keyGenerator(c);
|
|
2742
|
+
const now = Date.now();
|
|
2743
|
+
const windowMs = window * 1e3;
|
|
2744
|
+
let record = store.get(key);
|
|
2745
|
+
if (!record || now > record.resetAt) {
|
|
2746
|
+
record = { count: 0, resetAt: now + windowMs };
|
|
2747
|
+
}
|
|
2748
|
+
record.count++;
|
|
2749
|
+
store.set(key, record);
|
|
2750
|
+
c.header("X-RateLimit-Limit", String(max));
|
|
2751
|
+
c.header("X-RateLimit-Remaining", String(Math.max(0, max - record.count)));
|
|
2752
|
+
c.header("X-RateLimit-Reset", String(Math.ceil(record.resetAt / 1e3)));
|
|
2753
|
+
if (record.count > max) {
|
|
2754
|
+
return c.json({ error: message }, 429);
|
|
2755
|
+
}
|
|
2756
|
+
await next();
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
function clearRateLimitStore() {
|
|
2760
|
+
store.clear();
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/middleware/error-handler.ts
|
|
2764
|
+
var HttpError = class extends Error {
|
|
2765
|
+
constructor(statusCode, message, details) {
|
|
2766
|
+
super(message);
|
|
2767
|
+
this.statusCode = statusCode;
|
|
2768
|
+
this.details = details;
|
|
2769
|
+
this.name = "HttpError";
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
var BadRequestError = class extends HttpError {
|
|
2773
|
+
constructor(message = "Bad Request", details) {
|
|
2774
|
+
super(400, message, details);
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
var UnauthorizedError2 = class extends HttpError {
|
|
2778
|
+
constructor(message = "Unauthorized") {
|
|
2779
|
+
super(401, message);
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
var ForbiddenError2 = class extends HttpError {
|
|
2783
|
+
constructor(message = "Forbidden") {
|
|
2784
|
+
super(403, message);
|
|
2785
|
+
}
|
|
2786
|
+
};
|
|
2787
|
+
var NotFoundError2 = class extends HttpError {
|
|
2788
|
+
constructor(message = "Not Found") {
|
|
2789
|
+
super(404, message);
|
|
2790
|
+
}
|
|
2791
|
+
};
|
|
2792
|
+
var ConflictError = class extends HttpError {
|
|
2793
|
+
constructor(message = "Conflict", details) {
|
|
2794
|
+
super(409, message, details);
|
|
2795
|
+
}
|
|
2796
|
+
};
|
|
2797
|
+
var InternalServerError = class extends HttpError {
|
|
2798
|
+
constructor(message = "Internal Server Error") {
|
|
2799
|
+
super(500, message);
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
function errorHandler() {
|
|
2803
|
+
return async (c, next) => {
|
|
2804
|
+
try {
|
|
2805
|
+
await next();
|
|
2806
|
+
} catch (err) {
|
|
2807
|
+
if (err instanceof HttpError) {
|
|
2808
|
+
return c.json({
|
|
2809
|
+
error: err.message,
|
|
2810
|
+
status: err.statusCode,
|
|
2811
|
+
...err.details ? { details: err.details } : {}
|
|
2812
|
+
}, err.statusCode);
|
|
2813
|
+
}
|
|
2814
|
+
console.error("Unhandled error:", err);
|
|
2815
|
+
return c.json({
|
|
2816
|
+
error: "Internal Server Error",
|
|
2817
|
+
status: 500
|
|
2818
|
+
}, 500);
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// src/middleware/fileSystemRouting.ts
|
|
2824
|
+
import { readFile } from "fs/promises";
|
|
2825
|
+
import { resolve } from "path";
|
|
2826
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2827
|
+
async function readManifest(manifestPath, onMissing) {
|
|
2828
|
+
try {
|
|
2829
|
+
const raw = await readFile(manifestPath, "utf-8");
|
|
2830
|
+
return JSON.parse(raw);
|
|
2831
|
+
} catch (err) {
|
|
2832
|
+
onMissing(err instanceof Error ? err : new Error(String(err)));
|
|
2833
|
+
return null;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
async function importHandler(handlerPath) {
|
|
2837
|
+
try {
|
|
2838
|
+
const url = handlerPath.startsWith("file://") ? handlerPath : pathToFileURL2(handlerPath).href;
|
|
2839
|
+
const mod = await import(url);
|
|
2840
|
+
if (typeof mod.default !== "function") {
|
|
2841
|
+
console.warn(
|
|
2842
|
+
`[kozo:fsr] Skipping ${handlerPath}: no default export function`
|
|
2843
|
+
);
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
return mod.default;
|
|
2847
|
+
} catch (err) {
|
|
2848
|
+
console.warn(
|
|
2849
|
+
`[kozo:fsr] Failed to import handler ${handlerPath}:`,
|
|
2850
|
+
err.message
|
|
2851
|
+
);
|
|
2852
|
+
return null;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
async function applyFileSystemRouting(app, options = {}) {
|
|
2856
|
+
const {
|
|
2857
|
+
manifestPath = resolve(process.cwd(), "routes-manifest.json"),
|
|
2858
|
+
verbose = false,
|
|
2859
|
+
onMissingManifest = () => {
|
|
2860
|
+
},
|
|
2861
|
+
logger: logger2 = console.log
|
|
2862
|
+
} = options;
|
|
2863
|
+
const manifest = await readManifest(manifestPath, onMissingManifest);
|
|
2864
|
+
if (!manifest) return;
|
|
2865
|
+
const log = logger2;
|
|
2866
|
+
if (verbose) {
|
|
2867
|
+
log(
|
|
2868
|
+
`
|
|
2869
|
+
\u{1F4CB} [kozo:fsr] Loading ${manifest.routes.length} route(s) from manifest
|
|
2870
|
+
`
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
for (const route of manifest.routes) {
|
|
2874
|
+
const handler = await importHandler(route.handler);
|
|
2875
|
+
if (!handler) continue;
|
|
2876
|
+
app[route.method](route.path, handler);
|
|
2877
|
+
if (verbose) {
|
|
2878
|
+
log(
|
|
2879
|
+
` ${route.method.toUpperCase().padEnd(6)} ${route.path} \u2192 ${route.handler}`
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (verbose) {
|
|
2884
|
+
log("");
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
function createFileSystemRouting(options = {}) {
|
|
2888
|
+
return (app) => applyFileSystemRouting(app, options);
|
|
2889
|
+
}
|
|
2718
2890
|
export {
|
|
2719
2891
|
ERROR_RESPONSES,
|
|
2720
|
-
|
|
2892
|
+
ForbiddenError,
|
|
2721
2893
|
BadRequestError as HttpBadRequestError,
|
|
2722
2894
|
ConflictError as HttpConflictError,
|
|
2723
2895
|
HttpError,
|
|
2724
|
-
|
|
2896
|
+
ForbiddenError2 as HttpForbiddenError,
|
|
2725
2897
|
InternalServerError as HttpInternalServerError,
|
|
2726
|
-
|
|
2727
|
-
|
|
2898
|
+
NotFoundError2 as HttpNotFoundError,
|
|
2899
|
+
UnauthorizedError2 as HttpUnauthorizedError,
|
|
2728
2900
|
Kozo,
|
|
2729
2901
|
KozoError,
|
|
2730
|
-
|
|
2902
|
+
NotFoundError,
|
|
2731
2903
|
OpenAPIGenerator,
|
|
2732
2904
|
SchemaCompiler,
|
|
2733
2905
|
ShutdownManager,
|
|
2734
|
-
|
|
2906
|
+
UnauthorizedError,
|
|
2735
2907
|
ValidationFailedError,
|
|
2736
2908
|
applyFileSystemRouting,
|
|
2737
2909
|
buildNativeContext,
|
|
@@ -2767,3 +2939,4 @@ export {
|
|
|
2767
2939
|
validationErrorResponse,
|
|
2768
2940
|
z
|
|
2769
2941
|
};
|
|
2942
|
+
//# sourceMappingURL=index.js.map
|