create-celsian 0.3.12 → 0.3.14
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 +5 -586
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -60
- package/dist/index.js.map +1 -1
- package/dist/templates/basic.d.ts.map +1 -1
- package/dist/templates/basic.js +2 -4
- package/dist/templates/basic.js.map +1 -1
- package/dist/templates/full.d.ts.map +1 -1
- package/dist/templates/full.js +31 -33
- package/dist/templates/full.js.map +1 -1
- package/dist/templates/rest-api.d.ts.map +1 -1
- package/dist/templates/rest-api.js +2 -4
- package/dist/templates/rest-api.js.map +1 -1
- package/dist/templates/rpc-api.d.ts.map +1 -1
- package/dist/templates/rpc-api.js +7 -11
- package/dist/templates/rpc-api.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,596 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Create Celsian
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffold new CelsianJS applications.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- **Significantly faster than Express** -- Radix-tree router, zero-copy request building, pre-stringified error paths. 1.3x-1.7x faster across all scenarios.
|
|
7
|
-
- **First-party batteries** -- Background tasks, cron, WebSocket, CORS, CSRF protection, security headers, DB analytics, rate limiting, JWT, caching, compression, and OpenAPI docs via core APIs plus first-party packages.
|
|
8
|
-
- **Fastify-style plugin encapsulation** -- Scoped hooks and decorations by default. No accidental middleware leaks.
|
|
9
|
-
- **Schema-agnostic validation** -- Auto-detects Zod, TypeBox, or Valibot. No config, no adapters.
|
|
5
|
+
This package is part of the [CelsianJS](https://github.com/CelsianJs/celsian) monorepo. See the root repository README for framework documentation, examples, and release notes.
|
|
10
6
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
This monorepo is pinned to `pnpm@9.15.0`. For local release verification, prefer:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm run setup:pnpm
|
|
17
|
-
# or, if Corepack is unavailable locally:
|
|
18
|
-
npx -y pnpm@9.15.0 <script>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
CI and release gates should use the pinned package manager version, not an older global pnpm.
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npx create-celsian my-api
|
|
27
|
-
cd my-api
|
|
28
|
-
npm install
|
|
29
|
-
npm run dev
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Or manually for the core router/runtime:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
npm install @celsian/core
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { createApp, serve } from '@celsian/core';
|
|
40
|
-
|
|
41
|
-
const app = createApp({ logger: true });
|
|
42
|
-
|
|
43
|
-
// ─── Background tasks with retries ───
|
|
44
|
-
app.task({
|
|
45
|
-
name: 'sendWelcomeEmail',
|
|
46
|
-
retries: 3,
|
|
47
|
-
async handler(input: { to: string }) {
|
|
48
|
-
await sendEmail(input.to, 'Welcome!');
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// ─── Cron job: clean up expired sessions every night ───
|
|
53
|
-
app.cron('cleanup', '0 3 * * *', async () => {
|
|
54
|
-
await db.query('DELETE FROM sessions WHERE expires_at < NOW()');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// ─── API routes ───
|
|
58
|
-
app.post('/users', async (req, reply) => {
|
|
59
|
-
const body = req.parsedBody as { name: string; email: string };
|
|
60
|
-
const user = await db.createUser(body);
|
|
61
|
-
await app.enqueue('sendWelcomeEmail', { to: body.email });
|
|
62
|
-
return reply.status(201).json(user);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
app.get('/users/:id', (req, reply) => {
|
|
66
|
-
return reply.json({ id: req.params.id, name: 'Alice' });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
serve(app, { port: 3000 });
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Tasks, cron, and API routes in one file -- no separate worker process needed. On Bun or Deno, `serve()` auto-detects the runtime. No code changes needed.
|
|
7
|
+
## Installation
|
|
73
8
|
|
|
74
9
|
```bash
|
|
75
|
-
|
|
76
|
-
deno run server.ts # Uses Deno.serve() automatically
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Why CelsianJS
|
|
80
|
-
|
|
81
|
-
### Multi-Runtime
|
|
82
|
-
|
|
83
|
-
Built on Web Standard `Request`/`Response` -- not Node.js `IncomingMessage`/`ServerResponse`. One adapter line deploys anywhere:
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
export default createCloudflareHandler(app); // Cloudflare Workers
|
|
87
|
-
export const handler = createLambdaHandler(app); // AWS Lambda
|
|
88
|
-
export default createVercelEdgeHandler(app); // Vercel Edge
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Honest Benchmarks
|
|
92
|
-
|
|
93
|
-
Benchmarked on Node.js v22, Apple Silicon, 10 connections for 10 seconds per scenario:
|
|
94
|
-
|
|
95
|
-
| Scenario | Fastify (req/s) | CelsianJS (req/s) | Express (req/s) |
|
|
96
|
-
| --------------------- | ---------------: | -----------------: | --------------: |
|
|
97
|
-
| JSON response | 45,866 | 27,996 | 16,321 |
|
|
98
|
-
| Route params | 45,440 | 27,026 | 16,288 |
|
|
99
|
-
| Middleware (5 layers) | 41,380 | 24,445 | 15,751 |
|
|
100
|
-
| JSON body parsing | 29,998 | 19,074 | 14,648 |
|
|
101
|
-
| Error handling | 32,398 | 18,542 | 14,765 |
|
|
102
|
-
|
|
103
|
-
**Fastify is faster.** It operates directly on Node.js internals with `fast-json-stringify` — hard to beat. CelsianJS pays a performance tax for Web Standard API compatibility (`Request`/`Response` object creation per request).
|
|
104
|
-
|
|
105
|
-
**CelsianJS is 1.3-1.7x faster than Express** while shipping batteries that neither Fastify nor Express include: background task queues, cron scheduling, multi-runtime deployment, and DB analytics. If raw throughput is your only concern, use Fastify. If you need application infrastructure in a single framework, that's where CelsianJS fits.
|
|
106
|
-
|
|
107
|
-
### Built-In Everything
|
|
108
|
-
|
|
109
|
-
Celsian's batteries ship as first-party packages. Install the extras you use:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
npm install @celsian/core @celsian/rate-limit @celsian/jwt @celsian/compress
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import { createApp, security, cors, csrf, openapi } from '@celsian/core';
|
|
117
|
-
import { rateLimit } from '@celsian/rate-limit';
|
|
118
|
-
import { jwt } from '@celsian/jwt';
|
|
119
|
-
import { compress } from '@celsian/compress';
|
|
120
|
-
|
|
121
|
-
const app = createApp();
|
|
122
|
-
|
|
123
|
-
await app.register(security(), { encapsulate: false }); // Helmet-style headers
|
|
124
|
-
await app.register(cors({ origin: 'https://myapp.com' }));
|
|
125
|
-
await app.register(csrf(), { encapsulate: false }); // CSRF token protection
|
|
126
|
-
await app.register(rateLimit({ max: 100, window: 60_000, keyGenerator: req => req.headers.get("x-api-key") ?? "public" }));
|
|
127
|
-
await app.register(compress());
|
|
128
|
-
await app.register(jwt({ secret: process.env.JWT_SECRET! }));
|
|
129
|
-
await app.register(openapi({ title: 'My API' }));
|
|
130
|
-
|
|
131
|
-
app.health(); // /health + /ready
|
|
132
|
-
app.task({ name: 'email', handler, retries: 3 }); // Background tasks
|
|
133
|
-
app.cron('cleanup', '0 3 * * *', cleanupHandler); // Cron jobs
|
|
134
|
-
app.ws('/chat', { open, message, close }); // WebSocket
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Plugin Encapsulation
|
|
138
|
-
|
|
139
|
-
Plugins get isolated scopes by default. Hooks and decorations registered inside a plugin do not leak to sibling plugins or the parent scope.
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// Auth plugin -- hooks only apply to routes registered inside
|
|
143
|
-
async function authPlugin(app) {
|
|
144
|
-
app.addHook('onRequest', async (req, reply) => {
|
|
145
|
-
const token = req.headers.get('authorization');
|
|
146
|
-
if (!token) return reply.unauthorized();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
app.get('/me', (req, reply) => {
|
|
150
|
-
return reply.json({ user: req.user });
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Public routes -- no auth required
|
|
155
|
-
app.get('/health', (req, reply) => reply.json({ status: 'ok' }));
|
|
156
|
-
|
|
157
|
-
// Register auth plugin under /api prefix
|
|
158
|
-
await app.register(authPlugin, { prefix: '/api' });
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Use `{ encapsulate: false }` when a plugin should affect all routes (e.g., CORS, database):
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
await app.register(cors(), { encapsulate: false });
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Type-Safe Schema Validation
|
|
168
|
-
|
|
169
|
-
Pass any Zod, TypeBox, or Valibot schema. CelsianJS auto-detects the library.
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
app.route({
|
|
173
|
-
method: 'POST',
|
|
174
|
-
url: '/users',
|
|
175
|
-
schema: {
|
|
176
|
-
body: z.object({ name: z.string().min(1), email: z.string().email() }),
|
|
177
|
-
},
|
|
178
|
-
handler(req, reply) {
|
|
179
|
-
const { name, email } = req.parsedBody as { name: string; email: string };
|
|
180
|
-
return reply.status(201).json({ id: '1', name, email });
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
// Invalid input returns 400 with structured issues automatically
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### JSX Server Rendering
|
|
187
|
-
|
|
188
|
-
CelsianJS includes a built-in JSX runtime for server-side HTML rendering -- no React dependency needed. Supports both automatic and classic transforms.
|
|
189
|
-
|
|
190
|
-
**Setup (automatic transform -- recommended):**
|
|
191
|
-
|
|
192
|
-
```jsonc
|
|
193
|
-
// tsconfig.json
|
|
194
|
-
{
|
|
195
|
-
"compilerOptions": {
|
|
196
|
-
"jsx": "react-jsx",
|
|
197
|
-
"jsxImportSource": "@celsian/core"
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Setup (classic transform):**
|
|
203
|
-
|
|
204
|
-
```jsonc
|
|
205
|
-
// tsconfig.json
|
|
206
|
-
{
|
|
207
|
-
"compilerOptions": {
|
|
208
|
-
"jsx": "react",
|
|
209
|
-
"jsxFactory": "h",
|
|
210
|
-
"jsxFragmentFactory": "Fragment"
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
import { h, Fragment } from '@celsian/core/jsx';
|
|
10
|
+
npm install create-celsian
|
|
217
11
|
```
|
|
218
12
|
|
|
219
|
-
**Usage:**
|
|
220
|
-
|
|
221
|
-
```tsx
|
|
222
|
-
import { renderToString, renderToDocument } from '@celsian/core';
|
|
223
|
-
|
|
224
|
-
// Function components work like React
|
|
225
|
-
function Layout({ title, children }: { title: string; children: any }) {
|
|
226
|
-
return (
|
|
227
|
-
<html>
|
|
228
|
-
<head><title>{title}</title></head>
|
|
229
|
-
<body>{children}</body>
|
|
230
|
-
</html>
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function UserCard({ name, email }: { name: string; email: string }) {
|
|
235
|
-
return (
|
|
236
|
-
<div className="card">
|
|
237
|
-
<h2>{name}</h2>
|
|
238
|
-
<p>{email}</p>
|
|
239
|
-
</div>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
app.get('/page', (req, reply) => {
|
|
244
|
-
const html = renderToDocument(
|
|
245
|
-
<Layout title="Users">
|
|
246
|
-
<UserCard name="Alice" email="alice@example.com" />
|
|
247
|
-
</Layout>
|
|
248
|
-
);
|
|
249
|
-
return reply.html(html);
|
|
250
|
-
});
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
Supports `className`/`htmlFor` mapping, style objects, boolean attributes, void elements, `dangerouslySetInnerHTML`, Fragments, and XSS-safe escaping.
|
|
254
|
-
|
|
255
|
-
## Features at a Glance
|
|
256
|
-
|
|
257
|
-
| Category | Features |
|
|
258
|
-
| -------- | -------- |
|
|
259
|
-
| **Routing** | Radix-tree router, params, wildcards, HEAD fallback, 405, route tagging |
|
|
260
|
-
| **Hooks** | 8-hook lifecycle (onRequest through onResponse), route-level hooks |
|
|
261
|
-
| **Plugins** | Scoped encapsulation, app/request/reply decorators |
|
|
262
|
-
| **Validation** | Zod, TypeBox, Valibot auto-detect; body, querystring, params schemas |
|
|
263
|
-
| **Reply** | json, html, stream, redirect, sendFile (Range/ETag/304), download, cookies, 9 error helpers, JSX SSR |
|
|
264
|
-
| **Security** | Helmet-style headers, CORS, CSRF protection, JWT, fixed-window rate limiting |
|
|
265
|
-
| **Background** | Task queue with retries, cron scheduling, Redis queue backend |
|
|
266
|
-
| **Real-time** | WebSocket with broadcast and connection management |
|
|
267
|
-
| **Database** | Connection pool plugin, transactions, query analytics, Server-Timing |
|
|
268
|
-
| **Caching** | Response cache, session management (KV store) |
|
|
269
|
-
| **Infra** | Brotli/gzip/deflate compression, OpenAPI 3.1 + Swagger UI, structured logging, inject() testing |
|
|
270
|
-
| **Deploy** | Node, Bun, Deno, Workers, Lambda, Vercel, Fly.io, Railway, graceful shutdown |
|
|
271
|
-
|
|
272
|
-
## Core Concepts
|
|
273
|
-
|
|
274
|
-
### Routes and Handlers
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
app.get('/users/:id', (req, reply) => {
|
|
278
|
-
return reply.json({ id: req.params.id, include: req.query.include });
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Full route options with schema, hooks, and deployment tagging
|
|
282
|
-
app.route({
|
|
283
|
-
method: 'POST',
|
|
284
|
-
url: '/items',
|
|
285
|
-
kind: 'serverless',
|
|
286
|
-
schema: { body: mySchema },
|
|
287
|
-
preHandler: [authHook],
|
|
288
|
-
handler(req, reply) { return reply.status(201).json(req.parsedBody); },
|
|
289
|
-
});
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Hooks Lifecycle
|
|
293
|
-
|
|
294
|
-
8 hooks run in order: `onRequest` > `preParsing` > `preValidation` > `preHandler` > `handler` > `preSerialization` > `onSend` > `onResponse`. Plus `onError` for error handling. Any hook can short-circuit by returning a `Response`.
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
// Global hook
|
|
298
|
-
app.addHook('onRequest', async (req, reply) => {
|
|
299
|
-
reply.header('x-request-id', crypto.randomUUID());
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Route-level hook
|
|
303
|
-
app.route({
|
|
304
|
-
method: 'POST',
|
|
305
|
-
url: '/admin/users',
|
|
306
|
-
onRequest: [requireAdmin],
|
|
307
|
-
handler(req, reply) { return reply.json({ created: true }); },
|
|
308
|
-
});
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
See [Hooks Lifecycle](docs/hooks.md) for the complete guide.
|
|
312
|
-
|
|
313
|
-
### Reply Helpers
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
reply.json({ data: [] }); // JSON response
|
|
317
|
-
reply.html('<h1>Hello</h1>'); // HTML response
|
|
318
|
-
reply.stream(readableStream); // Streaming
|
|
319
|
-
reply.redirect('/new-path', 301); // Redirect
|
|
320
|
-
await reply.sendFile('/path/to/report.pdf'); // Serve file
|
|
321
|
-
await reply.download('/path/to/data.csv', 'export'); // Download
|
|
322
|
-
|
|
323
|
-
// Structured error responses
|
|
324
|
-
reply.notFound('User not found'); // 404
|
|
325
|
-
reply.badRequest('Missing email'); // 400
|
|
326
|
-
reply.unauthorized('Token expired'); // 401
|
|
327
|
-
reply.forbidden(); // 403
|
|
328
|
-
reply.conflict(); // 409
|
|
329
|
-
reply.tooManyRequests(); // 429
|
|
330
|
-
|
|
331
|
-
// Cookies + chaining
|
|
332
|
-
reply.cookie('session', token, { httpOnly: true, secure: true });
|
|
333
|
-
return reply.status(201).header('x-custom', 'value').json({ id: '1' });
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
#### File Serving with Range Requests
|
|
337
|
-
|
|
338
|
-
`sendFile` and `download` support Range requests (HTTP 206), conditional GET (304), ETag, and Last-Modified headers. Pass `options.request` to enable these features:
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
app.get('/files/:name', async (req, reply) => {
|
|
342
|
-
return reply.sendFile(req.params.name, {
|
|
343
|
-
root: './uploads', // Resolve relative to this directory (with traversal protection)
|
|
344
|
-
request: req, // Enable Range requests + conditional GET (304)
|
|
345
|
-
cacheControl: 'public, max-age=3600', // Set Cache-Control header
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// Download with Range support (for resumable downloads)
|
|
350
|
-
app.get('/export/:id', async (req, reply) => {
|
|
351
|
-
return reply.download(`./data/${req.params.id}.csv`, 'export.csv', {
|
|
352
|
-
request: req,
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Supported Range formats: `bytes=0-499`, `bytes=500-`, `bytes=-500`, and multi-range (`bytes=0-499, 1000-1499`). Invalid ranges return 416 Range Not Satisfiable.
|
|
358
|
-
|
|
359
|
-
#### Compression with Brotli
|
|
360
|
-
|
|
361
|
-
The compression plugin prefers Brotli over gzip when the client supports it, providing better compression ratios for text content:
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
import { compress } from '@celsian/compress';
|
|
365
|
-
|
|
366
|
-
// Brotli + gzip + deflate (default)
|
|
367
|
-
await app.register(compress());
|
|
368
|
-
|
|
369
|
-
// Custom Brotli quality (0-11, default 4)
|
|
370
|
-
await app.register(compress({ brotliQuality: 6 }));
|
|
371
|
-
|
|
372
|
-
// Gzip/deflate only (disable Brotli)
|
|
373
|
-
await app.register(compress({ encodings: ['gzip', 'deflate'] }));
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### Error Handling
|
|
377
|
-
|
|
378
|
-
Thrown errors are caught and returned as structured JSON. Stack traces are stripped in production.
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
import { HttpError } from '@celsian/core';
|
|
382
|
-
|
|
383
|
-
// Throw HTTP errors anywhere
|
|
384
|
-
throw new HttpError(403, 'Forbidden');
|
|
385
|
-
// { "error": "Forbidden", "statusCode": 403, "code": "FORBIDDEN" }
|
|
386
|
-
|
|
387
|
-
// Custom error handler
|
|
388
|
-
app.setErrorHandler((error, req, reply) => {
|
|
389
|
-
if (error.message.includes('UNIQUE constraint')) return reply.conflict();
|
|
390
|
-
return reply.internalServerError();
|
|
391
|
-
});
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Type-Safe RPC
|
|
395
|
-
|
|
396
|
-
`@celsian/rpc` provides tRPC-style procedures with type inference, middleware, and OpenAPI generation.
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
// server.ts
|
|
400
|
-
import { procedure, router, RPCHandler } from '@celsian/rpc';
|
|
401
|
-
|
|
402
|
-
const appRouter = router({
|
|
403
|
-
users: {
|
|
404
|
-
list: procedure
|
|
405
|
-
.input(z.object({ limit: z.number().optional() }))
|
|
406
|
-
.query(async ({ input }) => [{ id: '1', name: 'Alice' }]),
|
|
407
|
-
create: procedure
|
|
408
|
-
.input(z.object({ name: z.string(), email: z.string().email() }))
|
|
409
|
-
.mutation(async ({ input }) => ({ id: '2', ...input })),
|
|
410
|
-
},
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
const rpc = new RPCHandler(appRouter);
|
|
414
|
-
app.route({ method: ['GET', 'POST'], url: '/_rpc/*path', handler: (req) => rpc.handle(req) });
|
|
415
|
-
export type AppRouter = typeof appRouter;
|
|
416
|
-
|
|
417
|
-
// client.ts
|
|
418
|
-
const client = createRPCClient<AppRouter>({ baseUrl: 'http://localhost:3000/_rpc' });
|
|
419
|
-
const users = await client.users.list.query({ limit: 10 });
|
|
420
|
-
const newUser = await client.users.create.mutate({ name: 'Bob', email: 'bob@example.com' });
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
## Try the Demo
|
|
424
|
-
|
|
425
|
-
The [SaaS Demo](examples/saas-demo/) builds a complete backend in one file (~250 lines): JWT auth, users CRUD, background tasks, cron, SSE, and OpenAPI docs.
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
cd examples/saas-demo
|
|
429
|
-
npm install
|
|
430
|
-
npx tsx src/index.ts
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
Then hit `http://localhost:3000/docs` for the Swagger UI, or:
|
|
434
|
-
|
|
435
|
-
```bash
|
|
436
|
-
# Register
|
|
437
|
-
curl -X POST http://localhost:3000/register \
|
|
438
|
-
-H 'Content-Type: application/json' \
|
|
439
|
-
-d '{"email":"alice@example.com","password":"secret123","name":"Alice"}'
|
|
440
|
-
|
|
441
|
-
# Login and grab the token
|
|
442
|
-
curl -X POST http://localhost:3000/login \
|
|
443
|
-
-H 'Content-Type: application/json' \
|
|
444
|
-
-d '{"email":"alice@example.com","password":"secret123"}'
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
## Ecosystem
|
|
448
|
-
|
|
449
|
-
### Core Packages
|
|
450
|
-
|
|
451
|
-
| Package | Description |
|
|
452
|
-
| ------- | ----------- |
|
|
453
|
-
| `@celsian/core` | Server runtime, routing, hooks, plugins, task queue, cron, WebSocket, CORS, security, database, OpenAPI |
|
|
454
|
-
| `@celsian/schema` | Standard Schema adapters -- auto-detects Zod, TypeBox, Valibot |
|
|
455
|
-
| `@celsian/rpc` | Type-safe RPC procedures, middleware, OpenAPI generation, typed client |
|
|
456
|
-
| `@celsian/jwt` | JWT sign/verify plugin with route guard helper |
|
|
457
|
-
| `@celsian/cache` | KV store, response caching, session management |
|
|
458
|
-
| `@celsian/rate-limit` | Fixed-window rate limiter with pluggable store |
|
|
459
|
-
| `@celsian/compress` | Response compression (Brotli, gzip, deflate) |
|
|
460
|
-
| `@celsian/queue-redis` | Redis-backed task queue for production |
|
|
461
|
-
|
|
462
|
-
### Deployment Adapters
|
|
463
|
-
|
|
464
|
-
| Package | Target |
|
|
465
|
-
| ------- | ------ |
|
|
466
|
-
| `@celsian/adapter-cloudflare` | Cloudflare Workers (env bindings, execution context) |
|
|
467
|
-
| `@celsian/adapter-lambda` | AWS Lambda + API Gateway v2 |
|
|
468
|
-
| `@celsian/adapter-vercel` | Vercel Serverless + Edge Functions |
|
|
469
|
-
| `@celsian/adapter-node` | Standalone Node.js server |
|
|
470
|
-
| `@celsian/adapter-fly` | Fly.io (generates fly.toml, Dockerfile, multi-region) |
|
|
471
|
-
| `@celsian/adapter-railway` | Railway (generates railway.json, Procfile) |
|
|
472
|
-
|
|
473
|
-
### Tooling
|
|
474
|
-
|
|
475
|
-
| Package | Description |
|
|
476
|
-
| ------- | ----------- |
|
|
477
|
-
| `create-celsian` | Project scaffolder (`npx create-celsian my-api`) |
|
|
478
|
-
| `@celsian/cli` | Dev server, route listing, code generation |
|
|
479
|
-
| `celsian` | Meta-package for single-import convenience |
|
|
480
|
-
|
|
481
|
-
## Production Features
|
|
482
|
-
|
|
483
|
-
### Graceful Shutdown
|
|
484
|
-
|
|
485
|
-
On SIGTERM/SIGINT: stops accepting connections, drains in-flight requests, stops workers and cron, runs cleanup.
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
serve(app, {
|
|
489
|
-
shutdownTimeout: 15_000,
|
|
490
|
-
onShutdown: () => db.close(),
|
|
491
|
-
});
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
### Health Checks and Route Manifest
|
|
495
|
-
|
|
496
|
-
```typescript
|
|
497
|
-
app.health({ check: () => pool.isHealthy() }); // /health + /ready
|
|
498
|
-
|
|
499
|
-
// Tag routes for deployment tooling
|
|
500
|
-
app.route({ method: 'GET', url: '/api/users', kind: 'serverless', handler });
|
|
501
|
-
app.route({ method: 'GET', url: '/ws', kind: 'hot', handler });
|
|
502
|
-
const manifest = app.getRouteManifest(); // { serverless: [...], hot: [...], task: [...] }
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
### Database Analytics
|
|
506
|
-
|
|
507
|
-
Wrap your pool with `trackedPool()` for per-request query metrics, `Server-Timing` headers, and slow query logging -- zero handler changes. See [Database Plugin](docs/database.md).
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
const pool = trackedPool(pgPool);
|
|
511
|
-
await app.register(database({ createPool: () => pool }), { encapsulate: false });
|
|
512
|
-
await app.register(dbAnalytics({ slowThreshold: 100 }), { encapsulate: false });
|
|
513
|
-
// Response: Server-Timing: db;dur=12.5;desc="3 queries"
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Testing Without a Server
|
|
517
|
-
|
|
518
|
-
```typescript
|
|
519
|
-
const response = await app.inject({ method: 'GET', url: '/hello' });
|
|
520
|
-
const body = await response.json(); // { hello: 'world' }
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
## Deployment
|
|
524
|
-
|
|
525
|
-
Swap the entry point for the JavaScript runtime you are deploying to. See [Deployment Guide](docs/deployment.md) for full instructions and provider-specific credential/config requirements.
|
|
526
|
-
|
|
527
|
-
```typescript
|
|
528
|
-
serve(app, { port: 3000 }); // Node / Bun / Deno
|
|
529
|
-
|
|
530
|
-
export default createCloudflareHandler(app); // Cloudflare Workers
|
|
531
|
-
export const handler = createLambdaHandler(app); // AWS Lambda
|
|
532
|
-
export default createVercelHandler(app); // Vercel Serverless
|
|
533
|
-
export default createVercelEdgeHandler(app); // Vercel Edge
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Fly.io and Railway adapters auto-generate deployment configs (`fly.toml`, Dockerfile, `railway.json`). Adapter packages are covered by local generation/package smoke tests; live cloud deployment still depends on each provider's credentials and project configuration.
|
|
537
|
-
|
|
538
|
-
## Benchmark Results
|
|
539
|
-
|
|
540
|
-
Node.js v22.13.1, macOS Darwin (Apple Silicon), 10 connections, 10s per scenario.
|
|
541
|
-
|
|
542
|
-
| Scenario | Fastify (req/s) | CelsianJS (req/s) | Express (req/s) |
|
|
543
|
-
| --------------------- | ---------------: | -----------------: | --------------: |
|
|
544
|
-
| JSON response | 45,866 | 27,996 | 16,321 |
|
|
545
|
-
| Route params | 45,440 | 27,026 | 16,288 |
|
|
546
|
-
| Middleware (5) | 41,380 | 24,445 | 15,751 |
|
|
547
|
-
| JSON body parsing | 29,998 | 19,074 | 14,648 |
|
|
548
|
-
| Error handling | 32,398 | 18,542 | 14,765 |
|
|
549
|
-
|
|
550
|
-
Fastify is the fastest Node.js framework. CelsianJS is 1.3-1.7x faster than Express. The gap with Fastify comes from Web Standard API overhead (`Request`/`Response` per request). CelsianJS trades some throughput for multi-runtime portability and built-in application infrastructure.
|
|
551
|
-
|
|
552
|
-
## Configuration
|
|
553
|
-
|
|
554
|
-
CelsianJS loads `celsian.config.ts` (or `.js`/`.mjs`) automatically:
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
import { defineConfig } from '@celsian/core';
|
|
558
|
-
|
|
559
|
-
export default defineConfig({
|
|
560
|
-
server: { port: 3000, host: 'localhost', trustProxy: true },
|
|
561
|
-
schema: { provider: 'auto' }, // or 'zod' | 'typebox' | 'valibot'
|
|
562
|
-
});
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
## Documentation
|
|
566
|
-
|
|
567
|
-
- [Quick Start Guide](docs/quickstart.md)
|
|
568
|
-
- [Hooks Lifecycle](docs/hooks.md)
|
|
569
|
-
- [Plugins and Encapsulation](docs/plugins.md)
|
|
570
|
-
- [Deployment Guide](docs/deployment.md)
|
|
571
|
-
- [Database Plugin](docs/database.md)
|
|
572
|
-
|
|
573
|
-
## WhatStack
|
|
574
|
-
|
|
575
|
-
CelsianJS is the backend half of [WhatStack](https://whatfw.com) — the agent-first full-stack framework:
|
|
576
|
-
|
|
577
|
-
| Layer | Framework | What It Does |
|
|
578
|
-
|-------|-----------|-------------|
|
|
579
|
-
| Frontend | [WhatFW](https://whatfw.com) | Signals, fine-grained rendering, MCP DevTools |
|
|
580
|
-
| Backend | **CelsianJS** | Hooks, plugins, tasks, cron, RPC, multi-runtime |
|
|
581
|
-
| Deploy | [Vura](https://github.com/zvndev/vura) | Platform deployment (coming soon) |
|
|
582
|
-
|
|
583
|
-
## Contributing
|
|
584
|
-
|
|
585
|
-
```bash
|
|
586
|
-
git clone https://github.com/CelsianJs/celsian.git
|
|
587
|
-
cd celsian
|
|
588
|
-
pnpm install
|
|
589
|
-
pnpm test
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
The project uses pnpm workspaces. All packages are in `packages/`. Tests use Vitest.
|
|
593
|
-
|
|
594
13
|
## License
|
|
595
14
|
|
|
596
15
|
MIT
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// create-celsian — Project scaffolder
|
|
3
3
|
// Zero external dependencies. Interactive prompts via raw stdin.
|
|
4
|
-
import {
|
|
5
|
-
import { basename, dirname, isAbsolute, join,
|
|
4
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
7
|
import { basicTemplate } from "./templates/basic.js";
|
|
9
8
|
import { fullTemplate } from "./templates/full.js";
|
|
10
9
|
import { restApiTemplate } from "./templates/rest-api.js";
|
|
11
10
|
import { rpcApiTemplate } from "./templates/rpc-api.js";
|
|
11
|
+
// ─── Template Registry ───
|
|
12
12
|
const templates = {
|
|
13
13
|
full: fullTemplate,
|
|
14
14
|
basic: basicTemplate,
|
|
@@ -21,6 +21,17 @@ const templateDescriptions = {
|
|
|
21
21
|
"rest-api": "REST API with TypeBox schemas",
|
|
22
22
|
"rpc-api": "RPC-first with typed client",
|
|
23
23
|
};
|
|
24
|
+
// ─── CLI Argument Parsing ───
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
// Handle --help
|
|
27
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
28
|
+
printUsage();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
// Extract flags
|
|
32
|
+
const templateFlag = args.indexOf("--template");
|
|
33
|
+
const templateArg = templateFlag !== -1 ? args[templateFlag + 1] : undefined;
|
|
34
|
+
const nameArg = args.find((a) => !a.startsWith("--") && (templateFlag === -1 || args.indexOf(a) !== templateFlag + 1));
|
|
24
35
|
// ─── Interactive Mode ───
|
|
25
36
|
async function prompt(question, defaultValue) {
|
|
26
37
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -58,14 +69,27 @@ async function interactiveMode() {
|
|
|
58
69
|
return { name, template, pm };
|
|
59
70
|
}
|
|
60
71
|
// ─── Scaffold ───
|
|
61
|
-
|
|
72
|
+
function scaffold(name, template, pm) {
|
|
62
73
|
const files = templates[template];
|
|
63
74
|
if (!files) {
|
|
64
75
|
console.error(`\n Unknown template: ${template}`);
|
|
65
76
|
console.error(` Available: ${Object.keys(templates).join(", ")}\n`);
|
|
66
77
|
process.exit(1);
|
|
67
78
|
}
|
|
68
|
-
|
|
79
|
+
// Sanitize: reject names containing path traversal
|
|
80
|
+
if (name.includes("..")) {
|
|
81
|
+
console.error("\n Invalid project name: must not contain '..'.\n");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const cwd = process.cwd();
|
|
85
|
+
const dir = isAbsolute(name) ? name : join(cwd, name);
|
|
86
|
+
const resolved = resolve(dir);
|
|
87
|
+
// Ensure the resolved path is a child of cwd
|
|
88
|
+
if (!resolved.startsWith(cwd + "/") && resolved !== cwd) {
|
|
89
|
+
console.error("\n Invalid project name: resolved path must be inside the current directory.\n");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const projectName = basename(dir);
|
|
69
93
|
console.log(`\n Creating Celsian project: ${projectName}`);
|
|
70
94
|
console.log(` Template: ${template}`);
|
|
71
95
|
console.log("");
|
|
@@ -91,57 +115,8 @@ export function scaffold(name, template, pm) {
|
|
|
91
115
|
}
|
|
92
116
|
console.log("");
|
|
93
117
|
}
|
|
94
|
-
function validateTarget(name) {
|
|
95
|
-
const trimmedName = name.trim();
|
|
96
|
-
if (!trimmedName) {
|
|
97
|
-
console.error("\n Invalid project name: name is required.\n");
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
if (trimmedName.split(/[\\/]+/).includes("..")) {
|
|
101
|
-
console.error("\n Invalid project name: must not contain '..'.\n");
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
const cwd = resolve(process.cwd());
|
|
105
|
-
const dir = resolve(isAbsolute(trimmedName) ? trimmedName : join(cwd, trimmedName));
|
|
106
|
-
const rel = relative(cwd, dir);
|
|
107
|
-
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
108
|
-
console.error("\n Invalid project name: resolved path must be inside the current directory.\n");
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
if (existsSync(dir)) {
|
|
112
|
-
const entries = readdirSync(dir);
|
|
113
|
-
if (entries.length > 0) {
|
|
114
|
-
console.error("\n Refusing to create project: target directory already exists and is not empty.\n");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const projectName = sanitizePackageName(basename(dir));
|
|
119
|
-
if (!projectName) {
|
|
120
|
-
console.error("\n Invalid project name: cannot derive a valid package name.\n");
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
return { dir, projectName };
|
|
124
|
-
}
|
|
125
|
-
function sanitizePackageName(name) {
|
|
126
|
-
return name
|
|
127
|
-
.trim()
|
|
128
|
-
.toLowerCase()
|
|
129
|
-
.replace(/^[._-]+/, "")
|
|
130
|
-
.replace(/[._\s]+/g, "-")
|
|
131
|
-
.replace(/[^a-z0-9-]/g, "")
|
|
132
|
-
.replace(/-+/g, "-")
|
|
133
|
-
.replace(/-+$/g, "");
|
|
134
|
-
}
|
|
135
118
|
// ─── Main ───
|
|
136
119
|
async function main() {
|
|
137
|
-
const args = process.argv.slice(2);
|
|
138
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
139
|
-
printUsage();
|
|
140
|
-
process.exit(0);
|
|
141
|
-
}
|
|
142
|
-
const templateFlag = args.indexOf("--template");
|
|
143
|
-
const templateArg = templateFlag !== -1 ? args[templateFlag + 1] : undefined;
|
|
144
|
-
const nameArg = args.find((a) => !a.startsWith("--") && (templateFlag === -1 || args.indexOf(a) !== templateFlag + 1));
|
|
145
120
|
// If both name and template are provided via CLI args, skip interactive mode
|
|
146
121
|
if (nameArg) {
|
|
147
122
|
const template = templateArg ?? "full";
|
|
@@ -173,10 +148,8 @@ function printUsage() {
|
|
|
173
148
|
console.log(" Run without arguments for interactive mode.");
|
|
174
149
|
console.log("");
|
|
175
150
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
}
|
|
151
|
+
main().catch((err) => {
|
|
152
|
+
console.error(err);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
182
155
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,sCAAsC;AACtC,iEAAiE;AAEjE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,sCAAsC;AACtC,iEAAiE;AAEjE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,4BAA4B;AAE5B,MAAM,SAAS,GAA2C;IACxD,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,aAAa;IACpB,UAAU,EAAE,eAAe;IAC3B,SAAS,EAAE,cAAc;CAC1B,CAAC;AAEF,MAAM,oBAAoB,GAA2B;IACnD,IAAI,EAAE,mEAAmE;IACzE,KAAK,EAAE,oBAAoB;IAC3B,UAAU,EAAE,+BAA+B;IAC3C,SAAS,EAAE,6BAA6B;CACzC,CAAC;AAEF,+BAA+B;AAE/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,gBAAgB;AAChB,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnD,UAAU,EAAE,CAAC;IACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,gBAAgB;AAChB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AAChD,MAAM,WAAW,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;AAEvH,2BAA2B;AAE3B,KAAK,UAAU,MAAM,CAAC,QAAgB,EAAE,YAAoB;IAC1D,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,KAAK,YAAY,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE;YAC1D,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,oBAAoB;IAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IAC1D,IAAI,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAChD,IAAI,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAE5D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC;IACxC,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAErD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAChC,CAAC;AAED,mBAAmB;AAEnB,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAU;IAC1D,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,mDAAmD;IACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE9B,6CAA6C;IAC7C,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACxD,OAAO,CAAC,KAAK,CAAC,iFAAiF,CAAC,CAAC;QACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAElC,OAAO,CAAC,GAAG,CAAC,iCAAiC,WAAW,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;IAC/D,MAAM,GAAG,GAAG,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;IAE3D,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,QAAQ,WAAW,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IAC5B,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,eAAe;AAEf,KAAK,UAAU,IAAI;IACjB,6EAA6E;IAC7E,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,QAAQ,GAAG,WAAW,IAAI,MAAM,CAAC;QACvC,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAC;QAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAChC,OAAO;IACT,CAAC;IAED,2CAA2C;IAC3C,yCAAyC;IACzC,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,MAAM,eAAe,EAAE,CAAC;QACvD,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,UAAU,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC5B,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC/D,MAAM,aAAa,GAAG,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,aAAa,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;
|
|
1
|
+
{"version":3,"file":"basic.d.ts","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;CAuEzB,CAAC"}
|
package/dist/templates/basic.js
CHANGED
|
@@ -9,7 +9,7 @@ export const basicTemplate = {
|
|
|
9
9
|
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian: "
|
|
12
|
+
celsian: "latest",
|
|
13
13
|
},
|
|
14
14
|
devDependencies: {
|
|
15
15
|
typescript: "^5.7.0",
|
|
@@ -22,8 +22,6 @@ export const basicTemplate = {
|
|
|
22
22
|
target: "ES2022",
|
|
23
23
|
module: "ESNext",
|
|
24
24
|
moduleResolution: "bundler",
|
|
25
|
-
lib: ["ES2022"],
|
|
26
|
-
types: ["node"],
|
|
27
25
|
strict: true,
|
|
28
26
|
esModuleInterop: true,
|
|
29
27
|
skipLibCheck: true,
|
|
@@ -61,7 +59,7 @@ app.get('/hello/:name', (req, reply) => {
|
|
|
61
59
|
return reply.json({ message: \`Hello, \${req.params.name}!\` });
|
|
62
60
|
});
|
|
63
61
|
|
|
64
|
-
serve(app
|
|
62
|
+
serve(app);
|
|
65
63
|
`,
|
|
66
64
|
};
|
|
67
65
|
//# sourceMappingURL=basic.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"basic.js","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"basic.js","sourceRoot":"","sources":["../../src/templates/basic.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;SAClB;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;YACb,aAAa,EAAE,SAAS;SACzB;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BjB;CACA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"full.d.ts","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"full.d.ts","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAm4B/C,CAAC"}
|
package/dist/templates/full.js
CHANGED
|
@@ -11,11 +11,11 @@ export const fullTemplate = {
|
|
|
11
11
|
lint: "npx tsc --noEmit",
|
|
12
12
|
},
|
|
13
13
|
dependencies: {
|
|
14
|
-
celsian: "
|
|
15
|
-
"@celsian/core": "
|
|
16
|
-
"@celsian/jwt": "
|
|
17
|
-
"@celsian/rpc": "
|
|
18
|
-
"@celsian/rate-limit": "
|
|
14
|
+
celsian: "latest",
|
|
15
|
+
"@celsian/core": "latest",
|
|
16
|
+
"@celsian/jwt": "latest",
|
|
17
|
+
"@celsian/rpc": "latest",
|
|
18
|
+
"@celsian/rate-limit": "latest",
|
|
19
19
|
"@sinclair/typebox": "^0.34.0",
|
|
20
20
|
},
|
|
21
21
|
devDependencies: {
|
|
@@ -216,7 +216,9 @@ export function securityPlugins(): PluginFunction[] {
|
|
|
216
216
|
rateLimit({
|
|
217
217
|
max: 100,
|
|
218
218
|
window: 60_000,
|
|
219
|
-
|
|
219
|
+
// Local scaffold default: use a stable single-process key without trusting proxy headers.
|
|
220
|
+
// In production, replace this with a user/session/IP key appropriate for your deployment.
|
|
221
|
+
keyGenerator: () => 'local-scaffold',
|
|
220
222
|
}),
|
|
221
223
|
];
|
|
222
224
|
}
|
|
@@ -249,7 +251,7 @@ export default function healthRoutes(): PluginFunction {
|
|
|
249
251
|
|
|
250
252
|
import { Type } from '@sinclair/typebox';
|
|
251
253
|
import type { CelsianReply, CelsianRequest, PluginFunction } from '@celsian/core';
|
|
252
|
-
import type {
|
|
254
|
+
import type { User } from '../types.js';
|
|
253
255
|
import { db } from '../plugins/database.js';
|
|
254
256
|
import { requireAuth } from '../plugins/auth.js';
|
|
255
257
|
|
|
@@ -275,7 +277,7 @@ export default function userRoutes(): PluginFunction {
|
|
|
275
277
|
app.post('/users', {
|
|
276
278
|
schema: { body: CreateUserSchema },
|
|
277
279
|
}, (req, reply) => {
|
|
278
|
-
const { name, email } = req.parsedBody as
|
|
280
|
+
const { name, email } = req.parsedBody as { name: string; email: string };
|
|
279
281
|
const user: User = {
|
|
280
282
|
id: db.generateId(),
|
|
281
283
|
name,
|
|
@@ -300,7 +302,7 @@ export default function userRoutes(): PluginFunction {
|
|
|
300
302
|
}, (req, reply) => {
|
|
301
303
|
const user = db.users.get(req.params.id);
|
|
302
304
|
if (!user) return reply.status(404).json({ error: 'User not found' });
|
|
303
|
-
const updates = req.parsedBody as
|
|
305
|
+
const updates = req.parsedBody as { name?: string; email?: string };
|
|
304
306
|
if (updates.name !== undefined) user.name = updates.name;
|
|
305
307
|
if (updates.email !== undefined) user.email = updates.email;
|
|
306
308
|
db.users.set(user.id, user);
|
|
@@ -315,7 +317,7 @@ export default function userRoutes(): PluginFunction {
|
|
|
315
317
|
handler(req: CelsianRequest, reply: CelsianReply) {
|
|
316
318
|
const existed = db.users.delete(req.params.id);
|
|
317
319
|
if (!existed) return reply.status(404).json({ error: 'User not found' });
|
|
318
|
-
return reply.status(204).
|
|
320
|
+
return reply.status(204).json({ deleted: true });
|
|
319
321
|
},
|
|
320
322
|
});
|
|
321
323
|
};
|
|
@@ -333,24 +335,21 @@ import type { PluginFunction } from '@celsian/core';
|
|
|
333
335
|
const appRouter = router({
|
|
334
336
|
greeting: {
|
|
335
337
|
hello: procedure
|
|
336
|
-
.input(Type.Object({ name: Type.String() }))
|
|
338
|
+
.input<{ name: string }>(Type.Object({ name: Type.String() }))
|
|
337
339
|
.query(({ input }) => {
|
|
338
|
-
|
|
339
|
-
return { message: \`Hello, \${data.name}!\` };
|
|
340
|
+
return { message: \`Hello, \${input.name}!\` };
|
|
340
341
|
}),
|
|
341
342
|
},
|
|
342
343
|
math: {
|
|
343
344
|
add: procedure
|
|
344
|
-
.input(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
345
|
+
.input<{ a: number; b: number }>(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
345
346
|
.query(({ input }) => {
|
|
346
|
-
|
|
347
|
-
return { result: data.a + data.b };
|
|
347
|
+
return { result: input.a + input.b };
|
|
348
348
|
}),
|
|
349
349
|
multiply: procedure
|
|
350
|
-
.input(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
350
|
+
.input<{ a: number; b: number }>(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
351
351
|
.mutation(({ input }) => {
|
|
352
|
-
|
|
353
|
-
return { result: data.a * data.b };
|
|
352
|
+
return { result: input.a * input.b };
|
|
354
353
|
}),
|
|
355
354
|
},
|
|
356
355
|
system: {
|
|
@@ -509,19 +508,18 @@ import healthRoutes from '../src/routes/health.js';
|
|
|
509
508
|
import userRoutes from '../src/routes/users.js';
|
|
510
509
|
import rpcRoutes from '../src/routes/rpc.js';
|
|
511
510
|
|
|
512
|
-
|
|
511
|
+
function createTestApp() {
|
|
513
512
|
const app = createApp();
|
|
514
513
|
// Register just what we need — skip auth/security for tests
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
await app.ready();
|
|
514
|
+
app.register(healthRoutes());
|
|
515
|
+
app.register(userRoutes());
|
|
516
|
+
app.register(rpcRoutes());
|
|
519
517
|
return app;
|
|
520
518
|
}
|
|
521
519
|
|
|
522
520
|
describe('Health', () => {
|
|
523
521
|
it('GET /health returns status ok', async () => {
|
|
524
|
-
const app =
|
|
522
|
+
const app = createTestApp();
|
|
525
523
|
const res = await app.inject({ url: '/health' });
|
|
526
524
|
expect(res.status).toBe(200);
|
|
527
525
|
const body = await res.json();
|
|
@@ -533,7 +531,7 @@ describe('Health', () => {
|
|
|
533
531
|
|
|
534
532
|
describe('Users CRUD', () => {
|
|
535
533
|
it('GET /users returns the seeded user', async () => {
|
|
536
|
-
const app =
|
|
534
|
+
const app = createTestApp();
|
|
537
535
|
const res = await app.inject({ url: '/users' });
|
|
538
536
|
expect(res.status).toBe(200);
|
|
539
537
|
const users = await res.json();
|
|
@@ -543,7 +541,7 @@ describe('Users CRUD', () => {
|
|
|
543
541
|
});
|
|
544
542
|
|
|
545
543
|
it('POST /users creates a new user', async () => {
|
|
546
|
-
const app =
|
|
544
|
+
const app = createTestApp();
|
|
547
545
|
const res = await app.inject({
|
|
548
546
|
method: 'POST',
|
|
549
547
|
url: '/users',
|
|
@@ -558,7 +556,7 @@ describe('Users CRUD', () => {
|
|
|
558
556
|
});
|
|
559
557
|
|
|
560
558
|
it('GET /users/:id returns a specific user', async () => {
|
|
561
|
-
const app =
|
|
559
|
+
const app = createTestApp();
|
|
562
560
|
|
|
563
561
|
// Create a user first
|
|
564
562
|
const createRes = await app.inject({
|
|
@@ -576,13 +574,13 @@ describe('Users CRUD', () => {
|
|
|
576
574
|
});
|
|
577
575
|
|
|
578
576
|
it('GET /users/:id returns 404 for unknown user', async () => {
|
|
579
|
-
const app =
|
|
577
|
+
const app = createTestApp();
|
|
580
578
|
const res = await app.inject({ url: '/users/99999' });
|
|
581
579
|
expect(res.status).toBe(404);
|
|
582
580
|
});
|
|
583
581
|
|
|
584
582
|
it('DELETE /users/:id without auth returns 401', async () => {
|
|
585
|
-
const app =
|
|
583
|
+
const app = createTestApp();
|
|
586
584
|
const res = await app.inject({ method: 'DELETE', url: '/users/1' });
|
|
587
585
|
// Without the JWT guard registered in test mode, the route handler runs directly.
|
|
588
586
|
// In the full app with auth, this would return 401.
|
|
@@ -593,7 +591,7 @@ describe('Users CRUD', () => {
|
|
|
593
591
|
|
|
594
592
|
describe('RPC', () => {
|
|
595
593
|
it('GET /_rpc/system.ping returns pong', async () => {
|
|
596
|
-
const app =
|
|
594
|
+
const app = createTestApp();
|
|
597
595
|
const res = await app.inject({ url: '/_rpc/system.ping' });
|
|
598
596
|
expect(res.status).toBe(200);
|
|
599
597
|
const body = await res.json();
|
|
@@ -601,7 +599,7 @@ describe('RPC', () => {
|
|
|
601
599
|
});
|
|
602
600
|
|
|
603
601
|
it('GET /_rpc/greeting.hello returns greeting', async () => {
|
|
604
|
-
const app =
|
|
602
|
+
const app = createTestApp();
|
|
605
603
|
const res = await app.inject({
|
|
606
604
|
url: '/_rpc/greeting.hello?input=' + encodeURIComponent(JSON.stringify({ name: 'World' })),
|
|
607
605
|
});
|
|
@@ -611,7 +609,7 @@ describe('RPC', () => {
|
|
|
611
609
|
});
|
|
612
610
|
|
|
613
611
|
it('POST /_rpc/math.multiply performs mutation', async () => {
|
|
614
|
-
const app =
|
|
612
|
+
const app = createTestApp();
|
|
615
613
|
const res = await app.inject({
|
|
616
614
|
method: 'POST',
|
|
617
615
|
url: '/_rpc/math.multiply',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"full.js","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;YAC3B,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,kBAAkB;SACzB;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"full.js","sourceRoot":"","sources":["../../src/templates/full.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe;;;;CA4F3B,CAAC"}
|
|
@@ -9,7 +9,7 @@ export const restApiTemplate = {
|
|
|
9
9
|
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian: "
|
|
12
|
+
celsian: "latest",
|
|
13
13
|
"@sinclair/typebox": "^0.34.0",
|
|
14
14
|
},
|
|
15
15
|
devDependencies: {
|
|
@@ -23,8 +23,6 @@ export const restApiTemplate = {
|
|
|
23
23
|
target: "ES2022",
|
|
24
24
|
module: "ESNext",
|
|
25
25
|
moduleResolution: "bundler",
|
|
26
|
-
lib: ["ES2022"],
|
|
27
|
-
types: ["node"],
|
|
28
26
|
strict: true,
|
|
29
27
|
esModuleInterop: true,
|
|
30
28
|
skipLibCheck: true,
|
|
@@ -82,7 +80,7 @@ app.get('/users/:id', (req, reply) => {
|
|
|
82
80
|
return reply.json(user);
|
|
83
81
|
});
|
|
84
82
|
|
|
85
|
-
serve(app
|
|
83
|
+
serve(app);
|
|
86
84
|
`,
|
|
87
85
|
};
|
|
88
86
|
//# sourceMappingURL=rest-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../src/templates/rest-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,mBAAmB,EAAE,SAAS;SAC/B;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;YACb,aAAa,EAAE,SAAS;SACzB;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDjB;CACA,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-api.d.ts","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;
|
|
1
|
+
{"version":3,"file":"rpc-api.d.ts","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;CAgG1B,CAAC"}
|
|
@@ -9,8 +9,8 @@ export const rpcApiTemplate = {
|
|
|
9
9
|
start: "node dist/index.js",
|
|
10
10
|
},
|
|
11
11
|
dependencies: {
|
|
12
|
-
celsian: "
|
|
13
|
-
"@celsian/rpc": "
|
|
12
|
+
celsian: "latest",
|
|
13
|
+
"@celsian/rpc": "latest",
|
|
14
14
|
"@sinclair/typebox": "^0.34.0",
|
|
15
15
|
},
|
|
16
16
|
devDependencies: {
|
|
@@ -24,8 +24,6 @@ export const rpcApiTemplate = {
|
|
|
24
24
|
target: "ES2022",
|
|
25
25
|
module: "ESNext",
|
|
26
26
|
moduleResolution: "bundler",
|
|
27
|
-
lib: ["ES2022"],
|
|
28
|
-
types: ["node"],
|
|
29
27
|
strict: true,
|
|
30
28
|
esModuleInterop: true,
|
|
31
29
|
skipLibCheck: true,
|
|
@@ -60,18 +58,16 @@ await app.register(security({
|
|
|
60
58
|
const appRouter = router({
|
|
61
59
|
greeting: {
|
|
62
60
|
hello: procedure
|
|
63
|
-
.input(Type.Object({ name: Type.String() }))
|
|
61
|
+
.input<{ name: string }>(Type.Object({ name: Type.String() }))
|
|
64
62
|
.query(({ input }) => {
|
|
65
|
-
|
|
66
|
-
return { message: \`Hello, \${data.name}!\` };
|
|
63
|
+
return { message: 'Hello, ' + input.name + '!' };
|
|
67
64
|
}),
|
|
68
65
|
},
|
|
69
66
|
math: {
|
|
70
67
|
add: procedure
|
|
71
|
-
.input(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
68
|
+
.input<{ a: number; b: number }>(Type.Object({ a: Type.Number(), b: Type.Number() }))
|
|
72
69
|
.query(({ input }) => {
|
|
73
|
-
|
|
74
|
-
return { result: data.a + data.b };
|
|
70
|
+
return { result: input.a + input.b };
|
|
75
71
|
}),
|
|
76
72
|
},
|
|
77
73
|
});
|
|
@@ -86,7 +82,7 @@ app.route({
|
|
|
86
82
|
},
|
|
87
83
|
});
|
|
88
84
|
|
|
89
|
-
serve(app
|
|
85
|
+
serve(app);
|
|
90
86
|
|
|
91
87
|
export type AppRouter = typeof appRouter;
|
|
92
88
|
`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-api.js","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"rpc-api.js","sourceRoot":"","sources":["../../src/templates/rpc-api.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,cAAc,EAAE,IAAI,CAAC,SAAS,CAC5B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,GAAG,EAAE,8BAA8B;YACnC,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,oBAAoB;SAC5B;QACD,YAAY,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,cAAc,EAAE,QAAQ;YACxB,mBAAmB,EAAE,SAAS;SAC/B;QACD,eAAe,EAAE;YACf,UAAU,EAAE,QAAQ;YACpB,GAAG,EAAE,QAAQ;YACb,aAAa,EAAE,SAAS;SACzB;KACF,EACD,IAAI,EACJ,CAAC,CACF;IACD,eAAe,EAAE,IAAI,CAAC,SAAS,CAC7B;QACE,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;YACrB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,KAAK;SACf;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,EACD,IAAI,EACJ,CAAC,CACF;IACD,cAAc,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDjB;CACA,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-celsian",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-celsian": "dist/index.js"
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=20"
|
|
16
16
|
},
|
|
17
|
-
"sideEffects": false,
|
|
18
|
-
"description": "Scaffold new CelsianJS applications",
|
|
19
17
|
"repository": {
|
|
20
18
|
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/CelsianJs/celsian.git"
|
|
19
|
+
"url": "git+https://github.com/CelsianJs/celsian.git",
|
|
20
|
+
"directory": "packages/create-celsian"
|
|
22
21
|
},
|
|
23
22
|
"homepage": "https://github.com/CelsianJs/celsian#readme",
|
|
24
23
|
"bugs": {
|
|
25
24
|
"url": "https://github.com/CelsianJs/celsian/issues"
|
|
26
25
|
},
|
|
26
|
+
"description": "Scaffold new CelsianJS applications",
|
|
27
27
|
"keywords": [
|
|
28
28
|
"celsian",
|
|
29
29
|
"create",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"cli",
|
|
32
32
|
"typescript"
|
|
33
33
|
],
|
|
34
|
+
"sideEffects": false,
|
|
34
35
|
"scripts": {
|
|
35
36
|
"build": "tsc -b"
|
|
36
37
|
}
|