as-facile-js 1.0.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.
Files changed (47) hide show
  1. package/README.md +80 -0
  2. package/dist/cli/index.js +206 -0
  3. package/dist/cli/index.js.map +1 -0
  4. package/dist/cli/templates/health.controller.ts.txt +9 -0
  5. package/dist/cli/templates/health.payload.ts.txt +3 -0
  6. package/dist/cli/templates/health.route.ts.txt +6 -0
  7. package/dist/cli/templates/health.service.ts.txt +9 -0
  8. package/dist/cli/templates/module.controller.ts.txt +46 -0
  9. package/dist/cli/templates/module.payload.ts.txt +39 -0
  10. package/dist/cli/templates/module.route.ts.txt +15 -0
  11. package/dist/cli/templates/module.service.ts.txt +87 -0
  12. package/dist/cli/templates/starter.package.json.txt +18 -0
  13. package/dist/cli/templates/starter.server.ts.txt +11 -0
  14. package/dist/cli/templates/starter.tsconfig.json.txt +11 -0
  15. package/dist/core/app.d.ts +16 -0
  16. package/dist/core/app.d.ts.map +1 -0
  17. package/dist/core/app.js +84 -0
  18. package/dist/core/app.js.map +1 -0
  19. package/dist/core/compose.d.ts +15 -0
  20. package/dist/core/compose.d.ts.map +1 -0
  21. package/dist/core/compose.js +43 -0
  22. package/dist/core/compose.js.map +1 -0
  23. package/dist/core/response.d.ts +3 -0
  24. package/dist/core/response.d.ts.map +1 -0
  25. package/dist/core/response.js +41 -0
  26. package/dist/core/response.js.map +1 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +4 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/middlewares/json.d.ts +5 -0
  32. package/dist/middlewares/json.d.ts.map +1 -0
  33. package/dist/middlewares/json.js +38 -0
  34. package/dist/middlewares/json.js.map +1 -0
  35. package/dist/router/match.d.ts +8 -0
  36. package/dist/router/match.d.ts.map +1 -0
  37. package/dist/router/match.js +27 -0
  38. package/dist/router/match.js.map +1 -0
  39. package/dist/router/router.d.ts +16 -0
  40. package/dist/router/router.d.ts.map +1 -0
  41. package/dist/router/router.js +72 -0
  42. package/dist/router/router.js.map +1 -0
  43. package/dist/types.d.ts +15 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +2 -0
  46. package/dist/types.js.map +1 -0
  47. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ by Abdul Salam
2
+
3
+ <div align="center">
4
+ <img src="https://placehold.co/200x200?text=Facile" alt="Facile logo" width="200" height="200" />
5
+
6
+ <h1>Facile.js</h1>
7
+ <p>Lightweight, minimal API framework with a friendly CLI.</p>
8
+ </div>
9
+
10
+ ## Features
11
+ - ⚡ Lightweight HTTP framework with middleware and routing.
12
+ - 🧩 Simple router with path params and middleware chaining.
13
+ - 🧰 Built-in CLI to scaffold a new project or generate modules.
14
+
15
+ ## Installation
16
+ ```bash
17
+ npm install facile-js
18
+ ```
19
+
20
+ ## Quick start
21
+ ```ts
22
+ import { createApp, Router, json } from "facile-js";
23
+
24
+ const app = createApp();
25
+ app.use(json());
26
+
27
+ const router = Router();
28
+ router.get("/health", (req, res) => {
29
+ res.json({ ok: true });
30
+ });
31
+
32
+ app.use(router);
33
+
34
+ app.listen(3000, () => {
35
+ console.log("Server running on http://localhost:3000");
36
+ });
37
+ ```
38
+
39
+ ## CLI usage
40
+ ### Initialize a new project
41
+ ```bash
42
+ npx facile-js init my-api
43
+ # or shorthand
44
+ npx facile-js my-api
45
+ ```
46
+
47
+ ### Generate a module
48
+ ```bash
49
+ cd my-api
50
+ npx facile-js -g users
51
+ ```
52
+
53
+ ### CLI help / version
54
+ ```bash
55
+ npx facile-js --help
56
+ npx facile-js --version
57
+ ```
58
+
59
+ ## Project structure (generated)
60
+ ```
61
+ my-api/
62
+ src/
63
+ server.ts
64
+ modules/
65
+ # generated by default
66
+ health/
67
+ health.controller.ts
68
+ health.payload.ts
69
+ health.route.ts
70
+ health.service.ts
71
+ ```
72
+
73
+ ## Scripts (generated project)
74
+ ```bash
75
+ npm run dev
76
+ ```
77
+
78
+ ## License
79
+ ISC
80
+ by Abdul Salam
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, promises as fs } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join, relative } from "node:path";
5
+ import process from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+ async function main() {
8
+ const args = parseArgs(process.argv.slice(2));
9
+ if (args.cmd === "help") {
10
+ printHelp();
11
+ process.exit(0);
12
+ }
13
+ if (args.cmd === "version") {
14
+ console.log(getVersion());
15
+ process.exit(0);
16
+ }
17
+ if (args.cmd === "init") {
18
+ await initProject(args.projectName);
19
+ return;
20
+ }
21
+ if (args.cmd === "generate") {
22
+ await generateModule(args.name);
23
+ return;
24
+ }
25
+ }
26
+ function parseArgs(argv) {
27
+ // Supported:
28
+ // npx facile-js init my-app
29
+ // npx facile-js my-app (default = init)
30
+ // npx facile-js -g users
31
+ // npx facile-js --generate users
32
+ if (argv.length === 0)
33
+ return { cmd: "help" };
34
+ const first = argv[0];
35
+ if (first === "-h" || first === "--help")
36
+ return { cmd: "help" };
37
+ if (first === "-v" || first === "--version")
38
+ return { cmd: "version" };
39
+ if (first === "init") {
40
+ const projectName = argv[1];
41
+ if (!projectName)
42
+ return { cmd: "help" };
43
+ return { cmd: "init", projectName };
44
+ }
45
+ for (let i = 0; i < argv.length; i++) {
46
+ const a = argv[i];
47
+ if (a === "-g" || a === "--generate") {
48
+ const name = argv[i + 1];
49
+ if (!name) {
50
+ return { cmd: "help" };
51
+ }
52
+ return { cmd: "generate", name };
53
+ }
54
+ }
55
+ if (!first) {
56
+ return { cmd: "help" };
57
+ }
58
+ // default shorthand: `npx facile-js my-app`
59
+ if (!first.startsWith("-")) {
60
+ return { cmd: "init", projectName: first };
61
+ }
62
+ return { cmd: "help" };
63
+ }
64
+ function printHelp() {
65
+ console.log(`
66
+ facile-js CLI
67
+
68
+ Usage:
69
+ npx facile-js init <project-name>
70
+ npx facile-js <project-name> (shorthand init)
71
+ npx facile-js -g <module-name>
72
+ npx facile-js --version
73
+
74
+ Examples:
75
+ npx facile-js init my-api
76
+ npx facile-js my-api
77
+ cd my-api && npx facile-js -g users
78
+ `.trim());
79
+ }
80
+ /* ---------------- INIT PROJECT ---------------- */
81
+ async function initProject(projectNameRaw) {
82
+ const projectName = normalizeName(projectNameRaw);
83
+ const root = join(process.cwd(), projectName);
84
+ await ensureDir(root);
85
+ await ensureDir(join(root, "src"));
86
+ await ensureDir(join(root, "src", "modules"));
87
+ await ensureDir(join(root, "src", "modules", "health"));
88
+ // starter files
89
+ await writeFromTemplate("starter.package.json.txt", join(root, "package.json"), {
90
+ __project__: projectName
91
+ });
92
+ await writeFromTemplate("starter.tsconfig.json.txt", join(root, "tsconfig.json"), {});
93
+ await writeFromTemplate("starter.server.ts.txt", join(root, "src", "server.ts"), {});
94
+ // health module
95
+ await writeFromTemplate("health.route.ts.txt", join(root, "src", "modules", "health", "health.route.ts"), {});
96
+ await writeFromTemplate("health.controller.ts.txt", join(root, "src", "modules", "health", "health.controller.ts"), {});
97
+ await writeFromTemplate("health.service.ts.txt", join(root, "src", "modules", "health", "health.service.ts"), {});
98
+ await writeFromTemplate("health.payload.ts.txt", join(root, "src", "modules", "health", "health.payload.ts"), {});
99
+ console.log(`\nDone. Project created: ${relFromCwd(root)}`);
100
+ console.log(`\nNext:\n cd ${projectName}\n npm i\n npm run dev\n`);
101
+ }
102
+ /* ---------------- GENERATE MODULE ---------------- */
103
+ async function generateModule(rawName) {
104
+ const name = normalizeName(rawName.trim());
105
+ const pascal = toPascalCase(name);
106
+ const baseDir = join(process.cwd(), "src", "modules", name);
107
+ await ensureDir(baseDir);
108
+ await writeFromTemplate("module.route.ts.txt", join(baseDir, `${name}.route.ts`), {
109
+ __name__: name,
110
+ __Pascal__: pascal
111
+ });
112
+ await writeFromTemplate("module.controller.ts.txt", join(baseDir, `${name}.controller.ts`), {
113
+ __name__: name,
114
+ __Pascal__: pascal
115
+ });
116
+ await writeFromTemplate("module.service.ts.txt", join(baseDir, `${name}.service.ts`), {
117
+ __name__: name,
118
+ __Pascal__: pascal
119
+ });
120
+ await writeFromTemplate("module.payload.ts.txt", join(baseDir, `${name}.payload.ts`), {
121
+ __name__: name,
122
+ __Pascal__: pascal
123
+ });
124
+ console.log(`\nDone. Module created at: ${relFromCwd(baseDir)}`);
125
+ }
126
+ /* ---------------- Template Loader ---------------- */
127
+ function templatesDirPath() {
128
+ // IMPORTANT:
129
+ // When compiled, dist/cli/index.js should live next to dist/cli/templates/*
130
+ const cliDir = dirname(fileURLToPath(import.meta.url));
131
+ // 1) packaged mode: dist/cli/templates
132
+ const packaged = join(cliDir, "templates");
133
+ if (existsSync(packaged))
134
+ return packaged;
135
+ // 2) dev mode (running TS directly): src/cli/templates
136
+ const dev = join(process.cwd(), "src", "cli", "templates");
137
+ if (existsSync(dev))
138
+ return dev;
139
+ throw new Error("Unable to locate CLI templates. Did you run the build step?");
140
+ }
141
+ async function writeFromTemplate(templateFile, outPath, vars) {
142
+ const tplPath = join(templatesDirPath(), templateFile);
143
+ // Don’t overwrite
144
+ if (await fileExists(outPath)) {
145
+ console.log(`skip: ${relFromCwd(outPath)} (already exists)`);
146
+ return;
147
+ }
148
+ const tpl = await fs.readFile(tplPath, "utf8");
149
+ const rendered = render(tpl, vars);
150
+ await ensureDir(join(outPath, ".."));
151
+ await fs.writeFile(outPath, rendered, "utf8");
152
+ console.log(`create: ${relFromCwd(outPath)}`);
153
+ }
154
+ function render(tpl, vars) {
155
+ let out = tpl;
156
+ for (const [k, v] of Object.entries(vars))
157
+ out = out.replaceAll(k, v);
158
+ return out;
159
+ }
160
+ /* ---------------- Utils ---------------- */
161
+ function normalizeName(input) {
162
+ return input
163
+ .trim()
164
+ .toLowerCase()
165
+ .replace(/[^a-z0-9\-_.]/g, "-")
166
+ .replace(/-+/g, "-")
167
+ .replace(/^-|-$/g, "");
168
+ }
169
+ function toPascalCase(name) {
170
+ return name
171
+ .replace(/[_\-.]+/g, " ")
172
+ .split(" ")
173
+ .filter(Boolean)
174
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
175
+ .join("");
176
+ }
177
+ async function ensureDir(p) {
178
+ await fs.mkdir(p, { recursive: true });
179
+ }
180
+ async function fileExists(p) {
181
+ try {
182
+ await fs.access(p);
183
+ return true;
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ }
189
+ function relFromCwd(p) {
190
+ return relative(process.cwd(), p);
191
+ }
192
+ function getVersion() {
193
+ try {
194
+ const require = createRequire(import.meta.url);
195
+ const pkg = require("../../package.json");
196
+ return pkg.version ?? "unknown";
197
+ }
198
+ catch {
199
+ return "unknown";
200
+ }
201
+ }
202
+ main().catch((err) => {
203
+ console.error("CLI error:", err);
204
+ process.exit(1);
205
+ });
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Req, Res } from "facile-js";
2
+ import { HealthService } from "./health.service";
3
+
4
+ export class HealthController {
5
+ static async check(req: Req, res: Res) {
6
+ const data = await HealthService.check();
7
+ return res.json(data);
8
+ }
9
+ }
@@ -0,0 +1,3 @@
1
+ // Health module usually has no payload.
2
+ // Keep this for consistency.
3
+ export type HealthPayload = {};
@@ -0,0 +1,6 @@
1
+ import { Router } from "facile-js";
2
+ import { HealthController } from "./health.controller";
3
+
4
+ export const healthRouter = Router();
5
+
6
+ healthRouter.get("/health", HealthController.check);
@@ -0,0 +1,9 @@
1
+ export class HealthService {
2
+ static async check() {
3
+ return {
4
+ ok: true,
5
+ service: "facile-js-starter",
6
+ timestamp: new Date().toISOString()
7
+ };
8
+ }
9
+ }
@@ -0,0 +1,46 @@
1
+ import type { Req, Res } from "facile-js";
2
+ import { __Pascal__Service } from "./__name__.service";
3
+ import {
4
+ parseCreatePayload,
5
+ parseReplacePayload,
6
+ parseUpdatePayload
7
+ } from "./__name__.payload";
8
+
9
+ export class __Pascal__Controller {
10
+ static async list(req: Req, res: Res) {
11
+ const data = await __Pascal__Service.list();
12
+ return res.json({ data });
13
+ }
14
+
15
+ static async getById(req: Req, res: Res) {
16
+ const item = await __Pascal__Service.getById(req.params.id);
17
+ if (!item) return res.status(404).json({ message: "__Pascal__ not found" });
18
+ return res.json({ data: item });
19
+ }
20
+
21
+ static async create(req: Req, res: Res) {
22
+ const payload = parseCreatePayload(req.body);
23
+ const created = await __Pascal__Service.create(payload);
24
+ return res.status(201).json({ data: created });
25
+ }
26
+
27
+ static async replace(req: Req, res: Res) {
28
+ const payload = parseReplacePayload(req.body);
29
+ const updated = await __Pascal__Service.replace(req.params.id, payload);
30
+ if (!updated) return res.status(404).json({ message: "__Pascal__ not found" });
31
+ return res.json({ data: updated });
32
+ }
33
+
34
+ static async update(req: Req, res: Res) {
35
+ const payload = parseUpdatePayload(req.body);
36
+ const updated = await __Pascal__Service.update(req.params.id, payload);
37
+ if (!updated) return res.status(404).json({ message: "__Pascal__ not found" });
38
+ return res.json({ data: updated });
39
+ }
40
+
41
+ static async remove(req: Req, res: Res) {
42
+ const ok = await __Pascal__Service.remove(req.params.id);
43
+ if (!ok) return res.status(404).json({ message: "__Pascal__ not found" });
44
+ return res.status(204).send();
45
+ }
46
+ }
@@ -0,0 +1,39 @@
1
+ export type CreatePayload = {
2
+ // TODO: define fields
3
+ // example:
4
+ // name: string;
5
+ };
6
+
7
+ export type ReplacePayload = {
8
+ // PUT: typically full replace.
9
+ // Define required fields here.
10
+ };
11
+
12
+ export type UpdatePayload = Partial<ReplacePayload>; // PATCH: partial update
13
+
14
+ function isObject(v: unknown): v is Record<string, unknown> {
15
+ return typeof v === "object" && v !== null && !Array.isArray(v);
16
+ }
17
+
18
+ export function parseCreatePayload(body: unknown): CreatePayload {
19
+ if (!isObject(body)) throw badReq("Body must be a JSON object");
20
+ // TODO: validate required fields
21
+ return body as CreatePayload;
22
+ }
23
+
24
+ export function parseReplacePayload(body: unknown): ReplacePayload {
25
+ if (!isObject(body)) throw badReq("Body must be a JSON object");
26
+ // TODO: validate required fields for PUT
27
+ return body as ReplacePayload;
28
+ }
29
+
30
+ export function parseUpdatePayload(body: unknown): UpdatePayload {
31
+ if (!isObject(body)) throw badReq("Body must be a JSON object");
32
+ return body as UpdatePayload;
33
+ }
34
+
35
+ function badReq(message: string) {
36
+ const err = new Error(message);
37
+ (err as any).status = 400;
38
+ return err;
39
+ }
@@ -0,0 +1,15 @@
1
+ import { Router } from "facile-js";
2
+ import { __Pascal__Controller } from "./__name__.controller";
3
+
4
+ export const __name__Router = Router();
5
+
6
+ // Base: /__name__
7
+ __name__Router.get("/__name__", __Pascal__Controller.list);
8
+ __name__Router.get("/__name__/:id", __Pascal__Controller.getById);
9
+
10
+ __name__Router.post("/__name__", __Pascal__Controller.create);
11
+
12
+ __name__Router.put("/__name__/:id", __Pascal__Controller.replace);
13
+ __name__Router.patch("/__name__/:id", __Pascal__Controller.update);
14
+
15
+ __name__Router.delete("/__name__/:id", __Pascal__Controller.remove);
@@ -0,0 +1,87 @@
1
+ import type { CreatePayload, ReplacePayload, UpdatePayload } from "./__name__.payload";
2
+
3
+ export type __Pascal__Entity = {
4
+ id: string;
5
+ createdAt: string;
6
+ updatedAt: string;
7
+ } & Record<string, unknown>;
8
+
9
+ // In-memory store (replace with DB later)
10
+ const db: __Pascal__Entity[] = [];
11
+
12
+ function nowISO() {
13
+ return new Date().toISOString();
14
+ }
15
+
16
+ function newId() {
17
+ return String(Date.now()) + Math.random().toString(16).slice(2);
18
+ }
19
+
20
+ export class __Pascal__Service {
21
+ static async list() {
22
+ return db;
23
+ }
24
+
25
+ static async getById(id: string) {
26
+ return db.find(x => x.id === id) ?? null;
27
+ }
28
+
29
+ static async create(payload: CreatePayload) {
30
+ const t = nowISO();
31
+ const item: __Pascal__Entity = {
32
+ id: newId(),
33
+ createdAt: t,
34
+ updatedAt: t,
35
+ ...payload
36
+ };
37
+ db.push(item);
38
+
39
+ return item;
40
+ }
41
+
42
+ static async replace(id: string, payload: ReplacePayload) {
43
+ const idx = db.findIndex(x => x.id === id);
44
+ if (idx === -1) return null;
45
+
46
+ const t = nowISO();
47
+ const prev = db[idx];
48
+
49
+ const next: __Pascal__Entity = {
50
+ ...prev,
51
+ ...payload,
52
+ id,
53
+ updatedAt: t
54
+ };
55
+
56
+ db[idx] = next;
57
+ return next;
58
+ }
59
+
60
+ static async update(id: string, payload: UpdatePayload) {
61
+ const idx = db.findIndex(x => x.id === id);
62
+ if (idx === -1) return null;
63
+
64
+ const t = nowISO();
65
+ const prev = db[idx];
66
+
67
+ const next: __Pascal__Entity = {
68
+ ...prev,
69
+ ...payload,
70
+ id,
71
+ updatedAt: t
72
+ };
73
+
74
+ db[idx] = next;
75
+ return next;
76
+ }
77
+
78
+ static async remove(id: string) {
79
+ const before = db.length;
80
+ const after = db.filter(x => x.id !== id);
81
+
82
+ db.length = 0;
83
+ db.push(...after);
84
+
85
+ return after.length !== before;
86
+ }
87
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__project__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch src/server.ts",
7
+ "start": "tsx src/server.ts",
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "facile-js": "^0.0.1"
12
+ },
13
+ "devDependencies": {
14
+ "@types/node": "^22.0.0",
15
+ "typescript": "^5.0.0",
16
+ "tsx": "^4.0.0"
17
+ }
18
+ }
@@ -0,0 +1,11 @@
1
+ import { createApp, json } from "facile-js";
2
+ import { healthRouter } from "./modules/health/health.route";
3
+
4
+ const app = createApp();
5
+
6
+ app.use(json());
7
+ app.use(healthRouter);
8
+
9
+ app.listen(3000, () => {
10
+ console.log("Server running on http://localhost:3000");
11
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "types": ["node"],
8
+ "skipLibCheck": true
9
+ },
10
+ "include": ["src"]
11
+ }
@@ -0,0 +1,16 @@
1
+ import http from "node:http";
2
+ import { type Middleware, type ErrorMiddleware } from "./compose";
3
+ import { type FacileRouter } from "../router/router";
4
+ export type App = {
5
+ use(path: string, fn: Middleware | ErrorMiddleware | FacileRouter): App;
6
+ use(fn: Middleware | ErrorMiddleware | FacileRouter): App;
7
+ get(path: string, ...h: Middleware[]): App;
8
+ post(path: string, ...h: Middleware[]): App;
9
+ put(path: string, ...h: Middleware[]): App;
10
+ patch(path: string, ...h: Middleware[]): App;
11
+ delete(path: string, ...h: Middleware[]): App;
12
+ router: FacileRouter;
13
+ listen(port: number, cb?: () => void): http.Server;
14
+ };
15
+ export declare function createApp(): App;
16
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/core/app.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAW,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAG3E,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAM7D,MAAM,MAAM,GAAG,GAAG;IACd,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,eAAe,GAAG,YAAY,GAAG,GAAG,CAAC;IACxE,GAAG,CAAC,EAAE,EAAE,UAAU,GAAG,eAAe,GAAG,YAAY,GAAG,GAAG,CAAC;IAE1D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAE9C,MAAM,EAAE,YAAY,CAAC;IAErB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;CACtD,CAAC;AAEF,wBAAgB,SAAS,IAAI,GAAG,CA+F/B"}
@@ -0,0 +1,84 @@
1
+ import http from "node:http";
2
+ import { compose } from "./compose";
3
+ import { enhanceResponse } from "./response";
4
+ import { Router } from "../router/router";
5
+ export function createApp() {
6
+ const layers = [];
7
+ const router = Router();
8
+ let routerMounted = false;
9
+ const addLayer = (path, fn) => {
10
+ layers.push({
11
+ path,
12
+ fn,
13
+ isErrorHandler: fn.length === 4
14
+ });
15
+ };
16
+ const app = {
17
+ use(arg1, arg2) {
18
+ // app.use(fn)
19
+ if (typeof arg1 === "function" || (arg1 && typeof arg1.handle === "function")) {
20
+ const fnOrRouter = arg1;
21
+ // mount router
22
+ if (fnOrRouter && typeof fnOrRouter.handle === "function") {
23
+ routerMounted = true;
24
+ addLayer("", (req, res, next) => fnOrRouter.handle(req, res, next));
25
+ return app;
26
+ }
27
+ addLayer("", fnOrRouter);
28
+ return app;
29
+ }
30
+ // app.use("/prefix", fnOrRouter)
31
+ const prefix = String(arg1 || "");
32
+ const fnOrRouter = arg2;
33
+ if (fnOrRouter && typeof fnOrRouter.handle === "function") {
34
+ routerMounted = true;
35
+ addLayer(prefix, (req, res, next) => fnOrRouter.handle(req, res, next));
36
+ return app;
37
+ }
38
+ addLayer(prefix, fnOrRouter);
39
+ return app;
40
+ },
41
+ get(path, ...h) { router.get(path, ...h); return app; },
42
+ post(path, ...h) { router.post(path, ...h); return app; },
43
+ put(path, ...h) { router.put(path, ...h); return app; },
44
+ patch(path, ...h) { router.patch(path, ...h); return app; },
45
+ delete(path, ...h) { router.delete(path, ...h); return app; },
46
+ router,
47
+ listen(port, cb) {
48
+ // If user used app.get/post/etc, mount internal router automatically (last)
49
+ if (!routerMounted) {
50
+ addLayer("", (req, res, next) => router.handle(req, res, next));
51
+ }
52
+ // default 404 (after everything)
53
+ addLayer("", ((req, res) => {
54
+ if (!res.writableEnded)
55
+ res.status(404).json({ message: "Not Found" });
56
+ }));
57
+ const run = compose(layers);
58
+ const server = http.createServer((rawReq, rawRes) => {
59
+ const req = rawReq;
60
+ const res = enhanceResponse(rawRes);
61
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
62
+ req.path = url.pathname;
63
+ req.query = Object.fromEntries(url.searchParams.entries());
64
+ req.params = {};
65
+ req.body = undefined;
66
+ run(req, res, (err) => {
67
+ if (!err)
68
+ return;
69
+ // respect status if thrown error includes status
70
+ const status = typeof err?.status === "number" ? err.status : 500;
71
+ if (!res.writableEnded) {
72
+ res.status(status).json({
73
+ message: status === 500 ? "Internal Server Error" : "Error",
74
+ error: err?.message ?? String(err)
75
+ });
76
+ }
77
+ });
78
+ });
79
+ return server.listen(port, cb);
80
+ }
81
+ };
82
+ return app;
83
+ }
84
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/core/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Next, Req, Res } from "../types";
2
+ export type Middleware = (req: Req, res: Res, next: Next) => void | Promise<void>;
3
+ export type ErrorMiddleware = (err: unknown, req: Req, res: Res, next: Next) => void | Promise<void>;
4
+ type Layer = {
5
+ path?: string;
6
+ fn: Middleware;
7
+ isErrorHandler: false;
8
+ } | {
9
+ path?: string;
10
+ fn: ErrorMiddleware;
11
+ isErrorHandler: true;
12
+ };
13
+ export declare function compose(layers: Layer[]): (req: Req, res: Res, out?: Next) => void;
14
+ export {};
15
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAClF,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAErG,KAAK,KAAK,GACJ;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,UAAU,CAAC;IAAC,cAAc,EAAE,KAAK,CAAA;CAAE,GACxD;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,eAAe,CAAC;IAAC,cAAc,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnE,wBAAgB,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,IACf,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,IAAI,UA0CrD"}
@@ -0,0 +1,43 @@
1
+ export function compose(layers) {
2
+ return function run(req, res, out) {
3
+ let idx = -1;
4
+ const dispatch = (i, err) => {
5
+ if (i <= idx)
6
+ return out?.(new Error("next() called multiple times"));
7
+ idx = i;
8
+ if (i >= layers.length)
9
+ return out?.(err);
10
+ const layer = layers[i];
11
+ // path prefix check
12
+ if (!layer) {
13
+ return dispatch(i + 1, err);
14
+ }
15
+ if (layer.path && !req.path.startsWith(layer.path)) {
16
+ return dispatch(i + 1, err);
17
+ }
18
+ try {
19
+ if (err) {
20
+ // only error handlers should run
21
+ if (layer.isErrorHandler) {
22
+ const ret = layer.fn(err, req, res, (e) => dispatch(i + 1, e));
23
+ if (ret && typeof ret.then === "function")
24
+ ret.catch((e) => dispatch(i + 1, e));
25
+ return;
26
+ }
27
+ return dispatch(i + 1, err);
28
+ }
29
+ // normal handlers only
30
+ if (layer.isErrorHandler)
31
+ return dispatch(i + 1);
32
+ const ret = layer.fn(req, res, (e) => dispatch(i + 1, e));
33
+ if (ret && typeof ret.then === "function")
34
+ ret.catch((e) => dispatch(i + 1, e));
35
+ }
36
+ catch (e) {
37
+ dispatch(i + 1, e);
38
+ }
39
+ };
40
+ dispatch(0);
41
+ };
42
+ }
43
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,OAAO,CAAC,MAAe;IACnC,OAAO,SAAS,GAAG,CAAC,GAAQ,EAAE,GAAQ,EAAE,GAAU;QAC9C,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;QAEb,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,GAAa,EAAE,EAAE;YAC1C,IAAI,CAAC,IAAI,GAAG;gBAAE,OAAO,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACtE,GAAG,GAAG,CAAC,CAAC;YAER,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM;gBAAE,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;YAE1C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YAExB,oBAAoB;YACpB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,OAAO,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjD,OAAO,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;YAChC,CAAC;YAED,IAAI,CAAC;gBACD,IAAI,GAAG,EAAE,CAAC;oBACN,iCAAiC;oBACjC,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;wBACvB,MAAM,GAAG,GAAI,KAAK,CAAC,EAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;wBACpF,IAAI,GAAG,IAAI,OAAQ,GAAW,CAAC,IAAI,KAAK,UAAU;4BAAG,GAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;wBAC5G,OAAO;oBACX,CAAC;oBACD,OAAO,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;gBAChC,CAAC;gBAED,uBAAuB;gBACvB,IAAI,KAAK,CAAC,cAAc;oBAAE,OAAO,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAEjD,MAAM,GAAG,GAAI,KAAK,CAAC,EAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC1E,IAAI,GAAG,IAAI,OAAQ,GAAW,CAAC,IAAI,KAAK,UAAU;oBAAG,GAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACvB,CAAC;QACL,CAAC,CAAC;QAEF,QAAQ,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Res } from "../types";
2
+ export declare function enhanceResponse(res: Res): Res;
3
+ //# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CA6C7C"}
@@ -0,0 +1,41 @@
1
+ export function enhanceResponse(res) {
2
+ res.status = function status(code) {
3
+ res.statusCode = code;
4
+ return res;
5
+ };
6
+ res.set = function set(name, value) {
7
+ res.setHeader(name, value);
8
+ return res;
9
+ };
10
+ res.send = function send(data) {
11
+ if (data == null) {
12
+ res.end();
13
+ return res;
14
+ }
15
+ // Buffer
16
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
17
+ if (!res.getHeader("Content-Type")) {
18
+ res.setHeader("Content-Type", "application/octet-stream");
19
+ }
20
+ res.end(data);
21
+ return res;
22
+ }
23
+ // Object => JSON
24
+ if (typeof data === "object")
25
+ return res.json(data);
26
+ // String/number/boolean
27
+ if (!res.getHeader("Content-Type")) {
28
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
29
+ }
30
+ res.end(String(data));
31
+ return res;
32
+ };
33
+ res.json = function json(obj) {
34
+ const body = JSON.stringify(obj);
35
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
36
+ res.end(body);
37
+ return res;
38
+ };
39
+ return res;
40
+ }
41
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAAC,GAAQ;IACpC,GAAG,CAAC,MAAM,GAAG,SAAS,MAAM,CAAC,IAAY;QACrC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;QACtB,OAAO,GAAG,CAAC;IACf,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,GAAG,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa;QAC9C,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,CAAC;IACf,CAAC,CAAC;IAEF,GAAG,CAAC,IAAI,GAAG,SAAS,IAAI,CAAC,IAAc;QACnC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACf,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO,GAAG,CAAC;QACf,CAAC;QAED,SAAS;QACT,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;YAC9D,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACd,OAAO,GAAG,CAAC;QACf,CAAC;QAED,iBAAiB;QACjB,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpD,wBAAwB;QACxB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;QAC/D,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACtB,OAAO,GAAG,CAAC;IACf,CAAC,CAAC;IAEF,GAAG,CAAC,IAAI,GAAG,SAAS,IAAI,CAAC,GAAY;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;QACjE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACd,OAAO,GAAG,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,GAAG,CAAC;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { createApp } from "./core/app";
2
+ export { Router } from "./router/router";
3
+ export { json } from "./middlewares/json";
4
+ export type { Req, Res, Next } from "./types";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createApp } from "./core/app";
2
+ export { Router } from "./router/router";
3
+ export { json } from "./middlewares/json";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Middleware } from "../core/compose";
2
+ export declare function json(opts?: {
3
+ limitBytes?: number;
4
+ }): Middleware;
5
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/middlewares/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,wBAAgB,IAAI,CAAC,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,CAwCnE"}
@@ -0,0 +1,38 @@
1
+ export function json(opts = {}) {
2
+ const limitBytes = opts.limitBytes ?? 1_000_000;
3
+ return (req, res, next) => {
4
+ const type = String(req.headers["content-type"] || "");
5
+ const isJson = type.includes("application/json");
6
+ if (!isJson)
7
+ return next();
8
+ if (req.method === "GET" || req.method === "HEAD")
9
+ return next();
10
+ let size = 0;
11
+ let raw = "";
12
+ req.setEncoding("utf8");
13
+ req.on("data", (chunk) => {
14
+ size += chunk.length;
15
+ if (size > limitBytes) {
16
+ res.status(413).json({ message: "Payload Too Large" });
17
+ req.destroy();
18
+ return;
19
+ }
20
+ raw += chunk;
21
+ });
22
+ req.on("end", () => {
23
+ if (!raw) {
24
+ req.body = {};
25
+ return next();
26
+ }
27
+ try {
28
+ req.body = JSON.parse(raw);
29
+ next();
30
+ }
31
+ catch {
32
+ res.status(400).json({ message: "Invalid JSON" });
33
+ }
34
+ });
35
+ req.on("error", next);
36
+ };
37
+ }
38
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/middlewares/json.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,IAAI,CAAC,OAAgC,EAAE;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC;IAEhD,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAEjD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QAEjE,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,GAAG,GAAG,EAAE,CAAC;QAEb,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAExB,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YACrB,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,UAAU,EAAE,CAAC;gBACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;gBACvD,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO;YACX,CAAC;YACD,GAAG,IAAI,KAAK,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACf,IAAI,CAAC,GAAG,EAAE,CAAC;gBACP,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;gBACd,OAAO,IAAI,EAAE,CAAC;YAClB,CAAC;YACD,IAAI,CAAC;gBACD,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC3B,IAAI,EAAE,CAAC;YACX,CAAC;YAAC,MAAM,CAAC;gBACL,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YACtD,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function matchPath(pattern: string, path: string): {
2
+ ok: true;
3
+ params: Record<string, string>;
4
+ } | {
5
+ ok: false;
6
+ params: Record<string, string>;
7
+ };
8
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;YACgC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;YAK7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EAkBrG"}
@@ -0,0 +1,27 @@
1
+ export function matchPath(pattern, path) {
2
+ if (pattern === "*" || pattern === path)
3
+ return { ok: true, params: {} };
4
+ const pSeg = trim(pattern).split("/");
5
+ const aSeg = trim(path).split("/");
6
+ if (pSeg.length !== aSeg.length)
7
+ return { ok: false, params: {} };
8
+ const params = {};
9
+ for (let i = 0; i < pSeg.length; i++) {
10
+ const p = pSeg[i];
11
+ const a = aSeg[i];
12
+ if (!p || !a) {
13
+ continue;
14
+ }
15
+ if (p.startsWith(":")) {
16
+ params[p.slice(1)] = decodeURIComponent(a);
17
+ continue;
18
+ }
19
+ if (p !== a)
20
+ return { ok: false, params: {} };
21
+ }
22
+ return { ok: true, params };
23
+ }
24
+ function trim(s) {
25
+ return s.replace(/^\/+|\/+$/g, "");
26
+ }
27
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/router/match.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACnD,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,IAAa,EAAE,MAAM,EAAE,EAA4B,EAAE,CAAC;IAE5G,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAc,EAAE,MAAM,EAAE,EAA4B,EAAE,CAAC;IAErG,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAElB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACX,SAAS;QACb,CAAC;QACD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC3C,SAAS;QACb,CAAC;QACD,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAc,EAAE,MAAM,EAAE,EAA4B,EAAE,CAAC;IACrF,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAa,EAAE,MAAM,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,IAAI,CAAC,CAAS;IACnB,OAAO,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Middleware, ErrorMiddleware } from "../core/compose";
2
+ import type { Next, Req, Res } from "../types";
3
+ type Handler = Middleware | ErrorMiddleware;
4
+ export type FacileRouter = {
5
+ use(path: string, ...handlers: Handler[]): FacileRouter;
6
+ use(...handlers: Handler[]): FacileRouter;
7
+ get(path: string, ...handlers: Handler[]): FacileRouter;
8
+ post(path: string, ...handlers: Handler[]): FacileRouter;
9
+ put(path: string, ...handlers: Handler[]): FacileRouter;
10
+ patch(path: string, ...handlers: Handler[]): FacileRouter;
11
+ delete(path: string, ...handlers: Handler[]): FacileRouter;
12
+ handle(req: Req, res: Res, next: Next): void;
13
+ };
14
+ export declare function Router(): FacileRouter;
15
+ export {};
16
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/C,KAAK,OAAO,GAAG,UAAU,GAAG,eAAe,CAAC;AAQ5C,MAAM,MAAM,YAAY,GAAG;IACvB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IACxD,GAAG,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IAE1C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IACxD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IACzD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IACxD,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IAC1D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;IAE3D,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,MAAM,IAAI,YAAY,CAoDrC"}
@@ -0,0 +1,72 @@
1
+ import { matchPath } from "./match";
2
+ export function Router() {
3
+ const routes = [];
4
+ const add = (method, path, handlers) => {
5
+ routes.push({ method, path, handlers });
6
+ };
7
+ const router = {
8
+ use(arg1, ...rest) {
9
+ if (typeof arg1 === "string")
10
+ add("USE", arg1, rest);
11
+ else
12
+ add("USE", "", [arg1, ...rest]);
13
+ return router;
14
+ },
15
+ get(path, ...handlers) { add("GET", path, handlers); return router; },
16
+ post(path, ...handlers) { add("POST", path, handlers); return router; },
17
+ put(path, ...handlers) { add("PUT", path, handlers); return router; },
18
+ patch(path, ...handlers) { add("PATCH", path, handlers); return router; },
19
+ delete(path, ...handlers) { add("DELETE", path, handlers); return router; },
20
+ handle(req, res, next) {
21
+ let i = 0;
22
+ const runRoute = (err) => {
23
+ if (err)
24
+ return next(err);
25
+ if (i >= routes.length)
26
+ return next();
27
+ const r = routes[i++];
28
+ if (!r)
29
+ return runRoute();
30
+ const isUse = r.method === "USE";
31
+ if (isUse) {
32
+ // prefix match
33
+ if (r.path && !req.path.startsWith(r.path))
34
+ return runRoute();
35
+ return runHandlers(r.handlers, req, res, runRoute);
36
+ }
37
+ if (r.method !== (req.method || "GET"))
38
+ return runRoute();
39
+ const m = matchPath(r.path, req.path);
40
+ if (!m.ok)
41
+ return runRoute();
42
+ req.params = m.params;
43
+ return runHandlers(r.handlers, req, res, runRoute);
44
+ };
45
+ runRoute();
46
+ }
47
+ };
48
+ return router;
49
+ }
50
+ function runHandlers(handlers, req, res, next) {
51
+ let idx = 0;
52
+ const step = (err) => {
53
+ if (err)
54
+ return next(err);
55
+ if (idx >= handlers.length)
56
+ return next();
57
+ const fn = handlers[idx++];
58
+ try {
59
+ // ignore error-handlers in normal chain
60
+ if (fn.length === 4)
61
+ return step();
62
+ const ret = fn(req, res, step);
63
+ if (ret && typeof ret.then === "function")
64
+ ret.catch(step);
65
+ }
66
+ catch (e) {
67
+ step(e);
68
+ }
69
+ };
70
+ step();
71
+ }
72
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ export type Next = (err?: unknown) => void;
3
+ export type Req = IncomingMessage & {
4
+ path: string;
5
+ query: Record<string, string>;
6
+ params: Record<string, string>;
7
+ body?: unknown;
8
+ };
9
+ export type Res = ServerResponse & {
10
+ status(code: number): Res;
11
+ set(name: string, value: string): Res;
12
+ send(data?: unknown): Res;
13
+ json(obj: unknown): Res;
14
+ };
15
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE3C,MAAM,MAAM,GAAG,GAAG,eAAe,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,GAAG,GAAG,cAAc,GAAG;IAC/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC;IACtC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,GAAG,CAAC;IAC1B,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC;CAC3B,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "as-facile-js",
3
+ "version": "1.0.0",
4
+ "description": "Minimalistic API Framework with Strong Schema",
5
+ "type": "module",
6
+
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./cli": {
16
+ "import": "./dist/cli/index.js"
17
+ }
18
+ },
19
+
20
+ "bin": {
21
+ "facile-js": "dist/cli/index.js",
22
+ "facile": "dist/cli/index.js"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json && tsc -p tsconfig.cli.json && node ./scripts/copy-templates.mjs",
26
+ "dev:cli": "node --enable-source-maps dist/cli/index.js",
27
+ "test": "npm run test"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "keywords": [
33
+ "framework",
34
+ "api",
35
+ "js"
36
+ ],
37
+ "author": "@abdulsalam01",
38
+ "license": "ISC",
39
+ "devDependencies": {
40
+ "@types/node": "^25.0.3",
41
+ "typescript": "^5.9.3"
42
+ }
43
+ }