princejs 1.9.4 โ 1.9.5
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 +126 -208
- package/package.json +1 -1
package/Readme.md
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="./src/images/og.png" alt="PrinceJS" width="120" />
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# ๐ PrinceJS
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
](https://www.npmjs.com/package/princejs)
|
|
11
|
+
[](https://github.com/MatthewTheCoder1218/princejs)
|
|
12
|
+
[](https://www.npmjs.com/package/princejs)
|
|
13
|
+
[](https://github.com/MatthewTheCoder1218/princejs/blob/main/LICENSE)
|
|
14
|
+
|
|
15
|
+
[**Website**](https://princejs.vercel.app) ยท [**npm**](https://www.npmjs.com/package/princejs) ยท [**GitHub**](https://github.com/MatthewTheCoder1218/princejs) ยท [**Twitter**](https://twitter.com/princejs_bun)
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## โก Performance
|
|
22
|
+
|
|
23
|
+
Benchmarked with `oha -c 100 -z 30s` on Windows 10:
|
|
24
|
+
|
|
25
|
+
| Framework | Req/s | Total |
|
|
26
|
+
|-----------|------:|------:|
|
|
27
|
+
| Elysia | 25,312 | 759k |
|
|
28
|
+
| Hono | 22,124 | 664k |
|
|
29
|
+
| **PrinceJS** | **21,748** | **653k** |
|
|
30
|
+
| Express | 9,325 | 280k |
|
|
31
|
+
|
|
32
|
+
> PrinceJS is **2.3ร faster than Express** and sits comfortably in the top 3 โ at just **4.4kB gzipped**.
|
|
11
33
|
|
|
12
34
|
---
|
|
13
35
|
|
|
14
36
|
## ๐ Quick Start
|
|
37
|
+
|
|
15
38
|
```bash
|
|
16
39
|
bun create princejs my-app
|
|
17
40
|
cd my-app
|
|
@@ -37,77 +60,30 @@ app.listen(3000);
|
|
|
37
60
|
|
|
38
61
|
## ๐งฐ Features
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* CORS
|
|
59
|
-
* Logger
|
|
60
|
-
* Rate Limiting
|
|
61
|
-
* Static Files
|
|
63
|
+
| Feature | Import |
|
|
64
|
+
|---------|--------|
|
|
65
|
+
| Routing | `princejs` |
|
|
66
|
+
| Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
|
|
67
|
+
| Zod Validation | `princejs/middleware` |
|
|
68
|
+
| File Uploads | `princejs/helpers` |
|
|
69
|
+
| WebSockets | `princejs` |
|
|
70
|
+
| Server-Sent Events | `princejs/helpers` |
|
|
71
|
+
| Sessions | `princejs/middleware` |
|
|
72
|
+
| Response Compression | `princejs/middleware` |
|
|
73
|
+
| In-memory Cache | `princejs/helpers` |
|
|
74
|
+
| Cron Scheduler | `princejs/scheduler` |
|
|
75
|
+
| **OpenAPI + Scalar Docs** | `princejs` |
|
|
76
|
+
| JSX / SSR | `princejs/jsx` |
|
|
77
|
+
| SQLite Database | `princejs/db` |
|
|
78
|
+
| Plugin System | `princejs` |
|
|
79
|
+
| End-to-End Type Safety | `princejs/client` |
|
|
80
|
+
| Deploy Adapters | `princejs/vercel` ยท `princejs/cloudflare` ยท `princejs/deno` |
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
### File Uploads
|
|
66
|
-
|
|
67
|
-
### Response Builder
|
|
68
|
-
|
|
69
|
-
### WebSocket Support
|
|
70
|
-
|
|
71
|
-
### Auth & API Keys
|
|
72
|
-
|
|
73
|
-
### Server-Sent Events
|
|
74
|
-
|
|
75
|
-
### Sessions
|
|
76
|
-
|
|
77
|
-
### Response Compression
|
|
78
|
-
|
|
79
|
-
### Route-level Middleware
|
|
80
|
-
|
|
81
|
-
### Plugin System
|
|
82
|
-
|
|
83
|
-
You can share bundles of routes and middleware as plugins.
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { prince, type PrincePlugin } from "princejs";
|
|
87
|
-
|
|
88
|
-
const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
|
|
89
|
-
const base = opts?.prefix ?? "";
|
|
90
|
-
|
|
91
|
-
// plugin-wide middleware
|
|
92
|
-
app.use((req, next) => {
|
|
93
|
-
(req as any).fromPlugin = true;
|
|
94
|
-
return next();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// plugin routes
|
|
98
|
-
app.get(`${base}/users`, (req) => ({
|
|
99
|
-
ok: true,
|
|
100
|
-
fromPlugin: (req as any).fromPlugin,
|
|
101
|
-
}));
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const app = prince();
|
|
105
|
-
app.plugin(usersPlugin, { prefix: "/api" });
|
|
106
|
-
```
|
|
82
|
+
---
|
|
107
83
|
|
|
108
|
-
|
|
84
|
+
## ๐ OpenAPI + Scalar Docs โจ
|
|
109
85
|
|
|
110
|
-
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com)
|
|
86
|
+
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI โ all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
|
|
111
87
|
|
|
112
88
|
```ts
|
|
113
89
|
import { prince } from "princejs";
|
|
@@ -139,112 +115,99 @@ app.listen(3000);
|
|
|
139
115
|
// โ GET /docs.json Raw OpenAPI JSON
|
|
140
116
|
```
|
|
141
117
|
|
|
142
|
-
|
|
143
|
-
- Registers the route on PrinceJS (same as `app.get()` / `app.post()`)
|
|
144
|
-
- Auto-wires `validate(schema.body)` middleware โ no separate import needed
|
|
145
|
-
- Writes the full OpenAPI spec entry including path params, request body, query params, and response schema
|
|
118
|
+
`api.route()` does three things at once:
|
|
146
119
|
|
|
147
|
-
|
|
120
|
+
- โ
Registers the route on PrinceJS
|
|
121
|
+
- โ
Auto-wires `validate(schema.body)` โ no separate import needed
|
|
122
|
+
- โ
Writes the full OpenAPI spec entry
|
|
123
|
+
|
|
124
|
+
| `schema` key | Runtime | Scalar Docs |
|
|
148
125
|
|---|---|---|
|
|
149
|
-
| `body` | โ
Validates
|
|
150
|
-
| `query` |
|
|
151
|
-
| `response` |
|
|
126
|
+
| `body` | โ
Validates request | โ
requestBody model |
|
|
127
|
+
| `query` | โ | โ
Typed query params |
|
|
128
|
+
| `response` | โ | โ
200 response model |
|
|
129
|
+
|
|
130
|
+
> Routes on `app.get()` / `app.post()` stay private โ never appear in docs.
|
|
152
131
|
|
|
153
|
-
|
|
132
|
+
**Themes:** `default` ยท `moon` ยท `purple` ยท `solarized` ยท `bluePlanet` ยท `deepSpace` ยท `saturn` ยท `kepler` ยท `mars`
|
|
154
133
|
|
|
155
|
-
|
|
134
|
+
---
|
|
156
135
|
|
|
157
|
-
|
|
136
|
+
## ๐ Plugin System
|
|
158
137
|
|
|
159
|
-
|
|
138
|
+
Share bundles of routes and middleware as reusable plugins:
|
|
160
139
|
|
|
161
|
-
|
|
140
|
+
```ts
|
|
141
|
+
import { prince, type PrincePlugin } from "princejs";
|
|
162
142
|
|
|
143
|
+
const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
|
|
144
|
+
const base = opts?.prefix ?? "";
|
|
163
145
|
|
|
164
|
-
|
|
146
|
+
app.use((req, next) => {
|
|
147
|
+
(req as any).fromPlugin = true;
|
|
148
|
+
return next();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
app.get(`${base}/users`, (req) => ({
|
|
152
|
+
ok: true,
|
|
153
|
+
fromPlugin: (req as any).fromPlugin,
|
|
154
|
+
}));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const app = prince();
|
|
158
|
+
app.plugin(usersPlugin, { prefix: "/api" });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## ๐ End-to-End Type Safety
|
|
164
|
+
|
|
165
|
+
Define a contract once โ your client gets full TypeScript autocompletion automatically:
|
|
165
166
|
|
|
166
167
|
```ts
|
|
167
168
|
type ApiContract = {
|
|
168
|
-
"GET /users/:id": {
|
|
169
|
-
params: { id: string };
|
|
170
|
-
response: { id: string; name: string };
|
|
169
|
+
"GET /users/:id": {
|
|
170
|
+
params: { id: string };
|
|
171
|
+
response: { id: string; name: string };
|
|
171
172
|
};
|
|
172
|
-
"POST /users": {
|
|
173
|
-
body: { name: string };
|
|
174
|
-
response: { id: string; ok: boolean };
|
|
173
|
+
"POST /users": {
|
|
174
|
+
body: { name: string };
|
|
175
|
+
response: { id: string; ok: boolean };
|
|
175
176
|
};
|
|
176
177
|
};
|
|
177
|
-
```
|
|
178
178
|
|
|
179
|
-
**Initialize The Client**
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
179
|
import { createClient } from "princejs/client";
|
|
183
180
|
|
|
184
181
|
const client = createClient<ApiContract>("http://localhost:3000");
|
|
185
182
|
|
|
186
|
-
// Fully typed request and response
|
|
187
183
|
const user = await client.get("/users/:id", { params: { id: "42" } });
|
|
188
|
-
console.log(user.name); //
|
|
184
|
+
console.log(user.name); // typed as string โ
|
|
189
185
|
```
|
|
190
186
|
|
|
191
187
|
---
|
|
192
188
|
|
|
193
|
-
## Deploy
|
|
194
|
-
|
|
195
|
-
Official adapters let you run the same Prince app on Vercel Edge, Cloudflare Workers, and Deno Deploy.
|
|
196
|
-
|
|
197
|
-
**Vercel (Edge)** โ `api/[[...route]].ts`:
|
|
189
|
+
## ๐ Deploy Adapters
|
|
198
190
|
|
|
191
|
+
**Vercel Edge** โ `api/[[...route]].ts`
|
|
199
192
|
```ts
|
|
200
|
-
import { prince } from "princejs";
|
|
201
193
|
import { toVercel } from "princejs/vercel";
|
|
202
|
-
|
|
203
|
-
const app = prince();
|
|
204
|
-
app.get("/", () => ({ message: "Hello from Vercel!" }));
|
|
205
|
-
|
|
206
194
|
export default toVercel(app);
|
|
207
195
|
```
|
|
208
196
|
|
|
209
|
-
**Cloudflare Workers** โ `src/index.ts
|
|
210
|
-
|
|
197
|
+
**Cloudflare Workers** โ `src/index.ts`
|
|
211
198
|
```ts
|
|
212
|
-
import { prince } from "princejs";
|
|
213
199
|
import { toWorkers } from "princejs/cloudflare";
|
|
214
|
-
|
|
215
|
-
const app = prince();
|
|
216
|
-
app.get("/", () => ({ message: "Hello from Workers!" }));
|
|
217
|
-
|
|
218
200
|
export default toWorkers(app);
|
|
219
201
|
```
|
|
220
202
|
|
|
221
|
-
**Deno Deploy** โ `main.ts
|
|
222
|
-
|
|
203
|
+
**Deno Deploy** โ `main.ts`
|
|
223
204
|
```ts
|
|
224
|
-
import { prince } from "princejs";
|
|
225
205
|
import { toDeno } from "princejs/deno";
|
|
226
|
-
|
|
227
|
-
const app = prince();
|
|
228
|
-
app.get("/", () => ({ message: "Hello from Deno!" }));
|
|
229
|
-
|
|
230
206
|
Deno.serve(toDeno(app));
|
|
231
207
|
```
|
|
232
208
|
|
|
233
209
|
---
|
|
234
210
|
|
|
235
|
-
## Performance With Oha (oha -c 100 -z 30s)
|
|
236
|
-
|
|
237
|
-
| Framework | Req/s | Total |
|
|
238
|
-
|-----------|----------------|--------|
|
|
239
|
-
| Elysia | 25,312 req/s | 759k |
|
|
240
|
-
| Hono | 22,124 req/s | 664k |
|
|
241
|
-
| PrinceJS | 21,748 req/s | 653k |
|
|
242
|
-
| Express | 9,325 req/s | 280k |
|
|
243
|
-
|
|
244
|
-
### Among the top three
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
211
|
## ๐ฏ Full Example
|
|
249
212
|
|
|
250
213
|
```ts
|
|
@@ -252,89 +215,51 @@ import { prince } from "princejs";
|
|
|
252
215
|
import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
|
|
253
216
|
import { validate } from "princejs/validation";
|
|
254
217
|
import { cache, upload, sse } from "princejs/helpers";
|
|
255
|
-
import { cron
|
|
256
|
-
import { Html, Head, Body, H1, P, render } from "princejs/jsx"
|
|
218
|
+
import { cron } from "princejs/scheduler";
|
|
219
|
+
import { Html, Head, Body, H1, P, render } from "princejs/jsx";
|
|
257
220
|
import { db } from "princejs/db";
|
|
258
221
|
import { z } from "zod";
|
|
259
222
|
|
|
260
223
|
const app = prince(true);
|
|
261
224
|
|
|
225
|
+
// Global middleware
|
|
262
226
|
app.use(cors());
|
|
263
227
|
app.use(logger());
|
|
264
228
|
app.use(rateLimit({ max: 100, window: 60 }));
|
|
265
|
-
|
|
266
229
|
app.use(serve({ root: "./public" }));
|
|
267
|
-
|
|
268
|
-
app.use(validate(z.object({ name: z.string() })));
|
|
269
|
-
|
|
270
230
|
app.use(jwt(key));
|
|
271
231
|
app.use(session({ secret: "key" }));
|
|
272
232
|
app.use(compress());
|
|
273
233
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
H1("Hello World"),
|
|
278
|
-
P("This is a test")
|
|
279
|
-
)
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
// With props (optional)
|
|
283
|
-
const Card = (props: any) => Div(
|
|
284
|
-
{ className: "card", style: "padding: 1rem;" },
|
|
285
|
-
H1(props.title),
|
|
286
|
-
P(props.content)
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
// Without props
|
|
290
|
-
const Simple = () => Div(
|
|
291
|
-
H1("No Props Needed"),
|
|
292
|
-
P("Just pure content")
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
const requireAuth = async (req: any, next: any) => {
|
|
296
|
-
const token = req.headers.get("Authorization");
|
|
297
|
-
if (!token) return new Response("Unauthorized", { status: 401 });
|
|
298
|
-
req.user = { id: 1, name: "Alice" };
|
|
299
|
-
return next();
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
app.get("/protected", requireAuth, async (req) => {
|
|
303
|
-
return { user: req.user };
|
|
304
|
-
});
|
|
234
|
+
// JSX
|
|
235
|
+
const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
|
|
236
|
+
app.get("/jsx", () => render(Page()));
|
|
305
237
|
|
|
238
|
+
// Database
|
|
306
239
|
const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
|
|
240
|
+
app.get("/users", () => users.query("SELECT * FROM users"));
|
|
307
241
|
|
|
242
|
+
// WebSockets
|
|
308
243
|
app.ws("/chat", {
|
|
309
244
|
open: (ws) => ws.send("Welcome!"),
|
|
310
|
-
message: (ws, msg) => ws.send(`Echo: ${msg}`)
|
|
245
|
+
message: (ws, msg) => ws.send(`Echo: ${msg}`),
|
|
311
246
|
});
|
|
312
247
|
|
|
313
|
-
|
|
248
|
+
// Auth
|
|
314
249
|
app.get("/protected", auth(), (req) => ({ user: req.user }));
|
|
315
250
|
app.get("/api", apiKey({ keys: ["key_123"] }), handler);
|
|
316
251
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
320
|
-
|
|
321
|
-
app.get("/jsx", () => render(Page()));
|
|
322
|
-
|
|
252
|
+
// Helpers
|
|
323
253
|
app.get("/data", cache(60)(() => ({ time: Date.now() })));
|
|
324
|
-
|
|
325
254
|
app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
|
|
326
|
-
|
|
327
255
|
app.get("/events", sse(), (req) => {
|
|
328
256
|
setInterval(() => req.sseSend({ time: Date.now() }), 1000);
|
|
329
257
|
});
|
|
330
258
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
app.get("/users", () => users.query("SELECT * FROM users"));
|
|
334
|
-
|
|
259
|
+
// Cron
|
|
335
260
|
cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
|
|
336
261
|
|
|
337
|
-
// OpenAPI + Scalar
|
|
262
|
+
// OpenAPI + Scalar docs
|
|
338
263
|
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
|
|
339
264
|
api.route("GET", "/items", {
|
|
340
265
|
summary: "List items",
|
|
@@ -353,21 +278,15 @@ app.listen(3000);
|
|
|
353
278
|
## ๐ฆ Installation
|
|
354
279
|
|
|
355
280
|
```bash
|
|
356
|
-
npm install princejs
|
|
357
|
-
# or
|
|
358
281
|
bun add princejs
|
|
359
282
|
# or
|
|
283
|
+
npm install princejs
|
|
284
|
+
# or
|
|
360
285
|
yarn add princejs
|
|
361
286
|
```
|
|
362
287
|
|
|
363
288
|
---
|
|
364
289
|
|
|
365
|
-
## ๐ Documentation
|
|
366
|
-
|
|
367
|
-
Visit: **princejs.vercel.app**
|
|
368
|
-
|
|
369
|
-
---
|
|
370
|
-
|
|
371
290
|
## ๐ค Contributing
|
|
372
291
|
|
|
373
292
|
```bash
|
|
@@ -379,20 +298,19 @@ bun test
|
|
|
379
298
|
|
|
380
299
|
---
|
|
381
300
|
|
|
382
|
-
##
|
|
383
|
-
|
|
384
|
-
If PrinceJS helped you, star the repo!
|
|
301
|
+
## ๐ Links
|
|
385
302
|
|
|
386
|
-
|
|
303
|
+
- ๐ Website: [princejs.vercel.app](https://princejs.vercel.app)
|
|
304
|
+
- ๐ฆ npm: [npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
|
|
305
|
+
- ๐ป GitHub: [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
306
|
+
- ๐ฆ Twitter: [@princejs_bun](https://twitter.com/princejs_bun)
|
|
387
307
|
|
|
388
308
|
---
|
|
389
309
|
|
|
390
|
-
|
|
310
|
+
<div align="center">
|
|
391
311
|
|
|
392
|
-
|
|
393
|
-
* GitHub: [https://github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
394
|
-
* Twitter: [https://twitter.com/Lil_Prince_1218](https://twitter.com/Lil_Prince_1218)
|
|
312
|
+
**PrinceJS: Small in size. Giant in capability. ๐**
|
|
395
313
|
|
|
396
|
-
|
|
314
|
+
*Built with โค๏ธ in Nigeria*
|
|
397
315
|
|
|
398
|
-
|
|
316
|
+
</div>
|
package/package.json
CHANGED