velocious 1.0.65 → 1.0.66
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/README.md +25 -0
- package/package.json +1 -1
- package/run-tests.sh +1 -1
- package/spec/database/record/instance-relationships/belongs-to-relationship-spec.js +21 -0
- package/spec/database/record/instance-relationships/has-many-relationship-spec.js +24 -0
- package/spec/database/record/instance-relationships/has-one-relationship-spec.js +24 -0
- package/spec/database/record/preloader/belongs-to-spec.js +15 -0
- package/spec/database/record/preloader/has-many-spec.js +29 -0
- package/spec/database/record/preloader/has-one-spec.js +15 -0
- package/spec/database/record/query-spec.js +24 -0
- package/spec/dummy/src/database/migrations/20230728075328-create-projects.js +3 -2
- package/spec/dummy/src/database/migrations/20250912183605-create-users.js +1 -0
- package/spec/dummy/src/models/account.js +8 -0
- package/spec/dummy/src/models/project.js +1 -0
- package/spec/dummy/src/models/user.js +3 -0
- package/src/database/initializer-from-require-context.js +2 -0
- package/src/database/query/preloader/belongs-to.js +6 -10
- package/src/database/query/preloader/has-many.js +20 -14
- package/src/database/query/preloader/has-one.js +14 -13
- package/src/database/query/where-hash.js +25 -10
- package/src/database/record/index.js +40 -28
- package/src/database/record/instance-relationships/base.js +5 -12
- package/src/database/record/instance-relationships/belongs-to.js +11 -4
- package/src/database/record/instance-relationships/has-many.js +21 -1
- package/src/database/record/instance-relationships/has-one.js +16 -0
- package/src/http-server/worker-handler/index.js +2 -2
- package/src/testing/test-runner.js +6 -3
package/README.md
CHANGED
|
@@ -37,6 +37,31 @@ Task.validates("name", {presence: true, uniqueness: true})
|
|
|
37
37
|
export default Task
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Preloading relationships
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const tasks = await Task.preload({project: {translations: true}}).toArray()
|
|
44
|
+
const projectNames = tasks.map((task) => task.project().name())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Load a relationship after init
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const task = await Task.find(5)
|
|
51
|
+
|
|
52
|
+
await task.loadProject()
|
|
53
|
+
|
|
54
|
+
const projects = task.project()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const project = await Project.find(4)
|
|
59
|
+
|
|
60
|
+
await project.loadTasks()
|
|
61
|
+
|
|
62
|
+
const tasks = project.tasks().loaded()
|
|
63
|
+
```
|
|
64
|
+
|
|
40
65
|
# Migrations
|
|
41
66
|
|
|
42
67
|
Make a new migration from a template like this:
|
package/package.json
CHANGED
package/run-tests.sh
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import Task from "../../../dummy/src/models/task.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - belongs to relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const task = await Task.create({name: "Test task", project})
|
|
10
|
+
const foundTask = await Task.find(task.id())
|
|
11
|
+
const projectInstanceRelationship = foundTask.getRelationshipByName("project")
|
|
12
|
+
|
|
13
|
+
expect(projectInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundTask.loadProject()
|
|
16
|
+
|
|
17
|
+
expect(projectInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
expect(foundTask.project().id()).toEqual(project.id())
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import Task from "../../../dummy/src/models/task.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - has many relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const task = await Task.create({name: "Test task", project})
|
|
10
|
+
const foundProject = await Project.find(project.id())
|
|
11
|
+
const tasksInstanceRelationship = foundProject.getRelationshipByName("tasks")
|
|
12
|
+
|
|
13
|
+
expect(tasksInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundProject.loadTasks()
|
|
16
|
+
|
|
17
|
+
expect(tasksInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
|
|
19
|
+
const taskIDs = foundProject.tasks().loaded().map((task) => task.id())
|
|
20
|
+
|
|
21
|
+
expect(taskIDs).toEqual([task.id()])
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import ProjectDetail from "../../../dummy/src/models/project-detail.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - has one relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const projectDetail = await ProjectDetail.create({note: "Test project", project})
|
|
10
|
+
const foundProject = await Project.find(project.id())
|
|
11
|
+
const projectDetailInstanceRelationship = foundProject.getRelationshipByName("projectDetail")
|
|
12
|
+
|
|
13
|
+
expect(projectDetailInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundProject.loadProjectDetail()
|
|
16
|
+
|
|
17
|
+
expect(projectDetailInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
|
|
19
|
+
const projectsLoadedProjectDetail = foundProject.projectDetail()
|
|
20
|
+
|
|
21
|
+
expect(projectsLoadedProjectDetail.id()).toEqual(projectDetail.id())
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - belongs to", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundProject = await Project.preload({creatingUser: true}).find(project.id())
|
|
11
|
+
|
|
12
|
+
expect(foundProject.creatingUser().id()).toEqual(user.id())
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - has many", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundUser = await User.preload({createdProjects: true}).find(user.id())
|
|
11
|
+
const createdProjectsIDs = foundUser.createdProjects().loaded().map((createdProject) => createdProject.id())
|
|
12
|
+
|
|
13
|
+
expect(createdProjectsIDs).toEqual([project.id()])
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("preloads an empty array if nothing is found", async () => {
|
|
18
|
+
await Dummy.run(async () => {
|
|
19
|
+
// Differenre reference because nothing should be found as a kind of smoke test
|
|
20
|
+
await Project.create({creating_user_reference: "User-69"})
|
|
21
|
+
|
|
22
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
23
|
+
const foundUser = await User.preload({createdProjects: true}).find(user.id())
|
|
24
|
+
const createdProjectsIDs = foundUser.createdProjects().loaded().map((createdProject) => createdProject.id())
|
|
25
|
+
|
|
26
|
+
expect(createdProjectsIDs).toEqual([])
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - has one", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundUser = await User.preload({createdProject: true}).find(user.id())
|
|
11
|
+
|
|
12
|
+
expect(foundUser.createdProject().id()).toEqual([project.id()])
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -75,6 +75,30 @@ describe("Record - query", () => {
|
|
|
75
75
|
})
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
+
it("finds the record with joins and where hashes", async () => {
|
|
79
|
+
await Dummy.run(async () => {
|
|
80
|
+
const project1 = await Project.create({name: "Test project 1"})
|
|
81
|
+
const project2 = await Project.create({name: "Test project 2"})
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < 5; i++) {
|
|
84
|
+
await Task.create({name: `Task 1-${i}`, project: project1})
|
|
85
|
+
await Task.create({name: `Task 2-${i}`, project: project2})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tasks = await Task
|
|
89
|
+
.joins({project: {translations: true}})
|
|
90
|
+
.where({tasks: {name: "Task 2-2"}, project_translations: {name: "Test project 2"}})
|
|
91
|
+
.preload({project: {translations: true}})
|
|
92
|
+
.toArray()
|
|
93
|
+
|
|
94
|
+
const task = tasks[0]
|
|
95
|
+
|
|
96
|
+
expect(tasks.length).toEqual(1)
|
|
97
|
+
expect(task.name()).toEqual("Task 2-2")
|
|
98
|
+
expect(task.project().name()).toEqual("Test project 2")
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
78
102
|
it("counts the records", async () => {
|
|
79
103
|
await Dummy.run(async () => {
|
|
80
104
|
const taskIDs = []
|
|
@@ -2,8 +2,9 @@ import Migration from "../../../../../src/database/migration/index.js"
|
|
|
2
2
|
|
|
3
3
|
export default class CreateProjects extends Migration {
|
|
4
4
|
async change() {
|
|
5
|
-
await this.createTable("projects", (
|
|
6
|
-
|
|
5
|
+
await this.createTable("projects", (t) => {
|
|
6
|
+
t.string("creating_user_reference")
|
|
7
|
+
t.timestamps()
|
|
7
8
|
})
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -3,6 +3,7 @@ import DatabaseRecord from "../../../../src/database/record/index.js"
|
|
|
3
3
|
class Project extends DatabaseRecord {
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
Project.belongsTo("creatingUser", {className: "User", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
6
7
|
Project.hasMany("tasks")
|
|
7
8
|
Project.hasOne("projectDetail")
|
|
8
9
|
Project.translates("name")
|
|
@@ -4,7 +4,10 @@ import UserModule from "../../../../src/database/record/user-module.js"
|
|
|
4
4
|
class User extends Record {
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
User.hasOne("createdProject", {className: "Project", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
8
|
+
|
|
7
9
|
User.hasMany("authenticationTokens" , {dependent: "destroy"})
|
|
10
|
+
User.hasMany("createdProjects", {className: "Project", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
8
11
|
|
|
9
12
|
const userModule = new UserModule({
|
|
10
13
|
secretKey: "02e383b7-aad1-437c-b1e1-17c0240ad851"
|
|
@@ -15,6 +15,8 @@ export default class VelociousDatabaseInitializerFromRequireContext {
|
|
|
15
15
|
|
|
16
16
|
const modelClass = modelClassImport.default
|
|
17
17
|
|
|
18
|
+
if (!modelClass) throw new Error(`Model wasn't exported from: ${fileName}`)
|
|
19
|
+
|
|
18
20
|
await modelClass.initializeRecord({configuration})
|
|
19
21
|
|
|
20
22
|
if (await modelClass.hasTranslationsTable()) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
@@ -11,17 +10,12 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
12
|
const foreignKeyValues = []
|
|
14
|
-
const modelsById = {}
|
|
15
13
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const foreignKeyCamelized = inflection.camelize(foreignKey, true)
|
|
17
|
-
const preloadCollections = {}
|
|
18
14
|
|
|
19
15
|
for (const model of this.models) {
|
|
20
|
-
const foreignKeyValue = model
|
|
16
|
+
const foreignKeyValue = model.readColumn(foreignKey)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
foreignKeyValues.push(foreignKeyValue)
|
|
24
|
-
modelsById[model.id()] = model
|
|
18
|
+
if (!foreignKeyValues.includes(foreignKeyValue)) foreignKeyValues.push(foreignKeyValue)
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
const whereArgs = {}
|
|
@@ -34,12 +28,14 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
34
28
|
const targetModelsById = {}
|
|
35
29
|
|
|
36
30
|
for (const targetModel of targetModels) {
|
|
37
|
-
|
|
31
|
+
const primaryKeyValue = targetModel.readColumn(this.relationship.getPrimaryKey())
|
|
32
|
+
|
|
33
|
+
targetModelsById[primaryKeyValue] = targetModel
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
// Set the target preloaded models on the given models
|
|
41
37
|
for (const model of this.models) {
|
|
42
|
-
const foreignKeyValue = model
|
|
38
|
+
const foreignKeyValue = model.readColumn(foreignKey)
|
|
43
39
|
const targetModel = targetModelsById[foreignKeyValue]
|
|
44
40
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
45
41
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
@@ -10,43 +9,50 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const modelsPrimaryKeyValues = []
|
|
13
|
+
const modelsByPrimaryKeyValue = {}
|
|
15
14
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const
|
|
15
|
+
const primaryKey = this.relationship.getPrimaryKey()
|
|
17
16
|
const preloadCollections = {}
|
|
18
17
|
|
|
19
18
|
for (const model of this.models) {
|
|
20
|
-
|
|
21
|
-
modelIds.push(model.id())
|
|
19
|
+
const primaryKeyValue = model.readColumn(primaryKey)
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
preloadCollections[primaryKeyValue] = []
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
if (!modelsPrimaryKeyValues.includes(primaryKeyValue)) modelsPrimaryKeyValues.push(primaryKeyValue)
|
|
24
|
+
if (!(primaryKeyValue in modelsByPrimaryKeyValue)) modelsByPrimaryKeyValue[primaryKeyValue] = []
|
|
25
|
+
|
|
26
|
+
modelsByPrimaryKeyValue[primaryKeyValue].push(model)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const whereArgs = {}
|
|
29
30
|
|
|
30
|
-
whereArgs[foreignKey] =
|
|
31
|
+
whereArgs[foreignKey] = modelsPrimaryKeyValues
|
|
31
32
|
|
|
32
33
|
// Load target models to be preloaded on the given models
|
|
33
34
|
const targetModels = await this.relationship.getTargetModelClass().where(whereArgs).toArray()
|
|
34
35
|
|
|
35
36
|
for (const targetModel of targetModels) {
|
|
36
|
-
const foreignKeyValue = targetModel
|
|
37
|
+
const foreignKeyValue = targetModel.readColumn(foreignKey)
|
|
37
38
|
|
|
38
39
|
preloadCollections[foreignKeyValue].push(targetModel)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Set the target preloaded models on the given models
|
|
42
|
-
for (const
|
|
43
|
-
const preloadedCollection = preloadCollections[
|
|
43
|
+
for (const modelValue in preloadCollections) {
|
|
44
|
+
const preloadedCollection = preloadCollections[modelValue]
|
|
44
45
|
|
|
45
|
-
for (const model of
|
|
46
|
+
for (const model of modelsByPrimaryKeyValue[modelValue]) {
|
|
46
47
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
47
48
|
|
|
48
49
|
modelRelationship.setPreloaded(true)
|
|
49
|
-
|
|
50
|
+
|
|
51
|
+
if (preloadedCollection.length == 0) {
|
|
52
|
+
modelRelationship.setLoaded([])
|
|
53
|
+
} else {
|
|
54
|
+
modelRelationship.addToLoaded(preloadedCollection)
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
@@ -10,39 +9,41 @@ export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const modelsPrimaryKeyValues = []
|
|
13
|
+
const modelsByPrimaryKeyValue = {}
|
|
15
14
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const
|
|
15
|
+
const primaryKey = this.relationship.getPrimaryKey()
|
|
17
16
|
const preloadCollections = {}
|
|
18
17
|
|
|
19
18
|
for (const model of this.models) {
|
|
20
|
-
|
|
21
|
-
modelIds.push(model.id())
|
|
19
|
+
const primaryKeyValue = model.readColumn(primaryKey)
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
preloadCollections[primaryKeyValue] = null
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
if (!modelsPrimaryKeyValues.includes(primaryKeyValue)) modelsPrimaryKeyValues.push(primaryKeyValue)
|
|
24
|
+
if (!(primaryKeyValue in modelsByPrimaryKeyValue)) modelsByPrimaryKeyValue[primaryKeyValue] = []
|
|
25
|
+
|
|
26
|
+
modelsByPrimaryKeyValue[primaryKeyValue].push(model)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const whereArgs = {}
|
|
29
30
|
|
|
30
|
-
whereArgs[foreignKey] =
|
|
31
|
+
whereArgs[foreignKey] = modelsPrimaryKeyValues
|
|
31
32
|
|
|
32
33
|
// Load target models to be preloaded on the given models
|
|
33
34
|
const targetModels = await this.relationship.getTargetModelClass().where(whereArgs).toArray()
|
|
34
35
|
|
|
35
36
|
for (const targetModel of targetModels) {
|
|
36
|
-
const foreignKeyValue = targetModel
|
|
37
|
+
const foreignKeyValue = targetModel.readColumn(foreignKey)
|
|
37
38
|
|
|
38
39
|
preloadCollections[foreignKeyValue] = targetModel
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Set the target preloaded models on the given models
|
|
42
|
-
for (const
|
|
43
|
-
const preloadedModel = preloadCollections[
|
|
43
|
+
for (const modelValue in preloadCollections) {
|
|
44
|
+
const preloadedModel = preloadCollections[modelValue]
|
|
44
45
|
|
|
45
|
-
for (const model of
|
|
46
|
+
for (const model of modelsByPrimaryKeyValue[modelValue]) {
|
|
46
47
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
47
48
|
|
|
48
49
|
modelRelationship.setPreloaded(true)
|
|
@@ -8,28 +8,43 @@ export default class VelociousDatabaseQueryWhereHash extends WhereBase {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
toSql() {
|
|
11
|
-
const options = this.getOptions()
|
|
12
11
|
let sql = "("
|
|
12
|
+
|
|
13
|
+
sql += this._whereSQLFromHash(this.hash)
|
|
14
|
+
sql += ")"
|
|
15
|
+
|
|
16
|
+
return sql
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_whereSQLFromHash(hash, tableName) {
|
|
20
|
+
const options = this.getOptions()
|
|
21
|
+
let sql = ""
|
|
13
22
|
let index = 0
|
|
14
23
|
|
|
15
|
-
for (const whereKey in
|
|
16
|
-
const whereValue =
|
|
24
|
+
for (const whereKey in hash) {
|
|
25
|
+
const whereValue = hash[whereKey]
|
|
17
26
|
|
|
18
27
|
if (index > 0) sql += " AND "
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (Array.isArray(whereValue)) {
|
|
23
|
-
sql += ` IN (${whereValue.map((value) => options.quote(value)).join(", ")})`
|
|
29
|
+
if (!Array.isArray(whereValue) && typeof whereValue == "object") {
|
|
30
|
+
sql += this._whereSQLFromHash(whereValue, whereKey)
|
|
24
31
|
} else {
|
|
25
|
-
|
|
32
|
+
if (tableName) {
|
|
33
|
+
sql += `${options.quoteTableName(tableName)}.`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
sql += `${options.quoteColumnName(whereKey)}`
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(whereValue)) {
|
|
39
|
+
sql += ` IN (${whereValue.map((value) => options.quote(value)).join(", ")})`
|
|
40
|
+
} else {
|
|
41
|
+
sql += ` = ${options.quote(whereValue)}`
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
index++
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
sql += ")"
|
|
32
|
-
|
|
33
48
|
return sql
|
|
34
49
|
}
|
|
35
50
|
}
|
|
@@ -78,23 +78,24 @@ class VelociousDatabaseRecord {
|
|
|
78
78
|
if (actualData.type == "belongsTo") {
|
|
79
79
|
relationship = new BelongsToRelationship(actualData)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
const setMethodName = `set${inflection.camelize(relationshipName)}`
|
|
83
|
-
|
|
84
|
-
this.prototype[relationshipName] = function () {
|
|
81
|
+
this.prototype[relationshipName] = function() {
|
|
85
82
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
86
83
|
|
|
87
84
|
return relationship.loaded()
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
this.prototype[
|
|
87
|
+
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
91
88
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
92
89
|
const record = relationship.build(attributes)
|
|
93
90
|
|
|
94
91
|
return record
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
this.prototype[
|
|
94
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
95
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.prototype[`set${inflection.camelize(relationshipName)}`] = function(model) {
|
|
98
99
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
99
100
|
|
|
100
101
|
relationship.setLoaded(model)
|
|
@@ -103,24 +104,30 @@ class VelociousDatabaseRecord {
|
|
|
103
104
|
} else if (actualData.type == "hasMany") {
|
|
104
105
|
relationship = new HasManyRelationship(actualData)
|
|
105
106
|
|
|
106
|
-
this.prototype[relationshipName] = function
|
|
107
|
+
this.prototype[relationshipName] = function() {
|
|
107
108
|
return this.getRelationshipByName(relationshipName)
|
|
108
109
|
}
|
|
109
|
-
} else if (actualData.type == "hasOne") {
|
|
110
|
-
const buildMethodName = `build${inflection.camelize(relationshipName)}`
|
|
111
110
|
|
|
111
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
112
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
113
|
+
}
|
|
114
|
+
} else if (actualData.type == "hasOne") {
|
|
112
115
|
relationship = new HasOneRelationship(actualData)
|
|
113
116
|
|
|
114
|
-
this.prototype[relationshipName] = function
|
|
117
|
+
this.prototype[relationshipName] = function() {
|
|
115
118
|
return this.getRelationshipByName(relationshipName).loaded()
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
this.prototype[
|
|
121
|
+
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
119
122
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
120
123
|
const record = relationship.build(attributes)
|
|
121
124
|
|
|
122
125
|
return record
|
|
123
126
|
}
|
|
127
|
+
|
|
128
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
129
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
130
|
+
}
|
|
124
131
|
} else {
|
|
125
132
|
throw new Error(`Unknown relationship type: ${actualData.type}`)
|
|
126
133
|
}
|
|
@@ -168,8 +175,8 @@ class VelociousDatabaseRecord {
|
|
|
168
175
|
return this._instanceRelationships[relationshipName]
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
static belongsTo(relationshipName) {
|
|
172
|
-
this._defineRelationship(relationshipName, {type: "belongsTo"})
|
|
178
|
+
static belongsTo(relationshipName, options) {
|
|
179
|
+
this._defineRelationship(relationshipName, Object.assign({type: "belongsTo"}, options))
|
|
173
180
|
}
|
|
174
181
|
|
|
175
182
|
static connection() {
|
|
@@ -219,11 +226,14 @@ class VelociousDatabaseRecord {
|
|
|
219
226
|
return this._getConfiguration().getTranslator()(`velocious.database.record.attributes.${modelNameKey}.${attributeName}`, {defaultValue: inflection.camelize(attributeName)})
|
|
220
227
|
}
|
|
221
228
|
|
|
229
|
+
static getDatabaseType() { return this._databaseType }
|
|
230
|
+
|
|
222
231
|
static async initializeRecord({configuration}) {
|
|
223
232
|
if (!configuration) throw new Error(`No configuration given for ${this.name}`)
|
|
224
233
|
|
|
225
234
|
this._configuration = configuration
|
|
226
235
|
this._configuration.registerModelClass(this)
|
|
236
|
+
this._databaseType = this.connection().getType()
|
|
227
237
|
|
|
228
238
|
this._table = await this.connection().getTableByName(this.tableName())
|
|
229
239
|
this._columns = await this._getTable().getColumns()
|
|
@@ -285,13 +295,13 @@ class VelociousDatabaseRecord {
|
|
|
285
295
|
const nameCamelized = inflection.camelize(name)
|
|
286
296
|
const setterMethodName = `set${nameCamelized}`
|
|
287
297
|
|
|
288
|
-
this.prototype[name] = function
|
|
298
|
+
this.prototype[name] = function() {
|
|
289
299
|
const locale = this._getConfiguration().getLocale()
|
|
290
300
|
|
|
291
301
|
return this._getTranslatedAttributeWithFallback(name, locale)
|
|
292
302
|
}
|
|
293
303
|
|
|
294
|
-
this.prototype[setterMethodName] = function
|
|
304
|
+
this.prototype[setterMethodName] = function(newValue) {
|
|
295
305
|
const locale = this._getConfiguration().getLocale()
|
|
296
306
|
|
|
297
307
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
@@ -302,11 +312,11 @@ class VelociousDatabaseRecord {
|
|
|
302
312
|
const getterMethodNameLocalized = `${name}${localeCamelized}`
|
|
303
313
|
const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
|
|
304
314
|
|
|
305
|
-
this.prototype[getterMethodNameLocalized] = function
|
|
315
|
+
this.prototype[getterMethodNameLocalized] = function() {
|
|
306
316
|
return this._getTranslatedAttribute(name, locale)
|
|
307
317
|
}
|
|
308
318
|
|
|
309
|
-
this.prototype[setterMethodNameLocalized] = function
|
|
319
|
+
this.prototype[setterMethodNameLocalized] = function(newValue) {
|
|
310
320
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
311
321
|
}
|
|
312
322
|
}
|
|
@@ -469,14 +479,16 @@ class VelociousDatabaseRecord {
|
|
|
469
479
|
|
|
470
480
|
let useRelationship = false
|
|
471
481
|
|
|
472
|
-
|
|
473
|
-
const
|
|
482
|
+
if (loaded) {
|
|
483
|
+
for (const model of loaded) {
|
|
484
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
474
485
|
|
|
475
|
-
|
|
486
|
+
model.setAttribute(foreignKey, this.id())
|
|
476
487
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
488
|
+
if (model.isChanged()) {
|
|
489
|
+
useRelationship = true
|
|
490
|
+
continue
|
|
491
|
+
}
|
|
480
492
|
}
|
|
481
493
|
}
|
|
482
494
|
|
|
@@ -827,7 +839,7 @@ class VelociousDatabaseRecord {
|
|
|
827
839
|
readAttribute(attributeName) {
|
|
828
840
|
const columnName = this.constructor._attributeNameToColumnName[attributeName]
|
|
829
841
|
|
|
830
|
-
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName}`)
|
|
842
|
+
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.constructor._attributeNameToColumnName)}`)
|
|
831
843
|
|
|
832
844
|
return this.readColumn(columnName)
|
|
833
845
|
}
|
|
@@ -844,7 +856,7 @@ class VelociousDatabaseRecord {
|
|
|
844
856
|
throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
|
|
845
857
|
}
|
|
846
858
|
|
|
847
|
-
if (column && this.constructor.
|
|
859
|
+
if (column && this.constructor.getDatabaseType() == "sqlite") {
|
|
848
860
|
if (column.getType() == "date" || column.getType() == "datetime") {
|
|
849
861
|
result = new Date(Date.parse(result))
|
|
850
862
|
}
|
|
@@ -929,9 +941,9 @@ class VelociousDatabaseRecord {
|
|
|
929
941
|
}
|
|
930
942
|
}
|
|
931
943
|
|
|
932
|
-
id
|
|
933
|
-
isPersisted
|
|
934
|
-
isNewRecord
|
|
944
|
+
id() { return this.readAttribute(this.constructor._columnNameToAttributeName[this.constructor.primaryKey()]) }
|
|
945
|
+
isPersisted() { return !this._isNewRecord }
|
|
946
|
+
isNewRecord() { return this._isNewRecord }
|
|
935
947
|
|
|
936
948
|
setIsNewRecord(newIsNewRecord) {
|
|
937
949
|
this._isNewRecord = newIsNewRecord
|
|
@@ -5,11 +5,9 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
5
5
|
this.relationship = relationship
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
setDirty(newValue) {
|
|
9
|
-
this._dirty = newValue
|
|
10
|
-
}
|
|
11
|
-
|
|
8
|
+
setDirty(newValue) { this._dirty = newValue }
|
|
12
9
|
getDirty() { return this._dirty }
|
|
10
|
+
isLoaded() { return Boolean(this._loaded) }
|
|
13
11
|
|
|
14
12
|
loaded() {
|
|
15
13
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
@@ -19,15 +17,10 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
19
17
|
return this._loaded
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
setLoaded(model) {
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
setPreloaded(preloadedValue) {
|
|
27
|
-
this._preloaded = preloadedValue
|
|
28
|
-
}
|
|
29
|
-
|
|
20
|
+
setLoaded(model) { this._loaded = model }
|
|
21
|
+
setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
|
|
30
22
|
getForeignKey() { return this.getRelationship().getForeignKey() }
|
|
23
|
+
getModel() { return this.model }
|
|
31
24
|
getPrimaryKey() { return this.getRelationship().getPrimaryKey() }
|
|
32
25
|
getRelationship() { return this.relationship }
|
|
33
26
|
getTargetModelClass() { return this.getRelationship().getTargetModelClass() }
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import BaseInstanceRelationship from "./base.js"
|
|
2
2
|
|
|
3
3
|
export default class VelociousDatabaseRecordBelongsToInstanceRelationship extends BaseInstanceRelationship {
|
|
4
|
-
constructor(args) {
|
|
5
|
-
super(args)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
4
|
build(data) {
|
|
9
5
|
const targetModelClass = this.getTargetModelClass()
|
|
10
6
|
const newInstance = new targetModelClass(data)
|
|
@@ -17,4 +13,15 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
|
|
|
17
13
|
getLoadedOrNull() {
|
|
18
14
|
return this._loaded
|
|
19
15
|
}
|
|
16
|
+
|
|
17
|
+
async load() {
|
|
18
|
+
const foreignKey = this.getForeignKey()
|
|
19
|
+
const foreignModelID = this.getModel().readColumn(foreignKey)
|
|
20
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
21
|
+
const foreignModel = await TargetModelClass.find(foreignModelID)
|
|
22
|
+
|
|
23
|
+
this.setLoaded(foreignModel)
|
|
24
|
+
this.setDirty(false)
|
|
25
|
+
this.setPreloaded(true)
|
|
26
|
+
}
|
|
20
27
|
}
|
|
@@ -3,18 +3,36 @@ import BaseInstanceRelationship from "./base.js"
|
|
|
3
3
|
export default class VelociousDatabaseRecordHasManyInstanceRelationship extends BaseInstanceRelationship {
|
|
4
4
|
constructor(args) {
|
|
5
5
|
super(args)
|
|
6
|
-
this._loaded =
|
|
6
|
+
this._loaded = null
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
build(data) {
|
|
10
10
|
const targetModelClass = this.getTargetModelClass()
|
|
11
11
|
const newInstance = new targetModelClass(data)
|
|
12
12
|
|
|
13
|
+
if (this._loaded === null) this._loaded = []
|
|
14
|
+
|
|
13
15
|
this._loaded.push(newInstance)
|
|
14
16
|
|
|
15
17
|
return newInstance
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
async load() {
|
|
21
|
+
const foreignKey = this.getForeignKey()
|
|
22
|
+
const primaryKey = this.getPrimaryKey()
|
|
23
|
+
const primaryModelID = this.getModel().readColumn(primaryKey)
|
|
24
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
25
|
+
const whereArgs = {}
|
|
26
|
+
|
|
27
|
+
whereArgs[foreignKey] = primaryModelID
|
|
28
|
+
|
|
29
|
+
const foreignModels = await TargetModelClass.where(whereArgs).toArray()
|
|
30
|
+
|
|
31
|
+
this.setLoaded(foreignModels)
|
|
32
|
+
this.setDirty(false)
|
|
33
|
+
this.setPreloaded(true)
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
loaded() {
|
|
19
37
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
20
38
|
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
@@ -30,6 +48,8 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
30
48
|
addToLoaded(models) {
|
|
31
49
|
if (Array.isArray(models)) {
|
|
32
50
|
for (const model of models) {
|
|
51
|
+
if (this._loaded === null) this._loaded = []
|
|
52
|
+
|
|
33
53
|
this._loaded.push(model)
|
|
34
54
|
}
|
|
35
55
|
} else {
|
|
@@ -15,6 +15,22 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
15
15
|
return newInstance
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
async load() {
|
|
19
|
+
const foreignKey = this.getForeignKey()
|
|
20
|
+
const primaryKey = this.getPrimaryKey()
|
|
21
|
+
const primaryModelID = this.getModel().readColumn(primaryKey)
|
|
22
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
23
|
+
const whereArgs = {}
|
|
24
|
+
|
|
25
|
+
whereArgs[foreignKey] = primaryModelID
|
|
26
|
+
|
|
27
|
+
const foreignModel = await TargetModelClass.where(whereArgs).first()
|
|
28
|
+
|
|
29
|
+
this.setLoaded(foreignModel)
|
|
30
|
+
this.setDirty(false)
|
|
31
|
+
this.setPreloaded(true)
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
loaded() {
|
|
19
35
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
20
36
|
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
@@ -74,11 +74,11 @@ export default class VelociousHttpServerWorker {
|
|
|
74
74
|
|
|
75
75
|
const {clientCount, output} = digs(data, "clientCount", "output")
|
|
76
76
|
|
|
77
|
-
this.clients[clientCount]
|
|
77
|
+
this.clients[clientCount]?.send(output)
|
|
78
78
|
} else if (command == "clientClose") {
|
|
79
79
|
const {clientCount} = digs(data, "clientCount")
|
|
80
80
|
|
|
81
|
-
this.clients[clientCount]
|
|
81
|
+
this.clients[clientCount]?.close()
|
|
82
82
|
} else {
|
|
83
83
|
throw new Error(`Unknown command: ${command}`)
|
|
84
84
|
}
|
|
@@ -138,8 +138,11 @@ export default class TestRunner {
|
|
|
138
138
|
|
|
139
139
|
if (this._onlyFocussed && !testArgs.focus) continue
|
|
140
140
|
|
|
141
|
-
if (testArgs.type == "request") {
|
|
141
|
+
if (testArgs.type == "model" || testArgs.type == "request") {
|
|
142
142
|
testArgs.application = await this.application()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (testArgs.type == "request") {
|
|
143
146
|
testArgs.client = await this.requestClient()
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -147,7 +150,7 @@ export default class TestRunner {
|
|
|
147
150
|
|
|
148
151
|
try {
|
|
149
152
|
for (const beforeEachData of newBeforeEaches) {
|
|
150
|
-
await beforeEachData.callback({testArgs, testData})
|
|
153
|
+
await beforeEachData.callback({configuration: this.configuration, testArgs, testData})
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
await testData.function(testArgs)
|
|
@@ -167,7 +170,7 @@ export default class TestRunner {
|
|
|
167
170
|
}
|
|
168
171
|
} finally {
|
|
169
172
|
for (const afterEachData of newAfterEaches) {
|
|
170
|
-
await afterEachData.callback({testArgs, testData})
|
|
173
|
+
await afterEachData.callback({configuration: this.configuration, testArgs, testData})
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
}
|