@stonyx/orm 0.2.1-beta.2 → 0.2.1-beta.21

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.
@@ -0,0 +1,139 @@
1
+ # Code Improvements: @stonyx/orm
2
+
3
+ > Audited: 2026-02-09 | Auto-generated by project-docs-auditor
4
+
5
+ ---
6
+
7
+ ## Summary
8
+
9
+ The codebase is well-structured with clear separation of concerns and consistent patterns. The main areas for improvement are: a debug `console.log` left in production config, duplicated utility functions across multiple files, and documentation that has drifted from the actual code. The MySQL driver is well-tested and cleanly DI'd, but the JSON persistence side lacks equivalent test coverage.
10
+
11
+ **Findings by priority:**
12
+ | Priority | Count |
13
+ |----------|-------|
14
+ | High | 2 |
15
+ | Medium | 6 |
16
+ | Low | 5 |
17
+
18
+ ---
19
+
20
+ ## High Priority
21
+
22
+ ### [H1] Stray `console.log` in production config
23
+ - **Category:** Debug Leak
24
+ - **Files:** `config/environment.js`
25
+ - **Lines:** L57
26
+ - **Problem:** `console.log(MYSQL_HOST)` is left at the bottom of the environment config file. This will log the MySQL host (or `undefined`) on every application startup, leaking configuration details to stdout.
27
+ - **Suggestion:** Remove the `console.log(MYSQL_HOST)` line entirely.
28
+ - **Impact:** Eliminates unintended log noise and prevents leaking configuration values in production.
29
+
30
+ ### [H2] Duplicated `getRelationshipInfo()` function
31
+ - **Category:** WET Code
32
+ - **Files:** `src/orm-request.js` (L18-28), `src/mysql/schema-introspector.js` (L6-14), `src/meta-request.js` (L33-34)
33
+ - **Problem:** The pattern of inspecting a function's `toString()` for `getRelationships('belongsTo',` or `getRelationships('hasMany',` is repeated in three separate files with slight variations. Each independently detects relationship type from a property.
34
+ - **Suggestion:** Extract a shared `getRelationshipInfo(property)` function into `src/relationships.js` (which already acts as the relationship utility module) and import it from all three consumers.
35
+ - **Impact:** Single source of truth for relationship detection. Changes to the detection pattern only need to happen in one place.
36
+
37
+ ---
38
+
39
+ ## Medium Priority
40
+
41
+ ### [M1] Duplicated `getOrSet` helper
42
+ - **Category:** WET Code
43
+ - **Files:** `src/has-many.js` (imports from `@stonyx/utils/object`), `src/belongs-to.js` (L4-7)
44
+ - **Problem:** `belongs-to.js` defines its own local `getOrSet(map, key, defaultValue)` function identical to the one available from `@stonyx/utils/object` that `has-many.js` already imports.
45
+ - **Suggestion:** Replace the local `getOrSet` in `belongs-to.js` with `import { getOrSet } from '@stonyx/utils/object'`.
46
+ - **Impact:** Eliminates duplicated code and maintains consistency with `has-many.js`.
47
+
48
+ ### [M2] Duplicated `getCollectionKeys()` and `getDirPath()`
49
+ - **Category:** WET Code
50
+ - **Files:** `src/db.js` (L42-61), `src/migrate.js` (L7-26)
51
+ - **Problem:** Both files contain identical `getCollectionKeys()` and `getDirPath()` functions. The migration module duplicated these from the DB class as standalone functions.
52
+ - **Suggestion:** Export these methods from `src/db.js` (or extract into a shared `src/db-utils.js`) and import them in `src/migrate.js`.
53
+ - **Impact:** Eliminates duplication and ensures migration logic always matches DB logic.
54
+
55
+ ### [M3] `TYPES` array missing `pendingBelongsTo`
56
+ - **Category:** Bug Risk
57
+ - **Files:** `src/relationships.js` (L43)
58
+ - **Lines:** L43
59
+ - **Problem:** `export const TYPES = ['global', 'hasMany', 'belongsTo', 'pending']` is missing `'pendingBelongsTo'`, which is a relationship type initialized in `src/main.js` (L48). The `TYPES` array is used by `store.unloadAllRecords()` (L75) for cleanup, meaning `pendingBelongsTo` entries won't be cleaned up when unloading all records for a model.
60
+ - **Suggestion:** Add `'pendingBelongsTo'` to the `TYPES` array.
61
+ - **Impact:** Prevents potential stale pending belongsTo references when unloading all records for a model.
62
+
63
+ ### [M4] `introspectModels` called repeatedly on every persist operation
64
+ - **Category:** Performance
65
+ - **Files:** `src/mysql/mysql-db.js` (L147, L193, L231)
66
+ - **Problem:** `_persistCreate`, `_persistUpdate`, and `_persistDelete` all call `this.deps.introspectModels()` on every single write operation. Schema introspection iterates all models, creates instances, and inspects properties. Since model schemas don't change at runtime, this is unnecessary repeated work.
67
+ - **Suggestion:** Cache the introspected schemas in `MysqlDB.init()` (or lazily on first access) and reuse the cached result in persist methods.
68
+ - **Impact:** Reduces CPU overhead on every write operation, especially significant with frequent CRUD activity.
69
+
70
+ ### [M5] `startup()` also calls `introspectModels()` separately from `loadAllRecords()`
71
+ - **Category:** Performance
72
+ - **Files:** `src/mysql/mysql-db.js` (L69, L88)
73
+ - **Problem:** `startup()` calls `introspectModels()` at L69 for drift detection, but `loadAllRecords()` (called at L62 inside the migration-apply branch) also calls `introspectModels()` at L88. When migrations are applied, `introspectModels()` runs three times: once in `loadAllRecords` (via init), once in reloaded `loadAllRecords`, and once for drift check.
74
+ - **Suggestion:** Same as M4 — cache the result.
75
+ - **Impact:** Reduces redundant schema introspection calls during startup.
76
+
77
+ ### [M6] No test coverage for JSON DB `save()` and `getRecord()` flows
78
+ - **Category:** Test Gap
79
+ - **Files:** `src/db.js`
80
+ - **Problem:** The JSON file persistence layer (`DB` class) lacks unit tests for its core `save()`, `getRecord()`, `getRecordFromDirectory()`, `getRecordFromFile()`, and `validateMode()` methods. The MySQL driver has thorough unit tests via dependency injection, but the JSON DB has no equivalent.
81
+ - **Suggestion:** Add unit tests for `src/db.js`, potentially using the same DI pattern used by `MysqlDB` to mock file operations.
82
+ - **Impact:** Increases confidence in the JSON persistence path, which is the default storage mode.
83
+
84
+ ---
85
+
86
+ ## Low Priority
87
+
88
+ ### [L1] Documentation references non-existent files
89
+ - **Category:** Stale Docs
90
+ - **Files:** `.claude/index.md` (L38-39), `.claude/personal/outline.md` (L1107)
91
+ - **Problem:** The `.claude/index.md` references `src/include-parser.js` and `src/include-collector.js` which don't exist — include parsing is inline in `src/orm-request.js`. The outline references `stonyx-bootstrap.cjs` which doesn't exist.
92
+ - **Suggestion:** Remove the `include-parser.js` and `include-collector.js` references from `.claude/index.md` and the `stonyx-bootstrap.cjs` reference from the outline. Update the architecture section to point to `src/orm-request.js` for include logic.
93
+ - **Impact:** Prevents confusion when navigating docs.
94
+
95
+ ### [L2] Outdated version in outline doc
96
+ - **Category:** Stale Docs
97
+ - **Files:** `.claude/personal/outline.md` (L1076)
98
+ - **Problem:** Lists version as `0.1.0` but `package.json` shows `0.2.1-beta.1`.
99
+ - **Suggestion:** Update or remove the version reference. Since `package.json` is the source of truth, the docs shouldn't duplicate it.
100
+ - **Impact:** Minor — prevents stale version confusion.
101
+
102
+ ### [L3] Outline docs missing `./commands` and `./hooks` package exports
103
+ - **Category:** Stale Docs
104
+ - **Files:** `.claude/personal/outline.md` (L884-889)
105
+ - **Problem:** The package.json `exports` section in the outline doc only shows `"."` and `"./db"`, but the actual `package.json` also exports `"./migrate"`, `"./commands"`, and `"./hooks"`.
106
+ - **Suggestion:** Update the outline's package.json section to match the actual exports.
107
+ - **Impact:** Minor — ensures docs reflect the full public API surface.
108
+
109
+ ### [L4] Outline references old event-based system
110
+ - **Category:** Stale Docs
111
+ - **Files:** `.claude/index.md` (L204)
112
+ - **Problem:** States `@stonyx/events - Pub/sub event system (optional, not used for hooks)`. However, `@stonyx/events` is imported and `setup(eventNames)` is called in `src/main.js` (L91). The events are set up but the hooks system uses its own registry. This is confusing — events are initialized but the hooks use a separate middleware pattern.
113
+ - **Suggestion:** Clarify that `@stonyx/events` is still initialized for event names during ORM setup, but the actual hook dispatch uses the middleware-based system in `src/hooks.js`.
114
+ - **Impact:** Reduces confusion about the relationship between `@stonyx/events` and the hooks system.
115
+
116
+ ### [L5] Manual test scripts in project root
117
+ - **Category:** Dead Code
118
+ - **Files:** `test-hooks-with-logging.js`, `test-events-setup.js`, `test-hooks-manual.js`
119
+ - **Problem:** Three manual test scripts remain in the project root from the hooks implementation. They were used to verify functionality when the test harness had linking issues, but are not part of the test suite and won't run via `npm test`.
120
+ - **Suggestion:** Either move them to `test/manual/` or remove them if the hooks implementation is considered stable and the test harness issues are resolved.
121
+ - **Impact:** Cleaner project root; reduces confusion about which test files are authoritative.
122
+
123
+ ---
124
+
125
+ ## Refactoring Roadmap
126
+
127
+ **Quick wins (do first):**
128
+ 1. **H1** — Delete one line (`console.log(MYSQL_HOST)`) in config
129
+ 2. **M1** — Replace local `getOrSet` in `belongs-to.js` with import
130
+ 3. **M3** — Add `'pendingBelongsTo'` to `TYPES` array
131
+
132
+ **Medium effort:**
133
+ 4. **H2** — Extract shared `getRelationshipInfo` into `relationships.js`
134
+ 5. **M2** — Extract shared `getCollectionKeys`/`getDirPath` from `db.js`
135
+
136
+ **Longer term:**
137
+ 6. **M4/M5** — Cache introspected schemas in MysqlDB
138
+ 7. **M6** — Add DB unit tests
139
+ 8. **L1-L4** — Documentation cleanup pass
package/package.json CHANGED
@@ -4,13 +4,16 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-beta.2",
7
+ "version": "0.2.1-beta.21",
8
8
  "description": "",
9
9
  "main": "src/main.js",
10
10
  "type": "module",
11
11
  "exports": {
12
12
  ".": "./src/index.js",
13
- "./db": "./src/exports/db.js"
13
+ "./db": "./src/exports/db.js",
14
+ "./migrate": "./src/migrate.js",
15
+ "./commands": "./src/commands.js",
16
+ "./hooks": "./src/hooks.js"
14
17
  },
15
18
  "repository": {
16
19
  "type": "git",
@@ -30,17 +33,25 @@
30
33
  },
31
34
  "homepage": "https://github.com/abofs/stonyx-orm#readme",
32
35
  "dependencies": {
33
- "stonyx": "^0.2.2",
34
- "@stonyx/events": "^0.1.0",
35
- "@stonyx/cron": "^0.2.0"
36
+ "stonyx": "0.2.3-beta.4",
37
+ "@stonyx/events": "0.1.1-beta.5",
38
+ "@stonyx/cron": "0.2.1-beta.8"
39
+ },
40
+ "peerDependencies": {
41
+ "mysql2": "^3.0.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "mysql2": {
45
+ "optional": true
46
+ }
36
47
  },
37
48
  "devDependencies": {
38
- "@stonyx/rest-server": "^0.2.0",
39
- "@stonyx/utils": "^0.2.2",
49
+ "@stonyx/rest-server": "0.2.1-beta.11",
50
+ "@stonyx/utils": "0.2.3-beta.4",
40
51
  "qunit": "^2.24.1",
41
52
  "sinon": "^21.0.0"
42
53
  },
43
54
  "scripts": {
44
- "test": "qunit --require ./stonyx-bootstrap.cjs"
55
+ "test": "stonyx test"
45
56
  }
46
57
  }
@@ -0,0 +1,343 @@
1
+ # Project Documentation: @stonyx/orm
2
+
3
+ > Last audited: 2026-02-09 | Auto-generated by project-docs-auditor
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+ 1. [Overview](#overview)
9
+ 2. [Tech Stack](#tech-stack)
10
+ 3. [Architecture](#architecture)
11
+ 4. [Directory Map](#directory-map)
12
+ 5. [Key Files Reference](#key-files-reference)
13
+ 6. [Data Flow](#data-flow)
14
+ 7. [API / Routes](#api--routes)
15
+ 8. [Database & Models](#database--models)
16
+ 9. [Authentication & Authorization](#authentication--authorization)
17
+ 10. [Configuration & Environment](#configuration--environment)
18
+ 11. [Development Guide](#development-guide)
19
+ 12. [Testing](#testing)
20
+ 13. [Deployment & CI/CD](#deployment--cicd)
21
+ 14. [Conventions & Standards](#conventions--standards)
22
+ 15. [Known Issues & Technical Debt](#known-issues--technical-debt)
23
+
24
+ ---
25
+
26
+ ## Overview
27
+
28
+ A lightweight ORM for the Stonyx framework that provides structured data modeling with type-safe attributes, bidirectional relationships, serializers for third-party data normalization, and automatic REST API generation. Supports both JSON file-based persistence and MySQL as storage backends.
29
+
30
+ ## Tech Stack
31
+
32
+ | Category | Technology | Version | Notes |
33
+ |----------|-----------|---------|-------|
34
+ | Language | JavaScript (ES Modules) | Node 24.13.0 | `"type": "module"` in package.json |
35
+ | Framework | Stonyx | file ref | Core framework (peer dependency) |
36
+ | Database (JSON) | Built-in `DB` class | N/A | File-based JSON persistence |
37
+ | Database (SQL) | MySQL via mysql2 | ^3.0.0 | Optional peer dependency |
38
+ | Test Framework | QUnit | ^2.24.1 | Via `stonyx test` runner |
39
+ | Test Mocking | Sinon | ^21.0.0 | Stubs and spies |
40
+ | Package Manager | pnpm | N/A | Local `file:` references for sibling packages |
41
+
42
+ ## Architecture
43
+
44
+ The ORM follows a **singleton + registry** pattern. A single `Orm` instance manages models, serializers, and transforms. Records are stored in an in-memory `Store` (nested Maps) and optionally persisted to JSON files or MySQL.
45
+
46
+ ```
47
+ ┌──────────────┐
48
+ │ Orm (main) │ Singleton, initializes everything
49
+ └──────┬───────┘
50
+ ┌────────────┬────┴────┬────────────┐
51
+ v v v v
52
+ ┌────────┐ ┌─────────┐ ┌────┐ ┌──────────────┐
53
+ │ Models │ │Serializers│ │ DB │ │ REST Server │
54
+ └───┬────┘ └────┬─────┘ └──┬─┘ │ Integration │
55
+ │ │ │ └──────┬───────┘
56
+ v v v v
57
+ ┌────────┐ ┌──────────┐ ┌─────┐ ┌───────────┐
58
+ │ Record │──│ Store │ │JSON │ │OrmRequest │
59
+ │(instance)│ │(Map<Map>)│ │/MySQL│ │(CRUD+Hooks)│
60
+ └────────┘ └──────────┘ └─────┘ └───────────┘
61
+ ```
62
+
63
+ **Key Design Patterns:**
64
+ - **Singleton**: Orm, Store, DB, MysqlDB classes
65
+ - **Proxy**: `attr()` wraps `ModelProperty` in a Proxy for type-safe get/set
66
+ - **Registry**: Relationships tracked in nested Maps (`hasMany`, `belongsTo`, `global`, `pending`, `pendingBelongsTo`)
67
+ - **Factory**: `createRecord()` instantiates records with serialization
68
+ - **Middleware**: Hook system with sequential before/after execution and halting capability
69
+ - **Convention over Configuration**: Auto-discovery of models, serializers, transforms, and access classes by filesystem directory
70
+
71
+ ## Directory Map
72
+
73
+ ```
74
+ stonyx-orm/
75
+ ├── src/
76
+ │ ├── index.js # Public exports barrel
77
+ │ ├── main.js # Orm singleton class
78
+ │ ├── model.js # Base Model class
79
+ │ ├── record.js # Record instances (data + relationships)
80
+ │ ├── serializer.js # Base Serializer + computed properties
81
+ │ ├── store.js # In-memory store (Map<model, Map<id, Record>>)
82
+ │ ├── db.js # JSON file persistence layer
83
+ │ ├── attr.js # Attribute helper (Proxy-based)
84
+ │ ├── model-property.js # Transform handler for attributes
85
+ │ ├── transforms.js # Built-in transforms (boolean, number, string, etc.)
86
+ │ ├── has-many.js # One-to-many relationship handler
87
+ │ ├── belongs-to.js # Many-to-one relationship handler
88
+ │ ├── relationships.js # Relationship registry + helpers
89
+ │ ├── manage-record.js # createRecord / updateRecord
90
+ │ ├── hooks.js # Middleware hook registry (before/after)
91
+ │ ├── orm-request.js # REST CRUD handler with hooks + includes
92
+ │ ├── meta-request.js # Dev-only meta endpoint
93
+ │ ├── setup-rest-server.js # REST route registration
94
+ │ ├── migrate.js # JSON DB mode migration (file <-> directory)
95
+ │ ├── commands.js # CLI commands (db:migrate-*, etc.)
96
+ │ ├── utils.js # Pluralize wrapper for dasherized names
97
+ │ ├── exports/
98
+ │ │ └── db.js # Convenience re-export of DB instance
99
+ │ └── mysql/
100
+ │ ├── mysql-db.js # MySQL driver class (CRUD persistence)
101
+ │ ├── connection.js # mysql2 connection pool management
102
+ │ ├── query-builder.js # SQL query builders (INSERT/UPDATE/DELETE/SELECT)
103
+ │ ├── schema-introspector.js # Introspects models into MySQL schemas
104
+ │ ├── migration-generator.js # Generates .sql migration files from schema diffs
105
+ │ ├── migration-runner.js # Applies/rollbacks migrations with transactions
106
+ │ └── type-map.js # ORM attr types -> MySQL column types
107
+ ├── config/
108
+ │ └── environment.js # Default ORM configuration
109
+ ├── test/
110
+ │ ├── config/
111
+ │ │ └── environment.js # Test-specific config overrides
112
+ │ ├── integration/
113
+ │ │ ├── orm-test.js # Full pipeline integration tests
114
+ │ │ └── db-directory-test.js # Directory-mode DB tests
115
+ │ ├── unit/
116
+ │ │ ├── commands-test.js # CLI command structure tests
117
+ │ │ ├── create-record-test.js # Record creation tests
118
+ │ │ ├── relationships-test.js # Relationship wiring tests
119
+ │ │ ├── environment-test.js # Environment config tests
120
+ │ │ ├── orm-lifecycle-test.js # Startup/shutdown lifecycle tests
121
+ │ │ ├── mysql/
122
+ │ │ │ └── mysql-db-startup-test.js # MySQL startup behavior tests
123
+ │ │ ├── transforms/ # Per-transform unit tests
124
+ │ │ └── utils/
125
+ │ │ └── get-computed-properties-test.js
126
+ │ └── sample/ # Test fixtures
127
+ │ ├── models/ # Example model definitions
128
+ │ ├── serializers/ # Example serializers
129
+ │ ├── transforms/ # Custom transform functions
130
+ │ ├── access/ # Access control classes
131
+ │ ├── db-schema.js # Test DB schema
132
+ │ ├── constants.js # Test constants
133
+ │ └── payload.js # Sample raw + expected data
134
+ ├── .claude/ # Claude AI assistant context docs
135
+ ├── .github/workflows/
136
+ │ ├── ci.yml # PR CI pipeline
137
+ │ └── publish.yml # NPM publish workflow
138
+ ├── package.json
139
+ ├── .nvmrc # Node v24.13.0
140
+ ├── .npmignore
141
+ ├── .gitignore
142
+ └── LICENSE.md # Apache 2.0
143
+ ```
144
+
145
+ ## Key Files Reference
146
+
147
+ | File / Path | Purpose | Category |
148
+ |-------------|---------|----------|
149
+ | `src/main.js` | Orm singleton: initialization, model/serializer/transform loading, DB + REST setup | Core |
150
+ | `src/index.js` | Public API barrel export (Model, Serializer, attr, hooks, etc.) | Core |
151
+ | `src/store.js` | In-memory record storage with relationship-aware unload/cascade | Core |
152
+ | `src/record.js` | Record instances: serialize, format, toJSON (JSON:API), unload | Core |
153
+ | `src/serializer.js` | Base serializer: path mapping, property setup, computed properties | Serialization |
154
+ | `src/manage-record.js` | `createRecord` / `updateRecord` with pending relationship fulfillment | CRUD |
155
+ | `src/orm-request.js` | REST request handler: CRUD routes, hooks wrapper, includes, filters, fields | REST API |
156
+ | `src/hooks.js` | Middleware hook registry: before/after hooks with halting | Middleware |
157
+ | `src/db.js` | JSON file persistence: read, save, directory mode, auto-save via cron | Persistence |
158
+ | `src/mysql/mysql-db.js` | MySQL driver: load records on init, persist CRUD, migration prompts | Persistence |
159
+ | `src/mysql/schema-introspector.js` | Model introspection to MySQL schema + DDL generation | MySQL |
160
+ | `src/mysql/migration-generator.js` | Schema diff and `.sql` migration file generation | MySQL |
161
+ | `src/commands.js` | CLI commands for DB migrations (file/directory mode + MySQL) | CLI |
162
+ | `config/environment.js` | Default configuration with env var overrides | Config |
163
+
164
+ ## Data Flow
165
+
166
+ **Typical REST request lifecycle:**
167
+
168
+ 1. **Request arrives** at auto-generated route (e.g., `POST /animals`) via `@stonyx/rest-server`
169
+ 2. **Access control** (`OrmRequest.auth`) calls the access class's `access()` method -- returns permissions, filter function, or denial
170
+ 3. **Before hooks** run sequentially -- can halt the operation by returning a value (status code or response object)
171
+ 4. **Handler executes** the CRUD operation (reads from / writes to the in-memory `Store`)
172
+ 5. **MySQL persistence** (if configured) -- `MysqlDB.persist()` writes to MySQL after in-memory mutation
173
+ 6. **After hooks** run sequentially with enriched context (record, response, oldState)
174
+ 7. **Auto-save** (if `autosave: 'onUpdate'`) writes the entire store to the JSON file
175
+ 8. **Response** returned as JSON:API format with optional `included` sideloaded relationships
176
+
177
+ **Record creation flow:**
178
+
179
+ 1. `createRecord(modelName, rawData, options)` called
180
+ 2. Auto-increment ID assigned if missing (or pending MySQL ID in MySQL mode)
181
+ 3. Model + Serializer instantiated, Record wrapper created
182
+ 4. `record.serialize()` maps raw data through serializer paths, applies transforms, wires relationships
183
+ 5. Record stored in `Store` by ID
184
+ 6. Global, pending hasMany, and pending belongsTo relationships fulfilled
185
+
186
+ ## API / Routes
187
+
188
+ Auto-generated REST endpoints for each model with an access class:
189
+
190
+ | Method | Route Pattern | Operation | Notes |
191
+ |--------|--------------|-----------|-------|
192
+ | GET | `/:models` | List collection | Supports `?filter[path]=value`, `?fields[model]=attr1,attr2`, `?include=rel1,rel2.nested` |
193
+ | GET | `/:models/:id` | Get single record | Same query params as list; returns 404 if not found |
194
+ | POST | `/:models` | Create record | Body: `{ data: { type, id?, attributes } }`; 400 if no type; 409 if duplicate ID |
195
+ | PATCH | `/:models/:id` | Update record | Body: `{ data: { attributes } }`; updates only provided fields |
196
+ | DELETE | `/:models/:id` | Delete record | Removes from store; no response body |
197
+ | GET | `/:models/:id/:relationship` | Related resource | Returns full related records (array for hasMany, single for belongsTo) |
198
+ | GET | `/:models/:id/relationships/:relationship` | Relationship linkage | Returns JSON:API linkage objects with `links.self` and `links.related` |
199
+
200
+ **Include parameter** supports comma-separated relationships and dot-notation nesting: `?include=owner.pets,traits`
201
+
202
+ **Fields parameter** supports sparse fieldsets: `?fields[animals]=age,size`
203
+
204
+ **Filter parameter** supports dot-path filtering: `?filter[owner]=angela`
205
+
206
+ ## Database & Models
207
+
208
+ ### Model Definition
209
+
210
+ Models extend `Model` and define attributes with `attr(type)` and relationships with `hasMany(model)` / `belongsTo(model)`. Getters become computed properties.
211
+
212
+ ### In-Memory Store
213
+
214
+ `Store.data` is a `Map<modelName, Map<recordId, Record>>`. O(1) lookup by model + ID.
215
+
216
+ ### JSON File Persistence
217
+
218
+ Two modes configured via `db.mode`:
219
+ - **`'file'`** (default): Single `db.json` file
220
+ - **`'directory'`**: Per-collection files in a directory (e.g., `db/animals.json`)
221
+
222
+ Auto-save modes: `'true'` (cron interval), `'false'` (disabled), `'onUpdate'` (after each write operation)
223
+
224
+ ### MySQL Persistence
225
+
226
+ Enabled when `MYSQL_HOST` env var is set. Uses mysql2 connection pool. On init, loads all records from MySQL into the in-memory store. CRUD operations persist to MySQL after in-memory mutation. Supports:
227
+ - Schema introspection from model definitions
228
+ - Migration generation via schema diffs (`.snapshot.json`)
229
+ - Migration apply/rollback with transactions
230
+ - Schema drift detection on startup
231
+ - Auto-increment ID re-keying after INSERT
232
+
233
+ ### Relationship System
234
+
235
+ | Type | Handler | Storage | Resolution |
236
+ |------|---------|---------|------------|
237
+ | `hasMany` | `src/has-many.js` | Array of Records | Inline objects, ID references, or pending queue |
238
+ | `belongsTo` | `src/belongs-to.js` | Single Record or null | Inline object, ID reference, or pending queue |
239
+
240
+ Relationships are bidirectional: creating a `belongsTo` automatically populates the inverse `hasMany`, and vice versa. Pending relationships resolve when the target record is created later.
241
+
242
+ ## Authentication & Authorization
243
+
244
+ Access control is defined via access classes in the configured `paths.access` directory:
245
+
246
+ ```javascript
247
+ export default class GlobalAccess {
248
+ models = ['owner', 'animal']; // or '*' for all
249
+ access(request) {
250
+ // Return false → 403 Forbidden
251
+ // Return ['read', 'create', 'update', 'delete'] → allowed methods
252
+ // Return (record) => boolean → filter function for collections
253
+ }
254
+ }
255
+ ```
256
+
257
+ Method mapping: `GET → 'read'`, `POST → 'create'`, `DELETE → 'delete'`, `PATCH → 'update'`
258
+
259
+ ## Configuration & Environment
260
+
261
+ Located in `config/environment.js`. All values overridable via environment variables:
262
+
263
+ | Variable | Default | Description |
264
+ |----------|---------|-------------|
265
+ | `DB_AUTO_SAVE` | `'false'` | Auto-save mode: `'true'`, `'false'`, or `'onUpdate'` |
266
+ | `DB_FILE` | `'db.json'` | JSON file path |
267
+ | `DB_MODE` | `'file'` | `'file'` or `'directory'` |
268
+ | `DB_DIRECTORY` | `'db'` | Directory name for collection files |
269
+ | `DB_SAVE_INTERVAL` | `3600` | Auto-save interval in seconds |
270
+ | `DB_SCHEMA_PATH` | `'./config/db-schema.js'` | Path to DB schema |
271
+ | `ORM_MODEL_PATH` | `'./models'` | Models directory |
272
+ | `ORM_SERIALIZER_PATH` | `'./serializers'` | Serializers directory |
273
+ | `ORM_TRANSFORM_PATH` | `'./transforms'` | Transforms directory |
274
+ | `ORM_ACCESS_PATH` | `'./access'` | Access classes directory |
275
+ | `ORM_USE_REST_SERVER` | `'true'` | Enable auto REST route generation |
276
+ | `ORM_REST_ROUTE` | `'/'` | Base route for REST endpoints |
277
+ | `MYSQL_HOST` | undefined | Enables MySQL mode when set |
278
+ | `MYSQL_PORT` | `3306` | MySQL port |
279
+ | `MYSQL_USER` | `'root'` | MySQL user |
280
+ | `MYSQL_PASSWORD` | `''` | MySQL password |
281
+ | `MYSQL_DATABASE` | `'stonyx'` | MySQL database name |
282
+ | `MYSQL_CONNECTION_LIMIT` | `10` | Connection pool size |
283
+ | `MYSQL_MIGRATIONS_DIR` | `'migrations'` | Migration files directory |
284
+
285
+ ## Development Guide
286
+
287
+ | Task | Command |
288
+ |------|---------|
289
+ | Install deps | `pnpm install` |
290
+ | Run tests | `stonyx test` (or `npm test`) |
291
+ | Node version | v24.13.0 (see `.nvmrc`) |
292
+ | Generate MySQL migration | `stonyx db:generate-migration <description>` |
293
+ | Apply MySQL migrations | `stonyx db:migrate` |
294
+ | Rollback MySQL migration | `stonyx db:migrate:rollback` |
295
+ | Migration status | `stonyx db:migrate:status` |
296
+ | Migrate DB file→directory | `stonyx db:migrate-to-directory` |
297
+ | Migrate DB directory→file | `stonyx db:migrate-to-file` |
298
+
299
+ **Local development** uses `file:../package-name` references in `package.json` for sibling Stonyx packages.
300
+
301
+ ## Testing
302
+
303
+ | Aspect | Detail |
304
+ |--------|--------|
305
+ | Framework | QUnit via `stonyx test` |
306
+ | Mocking | Sinon |
307
+ | Unit tests | `test/unit/` — transforms, commands, relationships, environment, lifecycle, MySQL startup |
308
+ | Integration tests | `test/integration/` — full ORM pipeline, directory-mode DB |
309
+ | Test fixtures | `test/sample/` — models, serializers, transforms, access, payload, constants |
310
+ | Test config | `test/config/environment.js` |
311
+
312
+ ## Deployment & CI/CD
313
+
314
+ | Pipeline | Trigger | Workflow |
315
+ |----------|---------|----------|
316
+ | CI | Pull requests to `dev` or `main` | Shared via `abofs/stonyx-workflows/.github/workflows/ci.yml@main` |
317
+ | Publish | Push to `main`, manual dispatch | Shared via `abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main` |
318
+
319
+ Published to npm as `@stonyx/orm` with public access and provenance.
320
+
321
+ ## Conventions & Standards
322
+
323
+ - **Files**: kebab-case (e.g., `phone-number.js`)
324
+ - **Models**: PascalCase + `Model` suffix (e.g., `PhoneNumberModel`)
325
+ - **Serializers**: PascalCase + `Serializer` suffix (e.g., `AnimalSerializer`)
326
+ - **Transforms**: Original filename as key (e.g., `animal.js` → `'animal'` transform)
327
+ - **Model names in code**: kebab-case (e.g., `'phone-number'`)
328
+ - **REST routes**: Pluralized, dasherized (e.g., `/phone-numbers`)
329
+ - **Relationship URLs**: Dasherized (e.g., `phoneNumbers` → `/phone-numbers`)
330
+ - **ES Modules**: All files use `import`/`export`
331
+ - **License headers**: Apache 2.0 in core source files
332
+ - **Git workflow**: Feature branches → `dev` → `main`
333
+
334
+ ## Known Issues & Technical Debt
335
+
336
+ See [improvements.md](improvements.md) for detailed findings. Key items:
337
+
338
+ - `config/environment.js` contains a stray `console.log(MYSQL_HOST)` statement
339
+ - `getOrSet` utility is duplicated across `has-many.js` and `belongs-to.js`
340
+ - `getRelationshipInfo()` is duplicated across `orm-request.js`, `schema-introspector.js`, and `meta-request.js`
341
+ - `getCollectionKeys()` and `getDirPath()` are duplicated between `db.js` and `migrate.js`
342
+ - Package exports in `package.json` are missing `./commands` and `./hooks` entries (present in code but not in the outline docs)
343
+ - The outline doc (`/.claude/personal/outline.md`) references files that don't exist (`src/include-parser.js`, `src/include-collector.js`, `stonyx-bootstrap.cjs`) and lists version as `0.1.0` (actual: `0.2.1-beta.1`)