@stonyx/orm 0.1.0 → 0.2.1-alpha.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.
- package/.claude/project-structure.md +578 -0
- package/.github/workflows/ci.yml +36 -0
- package/.github/workflows/publish.yml +143 -0
- package/README.md +301 -9
- package/config/environment.js +8 -0
- package/package.json +18 -12
- package/src/attr.js +28 -0
- package/src/belongs-to.js +63 -0
- package/src/db.js +42 -68
- package/src/exports/db.js +7 -0
- package/src/has-many.js +61 -0
- package/src/index.js +28 -0
- package/src/main.js +64 -45
- package/src/manage-record.js +103 -0
- package/src/meta-request.js +55 -0
- package/src/model-property.js +7 -0
- package/src/model.js +5 -1
- package/src/orm-request.js +189 -0
- package/src/record.js +72 -8
- package/src/relationships.js +43 -0
- package/src/serializer.js +41 -24
- package/src/setup-rest-server.js +57 -0
- package/src/store.js +211 -0
- package/src/transforms.js +4 -1
- package/stonyx-bootstrap.cjs +30 -0
- package/.nvmrc +0 -1
- package/src/utils.js +0 -19
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# Manual trigger (kept for flexibility)
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
version-type:
|
|
8
|
+
description: 'Version type'
|
|
9
|
+
required: true
|
|
10
|
+
type: choice
|
|
11
|
+
options:
|
|
12
|
+
- alpha
|
|
13
|
+
- patch
|
|
14
|
+
- minor
|
|
15
|
+
- major
|
|
16
|
+
custom-version:
|
|
17
|
+
description: 'Custom version (optional, overrides version-type)'
|
|
18
|
+
required: false
|
|
19
|
+
type: string
|
|
20
|
+
|
|
21
|
+
# Auto-publish alpha on PR
|
|
22
|
+
pull_request:
|
|
23
|
+
types: [opened, synchronize, reopened]
|
|
24
|
+
branches: [main, dev]
|
|
25
|
+
|
|
26
|
+
# Auto-publish stable on merge to main
|
|
27
|
+
push:
|
|
28
|
+
branches: [main]
|
|
29
|
+
|
|
30
|
+
permissions:
|
|
31
|
+
contents: write
|
|
32
|
+
id-token: write # Required for npm provenance
|
|
33
|
+
pull-requests: write # For PR comments
|
|
34
|
+
|
|
35
|
+
jobs:
|
|
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
|
+
});
|
package/README.md
CHANGED
|
@@ -1,13 +1,305 @@
|
|
|
1
|
-
# stonyx
|
|
1
|
+
# @stonyx/orm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight ORM for Stonyx projects, featuring model definitions, serializers, relationships, transforms, and optional REST server integration.
|
|
4
|
+
`@stonyx/orm` provides a structured way to define models, manage relationships, and persist data in JSON files. It also allows integration with the Stonyx REST server for automatic route setup and access control.
|
|
5
|
+
|
|
6
|
+
## Highlights
|
|
7
|
+
|
|
8
|
+
- **Automatic Loading**: Models, serializers, transforms, and access classes are auto-registered from their configured directories.
|
|
9
|
+
- **Models**: Define attributes with type-safe proxies (`attr`) and relationships (`hasMany`, `belongsTo`).
|
|
10
|
+
- **Serializers**: Map raw data into model-friendly structures, including nested properties.
|
|
11
|
+
- **Transforms**: Apply custom transformations on data values automatically.
|
|
12
|
+
- **DB Integration**: Optional file-based persistence with auto-save support.
|
|
13
|
+
- **REST Server Integration**: Automatic route setup with customizable access control.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @stonyx/orm
|
|
19
|
+
````
|
|
20
|
+
|
|
21
|
+
## Usage example
|
|
22
|
+
|
|
23
|
+
This module is part of the **Stonyx framework**. To use it, first configure the `restServer` key in your `environment.js` file:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const {
|
|
27
|
+
ORM_ACCESS_PATH,
|
|
28
|
+
ORM_MODEL_PATH,
|
|
29
|
+
ORM_REST_ROUTE,
|
|
30
|
+
ORM_SERIALIZER_PATH,
|
|
31
|
+
ORM_TRANSFORM_PATH,
|
|
32
|
+
ORM_USE_REST_SERVER,
|
|
33
|
+
DB_AUTO_SAVE,
|
|
34
|
+
DB_FILE,
|
|
35
|
+
DB_SCHEMA_PATH,
|
|
36
|
+
DB_SAVE_INTERVAL
|
|
37
|
+
} = process;
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
orm: {
|
|
41
|
+
logColor: 'white',
|
|
42
|
+
logMethod: 'db',
|
|
43
|
+
|
|
44
|
+
db: {
|
|
45
|
+
autosave: DB_AUTO_SAVE ?? 'false',
|
|
46
|
+
file: DB_FILE ?? 'db.json',
|
|
47
|
+
saveInterval: DB_SAVE_INTERVAL ?? 3600, // 1 hour
|
|
48
|
+
schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
|
|
49
|
+
},
|
|
50
|
+
paths: {
|
|
51
|
+
access: ORM_ACCESS_PATH ?? './access',
|
|
52
|
+
model: ORM_MODEL_PATH ?? './models',
|
|
53
|
+
serializer: ORM_SERIALIZER_PATH ?? './serializers',
|
|
54
|
+
transform: ORM_TRANSFORM_PATH ?? './transforms'
|
|
55
|
+
},
|
|
56
|
+
restServer: {
|
|
57
|
+
enabled: ORM_USE_REST_SERVER ?? 'true',
|
|
58
|
+
route: ORM_REST_ROUTE ?? '/'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then initialize the Stonyx framework, which auto-initializes all of its modules, including `@stonyx/rest-server`:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import Stonyx from 'stonyx';
|
|
68
|
+
import config from './config/environment.js';
|
|
69
|
+
|
|
70
|
+
new Stonyx(config);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For further framework initialization instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
|
|
74
|
+
|
|
75
|
+
## Models
|
|
76
|
+
|
|
77
|
+
Define a model with attributes and relationships:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { Model, attr, hasMany, belongsTo } from '@stonyx/orm';
|
|
81
|
+
|
|
82
|
+
export default class OwnerModel extends Model {
|
|
83
|
+
id = attr('string');
|
|
84
|
+
age = attr('number');
|
|
85
|
+
pets = hasMany('animal');
|
|
86
|
+
|
|
87
|
+
get totalPets() {
|
|
88
|
+
return this.pets.length;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Serializers
|
|
94
|
+
|
|
95
|
+
Based on the following sample payload structure which represents a poorly structure third-party data source:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
export default {
|
|
99
|
+
animals: [
|
|
100
|
+
{ id: 1, type: 'dog', details: { age: 2, c: 'small', x: 'black', location: { type: 'farm', owner: 'angela' }}},
|
|
101
|
+
//...
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Map raw data to model fields:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
import { Serializer } from '@stonyx/orm';
|
|
110
|
+
|
|
111
|
+
export default class AnimalSerializer extends Serializer {
|
|
112
|
+
map = {
|
|
113
|
+
age: 'details.age',
|
|
114
|
+
size: 'details.c',
|
|
115
|
+
color: 'details.x',
|
|
116
|
+
owner: 'details.location.owner'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Relationships
|
|
122
|
+
|
|
123
|
+
### belongsTo
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { belongsTo } from '@stonyx/orm';
|
|
127
|
+
|
|
128
|
+
class AnimalModel extends Model {
|
|
129
|
+
owner = belongsTo('owner');
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### hasMany
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import { hasMany } from '@stonyx/orm';
|
|
137
|
+
|
|
138
|
+
class OwnerModel extends Model {
|
|
139
|
+
pets = hasMany('animal');
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Transforms
|
|
144
|
+
|
|
145
|
+
Apply custom transforms on field values:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
import { ANIMALS } from '../constants.js';
|
|
149
|
+
|
|
150
|
+
export default function(value) {
|
|
151
|
+
return ANIMALS.indexOf(value) || 0;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Database (DB) Integration
|
|
156
|
+
|
|
157
|
+
The ORM can automatically save records to a JSON file with optional auto-save intervals.
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
import Orm from '@stonyx/orm';
|
|
161
|
+
|
|
162
|
+
const orm = new Orm();
|
|
163
|
+
await orm.init();
|
|
164
|
+
|
|
165
|
+
// Access the DB record
|
|
166
|
+
const dbRecord = Orm.db;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Configuration options are in `config/environment.js`:
|
|
170
|
+
|
|
171
|
+
* `DB_AUTO_SAVE`: Whether to auto-save.
|
|
172
|
+
* `DB_FILE`: File path to store data.
|
|
173
|
+
* `DB_SAVE_INTERVAL`: Interval in seconds for auto-save.
|
|
174
|
+
* `DB_SCHEMA_PATH`: Path to DB schema.
|
|
175
|
+
|
|
176
|
+
## REST Server Integration
|
|
177
|
+
|
|
178
|
+
The ORM can automatically register REST routes using your access classes.
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
import setupRestServer from '@stonyx/orm/setup-rest-server';
|
|
182
|
+
|
|
183
|
+
await setupRestServer('/', './access');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Access classes define models and provide custom filtering/authorization logic:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
export default class GlobalAccess {
|
|
190
|
+
models = ['owner', 'animal'];
|
|
191
|
+
|
|
192
|
+
access(request) {
|
|
193
|
+
if (request.url.endsWith('/owner/angela')) return false;
|
|
194
|
+
return ['read', 'create', 'update', 'delete'];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Include Parameter (Sideloading Relationships)
|
|
200
|
+
|
|
201
|
+
The ORM supports JSON API-compliant relationship sideloading via the `include` query parameter. This reduces the need for multiple API requests by embedding related records in a single response.
|
|
202
|
+
|
|
203
|
+
#### Basic Usage
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
// Fetch animal with owner and traits included
|
|
207
|
+
GET /animals/1?include=owner,traits
|
|
208
|
+
|
|
209
|
+
// Response:
|
|
210
|
+
{
|
|
211
|
+
"data": {
|
|
212
|
+
"type": "animal",
|
|
213
|
+
"id": 1,
|
|
214
|
+
"attributes": { "age": 2, "size": "small" },
|
|
215
|
+
"relationships": {
|
|
216
|
+
"owner": { "data": { "type": "owner", "id": "angela" } },
|
|
217
|
+
"traits": { "data": [
|
|
218
|
+
{ "type": "trait", "id": 1 },
|
|
219
|
+
{ "type": "trait", "id": 2 }
|
|
220
|
+
]}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"included": [
|
|
224
|
+
{
|
|
225
|
+
"type": "owner",
|
|
226
|
+
"id": "angela",
|
|
227
|
+
"attributes": { "age": 36, "gender": "female" },
|
|
228
|
+
"relationships": { "pets": { "data": [...] } }
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"type": "trait",
|
|
232
|
+
"id": 1,
|
|
233
|
+
"attributes": { "type": "habitat", "value": "farm" },
|
|
234
|
+
"relationships": {}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"type": "trait",
|
|
238
|
+
"id": 2,
|
|
239
|
+
"attributes": { "type": "color", "value": "black" },
|
|
240
|
+
"relationships": {}
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
4
244
|
```
|
|
5
|
-
|
|
245
|
+
|
|
246
|
+
#### Features
|
|
247
|
+
|
|
248
|
+
- **Comma-separated relationship names:** `?include=owner,traits`
|
|
249
|
+
- **Nested relationship traversal:** `?include=owner.pets,owner.company` (supports multi-level nesting)
|
|
250
|
+
- **Works with collections and single records:** Both GET endpoints support includes
|
|
251
|
+
- **Automatic deduplication:** Each unique record (by type+id) appears only once in included array
|
|
252
|
+
- **Invalid relationships ignored:** Invalid relationship names are silently skipped
|
|
253
|
+
- **Backward compatible:** Omit the include parameter for original behavior (no included array)
|
|
254
|
+
|
|
255
|
+
#### Examples
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// Single resource with single include
|
|
259
|
+
GET /owners/gina?include=pets
|
|
260
|
+
|
|
261
|
+
// Single resource with multiple includes
|
|
262
|
+
GET /animals/1?include=owner,traits
|
|
263
|
+
|
|
264
|
+
// Nested includes (NEW!)
|
|
265
|
+
GET /animals/1?include=owner.pets
|
|
266
|
+
|
|
267
|
+
// Deep nesting (3+ levels)
|
|
268
|
+
GET /scenes/e001-s001?include=slides.dialogue.character
|
|
269
|
+
|
|
270
|
+
// Collection with includes (deduplicates automatically)
|
|
271
|
+
GET /animals?include=owner
|
|
272
|
+
|
|
273
|
+
// Combining nested and non-nested includes
|
|
274
|
+
GET /owners?include=pets.traits,company
|
|
275
|
+
|
|
276
|
+
// No include parameter (backward compatible)
|
|
277
|
+
GET /animals/1
|
|
278
|
+
// Returns: { data: {...} } // No included array
|
|
6
279
|
```
|
|
7
280
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
281
|
+
**How Nested Includes Work:**
|
|
282
|
+
1. Query param parsed into path segments: `owner.pets` → `['owner', 'pets']`
|
|
283
|
+
2. Recursively traverses relationships depth-first
|
|
284
|
+
3. Deduplication still by type+id (no duplicates in included array)
|
|
285
|
+
4. Gracefully handles null/missing relationships at any depth
|
|
286
|
+
5. Each included record gets full `toJSON()` representation
|
|
287
|
+
|
|
288
|
+
#### Limitations
|
|
289
|
+
|
|
290
|
+
- Only available on GET endpoints (not POST/PATCH)
|
|
291
|
+
|
|
292
|
+
## Exported Helpers
|
|
293
|
+
|
|
294
|
+
| Export | Description |
|
|
295
|
+
| --------------- | ----------------------------------------------------------------------- |
|
|
296
|
+
| `attr` | Define model attributes with type-safe proxy. |
|
|
297
|
+
| `belongsTo` | Define a one-to-one relationship. |
|
|
298
|
+
| `hasMany` | Define a one-to-many relationship. |
|
|
299
|
+
| `createRecord` | Instantiate a record with proper serialization and relationships. |
|
|
300
|
+
| `store` | Singleton store for all model instances. |
|
|
301
|
+
| `relationships` | Access all relationships (`hasMany`, `belongsTo`, `global`, `pending`). |
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
Apache — do what you want, just keep attribution.
|
package/config/environment.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const {
|
|
2
|
+
ORM_ACCESS_PATH,
|
|
2
3
|
ORM_MODEL_PATH,
|
|
4
|
+
ORM_REST_ROUTE,
|
|
3
5
|
ORM_SERIALIZER_PATH,
|
|
4
6
|
ORM_TRANSFORM_PATH,
|
|
7
|
+
ORM_USE_REST_SERVER,
|
|
5
8
|
DB_AUTO_SAVE,
|
|
6
9
|
DB_FILE,
|
|
7
10
|
DB_SCHEMA_PATH,
|
|
@@ -19,8 +22,13 @@ export default {
|
|
|
19
22
|
schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
|
|
20
23
|
},
|
|
21
24
|
paths: {
|
|
25
|
+
access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
|
|
22
26
|
model: ORM_MODEL_PATH ?? './models',
|
|
23
27
|
serializer: ORM_SERIALIZER_PATH ?? './serializers',
|
|
24
28
|
transform: ORM_TRANSFORM_PATH ?? './transforms'
|
|
29
|
+
},
|
|
30
|
+
restServer: {
|
|
31
|
+
enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
|
|
32
|
+
route: ORM_REST_ROUTE ?? '/',
|
|
25
33
|
}
|
|
26
34
|
}
|
package/package.json
CHANGED
|
@@ -4,17 +4,13 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.1.0",
|
|
7
|
+
"version": "0.2.1-alpha.0",
|
|
8
8
|
"description": "",
|
|
9
9
|
"main": "src/main.js",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"exports": {
|
|
12
|
-
".": "./src/
|
|
13
|
-
"./db": "./src/db.js"
|
|
14
|
-
"./utils": "./src/utils.js"
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"test": "qunit"
|
|
12
|
+
".": "./src/index.js",
|
|
13
|
+
"./db": "./src/exports/db.js"
|
|
18
14
|
},
|
|
19
15
|
"repository": {
|
|
20
16
|
"type": "git",
|
|
@@ -25,16 +21,26 @@
|
|
|
25
21
|
"contributors": [
|
|
26
22
|
"Stone Costa <stone.costa@synamicd.com>"
|
|
27
23
|
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"provenance": true
|
|
27
|
+
},
|
|
28
28
|
"bugs": {
|
|
29
29
|
"url": "https://github.com/abofs/stonyx-orm/issues"
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/abofs/stonyx-orm#readme",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"stonyx": "^0.
|
|
33
|
+
"stonyx": "^0.2.2",
|
|
34
|
+
"@stonyx/events": "^0.1.0",
|
|
35
|
+
"@stonyx/cron": "^0.2.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
|
-
"@stonyx/
|
|
37
|
-
"@stonyx/utils": "^0.
|
|
38
|
-
"qunit": "^2.24.1"
|
|
38
|
+
"@stonyx/rest-server": "^0.2.0",
|
|
39
|
+
"@stonyx/utils": "^0.2.2",
|
|
40
|
+
"qunit": "^2.24.1",
|
|
41
|
+
"sinon": "^21.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "qunit --require ./stonyx-bootstrap.cjs"
|
|
39
45
|
}
|
|
40
|
-
}
|
|
46
|
+
}
|
package/src/attr.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ModelProperty from './model-property.js';
|
|
2
|
+
|
|
3
|
+
export default function attr() {
|
|
4
|
+
const modelProp = new ModelProperty(...arguments);
|
|
5
|
+
|
|
6
|
+
return new Proxy(modelProp, {
|
|
7
|
+
get(target, prop, receiver) {
|
|
8
|
+
if (prop === 'valueOf' || prop === 'toString') {
|
|
9
|
+
return () => target.value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (prop in target) {
|
|
13
|
+
return Reflect.get(target, prop, receiver);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return target.value;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
set(target, prop, value, receiver) {
|
|
20
|
+
if (prop === 'value') {
|
|
21
|
+
target.value = value;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return Reflect.set(target, prop, value, receiver);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createRecord, relationships, store } from '@stonyx/orm';
|
|
2
|
+
import { getRelationships } from './relationships.js';
|
|
3
|
+
|
|
4
|
+
function getOrSet(map, key, defaultValue) {
|
|
5
|
+
if (!map.has(key)) map.set(key, defaultValue);
|
|
6
|
+
return map.get(key);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function belongsTo(modelName) {
|
|
10
|
+
const hasManyRelationships = relationships.get('hasMany');
|
|
11
|
+
const pendingHasManyQueue = relationships.get('pending');
|
|
12
|
+
const pendingBelongsToQueue = relationships.get('pendingBelongsTo');
|
|
13
|
+
|
|
14
|
+
return (sourceRecord, rawData, options) => {
|
|
15
|
+
if (!rawData) return null;
|
|
16
|
+
|
|
17
|
+
const { __name: sourceModelName } = sourceRecord.__model;
|
|
18
|
+
const relationshipId = sourceRecord.id;
|
|
19
|
+
const relationshipKey = options._relationshipKey;
|
|
20
|
+
const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId);
|
|
21
|
+
const modelStore = store.get(modelName);
|
|
22
|
+
|
|
23
|
+
// Try to get existing record
|
|
24
|
+
const output = typeof rawData === 'object'
|
|
25
|
+
? createRecord(modelName, rawData, options)
|
|
26
|
+
: modelStore.get(rawData);
|
|
27
|
+
|
|
28
|
+
// If not found and is a string ID, register as pending
|
|
29
|
+
if (!output && typeof rawData !== 'object') {
|
|
30
|
+
const targetId = rawData;
|
|
31
|
+
|
|
32
|
+
// Register pending belongsTo
|
|
33
|
+
const modelPendingMap = getOrSet(pendingBelongsToQueue, modelName, new Map());
|
|
34
|
+
const targetPendingArray = getOrSet(modelPendingMap, targetId, []);
|
|
35
|
+
|
|
36
|
+
targetPendingArray.push({
|
|
37
|
+
sourceRecord,
|
|
38
|
+
sourceModelName,
|
|
39
|
+
relationshipKey,
|
|
40
|
+
relationshipId
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
relationship.set(relationshipId, null);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
relationship.set(relationshipId, output || {});
|
|
48
|
+
|
|
49
|
+
// Populate hasMany side if the relationship is defined
|
|
50
|
+
const otherSide = hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(output?.id);
|
|
51
|
+
|
|
52
|
+
if (otherSide) {
|
|
53
|
+
otherSide.push(sourceRecord);
|
|
54
|
+
|
|
55
|
+
// Remove pending queue if it was just fulfilled
|
|
56
|
+
const pendingModelRelationships = pendingHasManyQueue.get(sourceModelName);
|
|
57
|
+
|
|
58
|
+
if (pendingModelRelationships) pendingModelRelationships.delete(relationshipId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
}
|