@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,234 @@
1
+ # Usage Patterns
2
+
3
+ ## 1. Model Definition
4
+
5
+ Models extend `Model` and use decorators for attributes and relationships:
6
+
7
+ ```javascript
8
+ // test/sample/models/animal.js
9
+ import { Model, attr, belongsTo, hasMany } from '@stonyx/orm';
10
+
11
+ export default class AnimalModel extends Model {
12
+ // Attributes with type transforms
13
+ type = attr('animal'); // Custom transform
14
+ age = attr('number'); // Built-in transform
15
+ size = attr('string');
16
+
17
+ // Relationships
18
+ owner = belongsTo('owner'); // Many-to-one
19
+ traits = hasMany('trait'); // One-to-many
20
+
21
+ // Computed properties
22
+ get tag() {
23
+ return `${this.owner.id}'s ${this.size} animal`;
24
+ }
25
+ }
26
+ ```
27
+
28
+ **Key Points:**
29
+ - Use `attr(type)` for simple attributes
30
+ - Use `belongsTo(modelName)` for many-to-one
31
+ - Use `hasMany(modelName)` for one-to-many
32
+ - Getters work as computed properties
33
+ - Relationships auto-establish bidirectionally
34
+ - Override auto-pluralization with `static pluralName` (see [Overriding Plural Names](#overriding-plural-names))
35
+
36
+ ### Overriding Plural Names
37
+
38
+ By default, model names are auto-pluralized (e.g., `animal` → `animals`) for REST routes, JSON:API URLs, and DB table names. When auto-pluralization produces the wrong result, override it with `static pluralName`:
39
+
40
+ ```javascript
41
+ import { Model, attr } from '@stonyx/orm';
42
+
43
+ export default class PersonModel extends Model {
44
+ static pluralName = 'people';
45
+
46
+ name = attr('string');
47
+ }
48
+ ```
49
+
50
+ The override is picked up automatically during ORM initialization — no additional registration is needed. All internal call sites (REST routes, JSON:API type references, MySQL table names, foreign key references) use the overridden value.
51
+
52
+ ## 2. Serializers (Data Transformation)
53
+
54
+ Serializers map raw data paths to model properties:
55
+
56
+ ```javascript
57
+ // test/sample/serializers/animal.js
58
+ import { Serializer } from '@stonyx/orm';
59
+
60
+ export default class AnimalSerializer extends Serializer {
61
+ map = {
62
+ // Nested path mapping
63
+ age: 'details.age',
64
+ size: 'details.c',
65
+ owner: 'details.location.owner',
66
+
67
+ // Custom transformation function
68
+ traits: ['details', ({ x:color }) => {
69
+ const traits = [{ id: 1, type: 'habitat', value: 'farm' }];
70
+ if (color) traits.push({ id: 2, type: 'color', value: color });
71
+ return traits;
72
+ }]
73
+ }
74
+ }
75
+ ```
76
+
77
+ **Key Points:**
78
+ - `map` object defines field mappings
79
+ - Supports nested paths (`'details.age'`)
80
+ - Custom functions for complex transformations
81
+ - Handlers receive raw data subset
82
+
83
+ ## 3. Custom Transforms
84
+
85
+ Transforms convert data types:
86
+
87
+ ```javascript
88
+ // test/sample/transforms/animal.js
89
+ const codeEnumMap = { 'dog': 1, 'cat': 2, 'bird': 3 };
90
+
91
+ export default function(value) {
92
+ return codeEnumMap[value] || 0;
93
+ }
94
+ ```
95
+
96
+ **Built-in Transforms:**
97
+ - Type: `boolean`, `number`, `float`, `string`, `date`, `timestamp`
98
+ - Math: `round`, `ceil`, `floor`
99
+ - String: `trim`, `uppercase`
100
+ - Utility: `passthrough`
101
+
102
+ ## 4. CRUD Operations
103
+
104
+ ```javascript
105
+ import { createRecord, updateRecord, store } from '@stonyx/orm';
106
+
107
+ // Create
108
+ createRecord('owner', { id: 'bob', age: 30 });
109
+
110
+ // Read
111
+ const owner = store.get('owner', 'bob');
112
+ const allOwners = store.get('owner');
113
+
114
+ // Update
115
+ updateRecord(owner, { age: 31 });
116
+ // Or direct: owner.age = 31;
117
+
118
+ // Delete
119
+ store.remove('owner', 'bob');
120
+ ```
121
+
122
+ ## 5. Database Schema
123
+
124
+ The DB schema is a Model defining top-level collections:
125
+
126
+ ```javascript
127
+ // test/sample/db-schema.js
128
+ import { Model, hasMany } from '@stonyx/orm';
129
+
130
+ export default class DBModel extends Model {
131
+ owners = hasMany('owner');
132
+ animals = hasMany('animal');
133
+ traits = hasMany('trait');
134
+ }
135
+ ```
136
+
137
+ ## 6. Persistence
138
+
139
+ ```javascript
140
+ import Orm from '@stonyx/orm';
141
+
142
+ // Save to file
143
+ await Orm.db.save();
144
+
145
+ // Data auto-serializes to JSON file
146
+ // Reload using createRecord with serialize:false, transform:false
147
+ ```
148
+
149
+ ## 7. Access Control
150
+
151
+ ```javascript
152
+ // test/sample/access/global-access.js
153
+ export default class GlobalAccess {
154
+ models = ['owner', 'animal']; // or '*' for all
155
+
156
+ access(request) {
157
+ // Deny specific access
158
+ if (request.url.endsWith('/owner/angela')) return false;
159
+
160
+ // Filter collections
161
+ if (request.url.endsWith('/owner')) {
162
+ return record => record.id !== 'angela';
163
+ }
164
+
165
+ // Grant CRUD permissions
166
+ return ['read', 'create', 'update', 'delete'];
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## 8. REST API (Auto-generated)
172
+
173
+ ```javascript
174
+ // Endpoints auto-generated for models:
175
+ // GET /owners - List all
176
+ // GET /owners/:id - Get one
177
+ // POST /animals - Create
178
+ // PATCH /animals/:id - Update (attributes and/or relationships)
179
+ // DELETE /animals/:id - Delete
180
+ ```
181
+
182
+ **PATCH supports both attributes and relationships:**
183
+ ```javascript
184
+ // Update attributes only
185
+ PATCH /animals/1
186
+ { data: { type: 'animal', attributes: { age: 5 } } }
187
+
188
+ // Update relationship only
189
+ PATCH /animals/1
190
+ { data: { type: 'animal', relationships: { owner: { data: { type: 'owner', id: 'gina' } } } } }
191
+
192
+ // Update both
193
+ PATCH /animals/1
194
+ { data: { type: 'animal', attributes: { age: 5 }, relationships: { owner: { data: { type: 'owner', id: 'gina' } } } } }
195
+ ```
196
+
197
+ ## 9. Include Parameter (Sideloading)
198
+
199
+ GET endpoints support sideloading related records with **nested relationship traversal**:
200
+
201
+ ```javascript
202
+ // Single-level includes
203
+ GET /animals/1?include=owner,traits
204
+
205
+ // Nested includes (NEW!)
206
+ GET /animals/1?include=owner.pets,owner.company
207
+
208
+ // Deep nesting (3+ levels)
209
+ GET /scenes/e001-s001?include=slides.dialogue.character
210
+
211
+ // Response structure (unchanged)
212
+ {
213
+ data: { type: 'animal', id: 1, attributes: {...}, relationships: {...} },
214
+ included: [
215
+ { type: 'owner', id: 'angela', ... },
216
+ { type: 'animal', id: 7, ... }, // owner's other pets
217
+ { type: 'animal', id: 11, ... }, // owner's other pets
218
+ { type: 'company', id: 'acme', ... } // owner's company (if requested)
219
+ ]
220
+ }
221
+ ```
222
+
223
+ **How Nested Includes Work:**
224
+ 1. Query param parsed into path segments: `owner.pets` -> `[['owner'], ['owner', 'pets'], ['traits']]`
225
+ 2. `traverseIncludePath()` recursively traverses relationships depth-first
226
+ 3. Deduplication still by type+id (no duplicates in included array)
227
+ 4. Gracefully handles null/missing relationships at any depth
228
+ 5. Each included record gets full `toJSON()` representation
229
+
230
+ **Key Functions:**
231
+ - `parseInclude()` - Splits comma-separated includes and parses nested paths
232
+ - `traverseIncludePath()` - Recursively traverses relationship paths
233
+ - `collectIncludedRecords()` - Orchestrates traversal and deduplication
234
+ - All implemented in [src/orm-request.js](src/orm-request.js)
@@ -1,6 +1,8 @@
1
1
  name: Publish to NPM
2
2
 
3
3
  on:
4
+ repository_dispatch:
5
+ types: [cascade-publish]
4
6
  workflow_dispatch:
5
7
  inputs:
6
8
  version-type:
@@ -17,10 +19,14 @@ on:
17
19
  type: string
18
20
  pull_request:
19
21
  types: [opened, synchronize, reopened]
20
- branches: [main, dev]
22
+ branches: [main]
21
23
  push:
22
24
  branches: [main]
23
25
 
26
+ concurrency:
27
+ group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
28
+ cancel-in-progress: false
29
+
24
30
  permissions:
25
31
  contents: write
26
32
  id-token: write
@@ -28,8 +34,18 @@ permissions:
28
34
 
29
35
  jobs:
30
36
  publish:
37
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
31
38
  uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
32
39
  with:
33
40
  version-type: ${{ github.event.inputs.version-type }}
34
41
  custom-version: ${{ github.event.inputs.custom-version }}
42
+ cascade-source: ${{ github.event.client_payload.source_package || '' }}
43
+ secrets: inherit
44
+
45
+ cascade:
46
+ needs: publish
47
+ uses: abofs/stonyx-workflows/.github/workflows/cascade.yml@main
48
+ with:
49
+ package-name: ${{ needs.publish.outputs.package-name }}
50
+ published-version: ${{ needs.publish.outputs.published-version }}
35
51
  secrets: inherit