@stonyx/orm 0.2.1-alpha.0 → 0.2.5-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/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.5-alpha.0",
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}`]: (request) => {
219
+ const { attributes } = request.body?.data || {};
165
220
 
166
221
  if (!attributes) return 400; // Bad request
167
222
 
223
+ const fieldsMap = parseFields(request.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
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
  };