@stonyx/orm 0.1.0 → 0.2.0

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,447 @@
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
+
16
+ ---
17
+
18
+ ## Architecture Overview
19
+
20
+ ### Key Components
21
+
22
+ 1. **Orm** ([src/main.js](src/main.js)) - Singleton that initializes and manages the entire system
23
+ 2. **Store** ([src/store.js](src/store.js)) - In-memory storage (nested Maps: `Map<modelName, Map<recordId, record>>`)
24
+ 3. **Model** ([src/model.js](src/model.js)) - Base class for all models
25
+ 4. **Record** ([src/record.js](src/record.js)) - Individual model instances
26
+ 5. **Serializer** ([src/serializer.js](src/serializer.js)) - Maps raw data to model format
27
+ 6. **DB** ([src/db.js](src/db.js)) - JSON file persistence layer
28
+ 7. **Relationships** ([src/has-many.js](src/has-many.js), [src/belongs-to.js](src/belongs-to.js)) - Relationship handlers
29
+ 8. **Include Parser** ([src/include-parser.js](src/include-parser.js)) - Parses include query params
30
+ 9. **Include Collector** ([src/include-collector.js](src/include-collector.js)) - Collects and deduplicates included records
31
+
32
+ ### Project Structure
33
+
34
+ ```
35
+ stonyx-orm/
36
+ ├── src/
37
+ │ ├── index.js # Main exports
38
+ │ ├── main.js # Orm class
39
+ │ ├── model.js # Base Model
40
+ │ ├── record.js # Record instances
41
+ │ ├── serializer.js # Base Serializer
42
+ │ ├── store.js # In-memory storage
43
+ │ ├── db.js # JSON persistence
44
+ │ ├── attr.js # Attribute helper (Proxy-based)
45
+ │ ├── has-many.js # One-to-many relationships
46
+ │ ├── belongs-to.js # Many-to-one relationships
47
+ │ ├── relationships.js # Relationship registry
48
+ │ ├── manage-record.js # createRecord/updateRecord
49
+ │ ├── model-property.js # Transform handler
50
+ │ ├── transforms.js # Built-in transforms
51
+ │ ├── setup-rest-server.js # REST integration
52
+ │ ├── orm-request.js # CRUD request handler
53
+ │ └── meta-request.js # Meta endpoint (dev only)
54
+ ├── config/
55
+ │ └── environment.js # Default configuration
56
+ ├── test/
57
+ │ ├── integration/ # Integration tests
58
+ │ ├── unit/ # Unit tests
59
+ │ └── sample/ # Test fixtures
60
+ │ ├── models/ # Example models
61
+ │ ├── serializers/ # Example serializers
62
+ │ ├── transforms/ # Custom transforms
63
+ │ ├── access/ # Access control
64
+ │ ├── db-schema.js # DB schema
65
+ │ └── payload.js # Test data
66
+ └── package.json
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Usage Patterns
72
+
73
+ ### 1. Model Definition
74
+
75
+ Models extend `Model` and use decorators for attributes and relationships:
76
+
77
+ ```javascript
78
+ // test/sample/models/animal.js
79
+ import { Model, attr, belongsTo, hasMany } from '@stonyx/orm';
80
+
81
+ export default class AnimalModel extends Model {
82
+ // Attributes with type transforms
83
+ type = attr('animal'); // Custom transform
84
+ age = attr('number'); // Built-in transform
85
+ size = attr('string');
86
+
87
+ // Relationships
88
+ owner = belongsTo('owner'); // Many-to-one
89
+ traits = hasMany('trait'); // One-to-many
90
+
91
+ // Computed properties
92
+ get tag() {
93
+ return `${this.owner.id}'s ${this.size} animal`;
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Key Points:**
99
+ - Use `attr(type)` for simple attributes
100
+ - Use `belongsTo(modelName)` for many-to-one
101
+ - Use `hasMany(modelName)` for one-to-many
102
+ - Getters work as computed properties
103
+ - Relationships auto-establish bidirectionally
104
+
105
+ ### 2. Serializers (Data Transformation)
106
+
107
+ Serializers map raw data paths to model properties:
108
+
109
+ ```javascript
110
+ // test/sample/serializers/animal.js
111
+ import { Serializer } from '@stonyx/orm';
112
+
113
+ export default class AnimalSerializer extends Serializer {
114
+ map = {
115
+ // Nested path mapping
116
+ age: 'details.age',
117
+ size: 'details.c',
118
+ owner: 'details.location.owner',
119
+
120
+ // Custom transformation function
121
+ traits: ['details', ({ x:color }) => {
122
+ const traits = [{ id: 1, type: 'habitat', value: 'farm' }];
123
+ if (color) traits.push({ id: 2, type: 'color', value: color });
124
+ return traits;
125
+ }]
126
+ }
127
+ }
128
+ ```
129
+
130
+ **Key Points:**
131
+ - `map` object defines field mappings
132
+ - Supports nested paths (`'details.age'`)
133
+ - Custom functions for complex transformations
134
+ - Handlers receive raw data subset
135
+
136
+ ### 3. Custom Transforms
137
+
138
+ Transforms convert data types:
139
+
140
+ ```javascript
141
+ // test/sample/transforms/animal.js
142
+ const codeEnumMap = { 'dog': 1, 'cat': 2, 'bird': 3 };
143
+
144
+ export default function(value) {
145
+ return codeEnumMap[value] || 0;
146
+ }
147
+ ```
148
+
149
+ **Built-in Transforms:**
150
+ - Type: `boolean`, `number`, `float`, `string`, `date`, `timestamp`
151
+ - Math: `round`, `ceil`, `floor`
152
+ - String: `trim`, `uppercase`
153
+ - Utility: `passthrough`
154
+
155
+ ### 4. CRUD Operations
156
+
157
+ ```javascript
158
+ import { createRecord, updateRecord, store } from '@stonyx/orm';
159
+
160
+ // Create
161
+ createRecord('owner', { id: 'bob', age: 30 });
162
+
163
+ // Read
164
+ const owner = store.get('owner', 'bob');
165
+ const allOwners = store.get('owner');
166
+
167
+ // Update
168
+ updateRecord(owner, { age: 31 });
169
+ // Or direct: owner.age = 31;
170
+
171
+ // Delete
172
+ store.remove('owner', 'bob');
173
+ ```
174
+
175
+ ### 5. Database Schema
176
+
177
+ The DB schema is a Model defining top-level collections:
178
+
179
+ ```javascript
180
+ // test/sample/db-schema.js
181
+ import { Model, hasMany } from '@stonyx/orm';
182
+
183
+ export default class DBModel extends Model {
184
+ owners = hasMany('owner');
185
+ animals = hasMany('animal');
186
+ traits = hasMany('trait');
187
+ }
188
+ ```
189
+
190
+ ### 6. Persistence
191
+
192
+ ```javascript
193
+ import Orm from '@stonyx/orm';
194
+
195
+ // Save to file
196
+ await Orm.db.save();
197
+
198
+ // Data auto-serializes to JSON file
199
+ // Reload using createRecord with serialize:false, transform:false
200
+ ```
201
+
202
+ ### 7. Access Control
203
+
204
+ ```javascript
205
+ // test/sample/access/global-access.js
206
+ export default class GlobalAccess {
207
+ models = ['owner', 'animal']; // or '*' for all
208
+
209
+ access(request) {
210
+ // Deny specific access
211
+ if (request.url.endsWith('/owner/angela')) return false;
212
+
213
+ // Filter collections
214
+ if (request.url.endsWith('/owner')) {
215
+ return record => record.id !== 'angela';
216
+ }
217
+
218
+ // Grant CRUD permissions
219
+ return ['read', 'create', 'update', 'delete'];
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### 8. REST API (Auto-generated)
225
+
226
+ ```javascript
227
+ // Endpoints auto-generated for models:
228
+ // GET /owners - List all
229
+ // GET /owners/:id - Get one
230
+ // POST /animals - Create
231
+ // PATCH /animals/:id - Update
232
+ // DELETE /animals/:id - Delete
233
+ ```
234
+
235
+ ### 9. Include Parameter (Sideloading)
236
+
237
+ GET endpoints support sideloading related records with **nested relationship traversal**:
238
+
239
+ ```javascript
240
+ // Single-level includes
241
+ GET /animals/1?include=owner,traits
242
+
243
+ // Nested includes (NEW!)
244
+ GET /animals/1?include=owner.pets,owner.company
245
+
246
+ // Deep nesting (3+ levels)
247
+ GET /scenes/e001-s001?include=slides.dialogue.character
248
+
249
+ // Response structure (unchanged)
250
+ {
251
+ data: { type: 'animal', id: 1, attributes: {...}, relationships: {...} },
252
+ included: [
253
+ { type: 'owner', id: 'angela', ... },
254
+ { type: 'animal', id: 7, ... }, // owner's other pets
255
+ { type: 'animal', id: 11, ... }, // owner's other pets
256
+ { type: 'company', id: 'acme', ... } // owner's company (if requested)
257
+ ]
258
+ }
259
+ ```
260
+
261
+ **How Nested Includes Work:**
262
+ 1. Query param parsed into path segments: `owner.pets` → `[['owner'], ['owner', 'pets'], ['traits']]`
263
+ 2. `traverseIncludePath()` recursively traverses relationships depth-first
264
+ 3. Deduplication still by type+id (no duplicates in included array)
265
+ 4. Gracefully handles null/missing relationships at any depth
266
+ 5. Each included record gets full `toJSON()` representation
267
+
268
+ **Key Functions:**
269
+ - `parseInclude()` - Splits comma-separated includes and parses nested paths
270
+ - `traverseIncludePath()` - Recursively traverses relationship paths
271
+ - `collectIncludedRecords()` - Orchestrates traversal and deduplication
272
+ - All implemented in [src/orm-request.js](src/orm-request.js)
273
+
274
+ ---
275
+
276
+ ## Configuration
277
+
278
+ Located in [config/environment.js](config/environment.js), overridable via environment variables:
279
+
280
+ ```javascript
281
+ config.orm = {
282
+ paths: {
283
+ model: './models',
284
+ serializer: './serializers',
285
+ transform: './transforms',
286
+ access: './access'
287
+ },
288
+ db: {
289
+ autosave: 'false',
290
+ file: 'db.json',
291
+ saveInterval: 3600,
292
+ schema: './config/db-schema.js'
293
+ },
294
+ restServer: {
295
+ enabled: 'true',
296
+ route: '/'
297
+ }
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Design Patterns
304
+
305
+ 1. **Singleton**: Orm, Store, DB classes
306
+ 2. **Proxy**: `attr()` uses Proxies for type-safe access
307
+ 3. **Registry**: Relationships in nested Maps
308
+ 4. **Factory**: `createRecord()` function
309
+ 5. **Observer**: Auto-save via Cron
310
+ 6. **Convention over Configuration**: Auto-discovery by naming
311
+
312
+ **Naming Conventions:**
313
+ - Models: `{PascalCase}Model` (e.g., `AnimalModel`)
314
+ - Serializers: `{PascalCase}Serializer` (e.g., `AnimalSerializer`)
315
+ - Transforms: Original filename (e.g., `animal.js`)
316
+
317
+ ---
318
+
319
+ ## Testing
320
+
321
+ **Test Runner**: QUnit with bootstrap
322
+ **Bootstrap**: [stonyx-bootstrap.cjs](stonyx-bootstrap.cjs) - Configures paths for test environment
323
+
324
+ **Test Structure:**
325
+ - **Integration**: [test/integration/orm-test.js](test/integration/orm-test.js) - Full pipeline test
326
+ - **Unit**: [test/unit/transforms/](test/unit/transforms/) - Transform tests
327
+ - **Sample**: [test/sample/](test/sample/) - Test fixtures
328
+
329
+ **Key Test Data:**
330
+ - [test/sample/payload.js](test/sample/payload.js) - Raw vs serialized data
331
+ - Demonstrates transformation from messy external data to clean models
332
+
333
+ ---
334
+
335
+ ## Common Workflows
336
+
337
+ ### Making Updates to Models
338
+
339
+ 1. Read existing model in [test/sample/models/](test/sample/models/)
340
+ 2. Understand attribute types and relationships
341
+ 3. Check if serializer exists in [test/sample/serializers/](test/sample/serializers/)
342
+ 4. Update model definition
343
+ 5. Update serializer if data mapping changes
344
+ 6. Run tests: `npm test`
345
+
346
+ ### Adding New Features
347
+
348
+ 1. Check [src/index.js](src/index.js) for exports
349
+ 2. Understand Store pattern in [src/store.js](src/store.js)
350
+ 3. Review Record lifecycle in [src/record.js](src/record.js)
351
+ 4. Add feature following existing patterns
352
+ 5. Update integration tests
353
+
354
+ ### Debugging Issues
355
+
356
+ 1. Check Store contents: `store.get(modelName)`
357
+ 2. Verify relationships: `relationships.registry`
358
+ 3. Test serialization: Create record and inspect `record.__data`
359
+ 4. Check transform application in [src/model-property.js](src/model-property.js)
360
+ 5. Review test fixtures for expected behavior
361
+
362
+ ---
363
+
364
+ ## Key Insights
365
+
366
+ **Strengths:**
367
+ - Zero-config REST API generation
368
+ - Clean declarative model definitions
369
+ - Automatic relationship management
370
+ - File-based (no database setup needed)
371
+ - Flexible serialization for messy data
372
+
373
+ **Use Cases:**
374
+ - Rapid prototyping
375
+ - Small to medium applications
376
+ - Third-party API consumption with normalization
377
+ - Development/testing environments
378
+ - Applications needing quick REST APIs
379
+
380
+ **Dependencies:**
381
+ - `stonyx` - Main framework (peer)
382
+ - `@stonyx/utils` - File/string utilities
383
+ - `@stonyx/cron` - Scheduled tasks
384
+ - `@stonyx/rest-server` - REST API
385
+
386
+ ---
387
+
388
+ ## Critical Files for Common Tasks
389
+
390
+ **Understanding Core Behavior:**
391
+ - [src/main.js](src/main.js) - Initialization flow
392
+ - [src/store.js](src/store.js) - Record storage/retrieval
393
+ - [src/manage-record.js](src/manage-record.js) - CRUD operations
394
+
395
+ **Understanding Relationships:**
396
+ - [src/relationships.js](src/relationships.js) - Registry system
397
+ - [src/has-many.js](src/has-many.js) - One-to-many logic
398
+ - [src/belongs-to.js](src/belongs-to.js) - Many-to-one logic
399
+
400
+ **Understanding Data Flow:**
401
+ - [src/serializer.js](src/serializer.js) - Raw → Model mapping
402
+ - [src/model-property.js](src/model-property.js) - Transform application
403
+ - [src/transforms.js](src/transforms.js) - Built-in transforms
404
+
405
+ **Understanding REST API:**
406
+ - [src/setup-rest-server.js](src/setup-rest-server.js) - Endpoint registration
407
+ - [src/orm-request.js](src/orm-request.js) - Request handling
408
+
409
+ ---
410
+
411
+ ## Quick Reference
412
+
413
+ **Import the ORM:**
414
+ ```javascript
415
+ import { Orm, Model, Serializer, attr, hasMany, belongsTo, createRecord, updateRecord, store } from '@stonyx/orm';
416
+ ```
417
+
418
+ **Initialize:**
419
+ ```javascript
420
+ const orm = new Orm({ dbType: 'json' });
421
+ await orm.init();
422
+ ```
423
+
424
+ **Access Database:**
425
+ ```javascript
426
+ await Orm.db.save();
427
+ ```
428
+
429
+ **Common Operations:**
430
+ ```javascript
431
+ // Create
432
+ const record = createRecord('modelName', data);
433
+
434
+ // Read
435
+ const record = store.get('modelName', id);
436
+ const all = store.get('modelName');
437
+
438
+ // Update
439
+ updateRecord(record, newData);
440
+
441
+ // Delete
442
+ store.remove('modelName', id);
443
+ ```
444
+
445
+ ---
446
+
447
+ 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.
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - dev
7
+ - main
8
+
9
+ concurrency:
10
+ group: ci-${{ github.head_ref || github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v3
20
+
21
+ - name: Setup pnpm
22
+ uses: pnpm/action-setup@v4
23
+ with:
24
+ version: 9
25
+
26
+ - name: Set up Node.js
27
+ uses: actions/setup-node@v3
28
+ with:
29
+ node-version: 24.13.0
30
+ cache: 'pnpm'
31
+
32
+ - name: Install dependencies
33
+ run: pnpm install --frozen-lockfile
34
+
35
+ - name: Run tests
36
+ run: pnpm test