@stonyx/orm 0.2.1-alpha.3 → 0.2.1-alpha.30

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 CHANGED
@@ -1,7 +1,11 @@
1
+ [![CI](https://github.com/abofs/stonyx-orm/actions/workflows/ci.yml/badge.svg)](https://github.com/abofs/stonyx-orm/actions/workflows/ci.yml)
2
+ [![npm version](https://img.shields.io/npm/v/@stonyx/orm.svg)](https://www.npmjs.com/package/@stonyx/orm)
3
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+
1
5
  # @stonyx/orm
2
6
 
3
7
  A lightweight ORM for Stonyx projects, featuring model definitions, serializers, relationships, transforms, and optional REST server integration.
4
- `@stonyx/orm` provides a structured way to define models, manage relationships, and persist data in JSON files. It also allows integration with the Stonyx REST server for automatic route setup and access control.
8
+ `@stonyx/orm` provides a structured way to define models, manage relationships, and persist data in JSON files or MySQL. It also allows integration with the Stonyx REST server for automatic route setup and access control.
5
9
 
6
10
  ## Highlights
7
11
 
@@ -9,8 +13,26 @@ A lightweight ORM for Stonyx projects, featuring model definitions, serializers,
9
13
  - **Models**: Define attributes with type-safe proxies (`attr`) and relationships (`hasMany`, `belongsTo`).
10
14
  - **Serializers**: Map raw data into model-friendly structures, including nested properties.
11
15
  - **Transforms**: Apply custom transformations on data values automatically.
12
- - **DB Integration**: Optional file-based persistence with auto-save support.
16
+ - **DB Integration**: Optional file-based persistence with auto-save support, or MySQL for production workloads.
13
17
  - **REST Server Integration**: Automatic route setup with customizable access control.
18
+ - **Lifecycle Hooks**: Middleware-based before/after hooks for validation, authorization, side effects, and auditing.
19
+
20
+ ## Public API vs Internals
21
+
22
+ Records use a proxy that exposes model attributes as direct properties. Always use direct property access for reading and writing field values:
23
+
24
+ ```js
25
+ // Correct: read/write via the proxy
26
+ const age = record.age;
27
+ record.age = 5;
28
+
29
+ // Correct: iterate fields using the record directly
30
+ for (const key of Object.keys(record.serialize())) {
31
+ console.log(key, record[key]);
32
+ }
33
+ ```
34
+
35
+ All properties prefixed with `__` (`__data`, `__relationships`, `__model`, `__serializer`, `__serialized`) are **internal implementation details** and must not be accessed by consumer code. Bypassing the proxy by reading or writing `__data` directly skips type transforms and change tracking, which can lead to silent data corruption.
14
36
 
15
37
  ## Installation
16
38
 
@@ -32,9 +54,18 @@ const {
32
54
  ORM_USE_REST_SERVER,
33
55
  DB_AUTO_SAVE,
34
56
  DB_FILE,
57
+ DB_MODE,
58
+ DB_DIRECTORY,
35
59
  DB_SCHEMA_PATH,
36
- DB_SAVE_INTERVAL
37
- } = process;
60
+ DB_SAVE_INTERVAL,
61
+ MYSQL_HOST,
62
+ MYSQL_PORT,
63
+ MYSQL_USER,
64
+ MYSQL_PASSWORD,
65
+ MYSQL_DATABASE,
66
+ MYSQL_CONNECTION_LIMIT,
67
+ MYSQL_MIGRATIONS_DIR,
68
+ } = process.env;
38
69
 
39
70
  export default {
40
71
  orm: {
@@ -44,6 +75,8 @@ export default {
44
75
  db: {
45
76
  autosave: DB_AUTO_SAVE ?? 'false',
46
77
  file: DB_FILE ?? 'db.json',
78
+ mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
79
+ directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
47
80
  saveInterval: DB_SAVE_INTERVAL ?? 3600, // 1 hour
48
81
  schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
49
82
  },
@@ -53,6 +86,16 @@ export default {
53
86
  serializer: ORM_SERIALIZER_PATH ?? './serializers',
54
87
  transform: ORM_TRANSFORM_PATH ?? './transforms'
55
88
  },
89
+ mysql: MYSQL_HOST ? {
90
+ host: MYSQL_HOST ?? 'localhost',
91
+ port: parseInt(MYSQL_PORT ?? '3306'),
92
+ user: MYSQL_USER ?? 'root',
93
+ password: MYSQL_PASSWORD ?? '',
94
+ database: MYSQL_DATABASE ?? 'stonyx',
95
+ connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
96
+ migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
97
+ migrationsTable: '__migrations',
98
+ } : undefined,
56
99
  restServer: {
57
100
  enabled: ORM_USE_REST_SERVER ?? 'true',
58
101
  route: ORM_REST_ROUTE ?? '/'
@@ -61,16 +104,13 @@ export default {
61
104
  };
62
105
  ```
63
106
 
64
- Then initialize the Stonyx framework, which auto-initializes all of its modules, including `@stonyx/rest-server`:
65
-
66
- ```js
67
- import Stonyx from 'stonyx';
68
- import config from './config/environment.js';
107
+ Then run the application via the Stonyx CLI, which auto-initializes all modules including the ORM:
69
108
 
70
- new Stonyx(config);
109
+ ```bash
110
+ stonyx serve
71
111
  ```
72
112
 
73
- For further framework initialization instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
113
+ For further framework instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
74
114
 
75
115
  ## Models
76
116
 
@@ -90,6 +130,22 @@ export default class OwnerModel extends Model {
90
130
  }
91
131
  ```
92
132
 
133
+ ### Overriding Plural Names
134
+
135
+ By default, model names are auto-pluralized for REST routes, JSON:API URLs, and DB table names (e.g., `animal` → `animals`). When auto-pluralization produces the wrong result, override it with `static pluralName`:
136
+
137
+ ```js
138
+ import { Model, attr } from '@stonyx/orm';
139
+
140
+ export default class PersonModel extends Model {
141
+ static pluralName = 'people';
142
+
143
+ name = attr('string');
144
+ }
145
+ ```
146
+
147
+ The override is picked up automatically during ORM initialization. All routes, JSON:API type references, and MySQL table names will use the overridden value.
148
+
93
149
  ## Serializers
94
150
 
95
151
  Based on the following sample payload structure which represents a poorly structure third-party data source:
@@ -154,7 +210,7 @@ export default function(value) {
154
210
 
155
211
  ## Database (DB) Integration
156
212
 
157
- The ORM can automatically save records to a JSON file with optional auto-save intervals.
213
+ The ORM can automatically save records to a JSON file or a directory of collection files, with optional auto-save intervals.
158
214
 
159
215
  ```js
160
216
  import Orm from '@stonyx/orm';
@@ -168,11 +224,47 @@ const dbRecord = Orm.db;
168
224
 
169
225
  Configuration options are in `config/environment.js`:
170
226
 
171
- * `DB_AUTO_SAVE`: Whether to auto-save.
227
+ * `DB_AUTO_SAVE`: Auto-save mode — `'true'` (cron-based interval), `'false'` (disabled), or `'onUpdate'` (save after every create/update/delete via REST API).
172
228
  * `DB_FILE`: File path to store data.
173
- * `DB_SAVE_INTERVAL`: Interval in seconds for auto-save.
229
+ * `DB_MODE`: Storage mode — `'file'` (single JSON file, default) or `'directory'` (one file per collection in a directory).
230
+ * `DB_DIRECTORY`: Directory name for collection files when mode is `'directory'` (default: `'db'`).
231
+ * `DB_SAVE_INTERVAL`: Interval in seconds for auto-save (only applies when `DB_AUTO_SAVE` is `'true'`).
174
232
  * `DB_SCHEMA_PATH`: Path to DB schema.
175
233
 
234
+ In directory mode, each collection is stored as `{directory}/{collection}.json` (e.g., `db/animals.json`, `db/owners.json`). The main `db.json` is kept as a skeleton with empty arrays. Migration commands are available: `stonyx db:migrate-to-directory` and `stonyx db:migrate-to-file`.
235
+
236
+ ### MySQL Mode
237
+
238
+ Set the `MYSQL_HOST` environment variable to enable MySQL persistence. The ORM loads all records into memory on startup and persists CRUD operations to MySQL automatically. Supports schema-aware migration generation, apply, rollback, and drift detection.
239
+
240
+ | Command | Description |
241
+ |---------|-------------|
242
+ | `stonyx db:generate-migration <desc>` | Generate a migration from model schema diffs |
243
+ | `stonyx db:migrate` | Apply pending migrations |
244
+ | `stonyx db:migrate:rollback` | Rollback the most recent migration |
245
+ | `stonyx db:migrate:status` | Show migration status |
246
+
247
+ ### Running MySQL Tests
248
+
249
+ The ORM includes integration tests that run against a real MySQL database. These are optional — all other tests work without MySQL.
250
+
251
+ **One-time setup:**
252
+
253
+ ```bash
254
+ # Requires local MySQL 8.0+ running
255
+ ./scripts/setup-test-db.sh
256
+ ```
257
+
258
+ This creates a `stonyx_orm_test` database with a `stonyx_test` user. Safe to re-run.
259
+
260
+ **Running tests:**
261
+
262
+ ```bash
263
+ npm test
264
+ ```
265
+
266
+ MySQL integration tests run automatically when MySQL is available. In CI (where `CI=true`), they skip gracefully.
267
+
176
268
  ## REST Server Integration
177
269
 
178
270
  The ORM can automatically register REST routes using your access classes.
@@ -289,6 +381,372 @@ GET /animals/1
289
381
 
290
382
  - Only available on GET endpoints (not POST/PATCH)
291
383
 
384
+ ## Lifecycle Hooks
385
+
386
+ The ORM provides a powerful middleware-based hook system that allows you to run custom logic before and after CRUD operations. Hooks are perfect for validation, transformation, side effects, authorization, and auditing.
387
+
388
+ ### Overview
389
+
390
+ Hooks run at key points in the request lifecycle:
391
+
392
+ - **Before hooks**: Run before the operation executes. **Can halt operations** by returning a value (status code or response object).
393
+ - **After hooks**: Run after the operation completes (logging, notifications, cache invalidation).
394
+
395
+ ### Event Naming Convention
396
+
397
+ Events follow the pattern: `{timing}:{operation}:{modelName}`
398
+
399
+ **Operations:**
400
+ - `list` - GET collection (`/animals`)
401
+ - `get` - GET single record (`/animals/1`)
402
+ - `create` - POST new record (`/animals`)
403
+ - `update` - PATCH existing record (`/animals/1`)
404
+ - `delete` - DELETE record (`/animals/1`)
405
+
406
+ **Examples:**
407
+ - `before:create:animal` - Before creating an animal
408
+ - `after:list:owner` - After fetching owner collection
409
+ - `before:update:trait` - Before updating a trait
410
+
411
+ ### Hook Context Object
412
+
413
+ Each hook receives a context object with comprehensive information:
414
+
415
+ ```javascript
416
+ {
417
+ model: 'animal', // Model name
418
+ operation: 'create', // Operation type
419
+ request, // Express request object
420
+ params, // URL params (e.g., { id: 5 })
421
+ body, // Request body (POST/PATCH)
422
+ query, // Query parameters
423
+ state, // Request state object
424
+ record, // Record instance (after hooks, single operations)
425
+ records, // Record array (after hooks, list operations)
426
+ response, // Response data (after hooks)
427
+ oldState, // Previous record state (update/delete operations only)
428
+ recordId, // Record ID (delete operations in after hooks)
429
+ }
430
+ ```
431
+
432
+ **Important Notes**:
433
+ - `oldState` is only available for `update` and `delete` operations
434
+ - It contains a deep copy of the record's state **before** the operation executes (captured before the `before` hook fires)
435
+ - The deep copy is created via JSON serialization (`JSON.parse(JSON.stringify())`) to ensure complete isolation
436
+ - For `delete` operations, `recordId` is provided in after hooks since the record may no longer exist in the store
437
+ - `oldState` is captured as a deep copy of the record's data before the operation, providing access to the previous field values
438
+
439
+ ### Usage Examples
440
+
441
+ #### Basic Hook Registration
442
+
443
+ ```javascript
444
+ import { beforeHook, afterHook } from '@stonyx/orm';
445
+
446
+ // Validation before creating - can halt by returning a value
447
+ beforeHook('create', 'animal', (context) => {
448
+ const { age } = context.body.data.attributes;
449
+ if (age < 0) {
450
+ return 400; // Halt with 400 Bad Request
451
+ }
452
+ // Return undefined to continue
453
+ });
454
+
455
+ // Logging after updates
456
+ afterHook('update', 'animal', (context) => {
457
+ console.log(`Animal ${context.record.id} was updated`);
458
+ });
459
+ ```
460
+
461
+ #### Halting Operations
462
+
463
+ Before hooks can halt operations by returning a value:
464
+
465
+ ```javascript
466
+ import { beforeHook } from '@stonyx/orm';
467
+
468
+ // Return a status code to halt with that HTTP status
469
+ beforeHook('create', 'animal', (context) => {
470
+ if (!context.body.data.attributes.name) {
471
+ return 400; // Bad Request
472
+ }
473
+ });
474
+
475
+ // Return an object to send a custom response
476
+ beforeHook('delete', 'animal', (context) => {
477
+ const animal = store.get('animal', context.params.id);
478
+ if (animal.protected) {
479
+ return { errors: [{ detail: 'Cannot delete protected animals' }] };
480
+ }
481
+ });
482
+
483
+ // Return undefined (or nothing) to allow operation to continue
484
+ beforeHook('update', 'animal', (context) => {
485
+ console.log('Update proceeding...');
486
+ // No return = operation continues
487
+ });
488
+ ```
489
+
490
+ #### Data Transformation
491
+
492
+ ```javascript
493
+ // Normalize data before saving
494
+ beforeHook('create', 'owner', (context) => {
495
+ const attrs = context.body.data.attributes;
496
+ if (attrs.email) {
497
+ attrs.email = attrs.email.toLowerCase().trim();
498
+ }
499
+ });
500
+ ```
501
+
502
+ #### Side Effects
503
+
504
+ ```javascript
505
+ // Send notification after animal is adopted (using oldState to detect changes)
506
+ afterHook('update', 'animal', async (context) => {
507
+ // Use oldState to compare before/after values
508
+ if (context.oldState && context.oldState.owner !== context.record.owner) {
509
+ await sendNotification({
510
+ type: 'adoption',
511
+ animalId: context.record.id,
512
+ previousOwner: context.oldState.owner,
513
+ newOwner: context.record.owner
514
+ });
515
+ }
516
+ });
517
+
518
+ // Cache invalidation
519
+ afterHook('delete', 'animal', async (context) => {
520
+ await cache.invalidate(`owner:${context.params.id}:pets`);
521
+ });
522
+ ```
523
+
524
+ #### Change Detection
525
+
526
+ The `oldState` property (available for `update` and `delete` operations) enables precise change tracking:
527
+
528
+ ```javascript
529
+ // Detect specific field changes
530
+ afterHook('update', 'animal', async (context) => {
531
+ if (!context.oldState) return; // No old state for create operations
532
+
533
+ // Check if a specific field changed
534
+ if (context.oldState.age !== context.record.age) {
535
+ console.log(`Age changed from ${context.oldState.age} to ${context.record.age}`);
536
+ }
537
+
538
+ // Track multiple field changes
539
+ const changedFields = [];
540
+ for (const key of Object.keys(context.oldState)) {
541
+ if (context.oldState[key] !== context.record[key]) {
542
+ changedFields.push(key);
543
+ }
544
+ }
545
+
546
+ if (changedFields.length > 0) {
547
+ console.log(`Fields changed: ${changedFields.join(', ')}`);
548
+ }
549
+ });
550
+
551
+ // Access deleted record data
552
+ afterHook('delete', 'animal', async (context) => {
553
+ console.log(`Deleted animal: ${context.oldState.type} (age: ${context.oldState.age})`);
554
+ // oldState contains full snapshot of the deleted record
555
+ });
556
+ ```
557
+
558
+ #### Authorization
559
+
560
+ ```javascript
561
+ // Additional access control - halt with 403 if unauthorized
562
+ beforeHook('delete', 'animal', (context) => {
563
+ const user = context.state.currentUser;
564
+ const animal = store.get('animal', context.params.id);
565
+
566
+ if (animal.owner !== user.id && !user.isAdmin) {
567
+ return 403; // Forbidden
568
+ }
569
+ });
570
+ ```
571
+
572
+ #### Auditing
573
+
574
+ ```javascript
575
+ // Audit log for all changes with field-level change tracking
576
+ afterHook('update', 'animal', async (context) => {
577
+ // Compare oldState with current record to capture exact changes
578
+ const changes = {};
579
+ if (context.oldState) {
580
+ for (const key of Object.keys(context.oldState)) {
581
+ if (context.oldState[key] !== context.record[key]) {
582
+ changes[key] = { from: context.oldState[key], to: context.record[key] };
583
+ }
584
+ }
585
+ }
586
+
587
+ await auditLog.create({
588
+ operation: 'update',
589
+ model: context.model,
590
+ recordId: context.record.id,
591
+ userId: context.state.currentUser?.id,
592
+ timestamp: new Date(),
593
+ changes // Precise field-level changes: { age: { from: 2, to: 3 } }
594
+ });
595
+ });
596
+
597
+ // Audit deletes with full record snapshot
598
+ afterHook('delete', 'animal', async (context) => {
599
+ await auditLog.create({
600
+ operation: 'delete',
601
+ model: context.model,
602
+ recordId: context.recordId,
603
+ userId: context.state.currentUser?.id,
604
+ timestamp: new Date(),
605
+ deletedData: context.oldState // Full snapshot of deleted record
606
+ });
607
+ });
608
+ ```
609
+
610
+ #### Error Handling
611
+
612
+ For after hooks, wrap in try/catch if errors should not propagate:
613
+
614
+ ```javascript
615
+ afterHook('create', 'animal', async (context) => {
616
+ try {
617
+ await sendWelcomeEmail(context.record.owner);
618
+ } catch (error) {
619
+ // Error is logged but doesn't fail the create operation
620
+ console.error('Failed to send welcome email:', error);
621
+ }
622
+ });
623
+ ```
624
+
625
+ ### Hook Lifecycle Management
626
+
627
+ #### Unsubscribing
628
+
629
+ ```javascript
630
+ import { beforeHook } from '@stonyx/orm';
631
+
632
+ // Get unsubscribe function
633
+ const unsubscribe = beforeHook('create', 'animal', handler);
634
+
635
+ // Later, remove the hook
636
+ unsubscribe();
637
+ ```
638
+
639
+ #### Clearing Hooks
640
+
641
+ ```javascript
642
+ import { clearHook, clearAllHooks } from '@stonyx/orm';
643
+
644
+ // Remove all hooks for a specific operation:model
645
+ clearHook('create', 'animal');
646
+
647
+ // Remove only before hooks
648
+ clearHook('create', 'animal', 'before');
649
+
650
+ // Remove only after hooks
651
+ clearHook('create', 'animal', 'after');
652
+
653
+ // Remove ALL hooks (useful for testing)
654
+ clearAllHooks();
655
+ ```
656
+
657
+ ### Advanced Patterns
658
+
659
+ #### Conditional Hooks
660
+
661
+ ```javascript
662
+ beforeHook('update', 'animal', (context) => {
663
+ // Only validate if age is being updated
664
+ if ('age' in context.body.data.attributes) {
665
+ const { age } = context.body.data.attributes;
666
+ if (age < 0 || age > 50) {
667
+ return 400; // Bad Request
668
+ }
669
+ }
670
+ });
671
+ ```
672
+
673
+ #### Cross-Model Hooks
674
+
675
+ ```javascript
676
+ // Update owner's pet count when animal is created
677
+ afterHook('create', 'animal', async (context) => {
678
+ const owner = store.get('owner', context.record.owner);
679
+ if (owner) {
680
+ owner.petCount = (owner.petCount || 0) + 1;
681
+ }
682
+ });
683
+ ```
684
+
685
+ #### Sequential Middleware
686
+
687
+ ```javascript
688
+ // Multiple hooks run in registration order
689
+ beforeHook('create', 'post', (context) => {
690
+ console.log('First middleware');
691
+ context.customData = { checked: true };
692
+ });
693
+
694
+ beforeHook('create', 'post', (context) => {
695
+ console.log('Second middleware');
696
+ // Can access data from previous hooks
697
+ if (!context.customData?.checked) {
698
+ return 403;
699
+ }
700
+ });
701
+ ```
702
+
703
+ ### Hook Execution Order
704
+
705
+ 1. **Before hooks** fire first (sequentially, in registration order)
706
+ 2. **Main operation** executes (if no before hook halted)
707
+ 3. **After hooks** fire last (sequentially, in registration order)
708
+
709
+ Before hooks can halt the operation by returning a value. After hooks run after completion and cannot halt.
710
+
711
+ ### Best Practices
712
+
713
+ 1. **Keep hooks focused**: Each hook should do one thing well
714
+ 2. **Use async/await**: Hooks support async functions for consistency
715
+ 3. **Return values intentionally**: Only return a value from before hooks when you want to halt
716
+ 4. **Document side effects**: Make it clear what each hook does
717
+ 5. **Test hooks independently**: Write unit tests for hook logic
718
+ 6. **Avoid heavy operations**: Keep hooks fast to maintain performance
719
+ 7. **Clean up in tests**: Use `clearAllHooks()` in test teardown
720
+
721
+ ### Testing Hooks
722
+
723
+ ```javascript
724
+ import { beforeHook, clearAllHooks } from '@stonyx/orm';
725
+
726
+ // Clean up after each test
727
+ afterEach(() => {
728
+ clearAllHooks();
729
+ });
730
+
731
+ // Test that validation hook halts with 400
732
+ test('validation hook rejects negative age', async () => {
733
+ beforeHook('create', 'animal', (context) => {
734
+ if (context.body.data.attributes.age < 0) {
735
+ return 400;
736
+ }
737
+ });
738
+
739
+ const response = await fetch('/animals', {
740
+ method: 'POST',
741
+ body: JSON.stringify({
742
+ data: { attributes: { age: -5 } }
743
+ })
744
+ });
745
+
746
+ assert.strictEqual(response.status, 400, 'Hook halted with 400');
747
+ });
748
+ ```
749
+
292
750
  ## Exported Helpers
293
751
 
294
752
  | Export | Description |
@@ -297,9 +755,18 @@ GET /animals/1
297
755
  | `belongsTo` | Define a one-to-one relationship. |
298
756
  | `hasMany` | Define a one-to-many relationship. |
299
757
  | `createRecord` | Instantiate a record with proper serialization and relationships. |
758
+ | `updateRecord` | Update an existing record with new data. |
300
759
  | `store` | Singleton store for all model instances. |
301
760
  | `relationships` | Access all relationships (`hasMany`, `belongsTo`, `global`, `pending`). |
761
+ | `beforeHook` | Register a before hook that can halt operations. |
762
+ | `afterHook` | Register an after hook for post-operation logic. |
763
+ | `clearHook` | Clear hooks for a specific operation:model. |
764
+ | `clearAllHooks` | Clear all registered hooks (useful for testing). |
765
+
766
+ ## Project Structure
767
+
768
+ For a full architectural reference, see [project-structure.md](project-structure.md).
302
769
 
303
770
  ## License
304
771
 
305
- Apache — do what you want, just keep attribution.
772
+ Apache 2.0 see [LICENSE.md](LICENSE.md).
@@ -4,20 +4,32 @@ const {
4
4
  ORM_REST_ROUTE,
5
5
  ORM_SERIALIZER_PATH,
6
6
  ORM_TRANSFORM_PATH,
7
+ ORM_VIEW_PATH,
7
8
  ORM_USE_REST_SERVER,
8
9
  DB_AUTO_SAVE,
9
10
  DB_FILE,
11
+ DB_MODE,
12
+ DB_DIRECTORY,
10
13
  DB_SCHEMA_PATH,
11
- DB_SAVE_INTERVAL
12
- } = process;
14
+ DB_SAVE_INTERVAL,
15
+ MYSQL_HOST,
16
+ MYSQL_PORT,
17
+ MYSQL_USER,
18
+ MYSQL_PASSWORD,
19
+ MYSQL_DATABASE,
20
+ MYSQL_CONNECTION_LIMIT,
21
+ MYSQL_MIGRATIONS_DIR,
22
+ } = process.env;
13
23
 
14
24
  export default {
15
25
  logColor: 'white',
16
26
  logMethod: 'db',
17
27
 
18
28
  db: {
19
- autosave: DB_AUTO_SAVE ?? 'false',
29
+ autosave: DB_AUTO_SAVE ?? 'false', // 'true' (cron interval), 'false' (disabled), 'onUpdate' (save after each write op)
20
30
  file: DB_FILE ?? 'db.json',
31
+ mode: DB_MODE ?? 'file', // 'file' (single db.json) or 'directory' (one file per collection)
32
+ directory: DB_DIRECTORY ?? 'db', // directory name for collection files when mode is 'directory'
21
33
  saveInterval: DB_SAVE_INTERVAL ?? 60 * 60, // 1 hour
22
34
  schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
23
35
  },
@@ -25,10 +37,21 @@ export default {
25
37
  access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
26
38
  model: ORM_MODEL_PATH ?? './models',
27
39
  serializer: ORM_SERIALIZER_PATH ?? './serializers',
28
- transform: ORM_TRANSFORM_PATH ?? './transforms'
40
+ transform: ORM_TRANSFORM_PATH ?? './transforms',
41
+ view: ORM_VIEW_PATH ?? './views'
29
42
  },
43
+ mysql: MYSQL_HOST ? {
44
+ host: MYSQL_HOST ?? 'localhost',
45
+ port: parseInt(MYSQL_PORT ?? '3306'),
46
+ user: MYSQL_USER ?? 'root',
47
+ password: MYSQL_PASSWORD ?? '',
48
+ database: MYSQL_DATABASE ?? 'stonyx',
49
+ connectionLimit: parseInt(MYSQL_CONNECTION_LIMIT ?? '10'),
50
+ migrationsDir: MYSQL_MIGRATIONS_DIR ?? 'migrations',
51
+ migrationsTable: '__migrations',
52
+ } : undefined,
30
53
  restServer: {
31
- enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
54
+ enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
32
55
  route: ORM_REST_ROUTE ?? '/',
33
56
  }
34
- }
57
+ }