velocious 1.0.59 → 1.0.61
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 +2 -2
- package/run-tests.sh +4 -0
- package/spec/cli/commands/db/migrate-spec.js +7 -3
- package/spec/database/record/create-spec.js +12 -0
- package/spec/database/record/find-spec.js +1 -0
- package/spec/database/record/query-spec.js +6 -1
- package/spec/dummy/dummy-directory.js +1 -1
- package/spec/dummy/src/database/migrations/20250921121002-create-project-details.js +15 -0
- package/spec/dummy/src/models/project-detail.js +8 -0
- package/spec/dummy/src/models/project.js +1 -0
- package/src/configuration.js +4 -4
- package/src/database/drivers/mssql/foreign-key.js +5 -5
- package/src/database/drivers/mssql/index.js +2 -2
- package/src/database/drivers/mssql/options.js +3 -3
- package/src/database/drivers/mysql/foreign-key.js +5 -5
- package/src/database/drivers/mysql/index.js +3 -3
- package/src/database/drivers/pgsql/foreign-key.js +5 -5
- package/src/database/drivers/pgsql/index.js +3 -3
- package/src/database/pool/async-tracked-multi-connection.js +1 -1
- package/src/database/pool/single-multi-use.js +1 -1
- package/src/database/query/from-plain.js +1 -1
- package/src/database/query/index.js +2 -2
- package/src/database/query/preloader/has-one.js +55 -0
- package/src/database/query/preloader.js +5 -0
- package/src/database/record/index.js +61 -14
- package/src/database/record/instance-relationships/base.js +6 -6
- package/src/database/record/instance-relationships/belongs-to.js +2 -2
- package/src/database/record/instance-relationships/has-many.js +4 -0
- package/src/database/record/instance-relationships/has-one.js +37 -0
- package/src/database/record/relationships/base.js +9 -7
- package/src/database/record/relationships/has-one.js +12 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"velocious": "bin/velocious.js"
|
|
4
4
|
},
|
|
5
5
|
"name": "velocious",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.61",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"test": "VELOCIOUS_TEST_DIR=../ cd spec/dummy && npx velocious test",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"diggerize": "^1.0.5",
|
|
30
30
|
"ejs": "^3.1.6",
|
|
31
31
|
"env-sense": "^1.0.0",
|
|
32
|
-
"epic-locks": "^1.0.
|
|
32
|
+
"epic-locks": "^1.0.4",
|
|
33
33
|
"escape-string-regexp": "^1.0.5",
|
|
34
34
|
"incorporator": "^1.0.2",
|
|
35
35
|
"inflection": "^3.0.0",
|
package/run-tests.sh
ADDED
|
@@ -18,7 +18,7 @@ describe("Cli - Commands - db:migrate", () => {
|
|
|
18
18
|
await cli.configuration.ensureConnections(async (dbs) => {
|
|
19
19
|
defaultDatabaseType = dbs.default.getType()
|
|
20
20
|
|
|
21
|
-
const tableNames = ["accounts", "authentication_tokens", "tasks", "project_translations", "projects", "schema_migrations", "users"]
|
|
21
|
+
const tableNames = ["accounts", "authentication_tokens", "tasks", "project_details", "project_translations", "projects", "schema_migrations", "users"]
|
|
22
22
|
|
|
23
23
|
for (const tableName of tableNames) {
|
|
24
24
|
await dbs.default.dropTable(tableName, {cascade: true, ifExists: true})
|
|
@@ -102,6 +102,7 @@ describe("Cli - Commands - db:migrate", () => {
|
|
|
102
102
|
[
|
|
103
103
|
"accounts",
|
|
104
104
|
"authentication_tokens",
|
|
105
|
+
"project_details",
|
|
105
106
|
"project_translations",
|
|
106
107
|
"projects",
|
|
107
108
|
"schema_migrations",
|
|
@@ -118,13 +119,15 @@ describe("Cli - Commands - db:migrate", () => {
|
|
|
118
119
|
"20250912183605",
|
|
119
120
|
"20250912183606",
|
|
120
121
|
"20250915085450",
|
|
121
|
-
"20250916111330"
|
|
122
|
+
"20250916111330",
|
|
123
|
+
"20250921121002"
|
|
122
124
|
])
|
|
123
125
|
} else {
|
|
124
126
|
expect(tablesResult.sort()).toEqual(
|
|
125
127
|
[
|
|
126
128
|
"accounts",
|
|
127
129
|
"authentication_tokens",
|
|
130
|
+
"project_details",
|
|
128
131
|
"project_translations",
|
|
129
132
|
"projects",
|
|
130
133
|
"schema_migrations",
|
|
@@ -142,7 +145,8 @@ describe("Cli - Commands - db:migrate", () => {
|
|
|
142
145
|
"20250912183605",
|
|
143
146
|
"20250912183606",
|
|
144
147
|
"20250915085450",
|
|
145
|
-
"20250916111330"
|
|
148
|
+
"20250916111330",
|
|
149
|
+
"20250921121002"
|
|
146
150
|
])
|
|
147
151
|
}
|
|
148
152
|
})
|
|
@@ -2,6 +2,7 @@ import Dummy from "../../dummy/index.js"
|
|
|
2
2
|
import Project from "../../dummy/src/models/project.js"
|
|
3
3
|
import Task from "../../dummy/src/models/task.js"
|
|
4
4
|
import {ValidationError} from "../../../src/database/record/index.js"
|
|
5
|
+
import ProjectDetail from "../../dummy/src/models/project-detail.js"
|
|
5
6
|
|
|
6
7
|
describe("Record - create", () => {
|
|
7
8
|
it("creates a new simple record with relationships and translations", async () => {
|
|
@@ -9,6 +10,8 @@ describe("Record - create", () => {
|
|
|
9
10
|
const task = new Task({name: "Test task"})
|
|
10
11
|
const project = task.buildProject({nameEn: "Test project", nameDe: "Test projekt"})
|
|
11
12
|
|
|
13
|
+
project.buildProjectDetail({note: "Test note"})
|
|
14
|
+
|
|
12
15
|
await task.save()
|
|
13
16
|
|
|
14
17
|
expect(task.id()).not.toBeUndefined()
|
|
@@ -25,6 +28,12 @@ describe("Record - create", () => {
|
|
|
25
28
|
|
|
26
29
|
// 'name' is not a column but rather a column on the translation data model.
|
|
27
30
|
expect(() => project.readColumn("name")).toThrowError("No such attribute or not selected Project#name")
|
|
31
|
+
|
|
32
|
+
// It saves a project note
|
|
33
|
+
const projectDetail = project.projectDetail()
|
|
34
|
+
|
|
35
|
+
expect(projectDetail.note()).toEqual("Test note")
|
|
36
|
+
expect(projectDetail.projectId()).toEqual(project.id())
|
|
28
37
|
})
|
|
29
38
|
})
|
|
30
39
|
|
|
@@ -53,6 +62,7 @@ describe("Record - create", () => {
|
|
|
53
62
|
const project = new Project({name: "Test project"})
|
|
54
63
|
|
|
55
64
|
project.tasks().build({name: " ", project})
|
|
65
|
+
project.buildProjectDetail({note: "Test note"})
|
|
56
66
|
|
|
57
67
|
try {
|
|
58
68
|
await project.save()
|
|
@@ -64,9 +74,11 @@ describe("Record - create", () => {
|
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
const projectsCount = await Project.count()
|
|
77
|
+
const projectDetailsCount = await ProjectDetail.count()
|
|
67
78
|
const tasksCount = await Task.count()
|
|
68
79
|
|
|
69
80
|
expect(projectsCount).toEqual(0)
|
|
81
|
+
expect(projectDetailsCount).toEqual(0)
|
|
70
82
|
expect(tasksCount).toEqual(0)
|
|
71
83
|
})
|
|
72
84
|
})
|
|
@@ -8,6 +8,8 @@ describe("Record - query", () => {
|
|
|
8
8
|
const task = new Task({name: "Test task"})
|
|
9
9
|
const project = task.buildProject({nameEn: "Test project", nameDe: "Test projekt"})
|
|
10
10
|
|
|
11
|
+
project.buildProjectDetail({note: "Test note"})
|
|
12
|
+
|
|
11
13
|
await task.save()
|
|
12
14
|
|
|
13
15
|
expect(task.id()).not.toBeUndefined()
|
|
@@ -20,9 +22,10 @@ describe("Record - query", () => {
|
|
|
20
22
|
expect(project.nameDe()).toEqual("Test projekt")
|
|
21
23
|
expect(project.nameEn()).toEqual("Test project")
|
|
22
24
|
|
|
23
|
-
const tasks = await Task.preload({project: {translations: true}}).toArray()
|
|
25
|
+
const tasks = await Task.preload({project: {projectDetail: true, translations: true}}).toArray()
|
|
24
26
|
const newTask = tasks[0]
|
|
25
27
|
const newProject = newTask.project()
|
|
28
|
+
const newProjectDetail = newProject.projectDetail()
|
|
26
29
|
|
|
27
30
|
expect(newTask.id()).not.toBeUndefined()
|
|
28
31
|
expect(newTask.name()).toEqual("Test task")
|
|
@@ -33,6 +36,8 @@ describe("Record - query", () => {
|
|
|
33
36
|
expect(newProject.name()).toEqual("Test project")
|
|
34
37
|
expect(newProject.nameDe()).toEqual("Test projekt")
|
|
35
38
|
expect(newProject.nameEn()).toEqual("Test project")
|
|
39
|
+
|
|
40
|
+
expect(newProjectDetail.note()).toEqual("Test note")
|
|
36
41
|
})
|
|
37
42
|
})
|
|
38
43
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Migration from "../../../../../src/database/migration/index.js"
|
|
2
|
+
|
|
3
|
+
export default class CreateProjectDetails extends Migration {
|
|
4
|
+
async up() {
|
|
5
|
+
await this.createTable("project_details", (t) => {
|
|
6
|
+
t.references("project", {foreignKey: true, null: false})
|
|
7
|
+
t.text("note")
|
|
8
|
+
t.timestamps()
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async down() {
|
|
13
|
+
await this.dropTable("project_details")
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/configuration.js
CHANGED
|
@@ -80,7 +80,7 @@ export default class VelociousConfiguration {
|
|
|
80
80
|
getEnvironment() { return digg(this, "_environment") }
|
|
81
81
|
setEnvironment(newEnvironment) { this._environment = newEnvironment }
|
|
82
82
|
|
|
83
|
-
getLocaleFallbacks
|
|
83
|
+
getLocaleFallbacks() { return this.localeFallbacks }
|
|
84
84
|
setLocaleFallbacks(newLocaleFallbacks) { this.localeFallbacks = newLocaleFallbacks }
|
|
85
85
|
|
|
86
86
|
getLocale() {
|
|
@@ -93,7 +93,7 @@ export default class VelociousConfiguration {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
getLocales
|
|
96
|
+
getLocales() { return digg(this, "locales") }
|
|
97
97
|
|
|
98
98
|
getModelClass(name) {
|
|
99
99
|
const modelClass = this.modelClasses[name]
|
|
@@ -115,8 +115,8 @@ export default class VelociousConfiguration {
|
|
|
115
115
|
this.databasePools[identifier].setCurrent()
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
isDatabasePoolInitialized
|
|
119
|
-
isInitialized
|
|
118
|
+
isDatabasePoolInitialized(identifier = "default") { return Boolean(this.databasePools[identifier]) }
|
|
119
|
+
isInitialized() { return this._isInitialized }
|
|
120
120
|
|
|
121
121
|
async initialize() {
|
|
122
122
|
if (!this.isInitialized()) {
|
|
@@ -2,9 +2,9 @@ import BaseForeignKey from "../base-foreign-key.js"
|
|
|
2
2
|
import {digg} from "diggerize"
|
|
3
3
|
|
|
4
4
|
export default class VelociousDatabaseDriversMssqlForeignKey extends BaseForeignKey {
|
|
5
|
-
getColumnName
|
|
6
|
-
getName
|
|
7
|
-
getTableName
|
|
8
|
-
getReferencedColumnName
|
|
9
|
-
getReferencedTableName
|
|
5
|
+
getColumnName() { return digg(this, "data", "ParentColumn") }
|
|
6
|
+
getName() { return digg(this, "data", "CONSTRAINT_NAME") }
|
|
7
|
+
getTableName() { return digg(this, "data", "TableName") }
|
|
8
|
+
getReferencedColumnName() { return digg(this, "data", "ReferencedColumn") }
|
|
9
|
+
getReferencedTableName() { return digg(this, "data", "ReferencedTable") }
|
|
10
10
|
}
|
|
@@ -82,8 +82,8 @@ export default class VelociousDatabaseDriversMssql extends Base{
|
|
|
82
82
|
return dropTable.toSql()
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
getType
|
|
86
|
-
primaryKeyType
|
|
85
|
+
getType() { return "mssql" }
|
|
86
|
+
primaryKeyType() { return "bigint" }
|
|
87
87
|
|
|
88
88
|
async query(sql) {
|
|
89
89
|
let result, request, tries = 0
|
|
@@ -16,7 +16,7 @@ export default class VelociousDatabaseDriversMssqlOptions extends QueryParserOpt
|
|
|
16
16
|
return this.driver.quote(string)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
quoteColumnName
|
|
19
|
+
quoteColumnName(string) {
|
|
20
20
|
if (string.includes("[") || string.includes("]")) throw new Error(`Possible SQL injection in column name: ${string}`)
|
|
21
21
|
|
|
22
22
|
return `[${string}]`
|
|
@@ -29,13 +29,13 @@ export default class VelociousDatabaseDriversMssqlOptions extends QueryParserOpt
|
|
|
29
29
|
return `[${databaseName}]`
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
quoteIndexName
|
|
32
|
+
quoteIndexName(string) {
|
|
33
33
|
if (string.includes("[") || string.includes("]")) throw new Error(`Possible SQL injection in index name: ${string}`)
|
|
34
34
|
|
|
35
35
|
return `[${string}]`
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
quoteTableName
|
|
38
|
+
quoteTableName(string) {
|
|
39
39
|
if (string.includes("[") || string.includes("]")) throw new Error(`Possible SQL injection in table name: ${string}`)
|
|
40
40
|
|
|
41
41
|
return `[${string}]`
|
|
@@ -2,9 +2,9 @@ import BaseForeignKey from "../base-foreign-key.js"
|
|
|
2
2
|
import {digg} from "diggerize"
|
|
3
3
|
|
|
4
4
|
export default class VelociousDatabaseDriversMysqlForeignKey extends BaseForeignKey {
|
|
5
|
-
getColumnName
|
|
6
|
-
getName
|
|
7
|
-
getTableName
|
|
8
|
-
getReferencedColumnName
|
|
9
|
-
getReferencedTableName
|
|
5
|
+
getColumnName() { return digg(this, "data", "COLUMN_NAME") }
|
|
6
|
+
getName() { return digg(this, "data", "CONSTRAINT_NAME") }
|
|
7
|
+
getTableName() { return digg(this, "data", "TABLE_NAME") }
|
|
8
|
+
getReferencedColumnName() { return digg(this, "data", "REFERENCED_COLUMN_NAME") }
|
|
9
|
+
getReferencedTableName() { return digg(this, "data", "REFERENCED_TABLE_NAME") }
|
|
10
10
|
}
|
|
@@ -91,8 +91,8 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
91
91
|
return dropTable.toSql()
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
getType
|
|
95
|
-
primaryKeyType
|
|
94
|
+
getType() { return "mysql" }
|
|
95
|
+
primaryKeyType() { return "bigint" }
|
|
96
96
|
|
|
97
97
|
async query(sql) {
|
|
98
98
|
try {
|
|
@@ -107,7 +107,7 @@ export default class VelociousDatabaseDriversMysql extends Base{
|
|
|
107
107
|
return new QueryParser({query}).toSql()
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
shouldSetAutoIncrementWhenPrimaryKey
|
|
110
|
+
shouldSetAutoIncrementWhenPrimaryKey() { return true }
|
|
111
111
|
|
|
112
112
|
escape(value) {
|
|
113
113
|
if (!this.connection) throw new Error("Can't escape before connected")
|
|
@@ -2,9 +2,9 @@ import BaseForeignKey from "../base-foreign-key.js"
|
|
|
2
2
|
import {digg} from "diggerize"
|
|
3
3
|
|
|
4
4
|
export default class VelociousDatabaseDriversPgsqlForeignKey extends BaseForeignKey {
|
|
5
|
-
getColumnName
|
|
6
|
-
getName
|
|
7
|
-
getTableName
|
|
8
|
-
getReferencedColumnName
|
|
9
|
-
getReferencedTableName
|
|
5
|
+
getColumnName() { return digg(this, "data", "column_name") }
|
|
6
|
+
getName() { return digg(this, "data", "constraint_name") }
|
|
7
|
+
getTableName() { return digg(this, "data", "table_name") }
|
|
8
|
+
getReferencedColumnName() { return digg(this, "data", "foreign_column_name") }
|
|
9
|
+
getReferencedTableName() { return digg(this, "data", "foreign_table_name") }
|
|
10
10
|
}
|
|
@@ -99,8 +99,8 @@ export default class VelociousDatabaseDriversPgsql extends Base{
|
|
|
99
99
|
return dropTable.toSql()
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
getType
|
|
103
|
-
primaryKeyType
|
|
102
|
+
getType() { return "pgsql" }
|
|
103
|
+
primaryKeyType() { return "bigint" }
|
|
104
104
|
|
|
105
105
|
async query(sql) {
|
|
106
106
|
let response
|
|
@@ -118,7 +118,7 @@ export default class VelociousDatabaseDriversPgsql extends Base{
|
|
|
118
118
|
return new QueryParser({query}).toSql()
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
shouldSetAutoIncrementWhenPrimaryKey
|
|
121
|
+
shouldSetAutoIncrementWhenPrimaryKey() { return true }
|
|
122
122
|
|
|
123
123
|
escape(value) {
|
|
124
124
|
if (!this.connection) throw new Error("Can't escape before connected")
|
|
@@ -71,7 +71,7 @@ export default class VelociousDatabaseQuery {
|
|
|
71
71
|
throw new Error("count multiple stub")
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
getOptions
|
|
74
|
+
getOptions() { return this.driver.options() }
|
|
75
75
|
|
|
76
76
|
async destroyAll() {
|
|
77
77
|
const records = await this.toArray()
|
|
@@ -255,7 +255,7 @@ export default class VelociousDatabaseQuery {
|
|
|
255
255
|
return models
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
toSql
|
|
258
|
+
toSql() { return this.driver.queryToSql(this) }
|
|
259
259
|
|
|
260
260
|
where(where) {
|
|
261
261
|
if (typeof where == "string") {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as inflection from "inflection"
|
|
2
|
+
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
|
+
|
|
4
|
+
export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
5
|
+
constructor({models, relationship, ...restArgs}) {
|
|
6
|
+
restArgsError(restArgs)
|
|
7
|
+
|
|
8
|
+
this.models = models
|
|
9
|
+
this.relationship = relationship
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run() {
|
|
13
|
+
const modelIds = []
|
|
14
|
+
const modelsById = {}
|
|
15
|
+
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
+
const foreignKeyCamelized = inflection.camelize(foreignKey, true)
|
|
17
|
+
const preloadCollections = {}
|
|
18
|
+
|
|
19
|
+
for (const model of this.models) {
|
|
20
|
+
preloadCollections[model.id()] = null
|
|
21
|
+
modelIds.push(model.id())
|
|
22
|
+
|
|
23
|
+
if (!(model.id in modelsById)) modelsById[model.id()] = []
|
|
24
|
+
|
|
25
|
+
modelsById[model.id()].push(model)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const whereArgs = {}
|
|
29
|
+
|
|
30
|
+
whereArgs[foreignKey] = modelIds
|
|
31
|
+
|
|
32
|
+
// Load target models to be preloaded on the given models
|
|
33
|
+
const targetModels = await this.relationship.getTargetModelClass().where(whereArgs).toArray()
|
|
34
|
+
|
|
35
|
+
for (const targetModel of targetModels) {
|
|
36
|
+
const foreignKeyValue = targetModel[foreignKeyCamelized]()
|
|
37
|
+
|
|
38
|
+
preloadCollections[foreignKeyValue] = targetModel
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Set the target preloaded models on the given models
|
|
42
|
+
for (const modelId in preloadCollections) {
|
|
43
|
+
const preloadedModel = preloadCollections[modelId]
|
|
44
|
+
|
|
45
|
+
for (const model of modelsById[modelId]) {
|
|
46
|
+
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
47
|
+
|
|
48
|
+
modelRelationship.setPreloaded(true)
|
|
49
|
+
modelRelationship.setLoaded(preloadedModel)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return targetModels
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import BelongsToPreloader from "./preloader/belongs-to.js"
|
|
2
2
|
import HasManyPreloader from "./preloader/has-many.js"
|
|
3
|
+
import HasOnePreloader from "./preloader/has-one.js"
|
|
3
4
|
import restArgsError from "../../utils/rest-args-error.js"
|
|
4
5
|
|
|
5
6
|
export default class VelociousDatabaseQueryPreloader {
|
|
@@ -24,6 +25,10 @@ export default class VelociousDatabaseQueryPreloader {
|
|
|
24
25
|
const hasManyPreloader = new HasManyPreloader({models: this.models, relationship: relationship})
|
|
25
26
|
|
|
26
27
|
targetModels = await hasManyPreloader.run()
|
|
28
|
+
} else if (relationship.getType() == "hasOne") {
|
|
29
|
+
const hasOnePreloader = new HasOnePreloader({models: this.models, relationship: relationship})
|
|
30
|
+
|
|
31
|
+
targetModels = await hasOnePreloader.run()
|
|
27
32
|
} else {
|
|
28
33
|
throw new Error(`Unknown relationship type: ${relationship.getType()}`)
|
|
29
34
|
}
|
|
@@ -5,6 +5,8 @@ import FromTable from "../query/from-table.js"
|
|
|
5
5
|
import Handler from "../handler.js"
|
|
6
6
|
import HasManyRelationship from "./relationships/has-many.js"
|
|
7
7
|
import HasManyInstanceRelationship from "./instance-relationships/has-many.js"
|
|
8
|
+
import HasOneRelationship from "./relationships/has-one.js"
|
|
9
|
+
import HasOneInstanceRelationship from "./instance-relationships/has-one.js"
|
|
8
10
|
import * as inflection from "inflection"
|
|
9
11
|
import Query from "../query/index.js"
|
|
10
12
|
import ValidatorsPresence from "./validators/presence.js"
|
|
@@ -104,6 +106,21 @@ class VelociousDatabaseRecord {
|
|
|
104
106
|
this.prototype[relationshipName] = function () {
|
|
105
107
|
return this.getRelationshipByName(relationshipName)
|
|
106
108
|
}
|
|
109
|
+
} else if (actualData.type == "hasOne") {
|
|
110
|
+
const buildMethodName = `build${inflection.camelize(relationshipName)}`
|
|
111
|
+
|
|
112
|
+
relationship = new HasOneRelationship(actualData)
|
|
113
|
+
|
|
114
|
+
this.prototype[relationshipName] = function () {
|
|
115
|
+
return this.getRelationshipByName(relationshipName).loaded()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.prototype[buildMethodName] = function (attributes) {
|
|
119
|
+
const relationship = this.getRelationshipByName(relationshipName)
|
|
120
|
+
const record = relationship.build(attributes)
|
|
121
|
+
|
|
122
|
+
return record
|
|
123
|
+
}
|
|
107
124
|
} else {
|
|
108
125
|
throw new Error(`Unknown relationship type: ${actualData.type}`)
|
|
109
126
|
}
|
|
@@ -132,14 +149,17 @@ class VelociousDatabaseRecord {
|
|
|
132
149
|
|
|
133
150
|
if (!(relationshipName in this._instanceRelationships)) {
|
|
134
151
|
const modelClassRelationship = this.constructor.getRelationshipByName(relationshipName)
|
|
152
|
+
const relationshipType = modelClassRelationship.getType()
|
|
135
153
|
let instanceRelationship
|
|
136
154
|
|
|
137
|
-
if (
|
|
155
|
+
if (relationshipType == "belongsTo") {
|
|
138
156
|
instanceRelationship = new BelongsToInstanceRelationship({model: this, relationship: modelClassRelationship})
|
|
139
|
-
} else if (
|
|
157
|
+
} else if (relationshipType == "hasMany") {
|
|
140
158
|
instanceRelationship = new HasManyInstanceRelationship({model: this, relationship: modelClassRelationship})
|
|
159
|
+
} else if (relationshipType == "hasOne") {
|
|
160
|
+
instanceRelationship = new HasOneInstanceRelationship({model: this, relationship: modelClassRelationship})
|
|
141
161
|
} else {
|
|
142
|
-
throw new Error(`Unknown relationship type: ${
|
|
162
|
+
throw new Error(`Unknown relationship type: ${relationshipType}`)
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
this._instanceRelationships[relationshipName] = instanceRelationship
|
|
@@ -189,6 +209,10 @@ class VelociousDatabaseRecord {
|
|
|
189
209
|
return this._defineRelationship(relationshipName, Object.assign({type: "hasMany"}, options))
|
|
190
210
|
}
|
|
191
211
|
|
|
212
|
+
static hasOne(relationshipName, options = {}) {
|
|
213
|
+
return this._defineRelationship(relationshipName, Object.assign({type: "hasOne"}, options))
|
|
214
|
+
}
|
|
215
|
+
|
|
192
216
|
static humanAttributeName(attributeName) {
|
|
193
217
|
const modelNameKey = inflection.underscore(this.constructor.name)
|
|
194
218
|
|
|
@@ -212,18 +236,31 @@ class VelociousDatabaseRecord {
|
|
|
212
236
|
|
|
213
237
|
const camelizedColumnName = inflection.camelize(column.getName(), true)
|
|
214
238
|
const camelizedColumnNameBigFirst = inflection.camelize(column.getName())
|
|
215
|
-
const setterMethodName = `set${camelizedColumnNameBigFirst}`
|
|
216
239
|
|
|
217
240
|
this._attributeNameToColumnName[camelizedColumnName] = column.getName()
|
|
218
241
|
this._columnNameToAttributeName[column.getName()] = camelizedColumnName
|
|
219
242
|
|
|
220
|
-
this.prototype[camelizedColumnName] = function
|
|
243
|
+
this.prototype[camelizedColumnName] = function() {
|
|
221
244
|
return this.readAttribute(camelizedColumnName)
|
|
222
245
|
}
|
|
223
246
|
|
|
224
|
-
this.prototype[
|
|
247
|
+
this.prototype[`set${camelizedColumnNameBigFirst}`] = function(newValue) {
|
|
225
248
|
return this._setColumnAttribute(camelizedColumnName, newValue)
|
|
226
249
|
}
|
|
250
|
+
|
|
251
|
+
this.prototype[`has${camelizedColumnNameBigFirst}`] = function() {
|
|
252
|
+
let value = this[camelizedColumnName]()
|
|
253
|
+
|
|
254
|
+
if (typeof value == "string") {
|
|
255
|
+
value = value.trim()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (value) {
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
227
264
|
}
|
|
228
265
|
|
|
229
266
|
await this._defineTranslationMethods()
|
|
@@ -361,7 +398,7 @@ class VelociousDatabaseRecord {
|
|
|
361
398
|
|
|
362
399
|
if (this.isPersisted()) {
|
|
363
400
|
// If any has-many-relationships will be saved, then updated-at should still be set on this record.
|
|
364
|
-
const autoSaveHasManyrelationships = this.
|
|
401
|
+
const autoSaveHasManyrelationships = this._autoSaveHasManyAndHasOneRelationshipsToSave()
|
|
365
402
|
|
|
366
403
|
if (this._hasChanges() || savedCount > 0 || autoSaveHasManyrelationships.length > 0) {
|
|
367
404
|
result = await this._updateRecordWithChanges()
|
|
@@ -370,7 +407,7 @@ class VelociousDatabaseRecord {
|
|
|
370
407
|
result = await this._createNewRecord()
|
|
371
408
|
}
|
|
372
409
|
|
|
373
|
-
await this.
|
|
410
|
+
await this._autoSaveHasManyAndHasOneRelationships({isNewRecord})
|
|
374
411
|
})
|
|
375
412
|
|
|
376
413
|
return result
|
|
@@ -404,19 +441,29 @@ class VelociousDatabaseRecord {
|
|
|
404
441
|
return {savedCount}
|
|
405
442
|
}
|
|
406
443
|
|
|
407
|
-
|
|
444
|
+
_autoSaveHasManyAndHasOneRelationshipsToSave() {
|
|
408
445
|
const relationships = []
|
|
409
446
|
|
|
410
447
|
for (const relationshipName in this._instanceRelationships) {
|
|
411
448
|
const instanceRelationship = this._instanceRelationships[relationshipName]
|
|
412
449
|
|
|
413
|
-
if (instanceRelationship.getType() != "hasMany") {
|
|
450
|
+
if (instanceRelationship.getType() != "hasMany" && instanceRelationship.getType() != "hasOne") {
|
|
414
451
|
continue
|
|
415
452
|
}
|
|
416
453
|
|
|
417
|
-
let loaded
|
|
454
|
+
let loaded
|
|
418
455
|
|
|
419
|
-
if (
|
|
456
|
+
if (instanceRelationship.getType() == "hasOne") {
|
|
457
|
+
const hasOneLoaded = instanceRelationship.getLoadedOrNull()
|
|
458
|
+
|
|
459
|
+
if (hasOneLoaded) {
|
|
460
|
+
loaded = [hasOneLoaded]
|
|
461
|
+
} else {
|
|
462
|
+
continue
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
loaded = instanceRelationship.getLoadedOrNull()
|
|
466
|
+
}
|
|
420
467
|
|
|
421
468
|
let useRelationship = false
|
|
422
469
|
|
|
@@ -437,8 +484,8 @@ class VelociousDatabaseRecord {
|
|
|
437
484
|
return relationships
|
|
438
485
|
}
|
|
439
486
|
|
|
440
|
-
async
|
|
441
|
-
for (const instanceRelationship of this.
|
|
487
|
+
async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
|
|
488
|
+
for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
|
|
442
489
|
let loaded = instanceRelationship._loaded
|
|
443
490
|
|
|
444
491
|
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
@@ -9,7 +9,7 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
9
9
|
this._dirty = newValue
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
getDirty
|
|
12
|
+
getDirty() { return this._dirty }
|
|
13
13
|
|
|
14
14
|
loaded() {
|
|
15
15
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
@@ -27,9 +27,9 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
27
27
|
this._preloaded = preloadedValue
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
getForeignKey
|
|
31
|
-
getPrimaryKey
|
|
32
|
-
getRelationship
|
|
33
|
-
getTargetModelClass
|
|
34
|
-
getType
|
|
30
|
+
getForeignKey() { return this.getRelationship().getForeignKey() }
|
|
31
|
+
getPrimaryKey() { return this.getRelationship().getPrimaryKey() }
|
|
32
|
+
getRelationship() { return this.relationship }
|
|
33
|
+
getTargetModelClass() { return this.getRelationship().getTargetModelClass() }
|
|
34
|
+
getType() { return this.getRelationship().getType() }
|
|
35
35
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import BaseInstanceRelationship from "./base.js"
|
|
2
|
+
|
|
3
|
+
export default class VelociousDatabaseRecordHasOneInstanceRelationship extends BaseInstanceRelationship {
|
|
4
|
+
constructor(args) {
|
|
5
|
+
super(args)
|
|
6
|
+
this._loaded = null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
build(data) {
|
|
10
|
+
const targetModelClass = this.getTargetModelClass()
|
|
11
|
+
const newInstance = new targetModelClass(data)
|
|
12
|
+
|
|
13
|
+
this._loaded = 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
|
+
getLoadedOrNull() {
|
|
27
|
+
return this._loaded
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setLoaded(model) {
|
|
31
|
+
if (Array.isArray(model)) throw new Error(`Argument given to setLoaded was an array: ${typeof model}`)
|
|
32
|
+
|
|
33
|
+
this._loaded = model
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getTargetModelClass = () => this.relationship.getTargetModelClass()
|
|
37
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import restArgsError from "../../../utils/rest-args-error.js"
|
|
2
|
+
|
|
1
3
|
export default class VelociousDatabaseRecordBaseRelationship {
|
|
2
|
-
constructor({className, configuration, dependent, foreignKey, klass, modelClass, relationshipName, through, type, ...restArgs}) {
|
|
3
|
-
|
|
4
|
+
constructor({className, configuration, dependent, foreignKey, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
|
|
5
|
+
restArgsError(restArgs)
|
|
4
6
|
|
|
5
|
-
if (restArgsKeys.length > 0) throw new Error(`Unknown args given: ${restArgsKeys.join(", ")}`)
|
|
6
7
|
if (!modelClass) throw new Error(`'modelClass' wasn't given for ${relationshipName}`)
|
|
7
8
|
if (!className && !klass) throw new Error(`Neither 'className' or 'klass' was given for ${modelClass.name}#${relationshipName}`)
|
|
8
9
|
|
|
@@ -12,15 +13,16 @@ export default class VelociousDatabaseRecordBaseRelationship {
|
|
|
12
13
|
this.foreignKey = foreignKey
|
|
13
14
|
this.klass = klass
|
|
14
15
|
this.modelClass = modelClass
|
|
16
|
+
this._primaryKey = primaryKey
|
|
15
17
|
this.relationshipName = relationshipName
|
|
16
18
|
this.through = through
|
|
17
19
|
this.type = type
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
getDependent
|
|
21
|
-
getRelationshipName
|
|
22
|
-
getPrimaryKey
|
|
23
|
-
getType
|
|
22
|
+
getDependent() { return this._dependent }
|
|
23
|
+
getRelationshipName() { return this.relationshipName }
|
|
24
|
+
getPrimaryKey() { return this._primaryKey }
|
|
25
|
+
getType() { return this.type }
|
|
24
26
|
|
|
25
27
|
getTargetModelClass() {
|
|
26
28
|
if (this.className) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import BaseRelationship from "./base.js"
|
|
2
|
+
import * as inflection from "inflection"
|
|
3
|
+
|
|
4
|
+
export default class VelociousDatabaseRecordHasOneRelationship extends BaseRelationship {
|
|
5
|
+
getForeignKey() {
|
|
6
|
+
if (!this.foreignKey) {
|
|
7
|
+
this.foreignKey = `${inflection.underscore(this.modelClass.name)}_id`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return this.foreignKey
|
|
11
|
+
}
|
|
12
|
+
}
|