princejs 1.6.2 → 1.6.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 +1 -2
- package/dist/helpers.d.ts +8 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +1 -43
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/middleware.d.ts +10 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/prince.d.ts +65 -0
- package/dist/prince.d.ts.map +1 -0
- package/dist/prince.js +4 -4
- package/dist/scheduler.d.ts +13 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/package.json +5 -3
- package/dist/index.js +0 -260
- package/dist/validation.js +0 -27
package/Readme.md
CHANGED
|
@@ -127,13 +127,12 @@ app
|
|
|
127
127
|
## New Tree-Shakable Features
|
|
128
128
|
|
|
129
129
|
```ts
|
|
130
|
-
import { cache, email,
|
|
130
|
+
import { cache, email, upload } from "princejs/helpers";
|
|
131
131
|
import { cron, openapi } from "princejs/scheduler";
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
* `cache(60)(handler)` — In-memory cache
|
|
135
135
|
* `email(to, subject, html)` — Resend.com
|
|
136
|
-
* `ai(prompt)` — Grok / OpenAI / Hugging Face
|
|
137
136
|
* `upload()` — 1-line file upload
|
|
138
137
|
* `cron("*/2 * * * *", task)` — Cron support
|
|
139
138
|
* `openapi({ title, version })` — Auto docs
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PrinceRequest } from "./prince";
|
|
2
|
+
export declare const cache: (ttl: number) => (handler: any) => (req: PrinceRequest) => Promise<any>;
|
|
3
|
+
export declare const email: (to: string, subject: string, html: string) => Promise<void>;
|
|
4
|
+
export declare const upload: () => (req: PrinceRequest) => Promise<{
|
|
5
|
+
name: string;
|
|
6
|
+
size: number;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,MAEvB,SAAS,GAAG,MAAY,KAAK,aAAa,iBASnD,CAAC;AAGF,eAAO,MAAM,KAAK,GAAU,IAAI,MAAM,EAAE,SAAS,MAAM,EAAE,MAAM,MAAM,kBAMpE,CAAC;AAGF,eAAO,MAAM,MAAM,SACH,KAAK,aAAa;;;EAKjC,CAAC"}
|
package/dist/helpers.js
CHANGED
|
@@ -20,47 +20,6 @@ var email = async (to, subject, html) => {
|
|
|
20
20
|
body: JSON.stringify({ from: "no-reply@princejs.dev", to, subject, html })
|
|
21
21
|
});
|
|
22
22
|
};
|
|
23
|
-
var DEFAULTS = {
|
|
24
|
-
openai: { url: "https://api.openai.com/v1/chat/completions", model: "gpt-4o-mini" },
|
|
25
|
-
grok: { url: "https://api.x.ai/v1/chat/completions", model: "grok-beta" },
|
|
26
|
-
huggingface: { url: "" }
|
|
27
|
-
};
|
|
28
|
-
var ai = async (prompt, opts = {}) => {
|
|
29
|
-
const provider = opts.provider || "openai";
|
|
30
|
-
const config = DEFAULTS[provider];
|
|
31
|
-
if (provider === "huggingface" && !opts.model) {
|
|
32
|
-
throw new Error("Hugging Face requires 'model' option (e.g. meta-llama/Llama-3.3-70B-Instruct)");
|
|
33
|
-
}
|
|
34
|
-
const apiKey = opts.apiKey || Bun.env[`${provider.toUpperCase()}_API_KEY`];
|
|
35
|
-
if (!apiKey) {
|
|
36
|
-
throw new Error(`Missing ${provider.toUpperCase()}_API_KEY in environment`);
|
|
37
|
-
}
|
|
38
|
-
const url = provider === "huggingface" ? `https://api-inference.huggingface.co/models/${opts.model}` : config.url;
|
|
39
|
-
const headers = {
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
Authorization: `Bearer ${apiKey}`
|
|
42
|
-
};
|
|
43
|
-
const body = provider === "huggingface" ? { inputs: prompt, parameters: { temperature: opts.temperature ?? 0.7 } } : {
|
|
44
|
-
model: opts.model || config.model,
|
|
45
|
-
messages: [{ role: "user", content: prompt }],
|
|
46
|
-
temperature: opts.temperature ?? 0.7,
|
|
47
|
-
max_tokens: 1024
|
|
48
|
-
};
|
|
49
|
-
const res = await fetch(url, {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers,
|
|
52
|
-
body: JSON.stringify(body)
|
|
53
|
-
});
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
const err = await res.text();
|
|
56
|
-
throw new Error(`${provider} API error ${res.status}: ${err}`);
|
|
57
|
-
}
|
|
58
|
-
const data = await res.json();
|
|
59
|
-
if (provider === "huggingface") {
|
|
60
|
-
return (data[0]?.generated_text || "").trim();
|
|
61
|
-
}
|
|
62
|
-
return data.choices?.[0]?.message?.content?.trim() || "No response";
|
|
63
|
-
};
|
|
64
23
|
var upload = () => {
|
|
65
24
|
return async (req) => {
|
|
66
25
|
const form = await req.formData();
|
|
@@ -71,6 +30,5 @@ var upload = () => {
|
|
|
71
30
|
export {
|
|
72
31
|
upload,
|
|
73
32
|
email,
|
|
74
|
-
cache
|
|
75
|
-
ai
|
|
33
|
+
cache
|
|
76
34
|
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PrinceRequest } from "./prince";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
type Next = () => Promise<Response | undefined>;
|
|
4
|
+
export declare const cors: (origin?: string) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
|
|
5
|
+
export declare const logger: () => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
|
|
6
|
+
export declare const jwt: (secret: string) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
|
|
7
|
+
export declare const rateLimit: (max: number, window?: number) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
|
|
8
|
+
export declare const validate: <T>(schema: z.ZodSchema<T>) => (req: PrinceRequest, next: Next) => Promise<Response | undefined>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;AAGhD,eAAO,MAAM,IAAI,GAAI,eAAY,MACjB,KAAK,aAAa,EAAE,MAAM,IAAI,kCAe7C,CAAC;AAGF,eAAO,MAAM,MAAM,SACH,KAAK,aAAa,EAAE,MAAM,IAAI,kCAM7C,CAAC;AAGF,eAAO,MAAM,GAAG,GAAI,QAAQ,MAAM,MAClB,KAAK,aAAa,EAAE,MAAM,IAAI,kCAS7C,CAAC;AAGF,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,eAAW,MAElC,KAAK,aAAa,EAAE,MAAM,IAAI,kCAO7C,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAClC,KAAK,aAAa,EAAE,MAAM,IAAI,kCAQ7C,CAAC"}
|
package/dist/prince.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
type Next = () => Promise<Response>;
|
|
2
|
+
type Middleware = (req: PrinceRequest, next: Next) => Promise<Response | undefined> | Response | undefined;
|
|
3
|
+
type HandlerResult = Response | Record<string, any> | string | Uint8Array;
|
|
4
|
+
export interface PrinceRequest extends Request {
|
|
5
|
+
body: BodyInit | null;
|
|
6
|
+
json(): Promise<any>;
|
|
7
|
+
text(): Promise<string>;
|
|
8
|
+
formData(): Promise<FormData>;
|
|
9
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
10
|
+
user?: any;
|
|
11
|
+
params?: Record<string, string>;
|
|
12
|
+
query?: URLSearchParams;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
export interface WebSocketHandler {
|
|
16
|
+
open?: (ws: any) => void;
|
|
17
|
+
message?: (ws: any, msg: string | Buffer) => void;
|
|
18
|
+
close?: (ws: any, code?: number, reason?: string) => void;
|
|
19
|
+
drain?: (ws: any) => void;
|
|
20
|
+
}
|
|
21
|
+
type RouteHandler = (req: PrinceRequest) => Promise<HandlerResult> | HandlerResult;
|
|
22
|
+
declare class ResponseBuilder {
|
|
23
|
+
private _status;
|
|
24
|
+
private _headers;
|
|
25
|
+
private _body;
|
|
26
|
+
status(code: number): this;
|
|
27
|
+
header(key: string, value: string): this;
|
|
28
|
+
json(data: any): Response;
|
|
29
|
+
text(data: string): Response;
|
|
30
|
+
html(data: string): Response;
|
|
31
|
+
redirect(url: string, status?: number): Response;
|
|
32
|
+
stream(cb: (push: (chunk: string) => void, close: () => void) => void): Response;
|
|
33
|
+
build(): Response;
|
|
34
|
+
}
|
|
35
|
+
export declare class Prince {
|
|
36
|
+
private devMode;
|
|
37
|
+
private rawRoutes;
|
|
38
|
+
private middlewares;
|
|
39
|
+
private errorHandler?;
|
|
40
|
+
private wsRoutes;
|
|
41
|
+
private openapiData;
|
|
42
|
+
private router;
|
|
43
|
+
constructor(devMode?: boolean);
|
|
44
|
+
use(mw: Middleware): this;
|
|
45
|
+
error(fn: (err: any, req: PrinceRequest) => Response): this;
|
|
46
|
+
json(data: any, status?: number): Response;
|
|
47
|
+
response(): ResponseBuilder;
|
|
48
|
+
ws(path: string, options: Partial<WebSocketHandler>): this;
|
|
49
|
+
openapi(path?: string): this;
|
|
50
|
+
get(path: string, handler: RouteHandler): this;
|
|
51
|
+
post(path: string, handler: RouteHandler): this;
|
|
52
|
+
put(path: string, handler: RouteHandler): this;
|
|
53
|
+
delete(path: string, handler: RouteHandler): this;
|
|
54
|
+
patch(path: string, handler: RouteHandler): this;
|
|
55
|
+
private add;
|
|
56
|
+
private parseUrl;
|
|
57
|
+
private parseBody;
|
|
58
|
+
private buildRouter;
|
|
59
|
+
private compilePipeline;
|
|
60
|
+
handleFetch(req: Request): Promise<Response>;
|
|
61
|
+
listen(port?: number): void;
|
|
62
|
+
}
|
|
63
|
+
export declare const prince: (dev?: boolean) => Prince;
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=prince.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"AAEA,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpC,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC3G,KAAK,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,UAAU,CAAC;AAE1E,MAAM,WAAW,aAAc,SAAQ,OAAO;IAC5C,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IACrB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IAGpC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAOD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAClD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;CAC3B;AAED,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC;AAiBnF,cAAM,eAAe;IACnB,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,KAAK,CAAa;IAE1B,MAAM,CAAC,IAAI,EAAE,MAAM;IAKnB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAKjC,IAAI,CAAC,IAAI,EAAE,GAAG;IAMd,IAAI,CAAC,IAAI,EAAE,MAAM;IAMjB,IAAI,CAAC,IAAI,EAAE,MAAM;IAMjB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAM;IAMlC,MAAM,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI;IAarE,KAAK;CAGN;AAED,qBAAa,MAAM;IAQL,OAAO,CAAC,OAAO;IAP3B,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,YAAY,CAAC,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,MAAM,CAAyB;gBAEnB,OAAO,UAAQ;IAEnC,GAAG,CAAC,EAAE,EAAE,UAAU;IAKlB,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,KAAK,QAAQ;IAKpD,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,SAAM;IAO5B,QAAQ;IAIR,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAKnD,OAAO,CAAC,IAAI,SAAU;IAuBtB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IACxC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IACvC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IAC1C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IAEzC,OAAO,CAAC,GAAG;IASX,OAAO,CAAC,QAAQ;YAOF,SAAS;IAyBvB,OAAO,CAAC,WAAW;IA8BnB,OAAO,CAAC,eAAe;IAsCjB,WAAW,CAAC,GAAG,EAAE,OAAO;IA2B9B,MAAM,CAAC,IAAI,SAAO;CAkCnB;AAED,eAAO,MAAM,MAAM,GAAI,aAAW,WAAoB,CAAC"}
|
package/dist/prince.js
CHANGED
|
@@ -260,16 +260,16 @@ class Prince {
|
|
|
260
260
|
},
|
|
261
261
|
websocket: {
|
|
262
262
|
open(ws) {
|
|
263
|
-
ws.data
|
|
263
|
+
ws.data?.ws?.open?.(ws);
|
|
264
264
|
},
|
|
265
265
|
message(ws, msg) {
|
|
266
|
-
ws.data
|
|
266
|
+
ws.data?.ws?.message?.(ws, msg);
|
|
267
267
|
},
|
|
268
268
|
close(ws, code, reason) {
|
|
269
|
-
ws.data
|
|
269
|
+
ws.data?.ws?.close?.(ws, code, reason);
|
|
270
270
|
},
|
|
271
271
|
drain(ws) {
|
|
272
|
-
ws.data
|
|
272
|
+
ws.data?.ws?.drain?.(ws);
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const cron: (pattern: string, task: () => void) => void;
|
|
2
|
+
export declare const openapi: (info: {
|
|
3
|
+
title: string;
|
|
4
|
+
version: string;
|
|
5
|
+
}) => {
|
|
6
|
+
openapi: string;
|
|
7
|
+
info: {
|
|
8
|
+
title: string;
|
|
9
|
+
version: string;
|
|
10
|
+
};
|
|
11
|
+
paths: {};
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI,GAAI,SAAS,MAAM,EAAE,MAAM,MAAM,IAAI,SAiCrD,CAAC;AAGF,eAAO,MAAM,OAAO,GAAI,MAAM;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;;eAAzB,MAAM;iBAAW,MAAM;;;CAE7D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "princejs",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.6",
|
|
4
4
|
"description": "An easy and fast backend framework — by a 13yo developer, for developers.",
|
|
5
5
|
"main": "dist/prince.js",
|
|
6
6
|
"types": "dist/prince.d.ts",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/bun": "^1.3.2",
|
|
64
64
|
"bun-types": "latest",
|
|
65
|
-
"typescript": "^5.9.3",
|
|
66
65
|
"fast-jwt": "^5.0.0",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
67
|
"zod": "^4.1.12"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
|
-
"build": "bun build src/prince.ts --outdir dist --target bun && bun build src/middleware.ts --outdir dist --target bun && bun build src/helpers.ts --outdir dist --target bun && bun build
|
|
78
|
+
"build:js": "bun build src/prince.ts --outdir dist --target bun && bun build src/middleware.ts --outdir dist --target bun && bun build src/helpers.ts --outdir dist --target bun && bun build src/scheduler.ts --outdir dist --target bun && bun build bin/create.ts --outdir dist --target bun --format esm",
|
|
79
|
+
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
80
|
+
"build": "bun run build:js && bun run build:types"
|
|
79
81
|
}
|
|
80
82
|
}
|
package/dist/index.js
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/prince.ts
|
|
3
|
-
class TrieNode {
|
|
4
|
-
children = Object.create(null);
|
|
5
|
-
paramChild;
|
|
6
|
-
wildcardChild;
|
|
7
|
-
catchAllChild;
|
|
8
|
-
handlers = null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class ResponseBuilder {
|
|
12
|
-
_status = 200;
|
|
13
|
-
_headers = {};
|
|
14
|
-
_body = null;
|
|
15
|
-
status(code) {
|
|
16
|
-
this._status = code;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
header(key, value) {
|
|
20
|
-
this._headers[key] = value;
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
json(data) {
|
|
24
|
-
this._headers["Content-Type"] = "application/json";
|
|
25
|
-
this._body = JSON.stringify(data);
|
|
26
|
-
return this.build();
|
|
27
|
-
}
|
|
28
|
-
text(data) {
|
|
29
|
-
this._headers["Content-Type"] = "text/plain";
|
|
30
|
-
this._body = data;
|
|
31
|
-
return this.build();
|
|
32
|
-
}
|
|
33
|
-
html(data) {
|
|
34
|
-
this._headers["Content-Type"] = "text/html";
|
|
35
|
-
this._body = data;
|
|
36
|
-
return this.build();
|
|
37
|
-
}
|
|
38
|
-
redirect(url, status = 302) {
|
|
39
|
-
this._status = status;
|
|
40
|
-
this._headers["Location"] = url;
|
|
41
|
-
return this.build();
|
|
42
|
-
}
|
|
43
|
-
stream(cb) {
|
|
44
|
-
const encoder = new TextEncoder;
|
|
45
|
-
const stream = new ReadableStream({
|
|
46
|
-
start(controller) {
|
|
47
|
-
cb((chunk) => controller.enqueue(encoder.encode(chunk)), () => controller.close());
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
return new Response(stream, { status: this._status, headers: this._headers });
|
|
51
|
-
}
|
|
52
|
-
build() {
|
|
53
|
-
return new Response(this._body, { status: this._status, headers: this._headers });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
class Prince {
|
|
58
|
-
devMode;
|
|
59
|
-
rawRoutes = [];
|
|
60
|
-
middlewares = [];
|
|
61
|
-
errorHandler;
|
|
62
|
-
wsRoutes = {};
|
|
63
|
-
openapiData = null;
|
|
64
|
-
constructor(devMode = false) {
|
|
65
|
-
this.devMode = devMode;
|
|
66
|
-
}
|
|
67
|
-
use(mw) {
|
|
68
|
-
this.middlewares.push(mw);
|
|
69
|
-
return this;
|
|
70
|
-
}
|
|
71
|
-
error(fn) {
|
|
72
|
-
this.errorHandler = fn;
|
|
73
|
-
return this;
|
|
74
|
-
}
|
|
75
|
-
json(data, status = 200) {
|
|
76
|
-
return new Response(JSON.stringify(data), {
|
|
77
|
-
status,
|
|
78
|
-
headers: { "Content-Type": "application/json" }
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
response() {
|
|
82
|
-
return new ResponseBuilder;
|
|
83
|
-
}
|
|
84
|
-
ws(path, options) {
|
|
85
|
-
this.wsRoutes[path] = options;
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
openapi(path = "/docs") {
|
|
89
|
-
const paths = {};
|
|
90
|
-
for (const route of this.rawRoutes) {
|
|
91
|
-
paths[route.path] ??= {};
|
|
92
|
-
paths[route.path][route.method.toLowerCase()] = {
|
|
93
|
-
summary: "",
|
|
94
|
-
responses: { 200: { description: "OK" } }
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
this.openapiData = {
|
|
98
|
-
openapi: "3.1.0",
|
|
99
|
-
info: { title: "PrinceJS API", version: "1.0.0" },
|
|
100
|
-
paths
|
|
101
|
-
};
|
|
102
|
-
this.get(path, () => this.openapiData);
|
|
103
|
-
return this;
|
|
104
|
-
}
|
|
105
|
-
get(path, handler) {
|
|
106
|
-
return this.add("GET", path, handler);
|
|
107
|
-
}
|
|
108
|
-
post(path, handler) {
|
|
109
|
-
return this.add("POST", path, handler);
|
|
110
|
-
}
|
|
111
|
-
put(path, handler) {
|
|
112
|
-
return this.add("PUT", path, handler);
|
|
113
|
-
}
|
|
114
|
-
delete(path, handler) {
|
|
115
|
-
return this.add("DELETE", path, handler);
|
|
116
|
-
}
|
|
117
|
-
patch(path, handler) {
|
|
118
|
-
return this.add("PATCH", path, handler);
|
|
119
|
-
}
|
|
120
|
-
add(method, path, handler) {
|
|
121
|
-
if (!path.startsWith("/"))
|
|
122
|
-
path = "/" + path;
|
|
123
|
-
if (path !== "/" && path.endsWith("/"))
|
|
124
|
-
path = path.slice(0, -1);
|
|
125
|
-
const parts = path === "/" ? [""] : path.split("/").slice(1);
|
|
126
|
-
this.rawRoutes.push({ method, path, parts, handler });
|
|
127
|
-
return this;
|
|
128
|
-
}
|
|
129
|
-
parseUrl(req) {
|
|
130
|
-
const url = new URL(req.url);
|
|
131
|
-
const query = {};
|
|
132
|
-
for (const [k, v] of url.searchParams.entries())
|
|
133
|
-
query[k] = v;
|
|
134
|
-
return { pathname: url.pathname, query };
|
|
135
|
-
}
|
|
136
|
-
async parseBody(req) {
|
|
137
|
-
const ct = req.headers.get("content-type") || "";
|
|
138
|
-
if (ct.includes("application/json"))
|
|
139
|
-
return await req.json();
|
|
140
|
-
if (ct.includes("application/x-www-form-urlencoded"))
|
|
141
|
-
return Object.fromEntries(new URLSearchParams(await req.text()).entries());
|
|
142
|
-
if (ct.startsWith("multipart/form-data")) {
|
|
143
|
-
const fd = await req.formData();
|
|
144
|
-
const files = {};
|
|
145
|
-
const fields = {};
|
|
146
|
-
for (const [k, v] of fd.entries()) {
|
|
147
|
-
if (v instanceof File)
|
|
148
|
-
files[k] = v;
|
|
149
|
-
else
|
|
150
|
-
fields[k] = v;
|
|
151
|
-
}
|
|
152
|
-
return { files, fields };
|
|
153
|
-
}
|
|
154
|
-
if (ct.startsWith("text/"))
|
|
155
|
-
return await req.text();
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
buildRouter() {
|
|
159
|
-
const root = new TrieNode;
|
|
160
|
-
for (const r of this.rawRoutes) {
|
|
161
|
-
let node = root;
|
|
162
|
-
if (r.parts.length === 1 && r.parts[0] === "") {
|
|
163
|
-
node.handlers ??= {};
|
|
164
|
-
node.handlers[r.method] = r.handler;
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
for (const part of r.parts) {
|
|
168
|
-
if (part.startsWith(":")) {
|
|
169
|
-
const name = part.slice(1);
|
|
170
|
-
node.paramChild ??= { name, node: new TrieNode };
|
|
171
|
-
node = node.paramChild.node;
|
|
172
|
-
} else {
|
|
173
|
-
node.children[part] ??= new TrieNode;
|
|
174
|
-
node = node.children[part];
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
node.handlers ??= {};
|
|
178
|
-
node.handlers[r.method] = r.handler;
|
|
179
|
-
}
|
|
180
|
-
return root;
|
|
181
|
-
}
|
|
182
|
-
compilePipeline(handler) {
|
|
183
|
-
return async (req, params, query) => {
|
|
184
|
-
req.params = params;
|
|
185
|
-
req.query = query;
|
|
186
|
-
let i = 0;
|
|
187
|
-
const next = async () => {
|
|
188
|
-
if (i < this.middlewares.length) {
|
|
189
|
-
return await this.middlewares[i++](req, next) ?? new Response("");
|
|
190
|
-
}
|
|
191
|
-
if (["POST", "PUT", "PATCH"].includes(req.method))
|
|
192
|
-
req.body = await this.parseBody(req);
|
|
193
|
-
const res = await handler(req);
|
|
194
|
-
if (res instanceof Response)
|
|
195
|
-
return res;
|
|
196
|
-
if (typeof res === "string")
|
|
197
|
-
return new Response(res);
|
|
198
|
-
return this.json(res);
|
|
199
|
-
};
|
|
200
|
-
return next();
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
async handleFetch(req) {
|
|
204
|
-
const { pathname, query } = this.parseUrl(req);
|
|
205
|
-
const r = req;
|
|
206
|
-
const segments = pathname === "/" ? [] : pathname.slice(1).split("/");
|
|
207
|
-
let node = this.buildRouter();
|
|
208
|
-
let params = {};
|
|
209
|
-
for (const seg of segments) {
|
|
210
|
-
if (node.children[seg])
|
|
211
|
-
node = node.children[seg];
|
|
212
|
-
else if (node.paramChild) {
|
|
213
|
-
params[node.paramChild.name] = seg;
|
|
214
|
-
node = node.paramChild.node;
|
|
215
|
-
} else
|
|
216
|
-
return this.json({ error: "Not Found" }, 404);
|
|
217
|
-
}
|
|
218
|
-
const handler = node.handlers?.[req.method];
|
|
219
|
-
if (!handler)
|
|
220
|
-
return this.json({ error: "Method Not Allowed" }, 405);
|
|
221
|
-
const pipeline = this.compilePipeline(handler);
|
|
222
|
-
return pipeline(r, params, query);
|
|
223
|
-
}
|
|
224
|
-
listen(port = 3000) {
|
|
225
|
-
const self = this;
|
|
226
|
-
Bun.serve({
|
|
227
|
-
port,
|
|
228
|
-
fetch(req, server) {
|
|
229
|
-
const { pathname } = new URL(req.url);
|
|
230
|
-
const ws = self.wsRoutes[pathname];
|
|
231
|
-
if (ws) {
|
|
232
|
-
server.upgrade(req, { data: { ws } });
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
return self.handleFetch(req).catch((err) => {
|
|
236
|
-
if (self.errorHandler)
|
|
237
|
-
return self.errorHandler(err, req);
|
|
238
|
-
return self.json({ error: String(err) }, 500);
|
|
239
|
-
});
|
|
240
|
-
},
|
|
241
|
-
websocket: {
|
|
242
|
-
open(ws) {
|
|
243
|
-
ws.data.ws?.open?.(ws);
|
|
244
|
-
},
|
|
245
|
-
message(ws, msg) {
|
|
246
|
-
ws.data.ws?.message?.(ws, msg);
|
|
247
|
-
},
|
|
248
|
-
close(ws) {
|
|
249
|
-
ws.data.ws?.close?.(ws);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
console.log(`\uD83D\uDE80 PrinceJS running http://localhost:${port}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
var prince = (dev = false) => new Prince(dev);
|
|
257
|
-
export {
|
|
258
|
-
prince,
|
|
259
|
-
Prince
|
|
260
|
-
};
|
package/dist/validation.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/validation.ts
|
|
3
|
-
var validate = (schema, source = "body") => {
|
|
4
|
-
return async (req, next) => {
|
|
5
|
-
try {
|
|
6
|
-
const data = source === "body" ? req.body : source === "query" ? req.query : req.params;
|
|
7
|
-
const validated = schema.parse(data);
|
|
8
|
-
req[`validated${source.charAt(0).toUpperCase() + source.slice(1)}`] = validated;
|
|
9
|
-
if (next) {
|
|
10
|
-
const result = await next();
|
|
11
|
-
return result;
|
|
12
|
-
}
|
|
13
|
-
return;
|
|
14
|
-
} catch (err) {
|
|
15
|
-
return new Response(JSON.stringify({
|
|
16
|
-
error: "Validation failed",
|
|
17
|
-
details: err.errors || err.message
|
|
18
|
-
}), {
|
|
19
|
-
status: 400,
|
|
20
|
-
headers: { "Content-Type": "application/json" }
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
export {
|
|
26
|
-
validate
|
|
27
|
-
};
|