@stonyx/orm 0.2.1-beta.71 → 0.2.1-beta.72

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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-beta.71",
7
+ "version": "0.2.1-beta.72",
8
8
  "description": "",
9
9
  "main": "src/main.js",
10
10
  "type": "module",
@@ -24,6 +24,11 @@
24
24
  "contributors": [
25
25
  "Stone Costa <stone.costa@synamicd.com>"
26
26
  ],
27
+ "files": [
28
+ "src",
29
+ "config",
30
+ "README.md"
31
+ ],
27
32
  "publishConfig": {
28
33
  "access": "public",
29
34
  "provenance": true
@@ -1,44 +0,0 @@
1
- # Stonyx Code Style Rules
2
-
3
- Strict prettier/eslint rules to apply across all Stonyx projects. These will be formalized into an ESLint/Prettier config once enough patterns are collected.
4
-
5
- ---
6
-
7
- ## Rules
8
-
9
- ### 1. Destructure config objects in function signatures
10
-
11
- When a function receives a config/options object and only uses specific properties, destructure them in the function signature rather than accessing them via dot notation in the body.
12
-
13
- **Bad:**
14
- ```javascript
15
- export async function getPool(mysqlConfig) {
16
- pool = mysql.createPool({
17
- host: mysqlConfig.host,
18
- port: mysqlConfig.port,
19
- user: mysqlConfig.user,
20
- password: mysqlConfig.password,
21
- database: mysqlConfig.database,
22
- connectionLimit: mysqlConfig.connectionLimit,
23
- // ...
24
- });
25
- }
26
- ```
27
-
28
- **Good:**
29
- ```javascript
30
- export async function getPool({ host, port, user, password, database, connectionLimit }) {
31
- pool = mysql.createPool({
32
- host,
33
- port,
34
- user,
35
- password,
36
- database,
37
- connectionLimit,
38
- // ...
39
- });
40
- }
41
- ```
42
-
43
- **Source:** PR #14, `src/mysql/connection.js`
44
- **ESLint rule (candidate):** `prefer-destructuring` (with custom config for function parameters)
package/.claude/hooks.md DELETED
@@ -1,250 +0,0 @@
1
- # Middleware Hooks System
2
-
3
- The ORM provides a powerful middleware-based hook system that allows custom logic before and after CRUD operations. **Before hooks can halt operations** by returning a value.
4
-
5
- ## Architecture
6
-
7
- **Hook Registry**: [src/hooks.js](src/hooks.js) - Stores before/after hooks in Maps
8
- **Integration**: [src/orm-request.js](src/orm-request.js) - `_withHooks()` wrapper executes hooks
9
- **Exports**: [src/index.js](src/index.js) - Exports `beforeHook`, `afterHook`, `clearHook`, `clearAllHooks`
10
-
11
- ## API
12
-
13
- ### `beforeHook(operation, model, handler)`
14
-
15
- Register a before hook that runs before the operation executes.
16
-
17
- ```javascript
18
- import { beforeHook } from '@stonyx/orm';
19
-
20
- beforeHook('create', 'animal', (context) => {
21
- // Validate, transform, authorize...
22
- if (invalid) {
23
- return 400; // Halt with status code
24
- }
25
- // Return undefined to continue
26
- });
27
- ```
28
-
29
- **Handler return values:**
30
- - `undefined` / no return - Operation continues
31
- - **Any other value** - Halts operation and returns that value:
32
- - Integer (e.g., `403`) - HTTP status code
33
- - Object - JSON response body
34
-
35
- **Returns:** Unregister function
36
-
37
- ### `afterHook(operation, model, handler)`
38
-
39
- Register an after hook that runs after the operation completes.
40
-
41
- ```javascript
42
- import { afterHook } from '@stonyx/orm';
43
-
44
- afterHook('update', 'animal', (context) => {
45
- console.log(`Updated animal ${context.record.id}`);
46
- // After hooks cannot halt (operation already complete)
47
- });
48
- ```
49
-
50
- **Returns:** Unregister function
51
-
52
- ### `clearHook(operation, model, [type])`
53
-
54
- Clear registered hooks for a specific operation:model.
55
-
56
- ```javascript
57
- import { clearHook } from '@stonyx/orm';
58
-
59
- clearHook('create', 'animal'); // Clear both before and after
60
- clearHook('create', 'animal', 'before'); // Clear only before hooks
61
- clearHook('create', 'animal', 'after'); // Clear only after hooks
62
- ```
63
-
64
- ### `clearAllHooks()`
65
-
66
- Clear all registered hooks (useful for testing).
67
-
68
- ```javascript
69
- import { clearAllHooks } from '@stonyx/orm';
70
-
71
- afterEach(() => {
72
- clearAllHooks();
73
- });
74
- ```
75
-
76
- ## Operations
77
-
78
- - `list` - GET collection (`/animals`)
79
- - `get` - GET single record (`/animals/1`)
80
- - `create` - POST new record (`/animals`)
81
- - `update` - PATCH existing record (`/animals/1`)
82
- - `delete` - DELETE record (`/animals/1`)
83
-
84
- ## Context Object
85
-
86
- Each hook receives a context object:
87
-
88
- ```javascript
89
- {
90
- model: 'animal', // Model name
91
- operation: 'create', // Operation type
92
- request, // Express request object
93
- params, // URL params (e.g., { id: 5 })
94
- body, // Request body (POST/PATCH)
95
- query, // Query parameters
96
- state, // Request state (includes filter for access control)
97
-
98
- // For update/delete operations:
99
- oldState, // Deep copy of record BEFORE operation
100
-
101
- // For after hooks only:
102
- response, // Handler response
103
- record, // Affected record (create/update/get)
104
- records, // All records (list)
105
- recordId, // Record ID (delete only, since record no longer exists)
106
- }
107
- ```
108
-
109
- **Notes:**
110
- - `oldState` is captured via `JSON.parse(JSON.stringify())` before operation executes
111
- - For delete operations, `recordId` is available since the record may no longer exist
112
- - `oldState` enables precise field-level change detection
113
-
114
- ## Implementation Details
115
-
116
- **Hook Wrapper** (`src/orm-request.js`):
117
-
118
- ```javascript
119
- _withHooks(operation, handler) {
120
- return async (request, state) => {
121
- const context = { model, operation, request, params, body, query, state };
122
-
123
- // Capture old state for update/delete
124
- if (operation === 'update' || operation === 'delete') {
125
- const existingRecord = store.get(this.model, getId(request.params));
126
- if (existingRecord) {
127
- context.oldState = JSON.parse(JSON.stringify(existingRecord.__data || existingRecord));
128
- }
129
- }
130
-
131
- // Run before hooks sequentially (can halt by returning a value)
132
- for (const hook of getBeforeHooks(operation, this.model)) {
133
- const result = await hook(context);
134
- if (result !== undefined) {
135
- return result; // Halt - return status/response
136
- }
137
- }
138
-
139
- // Execute main handler
140
- const response = await handler(request, state);
141
-
142
- // Enrich context for after hooks
143
- context.response = response;
144
- context.record = /* fetched from store */;
145
- context.records = /* for list operations */;
146
- context.recordId = /* for delete operations */;
147
-
148
- // Run after hooks sequentially
149
- for (const hook of getAfterHooks(operation, this.model)) {
150
- await hook(context);
151
- }
152
-
153
- return response;
154
- };
155
- }
156
- ```
157
-
158
- ## Usage Examples
159
-
160
- ### Validation (Halting)
161
-
162
- ```javascript
163
- beforeHook('create', 'animal', (context) => {
164
- const { age } = context.body.data.attributes;
165
- if (age < 0) {
166
- return 400; // Halt with Bad Request
167
- }
168
- });
169
- ```
170
-
171
- ### Custom Error Response
172
-
173
- ```javascript
174
- beforeHook('delete', 'animal', (context) => {
175
- const animal = store.get('animal', context.params.id);
176
- if (animal.protected) {
177
- return { errors: [{ detail: 'Cannot delete protected animals' }] };
178
- }
179
- });
180
- ```
181
-
182
- ### Change Detection with oldState
183
-
184
- ```javascript
185
- afterHook('update', 'animal', (context) => {
186
- if (!context.oldState) return;
187
-
188
- // Detect specific field changes
189
- if (context.oldState.owner !== context.record.owner) {
190
- console.log(`Owner changed from ${context.oldState.owner} to ${context.record.owner}`);
191
- }
192
- });
193
- ```
194
-
195
- ### Audit Logging
196
-
197
- ```javascript
198
- afterHook('update', 'animal', async (context) => {
199
- const changes = {};
200
- if (context.oldState) {
201
- for (const [key, newValue] of Object.entries(context.record.__data)) {
202
- if (context.oldState[key] !== newValue) {
203
- changes[key] = { from: context.oldState[key], to: newValue };
204
- }
205
- }
206
- }
207
-
208
- await auditLog.create({
209
- operation: 'update',
210
- model: context.model,
211
- recordId: context.record.id,
212
- changes // { age: { from: 2, to: 3 } }
213
- });
214
- });
215
- ```
216
-
217
- ### Delete Auditing
218
-
219
- ```javascript
220
- afterHook('delete', 'animal', async (context) => {
221
- await auditLog.create({
222
- operation: 'delete',
223
- model: context.model,
224
- recordId: context.recordId,
225
- deletedData: context.oldState // Full snapshot
226
- });
227
- });
228
- ```
229
-
230
- ## Key Differences from Event-Based System
231
-
232
- | Feature | Event-Based (Old) | Middleware-Based (Current) |
233
- |---------|-------------------|---------------------------|
234
- | Execution | Parallel (fire-and-forget) | Sequential |
235
- | Can halt operation | No | Yes (return any value) |
236
- | Error handling | Isolated (logged) | Propagated (halts operation) |
237
- | Middleware order | Not guaranteed | Registration order |
238
- | Context modification | Not reliable | Reliable (sequential) |
239
- | API | `subscribe('before:create:animal')` | `beforeHook('create', 'animal')` |
240
-
241
- ## Testing
242
-
243
- **Location**: `test/integration/orm-test.js`
244
- **Coverage**: Comprehensive hook tests including:
245
- - Before/after hooks for all operations
246
- - Halting with status codes
247
- - Halting with custom response objects
248
- - Sequential execution order
249
- - Unsubscribe functionality
250
- - clearHook functionality
package/.claude/index.md DELETED
@@ -1,292 +0,0 @@
1
- # Stonyx-ORM Guide for Claude
2
-
3
- ## Detailed Guides
4
-
5
- - [Usage Patterns](usage-patterns.md) — Model definitions, serializers, transforms, CRUD, DB schema, persistence, access control, REST API, and include parameters
6
- - [Views](views.md) — Read-only computed views with aggregate helpers, in-memory resolver, and MySQL VIEW auto-generation
7
- - [Middleware Hooks System](hooks.md) — Before/after hooks for CRUD operations, halting, context object, change detection, and testing
8
- - [Code Style Rules](code-style-rules.md) — Strict prettier/eslint rules to apply across all Stonyx projects
9
-
10
- ---
11
-
12
- ## Project Overview
13
-
14
- **stonyx-orm** is a lightweight Object-Relational Mapping (ORM) library designed specifically for the Stonyx framework. It provides structured data modeling, relationship management, serialization, and persistence to JSON files, with optional REST API integration.
15
-
16
- ## Core Problem It Solves
17
-
18
- 1. **Data Modeling**: Clean, type-safe model definitions with attributes and relationships
19
- 2. **Data Serialization**: Transforms messy third-party data into structured model instances
20
- 3. **Relationship Management**: Automatic bidirectional relationships (hasMany, belongsTo)
21
- 4. **Data Persistence**: File-based JSON storage with auto-save
22
- 5. **REST API Generation**: Auto-generated RESTful endpoints with access control
23
- 6. **Data Transformation**: Custom type conversion and formatting
24
- 7. **Middleware Hooks**: Before/after hooks for all CRUD operations with halting capability
25
- 8. **Views**: Read-only computed projections over model data with aggregate helpers (count, avg, sum, min, max)
26
-
27
- ---
28
-
29
- ## Architecture Overview
30
-
31
- ### Key Components
32
-
33
- 1. **Orm** ([src/main.js](src/main.js)) - Singleton that initializes and manages the entire system
34
- 2. **Store** ([src/store.js](src/store.js)) - In-memory storage (nested Maps: `Map<modelName, Map<recordId, record>>`)
35
- 3. **Model** ([src/model.js](src/model.js)) - Base class for all models
36
- 4. **Record** ([src/record.js](src/record.js)) - Individual model instances
37
- 5. **Serializer** ([src/serializer.js](src/serializer.js)) - Maps raw data to model format
38
- 6. **DB** ([src/db.js](src/db.js)) - JSON file persistence layer
39
- 7. **Relationships** ([src/has-many.js](src/has-many.js), [src/belongs-to.js](src/belongs-to.js)) - Relationship handlers
40
- 8. **Include Logic** (inline in [src/orm-request.js](src/orm-request.js)) - Parses include query params, traverses relationships, collects and deduplicates included records
41
- 9. **Hooks** ([src/hooks.js](src/hooks.js)) - Middleware-based hook registry for CRUD lifecycle
42
- 10. **MySQL Driver** ([src/mysql/mysql-db.js](src/mysql/mysql-db.js)) - MySQL persistence, migrations, schema introspection. Loads records in topological order. `_rowToRawData()` converts TINYINT(1) → boolean, remaps FK columns, strips timestamps.
43
- 11. **View** ([src/view.js](src/view.js)) - Read-only base class for computed views (does NOT extend Model)
44
- 12. **Aggregates** ([src/aggregates.js](src/aggregates.js)) - AggregateProperty class + helper functions (count, avg, sum, min, max)
45
- 13. **ViewResolver** ([src/view-resolver.js](src/view-resolver.js)) - In-memory view resolver (iterates source model, computes aggregates + resolve map)
46
-
47
- ### Project Structure
48
-
49
- ```
50
- stonyx-orm/
51
- ├── src/
52
- │ ├── index.js # Main exports (includes hook functions)
53
- │ ├── main.js # Orm class
54
- │ ├── model.js # Base Model
55
- │ ├── record.js # Record instances
56
- │ ├── serializer.js # Base Serializer
57
- │ ├── store.js # In-memory storage
58
- │ ├── db.js # JSON persistence
59
- │ ├── attr.js # Attribute helper (Proxy-based)
60
- │ ├── has-many.js # One-to-many relationships
61
- │ ├── belongs-to.js # Many-to-one relationships
62
- │ ├── relationships.js # Relationship registry
63
- │ ├── manage-record.js # createRecord/updateRecord
64
- │ ├── model-property.js # Transform handler
65
- │ ├── transforms.js # Built-in transforms
66
- │ ├── hooks.js # Middleware hook registry
67
- │ ├── setup-rest-server.js # REST integration
68
- │ ├── orm-request.js # CRUD request handler with hooks + includes
69
- │ ├── meta-request.js # Meta endpoint (dev only)
70
- │ ├── migrate.js # JSON DB mode migration (file <-> directory)
71
- │ ├── commands.js # CLI commands (db:migrate-*, etc.)
72
- │ ├── utils.js # Pluralize wrapper for dasherized names
73
- │ ├── plural-registry.js # Plural name registry (populated at init, supports Model.pluralName overrides)
74
- │ ├── view.js # View base class (read-only, source, resolve, memory)
75
- │ ├── aggregates.js # AggregateProperty + count/avg/sum/min/max helpers
76
- │ ├── view-resolver.js # In-memory view resolver
77
- │ ├── exports/
78
- │ │ └── db.js # Convenience re-export of DB instance
79
- │ └── mysql/
80
- │ ├── mysql-db.js # MySQL driver (CRUD persistence, record loading)
81
- │ ├── connection.js # mysql2 connection pool
82
- │ ├── query-builder.js # SQL builders (INSERT/UPDATE/DELETE/SELECT) with identifier validation
83
- │ ├── schema-introspector.js # Model-to-MySQL schema introspection
84
- │ ├── migration-generator.js # Schema diff and .sql migration generation
85
- │ ├── migration-runner.js # Migration apply/rollback with transactions
86
- │ └── type-map.js # ORM attr types -> MySQL column types (supports custom transform mysqlType)
87
- ├── config/
88
- │ └── environment.js # Default configuration
89
- ├── test/
90
- │ ├── integration/ # Integration tests
91
- │ ├── unit/ # Unit tests
92
- │ └── sample/ # Test fixtures
93
- │ ├── models/ # Example models
94
- │ ├── serializers/ # Example serializers
95
- │ ├── transforms/ # Custom transforms
96
- │ ├── access/ # Access control
97
- │ ├── views/ # Example views
98
- │ ├── db-schema.js # DB schema
99
- │ └── payload.js # Test data
100
- └── package.json
101
- ```
102
-
103
- ---
104
-
105
- ## Configuration
106
-
107
- Located in [config/environment.js](config/environment.js), overridable via environment variables:
108
-
109
- ```javascript
110
- config.orm = {
111
- paths: {
112
- model: './models',
113
- serializer: './serializers',
114
- transform: './transforms',
115
- access: './access',
116
- view: './views'
117
- },
118
- db: {
119
- autosave: 'false',
120
- file: 'db.json',
121
- mode: 'file', // 'file' (single db.json) or 'directory' (one file per collection)
122
- directory: 'db', // directory name for collection files when mode is 'directory'
123
- saveInterval: 3600,
124
- schema: './config/db-schema.js'
125
- },
126
- restServer: {
127
- enabled: 'true',
128
- route: '/'
129
- }
130
- }
131
- ```
132
-
133
- ---
134
-
135
- ## Storage Modes
136
-
137
- The ORM supports two storage modes, configured via `db.mode`:
138
-
139
- - **`'file'`** (default): All data is stored in a single `db.json` file.
140
- - **`'directory'`**: Each collection is stored as a separate file in the configured directory — `{directory}/{collection}.json` (e.g., `db/animals.json`, `db/owners.json`). The main `db.json` is kept as a skeleton with empty arrays.
141
-
142
- **Migration CLI commands:**
143
- - `stonyx-db-file-to-directory` — Splits a single `db.json` into per-collection files in the directory.
144
- - `stonyx-db-directory-to-file` — Merges per-collection files back into a single `db.json`.
145
-
146
- **Mode validation:** On startup, the ORM warns if the configured mode doesn't match the actual file state (e.g., mode is `'file'` but a `db/` directory exists, or mode is `'directory'` but no directory is found).
147
-
148
- ---
149
-
150
- ## Design Patterns
151
-
152
- 1. **Singleton**: Orm, Store, DB classes
153
- 2. **Proxy**: `attr()` uses Proxies for type-safe access
154
- 3. **Registry**: Relationships in nested Maps
155
- 4. **Factory**: `createRecord()` function
156
- 5. **Observer**: Auto-save via Cron
157
- 6. **Middleware**: Hook system with halting capability
158
- 7. **Convention over Configuration**: Auto-discovery by naming
159
-
160
- **Naming Conventions:**
161
- - Models: `{PascalCase}Model` (e.g., `AnimalModel`)
162
- - Serializers: `{PascalCase}Serializer` (e.g., `AnimalSerializer`)
163
- - Transforms: Original filename (e.g., `animal.js`)
164
- - Plural names: Auto-pluralized by default (e.g., `animal` → `animals`). Override with `static pluralName` on the model class (e.g., `static pluralName = 'people'`). All call sites use `getPluralName()` from the plural registry, **not** `pluralize()` directly.
165
-
166
- ---
167
-
168
- ## Testing
169
-
170
- **Test Runner**: QUnit via `stonyx test` (auto-bootstraps and runs `test/**/*-test.js`)
171
-
172
- **Test Structure:**
173
- - **Integration**: [test/integration/orm-test.js](test/integration/orm-test.js) - Full pipeline test
174
- - **Unit**: [test/unit/transforms/](test/unit/transforms/) - Transform tests
175
- - **Sample**: [test/sample/](test/sample/) - Test fixtures
176
-
177
- **Key Test Data:**
178
- - [test/sample/payload.js](test/sample/payload.js) - Raw vs serialized data
179
- - Demonstrates transformation from messy external data to clean models
180
-
181
- ---
182
-
183
- ## Critical Files for Common Tasks
184
-
185
- **Understanding Core Behavior:**
186
- - [src/main.js](src/main.js) - Initialization flow
187
- - [src/store.js](src/store.js) - Record storage/retrieval
188
- - [src/manage-record.js](src/manage-record.js) - CRUD operations
189
-
190
- **Understanding Relationships:**
191
- - [src/relationships.js](src/relationships.js) - Registry system
192
- - [src/has-many.js](src/has-many.js) - One-to-many logic
193
- - [src/belongs-to.js](src/belongs-to.js) - Many-to-one logic
194
-
195
- **Understanding Data Flow:**
196
- - [src/serializer.js](src/serializer.js) - Raw → Model mapping
197
- - [src/model-property.js](src/model-property.js) - Transform application
198
- - [src/transforms.js](src/transforms.js) - Built-in transforms
199
-
200
- **Understanding REST API:**
201
- - [src/setup-rest-server.js](src/setup-rest-server.js) - Endpoint registration
202
- - [src/orm-request.js](src/orm-request.js) - Request handling with hooks
203
-
204
- **Understanding Hooks:**
205
- - [src/hooks.js](src/hooks.js) - Hook registry (beforeHooks, afterHooks Maps)
206
- - [src/orm-request.js](src/orm-request.js) - `_withHooks()` wrapper
207
-
208
- ---
209
-
210
- ## Key Insights
211
-
212
- **Strengths:**
213
- - Zero-config REST API generation
214
- - Clean declarative model definitions
215
- - Automatic relationship management
216
- - File-based (no database setup needed)
217
- - Flexible serialization for messy data
218
- - Middleware hooks with halting capability
219
-
220
- **Use Cases:**
221
- - Rapid prototyping
222
- - Small to medium applications
223
- - Third-party API consumption with normalization
224
- - Development/testing environments
225
- - Applications needing quick REST APIs
226
-
227
- **Dependencies:**
228
- - `stonyx` - Main framework (peer)
229
- - `@stonyx/utils` - File/string utilities
230
- - `@stonyx/events` - Pub/sub event system (event names initialized on startup; hooks use separate middleware-based registry)
231
- - `@stonyx/cron` - Scheduled tasks (used by DB for auto-save)
232
- - `@stonyx/rest-server` - REST API
233
- - `mysql2` - Optional peer dependency for MySQL mode
234
-
235
- ---
236
-
237
- ## Quick Reference
238
-
239
- **Import the ORM:**
240
- ```javascript
241
- import {
242
- Orm, Model, View, Serializer, attr, hasMany, belongsTo,
243
- createRecord, updateRecord, store,
244
- beforeHook, afterHook, clearHook, clearAllHooks,
245
- count, avg, sum, min, max
246
- } from '@stonyx/orm';
247
- ```
248
-
249
- **Initialize:**
250
- ```javascript
251
- const orm = new Orm({ dbType: 'json' });
252
- await orm.init();
253
- ```
254
-
255
- **Access Database:**
256
- ```javascript
257
- await Orm.db.save();
258
- ```
259
-
260
- **Common Operations:**
261
- ```javascript
262
- // Create
263
- const record = createRecord('modelName', data);
264
-
265
- // Read
266
- const record = store.get('modelName', id);
267
- const all = store.get('modelName');
268
-
269
- // Update
270
- updateRecord(record, newData);
271
-
272
- // Delete
273
- store.remove('modelName', id);
274
- ```
275
-
276
- **Register Hooks:**
277
- ```javascript
278
- // Before hook (can halt)
279
- const unsubscribe = beforeHook('create', 'animal', (ctx) => {
280
- if (invalid) return 400;
281
- });
282
-
283
- // After hook
284
- afterHook('update', 'animal', (ctx) => {
285
- console.log('Updated:', ctx.record.id);
286
- });
287
-
288
- // Cleanup
289
- unsubscribe(); // Remove specific hook
290
- clearHook('create', 'animal'); // Clear all hooks for operation
291
- clearAllHooks(); // Clear everything
292
- ```