@spinajs/orm 2.0.180 → 2.0.181

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 (91) hide show
  1. package/lib/cjs/builders.d.ts +643 -643
  2. package/lib/cjs/builders.js +1602 -1602
  3. package/lib/cjs/builders.js.map +1 -1
  4. package/lib/cjs/converters.d.ts +34 -34
  5. package/lib/cjs/converters.js +104 -104
  6. package/lib/cjs/decorators.d.ts +152 -152
  7. package/lib/cjs/decorators.js +449 -449
  8. package/lib/cjs/dehydrators.d.ts +10 -10
  9. package/lib/cjs/dehydrators.js +47 -47
  10. package/lib/cjs/driver.d.ts +82 -82
  11. package/lib/cjs/driver.js +102 -102
  12. package/lib/cjs/driver.js.map +1 -1
  13. package/lib/cjs/enums.d.ts +116 -116
  14. package/lib/cjs/enums.js +126 -126
  15. package/lib/cjs/enums.js.map +1 -1
  16. package/lib/cjs/exceptions.d.ts +6 -6
  17. package/lib/cjs/exceptions.js +10 -10
  18. package/lib/cjs/hydrators.d.ts +19 -19
  19. package/lib/cjs/hydrators.js +132 -132
  20. package/lib/cjs/hydrators.js.map +1 -1
  21. package/lib/cjs/index.d.ts +17 -17
  22. package/lib/cjs/index.js +33 -33
  23. package/lib/cjs/interfaces.d.ts +919 -919
  24. package/lib/cjs/interfaces.js +279 -279
  25. package/lib/cjs/interfaces.js.map +1 -1
  26. package/lib/cjs/middlewares.d.ts +62 -62
  27. package/lib/cjs/middlewares.js +258 -258
  28. package/lib/cjs/model.d.ts +284 -284
  29. package/lib/cjs/model.js +810 -810
  30. package/lib/cjs/orm.d.ts +61 -61
  31. package/lib/cjs/orm.js +333 -333
  32. package/lib/cjs/orm.js.map +1 -1
  33. package/lib/cjs/relation-objects.d.ts +108 -108
  34. package/lib/cjs/relation-objects.js +221 -221
  35. package/lib/cjs/relations.d.ts +61 -61
  36. package/lib/cjs/relations.js +194 -194
  37. package/lib/cjs/relations.js.map +1 -1
  38. package/lib/cjs/statements.d.ts +143 -143
  39. package/lib/cjs/statements.js +309 -309
  40. package/lib/cjs/statements.js.map +1 -1
  41. package/lib/cjs/types.d.ts +32 -32
  42. package/lib/cjs/types.js +2 -2
  43. package/lib/cjs/wrappers.d.ts +5 -5
  44. package/lib/cjs/wrappers.js +12 -12
  45. package/lib/mjs/builders.d.ts +643 -643
  46. package/lib/mjs/builders.js +1594 -1594
  47. package/lib/mjs/builders.js.map +1 -1
  48. package/lib/mjs/converters.d.ts +34 -34
  49. package/lib/mjs/converters.js +96 -96
  50. package/lib/mjs/decorators.d.ts +152 -152
  51. package/lib/mjs/decorators.js +422 -422
  52. package/lib/mjs/dehydrators.d.ts +10 -10
  53. package/lib/mjs/dehydrators.js +41 -41
  54. package/lib/mjs/driver.d.ts +82 -82
  55. package/lib/mjs/driver.js +98 -98
  56. package/lib/mjs/driver.js.map +1 -1
  57. package/lib/mjs/enums.d.ts +116 -116
  58. package/lib/mjs/enums.js +123 -123
  59. package/lib/mjs/enums.js.map +1 -1
  60. package/lib/mjs/exceptions.d.ts +6 -6
  61. package/lib/mjs/exceptions.js +6 -6
  62. package/lib/mjs/hydrators.d.ts +19 -19
  63. package/lib/mjs/hydrators.js +128 -128
  64. package/lib/mjs/hydrators.js.map +1 -1
  65. package/lib/mjs/index.d.ts +17 -17
  66. package/lib/mjs/index.js +17 -17
  67. package/lib/mjs/interfaces.d.ts +919 -919
  68. package/lib/mjs/interfaces.js +267 -267
  69. package/lib/mjs/interfaces.js.map +1 -1
  70. package/lib/mjs/middlewares.d.ts +62 -62
  71. package/lib/mjs/middlewares.js +249 -249
  72. package/lib/mjs/model.d.ts +284 -284
  73. package/lib/mjs/model.js +800 -800
  74. package/lib/mjs/orm.d.ts +61 -61
  75. package/lib/mjs/orm.js +326 -326
  76. package/lib/mjs/orm.js.map +1 -1
  77. package/lib/mjs/relation-objects.d.ts +108 -108
  78. package/lib/mjs/relation-objects.js +211 -211
  79. package/lib/mjs/relations.d.ts +61 -61
  80. package/lib/mjs/relations.js +191 -191
  81. package/lib/mjs/relations.js.map +1 -1
  82. package/lib/mjs/statements.d.ts +143 -143
  83. package/lib/mjs/statements.js +301 -301
  84. package/lib/mjs/statements.js.map +1 -1
  85. package/lib/mjs/types.d.ts +32 -32
  86. package/lib/mjs/types.js +1 -1
  87. package/lib/mjs/wrappers.d.ts +5 -5
  88. package/lib/mjs/wrappers.js +9 -9
  89. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  90. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  91. package/package.json +5 -5
package/lib/mjs/model.js CHANGED
@@ -1,801 +1,801 @@
1
- /* eslint-disable prettier/prettier */
2
- import { SortOrder } from './enums.js';
3
- import { MODEL_DESCTRIPTION_SYMBOL } from './decorators.js';
4
- import { RelationType, InsertBehaviour, ModelToSqlConverter, ObjectToSqlConverter } from './interfaces.js';
5
- import { UpdateQueryBuilder, TruncateTableQueryBuilder, SelectQueryBuilder, DeleteQueryBuilder, InsertQueryBuilder } from './builders.js';
6
- import { DI, isConstructor } from '@spinajs/di';
7
- import { Orm } from './orm.js';
8
- import { ModelHydrator } from './hydrators.js';
9
- import _ from 'lodash';
10
- import { v4 as uuidv4 } from 'uuid';
11
- import { OrmException } from './exceptions.js';
12
- import { StandardModelDehydrator, StandardModelWithRelationsDehydrator } from './dehydrators.js';
13
- import { DateTime } from 'luxon';
14
- import { ManyToManyRelationList, OneToManyRelationList, SingleRelation } from './relation-objects.js';
15
- import { DiscriminationMapMiddleware } from './middlewares.js';
16
- export function extractModelDescriptor(targetOrForward) {
17
- const target = !isConstructor(targetOrForward) && targetOrForward ? targetOrForward() : targetOrForward;
18
- if (!target) {
19
- return null;
20
- }
21
- let descriptor = null;
22
- _reduce(target);
23
- return descriptor;
24
- function _reduce(t) {
25
- if (!t) {
26
- return;
27
- }
28
- if (t[MODEL_DESCTRIPTION_SYMBOL]) {
29
- descriptor = descriptor ?? {};
30
- _.mergeWith(descriptor, t[MODEL_DESCTRIPTION_SYMBOL], (a, b) => {
31
- if (!a) {
32
- return b;
33
- }
34
- if (Array.isArray(a)) {
35
- return a.concat(b);
36
- }
37
- return a;
38
- });
39
- }
40
- _reduce(t.prototype);
41
- _reduce(t.__proto__);
42
- }
43
- }
44
- export class ModelBase {
45
- /**
46
- * Gets descriptor for this model. It contains information about relations, orm driver, connection properties,
47
- * db table attached, column information and others.
48
- */
49
- get ModelDescriptor() {
50
- return extractModelDescriptor(this.constructor);
51
- }
52
- /**
53
- * Gets di container associated with this model ( via connection object eg. different drivers have their own implementation of things)
54
- */
55
- get Container() {
56
- if (!this._container) {
57
- const orm = DI.get(Orm);
58
- const driver = orm.Connections.get(this.ModelDescriptor.Connection);
59
- if (!driver) {
60
- throw new Error(`model ${this.constructor.name} have invalid connection ${this.ModelDescriptor.Connection}, please check your db config file or model connection name`);
61
- }
62
- this._container = driver.Container;
63
- }
64
- return this._container;
65
- }
66
- get PrimaryKeyName() {
67
- return this.ModelDescriptor.PrimaryKey;
68
- }
69
- get PrimaryKeyValue() {
70
- return this[this.PrimaryKeyName];
71
- }
72
- set PrimaryKeyValue(newVal) {
73
- this[this.PrimaryKeyName] = newVal;
74
- this.ModelDescriptor.Relations.forEach((r) => {
75
- const rel = this[r.Name];
76
- if (!rel)
77
- return;
78
- switch (r.Type) {
79
- case RelationType.One:
80
- rel[r.ForeignKey] = newVal;
81
- break;
82
- case RelationType.Many:
83
- rel.forEach((rVal) => (rVal[r.ForeignKey] = newVal));
84
- break;
85
- case RelationType.ManyToMany:
86
- // TODO: rethink this
87
- break;
88
- }
89
- });
90
- }
91
- valueOf() {
92
- return this.PrimaryKeyValue;
93
- }
94
- driver() {
95
- const orm = DI.get(Orm);
96
- const driver = orm.Connections.get(this.ModelDescriptor.Connection);
97
- return driver;
98
- }
99
- /**
100
- * Recursivelly takes all relation data and returns as single array
101
- */
102
- getFlattenRelationModels(recursive) {
103
- const reduceRelations = function (m) {
104
- const relations = [...m.ModelDescriptor.Relations.values()];
105
- const models = _.flatMap(relations, (r) => {
106
- if (r.Type === RelationType.Many || r.Type === RelationType.ManyToMany) {
107
- return m[r.Name];
108
- }
109
- if (m[r.Name].Value) {
110
- return [m[r.Name].Value];
111
- }
112
- }).filter((x) => x !== undefined);
113
- if (recursive) {
114
- return [...models, ..._.flatMap(models, reduceRelations)];
115
- }
116
- return models;
117
- };
118
- return reduceRelations(this);
119
- }
120
- static getModelDescriptor() {
121
- throw new Error('Not implemented');
122
- }
123
- static getRelationDescriptor(_relation) {
124
- throw new Error('Not implemented');
125
- }
126
- /**
127
- * Clears all data in table
128
- */
129
- static truncate() {
130
- throw new Error('Not implemented');
131
- }
132
- /**
133
- * Get all data from db
134
- */
135
- static all(_page, _perPage) {
136
- throw new Error('Not implemented');
137
- }
138
- /**
139
- * Inserts data to DB.
140
- *
141
- * @param _data - data to insert
142
- */
143
- static insert(_data, _insertBehaviour = InsertBehaviour.None) {
144
- throw new Error('Not implemented');
145
- }
146
- static where(_column, _operator, _value) {
147
- throw new Error('Not implemented');
148
- }
149
- /**
150
- * Updates single or multiple records at once with provided value based on condition
151
- *
152
- * @param _data - data to set
153
- */
154
- static update(_data) {
155
- throw new Error('Not implemented');
156
- }
157
- /**
158
- * Tries to find all models with given primary keys
159
- */
160
- static find(_pks) {
161
- throw new Error('Not implemented');
162
- }
163
- static first() {
164
- throw new Error('Not implemented');
165
- }
166
- static last() {
167
- throw new Error('Not implemented');
168
- }
169
- static newest() {
170
- throw new Error('Not implemented');
171
- }
172
- static oldest() {
173
- throw new Error('Not implemented');
174
- }
175
- static count() {
176
- throw new Error('Not implemented');
177
- }
178
- /**
179
- * Tries to find all models in db. If not all exists, throws exception
180
- */
181
- static findOrFail(_pks) {
182
- throw new Error('Not implemented');
183
- }
184
- /**
185
- * gets model by specified pk, if not exists, returns null
186
- *
187
- */
188
- static get(_pk) {
189
- throw new Error('Not implemented');
190
- }
191
- /**
192
- * Finds model by specified pk. If model not exists in db throws exception
193
- *
194
- */
195
- static getOrFail(_pk) {
196
- throw new Error('Not implemented');
197
- }
198
- /**
199
- *
200
- * Checks if model with pk key or unique fields exists and if not creates one AND NOT save in db
201
- * NOTE: it checks for unique fields constraint
202
- */
203
- static getOrNew(_pk, _data) {
204
- throw new Error('Not implemented');
205
- }
206
- /**
207
- * Creates query on this model. used for quering db for partial data, to perform some kind of operations
208
- * that dont need full ORM model to involve, or other non standard operations eg. joins or raw data queries based on this model
209
- */
210
- static query() {
211
- throw new Error('Not implemented');
212
- }
213
- /**
214
- *
215
- * Checks if model with pk key / unique fields exists and if not creates one and saves to db
216
- * NOTE: it checks for unique fields too.
217
- *
218
- * @param data - model width data to check
219
- */
220
- static getOrCreate(_pk, _data) {
221
- throw new Error('Not implemented');
222
- }
223
- /**
224
- * Creates new model & saves is to db
225
- *
226
- * @param data - initial model data
227
- */
228
- static create(_data) {
229
- throw new Error('Not implemented');
230
- }
231
- /**
232
- * Deletes model from db
233
- *
234
- * @param pk - primary key
235
- */
236
- static destroy(_pk) {
237
- throw new Error('Not implemented');
238
- }
239
- /**
240
- * Checks if model exists in db
241
- */
242
- static exists() {
243
- throw new Error('Not implemented');
244
- }
245
- constructor(data) {
246
- /**
247
- * List of hidden properties from JSON / dehydrations
248
- * eg. password field of user
249
- */
250
- this._hidden = [];
251
- this.setDefaults();
252
- if (data) {
253
- this.hydrate(data);
254
- }
255
- }
256
- /**
257
- * Fills model with data. It only fills properties that exists in database
258
- *
259
- * @param data - data to fill
260
- */
261
- hydrate(data) {
262
- this.Container.resolve(Array.ofType(ModelHydrator)).forEach((h) => h.hydrate(this, data));
263
- }
264
- /**
265
- *
266
- * Attachess model to proper relation an sets foreign key
267
- *
268
- * @param data - model to attach
269
- */
270
- attach(data) {
271
- // TODO: refactor this, to not check every time for relation
272
- // do this as map or smth
273
- for (const [_, v] of this.ModelDescriptor.Relations.entries()) {
274
- if (v.TargetModel.name === data.constructor.name) {
275
- // TODO: refactor this, so we dont update foreign key
276
- // instead we must use belongsTo relation on data model to update
277
- data[v.ForeignKey] = this.PrimaryKeyValue;
278
- switch (v.Type) {
279
- case RelationType.One:
280
- this[v.Name].attach(data);
281
- break;
282
- case RelationType.Many:
283
- case RelationType.ManyToMany:
284
- this[v.Name].push(data);
285
- break;
286
- }
287
- }
288
- }
289
- }
290
- /**
291
- * Extracts all data from model. It takes only properties that exists in DB
292
- */
293
- dehydrate(omit) {
294
- return this.Container.resolve(StandardModelDehydrator).dehydrate(this, [...(omit ?? []), ...this._hidden]);
295
- }
296
- /**
297
- *
298
- * Extracts all data from model with relation data. Relation data are dehydrated recursively.
299
- *
300
- * @param omit - fields to omit
301
- */
302
- dehydrateWithRelations(omit) {
303
- return this.Container.resolve(StandardModelWithRelationsDehydrator).dehydrate(this, [...(omit ?? []), ...this._hidden]);
304
- }
305
- toSql() {
306
- return this.Container.resolve(ModelToSqlConverter).toSql(this);
307
- }
308
- /**
309
- * deletes enitt from db. If model have SoftDelete decorator, model is marked as deleted
310
- */
311
- async destroy() {
312
- if (!this.PrimaryKeyValue) {
313
- return;
314
- }
315
- await this.constructor.destroy(this.PrimaryKeyValue);
316
- }
317
- /**
318
- * If model can be in achived state - sets archived at date and saves it to db
319
- */
320
- async archive() {
321
- if (this.ModelDescriptor.Archived) {
322
- this[this.ModelDescriptor.Archived.ArchivedAt] = DateTime.now();
323
- }
324
- else {
325
- throw new OrmException('archived at column not exists in model');
326
- }
327
- const { query } = this.createUpdateQuery();
328
- await query.update(this.toSql()).where(this.PrimaryKeyName, this.PrimaryKeyValue);
329
- }
330
- async update() {
331
- const { query } = this.createUpdateQuery();
332
- if (this.ModelDescriptor.Timestamps.UpdatedAt) {
333
- this[this.ModelDescriptor.Timestamps.UpdatedAt] = DateTime.now();
334
- }
335
- await query.update(this.toSql()).where(this.PrimaryKeyName, this.PrimaryKeyValue);
336
- }
337
- /**
338
- * Save all changes to db. It creates new entry id db or updates existing one if
339
- * primary key exists
340
- */
341
- async insert(insertBehaviour = InsertBehaviour.None) {
342
- const { query, description } = this.createInsertQuery();
343
- switch (insertBehaviour) {
344
- case InsertBehaviour.InsertOrIgnore:
345
- query.orIgnore();
346
- break;
347
- case InsertBehaviour.InsertOrUpdate:
348
- query.onDuplicate().update(description.Columns.filter((c) => !c.PrimaryKey).map((c) => c.Name));
349
- break;
350
- case InsertBehaviour.InsertOrReplace:
351
- query.orReplace();
352
- break;
353
- }
354
- const iMidleware = {
355
- afterQuery: (data) => {
356
- this.PrimaryKeyValue = this.PrimaryKeyValue ?? data.LastInsertId;
357
- return data;
358
- },
359
- modelCreation: () => null,
360
- afterHydration: () => null,
361
- };
362
- query.middleware(iMidleware);
363
- return query.values(this.toSql());
364
- }
365
- /**
366
- *
367
- * Shorthand for inserting model when no primary key exists, or update
368
- * its value in db if primary key is set
369
- *
370
- * @param insertBehaviour - insert mode
371
- */
372
- async insertOrUpdate(insertBehaviour = InsertBehaviour.None) {
373
- if (this.PrimaryKeyValue) {
374
- await this.update();
375
- }
376
- else {
377
- await this.insert(insertBehaviour);
378
- }
379
- }
380
- /**
381
- * Gets model data from database and returns as fresh instance.
382
- *
383
- * If primary key is not fetched, tries to load by columns with unique constraint.
384
- * If there is no unique columns or primary key, throws error
385
- */
386
- async fresh() {
387
- const { query, description } = this.createSelectQuery();
388
- query.select('*');
389
- _preparePkWhere(description, query, this);
390
- _prepareOrderBy(description, query);
391
- // TODO: rethink all cast of this type?
392
- return (await query.firstOrFail());
393
- }
394
- /**
395
- * Refresh model from database.
396
- *
397
- * If no primary key is set, tries to fetch data base on columns
398
- * with unique constraints. If none exists, throws exception
399
- */
400
- async refresh() {
401
- let model = null;
402
- model = await this.fresh();
403
- for (const c of this.ModelDescriptor.Columns) {
404
- this[c.Name] = model[c.Name];
405
- }
406
- }
407
- toJSON() {
408
- return this.dehydrate();
409
- }
410
- /**
411
- * sets default values for model. values are taken from DB default column prop
412
- */
413
- setDefaults() {
414
- this.ModelDescriptor.Columns?.forEach((c) => {
415
- if (c.Uuid) {
416
- this[c.Name] = uuidv4();
417
- }
418
- else {
419
- this[c.Name] = c.DefaultValue;
420
- }
421
- });
422
- if (this.ModelDescriptor.Timestamps.CreatedAt) {
423
- this[this.ModelDescriptor.Timestamps.CreatedAt] = DateTime.now();
424
- }
425
- for (const [, rel] of this.ModelDescriptor.Relations) {
426
- if (rel.Factory) {
427
- this[rel.Name] = rel.Factory(this, rel, this.Container);
428
- }
429
- else if (rel.RelationClass) {
430
- this[rel.Name] = this.Container.resolve(rel.RelationClass, [this, rel.TargetModel, rel, []]);
431
- }
432
- else if (rel.Type === RelationType.Many) {
433
- this[rel.Name] = new OneToManyRelationList(this, rel.TargetModel, rel, []);
434
- }
435
- else if (rel.Type === RelationType.ManyToMany) {
436
- this[rel.Name] = new ManyToManyRelationList(this, rel.TargetModel, rel, []);
437
- }
438
- else {
439
- this[rel.Name] = new SingleRelation(this, rel.TargetModel, rel, null);
440
- }
441
- }
442
- }
443
- createSelectQuery() {
444
- return createQuery(this.constructor, SelectQueryBuilder);
445
- }
446
- createUpdateQuery() {
447
- return createQuery(this.constructor, UpdateQueryBuilder);
448
- }
449
- createInsertQuery() {
450
- return createQuery(this.constructor, InsertQueryBuilder);
451
- }
452
- }
453
- function _descriptor(model) {
454
- return model[MODEL_DESCTRIPTION_SYMBOL];
455
- }
456
- function _preparePkWhere(description, query, model) {
457
- if (description.PrimaryKey) {
458
- query.where(description.PrimaryKey, model.PrimaryKeyValue);
459
- }
460
- else {
461
- const unique = description.Columns.filter((x) => x.Unique);
462
- if (unique.length !== 0) {
463
- for (const c of unique) {
464
- query.where(c.Name, '=', model[c.Name]);
465
- }
466
- }
467
- else {
468
- throw new OrmException('Model dont have primary key set or columns with unique constraint, cannot fetch model from database');
469
- }
470
- }
471
- }
472
- function _prepareOrderBy(description, query, order) {
473
- if (description.PrimaryKey) {
474
- query.order(description.PrimaryKey, order ?? SortOrder.DESC);
475
- }
476
- else {
477
- const unique = description.Columns.filter((c) => c.Unique);
478
- if (unique.length !== 0) {
479
- unique.forEach((c) => query.order(c.Name, order ?? SortOrder.DESC));
480
- }
481
- else if (description.Timestamps?.CreatedAt) {
482
- query.order(description.Timestamps.CreatedAt, order ?? SortOrder.DESC);
483
- }
484
- else if (description.Timestamps?.UpdatedAt) {
485
- query.order(description.Timestamps.UpdatedAt, order ?? SortOrder.DESC);
486
- }
487
- }
488
- }
489
- export class HistoricalModel {
490
- }
491
- /**
492
- * Helper function to create query based on model
493
- *
494
- * @param model - source model for query
495
- * @param query - query class
496
- * @param injectModel - should inject model information into query, if not, query will return raw data
497
- *
498
- * @returns
499
- */
500
- export function createQuery(model, query, injectModel = true) {
501
- const dsc = _descriptor(model);
502
- if (!dsc) {
503
- throw new Error(`model ${model.name} does not have model descriptor. Use @model decorator on class`);
504
- }
505
- const orm = DI.get(Orm);
506
- const driver = orm.Connections.get(dsc.Connection);
507
- if (!driver) {
508
- throw new Error(`model ${model.name} have invalid connection ${dsc.Connection}, please check your db config file or model connection name`);
509
- }
510
- const cnt = driver.Container;
511
- const qr = cnt.resolve(query, [driver, injectModel ? orm.Models.find((x) => x.name === model.name).type : null]);
512
- if (qr instanceof SelectQueryBuilder) {
513
- const scope = model._queryScopes;
514
- if (scope) {
515
- Object.getOwnPropertyNames(scope.__proto__)
516
- .filter((x) => x !== 'constructor')
517
- .forEach(function (property) {
518
- if (typeof scope[property] === 'function') {
519
- qr[property] = scope[property].bind(qr);
520
- }
521
- });
522
- }
523
- }
524
- qr.middleware(new DiscriminationMapMiddleware(dsc));
525
- qr.setTable(dsc.TableName);
526
- if (driver.Options.Database) {
527
- qr.database(driver.Options.Database);
528
- }
529
- return {
530
- query: qr,
531
- description: dsc,
532
- model,
533
- container: driver.Container,
534
- };
535
- }
536
- export const MODEL_STATIC_MIXINS = {
537
- getModelDescriptor() {
538
- const dsc = _descriptor(this);
539
- if (!dsc) {
540
- throw new OrmException(`Model ${this.constructor.name} has no descriptor`);
541
- }
542
- return dsc;
543
- },
544
- getRelationDescriptor(relation) {
545
- const descriptor = this.getModelDescriptor();
546
- let rDescriptor = null;
547
- for (const [key, value] of descriptor.Relations) {
548
- if (key.toLowerCase() === relation.toLowerCase().trim()) {
549
- rDescriptor = value;
550
- break;
551
- }
552
- }
553
- if (!rDescriptor) {
554
- throw new OrmException(`Model ${this.constructor.name} has no relation ${relation}`);
555
- }
556
- return rDescriptor;
557
- },
558
- truncate() {
559
- const { query } = createQuery(this, TruncateTableQueryBuilder, false);
560
- return query;
561
- },
562
- driver() {
563
- const dsc = this.getModelDescriptor();
564
- const orm = DI.get(Orm);
565
- const driver = orm.Connections.get(dsc.Connection);
566
- if (!driver) {
567
- throw new Error(`model ${this.name} have invalid connection ${dsc.Connection}, please check your db config file or model connection name`);
568
- }
569
- return driver;
570
- },
571
- query() {
572
- const { query } = createQuery(this, SelectQueryBuilder);
573
- return query;
574
- },
575
- where(column, operator, value) {
576
- const { query } = createQuery(this, SelectQueryBuilder);
577
- query.select('*');
578
- return query.where(column, operator, value);
579
- },
580
- update(data) {
581
- const { query } = createQuery(this, UpdateQueryBuilder);
582
- return query.update(data);
583
- },
584
- all(page, perPage) {
585
- const { query } = createQuery(this, SelectQueryBuilder);
586
- query.select('*');
587
- if (page >= 0 && perPage > 0) {
588
- query.take(perPage).skip(page * perPage);
589
- }
590
- return query;
591
- },
592
- /**
593
- * Try to insert new value
594
- */
595
- async insert(data, insertBehaviour = InsertBehaviour.None) {
596
- const { query, description, container } = createQuery(this, InsertQueryBuilder);
597
- const converter = container.resolve(ObjectToSqlConverter);
598
- if (Array.isArray(data)) {
599
- if (insertBehaviour !== InsertBehaviour.None) {
600
- throw new OrmException(`insert behaviour is not supported with arrays`);
601
- }
602
- query.values(data.map((d) => {
603
- if (d instanceof ModelBase) {
604
- return d.toSql();
605
- }
606
- return converter.toSql(d);
607
- }));
608
- }
609
- else {
610
- switch (insertBehaviour) {
611
- case InsertBehaviour.InsertOrIgnore:
612
- query.orIgnore();
613
- break;
614
- case InsertBehaviour.InsertOrUpdate:
615
- query.onDuplicate().update(description.Columns.filter((c) => !c.PrimaryKey).map((c) => c.Name));
616
- break;
617
- case InsertBehaviour.InsertOrReplace:
618
- query.orReplace();
619
- break;
620
- }
621
- if (data instanceof ModelBase) {
622
- query.values(data.toSql());
623
- }
624
- else {
625
- query.values(converter.toSql(data));
626
- }
627
- }
628
- const iMidleware = {
629
- afterQuery: (result) => {
630
- if (Array.isArray(data)) {
631
- data.forEach((v, idx) => {
632
- if (v instanceof ModelBase) {
633
- v.PrimaryKeyValue = v.PrimaryKeyValue ?? result.LastInsertId - data.length + idx + 1;
634
- }
635
- });
636
- }
637
- else if (data instanceof ModelBase) {
638
- data.PrimaryKeyValue = data.PrimaryKeyValue ?? result.LastInsertId;
639
- }
640
- return result;
641
- },
642
- modelCreation: () => null,
643
- afterHydration: () => null,
644
- };
645
- query.middleware(iMidleware);
646
- return query;
647
- },
648
- async find(pks) {
649
- const { query, description } = createQuery(this, SelectQueryBuilder);
650
- const pkey = description.PrimaryKey;
651
- query.select('*');
652
- query.whereIn(pkey, pks);
653
- return await query;
654
- },
655
- async findOrFail(pks) {
656
- const { query, description, model } = createQuery(this, SelectQueryBuilder);
657
- const pkey = description.PrimaryKey;
658
- query.select('*');
659
- query.whereIn(pkey, pks);
660
- const result = await query;
661
- if (result.length !== pks.length) {
662
- throw new Error(`could not find all results for model ${model.name}`);
663
- }
664
- return result;
665
- },
666
- async get(pk) {
667
- const { query, description } = createQuery(this, SelectQueryBuilder);
668
- const pkey = description.PrimaryKey;
669
- query.select('*');
670
- query.where(pkey, pk);
671
- _prepareOrderBy(description, query);
672
- return (await query.first());
673
- },
674
- async getOrFail(pk) {
675
- const { query, description } = createQuery(this, SelectQueryBuilder);
676
- const pkey = description.PrimaryKey;
677
- query.select('*');
678
- query.where(pkey, pk);
679
- _prepareOrderBy(description, query);
680
- return (await query.firstOrFail());
681
- },
682
- destroy(pks) {
683
- const description = _descriptor(this);
684
- const data = Array.isArray(pks) ? pks : [pks];
685
- const { query } = description.SoftDelete?.DeletedAt ? createQuery(this, UpdateQueryBuilder) : createQuery(this, DeleteQueryBuilder);
686
- if (description.SoftDelete?.DeletedAt) {
687
- query.update({
688
- [description.SoftDelete.DeletedAt]: DateTime.now(),
689
- });
690
- }
691
- if (pks) {
692
- query.whereIn(description.PrimaryKey, data);
693
- }
694
- return query;
695
- },
696
- async create(data) {
697
- const entity = new (Function.prototype.bind.apply(this))(data);
698
- await entity.insert();
699
- return entity;
700
- },
701
- async getOrCreate(pk, data) {
702
- const { query, description } = createQuery(this, SelectQueryBuilder);
703
- // pk constrain
704
- if (description.PrimaryKey && pk !== null) {
705
- query.where(description.PrimaryKey, pk);
706
- }
707
- // check for all unique columns ( unique constrain )
708
- description.Columns.filter((c) => c.Unique).forEach((c) => {
709
- query.andWhere(c, data[c.Name]);
710
- });
711
- _prepareOrderBy(description, query);
712
- let entity = (await query.first());
713
- if (!entity) {
714
- entity = new (Function.prototype.bind.apply(this))(data);
715
- await entity.insert();
716
- return entity;
717
- }
718
- return entity;
719
- },
720
- async getOrNew(pk, data) {
721
- const { query, description } = createQuery(this, SelectQueryBuilder);
722
- // pk constrain
723
- if (description.PrimaryKey) {
724
- query.where(description.PrimaryKey, pk);
725
- }
726
- // check for all unique columns ( unique constrain )
727
- description.Columns.filter((c) => c.Unique).forEach((c) => {
728
- query.andWhere(c, data[c.Name]);
729
- });
730
- _prepareOrderBy(description, query);
731
- let entity = (await query.first());
732
- if (!entity) {
733
- entity = new (Function.prototype.bind.apply(this))(data);
734
- return entity;
735
- }
736
- return entity;
737
- },
738
- async exists(pk) {
739
- const { query, description } = createQuery(this, SelectQueryBuilder);
740
- // pk constrain
741
- if (description.PrimaryKey && pk !== null) {
742
- query.where(description.PrimaryKey, pk);
743
- }
744
- const result = await query.clearColumns().select(description.PrimaryKey).first();
745
- if (result) {
746
- return true;
747
- }
748
- return false;
749
- },
750
- async first(callback) {
751
- const { query, description } = createQuery(this, SelectQueryBuilder);
752
- _prepareOrderBy(description, query, SortOrder.ASC);
753
- if (callback) {
754
- callback(query);
755
- }
756
- return (await query.first());
757
- },
758
- async last(callback) {
759
- const { query, description } = createQuery(this, SelectQueryBuilder);
760
- _prepareOrderBy(description, query, SortOrder.DESC);
761
- if (callback) {
762
- callback(query);
763
- }
764
- return (await query.first());
765
- },
766
- async newest(callback) {
767
- const { query, description } = createQuery(this, SelectQueryBuilder);
768
- if (description.Timestamps?.CreatedAt) {
769
- query.order(description.Timestamps.CreatedAt, SortOrder.DESC);
770
- }
771
- else {
772
- throw new OrmException('cannot fetch newest entity - CreateAt column not exists in model/db');
773
- }
774
- if (callback) {
775
- callback(query);
776
- }
777
- return (await query.first());
778
- },
779
- async oldest(callback) {
780
- const { query, description } = createQuery(this, SelectQueryBuilder);
781
- if (description.Timestamps?.CreatedAt) {
782
- query.order(description.Timestamps.CreatedAt, SortOrder.ASC);
783
- }
784
- else {
785
- throw new OrmException('cannot fetch oldest entity - CreateAt column not exists in model/db');
786
- }
787
- if (callback) {
788
- callback(query);
789
- }
790
- return (await query.first());
791
- },
792
- async count(callback) {
793
- const { query } = createQuery(this, SelectQueryBuilder);
794
- query.count('*', 'count');
795
- if (callback) {
796
- callback(query);
797
- }
798
- return await (await query.asRaw()).count;
799
- },
800
- };
1
+ /* eslint-disable prettier/prettier */
2
+ import { SortOrder } from './enums.js';
3
+ import { MODEL_DESCTRIPTION_SYMBOL } from './decorators.js';
4
+ import { RelationType, InsertBehaviour, ModelToSqlConverter, ObjectToSqlConverter } from './interfaces.js';
5
+ import { UpdateQueryBuilder, TruncateTableQueryBuilder, SelectQueryBuilder, DeleteQueryBuilder, InsertQueryBuilder } from './builders.js';
6
+ import { DI, isConstructor } from '@spinajs/di';
7
+ import { Orm } from './orm.js';
8
+ import { ModelHydrator } from './hydrators.js';
9
+ import _ from 'lodash';
10
+ import { v4 as uuidv4 } from 'uuid';
11
+ import { OrmException } from './exceptions.js';
12
+ import { StandardModelDehydrator, StandardModelWithRelationsDehydrator } from './dehydrators.js';
13
+ import { DateTime } from 'luxon';
14
+ import { ManyToManyRelationList, OneToManyRelationList, SingleRelation } from './relation-objects.js';
15
+ import { DiscriminationMapMiddleware } from './middlewares.js';
16
+ export function extractModelDescriptor(targetOrForward) {
17
+ const target = !isConstructor(targetOrForward) && targetOrForward ? targetOrForward() : targetOrForward;
18
+ if (!target) {
19
+ return null;
20
+ }
21
+ let descriptor = null;
22
+ _reduce(target);
23
+ return descriptor;
24
+ function _reduce(t) {
25
+ if (!t) {
26
+ return;
27
+ }
28
+ if (t[MODEL_DESCTRIPTION_SYMBOL]) {
29
+ descriptor = descriptor ?? {};
30
+ _.mergeWith(descriptor, t[MODEL_DESCTRIPTION_SYMBOL], (a, b) => {
31
+ if (!a) {
32
+ return b;
33
+ }
34
+ if (Array.isArray(a)) {
35
+ return a.concat(b);
36
+ }
37
+ return a;
38
+ });
39
+ }
40
+ _reduce(t.prototype);
41
+ _reduce(t.__proto__);
42
+ }
43
+ }
44
+ export class ModelBase {
45
+ /**
46
+ * Gets descriptor for this model. It contains information about relations, orm driver, connection properties,
47
+ * db table attached, column information and others.
48
+ */
49
+ get ModelDescriptor() {
50
+ return extractModelDescriptor(this.constructor);
51
+ }
52
+ /**
53
+ * Gets di container associated with this model ( via connection object eg. different drivers have their own implementation of things)
54
+ */
55
+ get Container() {
56
+ if (!this._container) {
57
+ const orm = DI.get(Orm);
58
+ const driver = orm.Connections.get(this.ModelDescriptor.Connection);
59
+ if (!driver) {
60
+ throw new Error(`model ${this.constructor.name} have invalid connection ${this.ModelDescriptor.Connection}, please check your db config file or model connection name`);
61
+ }
62
+ this._container = driver.Container;
63
+ }
64
+ return this._container;
65
+ }
66
+ get PrimaryKeyName() {
67
+ return this.ModelDescriptor.PrimaryKey;
68
+ }
69
+ get PrimaryKeyValue() {
70
+ return this[this.PrimaryKeyName];
71
+ }
72
+ set PrimaryKeyValue(newVal) {
73
+ this[this.PrimaryKeyName] = newVal;
74
+ this.ModelDescriptor.Relations.forEach((r) => {
75
+ const rel = this[r.Name];
76
+ if (!rel)
77
+ return;
78
+ switch (r.Type) {
79
+ case RelationType.One:
80
+ rel[r.ForeignKey] = newVal;
81
+ break;
82
+ case RelationType.Many:
83
+ rel.forEach((rVal) => (rVal[r.ForeignKey] = newVal));
84
+ break;
85
+ case RelationType.ManyToMany:
86
+ // TODO: rethink this
87
+ break;
88
+ }
89
+ });
90
+ }
91
+ valueOf() {
92
+ return this.PrimaryKeyValue;
93
+ }
94
+ driver() {
95
+ const orm = DI.get(Orm);
96
+ const driver = orm.Connections.get(this.ModelDescriptor.Connection);
97
+ return driver;
98
+ }
99
+ /**
100
+ * Recursivelly takes all relation data and returns as single array
101
+ */
102
+ getFlattenRelationModels(recursive) {
103
+ const reduceRelations = function (m) {
104
+ const relations = [...m.ModelDescriptor.Relations.values()];
105
+ const models = _.flatMap(relations, (r) => {
106
+ if (r.Type === RelationType.Many || r.Type === RelationType.ManyToMany) {
107
+ return m[r.Name];
108
+ }
109
+ if (m[r.Name].Value) {
110
+ return [m[r.Name].Value];
111
+ }
112
+ }).filter((x) => x !== undefined);
113
+ if (recursive) {
114
+ return [...models, ..._.flatMap(models, reduceRelations)];
115
+ }
116
+ return models;
117
+ };
118
+ return reduceRelations(this);
119
+ }
120
+ static getModelDescriptor() {
121
+ throw new Error('Not implemented');
122
+ }
123
+ static getRelationDescriptor(_relation) {
124
+ throw new Error('Not implemented');
125
+ }
126
+ /**
127
+ * Clears all data in table
128
+ */
129
+ static truncate() {
130
+ throw new Error('Not implemented');
131
+ }
132
+ /**
133
+ * Get all data from db
134
+ */
135
+ static all(_page, _perPage) {
136
+ throw new Error('Not implemented');
137
+ }
138
+ /**
139
+ * Inserts data to DB.
140
+ *
141
+ * @param _data - data to insert
142
+ */
143
+ static insert(_data, _insertBehaviour = InsertBehaviour.None) {
144
+ throw new Error('Not implemented');
145
+ }
146
+ static where(_column, _operator, _value) {
147
+ throw new Error('Not implemented');
148
+ }
149
+ /**
150
+ * Updates single or multiple records at once with provided value based on condition
151
+ *
152
+ * @param _data - data to set
153
+ */
154
+ static update(_data) {
155
+ throw new Error('Not implemented');
156
+ }
157
+ /**
158
+ * Tries to find all models with given primary keys
159
+ */
160
+ static find(_pks) {
161
+ throw new Error('Not implemented');
162
+ }
163
+ static first() {
164
+ throw new Error('Not implemented');
165
+ }
166
+ static last() {
167
+ throw new Error('Not implemented');
168
+ }
169
+ static newest() {
170
+ throw new Error('Not implemented');
171
+ }
172
+ static oldest() {
173
+ throw new Error('Not implemented');
174
+ }
175
+ static count() {
176
+ throw new Error('Not implemented');
177
+ }
178
+ /**
179
+ * Tries to find all models in db. If not all exists, throws exception
180
+ */
181
+ static findOrFail(_pks) {
182
+ throw new Error('Not implemented');
183
+ }
184
+ /**
185
+ * gets model by specified pk, if not exists, returns null
186
+ *
187
+ */
188
+ static get(_pk) {
189
+ throw new Error('Not implemented');
190
+ }
191
+ /**
192
+ * Finds model by specified pk. If model not exists in db throws exception
193
+ *
194
+ */
195
+ static getOrFail(_pk) {
196
+ throw new Error('Not implemented');
197
+ }
198
+ /**
199
+ *
200
+ * Checks if model with pk key or unique fields exists and if not creates one AND NOT save in db
201
+ * NOTE: it checks for unique fields constraint
202
+ */
203
+ static getOrNew(_pk, _data) {
204
+ throw new Error('Not implemented');
205
+ }
206
+ /**
207
+ * Creates query on this model. used for quering db for partial data, to perform some kind of operations
208
+ * that dont need full ORM model to involve, or other non standard operations eg. joins or raw data queries based on this model
209
+ */
210
+ static query() {
211
+ throw new Error('Not implemented');
212
+ }
213
+ /**
214
+ *
215
+ * Checks if model with pk key / unique fields exists and if not creates one and saves to db
216
+ * NOTE: it checks for unique fields too.
217
+ *
218
+ * @param data - model width data to check
219
+ */
220
+ static getOrCreate(_pk, _data) {
221
+ throw new Error('Not implemented');
222
+ }
223
+ /**
224
+ * Creates new model & saves is to db
225
+ *
226
+ * @param data - initial model data
227
+ */
228
+ static create(_data) {
229
+ throw new Error('Not implemented');
230
+ }
231
+ /**
232
+ * Deletes model from db
233
+ *
234
+ * @param pk - primary key
235
+ */
236
+ static destroy(_pk) {
237
+ throw new Error('Not implemented');
238
+ }
239
+ /**
240
+ * Checks if model exists in db
241
+ */
242
+ static exists() {
243
+ throw new Error('Not implemented');
244
+ }
245
+ constructor(data) {
246
+ /**
247
+ * List of hidden properties from JSON / dehydrations
248
+ * eg. password field of user
249
+ */
250
+ this._hidden = [];
251
+ this.setDefaults();
252
+ if (data) {
253
+ this.hydrate(data);
254
+ }
255
+ }
256
+ /**
257
+ * Fills model with data. It only fills properties that exists in database
258
+ *
259
+ * @param data - data to fill
260
+ */
261
+ hydrate(data) {
262
+ this.Container.resolve(Array.ofType(ModelHydrator)).forEach((h) => h.hydrate(this, data));
263
+ }
264
+ /**
265
+ *
266
+ * Attachess model to proper relation an sets foreign key
267
+ *
268
+ * @param data - model to attach
269
+ */
270
+ attach(data) {
271
+ // TODO: refactor this, to not check every time for relation
272
+ // do this as map or smth
273
+ for (const [_, v] of this.ModelDescriptor.Relations.entries()) {
274
+ if (v.TargetModel.name === data.constructor.name) {
275
+ // TODO: refactor this, so we dont update foreign key
276
+ // instead we must use belongsTo relation on data model to update
277
+ data[v.ForeignKey] = this.PrimaryKeyValue;
278
+ switch (v.Type) {
279
+ case RelationType.One:
280
+ this[v.Name].attach(data);
281
+ break;
282
+ case RelationType.Many:
283
+ case RelationType.ManyToMany:
284
+ this[v.Name].push(data);
285
+ break;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ /**
291
+ * Extracts all data from model. It takes only properties that exists in DB
292
+ */
293
+ dehydrate(omit) {
294
+ return this.Container.resolve(StandardModelDehydrator).dehydrate(this, [...(omit ?? []), ...this._hidden]);
295
+ }
296
+ /**
297
+ *
298
+ * Extracts all data from model with relation data. Relation data are dehydrated recursively.
299
+ *
300
+ * @param omit - fields to omit
301
+ */
302
+ dehydrateWithRelations(omit) {
303
+ return this.Container.resolve(StandardModelWithRelationsDehydrator).dehydrate(this, [...(omit ?? []), ...this._hidden]);
304
+ }
305
+ toSql() {
306
+ return this.Container.resolve(ModelToSqlConverter).toSql(this);
307
+ }
308
+ /**
309
+ * deletes enitt from db. If model have SoftDelete decorator, model is marked as deleted
310
+ */
311
+ async destroy() {
312
+ if (!this.PrimaryKeyValue) {
313
+ return;
314
+ }
315
+ await this.constructor.destroy(this.PrimaryKeyValue);
316
+ }
317
+ /**
318
+ * If model can be in achived state - sets archived at date and saves it to db
319
+ */
320
+ async archive() {
321
+ if (this.ModelDescriptor.Archived) {
322
+ this[this.ModelDescriptor.Archived.ArchivedAt] = DateTime.now();
323
+ }
324
+ else {
325
+ throw new OrmException('archived at column not exists in model');
326
+ }
327
+ const { query } = this.createUpdateQuery();
328
+ await query.update(this.toSql()).where(this.PrimaryKeyName, this.PrimaryKeyValue);
329
+ }
330
+ async update() {
331
+ const { query } = this.createUpdateQuery();
332
+ if (this.ModelDescriptor.Timestamps.UpdatedAt) {
333
+ this[this.ModelDescriptor.Timestamps.UpdatedAt] = DateTime.now();
334
+ }
335
+ await query.update(this.toSql()).where(this.PrimaryKeyName, this.PrimaryKeyValue);
336
+ }
337
+ /**
338
+ * Save all changes to db. It creates new entry id db or updates existing one if
339
+ * primary key exists
340
+ */
341
+ async insert(insertBehaviour = InsertBehaviour.None) {
342
+ const { query, description } = this.createInsertQuery();
343
+ switch (insertBehaviour) {
344
+ case InsertBehaviour.InsertOrIgnore:
345
+ query.orIgnore();
346
+ break;
347
+ case InsertBehaviour.InsertOrUpdate:
348
+ query.onDuplicate().update(description.Columns.filter((c) => !c.PrimaryKey).map((c) => c.Name));
349
+ break;
350
+ case InsertBehaviour.InsertOrReplace:
351
+ query.orReplace();
352
+ break;
353
+ }
354
+ const iMidleware = {
355
+ afterQuery: (data) => {
356
+ this.PrimaryKeyValue = this.PrimaryKeyValue ?? data.LastInsertId;
357
+ return data;
358
+ },
359
+ modelCreation: () => null,
360
+ afterHydration: () => null,
361
+ };
362
+ query.middleware(iMidleware);
363
+ return query.values(this.toSql());
364
+ }
365
+ /**
366
+ *
367
+ * Shorthand for inserting model when no primary key exists, or update
368
+ * its value in db if primary key is set
369
+ *
370
+ * @param insertBehaviour - insert mode
371
+ */
372
+ async insertOrUpdate(insertBehaviour = InsertBehaviour.None) {
373
+ if (this.PrimaryKeyValue) {
374
+ await this.update();
375
+ }
376
+ else {
377
+ await this.insert(insertBehaviour);
378
+ }
379
+ }
380
+ /**
381
+ * Gets model data from database and returns as fresh instance.
382
+ *
383
+ * If primary key is not fetched, tries to load by columns with unique constraint.
384
+ * If there is no unique columns or primary key, throws error
385
+ */
386
+ async fresh() {
387
+ const { query, description } = this.createSelectQuery();
388
+ query.select('*');
389
+ _preparePkWhere(description, query, this);
390
+ _prepareOrderBy(description, query);
391
+ // TODO: rethink all cast of this type?
392
+ return (await query.firstOrFail());
393
+ }
394
+ /**
395
+ * Refresh model from database.
396
+ *
397
+ * If no primary key is set, tries to fetch data base on columns
398
+ * with unique constraints. If none exists, throws exception
399
+ */
400
+ async refresh() {
401
+ let model = null;
402
+ model = await this.fresh();
403
+ for (const c of this.ModelDescriptor.Columns) {
404
+ this[c.Name] = model[c.Name];
405
+ }
406
+ }
407
+ toJSON() {
408
+ return this.dehydrate();
409
+ }
410
+ /**
411
+ * sets default values for model. values are taken from DB default column prop
412
+ */
413
+ setDefaults() {
414
+ this.ModelDescriptor.Columns?.forEach((c) => {
415
+ if (c.Uuid) {
416
+ this[c.Name] = uuidv4();
417
+ }
418
+ else {
419
+ this[c.Name] = c.DefaultValue;
420
+ }
421
+ });
422
+ if (this.ModelDescriptor.Timestamps.CreatedAt) {
423
+ this[this.ModelDescriptor.Timestamps.CreatedAt] = DateTime.now();
424
+ }
425
+ for (const [, rel] of this.ModelDescriptor.Relations) {
426
+ if (rel.Factory) {
427
+ this[rel.Name] = rel.Factory(this, rel, this.Container);
428
+ }
429
+ else if (rel.RelationClass) {
430
+ this[rel.Name] = this.Container.resolve(rel.RelationClass, [this, rel.TargetModel, rel, []]);
431
+ }
432
+ else if (rel.Type === RelationType.Many) {
433
+ this[rel.Name] = new OneToManyRelationList(this, rel.TargetModel, rel, []);
434
+ }
435
+ else if (rel.Type === RelationType.ManyToMany) {
436
+ this[rel.Name] = new ManyToManyRelationList(this, rel.TargetModel, rel, []);
437
+ }
438
+ else {
439
+ this[rel.Name] = new SingleRelation(this, rel.TargetModel, rel, null);
440
+ }
441
+ }
442
+ }
443
+ createSelectQuery() {
444
+ return createQuery(this.constructor, SelectQueryBuilder);
445
+ }
446
+ createUpdateQuery() {
447
+ return createQuery(this.constructor, UpdateQueryBuilder);
448
+ }
449
+ createInsertQuery() {
450
+ return createQuery(this.constructor, InsertQueryBuilder);
451
+ }
452
+ }
453
+ function _descriptor(model) {
454
+ return model[MODEL_DESCTRIPTION_SYMBOL];
455
+ }
456
+ function _preparePkWhere(description, query, model) {
457
+ if (description.PrimaryKey) {
458
+ query.where(description.PrimaryKey, model.PrimaryKeyValue);
459
+ }
460
+ else {
461
+ const unique = description.Columns.filter((x) => x.Unique);
462
+ if (unique.length !== 0) {
463
+ for (const c of unique) {
464
+ query.where(c.Name, '=', model[c.Name]);
465
+ }
466
+ }
467
+ else {
468
+ throw new OrmException('Model dont have primary key set or columns with unique constraint, cannot fetch model from database');
469
+ }
470
+ }
471
+ }
472
+ function _prepareOrderBy(description, query, order) {
473
+ if (description.PrimaryKey) {
474
+ query.order(description.PrimaryKey, order ?? SortOrder.DESC);
475
+ }
476
+ else {
477
+ const unique = description.Columns.filter((c) => c.Unique);
478
+ if (unique.length !== 0) {
479
+ unique.forEach((c) => query.order(c.Name, order ?? SortOrder.DESC));
480
+ }
481
+ else if (description.Timestamps?.CreatedAt) {
482
+ query.order(description.Timestamps.CreatedAt, order ?? SortOrder.DESC);
483
+ }
484
+ else if (description.Timestamps?.UpdatedAt) {
485
+ query.order(description.Timestamps.UpdatedAt, order ?? SortOrder.DESC);
486
+ }
487
+ }
488
+ }
489
+ export class HistoricalModel {
490
+ }
491
+ /**
492
+ * Helper function to create query based on model
493
+ *
494
+ * @param model - source model for query
495
+ * @param query - query class
496
+ * @param injectModel - should inject model information into query, if not, query will return raw data
497
+ *
498
+ * @returns
499
+ */
500
+ export function createQuery(model, query, injectModel = true) {
501
+ const dsc = _descriptor(model);
502
+ if (!dsc) {
503
+ throw new Error(`model ${model.name} does not have model descriptor. Use @model decorator on class`);
504
+ }
505
+ const orm = DI.get(Orm);
506
+ const driver = orm.Connections.get(dsc.Connection);
507
+ if (!driver) {
508
+ throw new Error(`model ${model.name} have invalid connection ${dsc.Connection}, please check your db config file or model connection name`);
509
+ }
510
+ const cnt = driver.Container;
511
+ const qr = cnt.resolve(query, [driver, injectModel ? orm.Models.find((x) => x.name === model.name).type : null]);
512
+ if (qr instanceof SelectQueryBuilder) {
513
+ const scope = model._queryScopes;
514
+ if (scope) {
515
+ Object.getOwnPropertyNames(scope.__proto__)
516
+ .filter((x) => x !== 'constructor')
517
+ .forEach(function (property) {
518
+ if (typeof scope[property] === 'function') {
519
+ qr[property] = scope[property].bind(qr);
520
+ }
521
+ });
522
+ }
523
+ }
524
+ qr.middleware(new DiscriminationMapMiddleware(dsc));
525
+ qr.setTable(dsc.TableName);
526
+ if (driver.Options.Database) {
527
+ qr.database(driver.Options.Database);
528
+ }
529
+ return {
530
+ query: qr,
531
+ description: dsc,
532
+ model,
533
+ container: driver.Container,
534
+ };
535
+ }
536
+ export const MODEL_STATIC_MIXINS = {
537
+ getModelDescriptor() {
538
+ const dsc = _descriptor(this);
539
+ if (!dsc) {
540
+ throw new OrmException(`Model ${this.constructor.name} has no descriptor`);
541
+ }
542
+ return dsc;
543
+ },
544
+ getRelationDescriptor(relation) {
545
+ const descriptor = this.getModelDescriptor();
546
+ let rDescriptor = null;
547
+ for (const [key, value] of descriptor.Relations) {
548
+ if (key.toLowerCase() === relation.toLowerCase().trim()) {
549
+ rDescriptor = value;
550
+ break;
551
+ }
552
+ }
553
+ if (!rDescriptor) {
554
+ throw new OrmException(`Model ${this.constructor.name} has no relation ${relation}`);
555
+ }
556
+ return rDescriptor;
557
+ },
558
+ truncate() {
559
+ const { query } = createQuery(this, TruncateTableQueryBuilder, false);
560
+ return query;
561
+ },
562
+ driver() {
563
+ const dsc = this.getModelDescriptor();
564
+ const orm = DI.get(Orm);
565
+ const driver = orm.Connections.get(dsc.Connection);
566
+ if (!driver) {
567
+ throw new Error(`model ${this.name} have invalid connection ${dsc.Connection}, please check your db config file or model connection name`);
568
+ }
569
+ return driver;
570
+ },
571
+ query() {
572
+ const { query } = createQuery(this, SelectQueryBuilder);
573
+ return query;
574
+ },
575
+ where(column, operator, value) {
576
+ const { query } = createQuery(this, SelectQueryBuilder);
577
+ query.select('*');
578
+ return query.where(column, operator, value);
579
+ },
580
+ update(data) {
581
+ const { query } = createQuery(this, UpdateQueryBuilder);
582
+ return query.update(data);
583
+ },
584
+ all(page, perPage) {
585
+ const { query } = createQuery(this, SelectQueryBuilder);
586
+ query.select('*');
587
+ if (page >= 0 && perPage > 0) {
588
+ query.take(perPage).skip(page * perPage);
589
+ }
590
+ return query;
591
+ },
592
+ /**
593
+ * Try to insert new value
594
+ */
595
+ async insert(data, insertBehaviour = InsertBehaviour.None) {
596
+ const { query, description, container } = createQuery(this, InsertQueryBuilder);
597
+ const converter = container.resolve(ObjectToSqlConverter);
598
+ if (Array.isArray(data)) {
599
+ if (insertBehaviour !== InsertBehaviour.None) {
600
+ throw new OrmException(`insert behaviour is not supported with arrays`);
601
+ }
602
+ query.values(data.map((d) => {
603
+ if (d instanceof ModelBase) {
604
+ return d.toSql();
605
+ }
606
+ return converter.toSql(d);
607
+ }));
608
+ }
609
+ else {
610
+ switch (insertBehaviour) {
611
+ case InsertBehaviour.InsertOrIgnore:
612
+ query.orIgnore();
613
+ break;
614
+ case InsertBehaviour.InsertOrUpdate:
615
+ query.onDuplicate().update(description.Columns.filter((c) => !c.PrimaryKey).map((c) => c.Name));
616
+ break;
617
+ case InsertBehaviour.InsertOrReplace:
618
+ query.orReplace();
619
+ break;
620
+ }
621
+ if (data instanceof ModelBase) {
622
+ query.values(data.toSql());
623
+ }
624
+ else {
625
+ query.values(converter.toSql(data));
626
+ }
627
+ }
628
+ const iMidleware = {
629
+ afterQuery: (result) => {
630
+ if (Array.isArray(data)) {
631
+ data.forEach((v, idx) => {
632
+ if (v instanceof ModelBase) {
633
+ v.PrimaryKeyValue = v.PrimaryKeyValue ?? result.LastInsertId - data.length + idx + 1;
634
+ }
635
+ });
636
+ }
637
+ else if (data instanceof ModelBase) {
638
+ data.PrimaryKeyValue = data.PrimaryKeyValue ?? result.LastInsertId;
639
+ }
640
+ return result;
641
+ },
642
+ modelCreation: () => null,
643
+ afterHydration: () => null,
644
+ };
645
+ query.middleware(iMidleware);
646
+ return query;
647
+ },
648
+ async find(pks) {
649
+ const { query, description } = createQuery(this, SelectQueryBuilder);
650
+ const pkey = description.PrimaryKey;
651
+ query.select('*');
652
+ query.whereIn(pkey, pks);
653
+ return await query;
654
+ },
655
+ async findOrFail(pks) {
656
+ const { query, description, model } = createQuery(this, SelectQueryBuilder);
657
+ const pkey = description.PrimaryKey;
658
+ query.select('*');
659
+ query.whereIn(pkey, pks);
660
+ const result = await query;
661
+ if (result.length !== pks.length) {
662
+ throw new Error(`could not find all results for model ${model.name}`);
663
+ }
664
+ return result;
665
+ },
666
+ async get(pk) {
667
+ const { query, description } = createQuery(this, SelectQueryBuilder);
668
+ const pkey = description.PrimaryKey;
669
+ query.select('*');
670
+ query.where(pkey, pk);
671
+ _prepareOrderBy(description, query);
672
+ return (await query.first());
673
+ },
674
+ async getOrFail(pk) {
675
+ const { query, description } = createQuery(this, SelectQueryBuilder);
676
+ const pkey = description.PrimaryKey;
677
+ query.select('*');
678
+ query.where(pkey, pk);
679
+ _prepareOrderBy(description, query);
680
+ return (await query.firstOrFail());
681
+ },
682
+ destroy(pks) {
683
+ const description = _descriptor(this);
684
+ const data = Array.isArray(pks) ? pks : [pks];
685
+ const { query } = description.SoftDelete?.DeletedAt ? createQuery(this, UpdateQueryBuilder) : createQuery(this, DeleteQueryBuilder);
686
+ if (description.SoftDelete?.DeletedAt) {
687
+ query.update({
688
+ [description.SoftDelete.DeletedAt]: DateTime.now(),
689
+ });
690
+ }
691
+ if (pks) {
692
+ query.whereIn(description.PrimaryKey, data);
693
+ }
694
+ return query;
695
+ },
696
+ async create(data) {
697
+ const entity = new (Function.prototype.bind.apply(this))(data);
698
+ await entity.insert();
699
+ return entity;
700
+ },
701
+ async getOrCreate(pk, data) {
702
+ const { query, description } = createQuery(this, SelectQueryBuilder);
703
+ // pk constrain
704
+ if (description.PrimaryKey && pk !== null) {
705
+ query.where(description.PrimaryKey, pk);
706
+ }
707
+ // check for all unique columns ( unique constrain )
708
+ description.Columns.filter((c) => c.Unique).forEach((c) => {
709
+ query.andWhere(c, data[c.Name]);
710
+ });
711
+ _prepareOrderBy(description, query);
712
+ let entity = (await query.first());
713
+ if (!entity) {
714
+ entity = new (Function.prototype.bind.apply(this))(data);
715
+ await entity.insert();
716
+ return entity;
717
+ }
718
+ return entity;
719
+ },
720
+ async getOrNew(pk, data) {
721
+ const { query, description } = createQuery(this, SelectQueryBuilder);
722
+ // pk constrain
723
+ if (description.PrimaryKey) {
724
+ query.where(description.PrimaryKey, pk);
725
+ }
726
+ // check for all unique columns ( unique constrain )
727
+ description.Columns.filter((c) => c.Unique).forEach((c) => {
728
+ query.andWhere(c, data[c.Name]);
729
+ });
730
+ _prepareOrderBy(description, query);
731
+ let entity = (await query.first());
732
+ if (!entity) {
733
+ entity = new (Function.prototype.bind.apply(this))(data);
734
+ return entity;
735
+ }
736
+ return entity;
737
+ },
738
+ async exists(pk) {
739
+ const { query, description } = createQuery(this, SelectQueryBuilder);
740
+ // pk constrain
741
+ if (description.PrimaryKey && pk !== null) {
742
+ query.where(description.PrimaryKey, pk);
743
+ }
744
+ const result = await query.clearColumns().select(description.PrimaryKey).first();
745
+ if (result) {
746
+ return true;
747
+ }
748
+ return false;
749
+ },
750
+ async first(callback) {
751
+ const { query, description } = createQuery(this, SelectQueryBuilder);
752
+ _prepareOrderBy(description, query, SortOrder.ASC);
753
+ if (callback) {
754
+ callback(query);
755
+ }
756
+ return (await query.first());
757
+ },
758
+ async last(callback) {
759
+ const { query, description } = createQuery(this, SelectQueryBuilder);
760
+ _prepareOrderBy(description, query, SortOrder.DESC);
761
+ if (callback) {
762
+ callback(query);
763
+ }
764
+ return (await query.first());
765
+ },
766
+ async newest(callback) {
767
+ const { query, description } = createQuery(this, SelectQueryBuilder);
768
+ if (description.Timestamps?.CreatedAt) {
769
+ query.order(description.Timestamps.CreatedAt, SortOrder.DESC);
770
+ }
771
+ else {
772
+ throw new OrmException('cannot fetch newest entity - CreateAt column not exists in model/db');
773
+ }
774
+ if (callback) {
775
+ callback(query);
776
+ }
777
+ return (await query.first());
778
+ },
779
+ async oldest(callback) {
780
+ const { query, description } = createQuery(this, SelectQueryBuilder);
781
+ if (description.Timestamps?.CreatedAt) {
782
+ query.order(description.Timestamps.CreatedAt, SortOrder.ASC);
783
+ }
784
+ else {
785
+ throw new OrmException('cannot fetch oldest entity - CreateAt column not exists in model/db');
786
+ }
787
+ if (callback) {
788
+ callback(query);
789
+ }
790
+ return (await query.first());
791
+ },
792
+ async count(callback) {
793
+ const { query } = createQuery(this, SelectQueryBuilder);
794
+ query.count('*', 'count');
795
+ if (callback) {
796
+ callback(query);
797
+ }
798
+ return await (await query.asRaw()).count;
799
+ },
800
+ };
801
801
  //# sourceMappingURL=model.js.map