@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.
Files changed (81) hide show
  1. package/.env.example +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -0
  4. package/bin/nexa.mjs +41 -0
  5. package/bin/nexa.ts +334 -0
  6. package/docs/AI.md +69 -0
  7. package/docs/ARCHITECTURE.md +74 -0
  8. package/docs/EXAMPLES.md +114 -0
  9. package/docs/FRAMEWORK.md +226 -0
  10. package/docs/LANGUAGE.md +39 -0
  11. package/docs/README.md +7 -0
  12. package/docs/READY.md +51 -0
  13. package/docs/REFERENCE.md +255 -0
  14. package/docs/START.md +98 -0
  15. package/docs/advanced.md +97 -0
  16. package/docs/authentication.md +54 -0
  17. package/docs/cli.md +15 -0
  18. package/docs/compare.md +51 -0
  19. package/docs/configuration.md +65 -0
  20. package/docs/database.md +396 -0
  21. package/docs/installation.md +57 -0
  22. package/docs/localization.md +47 -0
  23. package/docs/resources.md +75 -0
  24. package/docs/routing.md +59 -0
  25. package/docs/seeding.md +33 -0
  26. package/docs/services.md +146 -0
  27. package/package.json +77 -0
  28. package/packages/auth/src/auth.test.ts +23 -0
  29. package/packages/auth/src/auth.ts +287 -0
  30. package/packages/auth/src/index.ts +17 -0
  31. package/packages/cache/src/index.ts +203 -0
  32. package/packages/client/src/index.ts +49 -0
  33. package/packages/core/src/app.ts +97 -0
  34. package/packages/core/src/config.ts +55 -0
  35. package/packages/core/src/dev.ts +104 -0
  36. package/packages/core/src/fields.test.ts +81 -0
  37. package/packages/core/src/fields.ts +309 -0
  38. package/packages/core/src/index.ts +60 -0
  39. package/packages/core/src/lang.ts +79 -0
  40. package/packages/core/src/loader.ts +42 -0
  41. package/packages/core/src/migrate.ts +91 -0
  42. package/packages/core/src/policy.ts +36 -0
  43. package/packages/core/src/registry.ts +17 -0
  44. package/packages/core/src/reload.ts +92 -0
  45. package/packages/core/src/resource.test.ts +32 -0
  46. package/packages/core/src/resource.ts +87 -0
  47. package/packages/core/src/routes.ts +22 -0
  48. package/packages/core/src/runtime.ts +11 -0
  49. package/packages/database/src/builder.ts +266 -0
  50. package/packages/database/src/database.ts +252 -0
  51. package/packages/database/src/dialect.ts +186 -0
  52. package/packages/database/src/index.ts +6 -0
  53. package/packages/database/src/mysql.ts +114 -0
  54. package/packages/database/src/postgres.ts +117 -0
  55. package/packages/database/src/query.ts +115 -0
  56. package/packages/database/src/sqlite.ts +216 -0
  57. package/packages/database/src/types.ts +104 -0
  58. package/packages/events/src/index.ts +17 -0
  59. package/packages/export/src/index.ts +36 -0
  60. package/packages/log/src/index.ts +38 -0
  61. package/packages/mail/src/index.ts +130 -0
  62. package/packages/notifications/src/index.ts +84 -0
  63. package/packages/plugins/src/index.ts +39 -0
  64. package/packages/queue/src/index.ts +185 -0
  65. package/packages/queue/src/jobs.ts +9 -0
  66. package/packages/schedule/src/index.ts +64 -0
  67. package/packages/server/src/index.ts +1 -0
  68. package/packages/server/src/middleware.ts +143 -0
  69. package/packages/server/src/query.ts +40 -0
  70. package/packages/server/src/router.ts +813 -0
  71. package/packages/sms/src/index.ts +33 -0
  72. package/packages/storage/src/upload.ts +36 -0
  73. package/packages/testing/src/index.ts +67 -0
  74. package/packages/validation/src/index.ts +1 -0
  75. package/packages/validation/src/validate.test.ts +35 -0
  76. package/packages/validation/src/validate.ts +112 -0
  77. package/public/admin.html +369 -0
  78. package/public/compare.html +66 -0
  79. package/public/dev-bar.js +213 -0
  80. package/public/docs.html +315 -0
  81. package/public/index.html +66 -0
@@ -0,0 +1,396 @@
1
+ # Database
2
+
3
+
4
+
5
+ ## SQLite (default)
6
+
7
+
8
+
9
+ ```env
10
+
11
+ DB_PATH=./app.db
12
+
13
+ ```
14
+
15
+
16
+
17
+ ## PostgreSQL
18
+
19
+
20
+
21
+ ```env
22
+
23
+ DATABASE_URL=postgresql://user:pass@localhost:5432/nexa
24
+
25
+ ```
26
+
27
+
28
+
29
+ When `DATABASE_URL` is set, Nexa picks the driver from the URL scheme. Otherwise SQLite via `DB_PATH`.
30
+
31
+ ## MySQL / MariaDB
32
+
33
+ ```env
34
+ DATABASE_URL=mysql://user:pass@localhost:3306/nexa
35
+ ```
36
+
37
+ `mariadb://` URLs work the same way.
38
+
39
+ | Driver | URL prefix | Default env |
40
+ |--------|------------|-------------|
41
+ | SQLite | file path | `DB_PATH=./app.db` |
42
+ | PostgreSQL | `postgresql://` | `DATABASE_URL` |
43
+ | MySQL / MariaDB | `mysql://` or `mariadb://` | `DATABASE_URL` |
44
+
45
+
46
+
47
+ ## Query from code
48
+
49
+
50
+
51
+ ```ts
52
+
53
+ const db = getDb()!;
54
+
55
+
56
+
57
+ // All
58
+
59
+ await db.findAll("clients");
60
+
61
+
62
+
63
+ // One
64
+
65
+ await db.findById("clients", 1);
66
+
67
+
68
+
69
+ // Insert
70
+
71
+ await db.insert("clients", { name: "Ali", phone: "050..." });
72
+
73
+
74
+
75
+ // SQL
76
+
77
+ await db.query("SELECT * FROM clients WHERE city = ?", ["Damascus"]);
78
+
79
+ ```
80
+
81
+
82
+
83
+ ## Relations
84
+
85
+
86
+
87
+ ### belongsTo
88
+
89
+
90
+
91
+ ```ts
92
+
93
+ resource("orders", {
94
+
95
+ client: belongsTo("clients").required(),
96
+
97
+ });
98
+
99
+ ```
100
+
101
+
102
+
103
+ API returns nested object: `{ client: { id: 1, name: "Ali" } }`
104
+
105
+
106
+
107
+ ### hasMany
108
+
109
+
110
+
111
+ ```ts
112
+
113
+ resource("clients", {
114
+
115
+ name: string().required(),
116
+
117
+ orders: hasMany("orders"),
118
+
119
+ });
120
+
121
+ ```
122
+
123
+
124
+
125
+ API returns related records: `{ orders: [{ id: 1, ... }] }`
126
+
127
+
128
+
129
+ Foreign key on child table: `client_id` (auto from parent table name).
130
+
131
+
132
+
133
+ ## File uploads
134
+
135
+
136
+
137
+ Field types:
138
+
139
+
140
+
141
+ ```ts
142
+
143
+ resource("products", {
144
+
145
+ name: string().required(),
146
+
147
+ photo: image(),
148
+
149
+ manual: file(),
150
+
151
+ });
152
+
153
+ ```
154
+
155
+
156
+
157
+ Upload endpoint (requires login):
158
+
159
+
160
+
161
+ ```
162
+
163
+ POST /api/upload
164
+
165
+ Content-Type: multipart/form-data
166
+
167
+
168
+
169
+ file=<binary>
170
+
171
+ type=image # optional, validates image extensions
172
+
173
+ ```
174
+
175
+
176
+
177
+ Response:
178
+
179
+
180
+
181
+ ```json
182
+
183
+ { "data": { "url": "/storage/uploads/uuid.jpg", "name": "photo.jpg" } }
184
+
185
+ ```
186
+
187
+
188
+
189
+ Save the `url` in your resource field when creating/updating.
190
+
191
+
192
+
193
+ ## Policies
194
+
195
+
196
+
197
+ Protect resources per action:
198
+
199
+
200
+
201
+ ```ts
202
+
203
+ resource("invoices", { ... })
204
+
205
+ .policy({
206
+
207
+ view: "admin",
208
+
209
+ create: "admin",
210
+
211
+ update: (user) => user?.role === "manager",
212
+
213
+ delete: "admin",
214
+
215
+ });
216
+
217
+ ```
218
+
219
+
220
+
221
+ `.auth("admin")` is shorthand for all actions requiring that role.
222
+
223
+
224
+
225
+ ## Events & Queue
226
+
227
+
228
+
229
+ Events fire on CRUD:
230
+
231
+
232
+
233
+ - `orders.created`
234
+
235
+ - `orders.updated`
236
+
237
+ - `orders.deleted`
238
+
239
+
240
+
241
+ Listen:
242
+
243
+
244
+
245
+ ```ts
246
+
247
+ import { on } from "./packages/core/src/index.js";
248
+
249
+
250
+
251
+ on("orders.created", async (payload) => {
252
+
253
+ console.log("New order", payload);
254
+
255
+ });
256
+
257
+ ```
258
+
259
+
260
+
261
+ Queue jobs:
262
+
263
+
264
+
265
+ ```ts
266
+
267
+ import { dispatch } from "./packages/core/src/index.js";
268
+
269
+
270
+
271
+ await dispatch(getDb()!, "orders.created", { id: 1 });
272
+
273
+ ```
274
+
275
+
276
+
277
+ Process jobs:
278
+
279
+
280
+
281
+ ```bash
282
+
283
+ bun run nexa queue:work
284
+
285
+ ```
286
+
287
+
288
+
289
+ ## API — Search
290
+
291
+
292
+
293
+ ```
294
+
295
+ GET /api/clients?search=ali
296
+
297
+ ```
298
+
299
+
300
+
301
+ Searches all string/email/text fields.
302
+
303
+
304
+
305
+ ## API — Filter
306
+
307
+
308
+
309
+ ```
310
+
311
+ GET /api/clients?city=Damascus
312
+
313
+ GET /api/orders?status=paid
314
+
315
+ ```
316
+
317
+
318
+
319
+ ## API — Sort
320
+
321
+
322
+
323
+ ```
324
+
325
+ GET /api/clients?sort=name&order=asc
326
+
327
+ ```
328
+
329
+
330
+
331
+ ## API — Pagination (Laravel format)
332
+
333
+
334
+
335
+ ```
336
+
337
+ GET /api/clients?page=2&limit=20
338
+
339
+ ```
340
+
341
+
342
+
343
+ Response:
344
+
345
+
346
+
347
+ ```json
348
+
349
+ {
350
+
351
+ "data": [...],
352
+
353
+ "meta": {
354
+
355
+ "current_page": 2,
356
+
357
+ "last_page": 5,
358
+
359
+ "per_page": 20,
360
+
361
+ "total": 100,
362
+
363
+ "from": 21,
364
+
365
+ "to": 40
366
+
367
+ },
368
+
369
+ "links": {
370
+
371
+ "first": "...",
372
+
373
+ "last": "...",
374
+
375
+ "prev": "...",
376
+
377
+ "next": "..."
378
+
379
+ }
380
+
381
+ }
382
+
383
+ ```
384
+
385
+
386
+
387
+ ## Combine
388
+
389
+
390
+
391
+ ```
392
+
393
+ GET /api/clients?search=ali&city=Damascus&sort=name&order=asc&page=1&limit=10
394
+
395
+ ```
396
+
@@ -0,0 +1,57 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ - [Node.js](https://nodejs.org) ≥ 20
6
+ - Optional: PostgreSQL or MySQL for production
7
+ - Optional: Redis for `CACHE_DRIVER=redis`
8
+
9
+ ## In this repo
10
+
11
+ ```bash
12
+ cd F:\framework
13
+ npm install
14
+ npm run serve
15
+ ```
16
+
17
+ - Admin: http://localhost:3333/admin
18
+ - Docs: http://localhost:3333/docs
19
+ - API: http://localhost:3333/api
20
+
21
+ Login: `admin@nexa.dev` / `123456`
22
+
23
+ ## As a package
24
+
25
+ ```bash
26
+ npx @nexa-stack/framework new my-shop
27
+ cd my-shop
28
+ npm install
29
+ npm run serve
30
+ ```
31
+
32
+ ## Language
33
+
34
+ Default is **English** (`APP_LOCALE=en`).
35
+
36
+ ```env
37
+ APP_LOCALE=en # default
38
+ # APP_LOCALE=ar # Arabic UI + RTL in admin
39
+ ```
40
+
41
+ ## Hostinger
42
+
43
+ 1. Create a MySQL database in hPanel
44
+ 2. Node.js app (Node ≥ 20)
45
+ 3. `.env`:
46
+
47
+ ```env
48
+ DATABASE_URL=mysql://USER:PASS@localhost:3306/DBNAME
49
+ APP_SECRET=change-me-to-a-long-random-string
50
+ NODE_ENV=production
51
+ PORT=3333
52
+ APP_LOCALE=en
53
+ ```
54
+
55
+ 4. Start: `npx nexa serve`
56
+
57
+ SQLite uses **sql.js** (WASM) — no Visual Studio / node-gyp. Prefer MySQL in production.
@@ -0,0 +1,47 @@
1
+ # Localization
2
+
3
+ Default language is **English** (`APP_LOCALE=en`).
4
+
5
+ ## Resource labels
6
+
7
+ ```ts
8
+ resource("clients", { ... }, { labelKey: "clients" }).admin();
9
+ ```
10
+
11
+ `lang/en.json`:
12
+
13
+ ```json
14
+ {
15
+ "clients": "Clients",
16
+ "products": "Products",
17
+ "ui": { "login": "Login", "add": "Add" }
18
+ }
19
+ ```
20
+
21
+ Optional `lang/ar.json`:
22
+
23
+ ```json
24
+ {
25
+ "clients": "العملاء",
26
+ "products": "المنتجات",
27
+ "ui": { "login": "دخول", "add": "إضافة" }
28
+ }
29
+ ```
30
+
31
+ ## Admin language
32
+
33
+ EN / عربي toggle in the admin UI, or:
34
+
35
+ ```
36
+ GET /api/schema?lang=en
37
+ GET /api/schema?lang=ar
38
+ ```
39
+
40
+ ## Default locale
41
+
42
+ ```env
43
+ APP_LOCALE=en
44
+ # APP_LOCALE=ar
45
+ ```
46
+
47
+ See also [LANGUAGE.md](./LANGUAGE.md).
@@ -0,0 +1,75 @@
1
+ # Resources
2
+
3
+ A **resource** = database table + REST API + optional admin page.
4
+
5
+ ```ts
6
+ import { resource, string, money, belongsTo } from "@nexa-stack/framework";
7
+ // or: from "./packages/core/src/index.js"
8
+
9
+ resource("products", {
10
+ name: string().required(),
11
+ price: money().required(),
12
+ supplier: belongsTo("suppliers"),
13
+ }, { labelKey: "products" })
14
+ .admin()
15
+ .auth()
16
+ .softDeletes();
17
+ ```
18
+
19
+ ## Options
20
+
21
+ | Option | Description |
22
+ |--------|-------------|
23
+ | `label` | Display name (admin) |
24
+ | `labelKey` | Key in `lang/ar.json` / `lang/en.json` |
25
+
26
+ ## Chain methods
27
+
28
+ | Method | Effect |
29
+ |--------|--------|
30
+ | `.admin()` | Show in `/admin` sidebar + **soft delete by default** |
31
+ | `.auth(role?)` | Require login (default role `admin`) |
32
+ | `.policy({...})` | Per-action rules: view/create/update/delete |
33
+ | `.softDeletes()` | Soft delete + restore (already on with `.admin()`) |
34
+ | `.hardDeletes()` | Permanent DELETE instead of soft |
35
+
36
+ ## Field builders
37
+
38
+ | Builder | Column | Notes |
39
+ |---------|--------|-------|
40
+ | `string()` | TEXT | |
41
+ | `text()` | TEXT | long text |
42
+ | `number()` | number | |
43
+ | `money()` | number | money amounts |
44
+ | `email()` | TEXT | validated |
45
+ | `boolean()` | bool/int | returns **true/false** in JSON |
46
+ | `file()` / `image()` | TEXT | stores URL |
47
+ | `belongsTo("table")` | `{name}_id` | nested on GET |
48
+ | `hasMany("table")` | — | nested list on GET |
49
+ | `belongsToMany("table")` | pivot auto | e.g. `post_tag` |
50
+
51
+ Modifiers: `.required()` · `.min(n)` · `.max(n)`
52
+
53
+ ## Auto API
54
+
55
+ For `resource("products", ...)`:
56
+
57
+ | Method | URL |
58
+ |--------|-----|
59
+ | GET | `/api/products` |
60
+ | GET | `/api/products/:id` |
61
+ | POST | `/api/products` |
62
+ | PUT | `/api/products/:id` |
63
+ | DELETE | `/api/products/:id` |
64
+ | GET | `/api/products/export?format=csv` |
65
+ | POST | `/api/products/:id/restore` | if softDeletes |
66
+
67
+ ## Query params (list)
68
+
69
+ ```
70
+ ?search=ali&city=Damascus&sort=name&order=asc&page=1&limit=20
71
+ ?trashed=only # soft-deleted only
72
+ ?trashed=with # include deleted
73
+ ```
74
+
75
+ See [database.md](./database.md) for pagination response shape.
@@ -0,0 +1,59 @@
1
+ # Routing
2
+
3
+ You do **not** write routes. Nexa registers them from resources.
4
+
5
+ ## Built-in endpoints
6
+
7
+ ### Framework
8
+
9
+ | Method | URL | Description |
10
+ |--------|-----|-------------|
11
+ | GET | `/api` | List endpoints |
12
+ | GET | `/api/schema?lang=ar` | Admin schema |
13
+ | GET | `/api/dashboard` | Stats |
14
+
15
+ ### Auth
16
+
17
+ | Method | URL |
18
+ |--------|-----|
19
+ | POST | `/api/auth/register` |
20
+ | POST | `/api/auth/login` |
21
+ | POST | `/api/auth/forgot-password` |
22
+ | POST | `/api/auth/reset-password` |
23
+ | GET/POST | `/api/auth/verify-email` |
24
+ | POST | `/api/auth/resend-verification` |
25
+
26
+ ### Other
27
+
28
+ | Method | URL |
29
+ |--------|-----|
30
+ | POST | `/api/upload` | multipart `file` |
31
+ | GET | `/api/notifications` | unread (auth) |
32
+ | POST | `/api/notifications/:id/read` | |
33
+ | POST | `/api/notifications/read-all` | |
34
+ | GET | `/storage/uploads/...` | served files |
35
+
36
+ ### Pages
37
+
38
+ | URL | Page |
39
+ |-----|------|
40
+ | `/admin` | Admin UI |
41
+ | `/docs` | Documentation |
42
+ | `/cashier` | Demo (if present) |
43
+
44
+ ## Custom middleware
45
+
46
+ ```ts
47
+ import { use } from "@nexa-stack/framework";
48
+
49
+ use(async (req, user, next) => {
50
+ // before
51
+ const res = await next();
52
+ // after
53
+ return res;
54
+ });
55
+ ```
56
+
57
+ Default on `start()`: CORS + rate limit.
58
+
59
+ See [advanced.md](./advanced.md).
@@ -0,0 +1,33 @@
1
+ # Seeding
2
+
3
+ Put seeders in `database/seeders/*.ts`.
4
+
5
+ ```ts
6
+ // database/seeders/erp.ts
7
+ import type { Database } from "../../packages/database/src/database.js";
8
+
9
+ export default async function seed(db: Database) {
10
+ if ((await db.findAll("clients")).length > 0) return;
11
+
12
+ await db.insert("clients", {
13
+ name: "Al Noor",
14
+ phone: "0502222222",
15
+ email: "n@corp.com",
16
+ city: "Damascus",
17
+ });
18
+ }
19
+ ```
20
+
21
+ ## Run
22
+
23
+ ```bash
24
+ bun run nexa db:seed
25
+ ```
26
+
27
+ Also runs automatically on `nexa serve` after migrations.
28
+
29
+ ## Tips
30
+
31
+ - Check if data exists before inserting (idempotent)
32
+ - Seed order: parents before children (suppliers → products → orders)
33
+ - Default admin user is created by the framework (`admin@nexa.dev`)