princejs 1.9.2 → 1.9.4
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 +61 -1
- package/dist/prince.d.ts +51 -1
- package/dist/prince.d.ts.map +1 -1
- package/dist/prince.js +341 -1
- package/dist/scheduler.d.ts +83 -6
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +54 -1
- package/package.json +2 -2
package/Readme.md
CHANGED
|
@@ -105,6 +105,55 @@ const app = prince();
|
|
|
105
105
|
app.plugin(usersPlugin, { prefix: "/api" });
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
### OpenAPI + Scalar Docs
|
|
109
|
+
|
|
110
|
+
Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) API reference UI — all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { prince } from "princejs";
|
|
114
|
+
import { z } from "zod";
|
|
115
|
+
|
|
116
|
+
const app = prince();
|
|
117
|
+
|
|
118
|
+
const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs", { theme: "moon" });
|
|
119
|
+
|
|
120
|
+
api.route("GET", "/users/:id", {
|
|
121
|
+
summary: "Get user by ID",
|
|
122
|
+
tags: ["users"],
|
|
123
|
+
schema: {
|
|
124
|
+
response: z.object({ id: z.string(), name: z.string() }),
|
|
125
|
+
},
|
|
126
|
+
}, (req) => ({ id: req.params!.id, name: "Alice" }));
|
|
127
|
+
|
|
128
|
+
api.route("POST", "/users", {
|
|
129
|
+
summary: "Create user",
|
|
130
|
+
tags: ["users"],
|
|
131
|
+
schema: {
|
|
132
|
+
body: z.object({ name: z.string().min(2), email: z.string().email() }),
|
|
133
|
+
response: z.object({ id: z.string(), name: z.string(), email: z.string() }),
|
|
134
|
+
},
|
|
135
|
+
}, (req) => ({ id: crypto.randomUUID(), ...req.parsedBody }));
|
|
136
|
+
|
|
137
|
+
app.listen(3000);
|
|
138
|
+
// → GET /docs Scalar UI
|
|
139
|
+
// → GET /docs.json Raw OpenAPI JSON
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**`api.route()` does three things at once:**
|
|
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
|
|
146
|
+
|
|
147
|
+
| `schema` key | Runtime effect | Scalar docs |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `body` | ✅ Validates & strips via `validate()` | ✅ requestBody model |
|
|
150
|
+
| `query` | ❌ docs only | ✅ typed query params |
|
|
151
|
+
| `response` | ❌ docs only | ✅ 200 response model |
|
|
152
|
+
|
|
153
|
+
Routes registered with `app.get()` / `app.post()` directly never appear in the docs — useful for internal health checks, webhooks, and admin endpoints.
|
|
154
|
+
|
|
155
|
+
**Available themes:** `default` · `moon` · `purple` · `solarized` · `bluePlanet` · `deepSpace` · `saturn` · `kepler` · `mars`
|
|
156
|
+
|
|
108
157
|
### Database (SQLite)
|
|
109
158
|
|
|
110
159
|
### End to End Type-Safety
|
|
@@ -203,7 +252,7 @@ import { prince } from "princejs";
|
|
|
203
252
|
import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
|
|
204
253
|
import { validate } from "princejs/validation";
|
|
205
254
|
import { cache, upload, sse } from "princejs/helpers";
|
|
206
|
-
import { cron } from "princejs/scheduler";
|
|
255
|
+
import { cron, openapi } from "princejs/scheduler";
|
|
207
256
|
import { Html, Head, Body, H1, P, render } from "princejs/jsx"
|
|
208
257
|
import { db } from "princejs/db";
|
|
209
258
|
import { z } from "zod";
|
|
@@ -285,6 +334,17 @@ app.get("/users", () => users.query("SELECT * FROM users"));
|
|
|
285
334
|
|
|
286
335
|
cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
|
|
287
336
|
|
|
337
|
+
// OpenAPI + Scalar
|
|
338
|
+
const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
|
|
339
|
+
api.route("GET", "/items", {
|
|
340
|
+
summary: "List items",
|
|
341
|
+
tags: ["items"],
|
|
342
|
+
schema: {
|
|
343
|
+
query: z.object({ q: z.string().optional() }),
|
|
344
|
+
response: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
345
|
+
},
|
|
346
|
+
}, () => [{ id: "1", name: "Widget" }]);
|
|
347
|
+
|
|
288
348
|
app.listen(3000);
|
|
289
349
|
```
|
|
290
350
|
|
package/dist/prince.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OpenAPIBuilder, ScalarOptions } from "./scheduler";
|
|
2
|
+
import { z } from "zod";
|
|
1
3
|
type Next = () => Promise<Response>;
|
|
2
4
|
type Middleware = (req: PrinceRequest, next: Next) => Promise<Response | undefined> | Response | undefined;
|
|
3
5
|
type HandlerResult = Response | Record<string, any> | string | Uint8Array;
|
|
@@ -32,13 +34,30 @@ declare class ResponseBuilder {
|
|
|
32
34
|
redirect(url: string, status?: number): Response;
|
|
33
35
|
build(): Response;
|
|
34
36
|
}
|
|
37
|
+
export interface RouteSchema {
|
|
38
|
+
/** Zod schema for the request body — auto-wires validate() middleware */
|
|
39
|
+
body?: z.ZodTypeAny;
|
|
40
|
+
/** Zod schema for query parameters — written into the OpenAPI spec */
|
|
41
|
+
query?: z.ZodObject<any>;
|
|
42
|
+
/** Zod schema for the response body — written into the OpenAPI spec */
|
|
43
|
+
response?: z.ZodTypeAny;
|
|
44
|
+
}
|
|
45
|
+
export interface RouteOperation {
|
|
46
|
+
summary?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
tags?: string[];
|
|
49
|
+
/** Attach Zod schemas to auto-generate request/response models in Scalar */
|
|
50
|
+
schema?: RouteSchema;
|
|
51
|
+
responses?: Record<string, unknown>;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
35
54
|
export declare class Prince {
|
|
36
55
|
private devMode;
|
|
37
56
|
private rawRoutes;
|
|
38
57
|
private middlewares;
|
|
39
58
|
private errorHandler?;
|
|
40
59
|
private wsRoutes;
|
|
41
|
-
private
|
|
60
|
+
private openapiSpec;
|
|
42
61
|
private router;
|
|
43
62
|
private staticRoutes;
|
|
44
63
|
private staticMiddlewares;
|
|
@@ -77,6 +96,37 @@ export declare class Prince {
|
|
|
77
96
|
private executeHandler;
|
|
78
97
|
handleFetch(req: Request): Promise<Response>;
|
|
79
98
|
fetch(req: Request): Promise<Response>;
|
|
99
|
+
/**
|
|
100
|
+
* Mounts an OpenAPI spec + Scalar UI onto this Prince app.
|
|
101
|
+
*
|
|
102
|
+
* Calling this method returns an `OpenAPIBuilder` whose `.route()` method
|
|
103
|
+
* registers a route on the Prince app **and** writes the matching path entry
|
|
104
|
+
* into the OpenAPI spec simultaneously — so the two can never drift.
|
|
105
|
+
*
|
|
106
|
+
* Two routes are auto-registered:
|
|
107
|
+
* GET `docsPath` → Scalar UI (e.g. /docs)
|
|
108
|
+
* GET `docsPath`.json → Raw spec (e.g. /docs.json)
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* const app = prince();
|
|
112
|
+
* const api = app.openapi({ title: "My API", version: "1.0.0" }, "/docs");
|
|
113
|
+
*
|
|
114
|
+
* // Registers GET /hello on Prince AND adds it to the OpenAPI spec
|
|
115
|
+
* api.route("GET", "/hello", {
|
|
116
|
+
* summary: "Say hello",
|
|
117
|
+
* responses: { 200: { description: "OK" } },
|
|
118
|
+
* }, (req) => ({ message: "hello" }));
|
|
119
|
+
*
|
|
120
|
+
* app.listen(3000);
|
|
121
|
+
* // → GET /docs → Scalar UI
|
|
122
|
+
* // → GET /docs.json → raw OpenAPI JSON
|
|
123
|
+
*/
|
|
124
|
+
openapi(info: {
|
|
125
|
+
title: string;
|
|
126
|
+
version: string;
|
|
127
|
+
}, docsPath?: string, scalarOptions?: ScalarOptions): OpenAPIBuilder & {
|
|
128
|
+
route: (method: string, path: string, operation: Record<string, unknown>, ...args: (RouteHandler | Middleware)[]) => OpenAPIBuilder;
|
|
129
|
+
};
|
|
80
130
|
listen(port?: number): void;
|
|
81
131
|
}
|
|
82
132
|
export declare const prince: (dev?: boolean) => Prince;
|
package/dist/prince.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAe,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,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,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,UAAU,gBAAgB;IACxB,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;AAqBnF,MAAM,MAAM,YAAY,CAAC,QAAQ,GAAG,GAAG,IAAI,CACzC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,QAAQ,KACf,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,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,KAAK;CAGN;AAsLD,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,IAAI,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC;IACpB,sEAAsE;IACtE,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,MAAM;IAgBL,OAAO,CAAC,OAAO;IAf3B,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,YAAY,CAAC,CAA6C;IAClE,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,iBAAiB,CAAwC;IACjE,OAAO,CAAC,UAAU,CAKb;gBAEe,OAAO,UAAQ;IAEnC,GAAG,CAAC,EAAE,EAAE,UAAU;IAKlB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,QAAQ,GAAG,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ;IAOzE,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;IAKR,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IACxD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IACzD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IACxD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IAC3D,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IAC1D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE;IAC5D,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB;IAK3C,OAAO,CAAC,GAAG;IA6BX,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,WAAW;IA0CnB,OAAO,CAAC,SAAS;IAkEjB,OAAO,CAAC,SAAS;IAyBjB,OAAO,CAAC,UAAU;YA+CJ,SAAS;YAoCT,cAAc;IAmDtB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4B5C,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAa5C;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CACL,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EACxC,QAAQ,SAAU,EAClB,aAAa,GAAE,aAAkB,GAChC,cAAc,GAAG;QAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,YAAY,GAAG,UAAU,CAAC,EAAE,KAAK,cAAc,CAAA;KAAE;IAuF3J,MAAM,CAAC,IAAI,SAAO;CA8CnB;AAiCD,eAAO,MAAM,MAAM,GAAI,aAAW,WAAoB,CAAC"}
|
package/dist/prince.js
CHANGED
|
@@ -1,4 +1,123 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/scheduler.ts
|
|
3
|
+
var openapi = (info) => {
|
|
4
|
+
const spec = { openapi: "3.0.0", info, paths: {} };
|
|
5
|
+
const renderScalarHtml = (options = {}) => {
|
|
6
|
+
const {
|
|
7
|
+
pageTitle = spec.info.title,
|
|
8
|
+
theme = "default",
|
|
9
|
+
layout = "modern",
|
|
10
|
+
hideDownloadButton = false,
|
|
11
|
+
customCss = ""
|
|
12
|
+
} = options;
|
|
13
|
+
return `<!doctype html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8" />
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
18
|
+
<title>${pageTitle}</title>
|
|
19
|
+
${customCss ? `<style>${customCss}</style>` : ""}
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<script
|
|
23
|
+
id="api-reference"
|
|
24
|
+
type="application/json"
|
|
25
|
+
data-theme="${theme}"
|
|
26
|
+
data-layout="${layout}"
|
|
27
|
+
${hideDownloadButton ? 'data-hide-download-button="true"' : ""}
|
|
28
|
+
>${JSON.stringify(spec)}</script>
|
|
29
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
30
|
+
</body>
|
|
31
|
+
</html>`;
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
spec,
|
|
35
|
+
get openapi() {
|
|
36
|
+
return spec.openapi;
|
|
37
|
+
},
|
|
38
|
+
get info() {
|
|
39
|
+
return spec.info;
|
|
40
|
+
},
|
|
41
|
+
get paths() {
|
|
42
|
+
return spec.paths;
|
|
43
|
+
},
|
|
44
|
+
scalar(options = {}) {
|
|
45
|
+
return (_req, res) => {
|
|
46
|
+
const html = renderScalarHtml(options);
|
|
47
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
48
|
+
res.end(html);
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
json() {
|
|
52
|
+
return (_req, res) => {
|
|
53
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
54
|
+
res.end(JSON.stringify(spec, null, 2));
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/prince.ts
|
|
61
|
+
import { z } from "zod";
|
|
62
|
+
|
|
63
|
+
// src/middleware.ts
|
|
64
|
+
import { jwtVerify, SignJWT } from "jose";
|
|
65
|
+
var validate = (schema) => {
|
|
66
|
+
return async (req, next) => {
|
|
67
|
+
let bodyData = req.parsedBody;
|
|
68
|
+
let bodyParsed = false;
|
|
69
|
+
if (!bodyData && ["POST", "PUT", "PATCH"].includes(req.method)) {
|
|
70
|
+
const ct = req.headers.get("content-type") || "";
|
|
71
|
+
if (ct.includes("application/json")) {
|
|
72
|
+
try {
|
|
73
|
+
const clonedReq = req.clone();
|
|
74
|
+
const text = await clonedReq.text();
|
|
75
|
+
if (text) {
|
|
76
|
+
bodyData = JSON.parse(text);
|
|
77
|
+
bodyParsed = true;
|
|
78
|
+
}
|
|
79
|
+
} catch (parseError) {
|
|
80
|
+
return new Response(JSON.stringify({ error: "Invalid JSON" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
81
|
+
}
|
|
82
|
+
} else if (ct.includes("application/x-www-form-urlencoded")) {
|
|
83
|
+
try {
|
|
84
|
+
const clonedReq = req.clone();
|
|
85
|
+
const text = await clonedReq.text();
|
|
86
|
+
bodyData = Object.fromEntries(new URLSearchParams(text));
|
|
87
|
+
bodyParsed = true;
|
|
88
|
+
} catch (parseError) {
|
|
89
|
+
return new Response(JSON.stringify({ error: "Invalid form data" }), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (bodyData) {
|
|
94
|
+
const result = schema.safeParse(bodyData);
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
if (bodyParsed) {
|
|
97
|
+
req.parsedBody = bodyData;
|
|
98
|
+
}
|
|
99
|
+
return new Response(JSON.stringify({
|
|
100
|
+
error: "Validation failed",
|
|
101
|
+
details: result.error.issues.map((e) => ({
|
|
102
|
+
path: e.path.join("."),
|
|
103
|
+
message: e.message
|
|
104
|
+
}))
|
|
105
|
+
}), {
|
|
106
|
+
status: 400,
|
|
107
|
+
headers: { "Content-Type": "application/json" }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
req.parsedBody = result.data;
|
|
111
|
+
Object.defineProperty(req, "body", {
|
|
112
|
+
value: result.data,
|
|
113
|
+
writable: true,
|
|
114
|
+
configurable: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return next();
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
2
121
|
// src/prince.ts
|
|
3
122
|
class ResponseBuilder {
|
|
4
123
|
_status = 200;
|
|
@@ -36,6 +155,149 @@ class ResponseBuilder {
|
|
|
36
155
|
return new Response(this._body, { status: this._status, headers: this._headers });
|
|
37
156
|
}
|
|
38
157
|
}
|
|
158
|
+
function zodToJsonSchema(schema) {
|
|
159
|
+
const d = schema._def;
|
|
160
|
+
const typeName = d?.typeName ?? d?.type ?? "";
|
|
161
|
+
if (schema instanceof z.ZodString || typeName === "ZodString") {
|
|
162
|
+
const s = { type: "string" };
|
|
163
|
+
const checks = d.checks ?? [];
|
|
164
|
+
for (const c of checks) {
|
|
165
|
+
if (c.kind === "min")
|
|
166
|
+
s.minLength = c.value;
|
|
167
|
+
if (c.kind === "max")
|
|
168
|
+
s.maxLength = c.value;
|
|
169
|
+
if (c.kind === "email")
|
|
170
|
+
s.format = "email";
|
|
171
|
+
if (c.kind === "url")
|
|
172
|
+
s.format = "uri";
|
|
173
|
+
if (c.kind === "uuid")
|
|
174
|
+
s.format = "uuid";
|
|
175
|
+
if (c.kind === "regex")
|
|
176
|
+
s.pattern = c.regex?.source ?? c.pattern;
|
|
177
|
+
const def = c._zod?.def ?? {};
|
|
178
|
+
if (def.check === "min_length")
|
|
179
|
+
s.minLength = def.minimum;
|
|
180
|
+
if (def.check === "max_length")
|
|
181
|
+
s.maxLength = def.maximum;
|
|
182
|
+
if (def.check === "string_format") {
|
|
183
|
+
if (def.format === "email")
|
|
184
|
+
s.format = "email";
|
|
185
|
+
if (def.format === "url")
|
|
186
|
+
s.format = "uri";
|
|
187
|
+
if (def.format === "uuid")
|
|
188
|
+
s.format = "uuid";
|
|
189
|
+
if (def.format === "regex")
|
|
190
|
+
s.pattern = def.pattern;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return s;
|
|
194
|
+
}
|
|
195
|
+
if (schema instanceof z.ZodNumber || typeName === "ZodNumber") {
|
|
196
|
+
const s = { type: "number" };
|
|
197
|
+
const checks = d.checks ?? [];
|
|
198
|
+
for (const c of checks) {
|
|
199
|
+
if (c.kind === "min")
|
|
200
|
+
s.minimum = c.value ?? c.minimum;
|
|
201
|
+
if (c.kind === "max")
|
|
202
|
+
s.maximum = c.value ?? c.maximum;
|
|
203
|
+
if (c.kind === "int")
|
|
204
|
+
s.type = "integer";
|
|
205
|
+
if (c.kind === "multipleOf")
|
|
206
|
+
s.multipleOf = c.value;
|
|
207
|
+
const def = c._zod?.def ?? {};
|
|
208
|
+
if (def.check === "greater_than" || def.check === "greater_than_or_equal")
|
|
209
|
+
s.minimum = def.value;
|
|
210
|
+
if (def.check === "less_than" || def.check === "less_than_or_equal")
|
|
211
|
+
s.maximum = def.value;
|
|
212
|
+
if (def.check === "number_format" && (def.format === "safeint" || def.format === "int32" || def.format === "int64"))
|
|
213
|
+
s.type = "integer";
|
|
214
|
+
if (def.check === "multiple_of")
|
|
215
|
+
s.multipleOf = def.value;
|
|
216
|
+
}
|
|
217
|
+
return s;
|
|
218
|
+
}
|
|
219
|
+
if (schema instanceof z.ZodBoolean || typeName === "ZodBoolean")
|
|
220
|
+
return { type: "boolean" };
|
|
221
|
+
if (schema instanceof z.ZodNull || typeName === "ZodNull")
|
|
222
|
+
return { type: "null" };
|
|
223
|
+
if (schema instanceof z.ZodLiteral || typeName === "ZodLiteral") {
|
|
224
|
+
const val = d.value ?? d.values?.[0];
|
|
225
|
+
return { enum: [val] };
|
|
226
|
+
}
|
|
227
|
+
if (schema instanceof z.ZodEnum || typeName === "ZodEnum") {
|
|
228
|
+
const vals = d.values ?? (d.entries ? Object.values(d.entries) : undefined) ?? d.options ?? [];
|
|
229
|
+
return { type: "string", enum: vals };
|
|
230
|
+
}
|
|
231
|
+
if (schema instanceof z.ZodArray || typeName === "ZodArray") {
|
|
232
|
+
const items = d.element ?? d.type ?? d.items;
|
|
233
|
+
return { type: "array", items: items ? zodToJsonSchema(items) : {} };
|
|
234
|
+
}
|
|
235
|
+
if (schema instanceof z.ZodOptional || typeName === "ZodOptional" || schema instanceof z.ZodNullable || typeName === "ZodNullable") {
|
|
236
|
+
return zodToJsonSchema(d.innerType ?? d.type);
|
|
237
|
+
}
|
|
238
|
+
if (schema instanceof z.ZodDefault || typeName === "ZodDefault") {
|
|
239
|
+
const inner = zodToJsonSchema(d.innerType ?? d.type);
|
|
240
|
+
const dv = d.defaultValue ?? d.default;
|
|
241
|
+
return { ...inner, default: typeof dv === "function" ? dv() : dv };
|
|
242
|
+
}
|
|
243
|
+
if (schema instanceof z.ZodObject || typeName === "ZodObject") {
|
|
244
|
+
const shape = d.shape ?? {};
|
|
245
|
+
const properties = {};
|
|
246
|
+
const required = [];
|
|
247
|
+
for (const [key, val] of Object.entries(shape)) {
|
|
248
|
+
const v = val;
|
|
249
|
+
const vd = v._def;
|
|
250
|
+
const vType = vd?.typeName ?? vd?.type ?? "";
|
|
251
|
+
properties[key] = zodToJsonSchema(v);
|
|
252
|
+
const isOptional = v instanceof z.ZodOptional || vType === "ZodOptional" || v instanceof z.ZodDefault || vType === "ZodDefault";
|
|
253
|
+
if (!isOptional)
|
|
254
|
+
required.push(key);
|
|
255
|
+
}
|
|
256
|
+
return { type: "object", properties, ...required.length ? { required } : {} };
|
|
257
|
+
}
|
|
258
|
+
if (schema instanceof z.ZodUnion || typeName === "ZodUnion") {
|
|
259
|
+
return { oneOf: (d.options ?? []).map(zodToJsonSchema) };
|
|
260
|
+
}
|
|
261
|
+
if (schema instanceof z.ZodIntersection || typeName === "ZodIntersection") {
|
|
262
|
+
return { allOf: [d.left, d.right].map(zodToJsonSchema) };
|
|
263
|
+
}
|
|
264
|
+
if (schema instanceof z.ZodRecord || typeName === "ZodRecord") {
|
|
265
|
+
const valueSchema = d.valueType ?? d.valueSchema ?? d.element ?? (d.keyType && !d.valueType ? d.keyType : undefined);
|
|
266
|
+
return { type: "object", additionalProperties: valueSchema ? zodToJsonSchema(valueSchema) : {} };
|
|
267
|
+
}
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
function zodObjectToQueryParams(schema) {
|
|
271
|
+
const d = schema._def;
|
|
272
|
+
const shape = d.shape ?? {};
|
|
273
|
+
return Object.entries(shape).map(([name, val]) => {
|
|
274
|
+
const v = val;
|
|
275
|
+
const vTypeName = v._def?.typeName ?? v._def?.type ?? "";
|
|
276
|
+
const isOptional = v instanceof z.ZodOptional || vTypeName === "ZodOptional" || v instanceof z.ZodDefault || vTypeName === "ZodDefault";
|
|
277
|
+
return {
|
|
278
|
+
name,
|
|
279
|
+
in: "query",
|
|
280
|
+
required: !isOptional,
|
|
281
|
+
schema: zodToJsonSchema(v)
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function buildResponses(responseSchema, existingResponses) {
|
|
286
|
+
const base = existingResponses ?? {};
|
|
287
|
+
if (!responseSchema)
|
|
288
|
+
return Object.keys(base).length ? base : { 200: { description: "OK" } };
|
|
289
|
+
return {
|
|
290
|
+
200: {
|
|
291
|
+
description: "OK",
|
|
292
|
+
content: {
|
|
293
|
+
"application/json": {
|
|
294
|
+
schema: zodToJsonSchema(responseSchema)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
...base
|
|
299
|
+
};
|
|
300
|
+
}
|
|
39
301
|
|
|
40
302
|
class Prince {
|
|
41
303
|
devMode;
|
|
@@ -43,7 +305,7 @@ class Prince {
|
|
|
43
305
|
middlewares = [];
|
|
44
306
|
errorHandler;
|
|
45
307
|
wsRoutes = {};
|
|
46
|
-
|
|
308
|
+
openapiSpec = null;
|
|
47
309
|
router = null;
|
|
48
310
|
staticRoutes = new Map;
|
|
49
311
|
staticMiddlewares = new Map;
|
|
@@ -383,6 +645,56 @@ class Prince {
|
|
|
383
645
|
return this.json({ error: "Internal Server Error" }, 500);
|
|
384
646
|
}
|
|
385
647
|
}
|
|
648
|
+
openapi(info, docsPath = "/docs", scalarOptions = {}) {
|
|
649
|
+
const builder = openapi(info);
|
|
650
|
+
this.openapiSpec = builder.spec;
|
|
651
|
+
const toOpenAPIPath = (p) => p.replace(/:([^/]+)/g, "{$1}");
|
|
652
|
+
this.get(docsPath, (req) => {
|
|
653
|
+
return new Response(renderScalarHTML(builder.spec, scalarOptions), { headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
654
|
+
});
|
|
655
|
+
const jsonPath = docsPath.replace(/\/$/, "") + ".json";
|
|
656
|
+
this.get(jsonPath, (_req) => new Response(JSON.stringify(builder.spec, null, 2), {
|
|
657
|
+
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
658
|
+
}));
|
|
659
|
+
const self = this;
|
|
660
|
+
builder.route = function(method, path, operation, ...args) {
|
|
661
|
+
const m = method.toUpperCase();
|
|
662
|
+
const { schema, responses: manualResponses, ...operationRest } = operation;
|
|
663
|
+
const middlewares = [];
|
|
664
|
+
if (schema?.body) {
|
|
665
|
+
middlewares.push(validate(schema.body));
|
|
666
|
+
}
|
|
667
|
+
middlewares.push(...args);
|
|
668
|
+
self.add(m, path, middlewares);
|
|
669
|
+
const oaPath = toOpenAPIPath(path);
|
|
670
|
+
if (!builder.spec.paths[oaPath])
|
|
671
|
+
builder.spec.paths[oaPath] = {};
|
|
672
|
+
const pathParams = [...path.matchAll(/:([^/]+)/g)].map(([, name]) => ({
|
|
673
|
+
name,
|
|
674
|
+
in: "path",
|
|
675
|
+
required: true,
|
|
676
|
+
schema: { type: "string" }
|
|
677
|
+
}));
|
|
678
|
+
const queryParams = schema?.query ? zodObjectToQueryParams(schema.query) : [];
|
|
679
|
+
const requestBody = schema?.body ? {
|
|
680
|
+
required: true,
|
|
681
|
+
content: {
|
|
682
|
+
"application/json": {
|
|
683
|
+
schema: zodToJsonSchema(schema.body)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} : undefined;
|
|
687
|
+
const responses = buildResponses(schema?.response, manualResponses);
|
|
688
|
+
builder.spec.paths[oaPath][m.toLowerCase()] = {
|
|
689
|
+
parameters: [...pathParams, ...queryParams],
|
|
690
|
+
...requestBody ? { requestBody } : {},
|
|
691
|
+
responses,
|
|
692
|
+
...operationRest
|
|
693
|
+
};
|
|
694
|
+
return builder;
|
|
695
|
+
};
|
|
696
|
+
return builder;
|
|
697
|
+
}
|
|
386
698
|
listen(port = 3000) {
|
|
387
699
|
const self = this;
|
|
388
700
|
Bun.serve({
|
|
@@ -426,6 +738,34 @@ class Prince {
|
|
|
426
738
|
console.log(`\uD83D\uDE80 PrinceJS running on http://localhost:${port}`);
|
|
427
739
|
}
|
|
428
740
|
}
|
|
741
|
+
function renderScalarHTML(spec, options = {}) {
|
|
742
|
+
const {
|
|
743
|
+
pageTitle = spec.info.title ?? "API Reference",
|
|
744
|
+
theme = "default",
|
|
745
|
+
layout = "modern",
|
|
746
|
+
hideDownloadButton = false,
|
|
747
|
+
customCss = ""
|
|
748
|
+
} = options;
|
|
749
|
+
return `<!doctype html>
|
|
750
|
+
<html lang="en">
|
|
751
|
+
<head>
|
|
752
|
+
<meta charset="utf-8" />
|
|
753
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
754
|
+
<title>${pageTitle}</title>
|
|
755
|
+
${customCss ? `<style>${customCss}</style>` : ""}
|
|
756
|
+
</head>
|
|
757
|
+
<body>
|
|
758
|
+
<script
|
|
759
|
+
id="api-reference"
|
|
760
|
+
type="application/json"
|
|
761
|
+
data-theme="${theme}"
|
|
762
|
+
data-layout="${layout}"
|
|
763
|
+
${hideDownloadButton ? 'data-hide-download-button="true"' : ""}
|
|
764
|
+
>${JSON.stringify(spec)}</script>
|
|
765
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
766
|
+
</body>
|
|
767
|
+
</html>`;
|
|
768
|
+
}
|
|
429
769
|
var prince = (dev = false) => new Prince(dev);
|
|
430
770
|
export {
|
|
431
771
|
prince,
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -1,13 +1,90 @@
|
|
|
1
1
|
export declare const cron: (pattern: string, task: () => void) => void;
|
|
2
|
-
export
|
|
3
|
-
title: string;
|
|
4
|
-
version: string;
|
|
5
|
-
}) => {
|
|
2
|
+
export interface OpenAPISpec {
|
|
6
3
|
openapi: string;
|
|
7
4
|
info: {
|
|
8
5
|
title: string;
|
|
9
6
|
version: string;
|
|
7
|
+
[key: string]: unknown;
|
|
10
8
|
};
|
|
11
|
-
paths:
|
|
12
|
-
|
|
9
|
+
paths: Record<string, unknown>;
|
|
10
|
+
components?: Record<string, unknown>;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface ScalarOptions {
|
|
14
|
+
/** Scalar theme. Options: "default" | "alternate" | "moon" | "purple" | "solarized" | "bluePlanet" | "deepSpace" | "saturn" | "kepler" | "mars" | "none" */
|
|
15
|
+
theme?: string;
|
|
16
|
+
/** Page title shown in the browser tab. Defaults to the spec's info.title. */
|
|
17
|
+
pageTitle?: string;
|
|
18
|
+
/** Layout mode. "modern" (default) or "classic" */
|
|
19
|
+
layout?: "modern" | "classic";
|
|
20
|
+
/** Hide the spec download button. */
|
|
21
|
+
hideDownloadButton?: boolean;
|
|
22
|
+
/** Custom CSS injected into the page. */
|
|
23
|
+
customCss?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface OpenAPIBuilder {
|
|
26
|
+
/** The raw spec object — mutate this to add paths, components, etc. */
|
|
27
|
+
spec: OpenAPISpec;
|
|
28
|
+
/** Backward-compatible direct accessors — mirrors spec.openapi / .info / .paths */
|
|
29
|
+
readonly openapi: string;
|
|
30
|
+
readonly info: OpenAPISpec["info"];
|
|
31
|
+
readonly paths: OpenAPISpec["paths"];
|
|
32
|
+
/**
|
|
33
|
+
* Returns a plain-function route handler that serves the Scalar UI.
|
|
34
|
+
* Mount it on any path in your router.
|
|
35
|
+
*
|
|
36
|
+
* Works with any framework that uses `(req, res) => void` handlers
|
|
37
|
+
* (Node http, Express, Fastify inject, etc.).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Plain Node http
|
|
41
|
+
* server.on("request", (req, res) => {
|
|
42
|
+
* if (req.url === "/docs") return api.scalar()(req, res);
|
|
43
|
+
* if (req.url === "/openapi.json") return api.json()(req, res);
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Express / Hono-style
|
|
48
|
+
* app.get("/docs", api.scalar({ theme: "moon" }));
|
|
49
|
+
* app.get("/openapi.json", api.json());
|
|
50
|
+
*/
|
|
51
|
+
scalar(options?: ScalarOptions): (req: unknown, res: {
|
|
52
|
+
writeHead(status: number, headers: Record<string, string>): void;
|
|
53
|
+
end(body: string): void;
|
|
54
|
+
}) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Returns a route handler that serves the raw OpenAPI spec as JSON.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* app.get("/openapi.json", api.json());
|
|
60
|
+
*/
|
|
61
|
+
json(): (req: unknown, res: {
|
|
62
|
+
writeHead(status: number, headers: Record<string, string>): void;
|
|
63
|
+
end(body: string): void;
|
|
64
|
+
}) => void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates an OpenAPI builder with a `.scalar()` and `.json()` route handler,
|
|
68
|
+
* mirroring the Hono / @scalar/hono-api-reference middleware pattern —
|
|
69
|
+
* but framework-agnostic.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* import http from "http";
|
|
73
|
+
* import { openapi } from "./scheduler";
|
|
74
|
+
*
|
|
75
|
+
* const api = openapi({ title: "My API", version: "1.0.0" });
|
|
76
|
+
*
|
|
77
|
+
* api.spec.paths["/hello"] = {
|
|
78
|
+
* get: { summary: "Say hello", responses: { 200: { description: "OK" } } },
|
|
79
|
+
* };
|
|
80
|
+
*
|
|
81
|
+
* http.createServer((req, res) => {
|
|
82
|
+
* if (req.url === "/docs") return api.scalar({ theme: "moon" })(req, res);
|
|
83
|
+
* if (req.url === "/openapi.json") return api.json()(req, res);
|
|
84
|
+
* }).listen(3000);
|
|
85
|
+
*/
|
|
86
|
+
export declare const openapi: (info: {
|
|
87
|
+
title: string;
|
|
88
|
+
version: string;
|
|
89
|
+
}) => OpenAPIBuilder;
|
|
13
90
|
//# sourceMappingURL=scheduler.d.ts.map
|
package/dist/scheduler.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAIF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,4JAA4J;IAC5J,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC9B,qCAAqC;IACrC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,IAAI,EAAE,WAAW,CAAC;IAElB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAErC;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;QACnD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QACjE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,KAAK,IAAI,CAAC;IAEX;;;;;OAKG;IACH,IAAI,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;QAC1B,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;QACjE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,KAAK,IAAI,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,KAAG,cAyDlE,CAAC"}
|
package/dist/scheduler.js
CHANGED
|
@@ -23,7 +23,60 @@ var cron = (pattern, task) => {
|
|
|
23
23
|
setInterval(check, 60000);
|
|
24
24
|
};
|
|
25
25
|
var openapi = (info) => {
|
|
26
|
-
|
|
26
|
+
const spec = { openapi: "3.0.0", info, paths: {} };
|
|
27
|
+
const renderScalarHtml = (options = {}) => {
|
|
28
|
+
const {
|
|
29
|
+
pageTitle = spec.info.title,
|
|
30
|
+
theme = "default",
|
|
31
|
+
layout = "modern",
|
|
32
|
+
hideDownloadButton = false,
|
|
33
|
+
customCss = ""
|
|
34
|
+
} = options;
|
|
35
|
+
return `<!doctype html>
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="utf-8" />
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
40
|
+
<title>${pageTitle}</title>
|
|
41
|
+
${customCss ? `<style>${customCss}</style>` : ""}
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<script
|
|
45
|
+
id="api-reference"
|
|
46
|
+
type="application/json"
|
|
47
|
+
data-theme="${theme}"
|
|
48
|
+
data-layout="${layout}"
|
|
49
|
+
${hideDownloadButton ? 'data-hide-download-button="true"' : ""}
|
|
50
|
+
>${JSON.stringify(spec)}</script>
|
|
51
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>`;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
spec,
|
|
57
|
+
get openapi() {
|
|
58
|
+
return spec.openapi;
|
|
59
|
+
},
|
|
60
|
+
get info() {
|
|
61
|
+
return spec.info;
|
|
62
|
+
},
|
|
63
|
+
get paths() {
|
|
64
|
+
return spec.paths;
|
|
65
|
+
},
|
|
66
|
+
scalar(options = {}) {
|
|
67
|
+
return (_req, res) => {
|
|
68
|
+
const html = renderScalarHtml(options);
|
|
69
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
70
|
+
res.end(html);
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
json() {
|
|
74
|
+
return (_req, res) => {
|
|
75
|
+
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
76
|
+
res.end(JSON.stringify(spec, null, 2));
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
27
80
|
};
|
|
28
81
|
export {
|
|
29
82
|
openapi,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "princejs",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.4",
|
|
4
4
|
"description": "An easy and fast backend framework that is among the top three — by a 13yo developer, for developers.",
|
|
5
5
|
"main": "dist/prince.js",
|
|
6
6
|
"types": "dist/prince.d.ts",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"jose": "^6.1.2"
|
|
112
112
|
},
|
|
113
113
|
"scripts": {
|
|
114
|
-
"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 && bun build src/db.ts --outdir dist --target bun --format esm && bun build src/client.ts --outdir dist --format esm && bun build src/adapters/vercel.ts --outdir dist/adapters --format esm && bun build src/adapters/cloudflare.ts --outdir dist/adapters --format esm && bun build src/adapters/deno.ts --outdir dist/adapters --format esm",
|
|
114
|
+
"build:js": "bun build src/prince.ts --outdir dist --target bun --external zod --external jose && 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 && bun build src/db.ts --outdir dist --target bun --format esm && bun build src/client.ts --outdir dist --format esm && bun build src/adapters/vercel.ts --outdir dist/adapters --format esm && bun build src/adapters/cloudflare.ts --outdir dist/adapters --format esm && bun build src/adapters/deno.ts --outdir dist/adapters --format esm",
|
|
115
115
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
116
116
|
"build": "bun run build:js && bun run build:types",
|
|
117
117
|
"test": "bun test",
|