@nexa-stack/framework 1.0.0
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/.env.example +46 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/bin/nexa.mjs +41 -0
- package/bin/nexa.ts +334 -0
- package/docs/AI.md +69 -0
- package/docs/ARCHITECTURE.md +74 -0
- package/docs/EXAMPLES.md +114 -0
- package/docs/FRAMEWORK.md +226 -0
- package/docs/LANGUAGE.md +39 -0
- package/docs/README.md +7 -0
- package/docs/READY.md +51 -0
- package/docs/REFERENCE.md +255 -0
- package/docs/START.md +98 -0
- package/docs/advanced.md +97 -0
- package/docs/authentication.md +54 -0
- package/docs/cli.md +15 -0
- package/docs/compare.md +51 -0
- package/docs/configuration.md +65 -0
- package/docs/database.md +396 -0
- package/docs/installation.md +57 -0
- package/docs/localization.md +47 -0
- package/docs/resources.md +75 -0
- package/docs/routing.md +59 -0
- package/docs/seeding.md +33 -0
- package/docs/services.md +146 -0
- package/package.json +77 -0
- package/packages/auth/src/auth.test.ts +23 -0
- package/packages/auth/src/auth.ts +287 -0
- package/packages/auth/src/index.ts +17 -0
- package/packages/cache/src/index.ts +203 -0
- package/packages/client/src/index.ts +49 -0
- package/packages/core/src/app.ts +97 -0
- package/packages/core/src/config.ts +55 -0
- package/packages/core/src/dev.ts +104 -0
- package/packages/core/src/fields.test.ts +81 -0
- package/packages/core/src/fields.ts +309 -0
- package/packages/core/src/index.ts +60 -0
- package/packages/core/src/lang.ts +79 -0
- package/packages/core/src/loader.ts +42 -0
- package/packages/core/src/migrate.ts +91 -0
- package/packages/core/src/policy.ts +36 -0
- package/packages/core/src/registry.ts +17 -0
- package/packages/core/src/reload.ts +92 -0
- package/packages/core/src/resource.test.ts +32 -0
- package/packages/core/src/resource.ts +87 -0
- package/packages/core/src/routes.ts +22 -0
- package/packages/core/src/runtime.ts +11 -0
- package/packages/database/src/builder.ts +266 -0
- package/packages/database/src/database.ts +252 -0
- package/packages/database/src/dialect.ts +186 -0
- package/packages/database/src/index.ts +6 -0
- package/packages/database/src/mysql.ts +114 -0
- package/packages/database/src/postgres.ts +117 -0
- package/packages/database/src/query.ts +115 -0
- package/packages/database/src/sqlite.ts +216 -0
- package/packages/database/src/types.ts +104 -0
- package/packages/events/src/index.ts +17 -0
- package/packages/export/src/index.ts +36 -0
- package/packages/log/src/index.ts +38 -0
- package/packages/mail/src/index.ts +130 -0
- package/packages/notifications/src/index.ts +84 -0
- package/packages/plugins/src/index.ts +39 -0
- package/packages/queue/src/index.ts +185 -0
- package/packages/queue/src/jobs.ts +9 -0
- package/packages/schedule/src/index.ts +64 -0
- package/packages/server/src/index.ts +1 -0
- package/packages/server/src/middleware.ts +143 -0
- package/packages/server/src/query.ts +40 -0
- package/packages/server/src/router.ts +813 -0
- package/packages/sms/src/index.ts +33 -0
- package/packages/storage/src/upload.ts +36 -0
- package/packages/testing/src/index.ts +67 -0
- package/packages/validation/src/index.ts +1 -0
- package/packages/validation/src/validate.test.ts +35 -0
- package/packages/validation/src/validate.ts +112 -0
- package/public/admin.html +369 -0
- package/public/compare.html +66 -0
- package/public/dev-bar.js +213 -0
- package/public/docs.html +315 -0
- package/public/index.html +66 -0
package/docs/START.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Nexa — Start here
|
|
2
|
+
|
|
3
|
+
Ship a full backend in hours: API, ORM, auth, queue, and admin.
|
|
4
|
+
|
|
5
|
+
**Runtime:** Node.js ≥ 20
|
|
6
|
+
**Default language:** English (`APP_LOCALE=en`)
|
|
7
|
+
|
|
8
|
+
## Four words
|
|
9
|
+
|
|
10
|
+
| Word | Meaning |
|
|
11
|
+
|------|---------|
|
|
12
|
+
| `resource` | Table + API + CRUD |
|
|
13
|
+
| Fields (`string`…) | Column types |
|
|
14
|
+
| `.admin()` | Admin UI + auth + soft delete |
|
|
15
|
+
| `nexa serve` | Run |
|
|
16
|
+
|
|
17
|
+
## Full app (one file)
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { resource, string, text, boolean, select, money, belongsTo, belongsToMany } from "@nexa-stack/framework";
|
|
21
|
+
|
|
22
|
+
resource("clients", {
|
|
23
|
+
name: string().required().label("Name"),
|
|
24
|
+
phone: string().label("Phone"),
|
|
25
|
+
}, { label: "Clients" }).admin();
|
|
26
|
+
|
|
27
|
+
resource("orders", {
|
|
28
|
+
client: belongsTo("clients").required().label("Client"),
|
|
29
|
+
total: money().required().label("Total"),
|
|
30
|
+
status: select(["new", "paid", "cancelled"]).label("Status"),
|
|
31
|
+
note: text().label("Note"),
|
|
32
|
+
}, { label: "Orders" }).admin();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
nexa serve
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
→ http://localhost:3333/admin
|
|
40
|
+
→ `admin@nexa.dev` / `123456`
|
|
41
|
+
|
|
42
|
+
No controllers · no hand-written routes · missing columns are added automatically when you change a resource.
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
nexa new my-app
|
|
48
|
+
nexa make:resource products # loads from resources/
|
|
49
|
+
nexa serve
|
|
50
|
+
nexa migrate:rollback # roll back last file migration
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Field types
|
|
54
|
+
|
|
55
|
+
`string` `text` `number` `boolean` `email` `money` `date` `datetime` `select([...])`
|
|
56
|
+
`belongsTo` `hasMany` `belongsToMany` `file` `image`
|
|
57
|
+
|
|
58
|
+
## Extra power (optional)
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { db } from "@nexa-stack/framework";
|
|
62
|
+
|
|
63
|
+
const rows = await db.from("orders")
|
|
64
|
+
.where("status", "paid")
|
|
65
|
+
.with("client")
|
|
66
|
+
.orderBy("id", "desc")
|
|
67
|
+
.get();
|
|
68
|
+
|
|
69
|
+
await db.transaction(async (tx) => {
|
|
70
|
+
await tx.from("orders").insert({ ... });
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Thin frontend bridge: `createClient({ baseUrl, token })`.
|
|
75
|
+
|
|
76
|
+
## Scale (when you grow)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
nexa queue:work
|
|
80
|
+
# LIST_CACHE_TTL=15
|
|
81
|
+
# RATE_LIMIT_DRIVER=redis
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`/health` and `/ready` for load balancers.
|
|
85
|
+
|
|
86
|
+
## Arabic UI (optional)
|
|
87
|
+
|
|
88
|
+
```env
|
|
89
|
+
APP_LOCALE=en
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Keep `lang/ar.json` + `lang/en.json`. Admin can switch languages.
|
|
93
|
+
|
|
94
|
+
## Security
|
|
95
|
+
|
|
96
|
+
`.admin()` requires admin auth. Soft delete is on by default.
|
|
97
|
+
|
|
98
|
+
**Full reference:** [REFERENCE.md](./REFERENCE.md)
|
package/docs/advanced.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Advanced Features
|
|
2
|
+
|
|
3
|
+
## Soft deletes
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
resource("clients", {
|
|
7
|
+
name: string().required(),
|
|
8
|
+
}).softDeletes().admin();
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `DELETE /api/clients/1` → soft (sets `deleted_at`)
|
|
12
|
+
- `DELETE /api/clients/1?force=1` → hard delete
|
|
13
|
+
- `POST /api/clients/1/restore` → restore
|
|
14
|
+
- `GET /api/clients?trashed=only` → trash only
|
|
15
|
+
- `GET /api/clients?trashed=with` → include deleted
|
|
16
|
+
|
|
17
|
+
## Export
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
GET /api/clients/export?format=csv
|
|
21
|
+
GET /api/clients/export?format=json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## SMS
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { sms } from "./packages/core/src/index.js";
|
|
28
|
+
await sms("0501234567", "Verification code: 1234");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```env
|
|
32
|
+
SMS_DRIVER=log # default → storage/logs/sms.log
|
|
33
|
+
# SMS_DRIVER=webhook
|
|
34
|
+
# SMS_WEBHOOK=https://...
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Middleware
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { use, rateLimit, cors } from "./packages/core/src/index.js";
|
|
41
|
+
|
|
42
|
+
use(cors());
|
|
43
|
+
use(rateLimit(100, 60_000)); // 100 req / minute
|
|
44
|
+
|
|
45
|
+
use(async (req, user, next) => {
|
|
46
|
+
console.log(req.method, req.url);
|
|
47
|
+
return next();
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Default on `start()`: CORS + rate limit (`RATE_LIMIT`, default 120/min).
|
|
52
|
+
|
|
53
|
+
## Plugins
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { plugin } from "./packages/core/src/index.js";
|
|
57
|
+
|
|
58
|
+
plugin({
|
|
59
|
+
name: "audit",
|
|
60
|
+
boot: () => console.log("audit ready"),
|
|
61
|
+
hooks: {
|
|
62
|
+
"resource.created": async ({ resource, data }) => {
|
|
63
|
+
console.log("created", resource, data);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Testing helpers
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { resource, string, createTestApp } from "./packages/core/src/index.js";
|
|
73
|
+
|
|
74
|
+
resource("items", { name: string().required() });
|
|
75
|
+
const app = await createTestApp();
|
|
76
|
+
|
|
77
|
+
const res = await app.request("POST", "/api/items", {
|
|
78
|
+
body: JSON.stringify({ name: "X" }),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const asAdmin = app.actingAs({ id: 1, email: "a@b.com", role: "admin" });
|
|
82
|
+
await asAdmin.request("GET", "/api/items");
|
|
83
|
+
|
|
84
|
+
app.close();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## vs Laravel
|
|
88
|
+
|
|
89
|
+
| Laravel | Nexa |
|
|
90
|
+
|---------|------|
|
|
91
|
+
| SoftDeletes trait | `.softDeletes()` |
|
|
92
|
+
| Excel/Export | `/export?format=csv` |
|
|
93
|
+
| Notification SMS | `sms()` |
|
|
94
|
+
| Middleware | `use()` |
|
|
95
|
+
| RateLimiter | `rateLimit()` |
|
|
96
|
+
| Service Provider / Package | `plugin()` |
|
|
97
|
+
| Feature tests | `createTestApp()` |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
## Login / Register
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
POST /api/auth/register { email, password }
|
|
7
|
+
POST /api/auth/login { email, password } → { user, token }
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Use: `Authorization: Bearer {token}`
|
|
11
|
+
|
|
12
|
+
Protect resources: `.auth()` or `.policy({...})`
|
|
13
|
+
|
|
14
|
+
Default admin: `admin@nexa.dev` / `123456`
|
|
15
|
+
|
|
16
|
+
## Password reset
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
POST /api/auth/forgot-password { email }
|
|
20
|
+
POST /api/auth/reset-password { email, token, password }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Token is emailed (`MAIL_DRIVER=log` → `storage/logs/mail.log` in dev).
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { forgotPassword, resetPassword } from "@nexa-stack/framework";
|
|
27
|
+
|
|
28
|
+
await forgotPassword("ali@corp.com");
|
|
29
|
+
await resetPassword("ali@corp.com", token, "new-password");
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Email verification
|
|
33
|
+
|
|
34
|
+
On register, a verification email is sent.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
GET /api/auth/verify-email?email=...&token=...
|
|
38
|
+
POST /api/auth/verify-email { email, token }
|
|
39
|
+
POST /api/auth/resend-verification { email }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { verifyEmail, resendVerification } from "@nexa-stack/framework";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`users.email_verified_at` is set when verified.
|
|
47
|
+
|
|
48
|
+
## .env
|
|
49
|
+
|
|
50
|
+
```env
|
|
51
|
+
APP_URL=http://localhost:3333
|
|
52
|
+
APP_SECRET=change-me
|
|
53
|
+
MAIL_DRIVER=log
|
|
54
|
+
```
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# CLI
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
npm run serve # or: npx nexa serve
|
|
5
|
+
npx nexa migrate # run migrations
|
|
6
|
+
npx nexa db:seed # seed database
|
|
7
|
+
npx nexa queue:work # process queued jobs
|
|
8
|
+
npx nexa schedule:work # run scheduled tasks
|
|
9
|
+
npx nexa make:resource clients
|
|
10
|
+
npm test # run tests
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## make:resource
|
|
14
|
+
|
|
15
|
+
Creates `resources/clients.ts` — auto-loaded from `resources/` (no import needed).
|
package/docs/compare.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Nexa vs Laravel
|
|
2
|
+
|
|
3
|
+
Browser page: **[/compare](/compare)**
|
|
4
|
+
|
|
5
|
+
## CLI
|
|
6
|
+
|
|
7
|
+
| Task | Laravel | Nexa |
|
|
8
|
+
|------|---------|------|
|
|
9
|
+
| Serve | `php artisan serve` | `nexa serve` |
|
|
10
|
+
| Migrate | `php artisan migrate` | `nexa migrate` |
|
|
11
|
+
| Seed | `php artisan db:seed` | `nexa db:seed` |
|
|
12
|
+
| Queue | `php artisan queue:work` | `nexa queue:work` |
|
|
13
|
+
| Schedule | `php artisan schedule:work` | `nexa schedule:work` |
|
|
14
|
+
| Make | `php artisan make:model X -mcr` | `nexa make:resource x` |
|
|
15
|
+
| Test | `php artisan test` | `nexa test` |
|
|
16
|
+
|
|
17
|
+
## Defining a resource
|
|
18
|
+
|
|
19
|
+
**Laravel:** Migration + Model + Controller + Route
|
|
20
|
+
**Nexa:**
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
resource("posts", {
|
|
24
|
+
title: string().required(),
|
|
25
|
+
body: text(),
|
|
26
|
+
}).admin().auth().softDeletes();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Concepts
|
|
30
|
+
|
|
31
|
+
| Laravel | Nexa |
|
|
32
|
+
|---------|------|
|
|
33
|
+
| Eloquent Model | `resource()` |
|
|
34
|
+
| Migration files | auto from fields |
|
|
35
|
+
| Controller | auto CRUD |
|
|
36
|
+
| `Route::apiResource` | `/api/{name}` |
|
|
37
|
+
| FormRequest | field builders |
|
|
38
|
+
| SoftDeletes | `.softDeletes()` |
|
|
39
|
+
| Policy | `.policy()` |
|
|
40
|
+
| Filament/Nova | `.admin()` |
|
|
41
|
+
|
|
42
|
+
## Services
|
|
43
|
+
|
|
44
|
+
| Laravel | Nexa |
|
|
45
|
+
|---------|------|
|
|
46
|
+
| `Mail::` | `mail()` |
|
|
47
|
+
| `Cache::` | `cache` |
|
|
48
|
+
| `Log::` | `log` |
|
|
49
|
+
| `dispatch()` | `dispatch()` |
|
|
50
|
+
|
|
51
|
+
Open [/compare](/compare) for side-by-side tables and code.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
All config via `.env` (loaded automatically on start).
|
|
4
|
+
|
|
5
|
+
## Core
|
|
6
|
+
|
|
7
|
+
| Key | Default | Description |
|
|
8
|
+
|-----|---------|-------------|
|
|
9
|
+
| `PORT` | `3333` | HTTP port |
|
|
10
|
+
| `APP_SECRET` | `change-me` | JWT signing secret |
|
|
11
|
+
| `APP_URL` | `http://localhost:3333` | Used in reset/verify email links |
|
|
12
|
+
| `APP_LOCALE` | `en` | Default UI language (`en` or `ar`) |
|
|
13
|
+
|
|
14
|
+
## Database
|
|
15
|
+
|
|
16
|
+
| Key | Example | Description |
|
|
17
|
+
|-----|---------|-------------|
|
|
18
|
+
| `DB_PATH` | `./app.db` | SQLite file (default) |
|
|
19
|
+
| `DATABASE_URL` | `postgresql://user:pass@localhost:5432/nexa` | PostgreSQL |
|
|
20
|
+
| `DATABASE_URL` | `mysql://user:pass@localhost:3306/nexa` | MySQL / MariaDB |
|
|
21
|
+
|
|
22
|
+
If `DATABASE_URL` is set, it wins over `DB_PATH`.
|
|
23
|
+
|
|
24
|
+
## Mail
|
|
25
|
+
|
|
26
|
+
| Key | Default | Description |
|
|
27
|
+
|-----|---------|-------------|
|
|
28
|
+
| `MAIL_DRIVER` | `log` | `log` or `smtp` |
|
|
29
|
+
| `MAIL_FROM` | `nexa@localhost` | From address |
|
|
30
|
+
| `MAIL_HOST` | | SMTP host |
|
|
31
|
+
| `MAIL_PORT` | `587` | SMTP port |
|
|
32
|
+
| `MAIL_USER` | | SMTP user |
|
|
33
|
+
| `MAIL_PASS` | | SMTP password |
|
|
34
|
+
| `MAIL_SECURE` | `false` | TLS |
|
|
35
|
+
|
|
36
|
+
`log` writes to `storage/logs/mail.log` (good for local/dev).
|
|
37
|
+
|
|
38
|
+
## Cache / SMS / Limits
|
|
39
|
+
|
|
40
|
+
| Key | Default | Description |
|
|
41
|
+
|-----|---------|-------------|
|
|
42
|
+
| `CACHE_DRIVER` | `memory` | `memory` · `file` · `redis` |
|
|
43
|
+
| `REDIS_URL` | `redis://127.0.0.1:6379` | When cache is redis |
|
|
44
|
+
| `SMS_DRIVER` | `log` | `log` or `webhook` |
|
|
45
|
+
| `SMS_WEBHOOK` | | POST target for SMS |
|
|
46
|
+
| `RATE_LIMIT` | `120` | Requests per minute per IP (0 = off) |
|
|
47
|
+
| `LOG_LEVEL` | `debug` | `debug` · `info` · `warn` · `error` |
|
|
48
|
+
|
|
49
|
+
## Example production `.env`
|
|
50
|
+
|
|
51
|
+
```env
|
|
52
|
+
APP_SECRET=long-random-string
|
|
53
|
+
APP_URL=https://api.yourapp.com
|
|
54
|
+
DATABASE_URL=postgresql://nexa:secret@db:5432/nexa
|
|
55
|
+
MAIL_DRIVER=smtp
|
|
56
|
+
MAIL_HOST=smtp.mailgun.org
|
|
57
|
+
MAIL_PORT=587
|
|
58
|
+
MAIL_USER=...
|
|
59
|
+
MAIL_PASS=...
|
|
60
|
+
MAIL_FROM=noreply@yourapp.com
|
|
61
|
+
CACHE_DRIVER=redis
|
|
62
|
+
REDIS_URL=redis://redis:6379
|
|
63
|
+
RATE_LIMIT=120
|
|
64
|
+
LOG_LEVEL=info
|
|
65
|
+
```
|