velocious 1.0.4 → 1.0.6
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/index.mjs +1 -19
- package/package.json +6 -3
- package/spec/cli/commands/db/create-spec.mjs +1 -1
- package/spec/cli/commands/db/migrate-spec.mjs +2 -0
- package/spec/database/connection/drivers/mysql/query-parser-spec.mjs +6 -5
- package/spec/database/drivers/mysql/connection-spec.mjs +2 -3
- package/spec/database/record/create-spec.mjs +9 -0
- package/spec/database/record/find-spec.mjs +0 -1
- package/spec/database/record/query-spec.mjs +37 -0
- package/spec/dummy/index.mjs +14 -2
- package/spec/dummy/src/config/configuration.example.mjs +16 -1
- package/spec/dummy/src/config/configuration.peakflow.mjs +16 -1
- package/spec/dummy/src/database/migrations/20230728075328-create-projects.mjs +0 -1
- package/spec/dummy/src/database/migrations/20230728075329-create-tasks.mjs +0 -1
- package/spec/dummy/src/database/migrations/20250605133926-create-project-translations.mjs +16 -0
- package/spec/dummy/src/models/project.mjs +9 -0
- package/spec/dummy/src/models/task.mjs +6 -2
- package/src/big-brother.mjs +37 -0
- package/src/cli/commands/db/create.mjs +8 -6
- package/src/cli/commands/db/migrate.mjs +1 -2
- package/src/cli/commands/generate/migration.mjs +1 -1
- package/src/cli/commands/generate/model.mjs +1 -1
- package/src/configuration.mjs +39 -3
- package/src/database/drivers/base.mjs +60 -0
- package/src/database/drivers/mysql/column.mjs +8 -0
- package/src/database/drivers/mysql/index.mjs +34 -2
- package/src/database/drivers/mysql/options.mjs +1 -0
- package/src/database/drivers/mysql/query-parser.mjs +2 -23
- package/src/database/drivers/mysql/table.mjs +25 -0
- package/src/database/drivers/sqlite/base.mjs +108 -0
- package/src/database/drivers/sqlite/column.mjs +10 -0
- package/src/database/drivers/sqlite/index.native.mjs +22 -63
- package/src/database/drivers/sqlite/index.web.mjs +28 -37
- package/src/database/drivers/sqlite/options.mjs +2 -1
- package/src/database/drivers/sqlite/query-parser.mjs +2 -23
- package/src/database/drivers/sqlite/query.native.mjs +16 -1
- package/src/database/drivers/sqlite/query.web.mjs +27 -2
- package/src/database/drivers/sqlite/sql/create-index.mjs +4 -0
- package/src/database/drivers/sqlite/table.mjs +24 -0
- package/src/database/initializer-from-require-context.mjs +21 -0
- package/src/database/migrate-from-require-context.mjs +11 -2
- package/src/database/migration/index.mjs +34 -2
- package/src/database/migrator.mjs +75 -0
- package/src/database/pool/async-tracked-multi-connection.mjs +1 -1
- package/src/database/pool/base.mjs +19 -1
- package/src/database/pool/single-multi-use.mjs +1 -1
- package/src/database/query/base.mjs +2 -1
- package/src/database/query/create-index-base.mjs +50 -0
- package/src/database/query/create-table-base.mjs +40 -17
- package/src/database/query/index.mjs +83 -21
- package/src/database/query/insert-base.mjs +4 -0
- package/src/database/query/preloader/belongs-to.mjs +52 -0
- package/src/database/query/preloader/has-many.mjs +55 -0
- package/src/database/query/preloader.mjs +41 -0
- package/src/database/query/where-base.mjs +9 -0
- package/src/database/query/where-hash.mjs +35 -0
- package/src/database/query/where-plain.mjs +13 -0
- package/src/database/query-parser/base-query-parser.mjs +33 -0
- package/src/database/query-parser/group-parser.mjs +40 -0
- package/src/database/query-parser/joins-parser.mjs +48 -7
- package/src/database/query-parser/limit-parser.mjs +40 -0
- package/src/database/query-parser/options.mjs +4 -3
- package/src/database/query-parser/order-parser.mjs +39 -0
- package/src/database/query-parser/select-parser.mjs +5 -1
- package/src/database/query-parser/where-parser.mjs +39 -0
- package/src/database/record/index.mjs +464 -29
- package/src/database/record/instance-relationships/base.mjs +28 -0
- package/src/database/record/instance-relationships/belongs-to.mjs +20 -0
- package/src/database/record/instance-relationships/has-many.mjs +47 -0
- package/src/database/record/relationships/base.mjs +32 -0
- package/src/database/record/relationships/belongs-to.mjs +12 -0
- package/src/database/record/relationships/has-many.mjs +12 -0
- package/src/database/table-data/index.mjs +15 -25
- package/src/http-server/worker-handler/worker-thread.mjs +7 -4
- package/src/templates/generate-model.mjs +3 -1
- package/src/utils/rest-args-error.mjs +9 -0
- package/src/database/drivers/index.mjs +0 -5
- package/src/database/index.mjs +0 -15
- package/src/database/migrator/index.mjs +0 -15
|
@@ -1,24 +1,130 @@
|
|
|
1
|
+
import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.mjs"
|
|
2
|
+
import BelongsToRelationship from "./relationships/belongs-to.mjs"
|
|
1
3
|
import Configuration from "../../configuration.mjs"
|
|
4
|
+
import FromTable from "../query/from-table.mjs"
|
|
2
5
|
import Handler from "../handler.mjs"
|
|
3
|
-
import
|
|
6
|
+
import HasManyRelationship from "./relationships/has-many.mjs"
|
|
7
|
+
import HasManyInstanceRelationship from "./instance-relationships/has-many.mjs"
|
|
8
|
+
import * as inflection from "inflection"
|
|
4
9
|
import Query from "../query/index.mjs"
|
|
5
10
|
import RecordNotFoundError from "./record-not-found-error.mjs"
|
|
6
11
|
|
|
7
12
|
export default class VelociousDatabaseRecord {
|
|
13
|
+
static _relationshipExists(relationshipName) {
|
|
14
|
+
if (this._relationships && relationshipName in this._relationships) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static _defineRelationship(relationshipName, data) {
|
|
22
|
+
if (!this._relationships) this._relationships = {}
|
|
23
|
+
if (this._relationshipExists(relationshipName)) throw new Error(`Relationship ${relationshipName} already exists`)
|
|
24
|
+
|
|
25
|
+
const actualData = Object.assign(
|
|
26
|
+
{
|
|
27
|
+
modelClass: this,
|
|
28
|
+
relationshipName,
|
|
29
|
+
type: "hasMany"
|
|
30
|
+
},
|
|
31
|
+
data
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (!actualData.className && !actualData.klass) {
|
|
35
|
+
actualData.className = inflection.camelize(inflection.singularize(relationshipName))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let relationship
|
|
39
|
+
|
|
40
|
+
if (actualData.type == "belongsTo") {
|
|
41
|
+
relationship = new BelongsToRelationship(actualData)
|
|
42
|
+
|
|
43
|
+
const buildMethodName = `build${inflection.camelize(relationshipName)}`
|
|
44
|
+
|
|
45
|
+
this.prototype[relationshipName] = function () {
|
|
46
|
+
const relationship = this.getRelationshipByName(relationshipName)
|
|
47
|
+
|
|
48
|
+
return relationship.loaded()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.prototype[buildMethodName] = function (attributes) {
|
|
52
|
+
const relationship = this.getRelationshipByName(relationshipName)
|
|
53
|
+
const record = relationship.build(attributes)
|
|
54
|
+
|
|
55
|
+
return record
|
|
56
|
+
}
|
|
57
|
+
} else if (actualData.type == "hasMany") {
|
|
58
|
+
relationship = new HasManyRelationship(actualData)
|
|
59
|
+
|
|
60
|
+
this.prototype[relationshipName] = function () {
|
|
61
|
+
return this.getRelationshipByName(relationshipName)
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error(`Unknown relationship type: ${actualData.type}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._relationships[relationshipName] = relationship
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static getRelationshipByName(relationshipName) {
|
|
71
|
+
if (!this._relationships) this._relationships = {}
|
|
72
|
+
|
|
73
|
+
const relationship = this._relationships[relationshipName]
|
|
74
|
+
|
|
75
|
+
if (!relationship) throw new Error(`No relationship by that name: ${relationshipName}`)
|
|
76
|
+
|
|
77
|
+
return relationship
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getRelationshipByName(relationshipName) {
|
|
81
|
+
if (!this._instanceRelationships) this._instanceRelationships = {}
|
|
82
|
+
|
|
83
|
+
if (!(relationshipName in this._instanceRelationships)) {
|
|
84
|
+
const modelClassRelationship = this.constructor.getRelationshipByName(relationshipName)
|
|
85
|
+
let instanceRelationship
|
|
86
|
+
|
|
87
|
+
if (modelClassRelationship.getType() == "belongsTo") {
|
|
88
|
+
instanceRelationship = new BelongsToInstanceRelationship({model: this, relationship: modelClassRelationship})
|
|
89
|
+
} else if (modelClassRelationship.getType() == "hasMany") {
|
|
90
|
+
instanceRelationship = new HasManyInstanceRelationship({model: this, relationship: modelClassRelationship})
|
|
91
|
+
} else {
|
|
92
|
+
throw new Error(`Unknown relationship type: ${modelClassRelationship.getType()}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this._instanceRelationships[relationshipName] = instanceRelationship
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this._instanceRelationships[relationshipName]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static belongsTo(relationshipName) {
|
|
102
|
+
this._defineRelationship(relationshipName, {type: "belongsTo"})
|
|
103
|
+
}
|
|
104
|
+
|
|
8
105
|
static connection() {
|
|
9
|
-
const connection =
|
|
106
|
+
const connection = this._getConfiguration().getDatabasePoolType().current().getCurrentConnection()
|
|
10
107
|
|
|
11
108
|
if (!connection) throw new Error("No connection?")
|
|
12
109
|
|
|
13
110
|
return connection
|
|
14
111
|
}
|
|
15
112
|
|
|
113
|
+
static async create(attributes) {
|
|
114
|
+
const record = new this(attributes)
|
|
115
|
+
|
|
116
|
+
await record.save()
|
|
117
|
+
|
|
118
|
+
return record
|
|
119
|
+
}
|
|
120
|
+
|
|
16
121
|
static async find(recordId) {
|
|
17
122
|
const conditions = {}
|
|
18
123
|
|
|
19
124
|
conditions[this.primaryKey()] = recordId
|
|
20
125
|
|
|
21
|
-
const
|
|
126
|
+
const query = this.where(conditions)
|
|
127
|
+
const record = await query.first()
|
|
22
128
|
|
|
23
129
|
if (!record) {
|
|
24
130
|
throw new RecordNotFoundError(`Couldn't find ${this.name} with '${this.primaryKey()}'=${recordId}`)
|
|
@@ -27,8 +133,144 @@ export default class VelociousDatabaseRecord {
|
|
|
27
133
|
return record
|
|
28
134
|
}
|
|
29
135
|
|
|
136
|
+
static _getConfiguration() {
|
|
137
|
+
if (!this._configuration) {
|
|
138
|
+
this._configuration = Configuration.current()
|
|
139
|
+
|
|
140
|
+
if (!this._configuration) {
|
|
141
|
+
throw new Error("Configuration hasn't been set (model class probably hasn't been initialized)")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this._configuration
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_getConfiguration() {
|
|
149
|
+
return this.constructor._getConfiguration()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static hasMany(relationshipName, options = {}) {
|
|
153
|
+
return this._defineRelationship(relationshipName, Object.assign({type: "hasMany"}, options))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static async initializeRecord({configuration}) {
|
|
157
|
+
if (!configuration) throw new Error("No configuration given")
|
|
158
|
+
|
|
159
|
+
this._configuration = configuration
|
|
160
|
+
this._configuration.registerModelClass(this)
|
|
161
|
+
|
|
162
|
+
this._table = await this.connection().getTableByName(this.tableName())
|
|
163
|
+
this._columns = await this._getTable().getColumns()
|
|
164
|
+
this._columnsAsHash = {}
|
|
165
|
+
|
|
166
|
+
for (const column of this._columns) {
|
|
167
|
+
this._columnsAsHash[column.getName()] = column
|
|
168
|
+
|
|
169
|
+
const camelizedColumnName = inflection.camelize(column.getName(), true)
|
|
170
|
+
const camelizedColumnNameBigFirst = inflection.camelize(column.getName())
|
|
171
|
+
const setterMethodName = `set${camelizedColumnNameBigFirst}`
|
|
172
|
+
|
|
173
|
+
this.prototype[camelizedColumnName] = function () {
|
|
174
|
+
return this.readAttribute(camelizedColumnName)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.prototype[setterMethodName] = function (newValue) {
|
|
178
|
+
return this._setColumnAttribute(camelizedColumnName, newValue)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await this._defineTranslationMethods()
|
|
183
|
+
this._initialized = true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
static isInitialized() {
|
|
187
|
+
if (this._initialized) return true
|
|
188
|
+
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static async _defineTranslationMethods() {
|
|
193
|
+
if (this._translations) {
|
|
194
|
+
const locales = this._getConfiguration().getLocales()
|
|
195
|
+
|
|
196
|
+
if (!locales) throw new Error("Locales hasn't been set in the configuration")
|
|
197
|
+
|
|
198
|
+
await this.getTranslationClass().initializeRecord({configuration: this._getConfiguration()})
|
|
199
|
+
|
|
200
|
+
for (const name in this._translations) {
|
|
201
|
+
const nameCamelized = inflection.camelize(name)
|
|
202
|
+
const setterMethodName = `set${nameCamelized}`
|
|
203
|
+
|
|
204
|
+
this.prototype[name] = function () {
|
|
205
|
+
const locale = this._getConfiguration().getLocale()
|
|
206
|
+
|
|
207
|
+
return this._getTranslatedAttribute(name, locale)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.prototype[setterMethodName] = function (newValue) {
|
|
211
|
+
const locale = this._getConfiguration().getLocale()
|
|
212
|
+
|
|
213
|
+
return this._setTranslatedAttribute(name, locale, newValue)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const locale of locales) {
|
|
217
|
+
const localeCamelized = inflection.camelize(locale)
|
|
218
|
+
const getterMethodNameLocalized = `${name}${localeCamelized}`
|
|
219
|
+
const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
|
|
220
|
+
|
|
221
|
+
this.prototype[getterMethodNameLocalized] = function () {
|
|
222
|
+
return this._getTranslatedAttribute(name, locale)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.prototype[setterMethodNameLocalized] = function (newValue) {
|
|
226
|
+
return this._setTranslatedAttribute(name, locale, newValue)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getAttribute(name) {
|
|
234
|
+
const columnName = inflection.underscore(name)
|
|
235
|
+
|
|
236
|
+
if (!this.isNewRecord() && !(columnName in this._attributes)) {
|
|
237
|
+
throw new Error(`${this.constructor.name}#${name} attribute hasn't been loaded yet in ${Object.keys(this._attributes).join(", ")}`)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return this._attributes[columnName]
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
setAttribute(name, newValue) {
|
|
244
|
+
const setterName = `set${inflection.camelize(name)}`
|
|
245
|
+
|
|
246
|
+
if (!this.constructor.isInitialized()) throw new Error(`${this.constructor.name} model isn't initialized yet`)
|
|
247
|
+
if (!(setterName in this)) throw new Error(`No such setter method: ${this.constructor.name}#${setterName}`)
|
|
248
|
+
|
|
249
|
+
this[setterName](newValue)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_setColumnAttribute(name, newValue) {
|
|
253
|
+
const columnName = inflection.underscore(name)
|
|
254
|
+
|
|
255
|
+
if (this._attributes[columnName] != newValue) {
|
|
256
|
+
this._changes[columnName] = newValue
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static getColumns() {
|
|
261
|
+
if (!this._columns) throw new Error(`${this.name} hasn't been initialized yet`)
|
|
262
|
+
|
|
263
|
+
return this._columns
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static _getTable() {
|
|
267
|
+
if (!this._table) throw new Error(`${this.name} hasn't been initialized yet`)
|
|
268
|
+
|
|
269
|
+
return this._table
|
|
270
|
+
}
|
|
271
|
+
|
|
30
272
|
static async last() {
|
|
31
|
-
const query = this._newQuery().order(this.primaryKey())
|
|
273
|
+
const query = this._newQuery().order(this.primaryKey())
|
|
32
274
|
const record = await query.last()
|
|
33
275
|
|
|
34
276
|
return record
|
|
@@ -39,10 +281,69 @@ export default class VelociousDatabaseRecord {
|
|
|
39
281
|
}
|
|
40
282
|
|
|
41
283
|
async save() {
|
|
284
|
+
let result
|
|
285
|
+
|
|
286
|
+
const isNewRecord = this.isNewRecord()
|
|
287
|
+
|
|
288
|
+
await this._autoSaveBelongsToRelationships()
|
|
289
|
+
|
|
42
290
|
if (this.isPersisted()) {
|
|
43
|
-
|
|
291
|
+
result = await this._updateRecordWithChanges()
|
|
44
292
|
} else {
|
|
45
|
-
|
|
293
|
+
result = await this._createNewRecord()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
await this._autoSaveHasManyRelationships({isNewRecord})
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async _autoSaveBelongsToRelationships() {
|
|
302
|
+
for (const relationshipName in this._instanceRelationships) {
|
|
303
|
+
const instanceRelationship = this._instanceRelationships[relationshipName]
|
|
304
|
+
|
|
305
|
+
if (instanceRelationship.getType() != "belongsTo") {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const model = instanceRelationship.loaded()
|
|
310
|
+
|
|
311
|
+
if (model.isChanged()) {
|
|
312
|
+
await model.save()
|
|
313
|
+
|
|
314
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
315
|
+
|
|
316
|
+
this.setAttribute(foreignKey, model.id())
|
|
317
|
+
instanceRelationship.setPreloaded(true)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async _autoSaveHasManyRelationships({isNewRecord}) {
|
|
323
|
+
for (const relationshipName in this._instanceRelationships) {
|
|
324
|
+
const instanceRelationship = this._instanceRelationships[relationshipName]
|
|
325
|
+
|
|
326
|
+
if (instanceRelationship.getType() != "hasMany") {
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let loaded = instanceRelationship._loaded
|
|
331
|
+
|
|
332
|
+
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
333
|
+
|
|
334
|
+
for (const model of loaded) {
|
|
335
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
336
|
+
|
|
337
|
+
model.setAttribute(foreignKey, this.id())
|
|
338
|
+
|
|
339
|
+
if (model.isChanged()) {
|
|
340
|
+
await model.save()
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (isNewRecord) {
|
|
345
|
+
instanceRelationship.setPreloaded(true)
|
|
346
|
+
}
|
|
46
347
|
}
|
|
47
348
|
}
|
|
48
349
|
|
|
@@ -50,6 +351,75 @@ export default class VelociousDatabaseRecord {
|
|
|
50
351
|
return inflection.underscore(inflection.pluralize(this.name))
|
|
51
352
|
}
|
|
52
353
|
|
|
354
|
+
static setTableName(tableName) {
|
|
355
|
+
this._tableName = tableName
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
static translates(...names) {
|
|
359
|
+
for (const name of names) {
|
|
360
|
+
if (!this._translations) this._translations = {}
|
|
361
|
+
if (name in this._translations) throw new Error(`Translation already exists: ${name}`)
|
|
362
|
+
|
|
363
|
+
this._translations[name] = {}
|
|
364
|
+
|
|
365
|
+
if (!this._relationshipExists("translations")) {
|
|
366
|
+
this._defineRelationship("translations", {klass: this.getTranslationClass(), type: "hasMany"})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
static getTranslationClass() {
|
|
372
|
+
if (this._translationClass) return this._translationClass
|
|
373
|
+
if (this.tableName().endsWith("_translations")) throw new Error("Trying to define a translations class for a translation class")
|
|
374
|
+
|
|
375
|
+
const className = `${this.name}Translation`
|
|
376
|
+
const TranslationClass = class Translation extends VelociousDatabaseRecord {}
|
|
377
|
+
const belongsTo = `${inflection.camelize(inflection.singularize(this.tableName()), true)}`
|
|
378
|
+
const tableName = `${inflection.singularize(this.tableName())}_translations`
|
|
379
|
+
|
|
380
|
+
Object.defineProperty(TranslationClass, "name", {value: className})
|
|
381
|
+
TranslationClass.setTableName(tableName)
|
|
382
|
+
TranslationClass.belongsTo(belongsTo)
|
|
383
|
+
|
|
384
|
+
this._translationClass = TranslationClass
|
|
385
|
+
|
|
386
|
+
return this._translationClass
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static getTranslationsTableName() {
|
|
390
|
+
return `${inflection.singularize(this.tableName())}_translations`
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
static async hasTranslationsTable() {
|
|
394
|
+
try {
|
|
395
|
+
await this.connection().getTableByName(this.getTranslationsTableName())
|
|
396
|
+
|
|
397
|
+
return true
|
|
398
|
+
} catch {
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_getTranslatedAttribute(name, locale) {
|
|
404
|
+
const translation = this.translations().loaded().find((translation) => translation.locale() == locale)
|
|
405
|
+
|
|
406
|
+
if (translation) {
|
|
407
|
+
return translation[name]()
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
_setTranslatedAttribute(name, locale, newValue) {
|
|
412
|
+
let translation = this.translations().loaded()?.find((translation) =>translation.locale() == locale)
|
|
413
|
+
|
|
414
|
+
if (!translation) translation = this.translations().build({locale})
|
|
415
|
+
|
|
416
|
+
const assignments = {}
|
|
417
|
+
|
|
418
|
+
assignments[name] = newValue
|
|
419
|
+
|
|
420
|
+
translation.assign(assignments)
|
|
421
|
+
}
|
|
422
|
+
|
|
53
423
|
static _newQuery() {
|
|
54
424
|
const handler = new Handler()
|
|
55
425
|
const query = new Query({
|
|
@@ -58,29 +428,62 @@ export default class VelociousDatabaseRecord {
|
|
|
58
428
|
modelClass: this
|
|
59
429
|
})
|
|
60
430
|
|
|
61
|
-
return query.from(this.tableName())
|
|
431
|
+
return query.from(new FromTable({driver: this.connection(), tableName: this.tableName()}))
|
|
62
432
|
}
|
|
63
433
|
|
|
64
434
|
static orderableColumn() {
|
|
65
|
-
// Allow to change to 'created_at' if using UUID?
|
|
435
|
+
// FIXME: Allow to change to 'created_at' if using UUID?
|
|
66
436
|
|
|
67
437
|
return this.primaryKey()
|
|
68
438
|
}
|
|
69
439
|
|
|
70
|
-
static
|
|
71
|
-
|
|
440
|
+
static all() {
|
|
441
|
+
return this._newQuery()
|
|
442
|
+
}
|
|
72
443
|
|
|
73
|
-
|
|
444
|
+
static async findBy(...args) {
|
|
445
|
+
return this._newQuery().findBy(...args)
|
|
74
446
|
}
|
|
75
447
|
|
|
76
|
-
|
|
77
|
-
this.
|
|
448
|
+
static async findOrCreateBy(...args) {
|
|
449
|
+
return this._newQuery().findOrCreateBy(...args)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
static async findOrInitializeBy(...args) {
|
|
453
|
+
return this._newQuery().findOrInitializeBy(...args)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static joins(...args) {
|
|
457
|
+
return this._newQuery().joins(...args)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
static preload(...args) {
|
|
461
|
+
return this._newQuery().preload(...args)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
static where(...args) {
|
|
465
|
+
return this._newQuery().where(...args)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
constructor(changes = {}) {
|
|
469
|
+
this._attributes = {}
|
|
78
470
|
this._changes = {}
|
|
471
|
+
this._isNewRecord = true
|
|
472
|
+
this._relationships = {}
|
|
473
|
+
|
|
474
|
+
for (const key in changes) {
|
|
475
|
+
this.setAttribute(key, changes[key])
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
loadExistingRecord(attributes) {
|
|
480
|
+
this._attributes = attributes
|
|
481
|
+
this._isNewRecord = false
|
|
79
482
|
}
|
|
80
483
|
|
|
81
484
|
assign(attributesToAssign) {
|
|
82
485
|
for (const attributeToAssign in attributesToAssign) {
|
|
83
|
-
this.
|
|
486
|
+
this.setAttribute(attributeToAssign, attributesToAssign[attributeToAssign])
|
|
84
487
|
}
|
|
85
488
|
}
|
|
86
489
|
|
|
@@ -107,6 +510,33 @@ export default class VelociousDatabaseRecord {
|
|
|
107
510
|
await this._connection().query(sql)
|
|
108
511
|
}
|
|
109
512
|
|
|
513
|
+
_hasChanges = () => Object.keys(this._changes).length > 0
|
|
514
|
+
|
|
515
|
+
isChanged() {
|
|
516
|
+
if (this.isNewRecord() || this._hasChanges()) {
|
|
517
|
+
return true
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Check if a loaded sub-model of a relationship is changed and should be saved along with this model.
|
|
521
|
+
if (this._instanceRelationships) {
|
|
522
|
+
for (const instanceRelationshipName in this._instanceRelationships) {
|
|
523
|
+
const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
|
|
524
|
+
let loaded = instanceRelationship._loaded
|
|
525
|
+
|
|
526
|
+
if (!loaded) continue
|
|
527
|
+
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
528
|
+
|
|
529
|
+
for (const model of loaded) {
|
|
530
|
+
if (model.isChanged()) {
|
|
531
|
+
return true
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return false
|
|
538
|
+
}
|
|
539
|
+
|
|
110
540
|
_tableName() {
|
|
111
541
|
if (this.__tableName) return this.__tableName
|
|
112
542
|
|
|
@@ -114,9 +544,15 @@ export default class VelociousDatabaseRecord {
|
|
|
114
544
|
}
|
|
115
545
|
|
|
116
546
|
readAttribute(attributeName) {
|
|
117
|
-
|
|
547
|
+
const attributeNameUnderscore = inflection.underscore(attributeName)
|
|
118
548
|
|
|
119
|
-
return this.
|
|
549
|
+
if (attributeNameUnderscore in this._changes) return this._changes[attributeNameUnderscore]
|
|
550
|
+
|
|
551
|
+
if (!(attributeNameUnderscore in this._attributes) && this.isPersisted()) {
|
|
552
|
+
throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return this._attributes[attributeNameUnderscore]
|
|
120
556
|
}
|
|
121
557
|
|
|
122
558
|
async _createNewRecord() {
|
|
@@ -128,13 +564,16 @@ export default class VelociousDatabaseRecord {
|
|
|
128
564
|
tableName: this._tableName(),
|
|
129
565
|
data: this.attributes()
|
|
130
566
|
})
|
|
131
|
-
|
|
132
|
-
const id =
|
|
567
|
+
await this._connection().query(sql)
|
|
568
|
+
const id = await this._connection().lastInsertID()
|
|
133
569
|
|
|
134
570
|
await this._reloadWithId(id)
|
|
571
|
+
this.setIsNewRecord(false)
|
|
135
572
|
}
|
|
136
573
|
|
|
137
574
|
async _updateRecordWithChanges() {
|
|
575
|
+
if (!this._hasChanges()) return
|
|
576
|
+
|
|
138
577
|
const conditions = {}
|
|
139
578
|
|
|
140
579
|
conditions[this.constructor.primaryKey()] = this.id()
|
|
@@ -148,18 +587,12 @@ export default class VelociousDatabaseRecord {
|
|
|
148
587
|
await this._reloadWithId(this.id())
|
|
149
588
|
}
|
|
150
589
|
|
|
151
|
-
id()
|
|
152
|
-
|
|
153
|
-
|
|
590
|
+
id = () => this.readAttribute(this.constructor.primaryKey())
|
|
591
|
+
isPersisted = () => !this._isNewRecord
|
|
592
|
+
isNewRecord = () => this._isNewRecord
|
|
154
593
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return false
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
isNewRecord() {
|
|
162
|
-
return !this.isPersisted()
|
|
594
|
+
setIsNewRecord(newIsNewRecord) {
|
|
595
|
+
this._isNewRecord = newIsNewRecord
|
|
163
596
|
}
|
|
164
597
|
|
|
165
598
|
async _reloadWithId(id) {
|
|
@@ -171,6 +604,8 @@ export default class VelociousDatabaseRecord {
|
|
|
171
604
|
const query = this.constructor.where(whereObject)
|
|
172
605
|
const reloadedModel = await query.first()
|
|
173
606
|
|
|
607
|
+
if (!reloadedModel) throw new Error(`${this.constructor.name}#${this.id()} couldn't be reloaded - record didn't exist`)
|
|
608
|
+
|
|
174
609
|
this._attributes = reloadedModel.attributes()
|
|
175
610
|
this._changes = {}
|
|
176
611
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
2
|
+
constructor({model, relationship}) {
|
|
3
|
+
this.model = model
|
|
4
|
+
this.relationship = relationship
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
loaded() {
|
|
8
|
+
if (!this._preloaded && this.model.isPersisted()) {
|
|
9
|
+
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return this._loaded
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setLoaded(model) {
|
|
16
|
+
this._loaded = model
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setPreloaded(preloadedValue) {
|
|
20
|
+
this._preloaded = preloadedValue
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getForeignKey = () => this.getRelationship().getForeignKey()
|
|
24
|
+
getPrimaryKey = () => this.getRelationship().getPrimaryKey()
|
|
25
|
+
getRelationship = () => this.relationship
|
|
26
|
+
getTargetModelClass = () => this.getRelationship().getTargetModelClass()
|
|
27
|
+
getType = () => this.getRelationship().getType()
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import BaseInstanceRelationship from "./base.mjs"
|
|
2
|
+
|
|
3
|
+
export default class VelociousDatabaseRecordBelongsToInstanceRelationship extends BaseInstanceRelationship {
|
|
4
|
+
constructor(args) {
|
|
5
|
+
super(args)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
build(data) {
|
|
9
|
+
const targetModelClass = this.getTargetModelClass()
|
|
10
|
+
const newInstance = new targetModelClass(data)
|
|
11
|
+
|
|
12
|
+
this._loaded = newInstance
|
|
13
|
+
|
|
14
|
+
return newInstance
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setLoaded(models) {
|
|
18
|
+
this._loaded = models
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import BaseInstanceRelationship from "./base.mjs"
|
|
2
|
+
|
|
3
|
+
export default class VelociousDatabaseRecordHasManyInstanceRelationship extends BaseInstanceRelationship {
|
|
4
|
+
constructor(args) {
|
|
5
|
+
super(args)
|
|
6
|
+
this._loaded = []
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
build(data) {
|
|
10
|
+
const targetModelClass = this.getTargetModelClass()
|
|
11
|
+
const newInstance = new targetModelClass(data)
|
|
12
|
+
|
|
13
|
+
this._loaded.push(newInstance)
|
|
14
|
+
|
|
15
|
+
return newInstance
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
loaded() {
|
|
19
|
+
if (!this._preloaded && this.model.isPersisted()) {
|
|
20
|
+
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return this._loaded
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
addToLoaded(models) {
|
|
27
|
+
if (Array.isArray(models)) {
|
|
28
|
+
for (const model of models) {
|
|
29
|
+
this._loaded.push(model)
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
this._loaded.push(models)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setLoaded(models) {
|
|
37
|
+
if (!Array.isArray(models)) throw new Error(`Argument given to setLoaded wasn't an array: ${typeof models}`)
|
|
38
|
+
|
|
39
|
+
this._loaded = models
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setPreloaded(preloadedValue) {
|
|
43
|
+
this._preloaded = preloadedValue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getTargetModelClass = () => this.relationship.getTargetModelClass()
|
|
47
|
+
}
|