kilatjs 0.1.4 → 0.1.6
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 +75 -0
- package/dist/cli.js +169 -56
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +356 -0
- package/dist/core/kilat.d.ts +65 -0
- package/dist/core/kilat.js +172 -0
- package/dist/core/router.d.ts +5 -0
- package/dist/core/router.js +918 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.js +33 -0
- package/dist/index.js +511 -356
- package/dist/middleware/cors.d.ts +13 -0
- package/dist/middleware/cors.js +65 -0
- package/dist/middleware/jwt.d.ts +11 -0
- package/dist/middleware/jwt.js +69 -0
- package/dist/middleware/swagger.d.ts +12 -0
- package/dist/middleware/swagger.js +156 -0
- package/package.json +7 -2
- package/dist/core/vite-router.d.ts +0 -34
- package/dist/server/dev-server.d.ts +0 -0
- package/dist/server/vite-dev.d.ts +0 -38
- package/dist/server/vite-server.d.ts +0 -17
package/README.md
CHANGED
|
@@ -321,6 +321,41 @@ export async function POST(ctx: RouteContext) {
|
|
|
321
321
|
headers: { "Content-Type": "application/json" },
|
|
322
322
|
});
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* NEW: Standalone API Mode with Swagger Documentation
|
|
327
|
+
* KilatJS provides a dedicated core for building pure APIs.
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
// example/server.ts
|
|
331
|
+
import KilatJS, { swagger, RouteContext } from "kilatjs/core";
|
|
332
|
+
|
|
333
|
+
const app = new KilatJS();
|
|
334
|
+
|
|
335
|
+
// Register Swagger documentation
|
|
336
|
+
app.use(
|
|
337
|
+
swagger(app, {
|
|
338
|
+
path: "/docs",
|
|
339
|
+
title: "KilatJS API",
|
|
340
|
+
description: "Automatic API documentation",
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Route with metadata for Swagger
|
|
345
|
+
app.post(
|
|
346
|
+
"/api/echo",
|
|
347
|
+
{
|
|
348
|
+
summary: "Echo request",
|
|
349
|
+
body: { text: "string" }, // Optional: for documentation
|
|
350
|
+
response: { echo: {}, receivedAt: "string" }, // Optional: for documentation
|
|
351
|
+
},
|
|
352
|
+
async (ctx: RouteContext) => {
|
|
353
|
+
const body = await ctx.request.json();
|
|
354
|
+
return { echo: body, receivedAt: new Date() };
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
app.listen(4000);
|
|
324
359
|
```
|
|
325
360
|
|
|
326
361
|
### Step 5: Create Contact Form with PRG Pattern
|
|
@@ -614,6 +649,46 @@ KilatJS uniquely allows you to mix **HTML-First SSR** with **Full React CSR**:
|
|
|
614
649
|
|
|
615
650
|
---
|
|
616
651
|
|
|
652
|
+
## ⚡ Standalone API & Documentation
|
|
653
|
+
|
|
654
|
+
KilatJS provides a dedicated core for building pure JSON APIs with automatic documentation.
|
|
655
|
+
|
|
656
|
+
### Features
|
|
657
|
+
|
|
658
|
+
1. **Automatic Swagger Documentation**: Use the `swagger` middleware to generate interactive documentation.
|
|
659
|
+
2. **Typed Routes**: Use `RouteOptions` to provide metadata, request bodies, and response structures.
|
|
660
|
+
3. **Core API**: Use the lightweight `KilatJS` core for JSON-first services.
|
|
661
|
+
|
|
662
|
+
### Example
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
import KilatJS, { swagger, RouteContext } from "kilatjs/core";
|
|
666
|
+
|
|
667
|
+
const app = new KilatJS();
|
|
668
|
+
|
|
669
|
+
app.use(
|
|
670
|
+
swagger(app, {
|
|
671
|
+
path: "/docs",
|
|
672
|
+
title: "My API Documentation",
|
|
673
|
+
})
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
app.get(
|
|
677
|
+
"/api/users",
|
|
678
|
+
{
|
|
679
|
+
summary: "List users",
|
|
680
|
+
response: { users: [] },
|
|
681
|
+
},
|
|
682
|
+
(ctx) => {
|
|
683
|
+
return { users: ["Alice", "Bob"] };
|
|
684
|
+
}
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
app.listen(4000);
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
617
692
|
## 📝 License
|
|
618
693
|
|
|
619
694
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// @bun
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __getProtoOf = Object.getPrototypeOf;
|
|
5
4
|
var __defProp = Object.defineProperty;
|
|
@@ -34,7 +33,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
34
33
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
//
|
|
36
|
+
// server/live-reload.ts
|
|
38
37
|
var exports_live_reload = {};
|
|
39
38
|
__export(exports_live_reload, {
|
|
40
39
|
watchDirectory: () => watchDirectory,
|
|
@@ -186,7 +185,7 @@ var init_live_reload = __esm(() => {
|
|
|
186
185
|
clients = new Set;
|
|
187
186
|
});
|
|
188
187
|
|
|
189
|
-
//
|
|
188
|
+
// adapters/react.ts
|
|
190
189
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
191
190
|
import React from "react";
|
|
192
191
|
|
|
@@ -282,7 +281,7 @@ var init_react = __esm(() => {
|
|
|
282
281
|
init_live_reload();
|
|
283
282
|
});
|
|
284
283
|
|
|
285
|
-
//
|
|
284
|
+
// adapters/htmx.ts
|
|
286
285
|
class HTMXAdapter {
|
|
287
286
|
static async renderToString(template, props) {
|
|
288
287
|
try {
|
|
@@ -438,7 +437,7 @@ var init_htmx = __esm(() => {
|
|
|
438
437
|
init_live_reload();
|
|
439
438
|
});
|
|
440
439
|
|
|
441
|
-
//
|
|
440
|
+
// core/router.ts
|
|
442
441
|
var Router;
|
|
443
442
|
var init_router = __esm(() => {
|
|
444
443
|
init_react();
|
|
@@ -452,6 +451,7 @@ var init_router = __esm(() => {
|
|
|
452
451
|
preloadedRoutes = new Map;
|
|
453
452
|
routePatterns = [];
|
|
454
453
|
apiRoutes = new Map;
|
|
454
|
+
standaloneMiddleware = null;
|
|
455
455
|
static NOT_FOUND_RESPONSE = new Response("404 Not Found", { status: 404 });
|
|
456
456
|
static METHOD_NOT_ALLOWED_RESPONSE = new Response(JSON.stringify({ error: "Method Not Allowed" }), { status: 405, headers: { "Content-Type": "application/json" } });
|
|
457
457
|
static INTERNAL_ERROR_RESPONSE = new Response(JSON.stringify({ error: "Internal Server Error" }), { status: 500, headers: { "Content-Type": "application/json" } });
|
|
@@ -477,11 +477,40 @@ var init_router = __esm(() => {
|
|
|
477
477
|
this.routePatterns.length = 0;
|
|
478
478
|
this.apiRoutes.clear();
|
|
479
479
|
this.staticHtmlFiles.clear();
|
|
480
|
+
this.standaloneMiddleware = null;
|
|
481
|
+
await this.loadMiddlewareFile();
|
|
480
482
|
await this.preloadAllRoutes(silent);
|
|
481
483
|
if (!silent) {
|
|
482
484
|
console.log("\uD83D\uDD04 FileSystemRouter initialized with", this.preloadedRoutes.size, "routes");
|
|
483
485
|
}
|
|
484
486
|
}
|
|
487
|
+
async loadMiddlewareFile() {
|
|
488
|
+
const filenames = ["middleware.ts", "middleware.js"];
|
|
489
|
+
const possibleDirs = [
|
|
490
|
+
this.config.appDir,
|
|
491
|
+
process.cwd()
|
|
492
|
+
];
|
|
493
|
+
for (const dir of possibleDirs) {
|
|
494
|
+
for (const filename of filenames) {
|
|
495
|
+
let filePath = `${dir}/${filename}`.replace("//", "/");
|
|
496
|
+
if (!filePath.startsWith("/")) {
|
|
497
|
+
filePath = `${process.cwd()}/${filePath}`;
|
|
498
|
+
}
|
|
499
|
+
const file = Bun.file(filePath);
|
|
500
|
+
if (await file.exists()) {
|
|
501
|
+
try {
|
|
502
|
+
const module = await import(filePath);
|
|
503
|
+
if (module.middleware) {
|
|
504
|
+
this.standaloneMiddleware = module;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
} catch (e) {
|
|
508
|
+
console.warn(`Failed to load middleware from ${filePath}:`, e);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
485
514
|
async preloadAllRoutes(silent = false) {
|
|
486
515
|
await this.scanAndPreloadRoutes(this.config.routesDir, "", silent);
|
|
487
516
|
}
|
|
@@ -600,11 +629,65 @@ var init_router = __esm(() => {
|
|
|
600
629
|
this.routeCache.set(path, match);
|
|
601
630
|
return match;
|
|
602
631
|
}
|
|
632
|
+
async executeMiddlewares(middlewares, context, handler) {
|
|
633
|
+
let index = -1;
|
|
634
|
+
const next = async () => {
|
|
635
|
+
index++;
|
|
636
|
+
if (index < middlewares.length) {
|
|
637
|
+
try {
|
|
638
|
+
return await middlewares[index](context, next);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.error("Middleware Error:", error);
|
|
641
|
+
return Router.INTERNAL_ERROR_RESPONSE;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return await handler();
|
|
645
|
+
};
|
|
646
|
+
return await next();
|
|
647
|
+
}
|
|
648
|
+
shouldRunStandaloneMiddleware(path) {
|
|
649
|
+
if (!this.standaloneMiddleware || !this.standaloneMiddleware.middleware)
|
|
650
|
+
return false;
|
|
651
|
+
const config = this.standaloneMiddleware.config;
|
|
652
|
+
if (!config || !config.matcher)
|
|
653
|
+
return true;
|
|
654
|
+
const matchers = Array.isArray(config.matcher) ? config.matcher : [config.matcher];
|
|
655
|
+
for (const matcher of matchers) {
|
|
656
|
+
if (matcher === path)
|
|
657
|
+
return true;
|
|
658
|
+
if (matcher.endsWith("*")) {
|
|
659
|
+
const prefix = matcher.slice(0, -1);
|
|
660
|
+
if (path.startsWith(prefix))
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
if (matcher.includes(":")) {
|
|
664
|
+
const pattern = matcher.replace(/:[^\/]+/g, "[^/]+");
|
|
665
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
666
|
+
if (regex.test(path))
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
603
672
|
async handleRequest(request) {
|
|
604
673
|
const url = request.url;
|
|
605
674
|
const pathStart = url.indexOf("/", 8);
|
|
606
675
|
const pathEnd = url.indexOf("?", pathStart);
|
|
607
676
|
const path = pathEnd === -1 ? url.slice(pathStart) : url.slice(pathStart, pathEnd);
|
|
677
|
+
if (this.shouldRunStandaloneMiddleware(path)) {
|
|
678
|
+
const context = {
|
|
679
|
+
request,
|
|
680
|
+
params: {},
|
|
681
|
+
query: new URLSearchParams(url.split("?")[1] || ""),
|
|
682
|
+
state: {}
|
|
683
|
+
};
|
|
684
|
+
return await this.standaloneMiddleware.middleware(context, async () => {
|
|
685
|
+
return await this.internalDispatch(request, path, url);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return await this.internalDispatch(request, path, url);
|
|
689
|
+
}
|
|
690
|
+
async internalDispatch(request, path, url) {
|
|
608
691
|
if (path.startsWith("/api/")) {
|
|
609
692
|
return this.handleApiRouteFast(request, path);
|
|
610
693
|
}
|
|
@@ -658,44 +741,57 @@ var init_router = __esm(() => {
|
|
|
658
741
|
}
|
|
659
742
|
const { params, exports, routeType } = match;
|
|
660
743
|
const context = this.getContextFromPool(request, params, url);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
744
|
+
const middlewares = [
|
|
745
|
+
...this.config.middlewares || [],
|
|
746
|
+
...exports.middlewares || []
|
|
747
|
+
];
|
|
748
|
+
const handler = async () => {
|
|
749
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
750
|
+
const actionHandler = exports[request.method];
|
|
751
|
+
if (actionHandler && typeof actionHandler === "function") {
|
|
752
|
+
try {
|
|
753
|
+
return await actionHandler(context);
|
|
754
|
+
} catch (error) {
|
|
755
|
+
console.error(`Error handling ${request.method}:`, error);
|
|
756
|
+
return Router.INTERNAL_ERROR_RESPONSE;
|
|
757
|
+
}
|
|
672
758
|
}
|
|
759
|
+
return Router.METHOD_NOT_ALLOWED_RESPONSE;
|
|
673
760
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
761
|
+
try {
|
|
762
|
+
const data = exports.load ? await exports.load(context) : {};
|
|
763
|
+
if (data instanceof Response) {
|
|
764
|
+
return data;
|
|
765
|
+
}
|
|
766
|
+
const meta = exports.meta || {};
|
|
767
|
+
const html = await this.renderPage(exports.default, { data, params, state: context.state }, exports.ui, meta, { clientScript: exports.clientScript });
|
|
768
|
+
const cacheControl = routeType === "dynamic" ? "no-cache" : "public, max-age=3600";
|
|
769
|
+
return new Response(html, {
|
|
770
|
+
headers: {
|
|
771
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
772
|
+
"Cache-Control": cacheControl
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (error instanceof Response)
|
|
777
|
+
return error;
|
|
778
|
+
console.error("Error rendering page:", error);
|
|
779
|
+
return Router.INTERNAL_ERROR_RESPONSE;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
677
782
|
try {
|
|
678
|
-
|
|
679
|
-
|
|
783
|
+
if (middlewares.length > 0) {
|
|
784
|
+
const response = await this.executeMiddlewares(middlewares, context, handler);
|
|
785
|
+
this.returnContextToPool(context);
|
|
786
|
+
return response;
|
|
787
|
+
} else {
|
|
788
|
+
const response = await handler();
|
|
680
789
|
this.returnContextToPool(context);
|
|
681
|
-
return
|
|
790
|
+
return response;
|
|
682
791
|
}
|
|
683
|
-
const meta = exports.meta || {};
|
|
684
|
-
const html = await this.renderPage(exports.default, { data, params, state: context.state }, exports.ui, meta, { clientScript: exports.clientScript });
|
|
685
|
-
const cacheControl = routeType === "dynamic" ? "no-cache" : "public, max-age=3600";
|
|
686
|
-
this.returnContextToPool(context);
|
|
687
|
-
return new Response(html, {
|
|
688
|
-
headers: {
|
|
689
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
690
|
-
"Cache-Control": cacheControl
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
792
|
} catch (error) {
|
|
694
793
|
this.returnContextToPool(context);
|
|
695
|
-
|
|
696
|
-
return error;
|
|
697
|
-
console.error("Error rendering page:", error);
|
|
698
|
-
return Router.INTERNAL_ERROR_RESPONSE;
|
|
794
|
+
throw error;
|
|
699
795
|
}
|
|
700
796
|
}
|
|
701
797
|
async handleApiRouteFast(request, path) {
|
|
@@ -723,21 +819,32 @@ var init_router = __esm(() => {
|
|
|
723
819
|
}
|
|
724
820
|
if (!exports)
|
|
725
821
|
return Router.NOT_FOUND_RESPONSE;
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
if (
|
|
734
|
-
return
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
822
|
+
const context = this.getMinimalApiContext(request, params);
|
|
823
|
+
const middlewares = [
|
|
824
|
+
...this.config.middlewares || [],
|
|
825
|
+
...exports.middlewares || []
|
|
826
|
+
];
|
|
827
|
+
const handler = async () => {
|
|
828
|
+
const apiHandler = exports[request.method] || exports.default;
|
|
829
|
+
if (!apiHandler || typeof apiHandler !== "function") {
|
|
830
|
+
return Router.METHOD_NOT_ALLOWED_RESPONSE;
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
const response = await apiHandler(context);
|
|
834
|
+
if (response instanceof Response)
|
|
835
|
+
return response;
|
|
836
|
+
return new Response(JSON.stringify(response), {
|
|
837
|
+
headers: { "Content-Type": "application/json" }
|
|
838
|
+
});
|
|
839
|
+
} catch (error) {
|
|
840
|
+
console.error(`API Error [${request.method} ${path}]:`, error);
|
|
841
|
+
return Router.INTERNAL_ERROR_RESPONSE;
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
if (middlewares.length > 0) {
|
|
845
|
+
return await this.executeMiddlewares(middlewares, context, handler);
|
|
846
|
+
} else {
|
|
847
|
+
return await handler();
|
|
741
848
|
}
|
|
742
849
|
}
|
|
743
850
|
getContextFromPool(request, params, url) {
|
|
@@ -805,7 +912,6 @@ var init_router = __esm(() => {
|
|
|
805
912
|
}
|
|
806
913
|
};
|
|
807
914
|
});
|
|
808
|
-
|
|
809
915
|
// node:path
|
|
810
916
|
function assertPath(path) {
|
|
811
917
|
if (typeof path !== "string")
|
|
@@ -1127,7 +1233,7 @@ var init_path = __esm(() => {
|
|
|
1127
1233
|
posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
|
|
1128
1234
|
});
|
|
1129
1235
|
|
|
1130
|
-
//
|
|
1236
|
+
// server/server.ts
|
|
1131
1237
|
class KilatServer {
|
|
1132
1238
|
router;
|
|
1133
1239
|
config;
|
|
@@ -1138,7 +1244,7 @@ class KilatServer {
|
|
|
1138
1244
|
const { appDir, routesDir } = this.resolvePaths(config);
|
|
1139
1245
|
this.appDir = appDir;
|
|
1140
1246
|
this.routesDir = routesDir;
|
|
1141
|
-
this.router = new Router({ ...config, routesDir: this.routesDir });
|
|
1247
|
+
this.router = new Router({ ...config, appDir: this.appDir, routesDir: this.routesDir });
|
|
1142
1248
|
}
|
|
1143
1249
|
resolvePaths(config) {
|
|
1144
1250
|
const cwd = process.cwd();
|
|
@@ -1274,6 +1380,12 @@ class KilatServer {
|
|
|
1274
1380
|
notifyReload(filename);
|
|
1275
1381
|
});
|
|
1276
1382
|
}
|
|
1383
|
+
watchDirectory(this.appDir, async (filename) => {
|
|
1384
|
+
if (filename === "middleware.ts" || filename === "middleware.js" || filename === "kilat.config.ts") {
|
|
1385
|
+
await router.loadRoutes(true);
|
|
1386
|
+
notifyReload(filename);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1277
1389
|
} else {
|
|
1278
1390
|
console.log(`
|
|
1279
1391
|
\uD83D\uDE80 KilatJS Production Server
|
|
@@ -1634,7 +1746,8 @@ var init_server = __esm(() => {
|
|
|
1634
1746
|
init_path();
|
|
1635
1747
|
init_live_reload();
|
|
1636
1748
|
});
|
|
1637
|
-
|
|
1749
|
+
|
|
1750
|
+
// index.ts
|
|
1638
1751
|
var exports_src = {};
|
|
1639
1752
|
__export(exports_src, {
|
|
1640
1753
|
stopLiveReload: () => stopLiveReload,
|
|
@@ -2026,7 +2139,7 @@ var init_src = __esm(() => {
|
|
|
2026
2139
|
src_default = Kilat;
|
|
2027
2140
|
});
|
|
2028
2141
|
|
|
2029
|
-
//
|
|
2142
|
+
// cli.ts
|
|
2030
2143
|
var args = process.argv.slice(2);
|
|
2031
2144
|
var command = args[0];
|
|
2032
2145
|
var isHotMode = process.env.__KILAT_HOT === "1";
|