@stonyx/orm 0.2.5-alpha.0 → 0.3.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.
Files changed (166) hide show
  1. package/README.md +482 -15
  2. package/config/environment.js +63 -6
  3. package/dist/aggregates.d.ts +21 -0
  4. package/dist/aggregates.js +93 -0
  5. package/dist/attr.d.ts +2 -0
  6. package/dist/attr.js +22 -0
  7. package/dist/belongs-to.d.ts +11 -0
  8. package/dist/belongs-to.js +59 -0
  9. package/dist/cli.d.ts +22 -0
  10. package/dist/cli.js +148 -0
  11. package/dist/commands.d.ts +7 -0
  12. package/dist/commands.js +146 -0
  13. package/dist/db.d.ts +21 -0
  14. package/dist/db.js +180 -0
  15. package/dist/exports/db.d.ts +7 -0
  16. package/{src → dist}/exports/db.js +2 -4
  17. package/dist/has-many.d.ts +11 -0
  18. package/dist/has-many.js +58 -0
  19. package/dist/hooks.d.ts +75 -0
  20. package/dist/hooks.js +110 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.js +34 -0
  23. package/dist/main.d.ts +46 -0
  24. package/dist/main.js +181 -0
  25. package/dist/manage-record.d.ts +13 -0
  26. package/dist/manage-record.js +123 -0
  27. package/dist/meta-request.d.ts +6 -0
  28. package/dist/meta-request.js +52 -0
  29. package/dist/migrate.d.ts +2 -0
  30. package/dist/migrate.js +57 -0
  31. package/dist/model-property.d.ts +9 -0
  32. package/dist/model-property.js +29 -0
  33. package/dist/model.d.ts +15 -0
  34. package/dist/model.js +18 -0
  35. package/dist/mysql/connection.d.ts +14 -0
  36. package/dist/mysql/connection.js +24 -0
  37. package/dist/mysql/migration-generator.d.ts +45 -0
  38. package/dist/mysql/migration-generator.js +254 -0
  39. package/dist/mysql/migration-runner.d.ts +12 -0
  40. package/dist/mysql/migration-runner.js +88 -0
  41. package/dist/mysql/mysql-db.d.ts +100 -0
  42. package/dist/mysql/mysql-db.js +425 -0
  43. package/dist/mysql/query-builder.d.ts +10 -0
  44. package/dist/mysql/query-builder.js +44 -0
  45. package/dist/mysql/schema-introspector.d.ts +19 -0
  46. package/dist/mysql/schema-introspector.js +257 -0
  47. package/dist/mysql/type-map.d.ts +21 -0
  48. package/dist/mysql/type-map.js +36 -0
  49. package/dist/orm-request.d.ts +38 -0
  50. package/dist/orm-request.js +475 -0
  51. package/dist/plural-registry.d.ts +4 -0
  52. package/dist/plural-registry.js +9 -0
  53. package/dist/postgres/connection.d.ts +15 -0
  54. package/dist/postgres/connection.js +32 -0
  55. package/dist/postgres/migration-generator.d.ts +45 -0
  56. package/dist/postgres/migration-generator.js +280 -0
  57. package/dist/postgres/migration-runner.d.ts +10 -0
  58. package/dist/postgres/migration-runner.js +87 -0
  59. package/dist/postgres/postgres-db.d.ts +119 -0
  60. package/dist/postgres/postgres-db.js +477 -0
  61. package/dist/postgres/query-builder.d.ts +27 -0
  62. package/dist/postgres/query-builder.js +98 -0
  63. package/dist/postgres/schema-introspector.d.ts +29 -0
  64. package/dist/postgres/schema-introspector.js +296 -0
  65. package/dist/postgres/type-map.d.ts +23 -0
  66. package/dist/postgres/type-map.js +56 -0
  67. package/dist/record.d.ts +75 -0
  68. package/dist/record.js +129 -0
  69. package/dist/relationships.d.ts +10 -0
  70. package/dist/relationships.js +41 -0
  71. package/dist/schema-helpers.d.ts +20 -0
  72. package/dist/schema-helpers.js +48 -0
  73. package/dist/serializer.d.ts +17 -0
  74. package/dist/serializer.js +136 -0
  75. package/dist/setup-rest-server.d.ts +1 -0
  76. package/dist/setup-rest-server.js +52 -0
  77. package/dist/standalone-db.d.ts +58 -0
  78. package/dist/standalone-db.js +142 -0
  79. package/dist/store.d.ts +62 -0
  80. package/dist/store.js +286 -0
  81. package/dist/timescale/query-builder.d.ts +43 -0
  82. package/dist/timescale/query-builder.js +115 -0
  83. package/dist/timescale/timescale-db.d.ts +45 -0
  84. package/dist/timescale/timescale-db.js +84 -0
  85. package/dist/transforms.d.ts +2 -0
  86. package/dist/transforms.js +17 -0
  87. package/dist/types/orm-types.d.ts +153 -0
  88. package/dist/types/orm-types.js +1 -0
  89. package/dist/utils.d.ts +7 -0
  90. package/dist/utils.js +17 -0
  91. package/dist/view-resolver.d.ts +8 -0
  92. package/dist/view-resolver.js +171 -0
  93. package/dist/view.d.ts +11 -0
  94. package/dist/view.js +18 -0
  95. package/package.json +64 -11
  96. package/src/aggregates.ts +109 -0
  97. package/src/{attr.js → attr.ts} +2 -2
  98. package/src/belongs-to.ts +90 -0
  99. package/src/cli.ts +183 -0
  100. package/src/commands.ts +179 -0
  101. package/src/db.ts +232 -0
  102. package/src/exports/db.ts +7 -0
  103. package/src/has-many.ts +92 -0
  104. package/src/hooks.ts +151 -0
  105. package/src/{index.js → index.ts} +12 -2
  106. package/src/main.ts +229 -0
  107. package/src/manage-record.ts +161 -0
  108. package/src/{meta-request.js → meta-request.ts} +17 -14
  109. package/src/migrate.ts +72 -0
  110. package/src/model-property.ts +35 -0
  111. package/src/model.ts +21 -0
  112. package/src/mysql/connection.ts +43 -0
  113. package/src/mysql/migration-generator.ts +337 -0
  114. package/src/mysql/migration-runner.ts +121 -0
  115. package/src/mysql/mysql-db.ts +543 -0
  116. package/src/mysql/query-builder.ts +69 -0
  117. package/src/mysql/schema-introspector.ts +310 -0
  118. package/src/mysql/type-map.ts +42 -0
  119. package/src/orm-request.ts +582 -0
  120. package/src/plural-registry.ts +12 -0
  121. package/src/postgres/connection.ts +48 -0
  122. package/src/postgres/migration-generator.ts +370 -0
  123. package/src/postgres/migration-runner.ts +115 -0
  124. package/src/postgres/postgres-db.ts +616 -0
  125. package/src/postgres/query-builder.ts +148 -0
  126. package/src/postgres/schema-introspector.ts +360 -0
  127. package/src/postgres/type-map.ts +61 -0
  128. package/src/record.ts +186 -0
  129. package/src/relationships.ts +54 -0
  130. package/src/schema-helpers.ts +59 -0
  131. package/src/serializer.ts +161 -0
  132. package/src/setup-rest-server.ts +62 -0
  133. package/src/standalone-db.ts +185 -0
  134. package/src/store.ts +373 -0
  135. package/src/timescale/query-builder.ts +174 -0
  136. package/src/timescale/timescale-db.ts +119 -0
  137. package/src/transforms.ts +20 -0
  138. package/src/types/mysql2.d.ts +49 -0
  139. package/src/types/orm-types.ts +158 -0
  140. package/src/types/pg.d.ts +32 -0
  141. package/src/types/stonyx-cron.d.ts +5 -0
  142. package/src/types/stonyx-events.d.ts +4 -0
  143. package/src/types/stonyx-rest-server.d.ts +16 -0
  144. package/src/types/stonyx-utils.d.ts +33 -0
  145. package/src/types/stonyx.d.ts +21 -0
  146. package/src/utils.ts +22 -0
  147. package/src/view-resolver.ts +211 -0
  148. package/src/view.ts +22 -0
  149. package/.claude/project-structure.md +0 -578
  150. package/.github/workflows/ci.yml +0 -36
  151. package/.github/workflows/publish.yml +0 -143
  152. package/src/belongs-to.js +0 -63
  153. package/src/db.js +0 -80
  154. package/src/has-many.js +0 -61
  155. package/src/main.js +0 -119
  156. package/src/manage-record.js +0 -103
  157. package/src/model-property.js +0 -29
  158. package/src/model.js +0 -9
  159. package/src/orm-request.js +0 -249
  160. package/src/record.js +0 -100
  161. package/src/relationships.js +0 -43
  162. package/src/serializer.js +0 -138
  163. package/src/setup-rest-server.js +0 -57
  164. package/src/store.js +0 -211
  165. package/src/transforms.js +0 -20
  166. package/stonyx-bootstrap.cjs +0 -30
@@ -1,249 +0,0 @@
1
- import { Request } from '@stonyx/rest-server';
2
- import { createRecord, store } from '@stonyx/orm';
3
- import { pluralize } from '@stonyx/utils/string';
4
-
5
- const methodAccessMap = {
6
- GET: 'read',
7
- POST: 'create',
8
- DELETE: 'delete',
9
- PATCH: 'update',
10
- };
11
-
12
- function getId({ id }) {
13
- if (isNaN(id)) return id;
14
-
15
- return parseInt(id);
16
- }
17
-
18
- function buildResponse(data, includeParam, recordOrRecords) {
19
- const response = { data };
20
-
21
- if (!includeParam) return response;
22
-
23
- const includes = parseInclude(includeParam);
24
- if (includes.length === 0) return response;
25
-
26
- const includedRecords = collectIncludedRecords(recordOrRecords, includes);
27
- if (includedRecords.length > 0) {
28
- response.included = includedRecords.map(record => record.toJSON());
29
- }
30
-
31
- return response;
32
- }
33
-
34
- /**
35
- * Recursively traverse an include path and collect related records
36
- * @param {Array<Record>} currentRecords - Records to process at current depth
37
- * @param {Array<string>} includePath - Full path array (e.g., ['owner', 'pets', 'traits'])
38
- * @param {number} depth - Current depth in the path
39
- * @param {Map} seen - Deduplication map
40
- * @param {Array} included - Accumulator for included records
41
- */
42
- function traverseIncludePath(currentRecords, includePath, depth, seen, included) {
43
- if (depth >= includePath.length) return; // Reached end of path
44
-
45
- const relationshipName = includePath[depth];
46
- const nextRecords = [];
47
-
48
- for (const record of currentRecords) {
49
- if (!record.__relationships) continue;
50
- if (!(relationshipName in record.__relationships)) continue;
51
-
52
- const relatedRecords = record.__relationships[relationshipName];
53
- if (!relatedRecords) continue;
54
-
55
- // Handle both belongsTo (single) and hasMany (array)
56
- const recordsToProcess = Array.isArray(relatedRecords)
57
- ? relatedRecords
58
- : [relatedRecords];
59
-
60
- for (const relatedRecord of recordsToProcess) {
61
- if (!relatedRecord) continue;
62
-
63
- const type = relatedRecord.__model.__name;
64
- const id = relatedRecord.id;
65
-
66
- // Initialize Set for this type if needed
67
- if (!seen.has(type)) {
68
- seen.set(type, new Set());
69
- }
70
-
71
- // Check if we've already seen this type+id combination
72
- if (!seen.get(type).has(id)) {
73
- seen.get(type).add(id);
74
- included.push(relatedRecord);
75
- nextRecords.push(relatedRecord); // Prepare for next depth level
76
- } else if (depth < includePath.length - 1) {
77
- // Even if we've seen this record, we might need it for deeper traversal
78
- nextRecords.push(relatedRecord);
79
- }
80
- }
81
- }
82
-
83
- // If there are more segments in the path, recursively process
84
- if (depth < includePath.length - 1 && nextRecords.length > 0) {
85
- traverseIncludePath(nextRecords, includePath, depth + 1, seen, included);
86
- }
87
- }
88
-
89
- function collectIncludedRecords(data, includes) {
90
- if (!includes || includes.length === 0) return [];
91
- if (!data) return [];
92
-
93
- const seen = new Map(); // Map<type, Set<id>> for deduplication
94
- const included = [];
95
-
96
- // Normalize to array for consistent processing
97
- const records = Array.isArray(data) ? data : [data];
98
-
99
- // Process each include path
100
- for (const includePath of includes) {
101
- traverseIncludePath(records, includePath, 0, seen, included);
102
- }
103
-
104
- return included;
105
- }
106
-
107
- function parseInclude(includeParam) {
108
- if (!includeParam || typeof includeParam !== 'string') return [];
109
-
110
- return includeParam
111
- .split(',')
112
- .map(rel => rel.trim())
113
- .filter(rel => rel.length > 0)
114
- .map(rel => rel.split('.')); // Parse nested paths: "owner.pets" → ["owner", "pets"]
115
- }
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
-
162
- export default class OrmRequest extends Request {
163
- constructor({ model, access }) {
164
- super(...arguments);
165
-
166
- this.access = access;
167
- const pluralizedModel = pluralize(model);
168
-
169
- this.handlers = {
170
- get: {
171
- [`/${pluralizedModel}`]: (request, { filter: accessFilter }) => {
172
- const allRecords = Array.from(store.get(model).values());
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 }));
184
- return buildResponse(data, request.query?.include, recordsToReturn);
185
- },
186
-
187
- [`/${pluralizedModel}/:id`]: (request) => {
188
- const record = store.get(model, getId(request.params));
189
- if (!record) return 404;
190
-
191
- const fieldsMap = parseFields(request.query);
192
- const modelFields = fieldsMap.get(pluralizedModel) || fieldsMap.get(model);
193
-
194
- return buildResponse(record.toJSON({ fields: modelFields }), request.query?.include, record);
195
- }
196
- },
197
-
198
- patch: {
199
- [`/${pluralizedModel}/:id`]: async ({ body, params }) => {
200
- const record = store.get(model, getId(params));
201
- const { attributes } = body?.data || {};
202
-
203
- if (!attributes) return 400; // Bad request
204
-
205
- // Apply updates 1 by 1 to utilize built-in transform logic, ignore id key
206
- for (const [key, value] of Object.entries(attributes)) {
207
- if (!record.hasOwnProperty(key)) continue;
208
- if (key === 'id') continue;
209
-
210
- record[key] = value
211
- };
212
-
213
- return { data: record.toJSON() };
214
- }
215
- },
216
-
217
- post: {
218
- [`/${pluralizedModel}`]: (request) => {
219
- const { attributes } = request.body?.data || {};
220
-
221
- if (!attributes) return 400; // Bad request
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
-
228
- const record = createRecord(model, attributes, { serialize: false });
229
-
230
- return { data: record.toJSON({ fields: modelFields }) };
231
- }
232
- },
233
-
234
- delete: {
235
- [`/${pluralizedModel}/:id`]: ({ params }) => {
236
- store.remove(model, getId(params));
237
- }
238
- }
239
- }
240
- }
241
-
242
- auth(request, state) {
243
- const access = this.access(request);
244
-
245
- if (!access) return 403;
246
- if (Array.isArray(access) && !access.includes(methodAccessMap[request.method])) return 403;
247
- if (typeof access === 'function') state.filter = access;
248
- }
249
- }
package/src/record.js DELETED
@@ -1,100 +0,0 @@
1
- import { store } from './index.js';
2
- import { getComputedProperties } from "./serializer.js";
3
- export default class Record {
4
- __data = {};
5
- __relationships = {};
6
- __serialized = false;
7
-
8
- constructor(model, serializer) {
9
- this.__model = model;
10
- this.__serializer = serializer;
11
- }
12
-
13
- serialize(rawData, options={}) {
14
- const { __data:data } = this;
15
-
16
- if (this.__serialized && !options.update) {
17
- const relatedIds = {};
18
-
19
- for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
20
- relatedIds[key] = Array.isArray(childRecord)
21
- ? childRecord.map(r => r.id)
22
- : childRecord?.id ?? null;
23
- }
24
-
25
- return { ...data, ...relatedIds };
26
- }
27
-
28
- const normalizedData = this.__serializer.normalize(rawData);
29
- this.__serializer.setProperties(normalizedData, this, options);
30
-
31
- return data;
32
- }
33
-
34
- // Similar to serialize, but preserves top level relationship records
35
- format() {
36
- if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
37
-
38
- const { __data:data } = this;
39
- const records = {};
40
-
41
- for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
42
- records[key] = Array.isArray(childRecord)
43
- ? childRecord.map(r => r.serialize())
44
- : childRecord?.serialize() ?? null;
45
- }
46
-
47
- return { ...data, ...records };
48
- }
49
-
50
- // Formats record for JSON API output
51
- toJSON(options = {}) {
52
- if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
53
-
54
- const { __data:data } = this;
55
- const { fields } = options;
56
- const relationships = {};
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
- }
64
-
65
- for (const [key, getter] of getComputedProperties(this.__model)) {
66
- if (fields && !fields.has(key)) continue;
67
- attributes[key] = getter.call(this);
68
- }
69
-
70
- for (const [key, childRecord] of Object.entries(this.__relationships)) {
71
- if (fields && !fields.has(key)) continue;
72
- relationships[key] = {
73
- data: Array.isArray(childRecord)
74
- ? childRecord.map(r => ({ type: r.__model.__name, id: r.id }))
75
- : childRecord ? { type: childRecord.__model.__name, id: childRecord.id } : null
76
- };
77
- }
78
-
79
- return {
80
- attributes,
81
- relationships,
82
- id: data.id,
83
- type: this.__model.__name,
84
- };
85
- }
86
-
87
- unload(options={}) {
88
- store.unloadRecord(this.__model.__name, this.id, options);
89
- }
90
-
91
- clean() {
92
- try {
93
- for (const key of Object.keys(this)) {
94
- delete this[key];
95
- }
96
- } catch {
97
- // Ignore errors during cleanup, as some keys may not be deletable
98
- }
99
- }
100
- }
@@ -1,43 +0,0 @@
1
- import { relationships } from "@stonyx/orm";
2
-
3
- export default class Relationships {
4
- constructor() {
5
- if (Relationships.instance) return Relationships.instance;
6
- Relationships.instance = this;
7
-
8
- this.data = new Map();
9
- }
10
-
11
- get(key) {
12
- return this.data.get(key);
13
- }
14
-
15
- set(key, value) {
16
- this.data.set(key, value);
17
- }
18
- }
19
-
20
- // TODO: Refactor mapping to remove a level of iteration
21
- export function getRelationships(type, sourceModel, targetModel, relationshipId) {
22
- const allRelationships = relationships.get(type);
23
-
24
- // create relationship map for this type of it doesn't already exist
25
- if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
26
-
27
- const modelRelationship = allRelationships.get(sourceModel);
28
-
29
- if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
30
-
31
- const relationship = modelRelationship.get(targetModel);
32
-
33
- // TODO: Determine whether already having id should be handled differently
34
- //if (relationship.has(relationshipId)) return;
35
-
36
- return relationship;
37
- }
38
-
39
- export function getHasManyRelationships(sourceModel, targetModel) {
40
- return relationships.get('hasMany').get(sourceModel)?.get(targetModel);
41
- }
42
-
43
- export const TYPES = ['global', 'hasMany', 'belongsTo', 'pending'];
package/src/serializer.js DELETED
@@ -1,138 +0,0 @@
1
- import config from 'stonyx/config';
2
- import { get, makeArray } from '@stonyx/utils/object';
3
-
4
- const RESERVED_KEYS = ['__name'];
5
-
6
- function searchQuery(query, array, key) {
7
- const result = makeArray(array).find(item => {
8
- for (const [ prop, value ] of Object.entries(query)) {
9
- if (item[prop] !== value) return false;
10
-
11
- return true;
12
- }
13
- });
14
-
15
- if (!result) return null;
16
- if (key) return result[key];
17
-
18
- return result;
19
- }
20
-
21
- function query(rawData, pathPrefix, subPath) {
22
- if (!rawData) return null;
23
-
24
- const [ path, getter, pointer ] = makeArray(subPath);
25
- const fullPath = `${pathPrefix}${path}`;
26
- const value = get(rawData, fullPath);
27
-
28
- if (getter === undefined || getter === null) return value;
29
-
30
- try {
31
- switch(typeof getter) {
32
- case 'object':
33
- return searchQuery(getter, value, pointer);
34
-
35
- case 'function':
36
- return getter(value);
37
-
38
- case 'number':
39
- const element = value[getter];
40
- return pointer ? element[pointer] : element;
41
-
42
- default:
43
- return value[getter];
44
- }
45
- } catch (error) {
46
- if (config.debug) console.error(`Cannot parse value for ${fullPath}.`, { getter, query }, error);
47
- }
48
- }
49
-
50
- export default class Serializer {
51
- map = {};
52
- path = '';
53
-
54
- constructor(model) {
55
- this.model = model;
56
- }
57
-
58
- /**
59
- * This method populates the record's instance with instances of
60
- * the ModelProperty object, while setting parsed values to the record's
61
- * __data property, which represents the serialized version of the data
62
- */
63
- setProperties(rawData, record, options) {
64
- const { path, model } = this;
65
- const keys = Object.keys(model).filter(key => !RESERVED_KEYS.includes(key));
66
- const pathPrefix = path ? `${path}.` : '';
67
- const { __data:parsedData, __relationships:relatedRecords } = record;
68
-
69
- for (const key of keys) {
70
- const subPath = options.serialize ? (this.map[key] || key) : key;
71
- const handler = model[key];
72
- const data = query(rawData, pathPrefix, subPath);
73
-
74
- // Ignore null values on updates (TODO: What if we want it set to null?)
75
- if (data === null && options.update) continue;
76
-
77
- // Relationship handling
78
- if (typeof handler === 'function') {
79
- // Pass relationship key name to handler for pending fulfillment
80
- const handlerOptions = { ...options, _relationshipKey: key };
81
- const childRecord = handler(record, data, handlerOptions);
82
-
83
- record[key] = childRecord
84
- relatedRecords[key] = childRecord;
85
-
86
- continue;
87
- }
88
-
89
- // Direct assignment handling
90
- if (handler?.constructor?.name !== 'ModelProperty') {
91
- parsedData[key] = handler;
92
- record[key] = handler;
93
- continue;
94
- }
95
-
96
- Object.defineProperty(record, key, {
97
- enumerable: true,
98
- configurable: true,
99
- get: () => handler.value,
100
- set(newValue) {
101
- handler.ignoreFirstTransform = !options.transform;
102
- handler.value = newValue;
103
- parsedData[key] = handler.value;
104
- }
105
- });
106
-
107
- record[key] = data;
108
- }
109
-
110
- if (options.update) return;
111
-
112
- // Serialize computed properties
113
- for (const [key, getter] of getComputedProperties(this.model)) {
114
- Object.defineProperty(record, key, {
115
- enumerable: true,
116
- get: () => getter.call(record)
117
- });
118
- }
119
-
120
- record.__serialized = true;
121
- }
122
-
123
- /**
124
- * OVERRIDE: This hook allows for data manipulation prior to serialization logic
125
- */
126
- normalize(data) {
127
- return data;
128
- }
129
- }
130
-
131
- export function getComputedProperties(classInstance) {
132
- const proto = Object.getPrototypeOf(classInstance);
133
- if (!proto || proto === Object.prototype) return [];
134
-
135
- return Object.entries(Object.getOwnPropertyDescriptors(proto))
136
- .filter(([key, descriptor]) => key !== 'constructor' && descriptor.get)
137
- .map(([key, descriptor]) => [key, descriptor.get]);
138
- }
@@ -1,57 +0,0 @@
1
- import { waitForModule } from 'stonyx';
2
- import { store } from '@stonyx/orm';
3
- import OrmRequest from './orm-request.js';
4
- import MetaRequest from './meta-request.js';
5
- import RestServer from '@stonyx/rest-server';
6
- import { forEachFileImport } from '@stonyx/utils/file';
7
- import { dbKey } from './db.js';
8
- import log from 'stonyx/log';
9
-
10
- export default async function(route, accessPath, metaRoute) {
11
- let accessFiles = {};
12
-
13
- try {
14
- await forEachFileImport(accessPath, accessClass => {
15
- const accessInstance = new accessClass();
16
- const { models } = accessInstance;
17
-
18
- if (!models) throw new Error(`Access class "${accessClass.name}" must define a "models" list`);
19
-
20
- if (models.length === 0) return; // No models to assign access to
21
- if (typeof accessInstance.access !== 'function') throw new Error(`Access class "${accessClass.name}" must declare an "access" method`);
22
-
23
- const availableModels = Array.from(store.data.keys());
24
-
25
- for (const model of models === '*' ? availableModels : models) {
26
- if (model === dbKey) continue;
27
- if (!store.data.has(model)) throw new Error(`Unable to define access for Invalid Model "${model}". Model does not exist`);
28
- if (accessFiles[model]) throw new Error(`Access for model "${model}" has already been defined by another access class.`);
29
-
30
- accessFiles[model] = accessInstance.access;
31
- }
32
- });
33
- } catch (error) {
34
- log.error(error.message);
35
- log.warn('You must define a valid access configuration file in order to access ORM generated REST endpoints.');
36
- }
37
-
38
- await waitForModule('rest-server');
39
-
40
- // Remove "/" prefix and name mount point accordingly
41
- const name = route === '/' ? 'index' : (route[0] === '/' ? route.slice(1) : route);
42
-
43
- // Configure endpoints for models with access configuration
44
- for (const [model, access] of Object.entries(accessFiles)) {
45
- RestServer.instance.mountRoute(OrmRequest, { name, options: { model, access } });
46
- }
47
-
48
- // Mount the meta route when metaRoute config is enabled
49
- if (metaRoute) {
50
- log.warn('SECURITY RISK! - Meta route is enabled via metaRoute config. This feature is intended for development purposes only!');
51
-
52
- RestServer.instance.mountRoute(MetaRequest, { name });
53
- }
54
-
55
- // Cleanup references
56
- accessFiles = null;
57
- }