@nitronjs/framework 0.2.27 → 0.3.1
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 +260 -170
- package/lib/Auth/Auth.js +2 -2
- package/lib/Build/CssBuilder.js +5 -7
- package/lib/Build/EffectivePropUsage.js +174 -0
- package/lib/Build/FactoryTransform.js +1 -21
- package/lib/Build/FileAnalyzer.js +2 -33
- package/lib/Build/Manager.js +354 -58
- package/lib/Build/PropUsageAnalyzer.js +1189 -0
- package/lib/Build/jsxRuntime.js +25 -155
- package/lib/Build/plugins.js +212 -146
- package/lib/Build/propUtils.js +70 -0
- package/lib/Console/Commands/DevCommand.js +30 -10
- package/lib/Console/Commands/MakeCommand.js +8 -1
- package/lib/Console/Output.js +0 -2
- package/lib/Console/Stubs/rsc-consumer.tsx +74 -0
- package/lib/Console/Stubs/vendor-dev.tsx +30 -41
- package/lib/Console/Stubs/vendor.tsx +25 -1
- package/lib/Core/Config.js +0 -6
- package/lib/Core/Paths.js +0 -19
- package/lib/Database/Migration/Checksum.js +0 -3
- package/lib/Database/Migration/MigrationRepository.js +0 -8
- package/lib/Database/Migration/MigrationRunner.js +1 -2
- package/lib/Database/Model.js +19 -11
- package/lib/Database/QueryBuilder.js +25 -4
- package/lib/Database/Schema/Blueprint.js +10 -0
- package/lib/Database/Schema/Manager.js +2 -0
- package/lib/Date/DateTime.js +1 -1
- package/lib/Dev/DevContext.js +44 -0
- package/lib/Dev/DevErrorPage.js +990 -0
- package/lib/Dev/DevIndicator.js +836 -0
- package/lib/HMR/Server.js +16 -37
- package/lib/Http/Server.js +171 -23
- package/lib/Logging/Log.js +34 -2
- package/lib/Mail/Mail.js +41 -10
- package/lib/Route/Router.js +43 -19
- package/lib/Runtime/Entry.js +10 -6
- package/lib/Session/Manager.js +103 -1
- package/lib/Session/Session.js +0 -4
- package/lib/Support/Str.js +6 -4
- package/lib/Translation/Lang.js +376 -32
- package/lib/Translation/pluralize.js +81 -0
- package/lib/Validation/MagicBytes.js +120 -0
- package/lib/Validation/Validator.js +46 -29
- package/lib/View/Client/hmr-client.js +100 -90
- package/lib/View/Client/spa.js +121 -50
- package/lib/View/ClientManifest.js +60 -0
- package/lib/View/FlightRenderer.js +100 -0
- package/lib/View/Layout.js +0 -3
- package/lib/View/PropFilter.js +81 -0
- package/lib/View/View.js +230 -495
- package/lib/index.d.ts +22 -1
- package/package.json +2 -2
- package/skeleton/config/app.js +1 -0
- package/skeleton/config/server.js +13 -0
- package/skeleton/config/session.js +3 -0
- package/lib/Build/HydrationBuilder.js +0 -190
- package/lib/Console/Stubs/page-hydration-dev.tsx +0 -72
- package/lib/Console/Stubs/page-hydration.tsx +0 -53
package/README.md
CHANGED
|
@@ -1,39 +1,49 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">NitronJS</h1>
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
A full-stack MVC framework built on Fastify and React Server Components.<br/>
|
|
7
|
+
Build production-ready Node.js applications with a built-in ORM, authentication, mail, validation, and instant HMR.
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://www.npmjs.com/package/@nitronjs/framework"><img src="https://img.shields.io/npm/v/@nitronjs/framework.svg" alt="npm version"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@nitronjs/framework"><img src="https://img.shields.io/npm/l/@nitronjs/framework.svg" alt="license"></a>
|
|
13
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@nitronjs/framework.svg" alt="node version"></a>
|
|
14
|
+
</p>
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- **Instant HMR** - Changes reflect immediately without losing state
|
|
11
|
-
- **Full-Stack** - Database ORM, authentication, sessions, validation all built-in
|
|
16
|
+
<p align="center">
|
|
17
|
+
<b>NitronJS is currently under active development. APIs may change before the stable release.</b>
|
|
18
|
+
</p>
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
14
23
|
|
|
15
24
|
```bash
|
|
16
25
|
npx -y @nitronjs/framework my-app
|
|
17
26
|
cd my-app
|
|
27
|
+
npm run storage:link
|
|
18
28
|
npm run dev
|
|
19
29
|
```
|
|
20
30
|
|
|
21
|
-
> **Note:** The `-y` flag skips npm's confirmation prompt for a smoother experience.
|
|
22
|
-
|
|
23
31
|
Your app will be running at `http://localhost:3000`
|
|
24
32
|
|
|
33
|
+
---
|
|
34
|
+
|
|
25
35
|
## Core Concepts
|
|
26
36
|
|
|
27
37
|
### Server Components (Default)
|
|
28
38
|
|
|
29
|
-
Every `.tsx` file in `resources/views/` is a Server Component by default. They run on the server
|
|
39
|
+
Every `.tsx` file in `resources/views/` is a Server Component by default. They run on the server and have full access to your database and file system.
|
|
30
40
|
|
|
31
41
|
```tsx
|
|
32
42
|
// resources/views/Site/Home.tsx
|
|
33
|
-
import User from '
|
|
43
|
+
import User from '@models/User';
|
|
34
44
|
|
|
35
45
|
export default async function Home() {
|
|
36
|
-
const users = await User.get();
|
|
46
|
+
const users = await User.get();
|
|
37
47
|
|
|
38
48
|
return (
|
|
39
49
|
<div>
|
|
@@ -53,7 +63,6 @@ export default async function Home() {
|
|
|
53
63
|
Add `"use client"` at the top of a file to make it interactive. These components hydrate on the browser and can use React hooks.
|
|
54
64
|
|
|
55
65
|
```tsx
|
|
56
|
-
// resources/views/Components/Counter.tsx
|
|
57
66
|
"use client";
|
|
58
67
|
import { useState } from 'react';
|
|
59
68
|
|
|
@@ -68,25 +77,9 @@ export default function Counter() {
|
|
|
68
77
|
}
|
|
69
78
|
```
|
|
70
79
|
|
|
71
|
-
Use client components inside server components:
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
// resources/views/Site/Home.tsx (Server Component)
|
|
75
|
-
import Counter from '../Components/Counter';
|
|
76
|
-
|
|
77
|
-
export default function Home() {
|
|
78
|
-
return (
|
|
79
|
-
<div>
|
|
80
|
-
<h1>Welcome</h1>
|
|
81
|
-
<Counter /> {/* Hydrates as an island */}
|
|
82
|
-
</div>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
80
|
### Layouts
|
|
88
81
|
|
|
89
|
-
Create `Layout.tsx` files to wrap pages. Layouts are discovered automatically by walking up the directory tree.
|
|
82
|
+
Create `Layout.tsx` files to wrap pages. Layouts are discovered automatically by walking up the directory tree. During SPA navigation, layouts persist and only page content is updated — no full page reloads.
|
|
90
83
|
|
|
91
84
|
```
|
|
92
85
|
resources/views/
|
|
@@ -98,53 +91,14 @@ resources/views/
|
|
|
98
91
|
└── Dashboard.tsx # Uses both layouts
|
|
99
92
|
```
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
// resources/views/Layout.tsx
|
|
103
|
-
export default function RootLayout({ children }) {
|
|
104
|
-
return (
|
|
105
|
-
<html lang="en">
|
|
106
|
-
<head>
|
|
107
|
-
<meta charSet="UTF-8" />
|
|
108
|
-
<title>My App</title>
|
|
109
|
-
</head>
|
|
110
|
-
<body>
|
|
111
|
-
{children}
|
|
112
|
-
</body>
|
|
113
|
-
</html>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
// resources/views/Admin/Layout.tsx
|
|
120
|
-
export default function AdminLayout({ children }) {
|
|
121
|
-
return (
|
|
122
|
-
<div className="admin-wrapper">
|
|
123
|
-
<nav>Admin Navigation</nav>
|
|
124
|
-
<main>{children}</main>
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Disable layout for a specific page:
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
export const layout = false;
|
|
134
|
-
|
|
135
|
-
export default function FullscreenPage() {
|
|
136
|
-
return <div>No layout wrapper</div>;
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Routing
|
|
94
|
+
### Routing
|
|
141
95
|
|
|
142
96
|
Define routes in `routes/web.js`:
|
|
143
97
|
|
|
144
98
|
```javascript
|
|
145
99
|
import { Route } from '@nitronjs/framework';
|
|
146
|
-
import HomeController from '
|
|
147
|
-
import UserController from '
|
|
100
|
+
import HomeController from '../app/Controllers/HomeController.js';
|
|
101
|
+
import UserController from '../app/Controllers/UserController.js';
|
|
148
102
|
|
|
149
103
|
// Basic routes
|
|
150
104
|
Route.get('/', HomeController.index).name('home');
|
|
@@ -153,33 +107,60 @@ Route.get('/about', HomeController.about).name('about');
|
|
|
153
107
|
// Route parameters
|
|
154
108
|
Route.get('/users/:id', UserController.show).name('user.show');
|
|
155
109
|
|
|
156
|
-
//
|
|
157
|
-
Route.
|
|
158
|
-
|
|
159
|
-
|
|
110
|
+
// RESTful routes
|
|
111
|
+
Route.post('/users', UserController.store).name('user.store');
|
|
112
|
+
Route.put('/users/:id', UserController.update).name('user.update');
|
|
113
|
+
Route.delete('/users/:id', UserController.destroy).name('user.destroy');
|
|
114
|
+
|
|
115
|
+
// Route groups with prefix, name prefix, and middleware
|
|
116
|
+
Route.prefix('/admin').name('admin.').middleware('auth').group(() => {
|
|
117
|
+
Route.get('/', DashboardController.index).name('dashboard');
|
|
118
|
+
Route.get('/users', AdminUserController.index).name('users');
|
|
119
|
+
|
|
120
|
+
// Nested groups
|
|
121
|
+
Route.prefix('/pages').name('pages.').group(() => {
|
|
122
|
+
Route.get('/:id/edit', PagesController.edit).name('edit');
|
|
123
|
+
});
|
|
160
124
|
});
|
|
161
125
|
|
|
162
|
-
//
|
|
163
|
-
Route.
|
|
164
|
-
Route.
|
|
165
|
-
Route.
|
|
126
|
+
// Middleware-only groups
|
|
127
|
+
Route.middleware('guest').group(() => {
|
|
128
|
+
Route.get('/login', AuthController.getLogin).name('login');
|
|
129
|
+
Route.post('/login', AuthController.postLogin);
|
|
130
|
+
});
|
|
166
131
|
```
|
|
167
132
|
|
|
168
|
-
|
|
133
|
+
#### URL Generation
|
|
169
134
|
|
|
170
|
-
|
|
171
|
-
|
|
135
|
+
The global `route()` function is available everywhere — controllers, views, and client-side code:
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// Basic
|
|
139
|
+
route('home') // => "/"
|
|
140
|
+
|
|
141
|
+
// With parameters
|
|
142
|
+
route('user.show', { id: 1 }) // => "/users/1"
|
|
143
|
+
|
|
144
|
+
// With query string
|
|
145
|
+
route('admin.users', {}, { page: 2, q: 'search' }) // => "/admin/users?page=2&q=search"
|
|
146
|
+
|
|
147
|
+
// Parameters + query string
|
|
148
|
+
route('admin.pages.edit', { id: 5 }, { tab: 'seo' }) // => "/admin/pages/5/edit?tab=seo"
|
|
172
149
|
```
|
|
173
150
|
|
|
174
|
-
|
|
151
|
+
### Controllers
|
|
175
152
|
|
|
176
153
|
```javascript
|
|
177
154
|
// app/Controllers/UserController.js
|
|
155
|
+
import User from '../Models/User.js';
|
|
156
|
+
|
|
178
157
|
class UserController {
|
|
179
158
|
static async index(req, res) {
|
|
180
159
|
const users = await User.get();
|
|
181
160
|
|
|
182
|
-
return res.view('User/Index', {
|
|
161
|
+
return res.view('User/Index', {
|
|
162
|
+
users
|
|
163
|
+
});
|
|
183
164
|
}
|
|
184
165
|
|
|
185
166
|
static async show(req, res) {
|
|
@@ -189,7 +170,9 @@ class UserController {
|
|
|
189
170
|
return res.status(404).view('Errors/NotFound');
|
|
190
171
|
}
|
|
191
172
|
|
|
192
|
-
return res.view('User/Show', {
|
|
173
|
+
return res.view('User/Show', {
|
|
174
|
+
user
|
|
175
|
+
});
|
|
193
176
|
}
|
|
194
177
|
|
|
195
178
|
static async store(req, res) {
|
|
@@ -207,12 +190,28 @@ class UserController {
|
|
|
207
190
|
export default UserController;
|
|
208
191
|
```
|
|
209
192
|
|
|
210
|
-
|
|
193
|
+
### Path Aliases
|
|
194
|
+
|
|
195
|
+
NitronJS provides built-in path aliases for clean imports in `.tsx` view files:
|
|
196
|
+
|
|
197
|
+
| Alias | Path |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `@models/*` | `app/Models/*` |
|
|
200
|
+
| `@controllers/*` | `app/Controllers/*` |
|
|
201
|
+
| `@middlewares/*` | `app/Middlewares/*` |
|
|
202
|
+
| `@views/*` | `resources/views/*` |
|
|
203
|
+
| `@css/*` | `resources/css/*` |
|
|
204
|
+
| `@/*` | Project root |
|
|
205
|
+
|
|
206
|
+
---
|
|
211
207
|
|
|
212
|
-
|
|
208
|
+
## Features
|
|
209
|
+
|
|
210
|
+
### Database
|
|
211
|
+
|
|
212
|
+
#### Models
|
|
213
213
|
|
|
214
214
|
```javascript
|
|
215
|
-
// app/Models/User.js
|
|
216
215
|
import { Model } from '@nitronjs/framework';
|
|
217
216
|
|
|
218
217
|
export default class User extends Model {
|
|
@@ -220,30 +219,25 @@ export default class User extends Model {
|
|
|
220
219
|
}
|
|
221
220
|
```
|
|
222
221
|
|
|
223
|
-
|
|
222
|
+
#### Queries
|
|
224
223
|
|
|
225
224
|
```javascript
|
|
226
|
-
// Find all
|
|
227
225
|
const users = await User.get();
|
|
228
|
-
|
|
229
|
-
// Find by ID
|
|
230
226
|
const user = await User.find(1);
|
|
231
|
-
|
|
232
|
-
// Where clauses
|
|
233
227
|
const admins = await User.where('role', '=', 'admin').get();
|
|
234
|
-
const active = await User.where('active', true).get();
|
|
235
228
|
|
|
236
|
-
// Chaining
|
|
237
229
|
const results = await User
|
|
238
|
-
.where('role', '=', 'admin')
|
|
239
230
|
.where('active', true)
|
|
240
231
|
.orderBy('created_at', 'desc')
|
|
241
232
|
.limit(10)
|
|
242
233
|
.get();
|
|
243
234
|
|
|
244
|
-
// First result
|
|
245
235
|
const user = await User.where('email', '=', 'john@example.com').first();
|
|
246
236
|
|
|
237
|
+
// Aggregates
|
|
238
|
+
const total = await User.count();
|
|
239
|
+
const maxAge = await User.max('age');
|
|
240
|
+
|
|
247
241
|
// Create
|
|
248
242
|
const user = new User();
|
|
249
243
|
user.name = 'John';
|
|
@@ -257,59 +251,58 @@ await User.where('id', '=', 1).update({ name: 'Jane' });
|
|
|
257
251
|
await User.where('id', '=', 1).delete();
|
|
258
252
|
```
|
|
259
253
|
|
|
260
|
-
|
|
254
|
+
#### Migrations
|
|
261
255
|
|
|
262
256
|
```bash
|
|
263
|
-
npm run make:migration
|
|
257
|
+
npm run make:migration create_posts_table
|
|
264
258
|
```
|
|
265
259
|
|
|
266
260
|
```javascript
|
|
267
|
-
// database/migrations/2024_01_01_000000_create_users_table.js
|
|
268
261
|
import { Schema } from '@nitronjs/framework';
|
|
269
262
|
|
|
270
|
-
class
|
|
263
|
+
class CreatePostsTable {
|
|
271
264
|
static async up() {
|
|
272
|
-
await Schema.create('
|
|
265
|
+
await Schema.create('posts', (table) => {
|
|
273
266
|
table.id();
|
|
274
|
-
table.string('
|
|
275
|
-
table.
|
|
276
|
-
table.string('
|
|
267
|
+
table.string('title');
|
|
268
|
+
table.text('body');
|
|
269
|
+
table.string('slug').unique();
|
|
270
|
+
table.boolean('published').default(false);
|
|
271
|
+
table.json('metadata').nullable();
|
|
277
272
|
table.timestamp('created_at');
|
|
278
273
|
table.timestamp('updated_at').nullable();
|
|
279
274
|
});
|
|
280
275
|
}
|
|
281
276
|
|
|
282
277
|
static async down() {
|
|
283
|
-
await Schema.dropIfExists('
|
|
278
|
+
await Schema.dropIfExists('posts');
|
|
284
279
|
}
|
|
285
280
|
}
|
|
286
281
|
|
|
287
|
-
export default
|
|
282
|
+
export default CreatePostsTable;
|
|
288
283
|
```
|
|
289
284
|
|
|
290
|
-
Run migrations:
|
|
291
|
-
|
|
292
285
|
```bash
|
|
293
286
|
npm run migrate # Run pending migrations
|
|
294
287
|
npm run migrate:fresh # Drop all tables and re-run
|
|
295
|
-
npm run migrate:fresh
|
|
288
|
+
npm run migrate:fresh:seed # Drop, migrate, and seed
|
|
289
|
+
npm run migrate:rollback # Rollback last batch
|
|
290
|
+
npm run migrate:status # Show migration status
|
|
296
291
|
```
|
|
297
292
|
|
|
298
|
-
|
|
293
|
+
### Authentication
|
|
299
294
|
|
|
300
295
|
```javascript
|
|
301
|
-
// In a controller or middleware
|
|
302
296
|
class AuthController {
|
|
303
297
|
static async login(req, res) {
|
|
304
298
|
const { email, password } = req.body;
|
|
305
|
-
|
|
306
299
|
const success = await req.auth.attempt({ email, password });
|
|
307
300
|
|
|
308
301
|
if (success) {
|
|
309
|
-
return
|
|
302
|
+
return res.redirect(route('dashboard'));
|
|
310
303
|
}
|
|
311
304
|
|
|
312
|
-
return
|
|
305
|
+
return res.view('Auth/Login', {
|
|
313
306
|
error: 'Invalid credentials'
|
|
314
307
|
});
|
|
315
308
|
}
|
|
@@ -317,7 +310,7 @@ class AuthController {
|
|
|
317
310
|
static async logout(req, res) {
|
|
318
311
|
await req.auth.logout();
|
|
319
312
|
|
|
320
|
-
return res.redirect('
|
|
313
|
+
return res.redirect(route('home'));
|
|
321
314
|
}
|
|
322
315
|
}
|
|
323
316
|
|
|
@@ -325,32 +318,26 @@ class AuthController {
|
|
|
325
318
|
if (req.auth.check()) {
|
|
326
319
|
const user = await req.auth.user();
|
|
327
320
|
}
|
|
321
|
+
|
|
322
|
+
// Named guards
|
|
323
|
+
req.auth.guard('admin').check();
|
|
328
324
|
```
|
|
329
325
|
|
|
330
|
-
|
|
326
|
+
### Sessions
|
|
331
327
|
|
|
332
328
|
```javascript
|
|
333
|
-
// Set value
|
|
334
329
|
req.session.set('key', 'value');
|
|
335
|
-
|
|
336
|
-
// Get value
|
|
337
330
|
const value = req.session.get('key');
|
|
338
331
|
|
|
339
|
-
// Flash messages
|
|
332
|
+
// Flash messages
|
|
340
333
|
req.session.flash('success', 'Profile updated!');
|
|
341
334
|
const message = req.session.getFlash('success');
|
|
342
335
|
|
|
343
|
-
//
|
|
344
|
-
const data = req.session.all();
|
|
345
|
-
|
|
346
|
-
// Regenerate session ID (after login)
|
|
336
|
+
// Regenerate session ID
|
|
347
337
|
req.session.regenerate();
|
|
348
|
-
|
|
349
|
-
// CSRF token
|
|
350
|
-
const token = req.session.getCsrfToken();
|
|
351
338
|
```
|
|
352
339
|
|
|
353
|
-
|
|
340
|
+
### Validation
|
|
354
341
|
|
|
355
342
|
```javascript
|
|
356
343
|
import { Validator } from '@nitronjs/framework';
|
|
@@ -358,8 +345,9 @@ import { Validator } from '@nitronjs/framework';
|
|
|
358
345
|
const validation = Validator.make(req.body, {
|
|
359
346
|
name: 'required|string|min:2|max:100',
|
|
360
347
|
email: 'required|email',
|
|
361
|
-
password: 'required|string|min:8',
|
|
362
|
-
age: 'numeric|min:18'
|
|
348
|
+
password: 'required|string|min:8|confirmed',
|
|
349
|
+
age: 'numeric|min:18',
|
|
350
|
+
avatar: 'file|mimes:png,jpg|max:2097152'
|
|
363
351
|
});
|
|
364
352
|
|
|
365
353
|
if (validation.fails()) {
|
|
@@ -369,9 +357,7 @@ if (validation.fails()) {
|
|
|
369
357
|
}
|
|
370
358
|
```
|
|
371
359
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
Create custom middleware:
|
|
360
|
+
### Middleware
|
|
375
361
|
|
|
376
362
|
```javascript
|
|
377
363
|
// app/Middlewares/CheckAge.js
|
|
@@ -380,7 +366,6 @@ class CheckAge {
|
|
|
380
366
|
if (req.query.age < 18) {
|
|
381
367
|
return res.status(403).send('Access denied');
|
|
382
368
|
}
|
|
383
|
-
// No return = continue to next handler
|
|
384
369
|
}
|
|
385
370
|
}
|
|
386
371
|
|
|
@@ -393,34 +378,114 @@ Register in `app/Kernel.js`:
|
|
|
393
378
|
export default {
|
|
394
379
|
routeMiddlewares: {
|
|
395
380
|
'check-age': CheckAge,
|
|
396
|
-
'auth': AuthMiddleware
|
|
381
|
+
'auth': AuthMiddleware
|
|
397
382
|
}
|
|
398
383
|
};
|
|
399
384
|
```
|
|
400
385
|
|
|
401
|
-
|
|
386
|
+
### Mail
|
|
402
387
|
|
|
403
388
|
```javascript
|
|
404
|
-
|
|
405
|
-
|
|
389
|
+
import { Mail } from '@nitronjs/framework';
|
|
390
|
+
|
|
391
|
+
await Mail.from('noreply@app.com')
|
|
392
|
+
.to('user@email.com')
|
|
393
|
+
.subject('Welcome!')
|
|
394
|
+
.html('<h1>Hello</h1>')
|
|
395
|
+
.attachment({ filename: 'file.pdf', path: '/path/to/file.pdf' })
|
|
396
|
+
.send();
|
|
397
|
+
|
|
398
|
+
// Using a template
|
|
399
|
+
await Mail.to('user@email.com')
|
|
400
|
+
.view('emails/welcome', { name: 'Alice' })
|
|
401
|
+
.send();
|
|
406
402
|
```
|
|
407
403
|
|
|
408
|
-
|
|
404
|
+
### Storage
|
|
409
405
|
|
|
410
|
-
|
|
406
|
+
```javascript
|
|
407
|
+
import { Storage } from '@nitronjs/framework';
|
|
408
|
+
|
|
409
|
+
await Storage.put(file, 'upload_files', 'image.jpg');
|
|
410
|
+
const buffer = await Storage.get('upload_files/image.jpg');
|
|
411
|
+
await Storage.delete('upload_files/old.jpg');
|
|
412
|
+
await Storage.move('upload_files/a.jpg', 'upload_files/b.jpg');
|
|
413
|
+
Storage.exists('upload_files/image.jpg');
|
|
414
|
+
Storage.url('upload_files/image.jpg'); // => "/storage/upload_files/image.jpg"
|
|
415
|
+
```
|
|
411
416
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
### Encryption
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
import { AES } from '@nitronjs/framework';
|
|
421
|
+
|
|
422
|
+
const token = AES.encrypt({ userId: 1, expires: '2025-12-31' });
|
|
423
|
+
const data = AES.decrypt(token); // Returns false on tamper
|
|
415
424
|
```
|
|
416
425
|
|
|
417
|
-
|
|
426
|
+
### Hashing
|
|
418
427
|
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
|
|
428
|
+
```javascript
|
|
429
|
+
import { Hash } from '@nitronjs/framework';
|
|
430
|
+
|
|
431
|
+
const hashed = await Hash.make('password123');
|
|
432
|
+
const valid = await Hash.check('password123', hashed);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Logging
|
|
436
|
+
|
|
437
|
+
```javascript
|
|
438
|
+
import { Log } from '@nitronjs/framework';
|
|
439
|
+
|
|
440
|
+
Log.info('User registered', { userId: 1 });
|
|
441
|
+
Log.error('Payment failed', { orderId: 123 });
|
|
442
|
+
Log.debug('Query executed', { sql: '...' });
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### DateTime
|
|
446
|
+
|
|
447
|
+
```javascript
|
|
448
|
+
import { DateTime } from '@nitronjs/framework';
|
|
449
|
+
|
|
450
|
+
DateTime.toSQL(); // "2025-01-15 10:30:00"
|
|
451
|
+
DateTime.getDate(timestamp, 'Y-m-d H:i:s');
|
|
452
|
+
DateTime.addDays(7);
|
|
453
|
+
DateTime.subHours(2);
|
|
422
454
|
```
|
|
423
455
|
|
|
456
|
+
### Faker
|
|
457
|
+
|
|
458
|
+
Built-in fake data generator for seeders and testing:
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
import { Faker } from '@nitronjs/framework';
|
|
462
|
+
|
|
463
|
+
Faker.fullName(); // "John Smith"
|
|
464
|
+
Faker.email(); // "john@example.com"
|
|
465
|
+
Faker.sentence(); // "Lorem ipsum dolor sit amet."
|
|
466
|
+
Faker.int(1, 100); // 42
|
|
467
|
+
Faker.boolean(); // true
|
|
468
|
+
Faker.uuid(); // "550e8400-e29b-41d4-a716-446655440000"
|
|
469
|
+
Faker.creditCard(); // Luhn-valid card number
|
|
470
|
+
Faker.hexColor(); // "#a3f29c"
|
|
471
|
+
Faker.oneOf(['a', 'b', 'c']);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### String Utilities
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
import { Str } from '@nitronjs/framework';
|
|
478
|
+
|
|
479
|
+
Str.slug('Hello World'); // "hello-world"
|
|
480
|
+
Str.camel('user_name'); // "userName"
|
|
481
|
+
Str.pascal('user_name'); // "UserName"
|
|
482
|
+
Str.snake('userName'); // "user_name"
|
|
483
|
+
Str.random(32); // Random string
|
|
484
|
+
Str.limit('Long text...', 10); // "Long text..."
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
424
489
|
## CLI Commands
|
|
425
490
|
|
|
426
491
|
```bash
|
|
@@ -432,6 +497,9 @@ npm run start # Start production server
|
|
|
432
497
|
# Database
|
|
433
498
|
npm run migrate # Run migrations
|
|
434
499
|
npm run migrate:fresh # Fresh migration
|
|
500
|
+
npm run migrate:fresh:seed # Fresh migration + seed
|
|
501
|
+
npm run migrate:rollback # Rollback last batch
|
|
502
|
+
npm run migrate:status # Show migration status
|
|
435
503
|
npm run seed # Run seeders
|
|
436
504
|
|
|
437
505
|
# Code Generation
|
|
@@ -445,31 +513,55 @@ npm run make:seeder <name>
|
|
|
445
513
|
npm run storage:link # Create storage symlink
|
|
446
514
|
```
|
|
447
515
|
|
|
516
|
+
---
|
|
517
|
+
|
|
448
518
|
## Project Structure
|
|
449
519
|
|
|
450
520
|
```
|
|
451
521
|
my-app/
|
|
452
522
|
├── app/
|
|
453
|
-
│ ├── Controllers/
|
|
454
|
-
│ ├── Middlewares/
|
|
455
|
-
│ └── Models/
|
|
456
|
-
├── config/
|
|
523
|
+
│ ├── Controllers/ # Request handlers
|
|
524
|
+
│ ├── Middlewares/ # Custom middleware
|
|
525
|
+
│ └── Models/ # Database models
|
|
526
|
+
├── config/ # Configuration files
|
|
457
527
|
│ ├── app.js
|
|
528
|
+
│ ├── auth.js
|
|
458
529
|
│ ├── database.js
|
|
459
|
-
│
|
|
530
|
+
│ ├── hash.js
|
|
531
|
+
│ ├── server.js
|
|
532
|
+
│ └── session.js
|
|
460
533
|
├── database/
|
|
461
|
-
│ ├── migrations/
|
|
462
|
-
│ └── seeders/
|
|
463
|
-
├── public/
|
|
534
|
+
│ ├── migrations/ # Database migrations
|
|
535
|
+
│ └── seeders/ # Database seeders
|
|
536
|
+
├── public/ # Static assets
|
|
464
537
|
├── resources/
|
|
465
|
-
│ ├── css/
|
|
466
|
-
│ └── views/
|
|
538
|
+
│ ├── css/ # Stylesheets
|
|
539
|
+
│ └── views/ # React components (TSX)
|
|
467
540
|
├── routes/
|
|
468
|
-
│ └── web.js
|
|
469
|
-
├── storage/
|
|
470
|
-
└── .env
|
|
541
|
+
│ └── web.js # Route definitions
|
|
542
|
+
├── storage/ # File storage
|
|
543
|
+
└── .env # Environment variables
|
|
471
544
|
```
|
|
472
545
|
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## CSS & Tailwind
|
|
549
|
+
|
|
550
|
+
Put your CSS files in `resources/css/`. Tailwind CSS v4 is automatically detected and processed.
|
|
551
|
+
|
|
552
|
+
```css
|
|
553
|
+
/* resources/css/global.css */
|
|
554
|
+
@import "tailwindcss";
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Import in your `.tsx` files using the `@css` alias:
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
import "@css/global.css";
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
473
565
|
## Configuration
|
|
474
566
|
|
|
475
567
|
Access configuration values:
|
|
@@ -478,7 +570,7 @@ Access configuration values:
|
|
|
478
570
|
import { Config } from '@nitronjs/framework';
|
|
479
571
|
|
|
480
572
|
const appName = Config.get('app.name');
|
|
481
|
-
const dbHost = Config.get('database.host', 'localhost');
|
|
573
|
+
const dbHost = Config.get('database.host', 'localhost');
|
|
482
574
|
```
|
|
483
575
|
|
|
484
576
|
Environment variables in `.env`:
|
|
@@ -495,15 +587,13 @@ DATABASE_USERNAME=root
|
|
|
495
587
|
DATABASE_PASSWORD=
|
|
496
588
|
```
|
|
497
589
|
|
|
590
|
+
---
|
|
591
|
+
|
|
498
592
|
## Requirements
|
|
499
593
|
|
|
500
594
|
- Node.js 18+
|
|
501
595
|
- MySQL 5.7+ or MariaDB 10.3+
|
|
502
596
|
|
|
503
|
-
## Documentation
|
|
504
|
-
|
|
505
|
-
Full documentation available at [nitronjs.dev](https://nitronjs.dev/docs)
|
|
506
|
-
|
|
507
597
|
## License
|
|
508
598
|
|
|
509
599
|
ISC
|