@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.
@@ -1,578 +0,0 @@
1
- # Stonyx-ORM Guide for Claude
2
-
3
- ## Project Overview
4
-
5
- **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.
6
-
7
- ## Core Problem It Solves
8
-
9
- 1. **Data Modeling**: Clean, type-safe model definitions with attributes and relationships
10
- 2. **Data Serialization**: Transforms messy third-party data into structured model instances
11
- 3. **Relationship Management**: Automatic bidirectional relationships (hasMany, belongsTo)
12
- 4. **Data Persistence**: File-based JSON storage with auto-save
13
- 5. **REST API Generation**: Auto-generated RESTful endpoints with access control
14
- 6. **Data Transformation**: Custom type conversion and formatting
15
- 7. **Event System**: Pub/sub events for CRUD operations with before/after hooks
16
-
17
- ---
18
-
19
- ## Architecture Overview
20
-
21
- ### Key Components
22
-
23
- 1. **Orm** ([src/main.js](src/main.js)) - Singleton that initializes and manages the entire system
24
- 2. **Store** ([src/store.js](src/store.js)) - In-memory storage (nested Maps: `Map<modelName, Map<recordId, record>>`)
25
- 3. **Model** ([src/model.js](src/model.js)) - Base class for all models
26
- 4. **Record** ([src/record.js](src/record.js)) - Individual model instances
27
- 5. **Serializer** ([src/serializer.js](src/serializer.js)) - Maps raw data to model format
28
- 6. **DB** ([src/db.js](src/db.js)) - JSON file persistence layer
29
- 7. **Relationships** ([src/has-many.js](src/has-many.js), [src/belongs-to.js](src/belongs-to.js)) - Relationship handlers
30
- 8. **Include Parser** ([src/include-parser.js](src/include-parser.js)) - Parses include query params
31
- 9. **Include Collector** ([src/include-collector.js](src/include-collector.js)) - Collects and deduplicates included records
32
- 10. **Events** (from `@stonyx/events`) - Pub/sub event system for CRUD hooks
33
-
34
- ### Project Structure
35
-
36
- ```
37
- stonyx-orm/
38
- ├── src/
39
- │ ├── index.js # Main exports (includes ormEvents)
40
- │ ├── main.js # Orm class
41
- │ ├── model.js # Base Model
42
- │ ├── record.js # Record instances
43
- │ ├── serializer.js # Base Serializer
44
- │ ├── store.js # In-memory storage (emits delete events)
45
- │ ├── db.js # JSON persistence
46
- │ ├── attr.js # Attribute helper (Proxy-based)
47
- │ ├── has-many.js # One-to-many relationships
48
- │ ├── belongs-to.js # Many-to-one relationships
49
- │ ├── relationships.js # Relationship registry
50
- │ ├── manage-record.js # createRecord/updateRecord (emits create events)
51
- │ ├── model-property.js # Transform handler
52
- │ ├── transforms.js # Built-in transforms
53
- │ ├── setup-rest-server.js # REST integration
54
- │ ├── orm-request.js # CRUD request handler (emits update events)
55
- │ └── meta-request.js # Meta endpoint (dev only)
56
- ├── config/
57
- │ └── environment.js # Default configuration
58
- ├── test/
59
- │ ├── integration/ # Integration tests
60
- │ ├── unit/ # Unit tests
61
- │ └── sample/ # Test fixtures
62
- │ ├── models/ # Example models
63
- │ ├── serializers/ # Example serializers
64
- │ ├── transforms/ # Custom transforms
65
- │ ├── access/ # Access control
66
- │ ├── db-schema.js # DB schema
67
- │ └── payload.js # Test data
68
- └── package.json
69
- ```
70
-
71
- ---
72
-
73
- ## Usage Patterns
74
-
75
- ### 1. Model Definition
76
-
77
- Models extend `Model` and use decorators for attributes and relationships:
78
-
79
- ```javascript
80
- // test/sample/models/animal.js
81
- import { Model, attr, belongsTo, hasMany } from '@stonyx/orm';
82
-
83
- export default class AnimalModel extends Model {
84
- // Attributes with type transforms
85
- type = attr('animal'); // Custom transform
86
- age = attr('number'); // Built-in transform
87
- size = attr('string');
88
-
89
- // Relationships
90
- owner = belongsTo('owner'); // Many-to-one
91
- traits = hasMany('trait'); // One-to-many
92
-
93
- // Computed properties
94
- get tag() {
95
- return `${this.owner.id}'s ${this.size} animal`;
96
- }
97
- }
98
- ```
99
-
100
- **Key Points:**
101
- - Use `attr(type)` for simple attributes
102
- - Use `belongsTo(modelName)` for many-to-one
103
- - Use `hasMany(modelName)` for one-to-many
104
- - Getters work as computed properties
105
- - Relationships auto-establish bidirectionally
106
-
107
- ### 2. Serializers (Data Transformation)
108
-
109
- Serializers map raw data paths to model properties:
110
-
111
- ```javascript
112
- // test/sample/serializers/animal.js
113
- import { Serializer } from '@stonyx/orm';
114
-
115
- export default class AnimalSerializer extends Serializer {
116
- map = {
117
- // Nested path mapping
118
- age: 'details.age',
119
- size: 'details.c',
120
- owner: 'details.location.owner',
121
-
122
- // Custom transformation function
123
- traits: ['details', ({ x:color }) => {
124
- const traits = [{ id: 1, type: 'habitat', value: 'farm' }];
125
- if (color) traits.push({ id: 2, type: 'color', value: color });
126
- return traits;
127
- }]
128
- }
129
- }
130
- ```
131
-
132
- **Key Points:**
133
- - `map` object defines field mappings
134
- - Supports nested paths (`'details.age'`)
135
- - Custom functions for complex transformations
136
- - Handlers receive raw data subset
137
-
138
- ### 3. Custom Transforms
139
-
140
- Transforms convert data types:
141
-
142
- ```javascript
143
- // test/sample/transforms/animal.js
144
- const codeEnumMap = { 'dog': 1, 'cat': 2, 'bird': 3 };
145
-
146
- export default function(value) {
147
- return codeEnumMap[value] || 0;
148
- }
149
- ```
150
-
151
- **Built-in Transforms:**
152
- - Type: `boolean`, `number`, `float`, `string`, `date`, `timestamp`
153
- - Math: `round`, `ceil`, `floor`
154
- - String: `trim`, `uppercase`
155
- - Utility: `passthrough`
156
-
157
- ### 4. CRUD Operations
158
-
159
- ```javascript
160
- import { createRecord, updateRecord, store } from '@stonyx/orm';
161
-
162
- // Create
163
- createRecord('owner', { id: 'bob', age: 30 });
164
-
165
- // Read
166
- const owner = store.get('owner', 'bob');
167
- const allOwners = store.get('owner');
168
-
169
- // Update
170
- updateRecord(owner, { age: 31 });
171
- // Or direct: owner.age = 31;
172
-
173
- // Delete
174
- store.remove('owner', 'bob');
175
- ```
176
-
177
- ### 5. Database Schema
178
-
179
- The DB schema is a Model defining top-level collections:
180
-
181
- ```javascript
182
- // test/sample/db-schema.js
183
- import { Model, hasMany } from '@stonyx/orm';
184
-
185
- export default class DBModel extends Model {
186
- owners = hasMany('owner');
187
- animals = hasMany('animal');
188
- traits = hasMany('trait');
189
- }
190
- ```
191
-
192
- ### 6. Persistence
193
-
194
- ```javascript
195
- import Orm from '@stonyx/orm';
196
-
197
- // Save to file
198
- await Orm.db.save();
199
-
200
- // Data auto-serializes to JSON file
201
- // Reload using createRecord with serialize:false, transform:false
202
- ```
203
-
204
- ### 7. Access Control
205
-
206
- ```javascript
207
- // test/sample/access/global-access.js
208
- export default class GlobalAccess {
209
- models = ['owner', 'animal']; // or '*' for all
210
-
211
- access(request) {
212
- // Deny specific access
213
- if (request.url.endsWith('/owner/angela')) return false;
214
-
215
- // Filter collections
216
- if (request.url.endsWith('/owner')) {
217
- return record => record.id !== 'angela';
218
- }
219
-
220
- // Grant CRUD permissions
221
- return ['read', 'create', 'update', 'delete'];
222
- }
223
- }
224
- ```
225
-
226
- ### 8. REST API (Auto-generated)
227
-
228
- ```javascript
229
- // Endpoints auto-generated for models:
230
- // GET /owners - List all
231
- // GET /owners/:id - Get one
232
- // POST /animals - Create
233
- // PATCH /animals/:id - Update
234
- // DELETE /animals/:id - Delete
235
- ```
236
-
237
- ### 9. Include Parameter (Sideloading)
238
-
239
- GET endpoints support sideloading related records with **nested relationship traversal**:
240
-
241
- ```javascript
242
- // Single-level includes
243
- GET /animals/1?include=owner,traits
244
-
245
- // Nested includes (NEW!)
246
- GET /animals/1?include=owner.pets,owner.company
247
-
248
- // Deep nesting (3+ levels)
249
- GET /scenes/e001-s001?include=slides.dialogue.character
250
-
251
- // Response structure (unchanged)
252
- {
253
- data: { type: 'animal', id: 1, attributes: {...}, relationships: {...} },
254
- included: [
255
- { type: 'owner', id: 'angela', ... },
256
- { type: 'animal', id: 7, ... }, // owner's other pets
257
- { type: 'animal', id: 11, ... }, // owner's other pets
258
- { type: 'company', id: 'acme', ... } // owner's company (if requested)
259
- ]
260
- }
261
- ```
262
-
263
- **How Nested Includes Work:**
264
- 1. Query param parsed into path segments: `owner.pets` → `[['owner'], ['owner', 'pets'], ['traits']]`
265
- 2. `traverseIncludePath()` recursively traverses relationships depth-first
266
- 3. Deduplication still by type+id (no duplicates in included array)
267
- 4. Gracefully handles null/missing relationships at any depth
268
- 5. Each included record gets full `toJSON()` representation
269
-
270
- **Key Functions:**
271
- - `parseInclude()` - Splits comma-separated includes and parses nested paths
272
- - `traverseIncludePath()` - Recursively traverses relationship paths
273
- - `collectIncludedRecords()` - Orchestrates traversal and deduplication
274
- - All implemented in [src/orm-request.js](src/orm-request.js)
275
-
276
- ---
277
-
278
- ## Configuration
279
-
280
- Located in [config/environment.js](config/environment.js), overridable via environment variables:
281
-
282
- ```javascript
283
- config.orm = {
284
- paths: {
285
- model: './models',
286
- serializer: './serializers',
287
- transform: './transforms',
288
- access: './access'
289
- },
290
- db: {
291
- autosave: 'false',
292
- file: 'db.json',
293
- saveInterval: 3600,
294
- schema: './config/db-schema.js'
295
- },
296
- restServer: {
297
- enabled: 'true',
298
- route: '/'
299
- }
300
- }
301
- ```
302
-
303
- ---
304
-
305
- ## Design Patterns
306
-
307
- 1. **Singleton**: Orm, Store, DB classes
308
- 2. **Proxy**: `attr()` uses Proxies for type-safe access
309
- 3. **Registry**: Relationships in nested Maps
310
- 4. **Factory**: `createRecord()` function
311
- 5. **Observer**: Auto-save via Cron
312
- 6. **Convention over Configuration**: Auto-discovery by naming
313
-
314
- **Naming Conventions:**
315
- - Models: `{PascalCase}Model` (e.g., `AnimalModel`)
316
- - Serializers: `{PascalCase}Serializer` (e.g., `AnimalSerializer`)
317
- - Transforms: Original filename (e.g., `animal.js`)
318
-
319
- ---
320
-
321
- ## Testing
322
-
323
- **Test Runner**: QUnit with bootstrap
324
- **Bootstrap**: [stonyx-bootstrap.cjs](stonyx-bootstrap.cjs) - Configures paths for test environment
325
-
326
- **Test Structure:**
327
- - **Integration**: [test/integration/orm-test.js](test/integration/orm-test.js) - Full pipeline test
328
- - **Unit**: [test/unit/transforms/](test/unit/transforms/) - Transform tests
329
- - **Sample**: [test/sample/](test/sample/) - Test fixtures
330
-
331
- **Key Test Data:**
332
- - [test/sample/payload.js](test/sample/payload.js) - Raw vs serialized data
333
- - Demonstrates transformation from messy external data to clean models
334
-
335
- ---
336
-
337
- ## Common Workflows
338
-
339
- ### Making Updates to Models
340
-
341
- 1. Read existing model in [test/sample/models/](test/sample/models/)
342
- 2. Understand attribute types and relationships
343
- 3. Check if serializer exists in [test/sample/serializers/](test/sample/serializers/)
344
- 4. Update model definition
345
- 5. Update serializer if data mapping changes
346
- 6. Run tests: `npm test`
347
-
348
- ### Adding New Features
349
-
350
- 1. Check [src/index.js](src/index.js) for exports
351
- 2. Understand Store pattern in [src/store.js](src/store.js)
352
- 3. Review Record lifecycle in [src/record.js](src/record.js)
353
- 4. Add feature following existing patterns
354
- 5. Update integration tests
355
-
356
- ### Debugging Issues
357
-
358
- 1. Check Store contents: `store.get(modelName)`
359
- 2. Verify relationships: `relationships.registry`
360
- 3. Test serialization: Create record and inspect `record.__data`
361
- 4. Check transform application in [src/model-property.js](src/model-property.js)
362
- 5. Review test fixtures for expected behavior
363
-
364
- ---
365
-
366
- ## Key Insights
367
-
368
- **Strengths:**
369
- - Zero-config REST API generation
370
- - Clean declarative model definitions
371
- - Automatic relationship management
372
- - File-based (no database setup needed)
373
- - Flexible serialization for messy data
374
-
375
- **Use Cases:**
376
- - Rapid prototyping
377
- - Small to medium applications
378
- - Third-party API consumption with normalization
379
- - Development/testing environments
380
- - Applications needing quick REST APIs
381
-
382
- **Dependencies:**
383
- - `stonyx` - Main framework (peer)
384
- - `@stonyx/utils` - File/string utilities
385
- - `@stonyx/events` - Pub/sub event system for CRUD hooks
386
- - `@stonyx/cron` - Scheduled tasks (used by DB for auto-save)
387
- - `@stonyx/rest-server` - REST API
388
-
389
- ## Event System
390
-
391
- The ORM emits events during CRUD operations, allowing applications to hook into the data lifecycle.
392
-
393
- ### Event Architecture
394
-
395
- **Event Source**: `@stonyx/events` (Events class)
396
- **Integration**: `src/index.js` initializes singleton `ormEvents` instance
397
- **Event Registration**: 6 events registered on initialization:
398
- - `create:before`, `create:after`
399
- - `update:before`, `update:after`
400
- - `delete:before`, `delete:after`
401
-
402
- ### Event Emission Points
403
-
404
- **CREATE Events** - `src/manage-record.js`:
405
- ```javascript
406
- // Line ~34: Before serialization
407
- Events.instance?.emit('create:before', {
408
- model: modelName,
409
- record,
410
- rawData,
411
- options
412
- });
413
-
414
- // Line ~88: After record fully created
415
- Events.instance?.emit('create:after', {
416
- model: modelName,
417
- record,
418
- data: record.__data,
419
- rawData,
420
- options
421
- });
422
- ```
423
-
424
- **UPDATE Events** - `src/orm-request.js` (PATCH handler):
425
- ```javascript
426
- // Line ~155: Before applying updates
427
- const oldData = { ...record.__data };
428
- Events.instance?.emit('update:before', {
429
- model,
430
- record,
431
- data: record.__data,
432
- oldData,
433
- rawData: attributes,
434
- options: {}
435
- });
436
-
437
- // Line ~173: After updates applied
438
- Events.instance?.emit('update:after', {
439
- model,
440
- record,
441
- data: record.__data,
442
- oldData,
443
- rawData: attributes,
444
- options: {}
445
- });
446
- ```
447
-
448
- **DELETE Events** - `src/store.js` (unloadRecord):
449
- ```javascript
450
- // Line ~45: Before cleanup
451
- Events.instance?.emit('delete:before', {
452
- model,
453
- record,
454
- data: { ...record.__data },
455
- options
456
- });
457
-
458
- // Line ~65: After deletion
459
- Events.instance?.emit('delete:after', {
460
- model,
461
- record: null,
462
- data: null,
463
- options
464
- });
465
- ```
466
-
467
- ### Design Decisions
468
-
469
- **Fire Without Await**: Events are emitted without `await` to maintain backward compatibility
470
- - `createRecord()` remains synchronous
471
- - `unloadRecord()` remains synchronous
472
- - Synchronous handlers execute immediately
473
- - Async handlers run in background
474
-
475
- **Optional Chaining**: Uses `Events.instance?.emit()` for zero overhead when not used
476
-
477
- **Error Isolation**: Event errors are caught in Events class, never crash ORM operations
478
-
479
- ### Usage Patterns
480
-
481
- **Auditing**:
482
- ```javascript
483
- import { ormEvents } from '@stonyx/orm';
484
-
485
- ormEvents.subscribe('update:after', ({ model, data, oldData }) => {
486
- auditLog.write({
487
- action: 'update',
488
- model,
489
- changes: diff(oldData, data),
490
- timestamp: Date.now()
491
- });
492
- });
493
- ```
494
-
495
- **Auto-Timestamps**:
496
- ```javascript
497
- ormEvents.subscribe('create:before', ({ record }) => {
498
- record.__data.createdAt = new Date().toISOString();
499
- });
500
-
501
- ormEvents.subscribe('update:before', ({ record }) => {
502
- record.__data.updatedAt = new Date().toISOString();
503
- });
504
- ```
505
-
506
- **Cache Invalidation**:
507
- ```javascript
508
- ormEvents.subscribe('update:after', ({ model, record }) => {
509
- cache.invalidate(`${model}:${record.id}`);
510
- });
511
-
512
- ormEvents.subscribe('delete:after', ({ model, record }) => {
513
- if (record) cache.invalidate(`${model}:${record.id}`);
514
- });
515
- ```
516
-
517
- ---
518
-
519
- ## Critical Files for Common Tasks
520
-
521
- **Understanding Core Behavior:**
522
- - [src/main.js](src/main.js) - Initialization flow
523
- - [src/store.js](src/store.js) - Record storage/retrieval
524
- - [src/manage-record.js](src/manage-record.js) - CRUD operations
525
-
526
- **Understanding Relationships:**
527
- - [src/relationships.js](src/relationships.js) - Registry system
528
- - [src/has-many.js](src/has-many.js) - One-to-many logic
529
- - [src/belongs-to.js](src/belongs-to.js) - Many-to-one logic
530
-
531
- **Understanding Data Flow:**
532
- - [src/serializer.js](src/serializer.js) - Raw → Model mapping
533
- - [src/model-property.js](src/model-property.js) - Transform application
534
- - [src/transforms.js](src/transforms.js) - Built-in transforms
535
-
536
- **Understanding REST API:**
537
- - [src/setup-rest-server.js](src/setup-rest-server.js) - Endpoint registration
538
- - [src/orm-request.js](src/orm-request.js) - Request handling
539
-
540
- ---
541
-
542
- ## Quick Reference
543
-
544
- **Import the ORM:**
545
- ```javascript
546
- import { Orm, Model, Serializer, attr, hasMany, belongsTo, createRecord, updateRecord, store, ormEvents } from '@stonyx/orm';
547
- ```
548
-
549
- **Initialize:**
550
- ```javascript
551
- const orm = new Orm({ dbType: 'json' });
552
- await orm.init();
553
- ```
554
-
555
- **Access Database:**
556
- ```javascript
557
- await Orm.db.save();
558
- ```
559
-
560
- **Common Operations:**
561
- ```javascript
562
- // Create
563
- const record = createRecord('modelName', data);
564
-
565
- // Read
566
- const record = store.get('modelName', id);
567
- const all = store.get('modelName');
568
-
569
- // Update
570
- updateRecord(record, newData);
571
-
572
- // Delete
573
- store.remove('modelName', id);
574
- ```
575
-
576
- ---
577
-
578
- This guide provides the foundation for understanding stonyx-orm's architecture, patterns, and usage. Refer to test files for concrete examples and the source code for implementation details.
@@ -1,30 +0,0 @@
1
- /**
2
- * commonJS Bootstrap loading - Stonyx must be loaded first, prior to the rest of the application
3
- */
4
- const { default:Stonyx } = require('stonyx');
5
- const { default:config } = require('./config/environment.js');
6
-
7
- // Override paths for tests
8
- Object.assign(config.paths, {
9
- access: './test/sample/access',
10
- model: './test/sample/models',
11
- serializer: './test/sample/serializers',
12
- transform: './test/sample/transforms'
13
- })
14
-
15
- // Override db settings for tests
16
- Object.assign(config.db, {
17
- file: './test/sample/db.json',
18
- schema: './test/sample/db-schema.js'
19
- });
20
-
21
- // Create restServer module path for tests
22
- config.modules = {
23
- restServer: {
24
- dir: './test/sample/requests'
25
- }
26
- }
27
-
28
- new Stonyx(config, __dirname);
29
-
30
- module.exports = Stonyx;