@orchestr-sh/orchestr 1.5.9 → 1.5.11

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 (57) hide show
  1. package/README.md +275 -977
  2. package/dist/Database/Ensemble/Concerns/HasDynamicRelationGetters.d.ts +27 -0
  3. package/dist/Database/Ensemble/Concerns/HasDynamicRelationGetters.d.ts.map +1 -0
  4. package/dist/Database/Ensemble/Concerns/HasDynamicRelationGetters.js +109 -0
  5. package/dist/Database/Ensemble/Concerns/HasDynamicRelationGetters.js.map +1 -0
  6. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.d.ts +59 -3
  7. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.d.ts.map +1 -1
  8. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.js +159 -31
  9. package/dist/Database/Ensemble/Concerns/HasDynamicRelations.js.map +1 -1
  10. package/dist/Database/Ensemble/Concerns/HasRelationships.d.ts +20 -0
  11. package/dist/Database/Ensemble/Concerns/HasRelationships.d.ts.map +1 -1
  12. package/dist/Database/Ensemble/Concerns/HasRelationships.js +57 -0
  13. package/dist/Database/Ensemble/Concerns/HasRelationships.js.map +1 -1
  14. package/dist/Database/Ensemble/Ensemble.d.ts.map +1 -1
  15. package/dist/Database/Ensemble/Ensemble.js +0 -4
  16. package/dist/Database/Ensemble/Ensemble.js.map +1 -1
  17. package/dist/Database/Ensemble/EnsembleBuilder.d.ts +1 -1
  18. package/dist/Database/Ensemble/EnsembleBuilder.d.ts.map +1 -1
  19. package/dist/Database/Ensemble/EnsembleBuilder.js +7 -10
  20. package/dist/Database/Ensemble/EnsembleBuilder.js.map +1 -1
  21. package/dist/Database/Ensemble/Relations/BelongsTo.d.ts +5 -0
  22. package/dist/Database/Ensemble/Relations/BelongsTo.d.ts.map +1 -1
  23. package/dist/Database/Ensemble/Relations/BelongsTo.js +8 -0
  24. package/dist/Database/Ensemble/Relations/BelongsTo.js.map +1 -1
  25. package/dist/Database/Ensemble/Relations/MorphMany.d.ts +97 -0
  26. package/dist/Database/Ensemble/Relations/MorphMany.d.ts.map +1 -0
  27. package/dist/Database/Ensemble/Relations/MorphMany.js +189 -0
  28. package/dist/Database/Ensemble/Relations/MorphMany.js.map +1 -0
  29. package/dist/Database/Ensemble/Relations/MorphMap.d.ts +59 -0
  30. package/dist/Database/Ensemble/Relations/MorphMap.d.ts.map +1 -0
  31. package/dist/Database/Ensemble/Relations/MorphMap.js +84 -0
  32. package/dist/Database/Ensemble/Relations/MorphMap.js.map +1 -0
  33. package/dist/Database/Ensemble/Relations/MorphOne.d.ts +93 -0
  34. package/dist/Database/Ensemble/Relations/MorphOne.d.ts.map +1 -0
  35. package/dist/Database/Ensemble/Relations/MorphOne.js +179 -0
  36. package/dist/Database/Ensemble/Relations/MorphOne.js.map +1 -0
  37. package/dist/Database/Ensemble/Relations/MorphTo.d.ts +98 -0
  38. package/dist/Database/Ensemble/Relations/MorphTo.d.ts.map +1 -0
  39. package/dist/Database/Ensemble/Relations/MorphTo.js +214 -0
  40. package/dist/Database/Ensemble/Relations/MorphTo.js.map +1 -0
  41. package/dist/Database/Ensemble/Relations/MorphToMany.d.ts +73 -0
  42. package/dist/Database/Ensemble/Relations/MorphToMany.d.ts.map +1 -0
  43. package/dist/Database/Ensemble/Relations/MorphToMany.js +164 -0
  44. package/dist/Database/Ensemble/Relations/MorphToMany.js.map +1 -0
  45. package/dist/Database/Ensemble/Relations/MorphedByMany.d.ts +29 -0
  46. package/dist/Database/Ensemble/Relations/MorphedByMany.d.ts.map +1 -0
  47. package/dist/Database/Ensemble/Relations/MorphedByMany.js +58 -0
  48. package/dist/Database/Ensemble/Relations/MorphedByMany.js.map +1 -0
  49. package/dist/Database/Ensemble/Relations/index.d.ts +6 -0
  50. package/dist/Database/Ensemble/Relations/index.d.ts.map +1 -1
  51. package/dist/Database/Ensemble/Relations/index.js +14 -1
  52. package/dist/Database/Ensemble/Relations/index.js.map +1 -1
  53. package/dist/index.d.ts +4 -1
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +13 -1
  56. package/dist/index.js.map +1 -1
  57. package/package.json +1 -1
package/README.md CHANGED
@@ -1,1127 +1,425 @@
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';
74
-
75
- const app = new Application(__dirname);
51
+ import { Ensemble } from '@orchestr-sh/orchestr';
76
52
 
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';
166
+ import { Ensemble, MorphMany, MorphTo, DynamicRelation } from '@orchestr-sh/orchestr';
264
167
 
265
- export class AppRouteServiceProvider extends RouteServiceProvider {
266
- async boot(): Promise<void> {
267
- // Load web routes
268
- this.routes(() => import('../../routes/web'));
269
-
270
- // Load API routes
271
- this.routes(() => import('../../routes/api'));
272
-
273
- await super.boot();
168
+ export class Post extends Ensemble {
169
+ @DynamicRelation
170
+ comments(): MorphMany<Comment, Post> {
171
+ return this.morphMany(Comment, 'commentable');
274
172
  }
275
173
  }
276
- ```
277
174
 
278
- **index.ts**
279
- ```typescript
280
- import 'reflect-metadata';
281
- import { Application, Kernel } from 'orchestr';
282
- import { AppRouteServiceProvider } from './app/Providers/AppRouteServiceProvider';
283
-
284
- const app = new Application(__dirname);
285
- app.register(AppRouteServiceProvider);
286
- await app.boot();
287
-
288
- const kernel = new Kernel(app);
289
- kernel.listen(3000);
290
- ```
291
-
292
- ### Middleware
293
-
294
- Global and route-level middleware:
295
-
296
- ```typescript
297
- // Global middleware
298
- kernel.use(async (req, res, next) => {
299
- console.log(`${req.method} ${req.path}`);
300
- await next();
301
- });
175
+ export class Video extends Ensemble {
176
+ @DynamicRelation
177
+ comments(): MorphMany<Comment, Video> {
178
+ return this.morphMany(Comment, 'commentable');
179
+ }
180
+ }
302
181
 
303
- // Route middleware
304
- const authMiddleware = async (req, res, next) => {
305
- if (!req.header('authorization')) {
306
- return res.status(401).json({ message: 'Unauthorized' });
182
+ export class Comment extends Ensemble {
183
+ @DynamicRelation
184
+ commentable(): MorphTo<Post | Video> {
185
+ return this.morphTo('commentable');
307
186
  }
308
- await next();
309
- };
187
+ }
310
188
 
311
- Route.get('/profile', handler).addMiddleware(authMiddleware);
189
+ // Use polymorphic relations
190
+ const post = await Post.find(1);
191
+ const comments = await post.comments;
192
+
193
+ const comment = await Comment.find(1);
194
+ const parent = await comment.commentable; // Returns Post or Video
312
195
  ```
313
196
 
314
- ### FormRequest Validation
197
+ ## @DynamicRelation Decorator
315
198
 
316
- Laravel-style FormRequest for clean validation and authorization:
199
+ The `@DynamicRelation` decorator enables dual-mode access to relationships:
317
200
 
318
201
  ```typescript
319
- import { FormRequest, ValidationRules, ValidationException, ValidateRequest, Route, Request, Response } from 'orchestr';
320
-
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
- }
327
-
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
- }
338
-
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
- };
202
+ export class User extends Ensemble {
203
+ @DynamicRelation
204
+ posts(): HasMany<Post, User> {
205
+ return this.hasMany(Post);
346
206
  }
347
207
  }
348
208
 
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();
356
-
357
- const user = await User.create(validated);
358
- return res.status(201).json({ user });
359
- }
360
- }
209
+ const user = await User.find(1);
361
210
 
362
- Route.post('/users', [UserController, 'store']);
211
+ // Method syntax (returns query builder)
212
+ const query = user.posts();
213
+ const recentPosts = await query.where('created_at', '>', yesterday).get();
363
214
 
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
- });
215
+ // Property syntax (returns results directly)
216
+ const allPosts = await user.posts;
376
217
  ```
377
218
 
378
- ### Controllers
219
+ Without `@DynamicRelation`, you must always call the method: `user.posts().get()`.
379
220
 
380
- MVC pattern with base controller and dependency injection:
221
+ ## Controllers
381
222
 
382
223
  ```typescript
383
- import { Injectable, Controller, Request, Response, Route } from 'orchestr';
224
+ import { Controller, Injectable, ValidateRequest } from '@orchestr-sh/orchestr';
384
225
 
385
- // Use @Injectable() when injecting dependencies
386
226
  @Injectable()
387
- class UserController extends Controller {
388
- // Services are automatically injected
389
- constructor(private userService: UserService) {
227
+ export class UserController extends Controller {
228
+ constructor(private service: UserService) {
390
229
  super();
391
230
  }
392
231
 
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 });
232
+ @ValidateRequest()
233
+ async index(req: GetUsersRequest, res: any) {
234
+ const users = await User.query().with('posts').get();
235
+ return res.json({ data: users });
402
236
  }
403
237
 
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 });
238
+ async show(req: any, res: any) {
239
+ const user = await User.find(req.routeParam('id'));
240
+ if (!user) return res.status(404).json({ error: 'Not found' });
241
+ return res.json({ data: user });
411
242
  }
412
243
  }
413
244
 
414
- // Register controller routes
245
+ // Register route
415
246
  Route.get('/users', [UserController, 'index']);
416
247
  Route.get('/users/:id', [UserController, 'show']);
417
- Route.post('/users', [UserController, 'store']);
418
- ```
419
-
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:
425
-
426
- ```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');
444
-
445
- // Headers
446
- req.header('content-type');
447
- req.expectsJson();
448
- req.ajax();
449
-
450
- // Request info
451
- req.method;
452
- req.path;
453
- req.ip();
454
- ```
455
-
456
- ### Response
457
-
458
- Fluent response building:
459
-
460
- ```typescript
461
- // JSON responses
462
- res.json({ data: [] });
463
- res.status(201).json({ created: true });
464
-
465
- // Headers
466
- res.header('X-Custom', 'value');
467
- res.headers({ 'X-A': 'a', 'X-B': 'b' });
468
-
469
- // Cookies
470
- res.cookie('token', 'value', { httpOnly: true, maxAge: 3600 });
471
-
472
- // Redirects
473
- res.redirect('/home');
474
- res.redirect('/login', 301);
475
-
476
- // Downloads
477
- res.download(buffer, 'file.pdf');
478
-
479
- // Views (simplified)
480
- res.view('welcome', { name: 'John' });
481
- ```
482
-
483
- ### Facades
484
-
485
- Static access to services:
486
-
487
- ```typescript
488
- import { Route, DB } from 'orchestr';
489
-
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();
499
248
  ```
500
249
 
501
- ### Database Query Builder
502
-
503
- Fluent, chainable query builder with full Laravel API:
250
+ ## FormRequest Validation
504
251
 
505
252
  ```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();
253
+ import { FormRequest, ValidationRules } from '@orchestr-sh/orchestr';
528
254
 
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();
544
-
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
- });
557
-
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();
572
- ```
573
-
574
- ### Ensemble ORM
575
-
576
- ActiveRecord ORM (Eloquent equivalent) with relationships and advanced features:
577
-
578
- ```typescript
579
- import { Ensemble, HasOne, HasMany, BelongsTo, BelongsToMany, softDeletes } from 'orchestr';
580
-
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);
594
- }
595
-
596
- // One-to-Many: User has many posts
597
- posts(): HasMany<Post, User> {
598
- return this.hasMany(Post);
255
+ export class StoreUserRequest extends FormRequest {
256
+ protected authorize(): boolean {
257
+ return true; // Add authorization logic
599
258
  }
600
259
 
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();
260
+ protected rules(): ValidationRules {
261
+ return {
262
+ name: 'required|string|min:3',
263
+ email: 'required|email',
264
+ password: 'required|min:8',
265
+ };
606
266
  }
607
267
  }
608
268
 
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);
269
+ // Use with @ValidateRequest decorator
270
+ @Injectable()
271
+ export class UserController extends Controller {
272
+ @ValidateRequest()
273
+ async store(req: StoreUserRequest, res: any) {
274
+ const validated = req.validated();
275
+ const user = await User.create(validated);
276
+ return res.status(201).json({ data: user });
615
277
  }
616
278
  }
279
+ ```
617
280
 
618
- class Post extends Ensemble {
619
- protected table = 'posts';
281
+ ## Configuration
620
282
 
621
- // Belongs To: Post belongs to author (user)
622
- author(): BelongsTo<User, Post> {
623
- return this.belongsTo(User, 'user_id');
624
- }
283
+ ### Database Setup
625
284
 
626
- // One-to-Many: Post has many comments
627
- comments(): HasMany<Comment, Post> {
628
- return this.hasMany(Comment);
629
- }
285
+ ```typescript
286
+ import { DatabaseServiceProvider, DatabaseManager, DrizzleAdapter } from '@orchestr-sh/orchestr';
630
287
 
631
- // Many-to-Many: Post has many tags
632
- tags(): BelongsToMany<Tag, Post> {
633
- return this.belongsToMany(Tag);
288
+ export class DatabaseServiceProvider extends ServiceProvider {
289
+ register(): void {
290
+ this.app.singleton('db', () => {
291
+ const config = this.app.make('config').get('database');
292
+ const manager = new DatabaseManager(config);
293
+ manager.registerAdapter('drizzle', (config) => new DrizzleAdapter(config));
294
+ return manager;
295
+ });
634
296
  }
635
- }
636
-
637
- class Role extends Ensemble {
638
- protected table = 'roles';
639
297
 
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');
298
+ async boot(): Promise<void> {
299
+ const db = this.app.make('db');
300
+ await db.connection().connect();
301
+ Ensemble.setConnectionResolver(db);
643
302
  }
644
303
  }
645
304
 
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();
713
-
714
- // Or use create
715
- const user = await User.query().create({
716
- name: 'John Doe',
717
- email: 'john@example.com'
718
- });
719
-
720
- // Update
721
- const user = await User.query().find(1);
722
- user.name = 'Jane Doe';
723
- await user.save();
724
-
725
- // Delete
726
- await user.delete();
727
-
728
- // Soft deletes
729
- class Article extends softDeletes(Ensemble) {
730
- protected table = 'articles';
731
- }
732
-
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();
743
-
744
- // Query only trashed
745
- const trashedArticles = await Article.query().onlyTrashed().get();
305
+ // Register in your app
306
+ app.register(DatabaseServiceProvider);
307
+ ```
746
308
 
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
- }
309
+ ### Service Providers
753
310
 
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')}`;
766
- }
311
+ ```typescript
312
+ import { ServiceProvider } from '@orchestr-sh/orchestr';
767
313
 
768
- // Mutators
769
- setPasswordAttribute(value: string): void {
770
- this.setAttribute('password', hashPassword(value));
314
+ export class AppServiceProvider extends ServiceProvider {
315
+ register(): void {
316
+ this.app.singleton('myService', () => new MyService());
771
317
  }
772
- }
773
318
 
774
- const user = await User.query().find(1);
775
- console.log(user.full_name); // Uses accessor
776
- user.password = 'secret123'; // Uses mutator
777
- ```
778
-
779
- #### Many-to-Many Relationships
780
-
781
- Orchestr provides full support for many-to-many relationships with pivot table management, exactly like Laravel:
782
-
783
- ```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
319
+ async boot(): Promise<void> {
320
+ // Bootstrap code
790
321
  }
791
322
  }
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 }
830
323
  ```
831
324
 
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
325
+ ## API Reference
843
326
 
844
- Configure multiple database connections:
327
+ ### Ensemble Methods
845
328
 
846
329
  ```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();
330
+ // Query
331
+ User.query() // Get query builder
332
+ User.find(id) // Find by primary key
333
+ User.findOrFail(id) // Find or throw error
334
+ User.all() // Get all records
335
+ User.create(data) // Create and save
336
+
337
+ // Instance methods
338
+ user.save() // Save changes
339
+ user.delete() // Delete record
340
+ user.refresh() // Reload from database
341
+ user.load('posts') // Lazy load relationship
342
+ user.toObject() // Convert to plain object
875
343
  ```
876
344
 
877
- ## Complete Example
878
-
879
- Here's a complete example showing routing, database, and ORM:
345
+ ### Query Builder Methods
880
346
 
881
- **index.ts**
882
347
  ```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);
348
+ .where(column, value)
349
+ .where(column, operator, value)
350
+ .orWhere(column, value)
351
+ .whereIn(column, array)
352
+ .whereBetween(column, [min, max])
353
+ .whereNull(column)
354
+ .orderBy(column, direction)
355
+ .limit(number)
356
+ .offset(number)
357
+ .join(table, first, operator, second)
358
+ .groupBy(column)
359
+ .having(column, operator, value)
360
+ .select(columns)
361
+ .count()
362
+ .sum(column)
363
+ .avg(column)
364
+ .min(column)
365
+ .max(column)
909
366
  ```
910
367
 
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'];
368
+ ### Relationship Methods
920
369
 
921
- protected casts = {
922
- email_verified_at: 'datetime',
923
- is_admin: 'boolean'
924
- };
925
-
926
- // Define relationship
927
- posts(): HasMany<Post, User> {
928
- return this.hasMany(Post);
929
- }
930
- }
931
- ```
932
-
933
- **app/Models/Post.ts**
934
- ```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
- }
946
- ```
947
-
948
- **routes/api.ts**
949
370
  ```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
- });
371
+ // HasOne, HasMany, BelongsTo
372
+ .get() // Execute query
373
+ .first() // Get first result
374
+ .create(data) // Create related model
375
+ .where(column, value) // Add constraint
376
+
377
+ // BelongsToMany
378
+ .attach(ids) // Attach related models
379
+ .detach(ids) // Detach related models
380
+ .sync(ids) // Sync relationships
381
+ .toggle(ids) // Toggle relationships
382
+ .wherePivot(column, value) // Query pivot table
383
+ .updateExistingPivot(id, data) // Update pivot data
384
+
385
+ // All relationships
386
+ .with('relation') // Eager load
387
+ .with(['relation1', 'relation2'])
388
+ .with({ relation: (q) => q.where(...) })
992
389
  ```
993
390
 
994
- ## Architecture
391
+ ### Available Relationships
995
392
 
996
- Orchestr follows Laravel's architecture exactly:
393
+ - `HasOne` - One-to-one
394
+ - `HasMany` - One-to-many
395
+ - `BelongsTo` - Inverse of HasOne/HasMany
396
+ - `BelongsToMany` - Many-to-many
397
+ - `MorphOne` - Polymorphic one-to-one
398
+ - `MorphMany` - Polymorphic one-to-many
399
+ - `MorphTo` - Inverse of MorphOne/MorphMany
400
+ - `MorphToMany` - Polymorphic many-to-many
401
+ - `MorphedByMany` - Inverse of MorphToMany
997
402
 
998
- ```
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
- ```
403
+ ## Features
1046
404
 
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 | ✅ | 🚧 |
405
+ - Service Container & Dependency Injection
406
+ - ✅ Configuration System
407
+ - HTTP Router & Middleware
408
+ - ✅ Controllers with DI
409
+ - FormRequest Validation
410
+ - Query Builder
411
+ - Ensemble ORM (ActiveRecord)
412
+ - Relationships (Standard + Polymorphic)
413
+ - Eager/Lazy Loading
414
+ - ✅ Soft Deletes
415
+ - ✅ Attribute Casting
416
+ - ✅ Timestamps
417
+ - @DynamicRelation Decorator
1124
418
 
1125
419
  ## License
1126
420
 
1127
421
  MIT
422
+
423
+ ---
424
+
425
+ Built with TypeScript. Inspired by Laravel.