@stonyx/orm 0.2.1-alpha.0 → 0.2.1-alpha.10

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)
@@ -2,35 +2,15 @@ name: CI
2
2
 
3
3
  on:
4
4
  pull_request:
5
- branches:
6
- - dev
7
- - main
5
+ branches: [dev, main]
8
6
 
9
7
  concurrency:
10
8
  group: ci-${{ github.head_ref || github.ref }}
11
9
  cancel-in-progress: true
12
10
 
11
+ permissions:
12
+ contents: read
13
+
13
14
  jobs:
14
15
  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
16
+ uses: abofs/stonyx-workflows/.github/workflows/ci.yml@main
@@ -1,7 +1,8 @@
1
1
  name: Publish to NPM
2
2
 
3
3
  on:
4
- # Manual trigger (kept for flexibility)
4
+ repository_dispatch:
5
+ types: [cascade-publish]
5
6
  workflow_dispatch:
6
7
  inputs:
7
8
  version-type:
@@ -9,7 +10,6 @@ on:
9
10
  required: true
10
11
  type: choice
11
12
  options:
12
- - alpha
13
13
  - patch
14
14
  - minor
15
15
  - major
@@ -17,127 +17,35 @@ on:
17
17
  description: 'Custom version (optional, overrides version-type)'
18
18
  required: false
19
19
  type: string
20
-
21
- # Auto-publish alpha on PR
22
20
  pull_request:
23
21
  types: [opened, synchronize, reopened]
24
- branches: [main, dev]
25
-
26
- # Auto-publish stable on merge to main
22
+ branches: [main]
27
23
  push:
28
24
  branches: [main]
29
25
 
26
+ concurrency:
27
+ group: ${{ github.event_name == 'repository_dispatch' && 'cascade-update' || format('publish-{0}', github.ref) }}
28
+ cancel-in-progress: false
29
+
30
30
  permissions:
31
31
  contents: write
32
- id-token: write # Required for npm provenance
33
- pull-requests: write # For PR comments
32
+ id-token: write
33
+ pull-requests: write
34
34
 
35
35
  jobs:
36
36
  publish:
37
- runs-on: ubuntu-latest
38
-
39
- steps:
40
- - name: Checkout code
41
- uses: actions/checkout@v3
42
- with:
43
- fetch-depth: 0
44
- # For PR events, check out the PR branch
45
- ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
46
-
47
- - name: Setup pnpm
48
- uses: pnpm/action-setup@v4
49
- with:
50
- version: 9
51
-
52
- - name: Set up Node.js
53
- uses: actions/setup-node@v3
54
- with:
55
- node-version: 24.13.0
56
- cache: 'pnpm'
57
- registry-url: 'https://registry.npmjs.org'
58
-
59
- - name: Install dependencies
60
- run: pnpm install --frozen-lockfile
61
-
62
- - name: Run tests
63
- run: pnpm test
64
-
65
- - name: Configure git
66
- run: |
67
- git config user.name "github-actions[bot]"
68
- git config user.email "github-actions[bot]@users.noreply.github.com"
69
-
70
- # Determine version type based on trigger
71
- - name: Determine version bump type
72
- id: version-type
73
- run: |
74
- if [ "${{ github.event_name }}" = "pull_request" ]; then
75
- echo "type=alpha" >> $GITHUB_OUTPUT
76
- elif [ "${{ github.event_name }}" = "push" ]; then
77
- echo "type=patch" >> $GITHUB_OUTPUT
78
- elif [ "${{ github.event.inputs.custom-version }}" != "" ]; then
79
- echo "type=custom" >> $GITHUB_OUTPUT
80
- else
81
- echo "type=${{ github.event.inputs.version-type }}" >> $GITHUB_OUTPUT
82
- fi
83
-
84
- # Version bumping
85
- - name: Bump version (custom)
86
- if: steps.version-type.outputs.type == 'custom'
87
- run: pnpm version ${{ github.event.inputs.custom-version }} --no-git-tag-version
88
-
89
- - name: Bump version (alpha)
90
- if: steps.version-type.outputs.type == 'alpha'
91
- run: pnpm version prerelease --preid=alpha --no-git-tag-version
92
-
93
- - name: Bump version (patch/minor/major)
94
- if: steps.version-type.outputs.type == 'patch' || steps.version-type.outputs.type == 'minor' || steps.version-type.outputs.type == 'major'
95
- run: pnpm version ${{ steps.version-type.outputs.type }} --no-git-tag-version
96
-
97
- - name: Get package version
98
- id: package-version
99
- run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
100
-
101
- # Publishing
102
- - name: Publish to NPM (alpha)
103
- if: contains(steps.package-version.outputs.version, 'alpha')
104
- run: pnpm publish --tag alpha --access public --no-git-checks
105
-
106
- - name: Publish to NPM (stable)
107
- if: "!contains(steps.package-version.outputs.version, 'alpha')"
108
- run: pnpm publish --access public
109
-
110
- # Only commit and tag for stable releases (push to main or manual stable)
111
- - name: Commit version bump and create tag
112
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !contains(steps.package-version.outputs.version, 'alpha'))
113
- run: |
114
- git add package.json
115
- git commit -m "chore: release v${{ steps.package-version.outputs.version }}"
116
- git tag v${{ steps.package-version.outputs.version }}
117
- git push origin main --tags
118
-
119
- - name: Create GitHub Release
120
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !contains(steps.package-version.outputs.version, 'alpha'))
121
- uses: actions/create-release@v1
122
- env:
123
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124
- with:
125
- tag_name: v${{ steps.package-version.outputs.version }}
126
- release_name: v${{ steps.package-version.outputs.version }}
127
- draft: false
128
- prerelease: false
129
-
130
- # Add PR comment with alpha version info
131
- - name: Comment on PR with alpha version
132
- if: github.event_name == 'pull_request'
133
- uses: actions/github-script@v6
134
- with:
135
- script: |
136
- const version = '${{ steps.package-version.outputs.version }}';
137
- const packageName = require('./package.json').name;
138
- github.rest.issues.createComment({
139
- issue_number: context.issue.number,
140
- owner: context.repo.owner,
141
- repo: context.repo.repo,
142
- body: `## 🚀 Alpha Version Published\n\n**Version:** \`${version}\`\n\n**Install:**\n\`\`\`bash\npnpm add ${packageName}@${version}\n# or\npnpm add ${packageName}@alpha # latest alpha\n\`\`\`\n\nThis alpha version is now available for testing!`
143
- });
37
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
38
+ uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
39
+ with:
40
+ version-type: ${{ github.event.inputs.version-type }}
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 }}
51
+ secrets: inherit