velocious 1.0.101 → 1.0.103
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/package.json +1 -1
- package/spec/database/drivers/create-sql/create-index-sql-spec.js +24 -0
- package/src/configuration-types.js +7 -1
- package/src/configuration.js +2 -2
- package/src/database/drivers/base-column.js +2 -2
- package/src/database/drivers/base-columns-index.js +6 -0
- package/src/database/drivers/base.js +60 -22
- package/src/database/drivers/mssql/index.js +70 -22
- package/src/database/drivers/mssql/sql/create-database.js +9 -2
- package/src/database/drivers/mssql/table.js +14 -4
- package/src/database/drivers/mysql/column.js +6 -0
- package/src/database/drivers/mysql/columns-index.js +3 -6
- package/src/database/drivers/mysql/foreign-key.js +2 -0
- package/src/database/drivers/mysql/index.js +43 -16
- package/src/database/drivers/mysql/query-parser.js +2 -0
- package/src/database/drivers/mysql/query.js +7 -2
- package/src/database/drivers/mysql/table.js +6 -0
- package/src/database/drivers/pgsql/index.js +78 -11
- package/src/database/drivers/sqlite/base.js +77 -25
- package/src/database/drivers/sqlite/column.js +8 -0
- package/src/database/drivers/sqlite/sql/alter-table.js +25 -20
- package/src/database/drivers/sqlite/sql/create-index.js +2 -0
- package/src/database/drivers/sqlite/sql/create-table.js +2 -0
- package/src/database/drivers/sqlite/sql/delete.js +4 -2
- package/src/database/drivers/sqlite/sql/drop-table.js +2 -0
- package/src/database/drivers/sqlite/sql/insert.js +2 -0
- package/src/database/drivers/sqlite/sql/update.js +2 -0
- package/src/database/drivers/sqlite/table.js +14 -0
- package/src/database/migration/index.js +6 -4
- package/src/database/pool/base-methods-forward.js +2 -2
- package/src/database/query/alter-table-base.js +1 -1
- package/src/database/query/base.js +2 -2
- package/src/database/query/create-database-base.js +8 -4
- package/src/database/query/create-index-base.js +12 -7
- package/src/database/query/create-table-base.js +4 -4
- package/src/database/query/drop-table-base.js +8 -8
- package/src/database/query/index.js +31 -18
- package/src/database/query/insert-base.js +18 -3
- package/src/database/query-parser/base-query-parser.js +2 -2
- package/src/database/record/index.js +444 -172
- package/src/database/record/instance-relationships/base.js +41 -44
- package/src/database/record/instance-relationships/belongs-to.js +15 -3
- package/src/database/record/instance-relationships/has-many.js +49 -28
- package/src/database/record/instance-relationships/has-one.js +22 -7
- package/src/database/record/relationships/base.js +33 -43
- package/src/database/record/relationships/belongs-to.js +13 -3
- package/src/database/record/relationships/has-many.js +8 -2
- package/src/database/record/relationships/has-one.js +8 -2
- package/src/database/record/validators/base.js +14 -2
- package/src/database/record/validators/presence.js +7 -0
- package/src/database/table-data/table-column.js +3 -3
- package/src/environment-handlers/node.js +1 -2
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{type: string, message: string}} ValidationErrorObjectType
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.js"
|
|
2
8
|
import BelongsToRelationship from "./relationships/belongs-to.js"
|
|
3
9
|
import Configuration from "../../configuration.js"
|
|
4
10
|
import FromTable from "../query/from-table.js"
|
|
5
11
|
import Handler from "../handler.js"
|
|
6
|
-
import HasManyRelationship from "./relationships/has-many.js"
|
|
7
12
|
import HasManyInstanceRelationship from "./instance-relationships/has-many.js"
|
|
8
|
-
import
|
|
13
|
+
import HasManyRelationship from "./relationships/has-many.js"
|
|
9
14
|
import HasOneInstanceRelationship from "./instance-relationships/has-one.js"
|
|
15
|
+
import HasOneRelationship from "./relationships/has-one.js"
|
|
10
16
|
import * as inflection from "inflection"
|
|
11
17
|
import Query from "../query/index.js"
|
|
12
18
|
import restArgsError from "../../utils/rest-args-error.js"
|
|
@@ -15,53 +21,120 @@ import ValidatorsUniqueness from "./validators/uniqueness.js"
|
|
|
15
21
|
|
|
16
22
|
class ValidationError extends Error {
|
|
17
23
|
/**
|
|
18
|
-
* @
|
|
19
|
-
* @returns {T}
|
|
24
|
+
* @returns {VelociousDatabaseRecord}
|
|
20
25
|
*/
|
|
21
26
|
getModel() {
|
|
27
|
+
if (!this._model) throw new Error("Model hasn't been set")
|
|
28
|
+
|
|
22
29
|
return this._model
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
|
-
* @
|
|
27
|
-
* @param {T} model
|
|
33
|
+
* @param {VelociousDatabaseRecord} model
|
|
28
34
|
*/
|
|
29
35
|
setModel(model) {
|
|
30
36
|
this._model = model
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
/**
|
|
34
|
-
* @returns {
|
|
40
|
+
* @returns {Record<string, ValidationErrorObjectType[]>}
|
|
35
41
|
*/
|
|
36
42
|
getValidationErrors() {
|
|
43
|
+
if (!this._validationErrors) throw new Error("Validation errors hasn't been set")
|
|
44
|
+
|
|
37
45
|
return this._validationErrors
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {Record<string, ValidationErrorObjectType[]>} validationErrors
|
|
50
|
+
*/
|
|
40
51
|
setValidationErrors(validationErrors) {
|
|
41
52
|
this._validationErrors = validationErrors
|
|
42
53
|
}
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
class VelociousDatabaseRecord {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
static getAttributeNameToColumnNameMap() {
|
|
58
|
+
if (!this._attributeNameToColumnName) {
|
|
59
|
+
/** @type {Record<string, string>} */
|
|
60
|
+
this._attributeNameToColumnName = {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this._attributeNameToColumnName
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static getColumnNameToAttributeNameMap() {
|
|
67
|
+
if (!this._columnNameToAttributeName) {
|
|
68
|
+
/** @type {Record<string, string>} */
|
|
69
|
+
this._columnNameToAttributeName = {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this._columnNameToAttributeName
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static getTranslationsMap() {
|
|
76
|
+
if (!this._translations) {
|
|
77
|
+
/** @type {Record<string, object>} */
|
|
78
|
+
this._translations = {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return this._translations
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static getValidatorsMap() {
|
|
85
|
+
if (!this._validators) {
|
|
86
|
+
/** @type {Record<string, import("./validators/base.js").default[]>} */
|
|
87
|
+
this._validators = {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return this._validators
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static getValidatorTypesMap() {
|
|
94
|
+
if (!this._validatorTypes) {
|
|
95
|
+
/** @type {Record<string, typeof import("./validators/base.js").default>} */
|
|
96
|
+
this._validatorTypes = {}
|
|
97
|
+
}
|
|
51
98
|
|
|
52
99
|
return this._validatorTypes
|
|
53
100
|
}
|
|
54
101
|
|
|
102
|
+
/** @type {Record<string, any>} */
|
|
103
|
+
_attributes = {}
|
|
104
|
+
|
|
105
|
+
/** @type {Record<string, any>} */
|
|
106
|
+
_changes = {}
|
|
107
|
+
|
|
108
|
+
/** @type {Record<string, import("../drivers/base-column.js").default>} */
|
|
109
|
+
_columnsAsHash = {}
|
|
110
|
+
|
|
111
|
+
/** @type {import("../drivers/base.js").default | undefined} */
|
|
112
|
+
__connection = undefined
|
|
113
|
+
|
|
114
|
+
/** @type {Record<string, import("./instance-relationships/base.js").default>} */
|
|
115
|
+
_instanceRelationships = {}
|
|
116
|
+
|
|
117
|
+
/** @type {string | undefined} */
|
|
118
|
+
__tableName = undefined
|
|
119
|
+
|
|
120
|
+
/** @type {Record<string, ValidationErrorObjectType[]>} */
|
|
121
|
+
_validationErrors = {}
|
|
122
|
+
|
|
123
|
+
static validatorTypes() {
|
|
124
|
+
return this.getValidatorTypesMap()
|
|
125
|
+
}
|
|
126
|
+
|
|
55
127
|
/**
|
|
56
128
|
* @param {string} name
|
|
57
|
-
* @param {import("./validators/base.js").default} validatorClass
|
|
129
|
+
* @param {typeof import("./validators/base.js").default} validatorClass
|
|
58
130
|
*/
|
|
59
131
|
static registerValidatorType(name, validatorClass) {
|
|
60
132
|
this.validatorTypes()[name] = validatorClass
|
|
61
133
|
}
|
|
62
134
|
|
|
63
135
|
/**
|
|
64
|
-
* @
|
|
136
|
+
* @param {string} validatorName
|
|
137
|
+
* @returns {typeof import("./validators/base.js").default}
|
|
65
138
|
*/
|
|
66
139
|
static getValidatorType(validatorName) {
|
|
67
140
|
if (!(validatorName in this.validatorTypes())) throw new Error(`Validator type ${validatorName} not found`)
|
|
@@ -70,19 +143,29 @@ class VelociousDatabaseRecord {
|
|
|
70
143
|
}
|
|
71
144
|
|
|
72
145
|
/**
|
|
146
|
+
* @param {string} relationshipName
|
|
73
147
|
* @returns {boolean}
|
|
74
148
|
*/
|
|
75
149
|
static _relationshipExists(relationshipName) {
|
|
76
|
-
if (
|
|
150
|
+
if (relationshipName in this.getRelationshipsMap()) {
|
|
77
151
|
return true
|
|
78
152
|
}
|
|
79
153
|
|
|
80
154
|
return false
|
|
81
155
|
}
|
|
82
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @typedef {object} RelationshipDataArgumentType
|
|
159
|
+
* @property {string} [className]
|
|
160
|
+
* @property {typeof VelociousDatabaseRecord} [klass]
|
|
161
|
+
* @property {string} [type]
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} relationshipName
|
|
165
|
+
* @param {RelationshipDataArgumentType} data
|
|
166
|
+
*/
|
|
83
167
|
static _defineRelationship(relationshipName, data) {
|
|
84
168
|
if (!relationshipName) throw new Error(`Invalid relationship name given: ${relationshipName}`)
|
|
85
|
-
if (!this._relationships) this._relationships = {}
|
|
86
169
|
if (this._relationshipExists(relationshipName)) throw new Error(`Relationship ${relationshipName} already exists`)
|
|
87
170
|
|
|
88
171
|
const actualData = Object.assign(
|
|
@@ -98,21 +181,29 @@ class VelociousDatabaseRecord {
|
|
|
98
181
|
actualData.className = inflection.camelize(inflection.singularize(relationshipName))
|
|
99
182
|
}
|
|
100
183
|
|
|
184
|
+
/** @type {Record<string, (this: VelociousDatabaseRecord) => unknown>} */
|
|
185
|
+
const proto = /** @type {any} */ (this.prototype);
|
|
186
|
+
|
|
101
187
|
let relationship
|
|
102
188
|
|
|
103
189
|
if (actualData.type == "belongsTo") {
|
|
104
190
|
relationship = new BelongsToRelationship(actualData)
|
|
105
191
|
|
|
106
|
-
|
|
192
|
+
proto[relationshipName] = function() {
|
|
107
193
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
108
194
|
|
|
109
195
|
return relationship.loaded()
|
|
110
196
|
}
|
|
111
197
|
|
|
112
|
-
|
|
198
|
+
// @ts-expect-error
|
|
199
|
+
proto[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
200
|
+
// @ts-expect-error
|
|
113
201
|
const instanceRelationship = this.getRelationshipByName(relationshipName)
|
|
202
|
+
|
|
203
|
+
// @ts-expect-error
|
|
114
204
|
const record = instanceRelationship.build(attributes)
|
|
115
205
|
|
|
206
|
+
// @ts-expect-error
|
|
116
207
|
const inverseOf = instanceRelationship.getRelationship().getInverseOf()
|
|
117
208
|
|
|
118
209
|
if (inverseOf) {
|
|
@@ -132,41 +223,51 @@ class VelociousDatabaseRecord {
|
|
|
132
223
|
return record
|
|
133
224
|
}
|
|
134
225
|
|
|
135
|
-
|
|
226
|
+
proto[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
136
227
|
await this.getRelationshipByName(relationshipName).load()
|
|
137
228
|
}
|
|
138
229
|
|
|
139
|
-
|
|
230
|
+
// @ts-expect-error
|
|
231
|
+
proto[`set${inflection.camelize(relationshipName)}`] = function(model) {
|
|
232
|
+
// @ts-expect-error
|
|
140
233
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
141
234
|
|
|
235
|
+
// @ts-expect-error
|
|
142
236
|
relationship.setLoaded(model)
|
|
237
|
+
|
|
238
|
+
// @ts-expect-error
|
|
143
239
|
relationship.setDirty(true)
|
|
144
240
|
}
|
|
145
241
|
} else if (actualData.type == "hasMany") {
|
|
146
242
|
relationship = new HasManyRelationship(actualData)
|
|
147
243
|
|
|
148
|
-
|
|
244
|
+
proto[relationshipName] = function() {
|
|
149
245
|
return this.getRelationshipByName(relationshipName)
|
|
150
246
|
}
|
|
151
247
|
|
|
152
|
-
|
|
248
|
+
proto[`${relationshipName}Loaded`] = function() {
|
|
153
249
|
return this.getRelationshipByName(relationshipName).loaded()
|
|
154
250
|
}
|
|
155
251
|
|
|
156
|
-
|
|
252
|
+
proto[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
157
253
|
await this.getRelationshipByName(relationshipName).load()
|
|
158
254
|
}
|
|
159
255
|
} else if (actualData.type == "hasOne") {
|
|
160
256
|
relationship = new HasOneRelationship(actualData)
|
|
161
257
|
|
|
162
|
-
|
|
258
|
+
proto[relationshipName] = function() {
|
|
163
259
|
return this.getRelationshipByName(relationshipName).loaded()
|
|
164
260
|
}
|
|
165
261
|
|
|
166
|
-
|
|
262
|
+
// @ts-expect-error
|
|
263
|
+
proto[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
264
|
+
// @ts-expect-error
|
|
167
265
|
const instanceRelationship = this.getRelationshipByName(relationshipName)
|
|
266
|
+
|
|
267
|
+
// @ts-expect-error
|
|
168
268
|
const record = instanceRelationship.build(attributes)
|
|
169
269
|
|
|
270
|
+
// @ts-expect-error
|
|
170
271
|
const inverseOf = instanceRelationship.getRelationship().getInverseOf()
|
|
171
272
|
|
|
172
273
|
if (inverseOf) {
|
|
@@ -179,23 +280,22 @@ class VelociousDatabaseRecord {
|
|
|
179
280
|
return record
|
|
180
281
|
}
|
|
181
282
|
|
|
182
|
-
|
|
283
|
+
proto[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
183
284
|
await this.getRelationshipByName(relationshipName).load()
|
|
184
285
|
}
|
|
185
286
|
} else {
|
|
186
287
|
throw new Error(`Unknown relationship type: ${actualData.type}`)
|
|
187
288
|
}
|
|
188
289
|
|
|
189
|
-
this.
|
|
290
|
+
this.getRelationshipsMap()[relationshipName] = relationship
|
|
190
291
|
}
|
|
191
292
|
|
|
192
293
|
/**
|
|
294
|
+
* @param {string} relationshipName
|
|
193
295
|
* @returns {import("./relationships/base.js").default}
|
|
194
296
|
*/
|
|
195
297
|
static getRelationshipByName(relationshipName) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const relationship = this._relationships[relationshipName]
|
|
298
|
+
const relationship = this.getRelationshipsMap()[relationshipName]
|
|
199
299
|
|
|
200
300
|
if (!relationship) throw new Error(`No relationship by that name: ${relationshipName}`)
|
|
201
301
|
|
|
@@ -206,9 +306,16 @@ class VelociousDatabaseRecord {
|
|
|
206
306
|
* @returns {Array<import("./relationships/base.js").default>}
|
|
207
307
|
*/
|
|
208
308
|
static getRelationships() {
|
|
209
|
-
|
|
309
|
+
return Object.values(this.getRelationshipsMap())
|
|
310
|
+
}
|
|
210
311
|
|
|
211
|
-
|
|
312
|
+
static getRelationshipsMap() {
|
|
313
|
+
if (!this._relationships) {
|
|
314
|
+
/** @type {Record<string, import("./relationships/base.js").default>} */
|
|
315
|
+
this._relationships = {}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return this._relationships
|
|
212
319
|
}
|
|
213
320
|
|
|
214
321
|
/**
|
|
@@ -219,14 +326,12 @@ class VelociousDatabaseRecord {
|
|
|
219
326
|
}
|
|
220
327
|
|
|
221
328
|
/**
|
|
222
|
-
* @
|
|
223
|
-
* @returns {
|
|
329
|
+
* @param {string} relationshipName
|
|
330
|
+
* @returns {import("./instance-relationships/base.js").default}
|
|
224
331
|
*/
|
|
225
332
|
getRelationshipByName(relationshipName) {
|
|
226
|
-
if (!this._instanceRelationships) this._instanceRelationships = {}
|
|
227
|
-
|
|
228
333
|
if (!(relationshipName in this._instanceRelationships)) {
|
|
229
|
-
const modelClassRelationship = this.
|
|
334
|
+
const modelClassRelationship = this.getModelClass().getRelationshipByName(relationshipName)
|
|
230
335
|
const relationshipType = modelClassRelationship.getType()
|
|
231
336
|
let instanceRelationship
|
|
232
337
|
|
|
@@ -249,15 +354,14 @@ class VelociousDatabaseRecord {
|
|
|
249
354
|
/**
|
|
250
355
|
* Adds a belongs-to-relationship to the model.
|
|
251
356
|
* @param {string} relationshipName The name of the relationship.
|
|
252
|
-
* @param {object} options The options for the relationship.
|
|
357
|
+
* @param {object} [options] The options for the relationship.
|
|
253
358
|
*/
|
|
254
359
|
static belongsTo(relationshipName, options) {
|
|
255
360
|
this._defineRelationship(relationshipName, Object.assign({type: "belongsTo"}, options))
|
|
256
361
|
}
|
|
257
362
|
|
|
258
363
|
/**
|
|
259
|
-
* @
|
|
260
|
-
* @returns {T}
|
|
364
|
+
* @returns {import("../drivers/base.js").default}
|
|
261
365
|
*/
|
|
262
366
|
static connection() {
|
|
263
367
|
const databasePool = this._getConfiguration().getDatabasePool(this.getDatabaseIdentifier())
|
|
@@ -299,7 +403,7 @@ class VelociousDatabaseRecord {
|
|
|
299
403
|
* @returns {import("../../configuration.js").default}
|
|
300
404
|
*/
|
|
301
405
|
_getConfiguration() {
|
|
302
|
-
return this.
|
|
406
|
+
return this.getModelClass()._getConfiguration()
|
|
303
407
|
}
|
|
304
408
|
|
|
305
409
|
/**
|
|
@@ -335,12 +439,16 @@ class VelociousDatabaseRecord {
|
|
|
335
439
|
/**
|
|
336
440
|
* @returns {string}
|
|
337
441
|
*/
|
|
338
|
-
static getDatabaseType() {
|
|
442
|
+
static getDatabaseType() {
|
|
443
|
+
if (!this._databaseType) throw new Error("Database type hasn't been set")
|
|
444
|
+
|
|
445
|
+
return this._databaseType
|
|
446
|
+
}
|
|
339
447
|
|
|
340
448
|
/**
|
|
341
449
|
* @param {object} args
|
|
342
|
-
* @param {import("
|
|
343
|
-
* @returns {void}
|
|
450
|
+
* @param {import("../../configuration.js").default} args.configuration
|
|
451
|
+
* @returns {Promise<void>}
|
|
344
452
|
*/
|
|
345
453
|
static async initializeRecord({configuration, ...restArgs}) {
|
|
346
454
|
restArgsError(restArgs)
|
|
@@ -353,9 +461,15 @@ class VelociousDatabaseRecord {
|
|
|
353
461
|
|
|
354
462
|
this._table = await this.connection().getTableByName(this.tableName())
|
|
355
463
|
this._columns = await this._getTable().getColumns()
|
|
464
|
+
|
|
465
|
+
/** @type {Record<string, import("../drivers/base-column.js").default>} */
|
|
356
466
|
this._columnsAsHash = {}
|
|
357
|
-
|
|
358
|
-
|
|
467
|
+
|
|
468
|
+
const columnNameToAttributeName = this.getColumnNameToAttributeNameMap()
|
|
469
|
+
const attributeNameToColumnName = this.getAttributeNameToColumnNameMap()
|
|
470
|
+
|
|
471
|
+
/** @type {Record<string, (this: VelociousDatabaseRecord) => unknown>} */
|
|
472
|
+
const proto = /** @type {any} */ (this.prototype);
|
|
359
473
|
|
|
360
474
|
for (const column of this._columns) {
|
|
361
475
|
this._columnsAsHash[column.getName()] = column
|
|
@@ -363,18 +477,21 @@ class VelociousDatabaseRecord {
|
|
|
363
477
|
const camelizedColumnName = inflection.camelize(column.getName(), true)
|
|
364
478
|
const camelizedColumnNameBigFirst = inflection.camelize(column.getName())
|
|
365
479
|
|
|
366
|
-
|
|
367
|
-
|
|
480
|
+
attributeNameToColumnName[camelizedColumnName] = column.getName()
|
|
481
|
+
columnNameToAttributeName[column.getName()] = camelizedColumnName
|
|
368
482
|
|
|
369
|
-
|
|
483
|
+
proto[camelizedColumnName] = function() {
|
|
370
484
|
return this.readAttribute(camelizedColumnName)
|
|
371
485
|
}
|
|
372
486
|
|
|
373
|
-
|
|
487
|
+
// @ts-expect-error
|
|
488
|
+
proto[`set${camelizedColumnNameBigFirst}`] = function(newValue) {
|
|
489
|
+
// @ts-expect-error
|
|
374
490
|
return this._setColumnAttribute(camelizedColumnName, newValue)
|
|
375
491
|
}
|
|
376
492
|
|
|
377
|
-
|
|
493
|
+
proto[`has${camelizedColumnNameBigFirst}`] = function() {
|
|
494
|
+
// @ts-expect-error
|
|
378
495
|
let value = this[camelizedColumnName]()
|
|
379
496
|
|
|
380
497
|
return this._hasAttribute(value)
|
|
@@ -386,6 +503,7 @@ class VelociousDatabaseRecord {
|
|
|
386
503
|
}
|
|
387
504
|
|
|
388
505
|
/**
|
|
506
|
+
* @param {any} value
|
|
389
507
|
* @returns {boolean}
|
|
390
508
|
*/
|
|
391
509
|
_hasAttribute(value) {
|
|
@@ -421,21 +539,37 @@ class VelociousDatabaseRecord {
|
|
|
421
539
|
const nameCamelized = inflection.camelize(name)
|
|
422
540
|
const setterMethodName = `set${nameCamelized}`
|
|
423
541
|
|
|
424
|
-
|
|
542
|
+
/** @type {Record<string, unknown>} */
|
|
543
|
+
// @ts-expect-error
|
|
544
|
+
const self = this
|
|
545
|
+
|
|
546
|
+
/** @type {Record<string, (this: VelociousDatabaseRecord) => unknown>} */
|
|
547
|
+
const proto = /** @type {any} */ (this.prototype);
|
|
548
|
+
|
|
549
|
+
proto[name] = function getTranslatedAttribute() {
|
|
425
550
|
const locale = this._getConfiguration().getLocale()
|
|
426
551
|
|
|
427
552
|
return this._getTranslatedAttributeWithFallback(name, locale)
|
|
428
553
|
}
|
|
429
554
|
|
|
430
|
-
|
|
431
|
-
const
|
|
555
|
+
proto[`has${nameCamelized}`] = function hasTranslatedAttribute() {
|
|
556
|
+
const candidate = self[name]
|
|
432
557
|
|
|
433
|
-
|
|
558
|
+
if (typeof candidate == "function") {
|
|
559
|
+
const value = candidate()
|
|
560
|
+
|
|
561
|
+
return this._hasAttribute(value)
|
|
562
|
+
} else {
|
|
563
|
+
throw new Error(`Expected candidate to be a function but it was: ${typeof candidate}`)
|
|
564
|
+
}
|
|
434
565
|
}
|
|
435
566
|
|
|
436
|
-
|
|
567
|
+
// @ts-expect-error
|
|
568
|
+
proto[setterMethodName] = function setTranslatedAttribute(newValue) {
|
|
569
|
+
// @ts-expect-error
|
|
437
570
|
const locale = this._getConfiguration().getLocale()
|
|
438
571
|
|
|
572
|
+
// @ts-expect-error
|
|
439
573
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
440
574
|
}
|
|
441
575
|
|
|
@@ -444,10 +578,12 @@ class VelociousDatabaseRecord {
|
|
|
444
578
|
const getterMethodNameLocalized = `${name}${localeCamelized}`
|
|
445
579
|
const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
|
|
446
580
|
|
|
581
|
+
// @ts-expect-error
|
|
447
582
|
this.prototype[getterMethodNameLocalized] = function getTranslatedAttributeWithLocale() {
|
|
448
583
|
return this._getTranslatedAttribute(name, locale)
|
|
449
584
|
}
|
|
450
585
|
|
|
586
|
+
// @ts-expect-error
|
|
451
587
|
this.prototype[setterMethodNameLocalized] = function setTranslatedAttributeWithLocale(newValue) {
|
|
452
588
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
453
589
|
}
|
|
@@ -485,6 +621,16 @@ class VelociousDatabaseRecord {
|
|
|
485
621
|
return this._attributes[columnName]
|
|
486
622
|
}
|
|
487
623
|
|
|
624
|
+
/**
|
|
625
|
+
*
|
|
626
|
+
* @returns {typeof VelociousDatabaseRecord}
|
|
627
|
+
*/
|
|
628
|
+
getModelClass() {
|
|
629
|
+
const modelClass = /** @type {typeof VelociousDatabaseRecord} */ (this.constructor)
|
|
630
|
+
|
|
631
|
+
return modelClass
|
|
632
|
+
}
|
|
633
|
+
|
|
488
634
|
/**
|
|
489
635
|
* @param {string} name
|
|
490
636
|
* @param {*} newValue
|
|
@@ -493,16 +639,21 @@ class VelociousDatabaseRecord {
|
|
|
493
639
|
setAttribute(name, newValue) {
|
|
494
640
|
const setterName = `set${inflection.camelize(name)}`
|
|
495
641
|
|
|
496
|
-
if (!this.
|
|
642
|
+
if (!this.getModelClass().isInitialized()) throw new Error(`${this.constructor.name} model isn't initialized yet`)
|
|
497
643
|
if (!(setterName in this)) throw new Error(`No such setter method: ${this.constructor.name}#${setterName}`)
|
|
498
644
|
|
|
645
|
+
// @ts-expect-error
|
|
499
646
|
this[setterName](newValue)
|
|
500
647
|
}
|
|
501
648
|
|
|
649
|
+
/**
|
|
650
|
+
* @param {string} name
|
|
651
|
+
* @param {any} newValue
|
|
652
|
+
*/
|
|
502
653
|
_setColumnAttribute(name, newValue) {
|
|
503
|
-
if (!this.
|
|
654
|
+
if (!this.getModelClass()._attributeNameToColumnName) throw new Error("No attribute-to-column mapping. Has record been initialized?")
|
|
504
655
|
|
|
505
|
-
const columnName = this.
|
|
656
|
+
const columnName = this.getModelClass().getAttributeNameToColumnNameMap()[name]
|
|
506
657
|
|
|
507
658
|
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${name}`)
|
|
508
659
|
|
|
@@ -543,14 +694,14 @@ class VelociousDatabaseRecord {
|
|
|
543
694
|
/**
|
|
544
695
|
* @param {Array<string>} columns
|
|
545
696
|
* @param {Array<Array<string>>} rows
|
|
546
|
-
* @returns {void}
|
|
697
|
+
* @returns {Promise<void>}
|
|
547
698
|
*/
|
|
548
699
|
static async insertMultiple(columns, rows) {
|
|
549
700
|
return await this.connection().insertMultiple(this.tableName(), columns, rows)
|
|
550
701
|
}
|
|
551
702
|
|
|
552
703
|
/**
|
|
553
|
-
* @returns {number}
|
|
704
|
+
* @returns {Promise<number>}
|
|
554
705
|
*/
|
|
555
706
|
static async nextPrimaryKey() {
|
|
556
707
|
const primaryKey = this.primaryKey()
|
|
@@ -559,7 +710,13 @@ class VelociousDatabaseRecord {
|
|
|
559
710
|
const newestRecord = await this.order(`${connection.quoteTable(tableName)}.${connection.quoteColumn(primaryKey)}`).last()
|
|
560
711
|
|
|
561
712
|
if (newestRecord) {
|
|
562
|
-
|
|
713
|
+
const id = newestRecord.id()
|
|
714
|
+
|
|
715
|
+
if (typeof id == "number") {
|
|
716
|
+
return id + 1
|
|
717
|
+
} else {
|
|
718
|
+
throw new Error("ID from newest record wasn't a number")
|
|
719
|
+
}
|
|
563
720
|
} else {
|
|
564
721
|
return 1
|
|
565
722
|
}
|
|
@@ -583,16 +740,16 @@ class VelociousDatabaseRecord {
|
|
|
583
740
|
}
|
|
584
741
|
|
|
585
742
|
/**
|
|
586
|
-
* @returns {
|
|
743
|
+
* @returns {Promise<void>}
|
|
587
744
|
*/
|
|
588
745
|
async save() {
|
|
589
746
|
const isNewRecord = this.isNewRecord()
|
|
590
747
|
let result
|
|
591
748
|
|
|
592
|
-
await this.
|
|
749
|
+
await this._getConfiguration().ensureConnections(async () => {
|
|
593
750
|
await this._runValidations()
|
|
594
751
|
|
|
595
|
-
await this.
|
|
752
|
+
await this.getModelClass().transaction(async () => {
|
|
596
753
|
// If any belongs-to-relationships was saved, then updated-at should still be set on this record.
|
|
597
754
|
const {savedCount} = await this._autoSaveBelongsToRelationships()
|
|
598
755
|
|
|
@@ -630,17 +787,23 @@ class VelociousDatabaseRecord {
|
|
|
630
787
|
|
|
631
788
|
const model = instanceRelationship.loaded()
|
|
632
789
|
|
|
633
|
-
if (model
|
|
634
|
-
|
|
790
|
+
if (model) {
|
|
791
|
+
if (model instanceof VelociousDatabaseRecord) {
|
|
792
|
+
if (model.isChanged()) {
|
|
793
|
+
await model.save()
|
|
635
794
|
|
|
636
|
-
|
|
795
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
637
796
|
|
|
638
|
-
|
|
797
|
+
this.setAttribute(foreignKey, model.id())
|
|
639
798
|
|
|
640
|
-
|
|
641
|
-
|
|
799
|
+
instanceRelationship.setPreloaded(true)
|
|
800
|
+
instanceRelationship.setDirty(false)
|
|
642
801
|
|
|
643
|
-
|
|
802
|
+
savedCount++
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
throw new Error(`Expected a record but got: ${typeof model}`)
|
|
806
|
+
}
|
|
644
807
|
}
|
|
645
808
|
}
|
|
646
809
|
|
|
@@ -661,18 +824,21 @@ class VelociousDatabaseRecord {
|
|
|
661
824
|
continue
|
|
662
825
|
}
|
|
663
826
|
|
|
827
|
+
/** @type {VelociousDatabaseRecord[]} */
|
|
664
828
|
let loaded
|
|
665
829
|
|
|
666
|
-
|
|
667
|
-
const hasOneLoaded = instanceRelationship.getLoadedOrNull()
|
|
830
|
+
const hasManyOrOneLoaded = instanceRelationship.getLoadedOrUndefined()
|
|
668
831
|
|
|
669
|
-
|
|
670
|
-
|
|
832
|
+
if (hasManyOrOneLoaded) {
|
|
833
|
+
if (Array.isArray(hasManyOrOneLoaded)) {
|
|
834
|
+
loaded = hasManyOrOneLoaded
|
|
835
|
+
} else if (hasManyOrOneLoaded instanceof VelociousDatabaseRecord) {
|
|
836
|
+
loaded = [hasManyOrOneLoaded]
|
|
671
837
|
} else {
|
|
672
|
-
|
|
838
|
+
throw new Error(`Expected hasOneLoaded to be a record but it wasn't: ${typeof hasManyOrOneLoaded}`)
|
|
673
839
|
}
|
|
674
840
|
} else {
|
|
675
|
-
|
|
841
|
+
continue
|
|
676
842
|
}
|
|
677
843
|
|
|
678
844
|
let useRelationship = false
|
|
@@ -696,11 +862,26 @@ class VelociousDatabaseRecord {
|
|
|
696
862
|
return relationships
|
|
697
863
|
}
|
|
698
864
|
|
|
865
|
+
/**
|
|
866
|
+
* @param {object} args
|
|
867
|
+
* @param {boolean} args.isNewRecord
|
|
868
|
+
*/
|
|
699
869
|
async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
|
|
700
870
|
for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
|
|
701
|
-
let
|
|
871
|
+
let hasManyOrOneLoaded = instanceRelationship.getLoadedOrUndefined()
|
|
702
872
|
|
|
703
|
-
|
|
873
|
+
/** @type {VelociousDatabaseRecord[]} */
|
|
874
|
+
let loaded
|
|
875
|
+
|
|
876
|
+
if (hasManyOrOneLoaded === undefined) {
|
|
877
|
+
loaded = []
|
|
878
|
+
} else if (hasManyOrOneLoaded instanceof VelociousDatabaseRecord) {
|
|
879
|
+
loaded = [hasManyOrOneLoaded]
|
|
880
|
+
} else if (Array.isArray(hasManyOrOneLoaded)) {
|
|
881
|
+
loaded = hasManyOrOneLoaded
|
|
882
|
+
} else {
|
|
883
|
+
throw new Error(`Unexpected type for hasManyOrOneLoaded: ${typeof hasManyOrOneLoaded}`)
|
|
884
|
+
}
|
|
704
885
|
|
|
705
886
|
for (const model of loaded) {
|
|
706
887
|
const foreignKey = instanceRelationship.getForeignKey()
|
|
@@ -736,8 +917,8 @@ class VelociousDatabaseRecord {
|
|
|
736
917
|
}
|
|
737
918
|
|
|
738
919
|
/**
|
|
739
|
-
* @param {function() : void} callback
|
|
740
|
-
* @returns {
|
|
920
|
+
* @param {function() : Promise<void>} callback
|
|
921
|
+
* @returns {Promise<*>}
|
|
741
922
|
*/
|
|
742
923
|
static async transaction(callback) {
|
|
743
924
|
const useTransactions = this.connection().getArgs().record?.transactions
|
|
@@ -750,14 +931,16 @@ class VelociousDatabaseRecord {
|
|
|
750
931
|
}
|
|
751
932
|
|
|
752
933
|
/**
|
|
934
|
+
* @param {...string} names
|
|
753
935
|
* @returns {void}
|
|
754
936
|
*/
|
|
755
937
|
static translates(...names) {
|
|
938
|
+
const translations = this.getTranslationsMap()
|
|
939
|
+
|
|
756
940
|
for (const name of names) {
|
|
757
|
-
if (
|
|
758
|
-
if (name in this._translations) throw new Error(`Translation already exists: ${name}`)
|
|
941
|
+
if (name in translations) throw new Error(`Translation already exists: ${name}`)
|
|
759
942
|
|
|
760
|
-
|
|
943
|
+
translations[name] = {}
|
|
761
944
|
|
|
762
945
|
if (!this._relationshipExists("translations")) {
|
|
763
946
|
this._defineRelationship("translations", {klass: this.getTranslationClass(), type: "hasMany"})
|
|
@@ -812,11 +995,33 @@ class VelociousDatabaseRecord {
|
|
|
812
995
|
/**
|
|
813
996
|
* Adds a validation to an attribute.
|
|
814
997
|
* @param {string} attributeName The name of the attribute to validate.
|
|
815
|
-
* @param {
|
|
998
|
+
* @param {Record<string, boolean | Record<string, any>>} validators The validators to add. Key is the validator name, value is the validator arguments.
|
|
816
999
|
*/
|
|
817
1000
|
static async validates(attributeName, validators) {
|
|
818
1001
|
for (const validatorName in validators) {
|
|
819
|
-
|
|
1002
|
+
/** @type {Record<string, any>} */
|
|
1003
|
+
let validatorArgs
|
|
1004
|
+
|
|
1005
|
+
/** @type {boolean} */
|
|
1006
|
+
let useValidator = true
|
|
1007
|
+
|
|
1008
|
+
const validatorArgsCandidate = validators[validatorName]
|
|
1009
|
+
|
|
1010
|
+
if (typeof validatorArgsCandidate == "boolean") {
|
|
1011
|
+
validatorArgs = {}
|
|
1012
|
+
useValidator
|
|
1013
|
+
|
|
1014
|
+
if (!validatorArgsCandidate) {
|
|
1015
|
+
useValidator = false
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
validatorArgs = validatorArgsCandidate
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (!useValidator) {
|
|
1022
|
+
continue
|
|
1023
|
+
}
|
|
1024
|
+
|
|
820
1025
|
const ValidatorClass = this.getValidatorType(validatorName)
|
|
821
1026
|
const validator = new ValidatorClass({attributeName, args: validatorArgs})
|
|
822
1027
|
|
|
@@ -827,16 +1032,34 @@ class VelociousDatabaseRecord {
|
|
|
827
1032
|
}
|
|
828
1033
|
}
|
|
829
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* @abstract
|
|
1037
|
+
* @returns {TranslationBase[]}
|
|
1038
|
+
*/
|
|
1039
|
+
translationsLoaded() {
|
|
1040
|
+
throw new Error("'translationsLoaded' not implemented")
|
|
1041
|
+
}
|
|
1042
|
+
|
|
830
1043
|
/**
|
|
831
1044
|
* @param {string} name
|
|
832
1045
|
* @param {string} locale
|
|
833
1046
|
* @returns {*}
|
|
834
1047
|
*/
|
|
835
1048
|
_getTranslatedAttribute(name, locale) {
|
|
836
|
-
const translation = this.
|
|
1049
|
+
const translation = this.translationsLoaded().find((translation) => translation.locale() == locale)
|
|
837
1050
|
|
|
838
1051
|
if (translation) {
|
|
839
|
-
|
|
1052
|
+
/** @type {Record<string, unknown>} */
|
|
1053
|
+
// @ts-expect-error
|
|
1054
|
+
const dict = translation
|
|
1055
|
+
|
|
1056
|
+
const attributeMethod = /** @type {function() : any | undefined} */ (dict[name])
|
|
1057
|
+
|
|
1058
|
+
if (typeof attributeMethod == "function") {
|
|
1059
|
+
return attributeMethod.bind(translation)()
|
|
1060
|
+
} else {
|
|
1061
|
+
throw new Error(`No such translated method: ${name} (${typeof attributeMethod})`)
|
|
1062
|
+
}
|
|
840
1063
|
}
|
|
841
1064
|
}
|
|
842
1065
|
|
|
@@ -871,12 +1094,18 @@ class VelociousDatabaseRecord {
|
|
|
871
1094
|
* @returns {void}
|
|
872
1095
|
*/
|
|
873
1096
|
_setTranslatedAttribute(name, locale, newValue) {
|
|
874
|
-
|
|
1097
|
+
/** @type {VelociousDatabaseRecord | TranslationBase | undefined} */
|
|
1098
|
+
let translation
|
|
1099
|
+
|
|
1100
|
+
translation = this.translationsLoaded()?.find((translation) => translation.locale() == locale)
|
|
875
1101
|
|
|
876
1102
|
if (!translation) {
|
|
877
|
-
|
|
1103
|
+
const instanceRelationship = this.getRelationshipByName("translations")
|
|
1104
|
+
|
|
1105
|
+
translation = instanceRelationship.build({locale})
|
|
878
1106
|
}
|
|
879
1107
|
|
|
1108
|
+
/** @type {Record<string, any>} */
|
|
880
1109
|
const assignments = {}
|
|
881
1110
|
|
|
882
1111
|
assignments[name] = newValue
|
|
@@ -915,54 +1144,56 @@ class VelociousDatabaseRecord {
|
|
|
915
1144
|
}
|
|
916
1145
|
|
|
917
1146
|
/**
|
|
918
|
-
* @returns {number}
|
|
1147
|
+
* @returns {Promise<number>}
|
|
919
1148
|
*/
|
|
920
1149
|
static async count() {
|
|
921
1150
|
return await this._newQuery().count()
|
|
922
1151
|
}
|
|
923
1152
|
|
|
924
|
-
static async destroyAll(
|
|
925
|
-
return await this._newQuery().destroyAll(
|
|
1153
|
+
static async destroyAll() {
|
|
1154
|
+
return await this._newQuery().destroyAll()
|
|
926
1155
|
}
|
|
927
1156
|
|
|
928
1157
|
/**
|
|
929
|
-
* @param {
|
|
1158
|
+
* @param {number|string} recordId
|
|
930
1159
|
* @returns {Promise<InstanceType<typeof this>>}
|
|
931
1160
|
*/
|
|
932
|
-
static async find(
|
|
933
|
-
return await this._newQuery().find(
|
|
1161
|
+
static async find(recordId) {
|
|
1162
|
+
return await this._newQuery().find(recordId)
|
|
934
1163
|
}
|
|
935
1164
|
|
|
936
1165
|
/**
|
|
937
|
-
* @param {
|
|
938
|
-
* @returns {Promise<InstanceType<typeof this
|
|
1166
|
+
* @param {{[key: string]: any}} conditions
|
|
1167
|
+
* @returns {Promise<InstanceType<typeof this> | null>}
|
|
939
1168
|
*/
|
|
940
|
-
static async findBy(
|
|
941
|
-
return await this._newQuery().findBy(
|
|
1169
|
+
static async findBy(conditions) {
|
|
1170
|
+
return await this._newQuery().findBy(conditions)
|
|
942
1171
|
}
|
|
943
1172
|
|
|
944
1173
|
/**
|
|
945
|
-
* @param {
|
|
1174
|
+
* @param {{[key: string]: any}} conditions
|
|
946
1175
|
* @returns {Promise<InstanceType<typeof this>>}
|
|
947
1176
|
*/
|
|
948
|
-
static async findByOrFail(
|
|
949
|
-
return await this._newQuery().findByOrFail(
|
|
1177
|
+
static async findByOrFail(conditions) {
|
|
1178
|
+
return await this._newQuery().findByOrFail(conditions)
|
|
950
1179
|
}
|
|
951
1180
|
|
|
952
1181
|
/**
|
|
953
|
-
* @param {
|
|
1182
|
+
* @param {{[key: string]: any}} conditions
|
|
1183
|
+
* @param {function() : void} callback
|
|
954
1184
|
* @returns {Promise<InstanceType<typeof this>>}
|
|
955
1185
|
*/
|
|
956
|
-
static async findOrCreateBy(
|
|
957
|
-
return await this._newQuery().findOrCreateBy(
|
|
1186
|
+
static async findOrCreateBy(conditions, callback) {
|
|
1187
|
+
return await this._newQuery().findOrCreateBy(conditions, callback)
|
|
958
1188
|
}
|
|
959
1189
|
|
|
960
1190
|
/**
|
|
961
|
-
* @param {
|
|
1191
|
+
* @param {object} conditions
|
|
1192
|
+
* @param {function(import("../record/index.js").default) : void} callback
|
|
962
1193
|
* @returns {Promise<InstanceType<typeof this>>}
|
|
963
1194
|
*/
|
|
964
|
-
static async findOrInitializeBy(
|
|
965
|
-
return await this._newQuery().findOrInitializeBy(
|
|
1195
|
+
static async findOrInitializeBy(conditions, callback) {
|
|
1196
|
+
return await this._newQuery().findOrInitializeBy(conditions, callback)
|
|
966
1197
|
}
|
|
967
1198
|
|
|
968
1199
|
/**
|
|
@@ -973,69 +1204,74 @@ class VelociousDatabaseRecord {
|
|
|
973
1204
|
}
|
|
974
1205
|
|
|
975
1206
|
/**
|
|
1207
|
+
* @param {string|{[key: string]: any}} join
|
|
976
1208
|
* @returns {Query}
|
|
977
1209
|
*/
|
|
978
|
-
static joins(
|
|
979
|
-
return this._newQuery().joins(
|
|
1210
|
+
static joins(join) {
|
|
1211
|
+
return this._newQuery().joins(join)
|
|
980
1212
|
}
|
|
981
1213
|
|
|
982
1214
|
/**
|
|
983
1215
|
* @returns {Promise<InstanceType<typeof this>>}
|
|
984
1216
|
*/
|
|
985
|
-
static async last(
|
|
986
|
-
return await this._newQuery().last(
|
|
1217
|
+
static async last() {
|
|
1218
|
+
return await this._newQuery().last()
|
|
987
1219
|
}
|
|
988
1220
|
|
|
989
1221
|
/**
|
|
1222
|
+
* @param {number} value
|
|
990
1223
|
* @returns {Query}
|
|
991
1224
|
*/
|
|
992
|
-
static limit(
|
|
993
|
-
return this._newQuery().limit(
|
|
1225
|
+
static limit(value) {
|
|
1226
|
+
return this._newQuery().limit(value)
|
|
994
1227
|
}
|
|
995
1228
|
|
|
996
1229
|
/**
|
|
1230
|
+
* @param {string | number} order
|
|
997
1231
|
* @returns {Query}
|
|
998
1232
|
*/
|
|
999
|
-
static order(
|
|
1000
|
-
return this._newQuery().order(
|
|
1233
|
+
static order(order) {
|
|
1234
|
+
return this._newQuery().order(order)
|
|
1001
1235
|
}
|
|
1002
1236
|
|
|
1003
1237
|
/**
|
|
1238
|
+
* @param {import("../query/index.js").NestedPreloadRecord} preload
|
|
1004
1239
|
* @returns {Query}
|
|
1005
1240
|
*/
|
|
1006
|
-
static preload(
|
|
1007
|
-
return this._newQuery().preload(
|
|
1241
|
+
static preload(preload) {
|
|
1242
|
+
return this._newQuery().preload(preload)
|
|
1008
1243
|
}
|
|
1009
1244
|
|
|
1010
1245
|
/**
|
|
1246
|
+
* @param {import("../query/index.js").SelectArgumentType} select
|
|
1011
1247
|
* @returns {Query}
|
|
1012
1248
|
*/
|
|
1013
|
-
static select(
|
|
1014
|
-
return this._newQuery().select(
|
|
1249
|
+
static select(select) {
|
|
1250
|
+
return this._newQuery().select(select)
|
|
1015
1251
|
}
|
|
1016
1252
|
|
|
1017
1253
|
/**
|
|
1018
|
-
* @returns {
|
|
1254
|
+
* @returns {Promise<VelociousDatabaseRecord[]>}
|
|
1019
1255
|
*/
|
|
1020
|
-
static toArray(
|
|
1021
|
-
return this._newQuery().toArray(
|
|
1256
|
+
static toArray() {
|
|
1257
|
+
return this._newQuery().toArray()
|
|
1022
1258
|
}
|
|
1023
1259
|
|
|
1024
1260
|
/**
|
|
1261
|
+
* @param {import("../query/index.js").WhereArgumentType} where
|
|
1025
1262
|
* @returns {Query}
|
|
1026
1263
|
*/
|
|
1027
|
-
static where(
|
|
1028
|
-
return this._newQuery().where(
|
|
1264
|
+
static where(where) {
|
|
1265
|
+
return this._newQuery().where(where)
|
|
1029
1266
|
}
|
|
1030
1267
|
|
|
1031
1268
|
/**
|
|
1032
|
-
* @param {
|
|
1269
|
+
* @param {Record<string, any>} changes
|
|
1033
1270
|
*/
|
|
1034
1271
|
constructor(changes = {}) {
|
|
1035
1272
|
this._attributes = {}
|
|
1036
1273
|
this._changes = {}
|
|
1037
1274
|
this._isNewRecord = true
|
|
1038
|
-
this._relationships = {}
|
|
1039
1275
|
|
|
1040
1276
|
for (const key in changes) {
|
|
1041
1277
|
this.setAttribute(key, changes[key])
|
|
@@ -1064,7 +1300,7 @@ class VelociousDatabaseRecord {
|
|
|
1064
1300
|
|
|
1065
1301
|
/**
|
|
1066
1302
|
* Returns a the current attributes of the record (original attributes from database plus changes)
|
|
1067
|
-
* @returns {
|
|
1303
|
+
* @returns {Record<string, any>}
|
|
1068
1304
|
*/
|
|
1069
1305
|
attributes() {
|
|
1070
1306
|
return Object.assign({}, this._attributes, this._changes)
|
|
@@ -1076,20 +1312,22 @@ class VelociousDatabaseRecord {
|
|
|
1076
1312
|
_connection() {
|
|
1077
1313
|
if (this.__connection) return this.__connection
|
|
1078
1314
|
|
|
1079
|
-
return this.
|
|
1315
|
+
return this.getModelClass().connection()
|
|
1080
1316
|
}
|
|
1081
1317
|
|
|
1082
1318
|
/**
|
|
1083
1319
|
* Destroys the record in the database and all of its dependent records.
|
|
1084
|
-
* @returns {void}
|
|
1320
|
+
* @returns {Promise<void>}
|
|
1085
1321
|
*/
|
|
1086
1322
|
async destroy() {
|
|
1087
|
-
for (const relationship of this.
|
|
1323
|
+
for (const relationship of this.getModelClass().getRelationships()) {
|
|
1088
1324
|
if (relationship.getDependent() != "destroy") {
|
|
1089
1325
|
continue
|
|
1090
1326
|
}
|
|
1091
1327
|
|
|
1092
1328
|
const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
|
|
1329
|
+
|
|
1330
|
+
/** @type {VelociousDatabaseRecord[]} */
|
|
1093
1331
|
let models
|
|
1094
1332
|
|
|
1095
1333
|
if (instanceRelationship.getType() == "belongsTo") {
|
|
@@ -1099,13 +1337,23 @@ class VelociousDatabaseRecord {
|
|
|
1099
1337
|
|
|
1100
1338
|
const model = instanceRelationship.loaded()
|
|
1101
1339
|
|
|
1102
|
-
|
|
1340
|
+
if (model instanceof VelociousDatabaseRecord) {
|
|
1341
|
+
models = [model]
|
|
1342
|
+
} else {
|
|
1343
|
+
throw new Error(`Unexpected loaded type: ${typeof model}`)
|
|
1344
|
+
}
|
|
1103
1345
|
} else if (instanceRelationship.getType() == "hasMany") {
|
|
1104
1346
|
if (!instanceRelationship.isLoaded()) {
|
|
1105
1347
|
await instanceRelationship.load()
|
|
1106
1348
|
}
|
|
1107
1349
|
|
|
1108
|
-
|
|
1350
|
+
const loadedModels = instanceRelationship.loaded()
|
|
1351
|
+
|
|
1352
|
+
if (Array.isArray(loadedModels)) {
|
|
1353
|
+
models = loadedModels
|
|
1354
|
+
} else {
|
|
1355
|
+
throw new Error(`Unexpected loaded type: ${typeof loadedModels}`)
|
|
1356
|
+
}
|
|
1109
1357
|
} else {
|
|
1110
1358
|
throw new Error(`Unhandled relationship type: ${instanceRelationship.getType()}`)
|
|
1111
1359
|
}
|
|
@@ -1117,9 +1365,10 @@ class VelociousDatabaseRecord {
|
|
|
1117
1365
|
}
|
|
1118
1366
|
}
|
|
1119
1367
|
|
|
1368
|
+
/** @type {Record<string, any>} */
|
|
1120
1369
|
const conditions = {}
|
|
1121
1370
|
|
|
1122
|
-
conditions[this.
|
|
1371
|
+
conditions[this.getModelClass().primaryKey()] = this.id()
|
|
1123
1372
|
|
|
1124
1373
|
const sql = this._connection().deleteSql({
|
|
1125
1374
|
conditions,
|
|
@@ -1129,9 +1378,7 @@ class VelociousDatabaseRecord {
|
|
|
1129
1378
|
await this._connection().query(sql)
|
|
1130
1379
|
}
|
|
1131
1380
|
|
|
1132
|
-
/**
|
|
1133
|
-
* @returns {boolean}
|
|
1134
|
-
*/
|
|
1381
|
+
/** @returns {boolean} */
|
|
1135
1382
|
_hasChanges() { return Object.keys(this._changes).length > 0 }
|
|
1136
1383
|
|
|
1137
1384
|
/**
|
|
@@ -1167,11 +1414,9 @@ class VelociousDatabaseRecord {
|
|
|
1167
1414
|
return false
|
|
1168
1415
|
}
|
|
1169
1416
|
|
|
1170
|
-
/**
|
|
1171
|
-
* Returns the changes that have been made to this record since it was loaded from the database.
|
|
1172
|
-
* @returns {object}
|
|
1173
|
-
*/
|
|
1417
|
+
/** Returns the changes that have been made to this record since it was loaded from the database. */
|
|
1174
1418
|
changes() {
|
|
1419
|
+
/** @type {Record<string, any[]>} */
|
|
1175
1420
|
const changes = {}
|
|
1176
1421
|
|
|
1177
1422
|
for (const changeKey in this._changes) {
|
|
@@ -1189,18 +1434,18 @@ class VelociousDatabaseRecord {
|
|
|
1189
1434
|
_tableName() {
|
|
1190
1435
|
if (this.__tableName) return this.__tableName
|
|
1191
1436
|
|
|
1192
|
-
return this.
|
|
1437
|
+
return this.getModelClass().tableName()
|
|
1193
1438
|
}
|
|
1194
1439
|
|
|
1195
1440
|
/**
|
|
1196
1441
|
* Reads an attribute value from the record.
|
|
1197
1442
|
* @param {string} attributeName The name of the attribute to read. This is the attribute name, not the column name.
|
|
1198
|
-
* @returns {
|
|
1443
|
+
* @returns {any}
|
|
1199
1444
|
*/
|
|
1200
1445
|
readAttribute(attributeName) {
|
|
1201
|
-
const columnName = this.
|
|
1446
|
+
const columnName = this.getModelClass().getAttributeNameToColumnNameMap()[attributeName]
|
|
1202
1447
|
|
|
1203
|
-
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.
|
|
1448
|
+
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.getModelClass().getAttributeNameToColumnNameMap()).join(", ")}`)
|
|
1204
1449
|
|
|
1205
1450
|
return this.readColumn(columnName)
|
|
1206
1451
|
}
|
|
@@ -1210,7 +1455,7 @@ class VelociousDatabaseRecord {
|
|
|
1210
1455
|
* @param {string} attributeName The name of the column to read. This is the column name, not the attribute name.
|
|
1211
1456
|
*/
|
|
1212
1457
|
readColumn(attributeName) {
|
|
1213
|
-
const column = this.
|
|
1458
|
+
const column = this.getModelClass().getColumns().find((column) => column.getName() == attributeName)
|
|
1214
1459
|
let result
|
|
1215
1460
|
|
|
1216
1461
|
if (attributeName in this._changes) {
|
|
@@ -1221,7 +1466,7 @@ class VelociousDatabaseRecord {
|
|
|
1221
1466
|
throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
|
|
1222
1467
|
}
|
|
1223
1468
|
|
|
1224
|
-
if (column && this.
|
|
1469
|
+
if (column && this.getModelClass().getDatabaseType() == "sqlite") {
|
|
1225
1470
|
if (result && (column.getType() == "date" || column.getType() == "datetime")) {
|
|
1226
1471
|
result = new Date(Date.parse(result))
|
|
1227
1472
|
}
|
|
@@ -1231,6 +1476,7 @@ class VelociousDatabaseRecord {
|
|
|
1231
1476
|
}
|
|
1232
1477
|
|
|
1233
1478
|
_belongsToChanges() {
|
|
1479
|
+
/** @type {Record<string, any>} */
|
|
1234
1480
|
const belongsToChanges = {}
|
|
1235
1481
|
|
|
1236
1482
|
if (this._instanceRelationships) {
|
|
@@ -1238,7 +1484,15 @@ class VelociousDatabaseRecord {
|
|
|
1238
1484
|
const relationship = this._instanceRelationships[relationshipName]
|
|
1239
1485
|
|
|
1240
1486
|
if (relationship.getType() == "belongsTo" && relationship.getDirty()) {
|
|
1241
|
-
|
|
1487
|
+
const model = relationship.loaded()
|
|
1488
|
+
|
|
1489
|
+
if (model) {
|
|
1490
|
+
if (model instanceof VelociousDatabaseRecord) {
|
|
1491
|
+
belongsToChanges[relationship.getForeignKey()] = model?.id()
|
|
1492
|
+
} else {
|
|
1493
|
+
throw new Error(`Unexpected model type: ${typeof model}`)
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1242
1496
|
}
|
|
1243
1497
|
}
|
|
1244
1498
|
}
|
|
@@ -1247,29 +1501,29 @@ class VelociousDatabaseRecord {
|
|
|
1247
1501
|
}
|
|
1248
1502
|
|
|
1249
1503
|
/**
|
|
1250
|
-
* @returns {void}
|
|
1504
|
+
* @returns {Promise<void>}
|
|
1251
1505
|
*/
|
|
1252
1506
|
async _createNewRecord() {
|
|
1253
|
-
if (!this.
|
|
1254
|
-
throw new Error(`No insertSql on ${this.
|
|
1507
|
+
if (!this.getModelClass().connection()["insertSql"]) {
|
|
1508
|
+
throw new Error(`No insertSql on ${this.getModelClass().connection().constructor.name}`)
|
|
1255
1509
|
}
|
|
1256
1510
|
|
|
1257
|
-
const createdAtColumn = this.
|
|
1258
|
-
const updatedAtColumn = this.
|
|
1511
|
+
const createdAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "created_at")
|
|
1512
|
+
const updatedAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "updated_at")
|
|
1259
1513
|
const data = Object.assign({}, this._belongsToChanges(), this.attributes())
|
|
1260
1514
|
const currentDate = new Date()
|
|
1261
1515
|
|
|
1262
1516
|
if (createdAtColumn) data.created_at = currentDate
|
|
1263
1517
|
if (updatedAtColumn) data.updated_at = currentDate
|
|
1264
1518
|
|
|
1265
|
-
const columnNames = this.
|
|
1519
|
+
const columnNames = this.getModelClass().getColumnNames()
|
|
1266
1520
|
const sql = this._connection().insertSql({
|
|
1267
1521
|
returnLastInsertedColumnNames: columnNames,
|
|
1268
1522
|
tableName: this._tableName(),
|
|
1269
1523
|
data
|
|
1270
1524
|
})
|
|
1271
1525
|
const insertResult = await this._connection().query(sql)
|
|
1272
|
-
const primaryKey = this.
|
|
1526
|
+
const primaryKey = this.getModelClass().primaryKey()
|
|
1273
1527
|
|
|
1274
1528
|
if (Array.isArray(insertResult) && insertResult[0] && insertResult[0][primaryKey]) {
|
|
1275
1529
|
this._attributes = insertResult[0]
|
|
@@ -1283,10 +1537,10 @@ class VelociousDatabaseRecord {
|
|
|
1283
1537
|
this.setIsNewRecord(false)
|
|
1284
1538
|
|
|
1285
1539
|
// Mark all relationships as preloaded, since we don't expect anything to have magically appeared since we created the record.
|
|
1286
|
-
for (const relationship of this.
|
|
1540
|
+
for (const relationship of this.getModelClass().getRelationships()) {
|
|
1287
1541
|
const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
|
|
1288
1542
|
|
|
1289
|
-
if (instanceRelationship.getType() == "hasMany" && instanceRelationship.
|
|
1543
|
+
if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrUndefined() === null) {
|
|
1290
1544
|
instanceRelationship.setLoaded([])
|
|
1291
1545
|
}
|
|
1292
1546
|
|
|
@@ -1295,15 +1549,16 @@ class VelociousDatabaseRecord {
|
|
|
1295
1549
|
}
|
|
1296
1550
|
|
|
1297
1551
|
/**
|
|
1298
|
-
* @returns {void}
|
|
1552
|
+
* @returns {Promise<void>}
|
|
1299
1553
|
*/
|
|
1300
1554
|
async _updateRecordWithChanges() {
|
|
1555
|
+
/** @type {Record<string, any>} */
|
|
1301
1556
|
const conditions = {}
|
|
1302
1557
|
|
|
1303
|
-
conditions[this.
|
|
1558
|
+
conditions[this.getModelClass().primaryKey()] = this.id()
|
|
1304
1559
|
|
|
1305
1560
|
const changes = Object.assign({}, this._belongsToChanges(), this._changes)
|
|
1306
|
-
const updatedAtColumn = this.
|
|
1561
|
+
const updatedAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "updated_at")
|
|
1307
1562
|
const currentDate = new Date()
|
|
1308
1563
|
|
|
1309
1564
|
if (updatedAtColumn) changes.updated_at = currentDate
|
|
@@ -1323,15 +1578,15 @@ class VelociousDatabaseRecord {
|
|
|
1323
1578
|
* @returns {number|string}
|
|
1324
1579
|
*/
|
|
1325
1580
|
id() {
|
|
1326
|
-
if (!this.
|
|
1581
|
+
if (!this.getModelClass()._columnNameToAttributeName) {
|
|
1327
1582
|
throw new Error(`Column names mapping hasn't been set on ${this.constructor.name}. Has the model been initialized?`)
|
|
1328
1583
|
}
|
|
1329
1584
|
|
|
1330
|
-
const primaryKey = this.
|
|
1331
|
-
const attributeName = this.
|
|
1585
|
+
const primaryKey = this.getModelClass().primaryKey()
|
|
1586
|
+
const attributeName = this.getModelClass().getColumnNameToAttributeNameMap()[primaryKey]
|
|
1332
1587
|
|
|
1333
1588
|
if (attributeName === undefined) {
|
|
1334
|
-
throw new Error(`Primary key ${primaryKey} doesn't exist in columns: ${Object.keys(this.
|
|
1589
|
+
throw new Error(`Primary key ${primaryKey} doesn't exist in columns: ${Object.keys(this.getModelClass().getColumnNameToAttributeNameMap()).join(", ")}`)
|
|
1335
1590
|
}
|
|
1336
1591
|
|
|
1337
1592
|
return this.readAttribute(attributeName)
|
|
@@ -1355,13 +1610,18 @@ class VelociousDatabaseRecord {
|
|
|
1355
1610
|
this._isNewRecord = newIsNewRecord
|
|
1356
1611
|
}
|
|
1357
1612
|
|
|
1613
|
+
/**
|
|
1614
|
+
* @param {string | number} id
|
|
1615
|
+
*/
|
|
1358
1616
|
async _reloadWithId(id) {
|
|
1359
|
-
const primaryKey = this.
|
|
1617
|
+
const primaryKey = this.getModelClass().primaryKey()
|
|
1618
|
+
|
|
1619
|
+
/** @type {Record<string, any>} */
|
|
1360
1620
|
const whereObject = {}
|
|
1361
1621
|
|
|
1362
1622
|
whereObject[primaryKey] = id
|
|
1363
1623
|
|
|
1364
|
-
const query = this.
|
|
1624
|
+
const query = this.getModelClass().where(whereObject)
|
|
1365
1625
|
const reloadedModel = await query.first()
|
|
1366
1626
|
|
|
1367
1627
|
if (!reloadedModel) throw new Error(`${this.constructor.name}#${id} couldn't be reloaded - record didn't exist`)
|
|
@@ -1371,16 +1631,17 @@ class VelociousDatabaseRecord {
|
|
|
1371
1631
|
}
|
|
1372
1632
|
|
|
1373
1633
|
/**
|
|
1374
|
-
* @returns {void}
|
|
1634
|
+
* @returns {Promise<void>}
|
|
1375
1635
|
*/
|
|
1376
1636
|
async reload() {
|
|
1377
1637
|
this._reloadWithId(this.readAttribute("id"))
|
|
1378
1638
|
}
|
|
1379
1639
|
|
|
1380
1640
|
async _runValidations() {
|
|
1641
|
+
/** @type {Record<string, {type: string, message: string}>} */
|
|
1381
1642
|
this._validationErrors = {}
|
|
1382
1643
|
|
|
1383
|
-
const validators = this.
|
|
1644
|
+
const validators = this.getModelClass()._validators
|
|
1384
1645
|
|
|
1385
1646
|
if (validators) {
|
|
1386
1647
|
for (const attributeName in validators) {
|
|
@@ -1403,15 +1664,16 @@ class VelociousDatabaseRecord {
|
|
|
1403
1664
|
}
|
|
1404
1665
|
|
|
1405
1666
|
/**
|
|
1406
|
-
* @returns {
|
|
1667
|
+
* @returns {string[]}
|
|
1407
1668
|
*/
|
|
1408
1669
|
fullErrorMessages() {
|
|
1670
|
+
/** @type {string[]} */
|
|
1409
1671
|
const validationErrorMessages = []
|
|
1410
1672
|
|
|
1411
1673
|
if (this._validationErrors) {
|
|
1412
1674
|
for (const attributeName in this._validationErrors) {
|
|
1413
1675
|
for (const validationError of this._validationErrors[attributeName]) {
|
|
1414
|
-
const message = `${this.
|
|
1676
|
+
const message = `${this.getModelClass().humanAttributeName(attributeName)} ${validationError.message}`
|
|
1415
1677
|
|
|
1416
1678
|
validationErrorMessages.push(message)
|
|
1417
1679
|
}
|
|
@@ -1432,6 +1694,16 @@ class VelociousDatabaseRecord {
|
|
|
1432
1694
|
}
|
|
1433
1695
|
}
|
|
1434
1696
|
|
|
1697
|
+
class TranslationBase extends VelociousDatabaseRecord {
|
|
1698
|
+
/**
|
|
1699
|
+
* @abstract
|
|
1700
|
+
* @returns {string}
|
|
1701
|
+
*/
|
|
1702
|
+
locale() {
|
|
1703
|
+
throw new Error("'locale' not implemented")
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1435
1707
|
VelociousDatabaseRecord.registerValidatorType("presence", ValidatorsPresence)
|
|
1436
1708
|
VelociousDatabaseRecord.registerValidatorType("uniqueness", ValidatorsUniqueness)
|
|
1437
1709
|
|