jcc-express-mvc 1.9.7 → 1.9.8
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/final-documentation/Architecture Concept/Request-Lifecycle.md +4 -4
- package/final-documentation/Architecture Concept/Service-Container.md +4 -4
- package/final-documentation/Architecture Concept/Service-Provider.md +11 -7
- package/final-documentation/Database/Database-Introduction.md +7 -1
- package/final-documentation/Database/Migrations.md +1 -6
- package/final-documentation/Database/Prisma.md +11 -8
- package/final-documentation/Database/Query-Builder.md +1 -1
- package/final-documentation/Database/Seeding.md +1 -1
- package/final-documentation/Database/Sequelize.md +216 -41
- package/final-documentation/Database/Transactions.md +1 -1
- package/final-documentation/Database/TypeORM.md +114 -22
- package/final-documentation/Digging Deeper/Broadcasting.md +6 -9
- package/final-documentation/Digging Deeper/Cache.md +2 -2
- package/final-documentation/Digging Deeper/Carbon.md +3 -3
- package/final-documentation/Digging Deeper/Deferred-Functions.md +1 -14
- package/final-documentation/Digging Deeper/Events.md +1 -1
- package/final-documentation/Digging Deeper/File-Storage.md +4 -4
- package/final-documentation/Digging Deeper/HTTP-Client.md +1 -6
- package/final-documentation/Digging Deeper/Helpers.md +5 -6
- package/final-documentation/Digging Deeper/Logging.md +3 -4
- package/final-documentation/Digging Deeper/Mail.md +4 -4
- package/final-documentation/Digging Deeper/Monitor.md +6 -26
- package/final-documentation/Digging Deeper/Queues.md +9 -9
- package/final-documentation/Digging Deeper/Rate-Limiting.md +1 -1
- package/final-documentation/Digging Deeper/Strings.md +2 -4
- package/final-documentation/Getting-Started/Configuration.md +12 -9
- package/final-documentation/Getting-Started/Deployment.md +3 -3
- package/final-documentation/Getting-Started/Directory-structure.md +9 -9
- package/final-documentation/Getting-Started/Frontend.md +7 -7
- package/final-documentation/Getting-Started/Installation.md +10 -8
- package/final-documentation/JCC-Eloquent/Defining-Model.md +84 -11
- package/final-documentation/JCC-Eloquent/JCC-Eloquent-Introduction.md +1 -1
- package/final-documentation/JCC-Eloquent/Observer.md +276 -6
- package/final-documentation/JCC-Eloquent/Query-Builder.md +1 -1
- package/final-documentation/Packages/Cloudinary.md +1 -1
- package/final-documentation/Packages/JWT.md +2 -2
- package/final-documentation/Security/Authentication.md +4 -4
- package/final-documentation/Security/Authorization.md +3 -3
- package/final-documentation/Security/Email-Verification.md +2 -2
- package/final-documentation/Security/Encryption.md +1 -1
- package/final-documentation/Security/Hashing.md +1 -1
- package/final-documentation/Testing/Introduction.md +2 -2
- package/final-documentation/Testing/Testing-Overview.md +8 -8
- package/final-documentation/Testing/Unit-Testing.md +3 -36
- package/final-documentation/The Basics/API-Resources.md +7 -11
- package/final-documentation/The Basics/Artisan-Node.md +7 -1
- package/final-documentation/The Basics/Asset-bundling.md +1 -1
- package/final-documentation/The Basics/CSRF-protection.md +1 -1
- package/final-documentation/The Basics/Controllers.md +7 -7
- package/final-documentation/The Basics/Error-handling.md +28 -32
- package/final-documentation/The Basics/JCC-Blade.md +2 -2
- package/final-documentation/The Basics/Middleware.md +2 -2
- package/final-documentation/The Basics/Request.md +6 -5
- package/final-documentation/The Basics/Response.md +2 -2
- package/final-documentation/The Basics/Routing.md +7 -6
- package/final-documentation/The Basics/Session.md +8 -8
- package/final-documentation/The Basics/Validation.md +12 -14
- package/final-documentation/The Basics/Views.md +9 -14
- package/global.d.ts +1 -1
- package/lib/Jcc-eloquent/lib/Event/Event.d.ts +1 -0
- package/lib/Jcc-eloquent/lib/Event/Event.d.ts.map +1 -1
- package/lib/Jcc-eloquent/lib/Event/Event.js +21 -10
- package/lib/Jcc-eloquent/lib/Event/ModelEvent.d.ts +3 -3
- package/lib/Jcc-eloquent/lib/Event/ModelEvent.d.ts.map +1 -1
- package/lib/Jcc-eloquent/lib/Event/ModelEvent.js +23 -22
- package/lib/Jcc-eloquent/lib/Model.d.ts +1 -1
- package/lib/Jcc-eloquent/lib/Model.d.ts.map +1 -1
- package/lib/Jcc-eloquent/lib/Model.js +6 -3
- package/package.json +1 -1
|
@@ -59,7 +59,7 @@ Think of the stack as: framework defaults → your kernel `middlewares` (for exa
|
|
|
59
59
|
|
|
60
60
|
One of the most important bootstrap steps is loading service providers. Providers register bindings on the container (`register`) and run extra setup once registrations exist (`boot`).
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
During application bootstrap, the framework prepends `DatabaseServiceProvider`, registers your list from `bootstrap/providers.ts` (and may place `AuthServiceProvider` early), then appends `QueueServiceProvider`. That order matters so the database, auth, and queue subsystems are available where other code expects them.
|
|
63
63
|
|
|
64
64
|
Your own providers live under `app/Providers/` (for example `AppServiceProvider`) and are listed in `bootstrap/providers.ts`. Use `AppServiceProvider` (or additional providers) for container bindings, config-driven setup, and small bootstrap tasks—same idea as Laravel’s `AppServiceProvider`.
|
|
65
65
|
|
|
@@ -92,7 +92,7 @@ If the request fails a guard (for example user not authenticated), middleware ca
|
|
|
92
92
|
|
|
93
93
|
If the request passes all matched middleware, your route or controller runs and produces the response (JSON, Blade, Inertia, redirect, …).
|
|
94
94
|
|
|
95
|
-
Reference — framework middleware order (before kernel entries)
|
|
95
|
+
Reference — default framework middleware order (before kernel entries):
|
|
96
96
|
|
|
97
97
|
1. `express.json()`
|
|
98
98
|
2. `express.urlencoded({ extended: false })`
|
|
@@ -117,7 +117,7 @@ When your controller or closure finishes—by calling `res.send`, `res.json`, `r
|
|
|
117
117
|
|
|
118
118
|
The framework listens for `finish` on the response and clears the per-request `request` / `response` / `next` bindings on the container so the next request does not leak state.
|
|
119
119
|
|
|
120
|
-
If an error is passed to `next(error)` or thrown without being caught,
|
|
120
|
+
If an error is passed to `next(error)` or thrown without being caught, the framework error handler maps it to an HTTP response (status and body). See [Error handling](../The%20Basics/Error-handling.md).
|
|
121
121
|
|
|
122
122
|
---
|
|
123
123
|
|
|
@@ -132,6 +132,6 @@ Your `bootstrap/providers.ts` list is the counterpart to Laravel’s provider li
|
|
|
132
132
|
## Where to customize (quick reference)
|
|
133
133
|
|
|
134
134
|
- Global HTTP middleware — `app/Http/kernel.ts` → `middlewares` and `middlewareAliases`.
|
|
135
|
-
- Default Express stack —
|
|
135
|
+
- Default Express stack — framework global middleware + `app/Config` (CORS, rate limit, engine).
|
|
136
136
|
- Routes and API prefixes — `route/*.ts` and `bootstrap/app.ts` → `withRouting`.
|
|
137
137
|
- Container bindings and boot logic — `app/Providers/*` and `bootstrap/providers.ts`.
|
|
@@ -84,15 +84,15 @@ If resolution fails, you may need an explicit `bind` for that type or pass param
|
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
|
87
|
-
## Controllers and
|
|
87
|
+
## Controllers and method injection
|
|
88
88
|
|
|
89
|
-
For controller actions, the framework
|
|
89
|
+
For controller actions, the framework resolves method parameters from container metadata: FormRequest classes, models, route parameters, and services. Plain routes that use `(req, res) =>` do not need this—you still benefit from `view()` / `inertia()` because they read `response` from the container after it is bound.
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
93
|
## Global `app` and helpers
|
|
94
94
|
|
|
95
|
-
After
|
|
95
|
+
After the application boots, `globalThis.app` is the Application instance. Helpers like `env()`, `view()`, and `inertia()` use the container under the hood (for example `response` from `app.resolve("response")`).
|
|
96
96
|
|
|
97
97
|
Prefer constructor injection or explicit `app.resolve()` in providers and long-lived services; use globals where the framework already does (routes, Blade/Inertia helpers).
|
|
98
98
|
|
|
@@ -100,7 +100,7 @@ Prefer constructor injection or explicit `app.resolve()` in providers and long-l
|
|
|
100
100
|
|
|
101
101
|
## Comparison to Laravel (mental model)
|
|
102
102
|
|
|
103
|
-
- Container
|
|
103
|
+
- Container — Laravel: `Illuminate\Container\Container`. JCC: the framework service container (resolved via `app.resolve()` / `app.make()`).
|
|
104
104
|
- Application — Laravel’s `Application` extends the container. JCC: `Application` → `ExpressApplication` → `Container`.
|
|
105
105
|
- Registering services — Laravel: `AppServiceProvider::register`. JCC: same idea in `app/Providers/*` → `register()`.
|
|
106
106
|
- Resolving — Laravel: `app()`, `resolve()`. JCC: `app.resolve()`, `app.make()`.
|
|
@@ -15,7 +15,7 @@ A provider connects the framework to your app:
|
|
|
15
15
|
- Register container bindings (`bind`, `singleton`, `instance`) so other code can `resolve` them.
|
|
16
16
|
- Boot behavior that depends on those bindings (routes are not registered here—`RouteServiceProvider` loads route files during `app.run()`, but your `boot()` can use `Gate`, events, or other services already on the container).
|
|
17
17
|
|
|
18
|
-
The abstract base class
|
|
18
|
+
The abstract base class is **`ServiceProvider`**. Extend it from `jcc-express-mvc/Core/Provider`.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -28,14 +28,17 @@ The abstract base class lives in `jcc-express-mvc` (`ServiceProvider`). Extend i
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
## Registration order (
|
|
31
|
+
## Registration order (bootstrap)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
During `withProviders()`, the application builder does not use your array alone. It builds this chain:
|
|
34
34
|
|
|
35
|
-
1. `
|
|
36
|
-
2. `
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
1. `LogServiceProvider`
|
|
36
|
+
2. `DatabaseServiceProvider` — Knex / Sequelize / Mongoose setup (skipped when `DB_ORM=prisma` or `DB_ORM=typeorm`).
|
|
37
|
+
3. `PrismaServiceProvider` — Prisma client singleton and graceful `$disconnect` (when configured).
|
|
38
|
+
4. `TypeORMServiceProvider` — TypeORM `DataSource` singleton and graceful shutdown (when configured).
|
|
39
|
+
5. `SessionServiceProvider`, `CacheServiceProvider`
|
|
40
|
+
6. Your providers from `bootstrap/providers.ts` — If `AuthServiceProvider` (framework) is in the list but not first, the builder moves it to the front of your list so auth is set up early.
|
|
41
|
+
7. `QueueServiceProvider` — Always last in the chain.
|
|
39
42
|
|
|
40
43
|
That order matters when one provider’s `register()` assumes another binding already exists.
|
|
41
44
|
|
|
@@ -100,6 +103,7 @@ export const providers = [AppServiceProvider];
|
|
|
100
103
|
|
|
101
104
|
- `DatabaseServiceProvider` — Database / ORM wiring; runs before your list.
|
|
102
105
|
- `PrismaServiceProvider` — Registers `database.prisma.service` when configured.
|
|
106
|
+
- `TypeORMServiceProvider` — Registers `database.typeorm.dataSource` when configured.
|
|
103
107
|
- `AuthServiceProvider` (when included) — Intended to run early among your providers.
|
|
104
108
|
- `QueueServiceProvider` — Queue binding; runs after your list.
|
|
105
109
|
- `RouteServiceProvider` — Not in your `bootstrap/providers.ts` list by default; it is registered as a singleton on `Application` and `loadRoutes()` is invoked from `app.run()` to load `route/web`, `route/api`, etc.
|
|
@@ -8,6 +8,7 @@ Almost every real application needs a database. JCC Express MVC keeps this flexi
|
|
|
8
8
|
- **Sequelize** as an alternative SQL ORM
|
|
9
9
|
- **Mongoose** for MongoDB document workflows
|
|
10
10
|
- **Prisma** for schema-first SQL with generated client and migrations
|
|
11
|
+
- **TypeORM** for decorator-based entities and `DataSource` repositories
|
|
11
12
|
|
|
12
13
|
In practice, most projects start with the default JCC stack, then switch ORM mode only when needed.
|
|
13
14
|
|
|
@@ -26,6 +27,8 @@ For document databases, use `DB_ORM=mongoose` with your Mongo connection setting
|
|
|
26
27
|
|
|
27
28
|
For Prisma, use `DB_ORM=prisma` and configure `DATABASE_URL` plus `database.prisma.service` — see [Prisma](./Prisma.md).
|
|
28
29
|
|
|
30
|
+
For TypeORM, use `DB_ORM=typeorm` and configure `database.typeorm.dataSource` — see [TypeORM](./TypeORM.md).
|
|
31
|
+
|
|
29
32
|
---
|
|
30
33
|
|
|
31
34
|
## Configuration
|
|
@@ -42,12 +45,13 @@ Minimal shape from `app/Config/database.ts`:
|
|
|
42
45
|
export const database = {
|
|
43
46
|
orm: config.get("DB_ORM", "jcc"),
|
|
44
47
|
prisma: { service: PrismaService },
|
|
48
|
+
typeorm: { dataSource: TypeORMDataSource },
|
|
45
49
|
sequelize: { /* ... */ },
|
|
46
50
|
mongoose: { /* ... */ },
|
|
47
51
|
};
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
See [Prisma](./Prisma.md) for
|
|
54
|
+
See [Prisma](./Prisma.md) and [TypeORM](./TypeORM.md) for full setup.
|
|
51
55
|
|
|
52
56
|
---
|
|
53
57
|
|
|
@@ -74,6 +78,8 @@ The provider layer uses your env/config to decide connection behavior:
|
|
|
74
78
|
- `DB_ORM=jcc` -> JCC/Knex SQL flow
|
|
75
79
|
- `DB_ORM=sequelize` -> Sequelize connection path
|
|
76
80
|
- `DB_ORM=mongoose` -> Mongoose connection path
|
|
81
|
+
- `DB_ORM=prisma` -> Prisma client path (see [Prisma](./Prisma.md))
|
|
82
|
+
- `DB_ORM=typeorm` -> TypeORM DataSource path (see [TypeORM](./TypeORM.md))
|
|
77
83
|
|
|
78
84
|
Keep `DB_ORM` and `DB_CONNECTION` consistent (for example, do not pair `DB_ORM=mongoose` with SQL-only assumptions in your app code).
|
|
79
85
|
|
|
@@ -135,12 +135,7 @@ Common schema helpers seen in the current codebase:
|
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
-
## Full migration API
|
|
139
|
-
|
|
140
|
-
This list is taken from the current implementation in:
|
|
141
|
-
|
|
142
|
-
- `jcc-express-mvc/lib/Jcc-eloquent/lib/Schema/index.ts`
|
|
143
|
-
- `jcc-express-mvc/lib/Jcc-eloquent/lib/Schema/BluePrint/index.ts`
|
|
138
|
+
## Full migration API
|
|
144
139
|
|
|
145
140
|
### `Schema` methods
|
|
146
141
|
|
|
@@ -129,8 +129,8 @@ The framework only injects an adapter when you set `database.prisma.adapter` or
|
|
|
129
129
|
|
|
130
130
|
```typescript
|
|
131
131
|
import { PrismaClient } from "generated/prisma/client";
|
|
132
|
-
import { createMariaDbAdapter } from "
|
|
133
|
-
import type { PrismaClientOptions } from "
|
|
132
|
+
import { createMariaDbAdapter } from "jcc-express-mvc/lib/Database/Drivers/Prisma/adapters/mariadb";
|
|
133
|
+
import type { PrismaClientOptions } from "jcc-express-mvc/lib/Database/Drivers/Prisma/types";
|
|
134
134
|
|
|
135
135
|
export class PrismaService extends PrismaClient {
|
|
136
136
|
constructor(options?: PrismaClientOptions) {
|
|
@@ -160,7 +160,8 @@ PRISMA_ADAPTER=postgres
|
|
|
160
160
|
**3. `database.prisma.adapter` in app config:**
|
|
161
161
|
|
|
162
162
|
```typescript
|
|
163
|
-
import { createPostgresAdapter } from "jcc-express-mvc/lib/Database/Drivers/Prisma/adapters/
|
|
163
|
+
import { createPostgresAdapter } from "jcc-express-mvc/lib/Database/Drivers/Prisma/adapters/
|
|
164
|
+
postgres";
|
|
164
165
|
|
|
165
166
|
export const database = {
|
|
166
167
|
prisma: {
|
|
@@ -188,17 +189,16 @@ prisma: {
|
|
|
188
189
|
|
|
189
190
|
## Framework adapter helpers
|
|
190
191
|
|
|
192
|
+
Built-in adapter factories:
|
|
193
|
+
|
|
191
194
|
| Export | Use |
|
|
192
195
|
| --------------------------- | --------------------------------------------------- |
|
|
193
|
-
| `resolvePrismaAdapter(app)` | Used by `PrismaServiceProvider` |
|
|
194
196
|
| `createPrismaAdapter(name)` | Build adapter for a named driver (`PRISMA_ADAPTER`) |
|
|
195
197
|
| `createMariaDbAdapter()` | MySQL / MariaDB |
|
|
196
198
|
| `createPostgresAdapter()` | PostgreSQL |
|
|
197
199
|
| `createSqliteAdapter()` | SQLite file |
|
|
198
200
|
| `createLibSqlAdapter()` | LibSQL / Turso |
|
|
199
201
|
|
|
200
|
-
Import from `jcc-express-mvc/lib/Database` or `jcc-express-mvc/lib/Database/Drivers/Prisma/connection`.
|
|
201
|
-
|
|
202
202
|
The framework registers `PrismaService` as a **singleton** and aliases `prisma` and `database.connection`.
|
|
203
203
|
|
|
204
204
|
---
|
|
@@ -262,9 +262,12 @@ Equivalent npm scripts: `prisma:generate`, `prisma:migrate`, `prisma:studio`.
|
|
|
262
262
|
| `PrismaServiceProvider` | `jcc-express-mvc/lib/Database/PrismaServiceProvider.ts` |
|
|
263
263
|
| `Database` resolver | `jcc-express-mvc/lib/Database/Database.ts` (`DB_ORM=prisma`) |
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
When `DB_ORM=prisma` (or when `database.prisma.service` is set in `app/Config/database.ts`):
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
- Your `PrismaService` class is registered as a **singleton**.
|
|
268
|
+
- The client is available via constructor injection, `app.resolve("prisma")`, or the global `prisma()` helper.
|
|
269
|
+
- Knex / Sequelize / Mongoose setup is skipped so Prisma owns the database connection.
|
|
270
|
+
- The client disconnects gracefully on shutdown.
|
|
268
271
|
|
|
269
272
|
---
|
|
270
273
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
JCC Express MVC provides a fluent query builder through `DB.table(...)` and `DB.query()
|
|
5
|
+
JCC Express MVC provides a fluent query builder through `DB.table(...)` and `DB.query()`.
|
|
6
6
|
|
|
7
7
|
Use it for composable SQL queries without writing full raw SQL for every operation.
|
|
8
8
|
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
JCC Express MVC supports Sequelize as an alternative ORM
|
|
5
|
+
JCC Express MVC supports [Sequelize](https://sequelize.org/) as an alternative SQL ORM when `DB_ORM=sequelize`. Models live in `app/Models/` and use Sequelize's class + `Model.init(...)` pattern — not JCC Eloquent's `Model` from `jcc-express-mvc/Eloquent`.
|
|
6
6
|
|
|
7
|
-
When enabled, the framework
|
|
7
|
+
When enabled, the framework registers the connection as:
|
|
8
8
|
|
|
9
9
|
- `database.connection`
|
|
10
10
|
- `sequelize`
|
|
11
11
|
|
|
12
|
+
Resolve it with `app.resolve("sequelize")` or the global `app` helper during bootstrap.
|
|
13
|
+
|
|
12
14
|
---
|
|
13
15
|
|
|
14
16
|
## Enable Sequelize
|
|
15
17
|
|
|
16
|
-
Set env/config to use Sequelize:
|
|
17
|
-
|
|
18
18
|
```env
|
|
19
19
|
DB_ORM=sequelize
|
|
20
20
|
DB_CONNECTION=mysql
|
|
@@ -25,7 +25,7 @@ DB_USERNAME=root
|
|
|
25
25
|
DB_PASSWORD=secret
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
`app/Config/database.ts`
|
|
28
|
+
`app/Config/database.ts` includes the expected `sequelize` block:
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
31
|
sequelize: {
|
|
@@ -38,49 +38,111 @@ sequelize: {
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
`sync.alter` updates tables to match your models in development. Keep `sync.force` and `dropTables` disabled in production unless you intentionally reset schema.
|
|
42
|
+
|
|
41
43
|
---
|
|
42
44
|
|
|
43
45
|
## Required packages
|
|
44
46
|
|
|
45
|
-
Install Sequelize and a SQL driver package:
|
|
46
|
-
|
|
47
47
|
```bash
|
|
48
48
|
npm install sequelize mysql2
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
For PostgreSQL or SQLite, install the matching dialect
|
|
51
|
+
For PostgreSQL or SQLite, install the matching dialect driver (`pg`, `better-sqlite3`, etc.).
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
## Generate models
|
|
55
|
+
## Generate models
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
With `DB_ORM=sequelize`, ArtisanNode scaffolds Sequelize models:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
60
|
bun artisanNode make:model User
|
|
61
|
+
bun artisanNode make:model Post
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
Files are created in `app/Models/`. On connect, the framework loads every `.ts` / `.js` file in that folder, registers models, and runs `static associate` on each class that defines it.
|
|
64
65
|
|
|
65
66
|
---
|
|
66
67
|
|
|
67
|
-
##
|
|
68
|
+
## Model file structure
|
|
69
|
+
|
|
70
|
+
Each Sequelize model file should:
|
|
71
|
+
|
|
72
|
+
1. Import `Model`, `DataTypes`, and `Sequelize` from the **`sequelize`** package.
|
|
73
|
+
2. Resolve the shared connection with `app.resolve<Sequelize>("sequelize")`.
|
|
74
|
+
3. Extend Sequelize's `Model` class and `declare` typed attributes.
|
|
75
|
+
4. Define `static associate(models)` for relationships (optional).
|
|
76
|
+
5. Call `ModelName.init(attributes, options)` at the bottom of the file.
|
|
77
|
+
6. **Export a class whose name matches the filename** (e.g. `Test.ts` exports `class Test`) so autoloading can register it.
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
Example (`app/Models/Test.ts`):
|
|
70
80
|
|
|
71
81
|
```typescript
|
|
72
|
-
import { DataTypes,
|
|
82
|
+
import { Model, DataTypes, Sequelize } from "sequelize";
|
|
73
83
|
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
const sequelize = app.resolve<Sequelize>("sequelize");
|
|
85
|
+
|
|
86
|
+
export class Test extends Model {
|
|
87
|
+
declare id: number;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Define relationships between models
|
|
91
|
+
* @param models - Record of all loaded model classes
|
|
92
|
+
*/
|
|
93
|
+
static associate(models: any) {
|
|
94
|
+
//
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Test.init(
|
|
99
|
+
{
|
|
100
|
+
id: {
|
|
101
|
+
type: DataTypes.INTEGER,
|
|
102
|
+
primaryKey: true,
|
|
103
|
+
autoIncrement: true,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
sequelize,
|
|
108
|
+
tableName: "tests",
|
|
109
|
+
timestamps: true,
|
|
110
|
+
modelName: "Test",
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `Model.init` options
|
|
116
|
+
|
|
117
|
+
| Option | Purpose |
|
|
118
|
+
| ------------ | ---------------------------------------------------------- |
|
|
119
|
+
| `sequelize` | Shared connection from `app.resolve("sequelize")` |
|
|
120
|
+
| `tableName` | Database table name |
|
|
121
|
+
| `modelName` | Sequelize model name (used for route model binding suffix) |
|
|
122
|
+
| `timestamps` | When `true`, Sequelize manages `createdAt` / `updatedAt` |
|
|
123
|
+
|
|
124
|
+
Add columns in the first argument to `init` using `DataTypes` (`STRING`, `TEXT`, `BOOLEAN`, `DATE`, `JSON`, etc.).
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Full example: User model
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { Model, DataTypes, Sequelize } from "sequelize";
|
|
132
|
+
|
|
133
|
+
const sequelize = app.resolve<Sequelize>("sequelize");
|
|
76
134
|
|
|
77
135
|
export class User extends Model {
|
|
78
|
-
declare id
|
|
79
|
-
declare name
|
|
80
|
-
declare email
|
|
81
|
-
declare password
|
|
82
|
-
declare createdAt
|
|
83
|
-
declare updatedAt
|
|
136
|
+
declare id: number;
|
|
137
|
+
declare name: string;
|
|
138
|
+
declare email: string;
|
|
139
|
+
declare password: string;
|
|
140
|
+
declare createdAt: Date;
|
|
141
|
+
declare updatedAt: Date;
|
|
142
|
+
|
|
143
|
+
static associate(models: any) {
|
|
144
|
+
User.hasMany(models.Post, { foreignKey: "userId", as: "posts" });
|
|
145
|
+
}
|
|
84
146
|
}
|
|
85
147
|
|
|
86
148
|
User.init(
|
|
@@ -94,54 +156,167 @@ User.init(
|
|
|
94
156
|
type: DataTypes.STRING,
|
|
95
157
|
allowNull: false,
|
|
96
158
|
},
|
|
159
|
+
email: {
|
|
160
|
+
type: DataTypes.STRING,
|
|
161
|
+
allowNull: false,
|
|
162
|
+
unique: true,
|
|
163
|
+
},
|
|
164
|
+
password: {
|
|
165
|
+
type: DataTypes.STRING,
|
|
166
|
+
allowNull: false,
|
|
167
|
+
},
|
|
97
168
|
},
|
|
98
169
|
{
|
|
99
170
|
sequelize,
|
|
100
171
|
tableName: "users",
|
|
101
172
|
timestamps: true,
|
|
102
173
|
modelName: "User",
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Relationships (`associate`)
|
|
181
|
+
|
|
182
|
+
After all models in `app/Models/` are loaded, the framework calls `static associate(models)` on each model. The `models` argument is a map of loaded classes keyed by filename (`User`, `Post`, etc.).
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// app/Models/Post.ts
|
|
186
|
+
import { Model, DataTypes, Sequelize } from "sequelize";
|
|
187
|
+
|
|
188
|
+
const sequelize = app.resolve<Sequelize>("sequelize");
|
|
189
|
+
|
|
190
|
+
export class Post extends Model {
|
|
191
|
+
declare id: number;
|
|
192
|
+
declare title: string;
|
|
193
|
+
declare body: string;
|
|
194
|
+
declare userId: number;
|
|
195
|
+
|
|
196
|
+
static associate(models: any) {
|
|
197
|
+
Post.belongsTo(models.User, { foreignKey: "userId", as: "author" });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Post.init(
|
|
202
|
+
{
|
|
203
|
+
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
|
204
|
+
title: { type: DataTypes.STRING, allowNull: false },
|
|
205
|
+
body: { type: DataTypes.TEXT },
|
|
206
|
+
userId: { type: DataTypes.INTEGER, allowNull: false },
|
|
103
207
|
},
|
|
208
|
+
{
|
|
209
|
+
sequelize,
|
|
210
|
+
tableName: "posts",
|
|
211
|
+
timestamps: true,
|
|
212
|
+
modelName: "Post",
|
|
213
|
+
}
|
|
104
214
|
);
|
|
105
215
|
```
|
|
106
216
|
|
|
217
|
+
Models are loaded in **filename sort order**. Define foreign keys on child models and wire `associate` after both sides exist — parent models that appear earlier in the alphabet are available when `associate` runs on child models.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Using models in controllers
|
|
222
|
+
|
|
223
|
+
Import the model class from `app/Models/`:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { User } from "@/Models/User";
|
|
227
|
+
|
|
228
|
+
// List
|
|
229
|
+
const users = await User.findAll();
|
|
230
|
+
|
|
231
|
+
// Find by primary key
|
|
232
|
+
const user = await User.findByPk(1);
|
|
233
|
+
|
|
234
|
+
// Create
|
|
235
|
+
const created = await User.create({
|
|
236
|
+
name: "Jane",
|
|
237
|
+
email: "jane@example.com",
|
|
238
|
+
password: "secret",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Update
|
|
242
|
+
await user.update({ name: "Jane Doe" });
|
|
243
|
+
|
|
244
|
+
// Delete
|
|
245
|
+
await user.destroy();
|
|
246
|
+
|
|
247
|
+
// Eager load
|
|
248
|
+
const withPosts = await User.findByPk(1, { include: ["posts"] });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Use standard Sequelize query APIs on the model class and instances.
|
|
252
|
+
|
|
107
253
|
---
|
|
108
254
|
|
|
109
|
-
##
|
|
255
|
+
## Route model binding
|
|
110
256
|
|
|
111
|
-
|
|
257
|
+
When `DB_ORM=sequelize`, implicit binding resolves models with `findByPk` from the route parameter (e.g. `{user}` → `User.findByPk(id)`). See [Routing](../The%20Basics/Routing.md#route-model-binding).
|
|
112
258
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
259
|
+
```typescript
|
|
260
|
+
Route.get("/users/{user}", [UsersController, "show"]);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { Inject, Method } from "jcc-express-mvc/Core/Dependency";
|
|
265
|
+
import { User } from "@/Models/User";
|
|
266
|
+
|
|
267
|
+
@Inject()
|
|
268
|
+
export class UsersController {
|
|
269
|
+
@Method({ params: ["user"] })
|
|
270
|
+
async show(user: User) {
|
|
271
|
+
return user;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Column binding (`{email$user}`) uses Sequelize `findOne({ where: { email } })`.
|
|
118
277
|
|
|
119
278
|
---
|
|
120
279
|
|
|
121
280
|
## Access the Sequelize connection
|
|
122
281
|
|
|
123
|
-
|
|
282
|
+
For raw queries or transactions outside model classes:
|
|
124
283
|
|
|
125
284
|
```typescript
|
|
126
285
|
const sequelize = app.resolve("sequelize");
|
|
127
|
-
```
|
|
128
286
|
|
|
129
|
-
|
|
287
|
+
await sequelize.transaction(async (t) => {
|
|
288
|
+
// ...
|
|
289
|
+
});
|
|
290
|
+
```
|
|
130
291
|
|
|
131
292
|
---
|
|
132
293
|
|
|
133
|
-
##
|
|
294
|
+
## What happens on connect
|
|
134
295
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
296
|
+
When `DB_ORM=sequelize`, the framework:
|
|
297
|
+
|
|
298
|
+
1. Creates the Sequelize instance from `app/Config/database.ts`.
|
|
299
|
+
2. Authenticates against the database.
|
|
300
|
+
3. Requires every model file in `app/Models/` and collects exported classes.
|
|
301
|
+
4. Calls `associate(models)` on each model that defines it.
|
|
302
|
+
5. Runs `sequelize.sync(...)` using your `sync` config.
|
|
138
303
|
|
|
139
304
|
---
|
|
140
305
|
|
|
141
306
|
## Summary
|
|
142
307
|
|
|
143
|
-
- Set `DB_ORM=sequelize
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
308
|
+
- Set `DB_ORM=sequelize` and configure `database.sequelize` in app config.
|
|
309
|
+
- Models live in `app/Models/` — use `sequelize`'s `Model` + `Model.init`, not JCC Eloquent.
|
|
310
|
+
- Resolve the connection with `app.resolve<Sequelize>("sequelize")` in each model file.
|
|
311
|
+
- Define relationships in `static associate(models)`.
|
|
312
|
+
- Generate scaffolds with `bun artisanNode make:model <Name>`.
|
|
313
|
+
- Query with Sequelize APIs (`findAll`, `findByPk`, `create`, etc.).
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Related
|
|
318
|
+
|
|
319
|
+
- [Database Introduction](./Database-Introduction.md)
|
|
320
|
+
- [Configuration](../Getting-Started/Configuration.md)
|
|
321
|
+
- [Routing](../The%20Basics/Routing.md)
|
|
322
|
+
- [Artisan Node](../The%20Basics/Artisan-Node.md)
|
|
@@ -14,7 +14,7 @@ JCC Express MVC supports transactions through:
|
|
|
14
14
|
## Basic transaction usage
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
|
-
import { DB } from "jcc-express-mvc/
|
|
17
|
+
import { DB } from "jcc-express-mvc/Eloquent";
|
|
18
18
|
|
|
19
19
|
await DB.transaction(async () => {
|
|
20
20
|
await DB.table("wallets").where("id", 1).decrement("balance", 100);
|