@spinajs/orm 2.0.180 → 2.0.182
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/lib/cjs/builders.d.ts +643 -643
- package/lib/cjs/builders.js +1602 -1602
- package/lib/cjs/builders.js.map +1 -1
- package/lib/cjs/converters.d.ts +34 -34
- package/lib/cjs/converters.js +104 -104
- package/lib/cjs/decorators.d.ts +152 -152
- package/lib/cjs/decorators.js +449 -449
- package/lib/cjs/dehydrators.d.ts +10 -10
- package/lib/cjs/dehydrators.js +47 -47
- package/lib/cjs/driver.d.ts +82 -82
- package/lib/cjs/driver.js +102 -102
- package/lib/cjs/driver.js.map +1 -1
- package/lib/cjs/enums.d.ts +116 -116
- package/lib/cjs/enums.js +126 -126
- package/lib/cjs/enums.js.map +1 -1
- package/lib/cjs/exceptions.d.ts +6 -6
- package/lib/cjs/exceptions.js +10 -10
- package/lib/cjs/hydrators.d.ts +19 -19
- package/lib/cjs/hydrators.js +132 -132
- package/lib/cjs/hydrators.js.map +1 -1
- package/lib/cjs/index.d.ts +17 -17
- package/lib/cjs/index.js +33 -33
- package/lib/cjs/interfaces.d.ts +921 -919
- package/lib/cjs/interfaces.d.ts.map +1 -1
- package/lib/cjs/interfaces.js +279 -279
- package/lib/cjs/interfaces.js.map +1 -1
- package/lib/cjs/middlewares.d.ts +62 -62
- package/lib/cjs/middlewares.js +258 -258
- package/lib/cjs/model.d.ts +288 -284
- package/lib/cjs/model.d.ts.map +1 -1
- package/lib/cjs/model.js +826 -810
- package/lib/cjs/model.js.map +1 -1
- package/lib/cjs/orm.d.ts +61 -61
- package/lib/cjs/orm.js +333 -333
- package/lib/cjs/orm.js.map +1 -1
- package/lib/cjs/relation-objects.d.ts +108 -108
- package/lib/cjs/relation-objects.js +221 -221
- package/lib/cjs/relations.d.ts +61 -61
- package/lib/cjs/relations.js +194 -194
- package/lib/cjs/relations.js.map +1 -1
- package/lib/cjs/statements.d.ts +143 -143
- package/lib/cjs/statements.js +309 -309
- package/lib/cjs/statements.js.map +1 -1
- package/lib/cjs/types.d.ts +32 -32
- package/lib/cjs/types.js +2 -2
- package/lib/cjs/wrappers.d.ts +5 -5
- package/lib/cjs/wrappers.js +12 -12
- package/lib/mjs/builders.d.ts +643 -643
- package/lib/mjs/builders.js +1594 -1594
- package/lib/mjs/builders.js.map +1 -1
- package/lib/mjs/converters.d.ts +34 -34
- package/lib/mjs/converters.js +96 -96
- package/lib/mjs/decorators.d.ts +152 -152
- package/lib/mjs/decorators.js +422 -422
- package/lib/mjs/dehydrators.d.ts +10 -10
- package/lib/mjs/dehydrators.js +41 -41
- package/lib/mjs/driver.d.ts +82 -82
- package/lib/mjs/driver.js +98 -98
- package/lib/mjs/driver.js.map +1 -1
- package/lib/mjs/enums.d.ts +116 -116
- package/lib/mjs/enums.js +123 -123
- package/lib/mjs/enums.js.map +1 -1
- package/lib/mjs/exceptions.d.ts +6 -6
- package/lib/mjs/exceptions.js +6 -6
- package/lib/mjs/hydrators.d.ts +19 -19
- package/lib/mjs/hydrators.js +128 -128
- package/lib/mjs/hydrators.js.map +1 -1
- package/lib/mjs/index.d.ts +17 -17
- package/lib/mjs/index.js +17 -17
- package/lib/mjs/interfaces.d.ts +921 -919
- package/lib/mjs/interfaces.d.ts.map +1 -1
- package/lib/mjs/interfaces.js +267 -267
- package/lib/mjs/interfaces.js.map +1 -1
- package/lib/mjs/middlewares.d.ts +62 -62
- package/lib/mjs/middlewares.js +249 -249
- package/lib/mjs/model.d.ts +288 -284
- package/lib/mjs/model.d.ts.map +1 -1
- package/lib/mjs/model.js +816 -800
- package/lib/mjs/model.js.map +1 -1
- package/lib/mjs/orm.d.ts +61 -61
- package/lib/mjs/orm.js +326 -326
- package/lib/mjs/orm.js.map +1 -1
- package/lib/mjs/relation-objects.d.ts +108 -108
- package/lib/mjs/relation-objects.js +211 -211
- package/lib/mjs/relations.d.ts +61 -61
- package/lib/mjs/relations.js +191 -191
- package/lib/mjs/relations.js.map +1 -1
- package/lib/mjs/statements.d.ts +143 -143
- package/lib/mjs/statements.js +301 -301
- package/lib/mjs/statements.js.map +1 -1
- package/lib/mjs/types.d.ts +32 -32
- package/lib/mjs/types.js +1 -1
- package/lib/mjs/wrappers.d.ts +5 -5
- package/lib/mjs/wrappers.js +9 -9
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +5 -5
package/lib/cjs/orm.js
CHANGED
|
@@ -1,334 +1,334 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.Orm = void 0;
|
|
16
|
-
const interfaces_js_1 = require("./interfaces.js");
|
|
17
|
-
const configuration_common_1 = require("@spinajs/configuration-common");
|
|
18
|
-
const di_1 = require("@spinajs/di");
|
|
19
|
-
const log_common_1 = require("@spinajs/log-common");
|
|
20
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
21
|
-
const interfaces_js_2 = require("./interfaces.js");
|
|
22
|
-
const model_js_1 = require("./model.js");
|
|
23
|
-
const decorators_js_1 = require("./decorators.js");
|
|
24
|
-
const exceptions_1 = require("@spinajs/exceptions");
|
|
25
|
-
const exceptions_js_1 = require("./exceptions.js");
|
|
26
|
-
const luxon_1 = require("luxon");
|
|
27
|
-
/**
|
|
28
|
-
* Used to exclude sensitive data to others. eg. removed password field from cfg
|
|
29
|
-
*/
|
|
30
|
-
const CFG_PROPS = ['Database', 'User', 'Host', 'Port', 'Filename', 'Driver', 'Name'];
|
|
31
|
-
const MIGRATION_TABLE_NAME = 'spinajs_migration';
|
|
32
|
-
const MIGRATION_FILE_REGEXP = /(.*)_([0-9]{4}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2})/;
|
|
33
|
-
class Orm extends di_1.AsyncService {
|
|
34
|
-
constructor() {
|
|
35
|
-
super(...arguments);
|
|
36
|
-
this.Models = [];
|
|
37
|
-
this.Migrations = [];
|
|
38
|
-
this.Connections = new Map();
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
* Migrates schema up ( fill function is not executed )
|
|
43
|
-
*
|
|
44
|
-
* @param name - migration file name
|
|
45
|
-
*/
|
|
46
|
-
async migrateUp(name, force = true) {
|
|
47
|
-
this.Log.info('DB migration UP started ...');
|
|
48
|
-
await this.executeAvaibleMigrations(name, async (migration, driver) => {
|
|
49
|
-
const trFunction = async (driver) => {
|
|
50
|
-
await migration.up(driver);
|
|
51
|
-
await driver
|
|
52
|
-
.insert()
|
|
53
|
-
.into(driver.Options.Migration?.Table ?? MIGRATION_TABLE_NAME)
|
|
54
|
-
.values({
|
|
55
|
-
Migration: migration.constructor.name,
|
|
56
|
-
CreatedAt: new Date(),
|
|
57
|
-
});
|
|
58
|
-
this.Log.info(`Migration ${migration.constructor.name}:up() success !`);
|
|
59
|
-
};
|
|
60
|
-
if (driver.Options.Migration?.Transaction?.Mode === interfaces_js_2.MigrationTransactionMode.PerMigration) {
|
|
61
|
-
await driver.transaction(trFunction);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
await trFunction(driver);
|
|
65
|
-
}
|
|
66
|
-
}, false, force);
|
|
67
|
-
this.Log.info('DB migration ended ...');
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
-
* Migrates schema up ( fill function is not executed )
|
|
72
|
-
*
|
|
73
|
-
* @param name - migration file name
|
|
74
|
-
*/
|
|
75
|
-
async migrateDown(name, force = true) {
|
|
76
|
-
this.Log.info('DB migration DOWN started ...');
|
|
77
|
-
await this.executeAvaibleMigrations(name, async (migration, driver) => {
|
|
78
|
-
const trFunction = async (driver) => {
|
|
79
|
-
await migration.down(driver);
|
|
80
|
-
await driver
|
|
81
|
-
.del()
|
|
82
|
-
.from(driver.Options.Migration?.Table ?? MIGRATION_TABLE_NAME)
|
|
83
|
-
.where({
|
|
84
|
-
Migration: migration.constructor.name,
|
|
85
|
-
});
|
|
86
|
-
this.Log.info(`Migration down ${migration.constructor.name}:DOWN success !`);
|
|
87
|
-
};
|
|
88
|
-
if (driver.Options.Migration?.Transaction?.Mode === interfaces_js_2.MigrationTransactionMode.PerMigration) {
|
|
89
|
-
await driver.transaction(trFunction);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
await trFunction(driver);
|
|
93
|
-
}
|
|
94
|
-
}, true, force);
|
|
95
|
-
this.Log.info('DB migration ended ...');
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* This function is exposed mainly for unit testing purposes. It reloads table information for models
|
|
99
|
-
* ORM always try to load table at resolve time
|
|
100
|
-
*/
|
|
101
|
-
async reloadTableInfo() {
|
|
102
|
-
for (const m of this.Models) {
|
|
103
|
-
const descriptor = (0, model_js_1.extractModelDescriptor)(m.type);
|
|
104
|
-
if (descriptor) {
|
|
105
|
-
const connection = this.Connections.get(descriptor.Connection);
|
|
106
|
-
if (connection) {
|
|
107
|
-
m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Driver = connection;
|
|
108
|
-
const columns = await connection.tableInfo(descriptor.TableName, connection.Options.Database);
|
|
109
|
-
if (columns) {
|
|
110
|
-
m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Columns = lodash_1.default.uniqBy(lodash_1.default.map(columns, (c) => {
|
|
111
|
-
return lodash_1.default.assign(c, lodash_1.default.find(descriptor.Columns, { Name: c.Name }));
|
|
112
|
-
}), 'Name');
|
|
113
|
-
// m.type[MODEL_DESCTRIPTION_SYMBOL].Schema = buildJsonSchema(columns);
|
|
114
|
-
}
|
|
115
|
-
for (const [key, val] of descriptor.Converters) {
|
|
116
|
-
const column = m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Columns.find((c) => c.Name === key);
|
|
117
|
-
if (column) {
|
|
118
|
-
column.Converter = connection.Container.hasRegistered(val.Class) ? connection.Container.resolve(val.Class) : null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
async resolve() {
|
|
126
|
-
await this.createConnections();
|
|
127
|
-
// add all registered migrations via DI
|
|
128
|
-
const migrations = di_1.DI.getRegisteredTypes('__migrations__');
|
|
129
|
-
if (migrations) {
|
|
130
|
-
migrations.forEach((m) => {
|
|
131
|
-
this.registerMigration(m);
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
const models = di_1.DI.getRegisteredTypes('__models__');
|
|
135
|
-
if (models) {
|
|
136
|
-
models.forEach((m) => {
|
|
137
|
-
this.registerModel(m);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
await this.migrateUp(undefined, false);
|
|
141
|
-
await this.reloadTableInfo();
|
|
142
|
-
this.wireRelations();
|
|
143
|
-
this.applyModelMixins();
|
|
144
|
-
this.registerDefaultConverters();
|
|
145
|
-
}
|
|
146
|
-
registerDefaultConverters() {
|
|
147
|
-
this.Container.register(interfaces_js_1.DatetimeValueConverter).asMapValue('__orm_db_value_converters__', Date.name);
|
|
148
|
-
this.Container.register(interfaces_js_1.DatetimeValueConverter).asMapValue('__orm_db_value_converters__', luxon_1.DateTime.name);
|
|
149
|
-
}
|
|
150
|
-
wireRelations() {
|
|
151
|
-
this.Models.forEach((x) => {
|
|
152
|
-
const desc = (0, model_js_1.extractModelDescriptor)(x.type);
|
|
153
|
-
if (!desc)
|
|
154
|
-
return;
|
|
155
|
-
desc.Relations.forEach((rel) => {
|
|
156
|
-
const found = this.Models.find((y) => {
|
|
157
|
-
const type = lodash_1.default.isString(rel.TargetModelType) ? rel.TargetModelType : rel.TargetModelType.name;
|
|
158
|
-
return y.name === type;
|
|
159
|
-
});
|
|
160
|
-
if (!found) {
|
|
161
|
-
throw new exceptions_js_1.OrmException(`type ${rel.TargetModelType} not found for relation ${rel.Name} in model ${x.name} in file ${x.file}`);
|
|
162
|
-
}
|
|
163
|
-
rel.TargetModel = found.type;
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
*
|
|
169
|
-
* Register model to ORM programatically so ORM can see it and use it. Sometimes dynamical model discovery is not possible eg.
|
|
170
|
-
* in webpack evnironment. In such case we must tell ORM manually what to load.
|
|
171
|
-
*
|
|
172
|
-
* NOTE: use it in ORM constructor before ORM is resolved & model list used.
|
|
173
|
-
*
|
|
174
|
-
* @param model - model to register
|
|
175
|
-
*/
|
|
176
|
-
registerModel(model) {
|
|
177
|
-
this.Models.push({
|
|
178
|
-
file: `${model.name}.registered`,
|
|
179
|
-
name: model.name,
|
|
180
|
-
type: model,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
*
|
|
185
|
-
* Register migration to ORM programatically so ORM can see it and use it. Sometimes dynamical migration discovery is not possible eg.
|
|
186
|
-
* in webpack evnironment. In such case we must tell ORM manually what to load.
|
|
187
|
-
*
|
|
188
|
-
* NOTE: use it in ORM constructor before ORM is resolved & migrate function used.
|
|
189
|
-
*
|
|
190
|
-
* @param model - model to register
|
|
191
|
-
*/
|
|
192
|
-
registerMigration(migration) {
|
|
193
|
-
const created = this.getMigrationDate(migration);
|
|
194
|
-
if (created === null) {
|
|
195
|
-
throw new exceptions_js_1.OrmException(`Migration file ${migration.name} have invalid name format ( invalid migration name, expected: some_name_yyyy_MM_dd_HH_mm_ss got ${migration.name})`);
|
|
196
|
-
}
|
|
197
|
-
this.Migrations.push({
|
|
198
|
-
file: `${migration.name}.registered`,
|
|
199
|
-
name: `${migration.name}`,
|
|
200
|
-
type: migration,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
async createConnections() {
|
|
204
|
-
const cConnections = this.Configuration.get('db.Connections', []);
|
|
205
|
-
for (const c of cConnections) {
|
|
206
|
-
this.Log.trace(`Trying to create connection name: ${c.Name}, driver: ${c.Driver}`);
|
|
207
|
-
if (!this.Container.hasRegistered(c.Driver)) {
|
|
208
|
-
throw new exceptions_js_1.OrmException(`ORM connection driver ${c.Driver} not registerd`);
|
|
209
|
-
}
|
|
210
|
-
const driver = await this.Container.resolve(c.Driver, [c]);
|
|
211
|
-
await driver.connect();
|
|
212
|
-
this.Connections.set(c.Name, driver);
|
|
213
|
-
this.Log.success(`Created ORM connection ${c.Name} with parametes ${JSON.stringify(lodash_1.default.pick(c, CFG_PROPS))}`);
|
|
214
|
-
}
|
|
215
|
-
const defaultConnection = this.Configuration.get('db.DefaultConnection');
|
|
216
|
-
if (defaultConnection) {
|
|
217
|
-
if (!this.Connections.has(defaultConnection)) {
|
|
218
|
-
throw new exceptions_1.InvalidOperation(`default connection ${defaultConnection} not exists`);
|
|
219
|
-
}
|
|
220
|
-
this.Connections.set('default', this.Connections.get(defaultConnection));
|
|
221
|
-
}
|
|
222
|
-
// wire connection aliases
|
|
223
|
-
// for example if we have module that uses conn name of db-user-session
|
|
224
|
-
// and we want to wire it to some existinc connection instead creating new one
|
|
225
|
-
const aliases = this.Configuration.get('db.Aliases', {});
|
|
226
|
-
for (const a in aliases) {
|
|
227
|
-
const conn = aliases[a];
|
|
228
|
-
if (!this.Connections.has(conn)) {
|
|
229
|
-
throw new exceptions_1.InvalidOperation(`default connection ${conn} not exists`);
|
|
230
|
-
}
|
|
231
|
-
this.Connections.set(a, this.Connections.get(conn));
|
|
232
|
-
}
|
|
233
|
-
// register in continaer factory func for retrieving db connections
|
|
234
|
-
// it will allow for easy access to it in modules
|
|
235
|
-
di_1.DI.register((_container, connectionName) => {
|
|
236
|
-
if (this.Connections.has(connectionName)) {
|
|
237
|
-
return this.Connections.get(connectionName);
|
|
238
|
-
}
|
|
239
|
-
return null;
|
|
240
|
-
}).as("OrmConnection");
|
|
241
|
-
}
|
|
242
|
-
applyModelMixins() {
|
|
243
|
-
this.Models.forEach((m) => {
|
|
244
|
-
// tslint:disable-next-line: forin
|
|
245
|
-
for (const mixin in model_js_1.MODEL_STATIC_MIXINS) {
|
|
246
|
-
m.type[mixin] = model_js_1.MODEL_STATIC_MIXINS[mixin].bind(m.type);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
getMigrationDate(migration) {
|
|
251
|
-
const match = migration.name.match(MIGRATION_FILE_REGEXP);
|
|
252
|
-
if (match === null || match.length !== 3) {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
const created = luxon_1.DateTime.fromFormat(match[2], 'yyyy_MM_dd_HH_mm_ss');
|
|
256
|
-
if (!created.isValid) {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
return created;
|
|
260
|
-
}
|
|
261
|
-
async executeAvaibleMigrations(name, callback, down, force) {
|
|
262
|
-
const toMigrate = name ? this.Migrations.filter((m) => m.name === name) : this.Migrations;
|
|
263
|
-
let migrations = toMigrate
|
|
264
|
-
.map((x) => {
|
|
265
|
-
const created = this.getMigrationDate(x.type);
|
|
266
|
-
if (created === null) {
|
|
267
|
-
throw new exceptions_js_1.OrmException(`Migration file ${x.name} have invalid name format ( invalid migration name, expected: some_name_yyyy_MM_dd_HH_mm_ss got ${x.name})`);
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
created,
|
|
271
|
-
...x,
|
|
272
|
-
};
|
|
273
|
-
})
|
|
274
|
-
.filter((x) => x !== null)
|
|
275
|
-
.sort((a, b) => {
|
|
276
|
-
if (a.created < b.created) {
|
|
277
|
-
return -1;
|
|
278
|
-
}
|
|
279
|
-
return 1;
|
|
280
|
-
});
|
|
281
|
-
if (down) {
|
|
282
|
-
migrations = migrations.reverse();
|
|
283
|
-
}
|
|
284
|
-
for (const m of migrations) {
|
|
285
|
-
const md = m.type[decorators_js_1.MIGRATION_DESCRIPTION_SYMBOL];
|
|
286
|
-
const cn = this.Connections.get(md.Connection);
|
|
287
|
-
if (!cn) {
|
|
288
|
-
this.Log.warn(`Connection ${md.Connection} not exists for migration ${m.name} at file ${m.file}`);
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
const migrationTableName = cn.Options.Migration?.Table ?? MIGRATION_TABLE_NAME;
|
|
292
|
-
if (!cn.Options.Migration?.OnStartup) {
|
|
293
|
-
if (!force) {
|
|
294
|
-
this.Log.warn(`Migration for connection ${md.Connection} is disabled on startup, please check conf file for db.[connection].migration.OnStartup property`);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
// if there is no info on migraiton table
|
|
299
|
-
const migrationTableExists = await cn.schema().tableExists(migrationTableName, cn.Options.Database);
|
|
300
|
-
if (!migrationTableExists) {
|
|
301
|
-
this.Log.info(`No migration table in database, recreating migration information ...`);
|
|
302
|
-
await cn.schema().createTable(migrationTableName, (table) => {
|
|
303
|
-
table.string('Migration').unique().notNull();
|
|
304
|
-
table.dateTime('CreatedAt').notNull();
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
const exists = await cn.select().from(migrationTableName).where({ Migration: m.name }).orderByDescending('CreatedAt').first();
|
|
308
|
-
if (!exists) {
|
|
309
|
-
const migration = await this.Container.resolve(m.type, [cn]);
|
|
310
|
-
this.Log.info(`Setting up migration ${m.name} from file ${m.file} created at ${m.created} mode: ${down ? 'migrate down' : 'migrate up'}`);
|
|
311
|
-
await callback(migration, cn);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
async dispose() {
|
|
316
|
-
for (const [, value] of this.Connections) {
|
|
317
|
-
await value.disconnect();
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Orm = void 0;
|
|
16
|
+
const interfaces_js_1 = require("./interfaces.js");
|
|
17
|
+
const configuration_common_1 = require("@spinajs/configuration-common");
|
|
18
|
+
const di_1 = require("@spinajs/di");
|
|
19
|
+
const log_common_1 = require("@spinajs/log-common");
|
|
20
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
21
|
+
const interfaces_js_2 = require("./interfaces.js");
|
|
22
|
+
const model_js_1 = require("./model.js");
|
|
23
|
+
const decorators_js_1 = require("./decorators.js");
|
|
24
|
+
const exceptions_1 = require("@spinajs/exceptions");
|
|
25
|
+
const exceptions_js_1 = require("./exceptions.js");
|
|
26
|
+
const luxon_1 = require("luxon");
|
|
27
|
+
/**
|
|
28
|
+
* Used to exclude sensitive data to others. eg. removed password field from cfg
|
|
29
|
+
*/
|
|
30
|
+
const CFG_PROPS = ['Database', 'User', 'Host', 'Port', 'Filename', 'Driver', 'Name'];
|
|
31
|
+
const MIGRATION_TABLE_NAME = 'spinajs_migration';
|
|
32
|
+
const MIGRATION_FILE_REGEXP = /(.*)_([0-9]{4}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2}_[0-9]{2})/;
|
|
33
|
+
class Orm extends di_1.AsyncService {
|
|
34
|
+
constructor() {
|
|
35
|
+
super(...arguments);
|
|
36
|
+
this.Models = [];
|
|
37
|
+
this.Migrations = [];
|
|
38
|
+
this.Connections = new Map();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* Migrates schema up ( fill function is not executed )
|
|
43
|
+
*
|
|
44
|
+
* @param name - migration file name
|
|
45
|
+
*/
|
|
46
|
+
async migrateUp(name, force = true) {
|
|
47
|
+
this.Log.info('DB migration UP started ...');
|
|
48
|
+
await this.executeAvaibleMigrations(name, async (migration, driver) => {
|
|
49
|
+
const trFunction = async (driver) => {
|
|
50
|
+
await migration.up(driver);
|
|
51
|
+
await driver
|
|
52
|
+
.insert()
|
|
53
|
+
.into(driver.Options.Migration?.Table ?? MIGRATION_TABLE_NAME)
|
|
54
|
+
.values({
|
|
55
|
+
Migration: migration.constructor.name,
|
|
56
|
+
CreatedAt: new Date(),
|
|
57
|
+
});
|
|
58
|
+
this.Log.info(`Migration ${migration.constructor.name}:up() success !`);
|
|
59
|
+
};
|
|
60
|
+
if (driver.Options.Migration?.Transaction?.Mode === interfaces_js_2.MigrationTransactionMode.PerMigration) {
|
|
61
|
+
await driver.transaction(trFunction);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await trFunction(driver);
|
|
65
|
+
}
|
|
66
|
+
}, false, force);
|
|
67
|
+
this.Log.info('DB migration ended ...');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* Migrates schema up ( fill function is not executed )
|
|
72
|
+
*
|
|
73
|
+
* @param name - migration file name
|
|
74
|
+
*/
|
|
75
|
+
async migrateDown(name, force = true) {
|
|
76
|
+
this.Log.info('DB migration DOWN started ...');
|
|
77
|
+
await this.executeAvaibleMigrations(name, async (migration, driver) => {
|
|
78
|
+
const trFunction = async (driver) => {
|
|
79
|
+
await migration.down(driver);
|
|
80
|
+
await driver
|
|
81
|
+
.del()
|
|
82
|
+
.from(driver.Options.Migration?.Table ?? MIGRATION_TABLE_NAME)
|
|
83
|
+
.where({
|
|
84
|
+
Migration: migration.constructor.name,
|
|
85
|
+
});
|
|
86
|
+
this.Log.info(`Migration down ${migration.constructor.name}:DOWN success !`);
|
|
87
|
+
};
|
|
88
|
+
if (driver.Options.Migration?.Transaction?.Mode === interfaces_js_2.MigrationTransactionMode.PerMigration) {
|
|
89
|
+
await driver.transaction(trFunction);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
await trFunction(driver);
|
|
93
|
+
}
|
|
94
|
+
}, true, force);
|
|
95
|
+
this.Log.info('DB migration ended ...');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* This function is exposed mainly for unit testing purposes. It reloads table information for models
|
|
99
|
+
* ORM always try to load table at resolve time
|
|
100
|
+
*/
|
|
101
|
+
async reloadTableInfo() {
|
|
102
|
+
for (const m of this.Models) {
|
|
103
|
+
const descriptor = (0, model_js_1.extractModelDescriptor)(m.type);
|
|
104
|
+
if (descriptor) {
|
|
105
|
+
const connection = this.Connections.get(descriptor.Connection);
|
|
106
|
+
if (connection) {
|
|
107
|
+
m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Driver = connection;
|
|
108
|
+
const columns = await connection.tableInfo(descriptor.TableName, connection.Options.Database);
|
|
109
|
+
if (columns) {
|
|
110
|
+
m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Columns = lodash_1.default.uniqBy(lodash_1.default.map(columns, (c) => {
|
|
111
|
+
return lodash_1.default.assign(c, lodash_1.default.find(descriptor.Columns, { Name: c.Name }));
|
|
112
|
+
}), 'Name');
|
|
113
|
+
// m.type[MODEL_DESCTRIPTION_SYMBOL].Schema = buildJsonSchema(columns);
|
|
114
|
+
}
|
|
115
|
+
for (const [key, val] of descriptor.Converters) {
|
|
116
|
+
const column = m.type[decorators_js_1.MODEL_DESCTRIPTION_SYMBOL].Columns.find((c) => c.Name === key);
|
|
117
|
+
if (column) {
|
|
118
|
+
column.Converter = connection.Container.hasRegistered(val.Class) ? connection.Container.resolve(val.Class) : null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async resolve() {
|
|
126
|
+
await this.createConnections();
|
|
127
|
+
// add all registered migrations via DI
|
|
128
|
+
const migrations = di_1.DI.getRegisteredTypes('__migrations__');
|
|
129
|
+
if (migrations) {
|
|
130
|
+
migrations.forEach((m) => {
|
|
131
|
+
this.registerMigration(m);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const models = di_1.DI.getRegisteredTypes('__models__');
|
|
135
|
+
if (models) {
|
|
136
|
+
models.forEach((m) => {
|
|
137
|
+
this.registerModel(m);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
await this.migrateUp(undefined, false);
|
|
141
|
+
await this.reloadTableInfo();
|
|
142
|
+
this.wireRelations();
|
|
143
|
+
this.applyModelMixins();
|
|
144
|
+
this.registerDefaultConverters();
|
|
145
|
+
}
|
|
146
|
+
registerDefaultConverters() {
|
|
147
|
+
this.Container.register(interfaces_js_1.DatetimeValueConverter).asMapValue('__orm_db_value_converters__', Date.name);
|
|
148
|
+
this.Container.register(interfaces_js_1.DatetimeValueConverter).asMapValue('__orm_db_value_converters__', luxon_1.DateTime.name);
|
|
149
|
+
}
|
|
150
|
+
wireRelations() {
|
|
151
|
+
this.Models.forEach((x) => {
|
|
152
|
+
const desc = (0, model_js_1.extractModelDescriptor)(x.type);
|
|
153
|
+
if (!desc)
|
|
154
|
+
return;
|
|
155
|
+
desc.Relations.forEach((rel) => {
|
|
156
|
+
const found = this.Models.find((y) => {
|
|
157
|
+
const type = lodash_1.default.isString(rel.TargetModelType) ? rel.TargetModelType : rel.TargetModelType.name;
|
|
158
|
+
return y.name === type;
|
|
159
|
+
});
|
|
160
|
+
if (!found) {
|
|
161
|
+
throw new exceptions_js_1.OrmException(`type ${rel.TargetModelType} not found for relation ${rel.Name} in model ${x.name} in file ${x.file}`);
|
|
162
|
+
}
|
|
163
|
+
rel.TargetModel = found.type;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
* Register model to ORM programatically so ORM can see it and use it. Sometimes dynamical model discovery is not possible eg.
|
|
170
|
+
* in webpack evnironment. In such case we must tell ORM manually what to load.
|
|
171
|
+
*
|
|
172
|
+
* NOTE: use it in ORM constructor before ORM is resolved & model list used.
|
|
173
|
+
*
|
|
174
|
+
* @param model - model to register
|
|
175
|
+
*/
|
|
176
|
+
registerModel(model) {
|
|
177
|
+
this.Models.push({
|
|
178
|
+
file: `${model.name}.registered`,
|
|
179
|
+
name: model.name,
|
|
180
|
+
type: model,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
*
|
|
185
|
+
* Register migration to ORM programatically so ORM can see it and use it. Sometimes dynamical migration discovery is not possible eg.
|
|
186
|
+
* in webpack evnironment. In such case we must tell ORM manually what to load.
|
|
187
|
+
*
|
|
188
|
+
* NOTE: use it in ORM constructor before ORM is resolved & migrate function used.
|
|
189
|
+
*
|
|
190
|
+
* @param model - model to register
|
|
191
|
+
*/
|
|
192
|
+
registerMigration(migration) {
|
|
193
|
+
const created = this.getMigrationDate(migration);
|
|
194
|
+
if (created === null) {
|
|
195
|
+
throw new exceptions_js_1.OrmException(`Migration file ${migration.name} have invalid name format ( invalid migration name, expected: some_name_yyyy_MM_dd_HH_mm_ss got ${migration.name})`);
|
|
196
|
+
}
|
|
197
|
+
this.Migrations.push({
|
|
198
|
+
file: `${migration.name}.registered`,
|
|
199
|
+
name: `${migration.name}`,
|
|
200
|
+
type: migration,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async createConnections() {
|
|
204
|
+
const cConnections = this.Configuration.get('db.Connections', []);
|
|
205
|
+
for (const c of cConnections) {
|
|
206
|
+
this.Log.trace(`Trying to create connection name: ${c.Name}, driver: ${c.Driver}`);
|
|
207
|
+
if (!this.Container.hasRegistered(c.Driver)) {
|
|
208
|
+
throw new exceptions_js_1.OrmException(`ORM connection driver ${c.Driver} not registerd`);
|
|
209
|
+
}
|
|
210
|
+
const driver = await this.Container.resolve(c.Driver, [c]);
|
|
211
|
+
await driver.connect();
|
|
212
|
+
this.Connections.set(c.Name, driver);
|
|
213
|
+
this.Log.success(`Created ORM connection ${c.Name} with parametes ${JSON.stringify(lodash_1.default.pick(c, CFG_PROPS))}`);
|
|
214
|
+
}
|
|
215
|
+
const defaultConnection = this.Configuration.get('db.DefaultConnection');
|
|
216
|
+
if (defaultConnection) {
|
|
217
|
+
if (!this.Connections.has(defaultConnection)) {
|
|
218
|
+
throw new exceptions_1.InvalidOperation(`default connection ${defaultConnection} not exists`);
|
|
219
|
+
}
|
|
220
|
+
this.Connections.set('default', this.Connections.get(defaultConnection));
|
|
221
|
+
}
|
|
222
|
+
// wire connection aliases
|
|
223
|
+
// for example if we have module that uses conn name of db-user-session
|
|
224
|
+
// and we want to wire it to some existinc connection instead creating new one
|
|
225
|
+
const aliases = this.Configuration.get('db.Aliases', {});
|
|
226
|
+
for (const a in aliases) {
|
|
227
|
+
const conn = aliases[a];
|
|
228
|
+
if (!this.Connections.has(conn)) {
|
|
229
|
+
throw new exceptions_1.InvalidOperation(`default connection ${conn} not exists`);
|
|
230
|
+
}
|
|
231
|
+
this.Connections.set(a, this.Connections.get(conn));
|
|
232
|
+
}
|
|
233
|
+
// register in continaer factory func for retrieving db connections
|
|
234
|
+
// it will allow for easy access to it in modules
|
|
235
|
+
di_1.DI.register((_container, connectionName) => {
|
|
236
|
+
if (this.Connections.has(connectionName)) {
|
|
237
|
+
return this.Connections.get(connectionName);
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}).as("OrmConnection");
|
|
241
|
+
}
|
|
242
|
+
applyModelMixins() {
|
|
243
|
+
this.Models.forEach((m) => {
|
|
244
|
+
// tslint:disable-next-line: forin
|
|
245
|
+
for (const mixin in model_js_1.MODEL_STATIC_MIXINS) {
|
|
246
|
+
m.type[mixin] = model_js_1.MODEL_STATIC_MIXINS[mixin].bind(m.type);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
getMigrationDate(migration) {
|
|
251
|
+
const match = migration.name.match(MIGRATION_FILE_REGEXP);
|
|
252
|
+
if (match === null || match.length !== 3) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const created = luxon_1.DateTime.fromFormat(match[2], 'yyyy_MM_dd_HH_mm_ss');
|
|
256
|
+
if (!created.isValid) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
return created;
|
|
260
|
+
}
|
|
261
|
+
async executeAvaibleMigrations(name, callback, down, force) {
|
|
262
|
+
const toMigrate = name ? this.Migrations.filter((m) => m.name === name) : this.Migrations;
|
|
263
|
+
let migrations = toMigrate
|
|
264
|
+
.map((x) => {
|
|
265
|
+
const created = this.getMigrationDate(x.type);
|
|
266
|
+
if (created === null) {
|
|
267
|
+
throw new exceptions_js_1.OrmException(`Migration file ${x.name} have invalid name format ( invalid migration name, expected: some_name_yyyy_MM_dd_HH_mm_ss got ${x.name})`);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
created,
|
|
271
|
+
...x,
|
|
272
|
+
};
|
|
273
|
+
})
|
|
274
|
+
.filter((x) => x !== null)
|
|
275
|
+
.sort((a, b) => {
|
|
276
|
+
if (a.created < b.created) {
|
|
277
|
+
return -1;
|
|
278
|
+
}
|
|
279
|
+
return 1;
|
|
280
|
+
});
|
|
281
|
+
if (down) {
|
|
282
|
+
migrations = migrations.reverse();
|
|
283
|
+
}
|
|
284
|
+
for (const m of migrations) {
|
|
285
|
+
const md = m.type[decorators_js_1.MIGRATION_DESCRIPTION_SYMBOL];
|
|
286
|
+
const cn = this.Connections.get(md.Connection);
|
|
287
|
+
if (!cn) {
|
|
288
|
+
this.Log.warn(`Connection ${md.Connection} not exists for migration ${m.name} at file ${m.file}`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const migrationTableName = cn.Options.Migration?.Table ?? MIGRATION_TABLE_NAME;
|
|
292
|
+
if (!cn.Options.Migration?.OnStartup) {
|
|
293
|
+
if (!force) {
|
|
294
|
+
this.Log.warn(`Migration for connection ${md.Connection} is disabled on startup, please check conf file for db.[connection].migration.OnStartup property`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// if there is no info on migraiton table
|
|
299
|
+
const migrationTableExists = await cn.schema().tableExists(migrationTableName, cn.Options.Database);
|
|
300
|
+
if (!migrationTableExists) {
|
|
301
|
+
this.Log.info(`No migration table in database, recreating migration information ...`);
|
|
302
|
+
await cn.schema().createTable(migrationTableName, (table) => {
|
|
303
|
+
table.string('Migration').unique().notNull();
|
|
304
|
+
table.dateTime('CreatedAt').notNull();
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const exists = await cn.select().from(migrationTableName).where({ Migration: m.name }).orderByDescending('CreatedAt').first();
|
|
308
|
+
if (!exists) {
|
|
309
|
+
const migration = await this.Container.resolve(m.type, [cn]);
|
|
310
|
+
this.Log.info(`Setting up migration ${m.name} from file ${m.file} created at ${m.created} mode: ${down ? 'migrate down' : 'migrate up'}`);
|
|
311
|
+
await callback(migration, cn);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async dispose() {
|
|
316
|
+
for (const [, value] of this.Connections) {
|
|
317
|
+
await value.disconnect();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.Orm = Orm;
|
|
322
|
+
__decorate([
|
|
323
|
+
(0, di_1.Autoinject)(),
|
|
324
|
+
__metadata("design:type", di_1.Container)
|
|
325
|
+
], Orm.prototype, "Container", void 0);
|
|
326
|
+
__decorate([
|
|
327
|
+
(0, log_common_1.Logger)('ORM'),
|
|
328
|
+
__metadata("design:type", log_common_1.Log)
|
|
329
|
+
], Orm.prototype, "Log", void 0);
|
|
330
|
+
__decorate([
|
|
331
|
+
(0, di_1.Autoinject)(),
|
|
332
|
+
__metadata("design:type", configuration_common_1.Configuration)
|
|
333
|
+
], Orm.prototype, "Configuration", void 0);
|
|
334
334
|
//# sourceMappingURL=orm.js.map
|