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

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,217 @@
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
+
35
+ ## 2. Serializers (Data Transformation)
36
+
37
+ Serializers map raw data paths to model properties:
38
+
39
+ ```javascript
40
+ // test/sample/serializers/animal.js
41
+ import { Serializer } from '@stonyx/orm';
42
+
43
+ export default class AnimalSerializer extends Serializer {
44
+ map = {
45
+ // Nested path mapping
46
+ age: 'details.age',
47
+ size: 'details.c',
48
+ owner: 'details.location.owner',
49
+
50
+ // Custom transformation function
51
+ traits: ['details', ({ x:color }) => {
52
+ const traits = [{ id: 1, type: 'habitat', value: 'farm' }];
53
+ if (color) traits.push({ id: 2, type: 'color', value: color });
54
+ return traits;
55
+ }]
56
+ }
57
+ }
58
+ ```
59
+
60
+ **Key Points:**
61
+ - `map` object defines field mappings
62
+ - Supports nested paths (`'details.age'`)
63
+ - Custom functions for complex transformations
64
+ - Handlers receive raw data subset
65
+
66
+ ## 3. Custom Transforms
67
+
68
+ Transforms convert data types:
69
+
70
+ ```javascript
71
+ // test/sample/transforms/animal.js
72
+ const codeEnumMap = { 'dog': 1, 'cat': 2, 'bird': 3 };
73
+
74
+ export default function(value) {
75
+ return codeEnumMap[value] || 0;
76
+ }
77
+ ```
78
+
79
+ **Built-in Transforms:**
80
+ - Type: `boolean`, `number`, `float`, `string`, `date`, `timestamp`
81
+ - Math: `round`, `ceil`, `floor`
82
+ - String: `trim`, `uppercase`
83
+ - Utility: `passthrough`
84
+
85
+ ## 4. CRUD Operations
86
+
87
+ ```javascript
88
+ import { createRecord, updateRecord, store } from '@stonyx/orm';
89
+
90
+ // Create
91
+ createRecord('owner', { id: 'bob', age: 30 });
92
+
93
+ // Read
94
+ const owner = store.get('owner', 'bob');
95
+ const allOwners = store.get('owner');
96
+
97
+ // Update
98
+ updateRecord(owner, { age: 31 });
99
+ // Or direct: owner.age = 31;
100
+
101
+ // Delete
102
+ store.remove('owner', 'bob');
103
+ ```
104
+
105
+ ## 5. Database Schema
106
+
107
+ The DB schema is a Model defining top-level collections:
108
+
109
+ ```javascript
110
+ // test/sample/db-schema.js
111
+ import { Model, hasMany } from '@stonyx/orm';
112
+
113
+ export default class DBModel extends Model {
114
+ owners = hasMany('owner');
115
+ animals = hasMany('animal');
116
+ traits = hasMany('trait');
117
+ }
118
+ ```
119
+
120
+ ## 6. Persistence
121
+
122
+ ```javascript
123
+ import Orm from '@stonyx/orm';
124
+
125
+ // Save to file
126
+ await Orm.db.save();
127
+
128
+ // Data auto-serializes to JSON file
129
+ // Reload using createRecord with serialize:false, transform:false
130
+ ```
131
+
132
+ ## 7. Access Control
133
+
134
+ ```javascript
135
+ // test/sample/access/global-access.js
136
+ export default class GlobalAccess {
137
+ models = ['owner', 'animal']; // or '*' for all
138
+
139
+ access(request) {
140
+ // Deny specific access
141
+ if (request.url.endsWith('/owner/angela')) return false;
142
+
143
+ // Filter collections
144
+ if (request.url.endsWith('/owner')) {
145
+ return record => record.id !== 'angela';
146
+ }
147
+
148
+ // Grant CRUD permissions
149
+ return ['read', 'create', 'update', 'delete'];
150
+ }
151
+ }
152
+ ```
153
+
154
+ ## 8. REST API (Auto-generated)
155
+
156
+ ```javascript
157
+ // Endpoints auto-generated for models:
158
+ // GET /owners - List all
159
+ // GET /owners/:id - Get one
160
+ // POST /animals - Create
161
+ // PATCH /animals/:id - Update (attributes and/or relationships)
162
+ // DELETE /animals/:id - Delete
163
+ ```
164
+
165
+ **PATCH supports both attributes and relationships:**
166
+ ```javascript
167
+ // Update attributes only
168
+ PATCH /animals/1
169
+ { data: { type: 'animal', attributes: { age: 5 } } }
170
+
171
+ // Update relationship only
172
+ PATCH /animals/1
173
+ { data: { type: 'animal', relationships: { owner: { data: { type: 'owner', id: 'gina' } } } } }
174
+
175
+ // Update both
176
+ PATCH /animals/1
177
+ { data: { type: 'animal', attributes: { age: 5 }, relationships: { owner: { data: { type: 'owner', id: 'gina' } } } } }
178
+ ```
179
+
180
+ ## 9. Include Parameter (Sideloading)
181
+
182
+ GET endpoints support sideloading related records with **nested relationship traversal**:
183
+
184
+ ```javascript
185
+ // Single-level includes
186
+ GET /animals/1?include=owner,traits
187
+
188
+ // Nested includes (NEW!)
189
+ GET /animals/1?include=owner.pets,owner.company
190
+
191
+ // Deep nesting (3+ levels)
192
+ GET /scenes/e001-s001?include=slides.dialogue.character
193
+
194
+ // Response structure (unchanged)
195
+ {
196
+ data: { type: 'animal', id: 1, attributes: {...}, relationships: {...} },
197
+ included: [
198
+ { type: 'owner', id: 'angela', ... },
199
+ { type: 'animal', id: 7, ... }, // owner's other pets
200
+ { type: 'animal', id: 11, ... }, // owner's other pets
201
+ { type: 'company', id: 'acme', ... } // owner's company (if requested)
202
+ ]
203
+ }
204
+ ```
205
+
206
+ **How Nested Includes Work:**
207
+ 1. Query param parsed into path segments: `owner.pets` -> `[['owner'], ['owner', 'pets'], ['traits']]`
208
+ 2. `traverseIncludePath()` recursively traverses relationships depth-first
209
+ 3. Deduplication still by type+id (no duplicates in included array)
210
+ 4. Gracefully handles null/missing relationships at any depth
211
+ 5. Each included record gets full `toJSON()` representation
212
+
213
+ **Key Functions:**
214
+ - `parseInclude()` - Splits comma-separated includes and parses nested paths
215
+ - `traverseIncludePath()` - Recursively traverses relationship paths
216
+ - `collectIncludedRecords()` - Orchestrates traversal and deduplication
217
+ - 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