@orchestr-sh/orchestr 1.5.10 → 1.6.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 (129) hide show
  1. package/README.md +433 -936
  2. package/dist/Console/Command.d.ts +59 -0
  3. package/dist/Console/Command.d.ts.map +1 -0
  4. package/dist/Console/Command.js +68 -0
  5. package/dist/Console/Command.js.map +1 -0
  6. package/dist/Console/Commands/MakeMigrationCommand.d.ts +15 -0
  7. package/dist/Console/Commands/MakeMigrationCommand.d.ts.map +1 -0
  8. package/dist/Console/Commands/MakeMigrationCommand.js +41 -0
  9. package/dist/Console/Commands/MakeMigrationCommand.js.map +1 -0
  10. package/dist/Console/Commands/MakeSeederCommand.d.ts +23 -0
  11. package/dist/Console/Commands/MakeSeederCommand.d.ts.map +1 -0
  12. package/dist/Console/Commands/MakeSeederCommand.js +109 -0
  13. package/dist/Console/Commands/MakeSeederCommand.js.map +1 -0
  14. package/dist/Console/Commands/MigrateCommand.d.ts +15 -0
  15. package/dist/Console/Commands/MigrateCommand.d.ts.map +1 -0
  16. package/dist/Console/Commands/MigrateCommand.js +48 -0
  17. package/dist/Console/Commands/MigrateCommand.js.map +1 -0
  18. package/dist/Console/Commands/MigrateFreshCommand.d.ts +15 -0
  19. package/dist/Console/Commands/MigrateFreshCommand.d.ts.map +1 -0
  20. package/dist/Console/Commands/MigrateFreshCommand.js +54 -0
  21. package/dist/Console/Commands/MigrateFreshCommand.js.map +1 -0
  22. package/dist/Console/Commands/MigrateRefreshCommand.d.ts +15 -0
  23. package/dist/Console/Commands/MigrateRefreshCommand.d.ts.map +1 -0
  24. package/dist/Console/Commands/MigrateRefreshCommand.js +52 -0
  25. package/dist/Console/Commands/MigrateRefreshCommand.js.map +1 -0
  26. package/dist/Console/Commands/MigrateResetCommand.d.ts +15 -0
  27. package/dist/Console/Commands/MigrateResetCommand.d.ts.map +1 -0
  28. package/dist/Console/Commands/MigrateResetCommand.js +46 -0
  29. package/dist/Console/Commands/MigrateResetCommand.js.map +1 -0
  30. package/dist/Console/Commands/MigrateRollbackCommand.d.ts +15 -0
  31. package/dist/Console/Commands/MigrateRollbackCommand.d.ts.map +1 -0
  32. package/dist/Console/Commands/MigrateRollbackCommand.js +49 -0
  33. package/dist/Console/Commands/MigrateRollbackCommand.js.map +1 -0
  34. package/dist/Console/Commands/MigrateStatusCommand.d.ts +15 -0
  35. package/dist/Console/Commands/MigrateStatusCommand.d.ts.map +1 -0
  36. package/dist/Console/Commands/MigrateStatusCommand.js +58 -0
  37. package/dist/Console/Commands/MigrateStatusCommand.js.map +1 -0
  38. package/dist/Console/Commands/SeedCommand.d.ts +15 -0
  39. package/dist/Console/Commands/SeedCommand.d.ts.map +1 -0
  40. package/dist/Console/Commands/SeedCommand.js +44 -0
  41. package/dist/Console/Commands/SeedCommand.js.map +1 -0
  42. package/dist/Console/ConsoleKernel.d.ts +45 -0
  43. package/dist/Console/ConsoleKernel.d.ts.map +1 -0
  44. package/dist/Console/ConsoleKernel.js +109 -0
  45. package/dist/Console/ConsoleKernel.js.map +1 -0
  46. package/dist/Console/orchestr.d.ts +8 -0
  47. package/dist/Console/orchestr.d.ts.map +1 -0
  48. package/dist/Console/orchestr.js +45 -0
  49. package/dist/Console/orchestr.js.map +1 -0
  50. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.d.ts +5 -0
  51. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.d.ts.map +1 -1
  52. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.js.map +1 -1
  53. package/dist/Database/Ensemble/Concerns/HasRelationships.d.ts +20 -0
  54. package/dist/Database/Ensemble/Concerns/HasRelationships.d.ts.map +1 -1
  55. package/dist/Database/Ensemble/Concerns/HasRelationships.js +57 -0
  56. package/dist/Database/Ensemble/Concerns/HasRelationships.js.map +1 -1
  57. package/dist/Database/Ensemble/Relations/MorphMany.d.ts +97 -0
  58. package/dist/Database/Ensemble/Relations/MorphMany.d.ts.map +1 -0
  59. package/dist/Database/Ensemble/Relations/MorphMany.js +189 -0
  60. package/dist/Database/Ensemble/Relations/MorphMany.js.map +1 -0
  61. package/dist/Database/Ensemble/Relations/MorphMap.d.ts +59 -0
  62. package/dist/Database/Ensemble/Relations/MorphMap.d.ts.map +1 -0
  63. package/dist/Database/Ensemble/Relations/MorphMap.js +84 -0
  64. package/dist/Database/Ensemble/Relations/MorphMap.js.map +1 -0
  65. package/dist/Database/Ensemble/Relations/MorphOne.d.ts +93 -0
  66. package/dist/Database/Ensemble/Relations/MorphOne.d.ts.map +1 -0
  67. package/dist/Database/Ensemble/Relations/MorphOne.js +179 -0
  68. package/dist/Database/Ensemble/Relations/MorphOne.js.map +1 -0
  69. package/dist/Database/Ensemble/Relations/MorphTo.d.ts +98 -0
  70. package/dist/Database/Ensemble/Relations/MorphTo.d.ts.map +1 -0
  71. package/dist/Database/Ensemble/Relations/MorphTo.js +214 -0
  72. package/dist/Database/Ensemble/Relations/MorphTo.js.map +1 -0
  73. package/dist/Database/Ensemble/Relations/MorphToMany.d.ts +73 -0
  74. package/dist/Database/Ensemble/Relations/MorphToMany.d.ts.map +1 -0
  75. package/dist/Database/Ensemble/Relations/MorphToMany.js +164 -0
  76. package/dist/Database/Ensemble/Relations/MorphToMany.js.map +1 -0
  77. package/dist/Database/Ensemble/Relations/MorphedByMany.d.ts +29 -0
  78. package/dist/Database/Ensemble/Relations/MorphedByMany.d.ts.map +1 -0
  79. package/dist/Database/Ensemble/Relations/MorphedByMany.js +58 -0
  80. package/dist/Database/Ensemble/Relations/MorphedByMany.js.map +1 -0
  81. package/dist/Database/Ensemble/Relations/index.d.ts +6 -0
  82. package/dist/Database/Ensemble/Relations/index.d.ts.map +1 -1
  83. package/dist/Database/Ensemble/Relations/index.js +14 -1
  84. package/dist/Database/Ensemble/Relations/index.js.map +1 -1
  85. package/dist/Database/Migrations/Blueprint.d.ts +166 -0
  86. package/dist/Database/Migrations/Blueprint.d.ts.map +1 -0
  87. package/dist/Database/Migrations/Blueprint.js +318 -0
  88. package/dist/Database/Migrations/Blueprint.js.map +1 -0
  89. package/dist/Database/Migrations/ColumnDefinition.d.ts +62 -0
  90. package/dist/Database/Migrations/ColumnDefinition.d.ts.map +1 -0
  91. package/dist/Database/Migrations/ColumnDefinition.js +95 -0
  92. package/dist/Database/Migrations/ColumnDefinition.js.map +1 -0
  93. package/dist/Database/Migrations/ForeignKeyDefinition.d.ts +32 -0
  94. package/dist/Database/Migrations/ForeignKeyDefinition.d.ts.map +1 -0
  95. package/dist/Database/Migrations/ForeignKeyDefinition.js +50 -0
  96. package/dist/Database/Migrations/ForeignKeyDefinition.js.map +1 -0
  97. package/dist/Database/Migrations/Migration.d.ts +17 -0
  98. package/dist/Database/Migrations/Migration.d.ts.map +1 -0
  99. package/dist/Database/Migrations/Migration.js +12 -0
  100. package/dist/Database/Migrations/Migration.js.map +1 -0
  101. package/dist/Database/Migrations/MigrationCreator.d.ts +42 -0
  102. package/dist/Database/Migrations/MigrationCreator.d.ts.map +1 -0
  103. package/dist/Database/Migrations/MigrationCreator.js +184 -0
  104. package/dist/Database/Migrations/MigrationCreator.js.map +1 -0
  105. package/dist/Database/Migrations/MigrationRepository.d.ts +65 -0
  106. package/dist/Database/Migrations/MigrationRepository.d.ts.map +1 -0
  107. package/dist/Database/Migrations/MigrationRepository.js +135 -0
  108. package/dist/Database/Migrations/MigrationRepository.js.map +1 -0
  109. package/dist/Database/Migrations/Migrator.d.ts +84 -0
  110. package/dist/Database/Migrations/Migrator.d.ts.map +1 -0
  111. package/dist/Database/Migrations/Migrator.js +239 -0
  112. package/dist/Database/Migrations/Migrator.js.map +1 -0
  113. package/dist/Database/Migrations/SchemaBuilder.d.ts +136 -0
  114. package/dist/Database/Migrations/SchemaBuilder.d.ts.map +1 -0
  115. package/dist/Database/Migrations/SchemaBuilder.js +515 -0
  116. package/dist/Database/Migrations/SchemaBuilder.js.map +1 -0
  117. package/dist/Database/Seeders/Seeder.d.ts +29 -0
  118. package/dist/Database/Seeders/Seeder.d.ts.map +1 -0
  119. package/dist/Database/Seeders/Seeder.js +41 -0
  120. package/dist/Database/Seeders/Seeder.js.map +1 -0
  121. package/dist/Database/Seeders/SeederRunner.d.ts +41 -0
  122. package/dist/Database/Seeders/SeederRunner.d.ts.map +1 -0
  123. package/dist/Database/Seeders/SeederRunner.js +134 -0
  124. package/dist/Database/Seeders/SeederRunner.js.map +1 -0
  125. package/dist/index.d.ts +27 -2
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +58 -1
  128. package/dist/index.js.map +1 -1
  129. package/package.json +4 -1
package/README.md CHANGED
@@ -1,1127 +1,624 @@
1
1
  # Orchestr
2
2
 
3
- A 1:1 Laravel replica built in TypeScript. Brings Laravel's elegant syntax and architecture to the TypeScript/Node.js ecosystem.
4
-
5
- ## Features
6
-
7
- Built from the ground up with Laravel's core components:
8
-
9
- - **Service Container** - Full IoC container with dependency injection and reflection
10
- - **Service Providers** - Bootstrap and register services
11
- - **Configuration** - Dot-notation config repository with runtime access
12
- - **HTTP Router** - Laravel-style routing with parameter binding and file-based route loading
13
- - **Request/Response** - Elegant HTTP abstractions
14
- - **Middleware** - Global and route-level middleware pipeline
15
- - **Controllers** - MVC architecture support
16
- - **FormRequest** - Laravel-style validation and authorization
17
- - **Facades** - Static proxy access to services (Route, DB)
18
- - **Query Builder** - Fluent database query builder with full Laravel API
19
- - **Ensemble ORM** - ActiveRecord ORM (Laravel's Eloquent equivalent) with relationships (HasOne, HasMany, BelongsTo, BelongsToMany), eager/lazy loading, soft deletes, and more
20
- - **Database Manager** - Multi-connection database management
21
- - **Application Lifecycle** - Complete Laravel bootstrap process
3
+ A Laravel-inspired ORM and framework for TypeScript. Write elegant backend applications with ActiveRecord models (called Ensembles), relationships, query building, and more.
22
4
 
23
5
  ## Installation
24
6
 
25
7
  ```bash
26
- npm install @orchestr-sh/orchestr reflect-metadata
8
+ npm install @orchestr-sh/orchestr reflect-metadata drizzle-orm
9
+ npm install better-sqlite3 # or your preferred database driver
27
10
  ```
28
11
 
29
- **Note**: `reflect-metadata` is required for dependency injection to work.
30
-
31
12
  ## Quick Start
32
13
 
33
14
  ```typescript
34
- import 'reflect-metadata'; // Must be first!
35
- import { Application, Kernel, RouteServiceProvider, Route } from 'orchestr';
15
+ import 'reflect-metadata';
16
+ import { Application, Kernel, ConfigServiceProvider, Route } from '@orchestr-sh/orchestr';
36
17
 
37
- // Create application
38
- const app = new Application(__dirname);
18
+ const app = new Application(process.cwd());
39
19
 
40
- // Register providers
41
- app.register(RouteServiceProvider);
42
- await app.boot();
20
+ // Configure database
21
+ app.register(new ConfigServiceProvider(app, {
22
+ database: {
23
+ default: 'sqlite',
24
+ connections: {
25
+ sqlite: {
26
+ adapter: 'drizzle',
27
+ driver: 'sqlite',
28
+ database: './database.db',
29
+ },
30
+ },
31
+ },
32
+ }));
43
33
 
44
- // Create HTTP kernel
45
- const kernel = new Kernel(app);
34
+ await app.boot();
46
35
 
47
36
  // Define routes
48
37
  Route.get('/', async (req, res) => {
49
- return res.json({ message: 'Hello from Orchestr!' });
50
- });
51
-
52
- Route.get('/users/:id', async (req, res) => {
53
- const id = req.routeParam('id');
54
- return res.json({ user: { id, name: 'John Doe' } });
55
- });
56
-
57
- Route.post('/users', async (req, res) => {
58
- const data = req.only(['name', 'email']);
59
- return res.status(201).json({ user: data });
38
+ return res.json({ message: 'Welcome to Orchestr!' });
60
39
  });
61
40
 
62
41
  // Start server
42
+ const kernel = new Kernel(app);
63
43
  kernel.listen(3000);
64
44
  ```
65
45
 
66
- ## Core Concepts
67
-
68
- ### Configuration
46
+ ## Models (Ensembles)
69
47
 
70
- Orchestr provides a Laravel-style configuration system with dot-notation access:
48
+ Ensembles are ActiveRecord models with a fluent API for querying and relationships.
71
49
 
72
50
  ```typescript
73
- import { Application, ConfigServiceProvider, Config, config } from 'orchestr';
51
+ import { Ensemble } from '@orchestr-sh/orchestr';
74
52
 
75
- const app = new Application(__dirname);
76
-
77
- // Register configuration
78
- app.register(new ConfigServiceProvider(app, {
79
- app: {
80
- name: 'My Application',
81
- env: 'production',
82
- debug: false,
83
- url: 'http://localhost:3000',
84
- },
85
- database: {
86
- default: 'mysql',
87
- connections: {
88
- mysql: {
89
- host: 'localhost',
90
- port: 3306,
91
- database: 'mydb',
92
- }
93
- }
94
- }
95
- }));
96
-
97
- await app.boot();
98
-
99
- // Access via container
100
- const configInstance = app.make('config');
101
- const appName = configInstance.get('app.name'); // 'My Application'
102
- const dbHost = configInstance.get('database.connections.mysql.host'); // 'localhost'
103
-
104
- // Access via facade
105
- const name = Config.get('app.name');
106
- const debug = Config.get('app.debug', false); // with default
53
+ export class User extends Ensemble {
54
+ protected table = 'users';
55
+ protected fillable = ['name', 'email', 'password'];
56
+ protected hidden = ['password'];
57
+ }
107
58
 
108
- // Access via helper function
109
- const env = config('app.env');
110
- const url = config('app.url', 'http://default.com');
59
+ // Query
60
+ const users = await User.query().where('active', true).get();
61
+ const user = await User.find(1);
111
62
 
112
- // Set configuration values
113
- Config.set('app.debug', true);
114
- Config.set({
115
- 'app.timezone': 'UTC',
116
- 'app.locale': 'en'
117
- });
63
+ // Create
64
+ const user = await User.create({ name: 'John', email: 'john@example.com' });
118
65
 
119
- // Check if config exists
120
- if (Config.has('database.connections.mysql')) {
121
- // ...
122
- }
66
+ // Update
67
+ user.name = 'Jane';
68
+ await user.save();
123
69
 
124
- // Array operations
125
- Config.push('app.providers', 'NewServiceProvider');
126
- Config.prepend('app.middleware', 'LoggingMiddleware');
70
+ // Delete
71
+ await user.delete();
127
72
  ```
128
73
 
129
- ### Service Container
74
+ ## Querying
130
75
 
131
- Laravel's IoC container provides powerful dependency injection:
76
+ Fluent query builder with chainable methods.
132
77
 
133
78
  ```typescript
134
- // Bind a service
135
- app.bind('UserService', () => new UserService());
79
+ import { DB } from '@orchestr-sh/orchestr';
80
+
81
+ // Query builder
82
+ const users = await DB.table('users')
83
+ .where('votes', '>', 100)
84
+ .orderBy('created_at', 'desc')
85
+ .limit(10)
86
+ .get();
136
87
 
137
- // Bind a singleton
138
- app.singleton('Database', () => new Database());
88
+ // Using models
89
+ const posts = await Post.query()
90
+ .where('published', true)
91
+ .with('author')
92
+ .get();
139
93
 
140
- // Resolve from container
141
- const userService = app.make('UserService');
94
+ // Aggregates
95
+ const count = await Post.query().count();
96
+ const avg = await Post.query().avg('views');
142
97
  ```
143
98
 
144
- ### Dependency Injection
99
+ ## Relationships
145
100
 
146
- Orchestr supports automatic constructor-based dependency injection using TypeScript's reflection:
101
+ ### Standard Relationships
147
102
 
148
103
  ```typescript
149
- import { Injectable, Controller, Request, Response } from 'orchestr';
104
+ import { Ensemble, HasMany, BelongsTo, DynamicRelation } from '@orchestr-sh/orchestr';
105
+
106
+ export class User extends Ensemble {
107
+ protected table = 'users';
150
108
 
151
- // Define a service
152
- export class UserService {
153
- getUsers() {
154
- return [{ id: 1, name: 'John' }];
109
+ @DynamicRelation
110
+ posts(): HasMany<Post, User> {
111
+ return this.hasMany(Post);
155
112
  }
156
113
  }
157
114
 
158
- // Use @Injectable() decorator to enable DI
159
- @Injectable()
160
- export class UserController extends Controller {
161
- // Dependencies are automatically injected
162
- constructor(private userService: UserService) {
163
- super();
164
- }
115
+ export class Post extends Ensemble {
116
+ protected table = 'posts';
165
117
 
166
- async index(req: Request, res: Response) {
167
- const users = this.userService.getUsers();
168
- return res.json({ users });
118
+ @DynamicRelation
119
+ user(): BelongsTo<User, this> {
120
+ return this.belongsTo(User);
169
121
  }
170
122
  }
171
123
 
172
- // Register the service in a provider
173
- class AppServiceProvider extends ServiceProvider {
174
- register(): void {
175
- // Bind the service to the container
176
- this.app.singleton(UserService, () => new UserService());
177
- }
178
- }
179
- ```
124
+ // Use relationships
125
+ const user = await User.find(1);
126
+ const posts = await user.posts().get(); // Query builder
127
+ const posts = await user.posts; // Direct access (via @DynamicRelation)
180
128
 
181
- **Important**: The `@Injectable()` decorator is required for dependency injection to work. It triggers TypeScript to emit metadata about constructor parameters.
129
+ // Eager loading
130
+ const users = await User.query().with('posts').get();
182
131
 
183
- ### Service Providers
132
+ // Nested eager loading
133
+ const posts = await Post.query().with('user.posts').get();
134
+ ```
184
135
 
185
- Organize service registration and bootstrapping:
136
+ ### Many-to-Many
186
137
 
187
138
  ```typescript
188
- class AppServiceProvider extends ServiceProvider {
189
- register(): void {
190
- this.app.singleton('config', () => ({ /* config */ }));
191
- }
139
+ import { Ensemble, BelongsToMany, DynamicRelation } from '@orchestr-sh/orchestr';
192
140
 
193
- boot(): void {
194
- // Bootstrap code
141
+ export class User extends Ensemble {
142
+ @DynamicRelation
143
+ roles(): BelongsToMany<Role, User> {
144
+ return this.belongsToMany(Role, 'role_user')
145
+ .withPivot('expires_at')
146
+ .withTimestamps();
195
147
  }
196
148
  }
197
149
 
198
- app.register(AppServiceProvider);
199
- ```
200
-
201
- ### Routing
202
-
203
- Laravel-style routing with full parameter support:
204
-
205
- ```typescript
206
- // Simple routes
207
- Route.get('/users', handler);
208
- Route.post('/users', handler);
209
- Route.put('/users/:id', handler);
210
- Route.delete('/users/:id', handler);
211
-
212
- // Route parameters
213
- Route.get('/users/:id/posts/:postId', async (req, res) => {
214
- const userId = req.routeParam('id');
215
- const postId = req.routeParam('postId');
216
- });
217
-
218
- // Route groups
219
- Route.group({ prefix: 'api/v1', middleware: authMiddleware }, () => {
220
- Route.get('/profile', handler);
221
- Route.post('/posts', handler);
222
- });
223
-
224
- // Named routes
225
- const route = Route.get('/users', handler);
226
- route.setName('users.index');
227
- ```
228
-
229
- #### Loading Routes from Files
230
-
231
- Organize your routes in separate files, just like Laravel:
232
-
233
- **routes/web.ts**
234
- ```typescript
235
- import { Route } from 'orchestr';
150
+ // Attach/Detach
151
+ await user.roles().attach([1, 2, 3]);
152
+ await user.roles().detach([1]);
236
153
 
237
- Route.get('/', async (req, res) => {
238
- return res.json({ message: 'Welcome' });
239
- });
154
+ // Sync (detach all, attach new)
155
+ await user.roles().sync([1, 2, 3]);
240
156
 
241
- Route.get('/about', async (req, res) => {
242
- return res.json({ page: 'about' });
243
- });
157
+ // Query pivot
158
+ const activeRoles = await user.roles()
159
+ .wherePivot('expires_at', '>', new Date())
160
+ .get();
244
161
  ```
245
162
 
246
- **routes/api.ts**
247
- ```typescript
248
- import { Route } from 'orchestr';
249
-
250
- Route.group({ prefix: 'api/v1' }, () => {
251
- Route.get('/users', async (req, res) => {
252
- return res.json({ users: [] });
253
- });
254
-
255
- Route.post('/users', async (req, res) => {
256
- return res.status(201).json({ created: true });
257
- });
258
- });
259
- ```
163
+ ### Polymorphic Relationships
260
164
 
261
- **app/Providers/AppRouteServiceProvider.ts**
262
165
  ```typescript
263
- import { RouteServiceProvider } from 'orchestr';
264
-
265
- export class AppRouteServiceProvider extends RouteServiceProvider {
266
- async boot(): Promise<void> {
267
- // Load web routes
268
- this.routes(() => import('../../routes/web'));
166
+ import { Ensemble, MorphMany, MorphTo, DynamicRelation } from '@orchestr-sh/orchestr';
269
167
 
270
- // Load API routes
271
- this.routes(() => import('../../routes/api'));
168
+ export class Post extends Ensemble {
169
+ @DynamicRelation
170
+ comments(): MorphMany<Comment, Post> {
171
+ return this.morphMany(Comment, 'commentable');
172
+ }
173
+ }
272
174
 
273
- await super.boot();
175
+ export class Video extends Ensemble {
176
+ @DynamicRelation
177
+ comments(): MorphMany<Comment, Video> {
178
+ return this.morphMany(Comment, 'commentable');
274
179
  }
275
180
  }
276
- ```
277
181
 
278
- **index.ts**
279
- ```typescript
280
- import 'reflect-metadata';
281
- import { Application, Kernel } from 'orchestr';
282
- import { AppRouteServiceProvider } from './app/Providers/AppRouteServiceProvider';
182
+ export class Comment extends Ensemble {
183
+ @DynamicRelation
184
+ commentable(): MorphTo<Post | Video> {
185
+ return this.morphTo('commentable');
186
+ }
187
+ }
283
188
 
284
- const app = new Application(__dirname);
285
- app.register(AppRouteServiceProvider);
286
- await app.boot();
189
+ // Use polymorphic relations
190
+ const post = await Post.find(1);
191
+ const comments = await post.comments;
287
192
 
288
- const kernel = new Kernel(app);
289
- kernel.listen(3000);
193
+ const comment = await Comment.find(1);
194
+ const parent = await comment.commentable; // Returns Post or Video
290
195
  ```
291
196
 
292
- ### Middleware
197
+ ## @DynamicRelation Decorator
293
198
 
294
- Global and route-level middleware:
199
+ The `@DynamicRelation` decorator enables dual-mode access to relationships:
295
200
 
296
201
  ```typescript
297
- // Global middleware
298
- kernel.use(async (req, res, next) => {
299
- console.log(`${req.method} ${req.path}`);
300
- await next();
301
- });
302
-
303
- // Route middleware
304
- const authMiddleware = async (req, res, next) => {
305
- if (!req.header('authorization')) {
306
- return res.status(401).json({ message: 'Unauthorized' });
202
+ export class User extends Ensemble {
203
+ @DynamicRelation
204
+ posts(): HasMany<Post, User> {
205
+ return this.hasMany(Post);
307
206
  }
308
- await next();
309
- };
207
+ }
310
208
 
311
- Route.get('/profile', handler).addMiddleware(authMiddleware);
209
+ const user = await User.find(1);
210
+
211
+ // Method syntax (returns query builder)
212
+ const query = user.posts();
213
+ const recentPosts = await query.where('created_at', '>', yesterday).get();
214
+
215
+ // Property syntax (returns results directly)
216
+ const allPosts = await user.posts;
312
217
  ```
313
218
 
314
- ### FormRequest Validation
219
+ Without `@DynamicRelation`, you must always call the method: `user.posts().get()`.
315
220
 
316
- Laravel-style FormRequest for clean validation and authorization:
221
+ ## Migrations
317
222
 
318
- ```typescript
319
- import { FormRequest, ValidationRules, ValidationException, ValidateRequest, Route, Request, Response } from 'orchestr';
223
+ Laravel-style migrations with a fluent Schema builder.
320
224
 
321
- // Create a FormRequest class
322
- export class StoreUserRequest extends FormRequest {
323
- // Authorize the request
324
- protected authorize(): boolean {
325
- return this.request.header('authorization') !== undefined;
326
- }
225
+ ```bash
226
+ # Create a migration
227
+ orchestr make:migration create_users_table --create=users
327
228
 
328
- // Define validation rules
329
- protected rules(): ValidationRules {
330
- return {
331
- name: 'required|string|min:3|max:255',
332
- email: 'required|email',
333
- password: 'required|string|min:8|confirmed',
334
- age: 'numeric|min:18|max:120',
335
- role: 'required|in:user,admin,moderator',
336
- };
337
- }
229
+ # Run migrations
230
+ orchestr migrate
338
231
 
339
- // Custom error messages
340
- protected messages(): Record<string, string> {
341
- return {
342
- 'name.required': 'Please provide your full name.',
343
- 'email.email': 'Please provide a valid email address.',
344
- 'password.min': 'Password must be at least 8 characters.',
345
- };
346
- }
347
- }
232
+ # Rollback last batch
233
+ orchestr migrate:rollback
348
234
 
349
- // Use in controllers with auto-validation
350
- @Injectable()
351
- class UserController extends Controller {
352
- @ValidateRequest() // Enables automatic validation
353
- async store(request: StoreUserRequest, res: Response) {
354
- // Request is already validated! Get safe data
355
- const validated = request.validated();
235
+ # Rollback all migrations
236
+ orchestr migrate:reset
356
237
 
357
- const user = await User.create(validated);
358
- return res.status(201).json({ user });
359
- }
360
- }
238
+ # Drop all tables and re-run migrations
239
+ orchestr migrate:fresh
361
240
 
362
- Route.post('/users', [UserController, 'store']);
241
+ # Rollback and re-run all migrations
242
+ orchestr migrate:refresh
363
243
 
364
- // Or use manually in routes
365
- Route.post('/users', async (req: Request, res: Response) => {
366
- try {
367
- const formRequest = await StoreUserRequest.validate(StoreUserRequest, req, res);
368
- const validated = formRequest.validated();
369
- const user = await User.create(validated);
370
- return res.status(201).json({ user });
371
- } catch (error) {
372
- if (error instanceof ValidationException) return;
373
- throw error;
374
- }
375
- });
244
+ # Check migration status
245
+ orchestr migrate:status
376
246
  ```
377
247
 
378
- ### Controllers
379
-
380
- MVC pattern with base controller and dependency injection:
248
+ ### Creating Tables
381
249
 
382
250
  ```typescript
383
- import { Injectable, Controller, Request, Response, Route } from 'orchestr';
384
-
385
- // Use @Injectable() when injecting dependencies
386
- @Injectable()
387
- class UserController extends Controller {
388
- // Services are automatically injected
389
- constructor(private userService: UserService) {
390
- super();
391
- }
392
-
393
- async index(req: Request, res: Response) {
394
- const users = await this.userService.getAll();
395
- return res.json({ users });
396
- }
397
-
398
- async show(req: Request, res: Response) {
399
- const id = req.routeParam('id');
400
- const user = await this.userService.findById(id);
401
- return res.json({ user });
251
+ import { Migration, Schema } from '@orchestr-sh/orchestr';
252
+
253
+ export default class extends Migration {
254
+ async up(schema: Schema): Promise<void> {
255
+ await schema.create('users', (table) => {
256
+ table.id();
257
+ table.string('name');
258
+ table.string('email').unique();
259
+ table.string('password');
260
+ table.rememberToken();
261
+ table.timestamps();
262
+ });
402
263
  }
403
264
 
404
- async store(req: Request, res: Response) {
405
- const validated = await this.validate(req, {
406
- name: 'required',
407
- email: 'required|email',
408
- });
409
- const user = await this.userService.create(validated);
410
- return res.status(201).json({ user });
265
+ async down(schema: Schema): Promise<void> {
266
+ await schema.dropIfExists('users');
411
267
  }
412
268
  }
413
-
414
- // Register controller routes
415
- Route.get('/users', [UserController, 'index']);
416
- Route.get('/users/:id', [UserController, 'show']);
417
- Route.post('/users', [UserController, 'store']);
418
269
  ```
419
270
 
420
- **Note**: The `@Injectable()` decorator must be used on any class that needs constructor dependency injection. Without it, TypeScript won't emit the metadata needed for automatic resolution.
421
-
422
- ### Request
423
-
424
- Powerful request helper methods:
271
+ ### Column Types
425
272
 
426
273
  ```typescript
427
- // Get input
428
- req.input('name');
429
- req.get('email', 'default@example.com');
430
-
431
- // Get all inputs
432
- req.all();
433
-
434
- // Get specific inputs
435
- req.only(['name', 'email']);
436
- req.except(['password']);
437
-
438
- // Check input existence
439
- req.has('name');
440
- req.filled('email');
441
-
442
- // Route parameters
443
- req.routeParam('id');
274
+ table.id() // Auto-incrementing big integer
275
+ table.string('name', 255) // VARCHAR
276
+ table.text('bio') // TEXT
277
+ table.integer('votes') // INTEGER
278
+ table.bigInteger('amount') // BIGINT
279
+ table.decimal('price', 8, 2) // DECIMAL
280
+ table.boolean('active') // BOOLEAN
281
+ table.date('birth_date') // DATE
282
+ table.datetime('published_at') // DATETIME
283
+ table.timestamp('created_at') // TIMESTAMP
284
+ table.timestamps() // created_at & updated_at
285
+ table.json('metadata') // JSON
286
+ table.uuid('identifier') // UUID
287
+ table.enum('status', ['draft', 'published'])
288
+ ```
444
289
 
445
- // Headers
446
- req.header('content-type');
447
- req.expectsJson();
448
- req.ajax();
290
+ ### Column Modifiers
449
291
 
450
- // Request info
451
- req.method;
452
- req.path;
453
- req.ip();
292
+ ```typescript
293
+ table.string('email').nullable()
294
+ table.string('email').unique()
295
+ table.string('email').default('guest@example.com')
296
+ table.integer('votes').unsigned()
297
+ table.string('email').index()
454
298
  ```
455
299
 
456
- ### Response
457
-
458
- Fluent response building:
300
+ ### Foreign Keys
459
301
 
460
302
  ```typescript
461
- // JSON responses
462
- res.json({ data: [] });
463
- res.status(201).json({ created: true });
303
+ table.bigInteger('user_id').unsigned();
304
+ table.foreign('user_id').references('id').on('users').onDelete('cascade');
305
+ ```
464
306
 
465
- // Headers
466
- res.header('X-Custom', 'value');
467
- res.headers({ 'X-A': 'a', 'X-B': 'b' });
307
+ ## Seeders
468
308
 
469
- // Cookies
470
- res.cookie('token', 'value', { httpOnly: true, maxAge: 3600 });
309
+ Populate your database with test or initial data.
471
310
 
472
- // Redirects
473
- res.redirect('/home');
474
- res.redirect('/login', 301);
311
+ ```bash
312
+ # Create a seeder
313
+ orchestr make:seeder UserSeeder
475
314
 
476
- // Downloads
477
- res.download(buffer, 'file.pdf');
315
+ # Run all seeders (runs DatabaseSeeder)
316
+ orchestr db:seed
478
317
 
479
- // Views (simplified)
480
- res.view('welcome', { name: 'John' });
318
+ # Run a specific seeder
319
+ orchestr db:seed --class=UserSeeder
481
320
  ```
482
321
 
483
- ### Facades
484
-
485
- Static access to services:
322
+ ### Creating Seeders
486
323
 
487
324
  ```typescript
488
- import { Route, DB } from 'orchestr';
325
+ import { Seeder } from '@orchestr-sh/orchestr';
489
326
 
490
- // Route facade provides static access to Router
491
- Route.get('/path', handler);
492
- Route.post('/path', handler);
493
- Route.group({ prefix: 'api' }, () => {
494
- // ...
495
- });
496
-
497
- // DB facade provides static access to DatabaseManager
498
- const users = await DB.table('users').where('active', true).get();
327
+ export default class UserSeeder extends Seeder {
328
+ async run(): Promise<void> {
329
+ await this.connection?.table('users').insert([
330
+ { name: 'John Doe', email: 'john@example.com' },
331
+ { name: 'Jane Smith', email: 'jane@example.com' },
332
+ ]);
333
+ }
334
+ }
499
335
  ```
500
336
 
501
- ### Database Query Builder
502
-
503
- Fluent, chainable query builder with full Laravel API:
337
+ ### DatabaseSeeder Pattern
504
338
 
505
339
  ```typescript
506
- import { DB } from 'orchestr';
507
-
508
- // Basic queries
509
- const users = await DB.table('users').get();
510
- const user = await DB.table('users').where('id', 1).first();
511
-
512
- // Where clauses
513
- await DB.table('users')
514
- .where('votes', '>', 100)
515
- .where('status', 'active')
516
- .get();
517
-
518
- // Or where
519
- await DB.table('users')
520
- .where('votes', '>', 100)
521
- .orWhere('name', 'John')
522
- .get();
523
-
524
- // Additional where methods
525
- await DB.table('users').whereBetween('votes', [1, 100]).get();
526
- await DB.table('users').whereIn('id', [1, 2, 3]).get();
527
- await DB.table('users').whereNull('deleted_at').get();
528
-
529
- // Ordering, grouping, and limits
530
- await DB.table('users')
531
- .orderBy('name', 'desc')
532
- .groupBy('account_id')
533
- .having('account_id', '>', 100)
534
- .limit(10)
535
- .offset(20)
536
- .get();
537
-
538
- // Joins
539
- await DB.table('users')
540
- .join('contacts', 'users.id', '=', 'contacts.user_id')
541
- .leftJoin('orders', 'users.id', '=', 'orders.user_id')
542
- .select('users.*', 'contacts.phone', 'orders.price')
543
- .get();
340
+ import { Seeder } from '@orchestr-sh/orchestr';
341
+ import UserSeeder from './UserSeeder';
342
+ import PostSeeder from './PostSeeder';
544
343
 
545
- // Aggregates
546
- const count = await DB.table('users').count();
547
- const max = await DB.table('orders').max('price');
548
- const min = await DB.table('orders').min('price');
549
- const avg = await DB.table('orders').avg('price');
550
- const sum = await DB.table('orders').sum('price');
551
-
552
- // Inserts
553
- await DB.table('users').insert({
554
- name: 'John',
555
- email: 'john@example.com'
556
- });
344
+ export default class DatabaseSeeder extends Seeder {
345
+ async run(): Promise<void> {
346
+ await this.call(UserSeeder);
347
+ await this.call(PostSeeder);
557
348
 
558
- // Updates
559
- await DB.table('users')
560
- .where('id', 1)
561
- .update({ votes: 1 });
562
-
563
- // Deletes
564
- await DB.table('users').where('votes', '<', 100).delete();
565
-
566
- // Raw expressions
567
- await DB.table('users')
568
- .select(DB.raw('count(*) as user_count, status'))
569
- .where('status', '<>', 1)
570
- .groupBy('status')
571
- .get();
349
+ // Or call multiple seeders at once
350
+ await this.callMany([UserSeeder, PostSeeder]);
351
+ }
352
+ }
572
353
  ```
573
354
 
574
- ### Ensemble ORM
575
-
576
- ActiveRecord ORM (Eloquent equivalent) with relationships and advanced features:
355
+ ## Controllers
577
356
 
578
357
  ```typescript
579
- import { Ensemble, HasOne, HasMany, BelongsTo, BelongsToMany, softDeletes } from 'orchestr';
358
+ import { Controller, Injectable, ValidateRequest } from '@orchestr-sh/orchestr';
580
359
 
581
- // Define models with relationships
582
- class User extends Ensemble {
583
- protected table = 'users';
584
- protected fillable = ['name', 'email', 'password'];
585
- protected hidden = ['password'];
586
- protected casts = {
587
- email_verified_at: 'datetime',
588
- is_admin: 'boolean'
589
- };
590
-
591
- // One-to-One: User has one profile
592
- profile(): HasOne<Profile, User> {
593
- return this.hasOne(Profile);
360
+ @Injectable()
361
+ export class UserController extends Controller {
362
+ constructor(private service: UserService) {
363
+ super();
594
364
  }
595
365
 
596
- // One-to-Many: User has many posts
597
- posts(): HasMany<Post, User> {
598
- return this.hasMany(Post);
366
+ @ValidateRequest()
367
+ async index(req: GetUsersRequest, res: any) {
368
+ const users = await User.query().with('posts').get();
369
+ return res.json({ data: users });
599
370
  }
600
371
 
601
- // Many-to-Many: User has many roles
602
- roles(): BelongsToMany<Role, User> {
603
- return this.belongsToMany(Role, 'role_user')
604
- .withPivot('expires_at', 'granted_by')
605
- .withTimestamps();
372
+ async show(req: any, res: any) {
373
+ const user = await User.find(req.routeParam('id'));
374
+ if (!user) return res.status(404).json({ error: 'Not found' });
375
+ return res.json({ data: user });
606
376
  }
607
377
  }
608
378
 
609
- class Profile extends Ensemble {
610
- protected table = 'profiles';
611
-
612
- // Belongs To: Profile belongs to user
613
- user(): BelongsTo<User, Profile> {
614
- return this.belongsTo(User);
615
- }
616
- }
379
+ // Register route
380
+ Route.get('/users', [UserController, 'index']);
381
+ Route.get('/users/:id', [UserController, 'show']);
382
+ ```
617
383
 
618
- class Post extends Ensemble {
619
- protected table = 'posts';
384
+ ## FormRequest Validation
620
385
 
621
- // Belongs To: Post belongs to author (user)
622
- author(): BelongsTo<User, Post> {
623
- return this.belongsTo(User, 'user_id');
624
- }
386
+ ```typescript
387
+ import { FormRequest, ValidationRules } from '@orchestr-sh/orchestr';
625
388
 
626
- // One-to-Many: Post has many comments
627
- comments(): HasMany<Comment, Post> {
628
- return this.hasMany(Comment);
389
+ export class StoreUserRequest extends FormRequest {
390
+ protected authorize(): boolean {
391
+ return true; // Add authorization logic
629
392
  }
630
393
 
631
- // Many-to-Many: Post has many tags
632
- tags(): BelongsToMany<Tag, Post> {
633
- return this.belongsToMany(Tag);
394
+ protected rules(): ValidationRules {
395
+ return {
396
+ name: 'required|string|min:3',
397
+ email: 'required|email',
398
+ password: 'required|min:8',
399
+ };
634
400
  }
635
401
  }
636
402
 
637
- class Role extends Ensemble {
638
- protected table = 'roles';
639
-
640
- // Many-to-Many: Role has many users (inverse)
641
- users(): BelongsToMany<User, Role> {
642
- return this.belongsToMany(User, 'role_user', 'role_id', 'user_id');
403
+ // Use with @ValidateRequest decorator
404
+ @Injectable()
405
+ export class UserController extends Controller {
406
+ @ValidateRequest()
407
+ async store(req: StoreUserRequest, res: any) {
408
+ const validated = req.validated();
409
+ const user = await User.create(validated);
410
+ return res.status(201).json({ data: user });
643
411
  }
644
412
  }
413
+ ```
645
414
 
646
- // Query using the model
647
- const users = await User.query().where('active', true).get();
648
- const user = await User.query().find(1);
649
-
650
- // Lazy loading relationships
651
- await user.load('posts');
652
- await user.load(['posts', 'profile']);
653
- const posts = user.getRelation('posts');
654
-
655
- // Eager loading (solves N+1 problem)
656
- const users = await User.query()
657
- .with(['posts.comments', 'profile'])
658
- .get();
659
-
660
- // Eager load with constraints
661
- const users = await User.query()
662
- .with({
663
- posts: (query) => query.where('published', '=', true)
664
- })
665
- .get();
666
-
667
- // Create related models
668
- const post = await user.posts().create({
669
- title: 'My Post',
670
- content: 'Content here'
671
- });
672
-
673
- // Associate/dissociate (BelongsTo)
674
- const post = new Post();
675
- post.author().associate(user);
676
- await post.save();
677
-
678
- // Many-to-Many operations
679
- const user = await User.find(1);
680
-
681
- // Attach roles
682
- await user.roles().attach([1, 2, 3]);
683
- await user.roles().attach(1, {
684
- expires_at: new Date('2025-12-31'),
685
- granted_by: 'admin'
686
- });
687
-
688
- // Detach roles
689
- await user.roles().detach([1, 2]);
690
- await user.roles().detach(); // detach all
691
-
692
- // Sync roles (detach all existing, attach new)
693
- const changes = await user.roles().sync([1, 2, 3]);
694
-
695
- // Toggle roles (attach if not attached, detach if attached)
696
- await user.roles().toggle([1, 2, 3]);
697
-
698
- // Query with pivot constraints
699
- const activeRoles = await user.roles()
700
- .wherePivot('expires_at', '>', new Date())
701
- .get();
702
-
703
- // Update pivot data
704
- await user.roles().updateExistingPivot(1, {
705
- expires_at: new Date('2027-12-31')
706
- });
707
-
708
- // Create
709
- const user = new User();
710
- user.name = 'John Doe';
711
- user.email = 'john@example.com';
712
- await user.save();
415
+ ## Configuration
713
416
 
714
- // Or use create
715
- const user = await User.query().create({
716
- name: 'John Doe',
717
- email: 'john@example.com'
718
- });
417
+ ### Database Setup
719
418
 
720
- // Update
721
- const user = await User.query().find(1);
722
- user.name = 'Jane Doe';
723
- await user.save();
419
+ ```typescript
420
+ import { DatabaseServiceProvider, DatabaseManager, DrizzleAdapter } from '@orchestr-sh/orchestr';
724
421
 
725
- // Delete
726
- await user.delete();
422
+ export class DatabaseServiceProvider extends ServiceProvider {
423
+ register(): void {
424
+ this.app.singleton('db', () => {
425
+ const config = this.app.make('config').get('database');
426
+ const manager = new DatabaseManager(config);
427
+ manager.registerAdapter('drizzle', (config) => new DrizzleAdapter(config));
428
+ return manager;
429
+ });
430
+ }
727
431
 
728
- // Soft deletes
729
- class Article extends softDeletes(Ensemble) {
730
- protected table = 'articles';
432
+ async boot(): Promise<void> {
433
+ const db = this.app.make('db');
434
+ await db.connection().connect();
435
+ Ensemble.setConnectionResolver(db);
436
+ }
731
437
  }
732
438
 
733
- const article = await Article.query().find(1);
734
- await article.delete(); // Soft delete
735
- await article.restore(); // Restore
736
- await article.forceDelete(); // Permanent delete
737
-
738
- // Query only non-deleted
739
- const articles = await Article.query().get();
740
-
741
- // Query with trashed
742
- const allArticles = await Article.query().withTrashed().get();
439
+ // Register in your app
440
+ app.register(DatabaseServiceProvider);
441
+ ```
743
442
 
744
- // Query only trashed
745
- const trashedArticles = await Article.query().onlyTrashed().get();
443
+ ### Service Providers
746
444
 
747
- // Timestamps
748
- // Automatically manages created_at and updated_at
749
- class Post extends Ensemble {
750
- protected table = 'posts';
751
- public timestamps = true; // enabled by default
752
- }
445
+ ```typescript
446
+ import { ServiceProvider } from '@orchestr-sh/orchestr';
753
447
 
754
- // Custom attributes and casts
755
- class User extends Ensemble {
756
- protected casts = {
757
- email_verified_at: 'datetime',
758
- settings: 'json',
759
- is_admin: 'boolean',
760
- age: 'number'
761
- };
762
-
763
- // Accessors
764
- getFullNameAttribute(): string {
765
- return `${this.getAttribute('first_name')} ${this.getAttribute('last_name')}`;
448
+ export class AppServiceProvider extends ServiceProvider {
449
+ register(): void {
450
+ this.app.singleton('myService', () => new MyService());
766
451
  }
767
452
 
768
- // Mutators
769
- setPasswordAttribute(value: string): void {
770
- this.setAttribute('password', hashPassword(value));
453
+ async boot(): Promise<void> {
454
+ // Bootstrap code
771
455
  }
772
456
  }
773
-
774
- const user = await User.query().find(1);
775
- console.log(user.full_name); // Uses accessor
776
- user.password = 'secret123'; // Uses mutator
777
457
  ```
778
458
 
779
- #### Many-to-Many Relationships
459
+ ## API Reference
780
460
 
781
- Orchestr provides full support for many-to-many relationships with pivot table management, exactly like Laravel:
461
+ ### Ensemble Methods
782
462
 
783
463
  ```typescript
784
- class User extends Ensemble {
785
- // Define many-to-many relationship
786
- roles(): BelongsToMany<Role, User> {
787
- return this.belongsToMany(Role, 'role_user')
788
- .withPivot('expires_at', 'granted_by') // Additional pivot columns
789
- .withTimestamps(); // Auto-manage timestamps
790
- }
791
- }
792
-
793
- const user = await User.find(1);
794
-
795
- // Attach roles
796
- await user.roles().attach([1, 2, 3]);
797
- await user.roles().attach(1, { expires_at: new Date('2025-12-31') });
798
-
799
- // Detach roles
800
- await user.roles().detach([1, 2]);
801
-
802
- // Sync (detach all, attach new)
803
- const { attached, detached, updated } = await user.roles().sync([1, 2, 3]);
804
-
805
- // Toggle (attach if not present, detach if present)
806
- await user.roles().toggle([1, 2]);
807
-
808
- // Query with pivot constraints
809
- const activeRoles = await user.roles()
810
- .wherePivot('expires_at', '>', new Date())
811
- .wherePivotNotNull('granted_by')
812
- .get();
813
-
814
- // Update pivot data
815
- await user.roles().updateExistingPivot(1, {
816
- expires_at: new Date('2027-12-31')
817
- });
818
-
819
- // Eager load with pivot data
820
- const users = await User.query()
821
- .with({
822
- roles: (query) => query.wherePivot('active', true)
823
- })
824
- .get();
825
-
826
- // Access pivot data
827
- const roles = await user.roles().get();
828
- const pivot = roles[0].getRelation('pivot');
829
- // { user_id, role_id, expires_at, granted_by, created_at, updated_at }
464
+ // Query
465
+ User.query() // Get query builder
466
+ User.find(id) // Find by primary key
467
+ User.findOrFail(id) // Find or throw error
468
+ User.all() // Get all records
469
+ User.create(data) // Create and save
470
+
471
+ // Instance methods
472
+ user.save() // Save changes
473
+ user.delete() // Delete record
474
+ user.refresh() // Reload from database
475
+ user.load('posts') // Lazy load relationship
476
+ user.toObject() // Convert to plain object
830
477
  ```
831
478
 
832
- **Features:**
833
- - ✅ Automatic pivot table naming (alphabetically sorted model names)
834
- - ✅ Attach/detach/sync/toggle operations
835
- - ✅ Pivot table queries (wherePivot, wherePivotIn, wherePivotNull, etc.)
836
- - ✅ Additional pivot columns with `withPivot()`
837
- - ✅ Automatic pivot timestamps with `withTimestamps()`
838
- - ✅ Custom pivot accessor with `as()`
839
- - ✅ Update pivot data with `updateExistingPivot()`
840
- - ✅ Full eager loading support
841
-
842
- ### Database Setup
843
-
844
- Configure multiple database connections:
479
+ ### Query Builder Methods
845
480
 
846
481
  ```typescript
847
- import { Application, DatabaseServiceProvider, DB } from 'orchestr';
848
- import { drizzle } from 'drizzle-orm/better-sqlite3';
849
- import Database from 'better-sqlite3';
850
-
851
- const app = new Application(__dirname);
852
-
853
- // Register database service provider
854
- app.register(new DatabaseServiceProvider({
855
- default: 'sqlite',
856
- connections: {
857
- sqlite: {
858
- adapter: 'drizzle',
859
- client: drizzle(new Database('database.sqlite'))
860
- },
861
- postgres: {
862
- adapter: 'drizzle',
863
- client: drizzle(process.env.DATABASE_URL!)
864
- }
865
- }
866
- }));
867
-
868
- await app.boot();
869
-
870
- // Use default connection
871
- const users = await DB.table('users').get();
872
-
873
- // Use specific connection
874
- const posts = await DB.connection('postgres').table('posts').get();
482
+ .where(column, value)
483
+ .where(column, operator, value)
484
+ .orWhere(column, value)
485
+ .whereIn(column, array)
486
+ .whereBetween(column, [min, max])
487
+ .whereNull(column)
488
+ .orderBy(column, direction)
489
+ .limit(number)
490
+ .offset(number)
491
+ .join(table, first, operator, second)
492
+ .groupBy(column)
493
+ .having(column, operator, value)
494
+ .select(columns)
495
+ .count()
496
+ .sum(column)
497
+ .avg(column)
498
+ .min(column)
499
+ .max(column)
875
500
  ```
876
501
 
877
- ## Complete Example
502
+ ### Schema Methods
878
503
 
879
- Here's a complete example showing routing, database, and ORM:
880
-
881
- **index.ts**
882
504
  ```typescript
883
- import 'reflect-metadata';
884
- import { Application, Kernel, DatabaseServiceProvider } from 'orchestr';
885
- import { AppRouteServiceProvider } from './app/Providers/AppRouteServiceProvider';
886
- import { drizzle } from 'drizzle-orm/better-sqlite3';
887
- import Database from 'better-sqlite3';
888
-
889
- const app = new Application(__dirname);
890
-
891
- // Register database
892
- app.register(new DatabaseServiceProvider({
893
- default: 'sqlite',
894
- connections: {
895
- sqlite: {
896
- adapter: 'drizzle',
897
- client: drizzle(new Database('database.sqlite'))
898
- }
899
- }
900
- }));
901
-
902
- // Register routes
903
- app.register(AppRouteServiceProvider);
904
-
905
- await app.boot();
906
-
907
- const kernel = new Kernel(app);
908
- kernel.listen(3000);
505
+ schema.create(table, callback) // Create new table
506
+ schema.table(table, callback) // Modify existing table
507
+ schema.drop(table) // Drop table
508
+ schema.dropIfExists(table) // Drop table if exists
509
+ schema.rename(from, to) // Rename table
510
+ schema.hasTable(table) // Check if table exists
511
+ schema.hasColumn(table, column) // Check if column exists
909
512
  ```
910
513
 
911
- **app/Models/User.ts**
912
- ```typescript
913
- import { Ensemble, HasMany, softDeletes } from 'orchestr';
914
- import { Post } from './Post';
915
-
916
- export class User extends softDeletes(Ensemble) {
917
- protected table = 'users';
918
- protected fillable = ['name', 'email', 'password'];
919
- protected hidden = ['password'];
920
-
921
- protected casts = {
922
- email_verified_at: 'datetime',
923
- is_admin: 'boolean'
924
- };
514
+ ### Blueprint Column Types
925
515
 
926
- // Define relationship
927
- posts(): HasMany<Post, User> {
928
- return this.hasMany(Post);
929
- }
930
- }
931
- ```
932
-
933
- **app/Models/Post.ts**
934
516
  ```typescript
935
- import { Ensemble, BelongsTo } from 'orchestr';
936
- import { User } from './User';
937
-
938
- export class Post extends Ensemble {
939
- protected table = 'posts';
940
- protected fillable = ['user_id', 'title', 'content', 'published_at'];
941
-
942
- author(): BelongsTo<User, Post> {
943
- return this.belongsTo(User, 'user_id');
944
- }
945
- }
517
+ table.id() // Auto-increment big integer
518
+ table.increments(column) // Auto-increment integer
519
+ table.bigIncrements(column) // Auto-increment big integer
520
+ table.string(column, length) // VARCHAR
521
+ table.text(column) // TEXT
522
+ table.mediumText(column) // MEDIUMTEXT
523
+ table.longText(column) // LONGTEXT
524
+ table.integer(column) // INTEGER
525
+ table.bigInteger(column) // BIGINT
526
+ table.smallInteger(column) // SMALLINT
527
+ table.tinyInteger(column) // TINYINT
528
+ table.decimal(column, precision, scale)
529
+ table.float(column, precision, scale)
530
+ table.double(column, precision, scale)
531
+ table.boolean(column) // BOOLEAN
532
+ table.date(column) // DATE
533
+ table.datetime(column, precision) // DATETIME
534
+ table.timestamp(column, precision) // TIMESTAMP
535
+ table.timestamps(precision) // created_at & updated_at
536
+ table.json(column) // JSON
537
+ table.jsonb(column) // JSONB
538
+ table.uuid(column) // UUID
539
+ table.enum(column, values) // ENUM
540
+ table.binary(column) // BINARY
541
+ table.rememberToken() // remember_token VARCHAR(100)
542
+ table.softDeletes(column) // deleted_at timestamp
946
543
  ```
947
544
 
948
- **routes/api.ts**
545
+ ### Relationship Methods
546
+
949
547
  ```typescript
950
- import { Route, DB } from 'orchestr';
951
- import { User } from '../app/Models/User';
952
-
953
- Route.group({ prefix: 'api' }, () => {
954
- // Using query builder
955
- Route.get('/users', async (req, res) => {
956
- const users = await DB.table('users')
957
- .where('active', true)
958
- .orderBy('created_at', 'desc')
959
- .get();
960
-
961
- return res.json({ users });
962
- });
963
-
964
- // Using Ensemble ORM with eager loading
965
- Route.get('/users/:id', async (req, res) => {
966
- const user = await User.query()
967
- .with('posts')
968
- .find(req.routeParam('id'));
969
-
970
- if (!user) {
971
- return res.status(404).json({ message: 'User not found' });
972
- }
973
-
974
- return res.json({ user: user.toObject() });
975
- });
976
-
977
- Route.post('/users', async (req, res) => {
978
- const user = await User.query().create(
979
- req.only(['name', 'email', 'password'])
980
- );
981
-
982
- return res.status(201).json({ user });
983
- });
984
-
985
- Route.delete('/users/:id', async (req, res) => {
986
- const user = await User.query().find(req.routeParam('id'));
987
- await user?.delete(); // Soft delete
988
-
989
- return res.json({ message: 'User deleted' });
990
- });
991
- });
548
+ // HasOne, HasMany, BelongsTo
549
+ .get() // Execute query
550
+ .first() // Get first result
551
+ .create(data) // Create related model
552
+ .where(column, value) // Add constraint
553
+
554
+ // BelongsToMany
555
+ .attach(ids) // Attach related models
556
+ .detach(ids) // Detach related models
557
+ .sync(ids) // Sync relationships
558
+ .toggle(ids) // Toggle relationships
559
+ .wherePivot(column, value) // Query pivot table
560
+ .updateExistingPivot(id, data) // Update pivot data
561
+
562
+ // All relationships
563
+ .with('relation') // Eager load
564
+ .with(['relation1', 'relation2'])
565
+ .with({ relation: (q) => q.where(...) })
992
566
  ```
993
567
 
994
- ## Architecture
568
+ ### Available Relationships
995
569
 
996
- Orchestr follows Laravel's architecture exactly:
570
+ - `HasOne` - One-to-one
571
+ - `HasMany` - One-to-many
572
+ - `BelongsTo` - Inverse of HasOne/HasMany
573
+ - `BelongsToMany` - Many-to-many
574
+ - `MorphOne` - Polymorphic one-to-one
575
+ - `MorphMany` - Polymorphic one-to-many
576
+ - `MorphTo` - Inverse of MorphOne/MorphMany
577
+ - `MorphToMany` - Polymorphic many-to-many
578
+ - `MorphedByMany` - Inverse of MorphToMany
997
579
 
580
+ ### CLI Commands
581
+
582
+ ```bash
583
+ # Migrations
584
+ orchestr make:migration <name> # Create migration
585
+ orchestr make:migration <name> --create=<table> # Create table migration
586
+ orchestr make:migration <name> --table=<table> # Update table migration
587
+ orchestr migrate # Run migrations
588
+ orchestr migrate:rollback # Rollback last batch
589
+ orchestr migrate:reset # Rollback all migrations
590
+ orchestr migrate:refresh # Reset and re-run migrations
591
+ orchestr migrate:fresh # Drop all tables and migrate
592
+ orchestr migrate:status # Show migration status
593
+
594
+ # Seeders
595
+ orchestr make:seeder <name> # Create seeder
596
+ orchestr db:seed # Run DatabaseSeeder
597
+ orchestr db:seed --class=<name> # Run specific seeder
998
598
  ```
999
- src/
1000
- ├── Container/
1001
- │ └── Container.ts # IoC Container with DI
1002
- ├── Foundation/
1003
- │ ├── Application.ts # Core application class
1004
- │ ├── ServiceProvider.ts # Service provider base
1005
- │ └── Http/
1006
- │ └── Kernel.ts # HTTP kernel
1007
- ├── Routing/
1008
- │ ├── Router.ts # Route registration and dispatch
1009
- │ ├── Route.ts # Individual route
1010
- │ ├── Request.ts # HTTP request wrapper
1011
- │ ├── Response.ts # HTTP response wrapper
1012
- │ └── Controller.ts # Base controller
1013
- ├── Database/
1014
- │ ├── DatabaseManager.ts # Multi-connection manager
1015
- │ ├── Connection.ts # Database connection
1016
- │ ├── Query/
1017
- │ │ ├── Builder.ts # Query builder
1018
- │ │ └── Expression.ts # Raw SQL expressions
1019
- │ ├── Ensemble/
1020
- │ │ ├── Ensemble.ts # Base ORM model (like Eloquent)
1021
- │ │ ├── EnsembleBuilder.ts # Model query builder
1022
- │ │ ├── EnsembleCollection.ts # Model collection
1023
- │ │ ├── SoftDeletes.ts # Soft delete trait
1024
- │ │ ├── Relations/
1025
- │ │ │ ├── Relation.ts # Base relation class
1026
- │ │ │ ├── HasOne.ts # One-to-one relationship
1027
- │ │ │ ├── HasMany.ts # One-to-many relationship
1028
- │ │ │ ├── BelongsTo.ts # Inverse relationship
1029
- │ │ │ └── BelongsToMany.ts # Many-to-many relationship
1030
- │ │ └── Concerns/
1031
- │ │ ├── HasAttributes.ts # Attribute handling & casting
1032
- │ │ ├── HasTimestamps.ts # Timestamp management
1033
- │ │ └── HasRelationships.ts # Relationship functionality
1034
- │ ├── Adapters/
1035
- │ │ └── DrizzleAdapter.ts # Drizzle ORM adapter
1036
- │ └── DatabaseServiceProvider.ts
1037
- ├── Support/
1038
- │ ├── Facade.ts # Facade base class
1039
- │ └── helpers.ts # Helper functions
1040
- ├── Facades/
1041
- │ ├── Route.ts # Route facade
1042
- │ └── DB.ts # Database facade
1043
- └── Providers/
1044
- └── RouteServiceProvider.ts # Route service provider
1045
- ```
1046
599
 
1047
- ## TypeScript Benefits
1048
-
1049
- While maintaining Laravel's API, you get:
1050
-
1051
- - **Type Safety** - Full TypeScript type checking
1052
- - **Better IDE Support** - Autocomplete and IntelliSense
1053
- - **Reflection** - Automatic dependency injection
1054
- - **Modern Async** - Native async/await support
1055
- - **Performance** - Compiled JavaScript performance
1056
-
1057
- ## Roadmap
1058
-
1059
- Core components completed and in progress:
1060
-
1061
- - [x] Service Container & Dependency Injection
1062
- - [x] Service Providers
1063
- - [x] Configuration System
1064
- - [x] HTTP Router & Route Files
1065
- - [x] Request/Response
1066
- - [x] Middleware Pipeline
1067
- - [x] Controllers
1068
- - [x] Facades (Route, DB)
1069
- - [x] Database Query Builder
1070
- - [x] Ensemble ORM (Eloquent equivalent)
1071
- - [x] Multi-connection Database Manager
1072
- - [x] Soft Deletes
1073
- - [x] Model Attributes & Casting
1074
- - [x] Model Relationships (HasOne, HasMany, BelongsTo)
1075
- - [x] Many-to-Many Relationships (BelongsToMany)
1076
- - [x] Eager/Lazy Loading
1077
- - [x] FormRequest Validation & Authorization
1078
- - [ ] Relationship Queries (has, whereHas, withCount)
1079
- - [ ] Polymorphic Relationships
1080
- - [ ] Database Migrations
1081
- - [ ] Database Seeding
1082
- - [ ] Authentication & Authorization
1083
- - [ ] Queue System
1084
- - [ ] Events & Listeners
1085
- - [ ] File Storage
1086
- - [ ] Cache System
1087
- - [ ] Template Engine (Blade equivalent)
1088
- - [ ] CLI/Artisan equivalent
1089
- - [ ] Testing utilities
1090
-
1091
- ## Comparison to Laravel
1092
-
1093
- | Feature | Laravel | Orchestr |
1094
- |---------|---------|----------|
1095
- | Service Container | ✅ | ✅ |
1096
- | Service Providers | ✅ | ✅ |
1097
- | Configuration | ✅ | ✅ |
1098
- | Routing | ✅ | ✅ |
1099
- | Route Files | ✅ | ✅ |
1100
- | Middleware | ✅ | ✅ |
1101
- | Controllers | ✅ | ✅ |
1102
- | Request/Response | ✅ | ✅ |
1103
- | Facades | ✅ | ✅ |
1104
- | Query Builder | ✅ | ✅ |
1105
- | Eloquent ORM | ✅ | ✅ (Ensemble) |
1106
- | Soft Deletes | ✅ | ✅ |
1107
- | Timestamps | ✅ | ✅ |
1108
- | Attribute Casting | ✅ | ✅ |
1109
- | Basic Relationships | ✅ | ✅ |
1110
- | Eager/Lazy Loading | ✅ | ✅ |
1111
- | Many-to-Many | ✅ | ✅ |
1112
- | Polymorphic Relations | ✅ | 🚧 |
1113
- | Migrations | ✅ | 🚧 |
1114
- | Seeding | ✅ | 🚧 |
1115
- | FormRequest Validation | ✅ | ✅ |
1116
- | Authentication | ✅ | 🚧 |
1117
- | Authorization | ✅ | 🚧 |
1118
- | Events | ✅ | 🚧 |
1119
- | Queues | ✅ | 🚧 |
1120
- | Cache | ✅ | 🚧 |
1121
- | File Storage | ✅ | 🚧 |
1122
- | Mail | ✅ | 🚧 |
1123
- | Notifications | ✅ | 🚧 |
600
+ ## Features
601
+
602
+ - Service Container & Dependency Injection
603
+ - ✅ Configuration System
604
+ - HTTP Router & Middleware
605
+ - Controllers with DI
606
+ - FormRequest Validation
607
+ - Query Builder
608
+ - Ensemble ORM (ActiveRecord)
609
+ - ✅ Relationships (Standard + Polymorphic)
610
+ - ✅ Eager/Lazy Loading
611
+ - ✅ Migrations with Schema Builder
612
+ - Database Seeders
613
+ - ✅ Soft Deletes
614
+ - Attribute Casting
615
+ - Timestamps
616
+ - @DynamicRelation Decorator
1124
617
 
1125
618
  ## License
1126
619
 
1127
620
  MIT
621
+
622
+ ---
623
+
624
+ Built with TypeScript. Inspired by Laravel.