princejs 1.7.0 → 1.7.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/dist/prince.d.ts CHANGED
@@ -2,22 +2,13 @@ type Next = () => Promise<Response>;
2
2
  type Middleware = (req: PrinceRequest, next: Next) => Promise<Response | undefined> | Response | undefined;
3
3
  type HandlerResult = Response | Record<string, any> | string | Uint8Array;
4
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>;
5
+ parsedBody?: any;
6
+ files?: Record<string, File>;
10
7
  user?: any;
11
8
  params?: Record<string, string>;
12
9
  query?: URLSearchParams;
13
10
  [key: string]: any;
14
11
  }
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
12
  type RouteHandler = (req: PrinceRequest) => Promise<HandlerResult> | HandlerResult;
22
13
  declare class ResponseBuilder {
23
14
  private _status;
@@ -29,7 +20,6 @@ declare class ResponseBuilder {
29
20
  text(data: string): Response;
30
21
  html(data: string): Response;
31
22
  redirect(url: string, status?: number): Response;
32
- stream(cb: (push: (chunk: string) => void, close: () => void) => void): Response;
33
23
  build(): Response;
34
24
  }
35
25
  export declare class Prince {
@@ -40,24 +30,27 @@ export declare class Prince {
40
30
  private wsRoutes;
41
31
  private openapiData;
42
32
  private router;
33
+ private staticRoutes;
34
+ private routeCache;
43
35
  constructor(devMode?: boolean);
44
36
  use(mw: Middleware): this;
45
37
  error(fn: (err: any, req: PrinceRequest) => Response): this;
46
38
  json(data: any, status?: number): Response;
47
39
  response(): ResponseBuilder;
48
- ws(path: string, options: Partial<WebSocketHandler>): this;
49
- openapi(path?: string): this;
50
40
  get(path: string, handler: RouteHandler): this;
51
41
  post(path: string, handler: RouteHandler): this;
52
42
  put(path: string, handler: RouteHandler): this;
53
43
  delete(path: string, handler: RouteHandler): this;
54
44
  patch(path: string, handler: RouteHandler): this;
45
+ options(path: string, handler: RouteHandler): this;
55
46
  private add;
56
- private isWildcard;
57
- private parseUrl;
58
- private parseBody;
59
47
  private buildRouter;
60
- private compilePipeline;
48
+ private insertRoute;
49
+ private findRoute;
50
+ private matchPath;
51
+ private matchRoute;
52
+ private parseBody;
53
+ private executeHandler;
61
54
  handleFetch(req: Request): Promise<Response>;
62
55
  fetch(req: Request): Promise<Response>;
63
56
  listen(port?: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"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"}
package/dist/prince.js CHANGED
@@ -1,13 +1,5 @@
1
1
  // @bun
2
2
  // src/prince.ts
3
- class TrieNode {
4
- children = Object.create(null);
5
- paramChild;
6
- wildcardChild;
7
- catchAllChild;
8
- handlers = null;
9
- }
10
-
11
3
  class ResponseBuilder {
12
4
  _status = 200;
13
5
  _headers = {};
@@ -40,15 +32,6 @@ class ResponseBuilder {
40
32
  this._headers["Location"] = url;
41
33
  return this.build();
42
34
  }
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
35
  build() {
53
36
  return new Response(this._body, { status: this._status, headers: this._headers });
54
37
  }
@@ -62,6 +45,8 @@ class Prince {
62
45
  wsRoutes = {};
63
46
  openapiData = null;
64
47
  router = null;
48
+ staticRoutes = new Map;
49
+ routeCache = new Map;
65
50
  constructor(devMode = false) {
66
51
  this.devMode = devMode;
67
52
  }
@@ -82,27 +67,6 @@ class Prince {
82
67
  response() {
83
68
  return new ResponseBuilder;
84
69
  }
85
- ws(path, options) {
86
- this.wsRoutes[path] = options;
87
- return this;
88
- }
89
- openapi(path = "/docs") {
90
- const paths = {};
91
- for (const route of this.rawRoutes) {
92
- paths[route.path] ??= {};
93
- paths[route.path][route.method.toLowerCase()] = {
94
- summary: "",
95
- responses: { 200: { description: "OK" } }
96
- };
97
- }
98
- this.openapiData = {
99
- openapi: "3.1.0",
100
- info: { title: "PrinceJS API", version: "1.0.0" },
101
- paths
102
- };
103
- this.get(path, () => this.openapiData);
104
- return this;
105
- }
106
70
  get(path, handler) {
107
71
  return this.add("GET", path, handler);
108
72
  }
@@ -118,6 +82,9 @@ class Prince {
118
82
  patch(path, handler) {
119
83
  return this.add("PATCH", path, handler);
120
84
  }
85
+ options(path, handler) {
86
+ return this.add("OPTIONS", path, handler);
87
+ }
121
88
  add(method, path, handler) {
122
89
  if (!path.startsWith("/"))
123
90
  path = "/" + path;
@@ -125,175 +92,257 @@ class Prince {
125
92
  path = path.slice(0, -1);
126
93
  const parts = path === "/" ? [""] : path.split("/").slice(1);
127
94
  this.rawRoutes.push({ method, path, parts, handler });
128
- if (method !== "OPTIONS" && !this.rawRoutes.some((r) => r.path === path && r.method === "OPTIONS")) {
129
- this.rawRoutes.push({
130
- method: "OPTIONS",
131
- path,
132
- parts,
133
- handler: () => new Response(null, { status: 204 })
134
- });
95
+ const isStaticRoute = !parts.some((part) => part.includes(":") || part.includes("*") || part.includes("("));
96
+ if (isStaticRoute) {
97
+ const staticKey = `${method}:${path}`;
98
+ this.staticRoutes.set(staticKey, handler);
135
99
  }
100
+ this.routeCache.clear();
136
101
  this.router = null;
137
102
  return this;
138
103
  }
139
- isWildcard(part) {
140
- return part === "*" || part === "**";
104
+ buildRouter() {
105
+ if (this.router)
106
+ return this.router;
107
+ const root = {
108
+ pattern: "",
109
+ handlers: {},
110
+ children: []
111
+ };
112
+ for (const route of this.rawRoutes) {
113
+ if (this.staticRoutes.has(`${route.method}:${route.path}`)) {
114
+ continue;
115
+ }
116
+ this.insertRoute(root, route);
117
+ }
118
+ this.router = root;
119
+ return root;
141
120
  }
142
- parseUrl(req) {
143
- const url = new URL(req.url);
144
- const query = {};
145
- for (const [k, v] of url.searchParams.entries())
146
- query[k] = v;
147
- return { pathname: url.pathname, query };
121
+ insertRoute(node, route) {
122
+ let currentNode = node;
123
+ for (let i = 0;i < route.parts.length; i++) {
124
+ const part = route.parts[i];
125
+ let found = false;
126
+ for (const child of currentNode.children) {
127
+ if (child.pattern === part) {
128
+ currentNode = child;
129
+ found = true;
130
+ break;
131
+ }
132
+ }
133
+ if (!found) {
134
+ const newNode = {
135
+ pattern: part,
136
+ handlers: {},
137
+ children: []
138
+ };
139
+ if (part.startsWith(":")) {
140
+ newNode.paramName = part.slice(1);
141
+ } else if (part === "*") {
142
+ newNode.isWildcard = true;
143
+ } else if (part === "**") {
144
+ newNode.isCatchAll = true;
145
+ }
146
+ currentNode.children.push(newNode);
147
+ currentNode = newNode;
148
+ }
149
+ }
150
+ currentNode.handlers[route.method] = route.handler;
148
151
  }
149
- async parseBody(req) {
150
- const ct = req.headers.get("content-type") || "";
151
- if (ct.includes("application/json"))
152
- return await req.json();
153
- if (ct.includes("application/x-www-form-urlencoded"))
154
- return Object.fromEntries(new URLSearchParams(await req.text()).entries());
155
- if (ct.startsWith("multipart/form-data")) {
156
- const fd = await req.formData();
157
- const files = {};
158
- const fields = {};
159
- for (const [k, v] of fd.entries()) {
160
- if (v instanceof File)
161
- files[k] = v;
162
- else
163
- fields[k] = v;
152
+ findRoute(method, pathname) {
153
+ const cacheKey = `${method}:${pathname}`;
154
+ if (this.routeCache.has(cacheKey)) {
155
+ return this.routeCache.get(cacheKey);
156
+ }
157
+ const staticKey = `${method}:${pathname}`;
158
+ const staticHandler = this.staticRoutes.get(staticKey);
159
+ if (staticHandler) {
160
+ const result2 = { handler: staticHandler, params: {} };
161
+ this.routeCache.set(cacheKey, result2);
162
+ return result2;
163
+ }
164
+ const allowedMethods = new Set;
165
+ let pathExists = false;
166
+ for (const route of this.rawRoutes) {
167
+ if (this.matchPath(route.path, pathname)) {
168
+ pathExists = true;
169
+ allowedMethods.add(route.method);
164
170
  }
165
- return { files, fields };
166
171
  }
167
- if (ct.startsWith("text/"))
168
- return await req.text();
172
+ if (!pathExists) {
173
+ this.routeCache.set(cacheKey, null);
174
+ return null;
175
+ }
176
+ const segments = pathname === "/" ? [""] : pathname.split("/").slice(1);
177
+ const result = this.matchRoute(this.buildRouter(), segments, method);
178
+ if (result) {
179
+ this.routeCache.set(cacheKey, result);
180
+ return result;
181
+ }
182
+ if (pathExists) {
183
+ const methodNotAllowed = {
184
+ handler: null,
185
+ params: {},
186
+ allowedMethods: Array.from(allowedMethods)
187
+ };
188
+ this.routeCache.set(cacheKey, methodNotAllowed);
189
+ return methodNotAllowed;
190
+ }
191
+ this.routeCache.set(cacheKey, null);
169
192
  return null;
170
193
  }
171
- buildRouter() {
172
- if (this.router)
173
- return this.router;
174
- const root = new TrieNode;
175
- const sortedRoutes = [...this.rawRoutes].sort((a, b) => {
176
- const aHasWildcard = a.parts.some((p) => this.isWildcard(p));
177
- const bHasWildcard = b.parts.some((p) => this.isWildcard(p));
178
- const aHasParam = a.parts.some((p) => p.startsWith(":"));
179
- const bHasParam = b.parts.some((p) => p.startsWith(":"));
180
- if (aHasWildcard && !bHasWildcard)
181
- return 1;
182
- if (!aHasWildcard && bHasWildcard)
183
- return -1;
184
- if (aHasParam && !bHasParam)
185
- return 1;
186
- if (!aHasParam && bHasParam)
187
- return -1;
188
- return 0;
189
- });
190
- for (const r of sortedRoutes) {
191
- let node = root;
192
- if (r.parts.length === 1 && r.parts[0] === "") {
193
- node.handlers ??= {};
194
- node.handlers[r.method] = r.handler;
194
+ matchPath(routePath, requestPath) {
195
+ const routeParts = routePath === "/" ? [""] : routePath.split("/").slice(1);
196
+ const requestParts = requestPath === "/" ? [""] : requestPath.split("/").slice(1);
197
+ if (routeParts.length !== requestParts.length) {
198
+ if (routeParts.includes("**")) {
199
+ return true;
200
+ }
201
+ return false;
202
+ }
203
+ for (let i = 0;i < routeParts.length; i++) {
204
+ const routePart = routeParts[i];
205
+ const requestPart = requestParts[i];
206
+ if (routePart.startsWith(":") || routePart === "*" || routePart === "**") {
195
207
  continue;
196
208
  }
197
- for (let i = 0;i < r.parts.length; i++) {
198
- const part = r.parts[i];
199
- if (part === "*") {
200
- node.wildcardChild ??= new TrieNode;
201
- node = node.wildcardChild;
202
- } else if (part === "**") {
203
- node.catchAllChild ??= { node: new TrieNode };
204
- node = node.catchAllChild.node;
205
- } else if (part.startsWith(":")) {
206
- const name = part.slice(1);
207
- node.paramChild ??= { name, node: new TrieNode };
208
- node = node.paramChild.node;
209
- } else {
210
- node.children[part] ??= new TrieNode;
211
- node = node.children[part];
209
+ if (routePart !== requestPart) {
210
+ return false;
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+ matchRoute(node, segments, method, params = {}, index = 0) {
216
+ if (index === segments.length) {
217
+ const handler = node.handlers[method];
218
+ return handler ? { handler, params } : null;
219
+ }
220
+ const segment = segments[index];
221
+ for (const child of node.children) {
222
+ if (!child.paramName && !child.isWildcard && !child.isCatchAll) {
223
+ if (child.pattern === segment) {
224
+ const result = this.matchRoute(child, segments, method, params, index + 1);
225
+ if (result)
226
+ return result;
212
227
  }
213
228
  }
214
- node.handlers ??= {};
215
- node.handlers[r.method] = r.handler;
216
229
  }
217
- this.router = root;
218
- return root;
230
+ for (const child of node.children) {
231
+ if (child.paramName) {
232
+ params[child.paramName] = segment;
233
+ const result = this.matchRoute(child, segments, method, params, index + 1);
234
+ if (result)
235
+ return result;
236
+ delete params[child.paramName];
237
+ }
238
+ }
239
+ for (const child of node.children) {
240
+ if (child.isWildcard) {
241
+ const result = this.matchRoute(child, segments, method, params, index + 1);
242
+ if (result)
243
+ return result;
244
+ }
245
+ }
246
+ for (const child of node.children) {
247
+ if (child.isCatchAll) {
248
+ const handler = child.handlers[method];
249
+ if (handler) {
250
+ return { handler, params };
251
+ }
252
+ }
253
+ }
254
+ return null;
219
255
  }
220
- compilePipeline(handler) {
221
- return async (req, params, query) => {
222
- Object.defineProperty(req, "params", { value: params, writable: true, configurable: true });
223
- Object.defineProperty(req, "query", { value: query, writable: true, configurable: true });
224
- if (["POST", "PUT", "PATCH"].includes(req.method)) {
225
- const parsed = await this.parseBody(req);
226
- if (parsed && typeof parsed === "object" && "files" in parsed && "fields" in parsed) {
227
- Object.defineProperty(req, "body", { value: parsed.fields, writable: true, configurable: true });
256
+ async parseBody(req) {
257
+ const ct = req.headers.get("content-type") || "";
258
+ const clonedReq = req.clone();
259
+ try {
260
+ if (ct.includes("application/json")) {
261
+ return await clonedReq.json();
262
+ }
263
+ if (ct.includes("application/x-www-form-urlencoded")) {
264
+ const text = await clonedReq.text();
265
+ return Object.fromEntries(new URLSearchParams(text));
266
+ }
267
+ if (ct.startsWith("multipart/form-data")) {
268
+ const fd = await clonedReq.formData();
269
+ const files = {};
270
+ const fields = {};
271
+ for (const [k, v] of fd.entries()) {
272
+ if (v instanceof File)
273
+ files[k] = v;
274
+ else
275
+ fields[k] = v;
276
+ }
277
+ return { files, fields };
278
+ }
279
+ if (ct.startsWith("text/")) {
280
+ return await clonedReq.text();
281
+ }
282
+ } catch (error) {
283
+ console.error("Body parsing error:", error);
284
+ return null;
285
+ }
286
+ return null;
287
+ }
288
+ async executeHandler(req, handler, params, query) {
289
+ Object.defineProperty(req, "params", { value: params, writable: true, configurable: true });
290
+ Object.defineProperty(req, "query", { value: query, writable: true, configurable: true });
291
+ if (["POST", "PUT", "PATCH"].includes(req.method)) {
292
+ const parsed = await this.parseBody(req);
293
+ if (parsed) {
294
+ if (typeof parsed === "object" && "files" in parsed && "fields" in parsed) {
295
+ Object.defineProperty(req, "parsedBody", { value: parsed.fields, writable: true, configurable: true });
228
296
  Object.defineProperty(req, "files", { value: parsed.files, writable: true, configurable: true });
229
297
  } else {
230
- Object.defineProperty(req, "body", { value: parsed, writable: true, configurable: true });
298
+ Object.defineProperty(req, "parsedBody", { value: parsed, writable: true, configurable: true });
231
299
  }
232
300
  }
233
- let i = 0;
234
- const next = async () => {
235
- while (i < this.middlewares.length) {
236
- const result = await this.middlewares[i++](req, next);
237
- if (result instanceof Response)
238
- return result;
239
- }
240
- const res = await handler(req);
241
- if (res instanceof Response)
242
- return res;
243
- if (typeof res === "string")
244
- return new Response(res);
245
- if (res instanceof Uint8Array)
246
- return new Response(res);
247
- return this.json(res);
248
- };
249
- return next();
301
+ }
302
+ Object.defineProperty(req, "body", {
303
+ get: () => req.parsedBody,
304
+ set: (value) => {
305
+ req.parsedBody = value;
306
+ },
307
+ configurable: true
308
+ });
309
+ let i = 0;
310
+ const next = async () => {
311
+ while (i < this.middlewares.length) {
312
+ const result = await this.middlewares[i++](req, next);
313
+ if (result instanceof Response)
314
+ return result;
315
+ }
316
+ const res = await handler(req);
317
+ if (res instanceof Response)
318
+ return res;
319
+ if (typeof res === "string")
320
+ return new Response(res);
321
+ if (res instanceof Uint8Array)
322
+ return new Response(res);
323
+ return this.json(res);
250
324
  };
325
+ return next();
251
326
  }
252
327
  async handleFetch(req) {
253
328
  const url = new URL(req.url);
254
329
  const r = req;
255
- if (req.method === "OPTIONS") {
256
- return new Response(null, {
257
- status: 204,
330
+ const method = req.method;
331
+ const pathname = url.pathname;
332
+ const routeMatch = this.findRoute(method, pathname);
333
+ if (!routeMatch) {
334
+ return this.json({ error: "Not Found" }, 404);
335
+ }
336
+ if (routeMatch.allowedMethods && !routeMatch.handler) {
337
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
338
+ status: 405,
258
339
  headers: {
259
- "Access-Control-Allow-Origin": "*",
260
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
261
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
262
- "Access-Control-Max-Age": "86400"
340
+ Allow: routeMatch.allowedMethods.join(", "),
341
+ "Content-Type": "application/json"
263
342
  }
264
343
  });
265
344
  }
266
- Object.defineProperty(r, "query", {
267
- value: url.searchParams,
268
- writable: true,
269
- configurable: true
270
- });
271
- const pathname = url.pathname;
272
- const segments = pathname === "/" ? [] : pathname.slice(1).split("/");
273
- const router = this.buildRouter();
274
- let node = router;
275
- let params = {};
276
- for (let i = 0;i < segments.length; i++) {
277
- const seg = segments[i];
278
- if (node.children[seg]) {
279
- node = node.children[seg];
280
- } else if (node.paramChild) {
281
- params[node.paramChild.name] = seg;
282
- node = node.paramChild.node;
283
- } else if (node.wildcardChild) {
284
- node = node.wildcardChild;
285
- } else if (node.catchAllChild) {
286
- node = node.catchAllChild.node;
287
- break;
288
- } else {
289
- return this.json({ error: "Not Found" }, 404);
290
- }
291
- }
292
- const handler = node.handlers?.[req.method];
293
- if (!handler)
294
- return this.json({ error: "Method Not Allowed" }, 405);
295
- const pipeline = this.compilePipeline(handler);
296
- return pipeline(r, params, new URLSearchParams(url.search));
345
+ return this.executeHandler(r, routeMatch.handler, routeMatch.params, url.searchParams);
297
346
  }
298
347
  async fetch(req) {
299
348
  try {
@@ -312,36 +361,7 @@ class Prince {
312
361
  const self = this;
313
362
  Bun.serve({
314
363
  port,
315
- fetch(req, server) {
316
- const { pathname } = new URL(req.url);
317
- const ws = self.wsRoutes[pathname];
318
- if (ws && server.upgrade(req, { data: { ws } })) {
319
- return;
320
- }
321
- return self.handleFetch(req).catch((err) => {
322
- if (self.errorHandler)
323
- return self.errorHandler(err, req);
324
- if (self.devMode) {
325
- console.error("Error:", err);
326
- return self.json({ error: String(err), stack: err.stack }, 500);
327
- }
328
- return self.json({ error: "Internal Server Error" }, 500);
329
- });
330
- },
331
- websocket: {
332
- open(ws) {
333
- ws.data?.ws?.open?.(ws);
334
- },
335
- message(ws, msg) {
336
- ws.data?.ws?.message?.(ws, msg);
337
- },
338
- close(ws, code, reason) {
339
- ws.data?.ws?.close?.(ws, code, reason);
340
- },
341
- drain(ws) {
342
- ws.data?.ws?.drain?.(ws);
343
- }
344
- }
364
+ fetch: (req, server) => self.fetch(req)
345
365
  });
346
366
  console.log(`\uD83D\uDE80 PrinceJS running on http://localhost:${port}`);
347
367
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "princejs",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
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",
@@ -24,6 +24,10 @@
24
24
  "./scheduler": {
25
25
  "import": "./dist/scheduler.js",
26
26
  "types": "./dist/scheduler.d.ts"
27
+ },
28
+ "./jsx": {
29
+ "import": "./dist/jsx.js",
30
+ "types": "./dist/jsx.d.ts"
27
31
  }
28
32
  },
29
33
  "files": [
@@ -44,7 +48,9 @@
44
48
  "websocket",
45
49
  "middleware",
46
50
  "scheduler",
47
- "fast"
51
+ "fast",
52
+ "lightweight",
53
+ "ssr"
48
54
  ],
49
55
  "author": "Matthew Michael (MatthewTheCoder1218)",
50
56
  "license": "MIT",
@@ -80,7 +86,7 @@
80
86
  "jose": "^6.1.2"
81
87
  },
82
88
  "scripts": {
83
- "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",
89
+ "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 && bun build src/jsx.ts --outdir dist --target bun --format esm",
84
90
  "build:types": "tsc --emitDeclarationOnly --skipLibCheck",
85
91
  "build": "bun run build:js && bun run build:types",
86
92
  "test": "bun test",