@venizia/ignis-boot 0.0.4-0 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -117
- package/dist/cjs/base/base-artifact-booter.d.ts.map +1 -1
- package/dist/cjs/base/base-artifact-booter.js +2 -8
- package/dist/cjs/base/base-artifact-booter.js.map +1 -1
- package/dist/cjs/booters/controller.booter.d.ts.map +1 -1
- package/dist/cjs/booters/controller.booter.js +0 -3
- package/dist/cjs/booters/controller.booter.js.map +1 -1
- package/dist/cjs/booters/datasource.booter.d.ts.map +1 -1
- package/dist/cjs/booters/datasource.booter.js +0 -3
- package/dist/cjs/booters/datasource.booter.js.map +1 -1
- package/dist/cjs/booters/repository.booter.d.ts.map +1 -1
- package/dist/cjs/booters/repository.booter.js +0 -3
- package/dist/cjs/booters/repository.booter.js.map +1 -1
- package/dist/cjs/booters/service.booter.d.ts.map +1 -1
- package/dist/cjs/booters/service.booter.js +0 -3
- package/dist/cjs/booters/service.booter.js.map +1 -1
- package/dist/cjs/bootstrapper.d.ts +0 -8
- package/dist/cjs/bootstrapper.d.ts.map +1 -1
- package/dist/cjs/bootstrapper.js +0 -12
- package/dist/cjs/bootstrapper.js.map +1 -1
- package/dist/cjs/common/types.d.ts +0 -8
- package/dist/cjs/common/types.d.ts.map +1 -1
- package/dist/cjs/common/types.js.map +1 -1
- package/dist/cjs/utilities/boot.utility.d.ts +3 -20
- package/dist/cjs/utilities/boot.utility.d.ts.map +1 -1
- package/dist/cjs/utilities/boot.utility.js +3 -20
- package/dist/cjs/utilities/boot.utility.js.map +1 -1
- package/dist/esm/base/base-artifact-booter.d.ts.map +1 -1
- package/dist/esm/base/base-artifact-booter.js +2 -8
- package/dist/esm/base/base-artifact-booter.js.map +1 -1
- package/dist/esm/booters/controller.booter.d.ts.map +1 -1
- package/dist/esm/booters/controller.booter.js +0 -3
- package/dist/esm/booters/controller.booter.js.map +1 -1
- package/dist/esm/booters/datasource.booter.d.ts.map +1 -1
- package/dist/esm/booters/datasource.booter.js +0 -3
- package/dist/esm/booters/datasource.booter.js.map +1 -1
- package/dist/esm/booters/repository.booter.d.ts.map +1 -1
- package/dist/esm/booters/repository.booter.js +0 -3
- package/dist/esm/booters/repository.booter.js.map +1 -1
- package/dist/esm/booters/service.booter.d.ts.map +1 -1
- package/dist/esm/booters/service.booter.js +0 -3
- package/dist/esm/booters/service.booter.js.map +1 -1
- package/dist/esm/bootstrapper.d.ts +0 -8
- package/dist/esm/bootstrapper.d.ts.map +1 -1
- package/dist/esm/bootstrapper.js +0 -12
- package/dist/esm/bootstrapper.js.map +1 -1
- package/dist/esm/common/types.d.ts +0 -8
- package/dist/esm/common/types.d.ts.map +1 -1
- package/dist/esm/common/types.js.map +1 -1
- package/dist/esm/utilities/boot.utility.d.ts +3 -20
- package/dist/esm/utilities/boot.utility.d.ts.map +1 -1
- package/dist/esm/utilities/boot.utility.js +3 -20
- package/dist/esm/utilities/boot.utility.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
3
|
+
# :fire: IGNIS - @venizia/ignis-boot
|
|
6
4
|
|
|
7
|
-
Convention-based
|
|
5
|
+
**Convention-based auto-discovery and bootstrapping for the Ignis Framework**
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@venizia/ignis-boot)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
|
|
11
|
+
Discovers artifact files (controllers, services, repositories, datasources) by glob patterns and registers them into the IoC container during application startup. Three-phase lifecycle (configure, discover, load) with the Template Method pattern. Inspired by [LoopBack 4's boot system](https://loopback.io/doc/en/lb4/Booting-an-Application.html).
|
|
12
|
+
|
|
13
|
+
[Installation](#installation) • [Quick Start](#quick-start) • [API Reference](#core-concepts) • [Documentation](https://venizia-ai.github.io/ignis)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## Highlights
|
|
18
|
+
|
|
19
|
+
| | Feature | |
|
|
20
|
+
| :---: | :--- | :--- |
|
|
21
|
+
| **1** | **Three-Phase Lifecycle** | Configure, discover, load -- structured and predictable |
|
|
22
|
+
| **2** | **Convention Over Configuration** | Default dirs and extensions just work out of the box |
|
|
23
|
+
| **3** | **4 Built-in Booters** | Controllers, services, repositories, and datasources |
|
|
24
|
+
| **4** | **Template Method Pattern** | Extend `BaseArtifactBooter` for custom artifact types |
|
|
25
|
+
| **5** | **Performance Timing** | Built-in `performance.now()` boot report |
|
|
10
26
|
|
|
11
27
|
---
|
|
12
28
|
|
|
@@ -188,12 +204,12 @@ The base class handles all the common logic (option merging, glob execution, fil
|
|
|
188
204
|
|
|
189
205
|
The package ships with four built-in booters covering the most common artifact types:
|
|
190
206
|
|
|
191
|
-
| Booter
|
|
192
|
-
|
|
193
|
-
| `ControllerBooter`
|
|
194
|
-
| `ServiceBooter`
|
|
195
|
-
| `RepositoryBooter`
|
|
196
|
-
| `DatasourceBooter`
|
|
207
|
+
| Booter | Default Directory | Default Extension | Namespace | Scope | Binding Key Example |
|
|
208
|
+
| -------------------- | ----------------- | ------------------- | -------------- | ------------- | -------------------------------- |
|
|
209
|
+
| `ControllerBooter` | `controllers/` | `.controller.js` | `controllers` | transient | `controllers.UserController` |
|
|
210
|
+
| `ServiceBooter` | `services/` | `.service.js` | `services` | transient | `services.AuthService` |
|
|
211
|
+
| `RepositoryBooter` | `repositories/` | `.repository.js` | `repositories` | transient | `repositories.UserRepository` |
|
|
212
|
+
| `DatasourceBooter` | `datasources/` | `.datasource.js` | `datasources` | **singleton** | `datasources.PostgresDataSource` |
|
|
197
213
|
|
|
198
214
|
**Why are datasources singletons?** Datasources manage connection pools and shared resources (database connections, Redis clients, etc.). Creating new instances per injection would leak connections and defeat pool sharing. All other artifact types default to transient scope.
|
|
199
215
|
|
|
@@ -292,14 +308,14 @@ class MyApp extends BootMixin(Container) {
|
|
|
292
308
|
|
|
293
309
|
**What it registers in the constructor:**
|
|
294
310
|
|
|
295
|
-
| Binding Key
|
|
296
|
-
|
|
297
|
-
| `@app/boot-options`
|
|
298
|
-
| `booter.DatasourceBooter`
|
|
299
|
-
| `booter.RepositoryBooter`
|
|
300
|
-
| `booter.ServiceBooter`
|
|
301
|
-
| `booter.ControllerBooter`
|
|
302
|
-
| `bootstrapper`
|
|
311
|
+
| Binding Key | Value | Tags | Scope |
|
|
312
|
+
| --------------------------- | ----------------------- | -------- | --------- |
|
|
313
|
+
| `@app/boot-options` | User's `bootOptions` | -- | -- |
|
|
314
|
+
| `booter.DatasourceBooter` | `DatasourceBooter` class | `booter` | transient |
|
|
315
|
+
| `booter.RepositoryBooter` | `RepositoryBooter` class | `booter` | transient |
|
|
316
|
+
| `booter.ServiceBooter` | `ServiceBooter` class | `booter` | transient |
|
|
317
|
+
| `booter.ControllerBooter` | `ControllerBooter` class | `booter` | transient |
|
|
318
|
+
| `bootstrapper` | `Bootstrapper` class | -- | singleton |
|
|
303
319
|
|
|
304
320
|
**What it adds to the class:**
|
|
305
321
|
|
|
@@ -363,18 +379,19 @@ interface IArtifactOptions {
|
|
|
363
379
|
}
|
|
364
380
|
```
|
|
365
381
|
|
|
366
|
-
| Option
|
|
367
|
-
|
|
368
|
-
| `dirs`
|
|
369
|
-
| `extensions` | `string[]` | Booter-specific
|
|
370
|
-
| `isNested`
|
|
371
|
-
| `glob`
|
|
382
|
+
| Option | Type | Default | Description |
|
|
383
|
+
| ------------ | ---------- | ---------------- | --------------------------------------------------------------------- |
|
|
384
|
+
| `dirs` | `string[]` | Booter-specific | Directories to scan, relative to project root |
|
|
385
|
+
| `extensions` | `string[]` | Booter-specific | File extension patterns to match |
|
|
386
|
+
| `isNested` | `boolean` | `true` | Whether to recurse into subdirectories |
|
|
387
|
+
| `glob` | `string` | `undefined` | Custom glob pattern; if set, `dirs` and `extensions` are ignored |
|
|
372
388
|
|
|
373
389
|
### Pattern Generation
|
|
374
390
|
|
|
375
391
|
When no custom `glob` is provided, `BaseArtifactBooter.getPattern()` builds a glob pattern from `dirs`, `extensions`, and `isNested`:
|
|
376
392
|
|
|
377
393
|
**Single directory, single extension:**
|
|
394
|
+
|
|
378
395
|
```
|
|
379
396
|
dirs: ['repositories']
|
|
380
397
|
extensions: ['.repository.js']
|
|
@@ -384,6 +401,7 @@ Result: repositories/{**/*,*}.repository.js
|
|
|
384
401
|
```
|
|
385
402
|
|
|
386
403
|
**Multiple directories or extensions:**
|
|
404
|
+
|
|
387
405
|
```
|
|
388
406
|
dirs: ['dir1', 'dir2']
|
|
389
407
|
extensions: ['.ext1.js', '.ext2.js']
|
|
@@ -393,6 +411,7 @@ Result: {dir1,dir2}/{**/*,*}.{ext1.js,ext2.js}
|
|
|
393
411
|
```
|
|
394
412
|
|
|
395
413
|
**Non-nested (single level only):**
|
|
414
|
+
|
|
396
415
|
```
|
|
397
416
|
dirs: ['controllers']
|
|
398
417
|
extensions: ['.controller.js']
|
|
@@ -402,6 +421,7 @@ Result: controllers/*.controller.js
|
|
|
402
421
|
```
|
|
403
422
|
|
|
404
423
|
**Custom glob (overrides everything):**
|
|
424
|
+
|
|
405
425
|
```
|
|
406
426
|
glob: 'custom/glob/pattern/**/*.js'
|
|
407
427
|
|
|
@@ -535,13 +555,13 @@ The `Bootstrapper` returns an `IBootReport` object. The current implementation r
|
|
|
535
555
|
|
|
536
556
|
After boot completes, the IoC container holds:
|
|
537
557
|
|
|
538
|
-
| Binding Key
|
|
539
|
-
|
|
540
|
-
| `datasources.PostgresDataSource`
|
|
541
|
-
| `repositories.UserRepository`
|
|
542
|
-
| `services.AuthService`
|
|
543
|
-
| `controllers.UserController`
|
|
544
|
-
| `controllers.AdminController`
|
|
558
|
+
| Binding Key | Class | Scope | Tags |
|
|
559
|
+
| ---------------------------------- | -------------------- | --------- | ------------- |
|
|
560
|
+
| `datasources.PostgresDataSource` | `PostgresDataSource` | singleton | `datasources` |
|
|
561
|
+
| `repositories.UserRepository` | `UserRepository` | transient | `repositories`|
|
|
562
|
+
| `services.AuthService` | `AuthService` | transient | `services` |
|
|
563
|
+
| `controllers.UserController` | `UserController` | transient | `controllers` |
|
|
564
|
+
| `controllers.AdminController` | `AdminController` | transient | `controllers` |
|
|
545
565
|
|
|
546
566
|
These can now be resolved anywhere via `@inject({ key: 'services.AuthService' })` or `app.get({ key: 'controllers.UserController' })`.
|
|
547
567
|
|
|
@@ -567,12 +587,12 @@ export abstract class BaseArtifactBooter extends BaseHelper implements IBooter {
|
|
|
567
587
|
}
|
|
568
588
|
```
|
|
569
589
|
|
|
570
|
-
| Property
|
|
571
|
-
|
|
572
|
-
| `root`
|
|
573
|
-
| `artifactOptions` | `IArtifactOptions` | `{}`
|
|
574
|
-
| `discoveredFiles` | `string[]`
|
|
575
|
-
| `loadedClasses`
|
|
590
|
+
| Property | Type | Initial Value | Description |
|
|
591
|
+
| ----------------- | ----------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
592
|
+
| `root` | `string` | `''` | Absolute path to the project root directory. Set from constructor `opts.root`. Glob patterns are resolved relative to this path. |
|
|
593
|
+
| `artifactOptions` | `IArtifactOptions` | `{}` | The merged configuration after `configure()` runs. Before `configure()`, holds whatever the user passed (or `{}`). After `configure()`, has all fields populated with defaults. |
|
|
594
|
+
| `discoveredFiles` | `string[]` | `[]` | Array of absolute file paths populated by `discover()`. Reset to `[]` at the start of each `discover()` call. |
|
|
595
|
+
| `loadedClasses` | `TClass<any>[]` | `[]` | Array of class constructors extracted from discovered files by `load()`. Reset to `[]` at the start of each `load()` call. |
|
|
576
596
|
|
|
577
597
|
### BaseArtifactBooter Constructor
|
|
578
598
|
|
|
@@ -587,11 +607,11 @@ constructor(opts: IBooterOptions) {
|
|
|
587
607
|
|
|
588
608
|
The constructor receives an `IBooterOptions` object with three fields:
|
|
589
609
|
|
|
590
|
-
| Field
|
|
591
|
-
|
|
592
|
-
| `scope`
|
|
593
|
-
| `root`
|
|
594
|
-
| `artifactOptions`
|
|
610
|
+
| Field | Type | Description |
|
|
611
|
+
| ------------------ | ------------------ | ---------------------------------------------------------------------------------------------- |
|
|
612
|
+
| `scope` | `string` | Logger scope name, typically `BooterClassName.name` (e.g., `"ControllerBooter"`) |
|
|
613
|
+
| `root` | `string` | Absolute path to project root (injected as `@app/project_root`) |
|
|
614
|
+
| `artifactOptions` | `IArtifactOptions` | User-provided options from `bootOptions.controllers` (or whichever artifact key) |
|
|
595
615
|
|
|
596
616
|
The `super({ scope })` call initializes the `BaseHelper` which sets up scoped logging via `LoggerFactory.getLogger([scope])`.
|
|
597
617
|
|
|
@@ -741,17 +761,20 @@ protected getPattern(): string {
|
|
|
741
761
|
### Single Dir + Single Extension
|
|
742
762
|
|
|
743
763
|
**Input:**
|
|
764
|
+
|
|
744
765
|
```typescript
|
|
745
766
|
{ dirs: ['controllers'], extensions: ['.controller.js'], isNested: true }
|
|
746
767
|
```
|
|
747
768
|
|
|
748
769
|
**Processing:**
|
|
770
|
+
|
|
749
771
|
- `dirs.join(',')` => `"controllers"`
|
|
750
772
|
- Extensions: `.controller.js` => strip dot => `"controller.js"`
|
|
751
773
|
- `dirs.length === 1 && extensions.length === 1` => simple format
|
|
752
774
|
- `nested = '{**/*,*}'`
|
|
753
775
|
|
|
754
776
|
**Output:**
|
|
777
|
+
|
|
755
778
|
```
|
|
756
779
|
controllers/{**/*,*}.controller.js
|
|
757
780
|
```
|
|
@@ -763,16 +786,19 @@ controllers/{**/*,*}.controller.js
|
|
|
763
786
|
### Multiple Dirs + Multiple Extensions
|
|
764
787
|
|
|
765
788
|
**Input:**
|
|
789
|
+
|
|
766
790
|
```typescript
|
|
767
791
|
{ dirs: ['private-controllers', 'public-controllers'], extensions: ['.controller.js', '.ctrl.js'], isNested: true }
|
|
768
792
|
```
|
|
769
793
|
|
|
770
794
|
**Processing:**
|
|
795
|
+
|
|
771
796
|
- `dirs.join(',')` => `"private-controllers,public-controllers"`
|
|
772
797
|
- Extensions: `.controller.js` => `controller.js`, `.ctrl.js` => `ctrl.js` => `"controller.js,ctrl.js"`
|
|
773
798
|
- `dirs.length === 2 || extensions.length === 2` => brace expansion format
|
|
774
799
|
|
|
775
800
|
**Output:**
|
|
801
|
+
|
|
776
802
|
```
|
|
777
803
|
{private-controllers,public-controllers}/{**/*,*}.{controller.js,ctrl.js}
|
|
778
804
|
```
|
|
@@ -782,14 +808,17 @@ controllers/{**/*,*}.controller.js
|
|
|
782
808
|
### Multiple Dirs + Single Extension
|
|
783
809
|
|
|
784
810
|
**Input:**
|
|
811
|
+
|
|
785
812
|
```typescript
|
|
786
813
|
{ dirs: ['api', 'admin'], extensions: ['.controller.js'], isNested: true }
|
|
787
814
|
```
|
|
788
815
|
|
|
789
816
|
**Processing:**
|
|
817
|
+
|
|
790
818
|
- `dirs.length === 2` => brace expansion triggered
|
|
791
819
|
|
|
792
820
|
**Output:**
|
|
821
|
+
|
|
793
822
|
```
|
|
794
823
|
{api,admin}/{**/*,*}.controller.js
|
|
795
824
|
```
|
|
@@ -797,14 +826,17 @@ controllers/{**/*,*}.controller.js
|
|
|
797
826
|
### Single Dir + Multiple Extensions
|
|
798
827
|
|
|
799
828
|
**Input:**
|
|
829
|
+
|
|
800
830
|
```typescript
|
|
801
831
|
{ dirs: ['services'], extensions: ['.service.js', '.svc.js'], isNested: true }
|
|
802
832
|
```
|
|
803
833
|
|
|
804
834
|
**Processing:**
|
|
835
|
+
|
|
805
836
|
- `extensions.length === 2` => brace expansion triggered
|
|
806
837
|
|
|
807
838
|
**Output:**
|
|
839
|
+
|
|
808
840
|
```
|
|
809
841
|
{services}/{**/*,*}.{service.js,svc.js}
|
|
810
842
|
```
|
|
@@ -812,33 +844,43 @@ controllers/{**/*,*}.controller.js
|
|
|
812
844
|
### Nested vs Non-Nested
|
|
813
845
|
|
|
814
846
|
**Nested (default, `isNested: true`):**
|
|
847
|
+
|
|
815
848
|
```typescript
|
|
816
849
|
{ dirs: ['repositories'], extensions: ['.repository.js'], isNested: true }
|
|
817
850
|
```
|
|
851
|
+
|
|
818
852
|
**Output:**
|
|
853
|
+
|
|
819
854
|
```
|
|
820
855
|
repositories/{**/*,*}.repository.js
|
|
821
856
|
```
|
|
857
|
+
|
|
822
858
|
The `{**/*,*}` alternation ensures files are matched at any depth, including the root of the directory.
|
|
823
859
|
|
|
824
860
|
**Non-nested (`isNested: false`):**
|
|
861
|
+
|
|
825
862
|
```typescript
|
|
826
863
|
{ dirs: ['repositories'], extensions: ['.repository.js'], isNested: false }
|
|
827
864
|
```
|
|
865
|
+
|
|
828
866
|
**Output:**
|
|
867
|
+
|
|
829
868
|
```
|
|
830
869
|
repositories/*.repository.js
|
|
831
870
|
```
|
|
871
|
+
|
|
832
872
|
Only matches files directly inside `repositories/` -- no subdirectory scanning.
|
|
833
873
|
|
|
834
874
|
### Custom Glob Override
|
|
835
875
|
|
|
836
876
|
**Input:**
|
|
877
|
+
|
|
837
878
|
```typescript
|
|
838
879
|
{ dirs: ['ignored'], extensions: ['.ignored.js'], glob: 'modules/**/handlers/*.handler.js' }
|
|
839
880
|
```
|
|
840
881
|
|
|
841
882
|
**Output:**
|
|
883
|
+
|
|
842
884
|
```
|
|
843
885
|
modules/**/handlers/*.handler.js
|
|
844
886
|
```
|
|
@@ -855,26 +897,26 @@ const exts = this.artifactOptions.extensions
|
|
|
855
897
|
.join(',');
|
|
856
898
|
```
|
|
857
899
|
|
|
858
|
-
| Input Extension
|
|
859
|
-
|
|
860
|
-
| `.controller.js`
|
|
861
|
-
| `.service.js`
|
|
862
|
-
| `handler.js`
|
|
863
|
-
| `.my.custom.ext.js`
|
|
900
|
+
| Input Extension | After Stripping | In Pattern |
|
|
901
|
+
| --------------------- | --------------------- | ----------------------- |
|
|
902
|
+
| `.controller.js` | `controller.js` | `*.controller.js` |
|
|
903
|
+
| `.service.js` | `service.js` | `*.service.js` |
|
|
904
|
+
| `handler.js` | `handler.js` (no dot) | `*.handler.js` |
|
|
905
|
+
| `.my.custom.ext.js` | `my.custom.ext.js` | `*.my.custom.ext.js` |
|
|
864
906
|
|
|
865
907
|
This means files need a dot before the extension in their filename. For example, the pattern `*.controller.js` matches `user.controller.js` but does NOT match `usercontrollerjs` or `user-controller.js`.
|
|
866
908
|
|
|
867
909
|
### Pattern Summary Table
|
|
868
910
|
|
|
869
|
-
| dirs
|
|
870
|
-
|
|
871
|
-
| `['controllers']`
|
|
872
|
-
| `['controllers']`
|
|
873
|
-
| `['api', 'admin']`
|
|
874
|
-
| `['services']`
|
|
875
|
-
| `['a', 'b']`
|
|
876
|
-
| `['a', 'b']`
|
|
877
|
-
| any
|
|
911
|
+
| dirs | extensions | isNested | glob | Generated Pattern |
|
|
912
|
+
| --------------------- | ---------------------------------- | -------- | ------------------- | ------------------------------------------------------ |
|
|
913
|
+
| `['controllers']` | `['.controller.js']` | `true` | -- | `controllers/{**/*,*}.controller.js` |
|
|
914
|
+
| `['controllers']` | `['.controller.js']` | `false` | -- | `controllers/*.controller.js` |
|
|
915
|
+
| `['api', 'admin']` | `['.controller.js']` | `true` | -- | `{api,admin}/{**/*,*}.controller.js` |
|
|
916
|
+
| `['services']` | `['.service.js', '.svc.js']` | `true` | -- | `{services}/{**/*,*}.{service.js,svc.js}` |
|
|
917
|
+
| `['a', 'b']` | `['.x.js', '.y.js']` | `true` | -- | `{a,b}/{**/*,*}.{x.js,y.js}` |
|
|
918
|
+
| `['a', 'b']` | `['.x.js', '.y.js']` | `false` | -- | `{a,b}/*.{x.js,y.js}` |
|
|
919
|
+
| any | any | any | `'custom/**/*.js'` | `custom/**/*.js` |
|
|
878
920
|
|
|
879
921
|
---
|
|
880
922
|
|
|
@@ -882,11 +924,11 @@ This means files need a dot before the extension in their filename. For example,
|
|
|
882
924
|
|
|
883
925
|
All four built-in booters share the same structure. They differ only in their default directories, default extensions, binding namespace, and binding scope. Each one receives three constructor-injected values:
|
|
884
926
|
|
|
885
|
-
| Injection Key
|
|
886
|
-
|
|
887
|
-
| `@app/project_root`
|
|
888
|
-
| `@app/instance`
|
|
889
|
-
| `@app/boot-options`
|
|
927
|
+
| Injection Key | Type | Description |
|
|
928
|
+
| -------------------- | -------------- | -------------------------------------------------------- |
|
|
929
|
+
| `@app/project_root` | `string` | Absolute path to the project's build output directory |
|
|
930
|
+
| `@app/instance` | `IApplication` | The application container instance (for binding classes) |
|
|
931
|
+
| `@app/boot-options` | `IBootOptions` | The user's boot options object |
|
|
890
932
|
|
|
891
933
|
### ControllerBooter
|
|
892
934
|
|
|
@@ -920,14 +962,14 @@ export class ControllerBooter extends BaseArtifactBooter {
|
|
|
920
962
|
}
|
|
921
963
|
```
|
|
922
964
|
|
|
923
|
-
| Property
|
|
924
|
-
|
|
925
|
-
| Default dirs
|
|
926
|
-
| Default extensions
|
|
927
|
-
| Namespace
|
|
928
|
-
| Binding key format
|
|
929
|
-
| Scope
|
|
930
|
-
| Tags
|
|
965
|
+
| Property | Value |
|
|
966
|
+
| -------------------- | -------------------------------------------------------------- |
|
|
967
|
+
| Default dirs | `['controllers']` |
|
|
968
|
+
| Default extensions | `['.controller.js']` |
|
|
969
|
+
| Namespace | `controllers` |
|
|
970
|
+
| Binding key format | `controllers.{ClassName}` (e.g., `controllers.UserController`) |
|
|
971
|
+
| Scope | transient (default) |
|
|
972
|
+
| Tags | `controllers` |
|
|
931
973
|
|
|
932
974
|
### ServiceBooter
|
|
933
975
|
|
|
@@ -961,14 +1003,14 @@ export class ServiceBooter extends BaseArtifactBooter {
|
|
|
961
1003
|
}
|
|
962
1004
|
```
|
|
963
1005
|
|
|
964
|
-
| Property
|
|
965
|
-
|
|
966
|
-
| Default dirs
|
|
967
|
-
| Default extensions
|
|
968
|
-
| Namespace
|
|
969
|
-
| Binding key format
|
|
970
|
-
| Scope
|
|
971
|
-
| Tags
|
|
1006
|
+
| Property | Value |
|
|
1007
|
+
| -------------------- | ---------------------------------------------------------- |
|
|
1008
|
+
| Default dirs | `['services']` |
|
|
1009
|
+
| Default extensions | `['.service.js']` |
|
|
1010
|
+
| Namespace | `services` |
|
|
1011
|
+
| Binding key format | `services.{ClassName}` (e.g., `services.AuthService`) |
|
|
1012
|
+
| Scope | transient (default) |
|
|
1013
|
+
| Tags | `services` |
|
|
972
1014
|
|
|
973
1015
|
### RepositoryBooter
|
|
974
1016
|
|
|
@@ -1002,14 +1044,14 @@ export class RepositoryBooter extends BaseArtifactBooter {
|
|
|
1002
1044
|
}
|
|
1003
1045
|
```
|
|
1004
1046
|
|
|
1005
|
-
| Property
|
|
1006
|
-
|
|
1007
|
-
| Default dirs
|
|
1008
|
-
| Default extensions
|
|
1009
|
-
| Namespace
|
|
1010
|
-
| Binding key format
|
|
1011
|
-
| Scope
|
|
1012
|
-
| Tags
|
|
1047
|
+
| Property | Value |
|
|
1048
|
+
| -------------------- | ---------------------------------------------------------------------- |
|
|
1049
|
+
| Default dirs | `['repositories']` |
|
|
1050
|
+
| Default extensions | `['.repository.js']` |
|
|
1051
|
+
| Namespace | `repositories` |
|
|
1052
|
+
| Binding key format | `repositories.{ClassName}` (e.g., `repositories.UserRepository`) |
|
|
1053
|
+
| Scope | transient (default) |
|
|
1054
|
+
| Tags | `repositories` |
|
|
1013
1055
|
|
|
1014
1056
|
### DatasourceBooter
|
|
1015
1057
|
|
|
@@ -1043,14 +1085,14 @@ export class DatasourceBooter extends BaseArtifactBooter {
|
|
|
1043
1085
|
}
|
|
1044
1086
|
```
|
|
1045
1087
|
|
|
1046
|
-
| Property
|
|
1047
|
-
|
|
1048
|
-
| Default dirs
|
|
1049
|
-
| Default extensions
|
|
1050
|
-
| Namespace
|
|
1051
|
-
| Binding key format
|
|
1052
|
-
| Scope
|
|
1053
|
-
| Tags
|
|
1088
|
+
| Property | Value |
|
|
1089
|
+
| -------------------- | -------------------------------------------------------------------------- |
|
|
1090
|
+
| Default dirs | `['datasources']` |
|
|
1091
|
+
| Default extensions | `['.datasource.js']` |
|
|
1092
|
+
| Namespace | `datasources` |
|
|
1093
|
+
| Binding key format | `datasources.{ClassName}` (e.g., `datasources.PostgresDataSource`) |
|
|
1094
|
+
| Scope | **singleton** |
|
|
1095
|
+
| Tags | `datasources` |
|
|
1054
1096
|
|
|
1055
1097
|
### Why Are Datasources Singletons?
|
|
1056
1098
|
|
|
@@ -1120,12 +1162,15 @@ export const BootMixin = <T extends TMixinTarget<Container>>(baseClass: T) => {
|
|
|
1120
1162
|
When the `BootMixin` constructor runs, it performs exactly 6 binding registrations in this order:
|
|
1121
1163
|
|
|
1122
1164
|
**1. Boot options binding:**
|
|
1165
|
+
|
|
1123
1166
|
```typescript
|
|
1124
1167
|
this.bind({ key: '@app/boot-options' }).toValue(this.bootOptions ?? {});
|
|
1125
1168
|
```
|
|
1169
|
+
|
|
1126
1170
|
Binds the user's `bootOptions` object (or `{}` if undefined) as a plain value. This is injected into every booter's constructor.
|
|
1127
1171
|
|
|
1128
1172
|
**2-5. Booter class registrations (in dependency order):**
|
|
1173
|
+
|
|
1129
1174
|
```typescript
|
|
1130
1175
|
this.bind({ key: 'booter.DatasourceBooter' }).toClass(DatasourceBooter).setTags('booter');
|
|
1131
1176
|
this.bind({ key: 'booter.RepositoryBooter' }).toClass(RepositoryBooter).setTags('booter');
|
|
@@ -1134,6 +1179,7 @@ this.bind({ key: 'booter.ControllerBooter' }).toClass(ControllerBooter).setTags(
|
|
|
1134
1179
|
```
|
|
1135
1180
|
|
|
1136
1181
|
Each booter is:
|
|
1182
|
+
|
|
1137
1183
|
- Bound as a **class** (not an instance) -- the container instantiates it on resolution with constructor injection
|
|
1138
1184
|
- Tagged with `'booter'` -- this is how the `Bootstrapper` discovers them via `findByTag({ tag: 'booter' })`
|
|
1139
1185
|
- Registered in **dependency order** -- datasources before repositories before services before controllers
|
|
@@ -1141,9 +1187,11 @@ Each booter is:
|
|
|
1141
1187
|
The registration order determines execution order within each phase. This ensures that during the LOAD phase, datasources are bound to the container before repositories try to resolve them.
|
|
1142
1188
|
|
|
1143
1189
|
**6. Bootstrapper singleton:**
|
|
1190
|
+
|
|
1144
1191
|
```typescript
|
|
1145
1192
|
this.bind({ key: 'bootstrapper' }).toClass(Bootstrapper).setScope(BindingScopes.SINGLETON);
|
|
1146
1193
|
```
|
|
1194
|
+
|
|
1147
1195
|
The `Bootstrapper` is a singleton because it maintains internal state (the booter list, phase timings). Multiple `boot()` calls resolve the same `Bootstrapper` instance.
|
|
1148
1196
|
|
|
1149
1197
|
### boot() Method
|
|
@@ -1156,6 +1204,7 @@ boot(): Promise<IBootReport> {
|
|
|
1156
1204
|
```
|
|
1157
1205
|
|
|
1158
1206
|
The `boot()` method:
|
|
1207
|
+
|
|
1159
1208
|
1. Resolves the `Bootstrapper` singleton from the container (which triggers `@inject({ key: '@app/instance' })` constructor injection)
|
|
1160
1209
|
2. Calls `bootstrapper.boot({})` with an empty options object, which means all phases run on all booters
|
|
1161
1210
|
3. Returns the `IBootReport` promise
|
|
@@ -1209,6 +1258,7 @@ private async discoverBooters(): Promise<void> {
|
|
|
1209
1258
|
```
|
|
1210
1259
|
|
|
1211
1260
|
This method:
|
|
1261
|
+
|
|
1212
1262
|
1. Calls `this.application.findByTag({ tag: 'booter' })` which returns all `Binding` objects that have the `'booter'` tag
|
|
1213
1263
|
2. For each binding, calls `binding.getValue(this.application)` which:
|
|
1214
1264
|
- Instantiates the booter class (since booters are bound via `.toClass()`)
|
|
@@ -1300,11 +1350,13 @@ throw getError({
|
|
|
1300
1350
|
```
|
|
1301
1351
|
|
|
1302
1352
|
Example error message:
|
|
1353
|
+
|
|
1303
1354
|
```
|
|
1304
1355
|
[Bootstrapper][runPhase] Error during phase 'discover' on booter 'ControllerBooter': [discover] Failed to discover files using pattern: controllers/{**/*,*}.controller.js | Error: ENOENT: no such file or directory
|
|
1305
1356
|
```
|
|
1306
1357
|
|
|
1307
1358
|
This multi-layer error wrapping makes it clear:
|
|
1359
|
+
|
|
1308
1360
|
1. Which component threw (`Bootstrapper`)
|
|
1309
1361
|
2. Which phase was running (`discover`)
|
|
1310
1362
|
3. Which booter failed (`ControllerBooter`)
|
|
@@ -1471,6 +1523,7 @@ export class MiddlewareBooter extends BaseArtifactBooter {
|
|
|
1471
1523
|
```
|
|
1472
1524
|
|
|
1473
1525
|
**Registration:**
|
|
1526
|
+
|
|
1474
1527
|
```typescript
|
|
1475
1528
|
class MyApp extends BootMixin(Container) {
|
|
1476
1529
|
bootOptions: IBootOptions = {
|
|
@@ -1526,6 +1579,7 @@ export class MigrationBooter extends BaseArtifactBooter {
|
|
|
1526
1579
|
```
|
|
1527
1580
|
|
|
1528
1581
|
**Registration with custom extensions:**
|
|
1582
|
+
|
|
1529
1583
|
```typescript
|
|
1530
1584
|
class MyApp extends BootMixin(Container) {
|
|
1531
1585
|
bootOptions: IBootOptions = {
|
|
@@ -1615,6 +1669,7 @@ const bootOptions: IBootOptions = {
|
|
|
1615
1669
|
},
|
|
1616
1670
|
};
|
|
1617
1671
|
```
|
|
1672
|
+
|
|
1618
1673
|
Generated pattern: `{api-controllers,admin-controllers}/{**/*,*}.controller.js`
|
|
1619
1674
|
|
|
1620
1675
|
#### Override extensions only
|
|
@@ -1627,6 +1682,7 @@ const bootOptions: IBootOptions = {
|
|
|
1627
1682
|
},
|
|
1628
1683
|
};
|
|
1629
1684
|
```
|
|
1685
|
+
|
|
1630
1686
|
Generated pattern: `{services}/{**/*,*}.{service.js,provider.js}`
|
|
1631
1687
|
|
|
1632
1688
|
#### Disable nested scanning
|
|
@@ -1639,6 +1695,7 @@ const bootOptions: IBootOptions = {
|
|
|
1639
1695
|
},
|
|
1640
1696
|
};
|
|
1641
1697
|
```
|
|
1698
|
+
|
|
1642
1699
|
Generated pattern: `repositories/*.repository.js`
|
|
1643
1700
|
|
|
1644
1701
|
#### Full override with custom glob
|
|
@@ -1651,6 +1708,7 @@ const bootOptions: IBootOptions = {
|
|
|
1651
1708
|
},
|
|
1652
1709
|
};
|
|
1653
1710
|
```
|
|
1711
|
+
|
|
1654
1712
|
Generated pattern: `config/datasources/*.datasource.js`
|
|
1655
1713
|
|
|
1656
1714
|
#### Mixed overrides across artifact types
|
|
@@ -1813,6 +1871,7 @@ async load(): Promise<void> {
|
|
|
1813
1871
|
```
|
|
1814
1872
|
|
|
1815
1873
|
**Debug output:**
|
|
1874
|
+
|
|
1816
1875
|
```
|
|
1817
1876
|
[ServiceBooter][discover] Root: /app/dist | Using pattern: services/{**/*,*}.service.js | Discovered file: []
|
|
1818
1877
|
[ServiceBooter][load] No files discovered to load.
|
|
@@ -1825,11 +1884,13 @@ This is common during early development when you may not yet have files for ever
|
|
|
1825
1884
|
If a discovered file exports only non-class values (arrow functions, constants, objects, primitives), `loadClasses()` will filter them all out. The `loadedClasses` array will be empty, and `bind()` will execute but do nothing (it iterates an empty array).
|
|
1826
1885
|
|
|
1827
1886
|
**Example file** (`non.repository.ts`):
|
|
1887
|
+
|
|
1828
1888
|
```typescript
|
|
1829
1889
|
// LET THIS FILE BE EMPTY
|
|
1830
1890
|
```
|
|
1831
1891
|
|
|
1832
1892
|
Or a file that exports only constants:
|
|
1893
|
+
|
|
1833
1894
|
```typescript
|
|
1834
1895
|
export const CONFIG = { host: 'localhost' };
|
|
1835
1896
|
export const helper = () => 'helper';
|
|
@@ -1842,6 +1903,7 @@ Both result in zero loaded classes -- no error, no binding.
|
|
|
1842
1903
|
If your glob pattern is too broad (e.g., `**/*.js`), you may discover files that are not artifact classes. The `isClass()` check prevents non-class exports from being bound, but unexpected classes could still be registered.
|
|
1843
1904
|
|
|
1844
1905
|
**Prevention strategies:**
|
|
1906
|
+
|
|
1845
1907
|
- Use specific file extensions: `.controller.js`, `.service.js`
|
|
1846
1908
|
- Use dedicated directories: `controllers/`, `services/`
|
|
1847
1909
|
- Avoid overly broad custom globs
|
|
@@ -1993,14 +2055,17 @@ for (const binding of datasourceBindings) {
|
|
|
1993
2055
|
For large codebases with many files, overly broad glob patterns can slow down the DISCOVER phase. Here are strategies to optimize:
|
|
1994
2056
|
|
|
1995
2057
|
**1. Disable nested scanning when not needed:**
|
|
2058
|
+
|
|
1996
2059
|
```typescript
|
|
1997
2060
|
repositories: {
|
|
1998
2061
|
isNested: false, // Only scan the top-level directory
|
|
1999
2062
|
}
|
|
2000
2063
|
```
|
|
2064
|
+
|
|
2001
2065
|
This changes the pattern from `repositories/{**/*,*}.repository.js` to `repositories/*.repository.js`, which is significantly faster for directories with deep structures.
|
|
2002
2066
|
|
|
2003
2067
|
**2. Use specific directory lists instead of nested scanning:**
|
|
2068
|
+
|
|
2004
2069
|
```typescript
|
|
2005
2070
|
controllers: {
|
|
2006
2071
|
dirs: ['controllers/api', 'controllers/admin'],
|
|
@@ -2010,6 +2075,7 @@ controllers: {
|
|
|
2010
2075
|
```
|
|
2011
2076
|
|
|
2012
2077
|
**3. Use custom glob patterns for surgical targeting:**
|
|
2078
|
+
|
|
2013
2079
|
```typescript
|
|
2014
2080
|
controllers: {
|
|
2015
2081
|
glob: 'controllers/*.controller.js',
|
|
@@ -2065,12 +2131,12 @@ However, the standard Ignis workflow uses compiled output, so `.js` is the corre
|
|
|
2065
2131
|
|
|
2066
2132
|
The convention follows the pattern: `{name}.{artifact-type}.{extension}`
|
|
2067
2133
|
|
|
2068
|
-
| Artifact Type | Convention
|
|
2069
|
-
|
|
2070
|
-
| Controller
|
|
2071
|
-
| Service
|
|
2072
|
-
| Repository
|
|
2073
|
-
| DataSource
|
|
2134
|
+
| Artifact Type | Convention | Example |
|
|
2135
|
+
| ------------- | ------------------------ | -------------------------- |
|
|
2136
|
+
| Controller | `{name}.controller.ts` | `user.controller.ts` |
|
|
2137
|
+
| Service | `{name}.service.ts` | `auth.service.ts` |
|
|
2138
|
+
| Repository | `{name}.repository.ts` | `user.repository.ts` |
|
|
2139
|
+
| DataSource | `{name}.datasource.ts` | `postgres.datasource.ts` |
|
|
2074
2140
|
|
|
2075
2141
|
These conventions are not enforced by the framework -- they are what the default extensions expect. You can use any naming scheme by overriding `extensions` or `glob` in your boot options.
|
|
2076
2142
|
|
|
@@ -2079,11 +2145,11 @@ These conventions are not enforced by the framework -- they are what the default
|
|
|
2079
2145
|
The default directory names match the artifact types:
|
|
2080
2146
|
|
|
2081
2147
|
| Artifact Type | Default Directory |
|
|
2082
|
-
|
|
2083
|
-
| Controllers
|
|
2084
|
-
| Services
|
|
2085
|
-
| Repositories
|
|
2086
|
-
| DataSources
|
|
2148
|
+
| ------------- | ----------------- |
|
|
2149
|
+
| Controllers | `controllers/` |
|
|
2150
|
+
| Services | `services/` |
|
|
2151
|
+
| Repositories | `repositories/` |
|
|
2152
|
+
| DataSources | `datasources/` |
|
|
2087
2153
|
|
|
2088
2154
|
These are relative to the project root (the `@app/project_root` binding). In a typical Ignis application built with `tsc`, the project root is the `dist/cjs/` directory.
|
|
2089
2155
|
|
|
@@ -2173,6 +2239,7 @@ const isClass: <T>(target: AnyType) => target is TClass<T>;
|
|
|
2173
2239
|
```
|
|
2174
2240
|
|
|
2175
2241
|
**Implementation:**
|
|
2242
|
+
|
|
2176
2243
|
```typescript
|
|
2177
2244
|
export const isClass = <T>(target: AnyType): target is TClass<T> => {
|
|
2178
2245
|
return typeof target === 'function' && target.prototype !== undefined;
|
|
@@ -2419,16 +2486,16 @@ const BOOT_PHASES: TBootPhase[] = ['configure', 'discover', 'load'];
|
|
|
2419
2486
|
|
|
2420
2487
|
### DI Binding Keys Used by the Boot System
|
|
2421
2488
|
|
|
2422
|
-
| Key
|
|
2423
|
-
|
|
2424
|
-
| `@app/project_root`
|
|
2425
|
-
| `@app/instance`
|
|
2426
|
-
| `@app/boot-options`
|
|
2427
|
-
| `booter.DatasourceBooter`
|
|
2428
|
-
| `booter.RepositoryBooter`
|
|
2429
|
-
| `booter.ServiceBooter`
|
|
2430
|
-
| `booter.ControllerBooter`
|
|
2431
|
-
| `bootstrapper`
|
|
2489
|
+
| Key | Bound By | Type | Description |
|
|
2490
|
+
| --------------------------- | --------------------------- | ----------------------- | ------------------------------------------------ |
|
|
2491
|
+
| `@app/project_root` | Application | `string` | Absolute path to project's build output root |
|
|
2492
|
+
| `@app/instance` | Application | `IApplication` | The application container itself |
|
|
2493
|
+
| `@app/boot-options` | BootMixin / BaseApplication | `IBootOptions` | User's boot configuration |
|
|
2494
|
+
| `booter.DatasourceBooter` | BootMixin | `DatasourceBooter` class | Tagged `'booter'` |
|
|
2495
|
+
| `booter.RepositoryBooter` | BootMixin | `RepositoryBooter` class | Tagged `'booter'` |
|
|
2496
|
+
| `booter.ServiceBooter` | BootMixin | `ServiceBooter` class | Tagged `'booter'` |
|
|
2497
|
+
| `booter.ControllerBooter` | BootMixin | `ControllerBooter` class | Tagged `'booter'` |
|
|
2498
|
+
| `bootstrapper` | BootMixin / BaseApplication | `Bootstrapper` class | Singleton scope |
|
|
2432
2499
|
|
|
2433
2500
|
Note: When used with `BaseApplication`, the booter keys follow the pattern `booters.{ClassName}` (using the `BindingNamespaces.BOOTERS` namespace) instead of `booter.{ClassName}`.
|
|
2434
2501
|
|