@stonyx/orm 0.2.1-beta.87 → 0.2.1-beta.88
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/dist/aggregates.js +9 -6
- package/dist/db.js +4 -4
- package/dist/hooks.js +6 -2
- package/dist/main.js +3 -1
- package/dist/manage-record.js +6 -2
- package/dist/mysql/migration-generator.js +15 -6
- package/dist/mysql/mysql-db.js +24 -14
- package/dist/mysql/schema-introspector.js +11 -6
- package/dist/orm-request.js +19 -11
- package/dist/postgres/migration-generator.js +9 -5
- package/dist/postgres/postgres-db.js +14 -13
- package/dist/postgres/schema-introspector.js +11 -6
- package/dist/relationships.js +2 -0
- package/dist/setup-rest-server.js +4 -6
- package/dist/store.js +2 -0
- package/dist/utils.js +2 -1
- package/dist/view-resolver.js +3 -1
- package/package.json +1 -1
- package/src/aggregates.ts +9 -7
- package/src/belongs-to.ts +1 -1
- package/src/db.ts +4 -4
- package/src/hooks.ts +4 -2
- package/src/main.ts +3 -2
- package/src/manage-record.ts +5 -2
- package/src/mysql/migration-generator.ts +12 -7
- package/src/mysql/mysql-db.ts +23 -17
- package/src/mysql/schema-introspector.ts +10 -7
- package/src/orm-request.ts +19 -12
- package/src/postgres/migration-generator.ts +7 -5
- package/src/postgres/postgres-db.ts +14 -13
- package/src/postgres/schema-introspector.ts +10 -7
- package/src/relationships.ts +3 -2
- package/src/setup-rest-server.ts +7 -10
- package/src/store.ts +2 -1
- package/src/utils.ts +2 -1
- package/src/view-resolver.ts +2 -1
|
@@ -122,7 +122,7 @@ export default class PostgresDB {
|
|
|
122
122
|
const pending = files.filter(f => !applied.includes(f));
|
|
123
123
|
|
|
124
124
|
if (pending.length > 0) {
|
|
125
|
-
this.deps.log.db
|
|
125
|
+
this.deps.log.db?.(`${pending.length} pending migration(s) found.`);
|
|
126
126
|
|
|
127
127
|
const shouldApply = await this.deps.confirm(`${pending.length} pending migration(s) found. Apply now?`);
|
|
128
128
|
|
|
@@ -132,13 +132,13 @@ export default class PostgresDB {
|
|
|
132
132
|
const { up } = this.deps.parseMigrationFile(content);
|
|
133
133
|
|
|
134
134
|
await this.deps.applyMigration(this.requirePool(), filename, up, this.pgConfig.migrationsTable as string | undefined);
|
|
135
|
-
this.deps.log.db
|
|
135
|
+
this.deps.log.db?.(`Applied migration: ${filename}`);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// Reload records after applying migrations
|
|
139
139
|
await this.loadMemoryRecords();
|
|
140
140
|
} else {
|
|
141
|
-
this.deps.log.warn
|
|
141
|
+
this.deps.log.warn?.('Skipping pending migrations. Schema may be outdated.');
|
|
142
142
|
}
|
|
143
143
|
} else if (files.length === 0) {
|
|
144
144
|
const schemas = this.deps.introspectModels();
|
|
@@ -156,11 +156,11 @@ export default class PostgresDB {
|
|
|
156
156
|
if (result) {
|
|
157
157
|
const { up } = this.deps.parseMigrationFile(result.content);
|
|
158
158
|
await this.deps.applyMigration(this.requirePool(), result.filename, up, this.pgConfig.migrationsTable as string | undefined);
|
|
159
|
-
this.deps.log.db
|
|
159
|
+
this.deps.log.db?.(`Applied migration: ${result.filename}`);
|
|
160
160
|
await this.loadMemoryRecords();
|
|
161
161
|
}
|
|
162
162
|
} else {
|
|
163
|
-
this.deps.log.warn
|
|
163
|
+
this.deps.log.warn?.('Skipping initial migration. Tables may not exist.');
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
}
|
|
@@ -173,8 +173,8 @@ export default class PostgresDB {
|
|
|
173
173
|
const drift = this.deps.detectSchemaDrift(schemas, snapshot as Parameters<typeof detectSchemaDrift>[1]);
|
|
174
174
|
|
|
175
175
|
if (drift.hasChanges) {
|
|
176
|
-
this.deps.log.warn
|
|
177
|
-
this.deps.log.warn
|
|
176
|
+
this.deps.log.warn?.('Schema drift detected: models have changed since the last migration.');
|
|
177
|
+
this.deps.log.warn?.('Run `stonyx db:generate-migration` to create a new migration.');
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -200,7 +200,7 @@ export default class PostgresDB {
|
|
|
200
200
|
for (const modelName of order) {
|
|
201
201
|
const { modelClass } = Orm.instance.getRecordClasses(modelName) as { modelClass: { memory?: boolean } };
|
|
202
202
|
if (modelClass?.memory === false) {
|
|
203
|
-
this.deps.log.db
|
|
203
|
+
this.deps.log.db?.(`Skipping memory load for '${modelName}' (memory: false)`);
|
|
204
204
|
continue;
|
|
205
205
|
}
|
|
206
206
|
|
|
@@ -217,7 +217,7 @@ export default class PostgresDB {
|
|
|
217
217
|
} catch (error) {
|
|
218
218
|
// 42P01 = undefined_table (PG equivalent of ER_NO_SUCH_TABLE)
|
|
219
219
|
if (isDbError(error) && error.code === '42P01') {
|
|
220
|
-
this.deps.log.db
|
|
220
|
+
this.deps.log.db?.(`Table '${schema.table}' does not exist yet. Skipping load for '${modelName}'.`);
|
|
221
221
|
continue;
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -231,7 +231,7 @@ export default class PostgresDB {
|
|
|
231
231
|
for (const [viewName, viewSchema] of Object.entries(viewSchemas)) {
|
|
232
232
|
const { modelClass: viewClass } = Orm.instance.getRecordClasses(viewName) as { modelClass: { memory?: boolean } };
|
|
233
233
|
if (viewClass?.memory !== true) {
|
|
234
|
-
this.deps.log.db
|
|
234
|
+
this.deps.log.db?.(`Skipping memory load for view '${viewName}' (memory: false)`);
|
|
235
235
|
continue;
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -256,7 +256,7 @@ export default class PostgresDB {
|
|
|
256
256
|
}
|
|
257
257
|
} catch (error) {
|
|
258
258
|
if (isDbError(error) && error.code === '42P01') {
|
|
259
|
-
this.deps.log.db
|
|
259
|
+
this.deps.log.db?.(`View '${viewSchema.viewName}' does not exist yet. Skipping load for '${viewName}'.`);
|
|
260
260
|
continue;
|
|
261
261
|
}
|
|
262
262
|
throw error;
|
|
@@ -345,13 +345,14 @@ export default class PostgresDB {
|
|
|
345
345
|
|
|
346
346
|
if (!schema) return [];
|
|
347
347
|
|
|
348
|
-
const
|
|
348
|
+
const resolvedSchema = schema;
|
|
349
|
+
const { sql, values } = this.deps.buildSelect(resolvedSchema.table, conditions);
|
|
349
350
|
|
|
350
351
|
try {
|
|
351
352
|
const result = await this.requirePool().query(sql, values);
|
|
352
353
|
|
|
353
354
|
const records = result.rows.map(row => {
|
|
354
|
-
const rawData = this._rowToRawData(row,
|
|
355
|
+
const rawData = this._rowToRawData(row, resolvedSchema);
|
|
355
356
|
return this.deps.createRecord(modelName, rawData, { isDbRecord: true, serialize: false, transform: false }) as unknown as OrmRecord;
|
|
356
357
|
});
|
|
357
358
|
|
|
@@ -93,9 +93,10 @@ export function introspectModels(): Record<string, ModelSchema> {
|
|
|
93
93
|
|
|
94
94
|
// Build foreign keys from belongsTo relationships
|
|
95
95
|
for (const [relName, targetModelName] of Object.entries(relationships.belongsTo)) {
|
|
96
|
+
if (!targetModelName) continue;
|
|
96
97
|
const fkColumn = `${relName}_id`;
|
|
97
98
|
foreignKeys[fkColumn] = {
|
|
98
|
-
references: sanitizeTableName(getPluralName(targetModelName
|
|
99
|
+
references: sanitizeTableName(getPluralName(targetModelName)),
|
|
99
100
|
column: 'id',
|
|
100
101
|
};
|
|
101
102
|
}
|
|
@@ -189,7 +190,7 @@ export function getTopologicalOrder(schemas: Record<string, ModelSchema>): strin
|
|
|
189
190
|
|
|
190
191
|
// Visit dependencies (belongsTo targets) first
|
|
191
192
|
for (const targetModelName of Object.values(schema.relationships.belongsTo)) {
|
|
192
|
-
visit(targetModelName
|
|
193
|
+
if (targetModelName) visit(targetModelName);
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
order.push(name);
|
|
@@ -233,11 +234,13 @@ export function introspectViews(): Record<string, ViewSchema> {
|
|
|
233
234
|
|
|
234
235
|
if (relInfo?.type === 'belongsTo') {
|
|
235
236
|
relationships.belongsTo[key] = relInfo.modelName;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
if (relInfo.modelName) {
|
|
238
|
+
const fkColumn = `${key}_id`;
|
|
239
|
+
foreignKeys[fkColumn] = {
|
|
240
|
+
references: sanitizeTableName(getPluralName(relInfo.modelName)),
|
|
241
|
+
column: 'id',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
241
244
|
} else if (relInfo?.type === 'hasMany') {
|
|
242
245
|
relationships.hasMany[key] = relInfo.modelName;
|
|
243
246
|
} else if (property instanceof ModelProperty) {
|
package/src/relationships.ts
CHANGED
|
@@ -13,11 +13,12 @@ export function getRelationships(type: string, sourceModel: string, targetModel:
|
|
|
13
13
|
// create relationship map for this type of it doesn't already exist
|
|
14
14
|
if (!allRelationships.has(sourceModel)) allRelationships.set(sourceModel, new Map());
|
|
15
15
|
|
|
16
|
-
const modelRelationship = allRelationships.get(sourceModel)
|
|
16
|
+
const modelRelationship = allRelationships.get(sourceModel) as Map<string, Map<unknown, unknown>> | undefined;
|
|
17
|
+
if (!modelRelationship) return undefined;
|
|
17
18
|
|
|
18
19
|
if (!modelRelationship.has(targetModel)) modelRelationship.set(targetModel, new Map());
|
|
19
20
|
|
|
20
|
-
const relationship = modelRelationship.get(targetModel)
|
|
21
|
+
const relationship = modelRelationship.get(targetModel) as Map<unknown, unknown> | undefined;
|
|
21
22
|
|
|
22
23
|
// TODO: Determine whether already having id should be handled differently
|
|
23
24
|
//if (relationship.has(relationshipId)) return;
|
package/src/setup-rest-server.ts
CHANGED
|
@@ -14,7 +14,7 @@ interface AccessInstance {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export default async function(route: string, accessPath: string, metaRoute: boolean): Promise<void> {
|
|
17
|
-
|
|
17
|
+
const accessFiles: Record<string, (request: unknown) => unknown> = {};
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
20
|
await forEachFileImport(accessPath, (accessClass: unknown) => {
|
|
@@ -31,14 +31,14 @@ export default async function(route: string, accessPath: string, metaRoute: bool
|
|
|
31
31
|
for (const model of models === '*' ? availableModels : models) {
|
|
32
32
|
if (model === dbKey) continue;
|
|
33
33
|
if (!store.data.has(model)) throw new Error(`Unable to define access for Invalid Model "${model}". Model does not exist`);
|
|
34
|
-
if (accessFiles
|
|
34
|
+
if (accessFiles[model]) throw new Error(`Access for model "${model}" has already been defined by another access class.`);
|
|
35
35
|
|
|
36
|
-
accessFiles
|
|
36
|
+
accessFiles[model] = accessInstance.access;
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
} catch (error) {
|
|
40
|
-
log.error
|
|
41
|
-
log.warn
|
|
40
|
+
log.error?.(error instanceof Error ? error.message : String(error));
|
|
41
|
+
log.warn?.('You must define a valid access configuration file in order to access ORM generated REST endpoints.');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
await waitForModule('rest-server');
|
|
@@ -47,7 +47,7 @@ export default async function(route: string, accessPath: string, metaRoute: bool
|
|
|
47
47
|
const name = route === '/' ? 'index' : (route[0] === '/' ? route.slice(1) : route);
|
|
48
48
|
|
|
49
49
|
// Configure endpoints for models and views with access configuration
|
|
50
|
-
for (const [model, access] of Object.entries(accessFiles
|
|
50
|
+
for (const [model, access] of Object.entries(accessFiles)) {
|
|
51
51
|
const pluralizedModel = getPluralName(model);
|
|
52
52
|
const modelName = name === 'index' ? pluralizedModel : `${name}/${pluralizedModel}`;
|
|
53
53
|
RestServer.instance.mountRoute(OrmRequest, { name: modelName, options: { model, access } });
|
|
@@ -55,11 +55,8 @@ export default async function(route: string, accessPath: string, metaRoute: bool
|
|
|
55
55
|
|
|
56
56
|
// Mount the meta route when metaRoute config is enabled
|
|
57
57
|
if (metaRoute) {
|
|
58
|
-
log.warn
|
|
58
|
+
log.warn?.('SECURITY RISK! - Meta route is enabled via metaRoute config. This feature is intended for development purposes only!');
|
|
59
59
|
|
|
60
60
|
RestServer.instance.mountRoute(MetaRequest, { name });
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
// Cleanup references
|
|
64
|
-
accessFiles = null;
|
|
65
62
|
}
|
package/src/store.ts
CHANGED
|
@@ -332,7 +332,8 @@ export default class Store {
|
|
|
332
332
|
}];
|
|
333
333
|
|
|
334
334
|
while (queue.length > 0) {
|
|
335
|
-
const item = queue.shift()
|
|
335
|
+
const item = queue.shift();
|
|
336
|
+
if (!item) break;
|
|
336
337
|
const key = `${item.modelName}:${item.recordId}`;
|
|
337
338
|
|
|
338
339
|
if (visited.has(key)) continue;
|
package/src/utils.ts
CHANGED
|
@@ -8,7 +8,8 @@ export function isDbError(error: unknown): error is { code: string; message: str
|
|
|
8
8
|
export function pluralize(word: string): string {
|
|
9
9
|
if (word.includes('-')) {
|
|
10
10
|
const parts = word.split('-');
|
|
11
|
-
const
|
|
11
|
+
const last = parts.pop() as string;
|
|
12
|
+
const pluralizedLast = basePluralize(last);
|
|
12
13
|
return [...parts, pluralizedLast].join('-');
|
|
13
14
|
}
|
|
14
15
|
|
package/src/view-resolver.ts
CHANGED