velocious 1.0.5 → 1.0.7
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/bin/{velocious.mjs → velocious.js} +1 -1
- package/package.json +7 -5
- package/peak_flow.yml +1 -1
- package/spec/cli/commands/db/{create-spec.mjs → create-spec.js} +3 -3
- package/spec/cli/commands/db/{migrate-spec.mjs → migrate-spec.js} +4 -2
- package/spec/cli/commands/destroy/{migration-spec.mjs → migration-spec.js} +2 -2
- package/spec/cli/commands/generate/{migration-spec.mjs → migration-spec.js} +3 -3
- package/spec/cli/commands/{init-spec.mjs → init-spec.js} +6 -6
- package/spec/cli/commands/test/{test-files-finder-spec.mjs → test-files-finder-spec.js} +2 -2
- package/spec/database/connection/drivers/mysql/{query-parser-spec.mjs → query-parser-spec.js} +6 -6
- package/spec/database/drivers/mysql/{connection-spec.mjs → connection-spec.js} +2 -2
- package/spec/database/record/create-spec.js +23 -0
- package/spec/database/record/{destroy-spec.mjs → destroy-spec.js} +2 -2
- package/spec/database/record/{find-spec.mjs → find-spec.js} +2 -3
- package/spec/database/record/query-spec.js +37 -0
- package/spec/database/record/{update-spec.mjs → update-spec.js} +2 -2
- package/spec/dummy/{index.mjs → index.js} +16 -4
- package/spec/dummy/src/config/configuration.example.js +36 -0
- package/spec/dummy/src/config/configuration.peakflow.js +37 -0
- package/spec/dummy/src/config/{routes.mjs → routes.js} +1 -1
- package/spec/dummy/src/database/migrations/{20230728075328-create-projects.mjs → 20230728075328-create-projects.js} +1 -2
- package/spec/dummy/src/database/migrations/{20230728075329-create-tasks.mjs → 20230728075329-create-tasks.js} +1 -2
- package/spec/dummy/src/database/migrations/20250605133926-create-project-translations.js +16 -0
- package/spec/dummy/src/models/project.js +9 -0
- package/spec/dummy/src/models/task.js +8 -0
- package/spec/dummy/src/routes/tasks/{controller.mjs → controller.js} +2 -2
- package/spec/http-server/{client-spec.mjs → client-spec.js} +3 -3
- package/spec/http-server/{get-spec.mjs → get-spec.js} +1 -1
- package/spec/http-server/{post-spec.mjs → post-spec.js} +2 -2
- package/spec/support/jasmine.json +2 -2
- package/src/{application.mjs → application.js} +3 -3
- package/src/big-brother.js +37 -0
- package/src/cli/commands/db/{create.mjs → create.js} +9 -7
- package/src/cli/commands/db/{migrate.mjs → migrate.js} +4 -5
- package/src/cli/commands/destroy/{migration.mjs → migration.js} +2 -2
- package/src/cli/commands/generate/{migration.mjs → migration.js} +5 -5
- package/src/cli/commands/generate/{model.mjs → model.js} +5 -5
- package/src/cli/commands/{init.mjs → init.js} +6 -6
- package/src/cli/commands/{server.mjs → server.js} +1 -1
- package/src/cli/commands/test/{index.mjs → index.js} +3 -3
- package/src/cli/commands/test/{test-files-finder.mjs → test-files-finder.js} +1 -1
- package/src/cli/{index.mjs → index.js} +4 -4
- package/src/{configuration-resolver.mjs → configuration-resolver.js} +2 -2
- package/src/{configuration.mjs → configuration.js} +39 -3
- package/src/database/drivers/{base.mjs → base.js} +23 -4
- package/src/database/drivers/mysql/column.js +8 -0
- package/src/database/drivers/mysql/{index.mjs → index.js} +44 -12
- package/src/database/drivers/{sqlite/options.mjs → mysql/options.js} +2 -1
- package/src/database/drivers/mysql/query-parser.js +4 -0
- package/src/database/drivers/mysql/sql/{create-database.mjs → create-database.js} +1 -1
- package/src/database/drivers/{sqlite/sql/create-table.mjs → mysql/sql/create-table.js} +1 -1
- package/src/database/drivers/mysql/sql/{delete.mjs → delete.js} +1 -1
- package/src/database/drivers/{sqlite/sql/insert.mjs → mysql/sql/insert.js} +1 -1
- package/src/database/drivers/mysql/sql/{update.mjs → update.js} +1 -1
- package/src/database/drivers/mysql/table.js +25 -0
- package/src/database/drivers/sqlite/base.js +108 -0
- package/src/database/drivers/sqlite/column.js +10 -0
- package/src/database/drivers/sqlite/{index.native.mjs → index.native.js} +19 -22
- package/src/database/drivers/sqlite/index.web.js +55 -0
- package/src/database/drivers/{mysql/options.mjs → sqlite/options.js} +3 -2
- package/src/database/drivers/sqlite/query-parser.js +4 -0
- package/src/database/drivers/sqlite/query.native.js +24 -0
- package/src/database/drivers/sqlite/query.web.js +34 -0
- package/src/database/drivers/sqlite/sql/create-index.js +4 -0
- package/src/database/drivers/{mysql/sql/create-table.mjs → sqlite/sql/create-table.js} +1 -1
- package/src/database/drivers/sqlite/sql/{delete.mjs → delete.js} +1 -1
- package/src/database/drivers/{mysql/sql/insert.mjs → sqlite/sql/insert.js} +1 -1
- package/src/database/drivers/sqlite/sql/{update.mjs → update.js} +1 -1
- package/src/database/drivers/sqlite/table.js +24 -0
- package/src/database/initializer-from-require-context.js +21 -0
- package/src/database/{migrate-from-require-context.mjs → migrate-from-require-context.js} +2 -2
- package/src/database/migration/index.js +50 -0
- package/src/database/{migrator.mjs → migrator.js} +4 -2
- package/src/database/pool/{async-tracked-multi-connection.mjs → async-tracked-multi-connection.js} +1 -1
- package/src/database/pool/{base.mjs → base.js} +6 -1
- package/src/database/pool/{single-multi-use.mjs → single-multi-use.js} +1 -1
- package/src/database/query/{base.mjs → base.js} +2 -1
- package/src/database/query/{create-database-base.mjs → create-database-base.js} +1 -1
- package/src/database/query/create-index-base.js +50 -0
- package/src/database/query/create-table-base.js +92 -0
- package/src/database/query/{delete-base.mjs → delete-base.js} +1 -1
- package/src/database/query/{from-plain.mjs → from-plain.js} +1 -1
- package/src/database/query/{from-table.mjs → from-table.js} +1 -1
- package/src/database/query/index.js +206 -0
- package/src/database/query/{join-plain.mjs → join-plain.js} +1 -1
- package/src/database/query/{order-plain.mjs → order-plain.js} +1 -1
- package/src/database/query/preloader/belongs-to.js +52 -0
- package/src/database/query/preloader/has-many.js +55 -0
- package/src/database/query/preloader.js +41 -0
- package/src/database/query/{select-plain.mjs → select-plain.js} +1 -1
- package/src/database/query/{select-table-and-column.mjs → select-table-and-column.js} +1 -1
- package/src/database/query/where-base.js +9 -0
- package/src/database/query/where-hash.js +35 -0
- package/src/database/query/where-plain.js +13 -0
- package/src/database/query-parser/base-query-parser.js +33 -0
- package/src/database/query-parser/group-parser.js +40 -0
- package/src/database/query-parser/joins-parser.js +71 -0
- package/src/database/query-parser/limit-parser.js +40 -0
- package/src/database/query-parser/{options.mjs → options.js} +2 -1
- package/src/database/query-parser/order-parser.js +39 -0
- package/src/database/query-parser/{select-parser.mjs → select-parser.js} +5 -1
- package/src/database/query-parser/where-parser.js +39 -0
- package/src/database/record/index.js +622 -0
- package/src/database/record/instance-relationships/base.js +28 -0
- package/src/database/record/instance-relationships/belongs-to.js +20 -0
- package/src/database/record/instance-relationships/has-many.js +47 -0
- package/src/database/record/relationships/base.js +32 -0
- package/src/database/record/relationships/belongs-to.js +12 -0
- package/src/database/record/relationships/has-many.js +12 -0
- package/src/database/table-data/{index.mjs → index.js} +15 -25
- package/src/http-server/client/{index.mjs → index.js} +3 -3
- package/src/http-server/client/request-buffer/{index.mjs → index.js} +4 -4
- package/src/http-server/client/{request-parser.mjs → request-parser.js} +2 -2
- package/src/http-server/client/{request-runner.mjs → request-runner.js} +3 -3
- package/src/http-server/client/{request.mjs → request.js} +1 -1
- package/src/http-server/{index.mjs → index.js} +3 -3
- package/src/http-server/{server-client.mjs → server-client.js} +1 -1
- package/src/http-server/worker-handler/{index.mjs → index.js} +2 -2
- package/src/http-server/worker-handler/{worker-script.mjs → worker-script.js} +1 -1
- package/src/http-server/worker-handler/{worker-thread.mjs → worker-thread.js} +12 -9
- package/src/routes/{app-routes.mjs → app-routes.js} +1 -1
- package/src/routes/{base-route.mjs → base-route.js} +2 -2
- package/src/routes/{get-route.mjs → get-route.js} +1 -1
- package/src/routes/{index.mjs → index.js} +1 -1
- package/src/routes/{resolver.mjs → resolver.js} +1 -1
- package/src/routes/{resource-route.mjs → resource-route.js} +1 -1
- package/src/routes/{root-route.mjs → root-route.js} +1 -1
- package/src/templates/{configuration.mjs → configuration.js} +3 -3
- package/src/templates/{generate-migration.mjs → generate-migration.js} +1 -1
- package/src/templates/generate-model.js +6 -0
- package/src/templates/{routes.mjs → routes.js} +1 -1
- package/src/utils/rest-args-error.js +9 -0
- package/spec/database/record/create-spec.mjs +0 -14
- package/spec/dummy/src/config/configuration.example.mjs +0 -21
- package/spec/dummy/src/config/configuration.peakflow.mjs +0 -22
- package/spec/dummy/src/models/task.mjs +0 -4
- package/src/database/drivers/mysql/query-parser.mjs +0 -25
- package/src/database/drivers/sqlite/base.mjs +0 -36
- package/src/database/drivers/sqlite/index.web.mjs +0 -45
- package/src/database/drivers/sqlite/query-parser.mjs +0 -25
- package/src/database/drivers/sqlite/query.native.mjs +0 -9
- package/src/database/drivers/sqlite/query.web.mjs +0 -9
- package/src/database/drivers/sqlite/table.mjs +0 -9
- package/src/database/migration/index.mjs +0 -18
- package/src/database/query/create-table-base.mjs +0 -69
- package/src/database/query/index.mjs +0 -144
- package/src/database/query-parser/joins-parser.mjs +0 -30
- package/src/database/record/index.mjs +0 -187
- package/src/templates/generate-model.mjs +0 -4
- /package/{index.mjs → index.js} +0 -0
- /package/spec/dummy/{dummy-directory.mjs → dummy-directory.js} +0 -0
- /package/src/cli/{base-command.mjs → base-command.js} +0 -0
- /package/src/cli/commands/test/{test-runner.mjs → test-runner.js} +0 -0
- /package/src/{controller.mjs → controller.js} +0 -0
- /package/src/database/drivers/mysql/{connect-connection.mjs → connect-connection.js} +0 -0
- /package/src/database/drivers/mysql/{query.mjs → query.js} +0 -0
- /package/src/database/{handler.mjs → handler.js} +0 -0
- /package/src/database/query/{from-base.mjs → from-base.js} +0 -0
- /package/src/database/query/{insert-base.mjs → insert-base.js} +0 -0
- /package/src/database/query/{join-base.mjs → join-base.js} +0 -0
- /package/src/database/query/{order-base.mjs → order-base.js} +0 -0
- /package/src/database/query/{select-base.mjs → select-base.js} +0 -0
- /package/src/database/query/{update-base.mjs → update-base.js} +0 -0
- /package/src/database/query-parser/{from-parser.mjs → from-parser.js} +0 -0
- /package/src/database/record/{record-not-found-error.mjs → record-not-found-error.js} +0 -0
- /package/src/{error-logger.mjs → error-logger.js} +0 -0
- /package/src/http-server/client/{params-to-object.mjs → params-to-object.js} +0 -0
- /package/src/http-server/client/request-buffer/{form-data-part.mjs → form-data-part.js} +0 -0
- /package/src/http-server/client/request-buffer/{header.mjs → header.js} +0 -0
- /package/src/http-server/client/{response.mjs → response.js} +0 -0
- /package/src/{logger.mjs → logger.js} +0 -0
- /package/src/spec/{index.mjs → index.js} +0 -0
- /package/src/utils/{file-exists.mjs → file-exists.js} +0 -0
|
@@ -35,7 +35,11 @@ export default class VelociousDatabaseQueryParserSelectParser {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if (query._selects.length == 0) {
|
|
38
|
-
|
|
38
|
+
if (query.modelClass) {
|
|
39
|
+
sql += `${query.modelClass.connection().quoteTable(query.modelClass.tableName())}.*`
|
|
40
|
+
} else {
|
|
41
|
+
sql += "*"
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
return sql
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {digs} from "diggerize"
|
|
2
|
+
|
|
3
|
+
export default class VelocuiousDatabaseQueryParserWhereParser {
|
|
4
|
+
constructor({pretty, query}) {
|
|
5
|
+
this.pretty = pretty
|
|
6
|
+
this.query = query
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
toSql() {
|
|
10
|
+
const {pretty, query} = digs(this, "pretty", "query")
|
|
11
|
+
let sql = ""
|
|
12
|
+
|
|
13
|
+
if (query._wheres.length == 0) return sql
|
|
14
|
+
|
|
15
|
+
if (pretty) {
|
|
16
|
+
sql += "\n\n"
|
|
17
|
+
} else {
|
|
18
|
+
sql += " "
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
sql += "WHERE"
|
|
22
|
+
|
|
23
|
+
for (const whereKey in query._wheres) {
|
|
24
|
+
const where = query._wheres[whereKey]
|
|
25
|
+
|
|
26
|
+
if (whereKey > 0) sql += " &&"
|
|
27
|
+
|
|
28
|
+
if (pretty) {
|
|
29
|
+
sql += "\n "
|
|
30
|
+
} else {
|
|
31
|
+
sql += " "
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
sql += where.toSql()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return sql
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.js"
|
|
2
|
+
import BelongsToRelationship from "./relationships/belongs-to.js"
|
|
3
|
+
import Configuration from "../../configuration.js"
|
|
4
|
+
import FromTable from "../query/from-table.js"
|
|
5
|
+
import Handler from "../handler.js"
|
|
6
|
+
import HasManyRelationship from "./relationships/has-many.js"
|
|
7
|
+
import HasManyInstanceRelationship from "./instance-relationships/has-many.js"
|
|
8
|
+
import * as inflection from "inflection"
|
|
9
|
+
import Query from "../query/index.js"
|
|
10
|
+
import RecordNotFoundError from "./record-not-found-error.js"
|
|
11
|
+
|
|
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
|
+
|
|
105
|
+
static connection() {
|
|
106
|
+
const connection = this._getConfiguration().getDatabasePoolType().current().getCurrentConnection()
|
|
107
|
+
|
|
108
|
+
if (!connection) throw new Error("No connection?")
|
|
109
|
+
|
|
110
|
+
return connection
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static async create(attributes) {
|
|
114
|
+
const record = new this(attributes)
|
|
115
|
+
|
|
116
|
+
await record.save()
|
|
117
|
+
|
|
118
|
+
return record
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static async find(recordId) {
|
|
122
|
+
const conditions = {}
|
|
123
|
+
|
|
124
|
+
conditions[this.primaryKey()] = recordId
|
|
125
|
+
|
|
126
|
+
const query = this.where(conditions)
|
|
127
|
+
const record = await query.first()
|
|
128
|
+
|
|
129
|
+
if (!record) {
|
|
130
|
+
throw new RecordNotFoundError(`Couldn't find ${this.name} with '${this.primaryKey()}'=${recordId}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return record
|
|
134
|
+
}
|
|
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
|
+
|
|
272
|
+
static async last() {
|
|
273
|
+
const query = this._newQuery().order(this.primaryKey())
|
|
274
|
+
const record = await query.last()
|
|
275
|
+
|
|
276
|
+
return record
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static primaryKey() {
|
|
280
|
+
return "id"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async save() {
|
|
284
|
+
let result
|
|
285
|
+
|
|
286
|
+
const isNewRecord = this.isNewRecord()
|
|
287
|
+
|
|
288
|
+
await this._autoSaveBelongsToRelationships()
|
|
289
|
+
|
|
290
|
+
if (this.isPersisted()) {
|
|
291
|
+
result = await this._updateRecordWithChanges()
|
|
292
|
+
} else {
|
|
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
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
static tableName() {
|
|
351
|
+
return inflection.underscore(inflection.pluralize(this.name))
|
|
352
|
+
}
|
|
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
|
+
|
|
423
|
+
static _newQuery() {
|
|
424
|
+
const handler = new Handler()
|
|
425
|
+
const query = new Query({
|
|
426
|
+
driver: this.connection(),
|
|
427
|
+
handler,
|
|
428
|
+
modelClass: this
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
return query.from(new FromTable({driver: this.connection(), tableName: this.tableName()}))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static orderableColumn() {
|
|
435
|
+
// FIXME: Allow to change to 'created_at' if using UUID?
|
|
436
|
+
|
|
437
|
+
return this.primaryKey()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
static all() {
|
|
441
|
+
return this._newQuery()
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
static async findBy(...args) {
|
|
445
|
+
return this._newQuery().findBy(...args)
|
|
446
|
+
}
|
|
447
|
+
|
|
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 = {}
|
|
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
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
assign(attributesToAssign) {
|
|
485
|
+
for (const attributeToAssign in attributesToAssign) {
|
|
486
|
+
this.setAttribute(attributeToAssign, attributesToAssign[attributeToAssign])
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
attributes() {
|
|
491
|
+
return Object.assign({}, this._attributes, this._changes)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
_connection() {
|
|
495
|
+
if (this.__connection) return this.__connection
|
|
496
|
+
|
|
497
|
+
return this.constructor.connection()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async destroy() {
|
|
501
|
+
const conditions = {}
|
|
502
|
+
|
|
503
|
+
conditions[this.constructor.primaryKey()] = this.id()
|
|
504
|
+
|
|
505
|
+
const sql = this._connection().deleteSql({
|
|
506
|
+
conditions,
|
|
507
|
+
tableName: this._tableName()
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
await this._connection().query(sql)
|
|
511
|
+
}
|
|
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
|
+
|
|
540
|
+
_tableName() {
|
|
541
|
+
if (this.__tableName) return this.__tableName
|
|
542
|
+
|
|
543
|
+
return this.constructor.tableName()
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
readAttribute(attributeName) {
|
|
547
|
+
const attributeNameUnderscore = inflection.underscore(attributeName)
|
|
548
|
+
|
|
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]
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async _createNewRecord() {
|
|
559
|
+
if (!this.constructor.connection()["insertSql"]) {
|
|
560
|
+
throw new Error(`No insertSql on ${this.constructor.connection().constructor.name}`)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const sql = this._connection().insertSql({
|
|
564
|
+
tableName: this._tableName(),
|
|
565
|
+
data: this.attributes()
|
|
566
|
+
})
|
|
567
|
+
await this._connection().query(sql)
|
|
568
|
+
const id = await this._connection().lastInsertID()
|
|
569
|
+
|
|
570
|
+
await this._reloadWithId(id)
|
|
571
|
+
this.setIsNewRecord(false)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async _updateRecordWithChanges() {
|
|
575
|
+
if (!this._hasChanges()) return
|
|
576
|
+
|
|
577
|
+
const conditions = {}
|
|
578
|
+
|
|
579
|
+
conditions[this.constructor.primaryKey()] = this.id()
|
|
580
|
+
|
|
581
|
+
const sql = this._connection().updateSql({
|
|
582
|
+
tableName: this._tableName(),
|
|
583
|
+
data: this._changes,
|
|
584
|
+
conditions
|
|
585
|
+
})
|
|
586
|
+
await this._connection().query(sql)
|
|
587
|
+
await this._reloadWithId(this.id())
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
id = () => this.readAttribute(this.constructor.primaryKey())
|
|
591
|
+
isPersisted = () => !this._isNewRecord
|
|
592
|
+
isNewRecord = () => this._isNewRecord
|
|
593
|
+
|
|
594
|
+
setIsNewRecord(newIsNewRecord) {
|
|
595
|
+
this._isNewRecord = newIsNewRecord
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async _reloadWithId(id) {
|
|
599
|
+
const primaryKey = this.constructor.primaryKey()
|
|
600
|
+
const whereObject = {}
|
|
601
|
+
|
|
602
|
+
whereObject[primaryKey] = id
|
|
603
|
+
|
|
604
|
+
const query = this.constructor.where(whereObject)
|
|
605
|
+
const reloadedModel = await query.first()
|
|
606
|
+
|
|
607
|
+
if (!reloadedModel) throw new Error(`${this.constructor.name}#${this.id()} couldn't be reloaded - record didn't exist`)
|
|
608
|
+
|
|
609
|
+
this._attributes = reloadedModel.attributes()
|
|
610
|
+
this._changes = {}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async reload() {
|
|
614
|
+
this._reloadWithId(this.readAttribute("id"))
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async update(attributesToAssign) {
|
|
618
|
+
if (attributesToAssign) this.assign(attributesToAssign)
|
|
619
|
+
|
|
620
|
+
await this.save()
|
|
621
|
+
}
|
|
622
|
+
}
|
|
@@ -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
|
+
}
|