@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,189 @@
|
|
|
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
|
+
export default class OrmRequest extends Request {
|
|
118
|
+
constructor({ model, access }) {
|
|
119
|
+
super(...arguments);
|
|
120
|
+
|
|
121
|
+
this.access = access;
|
|
122
|
+
const pluralizedModel = pluralize(model);
|
|
123
|
+
|
|
124
|
+
this.handlers = {
|
|
125
|
+
get: {
|
|
126
|
+
[`/${pluralizedModel}`]: (request, { filter }) => {
|
|
127
|
+
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
|
+
|
|
131
|
+
return buildResponse(data, request.query?.include, recordsToReturn);
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
[`/${pluralizedModel}/:id`]: (request) => {
|
|
135
|
+
const record = store.get(model, getId(request.params));
|
|
136
|
+
|
|
137
|
+
if (!record) return 404; // Record not found
|
|
138
|
+
|
|
139
|
+
return buildResponse(record.toJSON(), request.query?.include, record);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
patch: {
|
|
144
|
+
[`/${pluralizedModel}/:id`]: async ({ body, params }) => {
|
|
145
|
+
const record = store.get(model, getId(params));
|
|
146
|
+
const { attributes } = body?.data || {};
|
|
147
|
+
|
|
148
|
+
if (!attributes) return 400; // Bad request
|
|
149
|
+
|
|
150
|
+
// Apply updates 1 by 1 to utilize built-in transform logic, ignore id key
|
|
151
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
152
|
+
if (!record.hasOwnProperty(key)) continue;
|
|
153
|
+
if (key === 'id') continue;
|
|
154
|
+
|
|
155
|
+
record[key] = value
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return { data: record.toJSON() };
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
post: {
|
|
163
|
+
[`/${pluralizedModel}`]: ({ body }) => {
|
|
164
|
+
const { attributes } = body?.data || {};
|
|
165
|
+
|
|
166
|
+
if (!attributes) return 400; // Bad request
|
|
167
|
+
|
|
168
|
+
const record = createRecord(model, attributes, { serialize: false });
|
|
169
|
+
|
|
170
|
+
return { data: record.toJSON() };
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
delete: {
|
|
175
|
+
[`/${pluralizedModel}/:id`]: ({ params }) => {
|
|
176
|
+
store.remove(model, getId(params));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
auth(request, state) {
|
|
183
|
+
const access = this.access(request);
|
|
184
|
+
|
|
185
|
+
if (!access) return 403;
|
|
186
|
+
if (Array.isArray(access) && !access.includes(methodAccessMap[request.method])) return 403;
|
|
187
|
+
if (typeof access === 'function') state.filter = access;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/src/record.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { store } from './index.js';
|
|
2
|
+
import { getComputedProperties } from "./serializer.js";
|
|
3
3
|
export default class Record {
|
|
4
4
|
__data = {};
|
|
5
|
+
__relationships = {};
|
|
5
6
|
__serialized = false;
|
|
6
7
|
|
|
7
8
|
constructor(model, serializer) {
|
|
@@ -9,20 +10,83 @@ export default class Record {
|
|
|
9
10
|
this.__serializer = serializer;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
serialize(rawData) {
|
|
13
|
+
serialize(rawData, options={}) {
|
|
13
14
|
const { __data:data } = this;
|
|
14
15
|
|
|
15
|
-
if (this.__serialized)
|
|
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
|
+
}
|
|
16
27
|
|
|
17
28
|
const normalizedData = this.__serializer.normalize(rawData);
|
|
18
|
-
this.__serializer.setProperties(normalizedData, this);
|
|
29
|
+
this.__serializer.setProperties(normalizedData, this, options);
|
|
19
30
|
|
|
20
31
|
return data;
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
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() {
|
|
52
|
+
if (!this.__serialized) throw new Error('Record must be serialized before being converted to JSON');
|
|
25
53
|
|
|
26
|
-
|
|
54
|
+
const { __data:data } = this;
|
|
55
|
+
const relationships = {};
|
|
56
|
+
const attributes = { ...data };
|
|
57
|
+
delete attributes.id;
|
|
58
|
+
|
|
59
|
+
for (const [key, getter] of getComputedProperties(this.__model)) {
|
|
60
|
+
attributes[key] = getter.call(this);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const [ key, childRecord ] of Object.entries(this.__relationships)) {
|
|
64
|
+
relationships[key] = {
|
|
65
|
+
data: Array.isArray(childRecord)
|
|
66
|
+
? childRecord.map(r => ({ type: r.__model.__name, id: r.id }))
|
|
67
|
+
: childRecord ? { type: childRecord.__model.__name, id: childRecord.id } : null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
attributes,
|
|
73
|
+
relationships,
|
|
74
|
+
id: data.id,
|
|
75
|
+
type: this.__model.__name,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
unload(options={}) {
|
|
80
|
+
store.unloadRecord(this.__model.__name, this.id, options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clean() {
|
|
84
|
+
try {
|
|
85
|
+
for (const key of Object.keys(this)) {
|
|
86
|
+
delete this[key];
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore errors during cleanup, as some keys may not be deletable
|
|
90
|
+
}
|
|
27
91
|
}
|
|
28
92
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import config from 'stonyx/config';
|
|
2
|
-
import { makeArray } from '@stonyx/utils/object';
|
|
3
|
-
import { get, getComputedProperties } from '@stonyx/orm/utils';
|
|
2
|
+
import { get, makeArray } from '@stonyx/utils/object';
|
|
4
3
|
|
|
5
4
|
const RESERVED_KEYS = ['__name'];
|
|
6
5
|
|
|
@@ -48,7 +47,7 @@ function query(rawData, pathPrefix, subPath) {
|
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
export default class
|
|
50
|
+
export default class Serializer {
|
|
52
51
|
map = {};
|
|
53
52
|
path = '';
|
|
54
53
|
|
|
@@ -61,25 +60,28 @@ export default class BaseSerializer {
|
|
|
61
60
|
* the ModelProperty object, while setting parsed values to the record's
|
|
62
61
|
* __data property, which represents the serialized version of the data
|
|
63
62
|
*/
|
|
64
|
-
setProperties(rawData, record) {
|
|
63
|
+
setProperties(rawData, record, options) {
|
|
65
64
|
const { path, model } = this;
|
|
66
65
|
const keys = Object.keys(model).filter(key => !RESERVED_KEYS.includes(key));
|
|
67
66
|
const pathPrefix = path ? `${path}.` : '';
|
|
68
|
-
const { __data:parsedData } = record;
|
|
69
|
-
const { postTransform } = model;
|
|
67
|
+
const { __data:parsedData, __relationships:relatedRecords } = record;
|
|
70
68
|
|
|
71
69
|
for (const key of keys) {
|
|
72
|
-
const subPath = this.map[key] || key;
|
|
70
|
+
const subPath = options.serialize ? (this.map[key] || key) : key;
|
|
73
71
|
const handler = model[key];
|
|
74
72
|
const data = query(rawData, pathPrefix, subPath);
|
|
75
73
|
|
|
74
|
+
// Ignore null values on updates (TODO: What if we want it set to null?)
|
|
75
|
+
if (data === null && options.update) continue;
|
|
76
|
+
|
|
76
77
|
// Relationship handling
|
|
77
78
|
if (typeof handler === 'function') {
|
|
78
|
-
|
|
79
|
+
// Pass relationship key name to handler for pending fulfillment
|
|
80
|
+
const handlerOptions = { ...options, _relationshipKey: key };
|
|
81
|
+
const childRecord = handler(record, data, handlerOptions);
|
|
82
|
+
|
|
79
83
|
record[key] = childRecord
|
|
80
|
-
|
|
81
|
-
? childRecord.map(record => record.serialize())
|
|
82
|
-
: childRecord.serialize();
|
|
84
|
+
relatedRecords[key] = childRecord;
|
|
83
85
|
|
|
84
86
|
continue;
|
|
85
87
|
}
|
|
@@ -91,22 +93,28 @@ export default class BaseSerializer {
|
|
|
91
93
|
continue;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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;
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
if (options.update) return;
|
|
111
|
+
|
|
104
112
|
// Serialize computed properties
|
|
105
|
-
for (const [key,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
for (const [key, getter] of getComputedProperties(this.model)) {
|
|
114
|
+
Object.defineProperty(record, key, {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: () => getter.call(record)
|
|
117
|
+
});
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
record.__serialized = true;
|
|
@@ -119,3 +127,12 @@ export default class BaseSerializer {
|
|
|
119
127
|
return data;
|
|
120
128
|
}
|
|
121
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
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|