@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.
- package/.claude/code-style-rules.md +44 -0
- package/.claude/hooks.md +250 -0
- package/.claude/index.md +279 -0
- package/.claude/usage-patterns.md +217 -0
- package/.github/workflows/publish.yml +17 -1
- package/README.md +424 -15
- package/config/environment.js +26 -5
- package/improvements.md +139 -0
- package/package.json +19 -8
- package/project-structure.md +343 -0
- package/src/commands.js +170 -0
- package/src/db.js +132 -6
- package/src/hooks.js +124 -0
- package/src/index.js +2 -1
- package/src/main.js +31 -2
- package/src/manage-record.js +19 -4
- package/src/migrate.js +72 -0
- package/src/mysql/connection.js +28 -0
- package/src/mysql/migration-generator.js +188 -0
- package/src/mysql/migration-runner.js +110 -0
- package/src/mysql/mysql-db.js +320 -0
- package/src/mysql/query-builder.js +64 -0
- package/src/mysql/schema-introspector.js +158 -0
- package/src/mysql/type-map.js +37 -0
- package/src/orm-request.js +306 -52
- package/src/record.js +35 -8
- package/src/serializer.js +2 -2
- package/src/setup-rest-server.js +4 -1
- package/src/utils.js +12 -0
- package/test-events-setup.js +41 -0
- package/test-hooks-manual.js +54 -0
- package/test-hooks-with-logging.js +52 -0
- package/.claude/project-structure.md +0 -578
- package/stonyx-bootstrap.cjs +0 -30
|
@@ -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
|
|
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
|