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

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.
@@ -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,6 @@
1
1
  name: Publish to NPM
2
2
 
3
3
  on:
4
- # Manual trigger (kept for flexibility)
5
4
  workflow_dispatch:
6
5
  inputs:
7
6
  version-type:
@@ -9,7 +8,6 @@ on:
9
8
  required: true
10
9
  type: choice
11
10
  options:
12
- - alpha
13
11
  - patch
14
12
  - minor
15
13
  - major
@@ -17,127 +15,21 @@ on:
17
15
  description: 'Custom version (optional, overrides version-type)'
18
16
  required: false
19
17
  type: string
20
-
21
- # Auto-publish alpha on PR
22
18
  pull_request:
23
19
  types: [opened, synchronize, reopened]
24
20
  branches: [main, dev]
25
-
26
- # Auto-publish stable on merge to main
27
21
  push:
28
22
  branches: [main]
29
23
 
30
24
  permissions:
31
25
  contents: write
32
- id-token: write # Required for npm provenance
33
- pull-requests: write # For PR comments
26
+ id-token: write
27
+ pull-requests: write
34
28
 
35
29
  jobs:
36
30
  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
- });
31
+ uses: abofs/stonyx-workflows/.github/workflows/npm-publish.yml@main
32
+ with:
33
+ version-type: ${{ github.event.inputs.version-type }}
34
+ custom-version: ${{ github.event.inputs.custom-version }}
35
+ secrets: inherit
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-alpha.0",
7
+ "version": "0.2.1-alpha.1",
8
8
  "description": "",
9
9
  "main": "src/main.js",
10
10
  "type": "module",
@@ -114,6 +114,51 @@ function parseInclude(includeParam) {
114
114
  .map(rel => rel.split('.')); // Parse nested paths: "owner.pets" → ["owner", "pets"]
115
115
  }
116
116
 
117
+ function parseFields(query) {
118
+ const fields = new Map();
119
+ if (!query) return fields;
120
+
121
+ for (const [key, value] of Object.entries(query)) {
122
+ const match = key.match(/^fields\[(\w+)\]$/);
123
+ if (match && typeof value === 'string') {
124
+ const modelName = match[1];
125
+ const fieldNames = value.split(',').map(f => f.trim()).filter(f => f);
126
+ fields.set(modelName, new Set(fieldNames));
127
+ }
128
+ }
129
+
130
+ return fields;
131
+ }
132
+
133
+ function parseFilters(query) {
134
+ const filters = [];
135
+ if (!query) return filters;
136
+
137
+ for (const [key, value] of Object.entries(query)) {
138
+ const match = key.match(/^filter\[(.+)\]$/);
139
+ if (match && typeof value === 'string') {
140
+ filters.push({ path: match[1].split('.'), value });
141
+ }
142
+ }
143
+
144
+ return filters;
145
+ }
146
+
147
+ function createFilterPredicate(filters) {
148
+ if (filters.length === 0) return null;
149
+
150
+ return (record) => filters.every(({ path, value }) => {
151
+ let current = record;
152
+
153
+ for (const segment of path) {
154
+ if (current == null) return false;
155
+ current = current[segment];
156
+ }
157
+
158
+ return String(current) === value;
159
+ });
160
+ }
161
+
117
162
  export default class OrmRequest extends Request {
118
163
  constructor({ model, access }) {
119
164
  super(...arguments);
@@ -123,20 +168,30 @@ export default class OrmRequest extends Request {
123
168
 
124
169
  this.handlers = {
125
170
  get: {
126
- [`/${pluralizedModel}`]: (request, { filter }) => {
171
+ [`/${pluralizedModel}`]: (request, { filter: accessFilter }) => {
127
172
  const allRecords = Array.from(store.get(model).values());
128
- const recordsToReturn = filter ? allRecords.filter(filter) : allRecords;
129
- const data = recordsToReturn.map(record => record.toJSON());
130
173
 
174
+ const queryFilters = parseFilters(request.query);
175
+ const queryFilterPredicate = createFilterPredicate(queryFilters);
176
+ const fieldsMap = parseFields(request.query);
177
+ const modelFields = fieldsMap.get(pluralizedModel) || fieldsMap.get(model);
178
+
179
+ let recordsToReturn = allRecords;
180
+ if (accessFilter) recordsToReturn = recordsToReturn.filter(accessFilter);
181
+ if (queryFilterPredicate) recordsToReturn = recordsToReturn.filter(queryFilterPredicate);
182
+
183
+ const data = recordsToReturn.map(record => record.toJSON({ fields: modelFields }));
131
184
  return buildResponse(data, request.query?.include, recordsToReturn);
132
185
  },
133
186
 
134
187
  [`/${pluralizedModel}/:id`]: (request) => {
135
188
  const record = store.get(model, getId(request.params));
189
+ if (!record) return 404;
136
190
 
137
- if (!record) return 404; // Record not found
191
+ const fieldsMap = parseFields(request.query);
192
+ const modelFields = fieldsMap.get(pluralizedModel) || fieldsMap.get(model);
138
193
 
139
- return buildResponse(record.toJSON(), request.query?.include, record);
194
+ return buildResponse(record.toJSON({ fields: modelFields }), request.query?.include, record);
140
195
  }
141
196
  },
142
197
 
@@ -160,14 +215,19 @@ export default class OrmRequest extends Request {
160
215
  },
161
216
 
162
217
  post: {
163
- [`/${pluralizedModel}`]: ({ body }) => {
164
- const { attributes } = body?.data || {};
218
+ [`/${pluralizedModel}`]: ({ body, query }) => {
219
+ const { type, attributes } = body?.data || {};
165
220
 
166
- if (!attributes) return 400; // Bad request
221
+ if (!type) return 400; // Bad request
222
+
223
+ const fieldsMap = parseFields(query);
224
+ const modelFields = fieldsMap.get(pluralizedModel) || fieldsMap.get(model);
225
+ // Check for duplicate ID
226
+ if (attributes?.id !== undefined && store.get(model, attributes.id)) return 409; // Conflict
167
227
 
168
228
  const record = createRecord(model, attributes, { serialize: false });
169
229
 
170
- return { data: record.toJSON() };
230
+ return { data: record.toJSON({ fields: modelFields }) };
171
231
  }
172
232
  },
173
233
 
package/src/record.js CHANGED
@@ -48,21 +48,29 @@ export default class Record {
48
48
  }
49
49
 
50
50
  // Formats record for JSON API output
51
- toJSON() {
51
+ toJSON(options = {}) {
52
52
  if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
53
-
53
+
54
54
  const { __data:data } = this;
55
+ const { fields } = options;
55
56
  const relationships = {};
56
- const attributes = { ...data };
57
- delete attributes.id;
57
+ const attributes = {};
58
+
59
+ for (const [key, value] of Object.entries(data)) {
60
+ if (key === 'id') continue;
61
+ if (fields && !fields.has(key)) continue;
62
+ attributes[key] = value;
63
+ }
58
64
 
59
65
  for (const [key, getter] of getComputedProperties(this.__model)) {
66
+ if (fields && !fields.has(key)) continue;
60
67
  attributes[key] = getter.call(this);
61
68
  }
62
69
 
63
- for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
70
+ for (const [key, childRecord] of Object.entries(this.__relationships)) {
71
+ if (fields && !fields.has(key)) continue;
64
72
  relationships[key] = {
65
- data: Array.isArray(childRecord)
73
+ data: Array.isArray(childRecord)
66
74
  ? childRecord.map(r => ({ type: r.__model.__name, id: r.id }))
67
75
  : childRecord ? { type: childRecord.__model.__name, id: childRecord.id } : null
68
76
  };