velocious 1.0.65 → 1.0.67

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.
Files changed (32) hide show
  1. package/README.md +25 -0
  2. package/package.json +1 -1
  3. package/run-tests.sh +1 -1
  4. package/spec/database/record/create-spec.js +42 -1
  5. package/spec/database/record/instance-relationships/belongs-to-relationship-spec.js +21 -0
  6. package/spec/database/record/instance-relationships/has-many-relationship-spec.js +24 -0
  7. package/spec/database/record/instance-relationships/has-one-relationship-spec.js +24 -0
  8. package/spec/database/record/preloader/belongs-to-spec.js +15 -0
  9. package/spec/database/record/preloader/has-many-spec.js +29 -0
  10. package/spec/database/record/preloader/has-one-spec.js +15 -0
  11. package/spec/database/record/query-spec.js +24 -0
  12. package/spec/dummy/src/database/migrations/20230728075328-create-projects.js +3 -2
  13. package/spec/dummy/src/database/migrations/20250912183605-create-users.js +1 -0
  14. package/spec/dummy/src/models/account.js +8 -0
  15. package/spec/dummy/src/models/project.js +1 -0
  16. package/spec/dummy/src/models/user.js +3 -0
  17. package/src/database/initializer-from-require-context.js +2 -0
  18. package/src/database/query/preloader/belongs-to.js +6 -10
  19. package/src/database/query/preloader/has-many.js +20 -14
  20. package/src/database/query/preloader/has-one.js +14 -13
  21. package/src/database/query/where-hash.js +25 -10
  22. package/src/database/record/index.js +91 -33
  23. package/src/database/record/instance-relationships/base.js +9 -12
  24. package/src/database/record/instance-relationships/belongs-to.js +11 -6
  25. package/src/database/record/instance-relationships/has-many.js +53 -5
  26. package/src/database/record/instance-relationships/has-one.js +18 -4
  27. package/src/database/record/relationships/base.js +2 -1
  28. package/src/database/record/relationships/belongs-to.js +24 -0
  29. package/src/database/record/relationships/has-many.js +21 -0
  30. package/src/database/record/relationships/has-one.js +21 -0
  31. package/src/http-server/worker-handler/index.js +2 -2
  32. 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
@@ -3,7 +3,7 @@
3
3
  "velocious": "bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.65",
6
+ "version": "1.0.67",
7
7
  "main": "index.js",
8
8
  "scripts": {
9
9
  "test": "VELOCIOUS_TEST_DIR=../ cd spec/dummy && npx velocious test",
package/run-tests.sh CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
 
3
- VELOCIOUS_TEST_DIR=/home/dev/Development/velocious/spec cd spec/dummy && npx velocious test
3
+ cd spec/dummy && VELOCIOUS_TEST_DIR=/home/dev/Development/velocious/spec npx velocious test
4
4
 
@@ -29,14 +29,55 @@ describe("Record - create", () => {
29
29
  // 'name' is not a column but rather a column on the translation data model.
30
30
  expect(() => project.readColumn("name")).toThrowError("No such attribute or not selected Project#name")
31
31
 
32
- // It saves a project note
32
+
33
+ // It saves a project note through a has one relationship
33
34
  const projectDetail = project.projectDetail()
34
35
 
36
+ expect(projectDetail.isNewRecord()).toBeFalse()
37
+ expect(projectDetail.isPersisted()).toBeTrue()
35
38
  expect(projectDetail.note()).toEqual("Test note")
36
39
  expect(projectDetail.projectId()).toEqual(project.id())
40
+
41
+
42
+ // It automatically sets the relationship that saved it on a has-one-relationship
43
+ const projectInstanceRelationship = projectDetail.getRelationshipByName("project")
44
+
45
+ expect(projectInstanceRelationship.getPreloaded()).toBeTrue()
46
+ expect(projectDetail.project().id()).toEqual(project.id())
47
+
48
+
49
+ // It automatically sets the relationship that saved it on a has-many-relationship
50
+ const tasksRelationship = project.getRelationshipByName("tasks")
51
+
52
+ expect(tasksRelationship.getPreloaded()).toBeTrue()
53
+
54
+ const projectTasksIDs = project.tasks().loaded().map((task) => task.id())
55
+
56
+ expect(projectTasksIDs).toEqual([task.id()])
37
57
  })
38
58
  })
39
59
 
60
+ it("sets the inversed relationship on has-many-relationships", async () => {
61
+ const project = new Project({name: "Test project"})
62
+
63
+ project.tasks().build({name: "Test task 1"})
64
+ project.tasks().build({name: "Test task 2"})
65
+
66
+ await project.save()
67
+
68
+ const tasks = project.tasks().loaded()
69
+ const task1 = tasks.find((task) => task.name() == "Test task 1")
70
+ const task2 = tasks.find((task) => task.name() == "Test task 2")
71
+
72
+ expect(tasks.length).toEqual(2)
73
+
74
+ expect(task1.projectId()).toEqual(project.id())
75
+ expect(task1.project().id()).toEqual(project.id())
76
+
77
+ expect(task2.projectId()).toEqual(project.id())
78
+ expect(task2.project().id()).toEqual(project.id())
79
+ })
80
+
40
81
  it("creates a new task with an existing project", async () => {
41
82
  await Dummy.run(async () => {
42
83
  const project = await Project.create({name: "Test project"})
@@ -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", (table) => {
6
- table.timestamps()
5
+ await this.createTable("projects", (t) => {
6
+ t.string("creating_user_reference")
7
+ t.timestamps()
7
8
  })
8
9
  }
9
10
  }
@@ -5,6 +5,7 @@ export default class CreateUsers extends Migration {
5
5
  await this.createTable("users", (t) => {
6
6
  t.string("email", {index: {unique: true}, null: false})
7
7
  t.string("encrypted_password", {null: false})
8
+ t.string("reference")
8
9
  t.timestamps()
9
10
  })
10
11
  }
@@ -0,0 +1,8 @@
1
+ import DatabaseRecord from "../../../../src/database/record/index.js"
2
+
3
+ class Account extends DatabaseRecord {
4
+ }
5
+
6
+ Account.setDatabaseIdentifier("mssql")
7
+
8
+ export default Account
@@ -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[foreignKeyCamelized]()
16
+ const foreignKeyValue = model.readColumn(foreignKey)
21
17
 
22
- preloadCollections[model.id()] = []
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
- targetModelsById[targetModel.id()] = targetModel
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[foreignKeyCamelized]()
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 modelIds = []
14
- const modelsById = {}
12
+ const modelsPrimaryKeyValues = []
13
+ const modelsByPrimaryKeyValue = {}
15
14
  const foreignKey = this.relationship.getForeignKey()
16
- const foreignKeyCamelized = inflection.camelize(foreignKey, true)
15
+ const primaryKey = this.relationship.getPrimaryKey()
17
16
  const preloadCollections = {}
18
17
 
19
18
  for (const model of this.models) {
20
- preloadCollections[model.id()] = []
21
- modelIds.push(model.id())
19
+ const primaryKeyValue = model.readColumn(primaryKey)
22
20
 
23
- if (!(model.id in modelsById)) modelsById[model.id()] = []
21
+ preloadCollections[primaryKeyValue] = []
24
22
 
25
- modelsById[model.id()].push(model)
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] = modelIds
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[foreignKeyCamelized]()
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 modelId in preloadCollections) {
43
- const preloadedCollection = preloadCollections[modelId]
43
+ for (const modelValue in preloadCollections) {
44
+ const preloadedCollection = preloadCollections[modelValue]
44
45
 
45
- for (const model of modelsById[modelId]) {
46
+ for (const model of modelsByPrimaryKeyValue[modelValue]) {
46
47
  const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
47
48
 
49
+ if (preloadedCollection.length == 0) {
50
+ modelRelationship.setLoaded([])
51
+ } else {
52
+ modelRelationship.addToLoaded(preloadedCollection)
53
+ }
54
+
48
55
  modelRelationship.setPreloaded(true)
49
- modelRelationship.addToLoaded(preloadedCollection)
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 modelIds = []
14
- const modelsById = {}
12
+ const modelsPrimaryKeyValues = []
13
+ const modelsByPrimaryKeyValue = {}
15
14
  const foreignKey = this.relationship.getForeignKey()
16
- const foreignKeyCamelized = inflection.camelize(foreignKey, true)
15
+ const primaryKey = this.relationship.getPrimaryKey()
17
16
  const preloadCollections = {}
18
17
 
19
18
  for (const model of this.models) {
20
- preloadCollections[model.id()] = null
21
- modelIds.push(model.id())
19
+ const primaryKeyValue = model.readColumn(primaryKey)
22
20
 
23
- if (!(model.id in modelsById)) modelsById[model.id()] = []
21
+ preloadCollections[primaryKeyValue] = null
24
22
 
25
- modelsById[model.id()].push(model)
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] = modelIds
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[foreignKeyCamelized]()
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 modelId in preloadCollections) {
43
- const preloadedModel = preloadCollections[modelId]
43
+ for (const modelValue in preloadCollections) {
44
+ const preloadedModel = preloadCollections[modelValue]
44
45
 
45
- for (const model of modelsById[modelId]) {
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 this.hash) {
16
- const whereValue = this.hash[whereKey]
24
+ for (const whereKey in hash) {
25
+ const whereValue = hash[whereKey]
17
26
 
18
27
  if (index > 0) sql += " AND "
19
28
 
20
- sql += `${options.quoteColumnName(whereKey)}`
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
- sql += ` = ${options.quote(whereValue)}`
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,40 @@ class VelociousDatabaseRecord {
78
78
  if (actualData.type == "belongsTo") {
79
79
  relationship = new BelongsToRelationship(actualData)
80
80
 
81
- const buildMethodName = `build${inflection.camelize(relationshipName)}`
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[buildMethodName] = function (attributes) {
91
- const relationship = this.getRelationshipByName(relationshipName)
92
- const record = relationship.build(attributes)
87
+ this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
88
+ const instanceRelationship = this.getRelationshipByName(relationshipName)
89
+ const record = instanceRelationship.build(attributes)
90
+
91
+ const inverseOf = instanceRelationship.getRelationship().getInverseOf()
92
+
93
+ if (inverseOf) {
94
+ const inverseInstanceRelationship = record.getRelationshipByName(inverseOf)
95
+
96
+ inverseInstanceRelationship.setAutoSave(false)
97
+
98
+ if (inverseInstanceRelationship.getType() == "hasOne") {
99
+ inverseInstanceRelationship.setLoaded(this)
100
+ } else if (inverseInstanceRelationship.getType() == "hasMany") {
101
+ inverseInstanceRelationship.addToLoaded(this)
102
+ } else {
103
+ throw new Error(`Unknown relationship type: ${inverseInstanceRelationship.getType()}`)
104
+ }
105
+ }
93
106
 
94
107
  return record
95
108
  }
96
109
 
97
- this.prototype[setMethodName] = function (model) {
110
+ this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
111
+ await this.getRelationshipByName(relationshipName).load()
112
+ }
113
+
114
+ this.prototype[`set${inflection.camelize(relationshipName)}`] = function(model) {
98
115
  const relationship = this.getRelationshipByName(relationshipName)
99
116
 
100
117
  relationship.setLoaded(model)
@@ -103,24 +120,39 @@ class VelociousDatabaseRecord {
103
120
  } else if (actualData.type == "hasMany") {
104
121
  relationship = new HasManyRelationship(actualData)
105
122
 
106
- this.prototype[relationshipName] = function () {
123
+ this.prototype[relationshipName] = function() {
107
124
  return this.getRelationshipByName(relationshipName)
108
125
  }
109
- } else if (actualData.type == "hasOne") {
110
- const buildMethodName = `build${inflection.camelize(relationshipName)}`
111
126
 
127
+ this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
128
+ await this.getRelationshipByName(relationshipName).load()
129
+ }
130
+ } else if (actualData.type == "hasOne") {
112
131
  relationship = new HasOneRelationship(actualData)
113
132
 
114
- this.prototype[relationshipName] = function () {
133
+ this.prototype[relationshipName] = function() {
115
134
  return this.getRelationshipByName(relationshipName).loaded()
116
135
  }
117
136
 
118
- this.prototype[buildMethodName] = function (attributes) {
119
- const relationship = this.getRelationshipByName(relationshipName)
120
- const record = relationship.build(attributes)
137
+ this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
138
+ const instanceRelationship = this.getRelationshipByName(relationshipName)
139
+ const record = instanceRelationship.build(attributes)
140
+
141
+ const inverseOf = instanceRelationship.getRelationship().getInverseOf()
142
+
143
+ if (inverseOf) {
144
+ const inverseInstanceRelationship = record.getRelationshipByName(inverseOf)
145
+
146
+ inverseInstanceRelationship.setAutoSave(false)
147
+ inverseInstanceRelationship.setLoaded(this)
148
+ }
121
149
 
122
150
  return record
123
151
  }
152
+
153
+ this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
154
+ await this.getRelationshipByName(relationshipName).load()
155
+ }
124
156
  } else {
125
157
  throw new Error(`Unknown relationship type: ${actualData.type}`)
126
158
  }
@@ -144,6 +176,10 @@ class VelociousDatabaseRecord {
144
176
  return []
145
177
  }
146
178
 
179
+ static getRelationshipNames() {
180
+ return this.getRelationships().map((relationship) => relationship.getRelationshipName())
181
+ }
182
+
147
183
  getRelationshipByName(relationshipName) {
148
184
  if (!this._instanceRelationships) this._instanceRelationships = {}
149
185
 
@@ -168,8 +204,8 @@ class VelociousDatabaseRecord {
168
204
  return this._instanceRelationships[relationshipName]
169
205
  }
170
206
 
171
- static belongsTo(relationshipName) {
172
- this._defineRelationship(relationshipName, {type: "belongsTo"})
207
+ static belongsTo(relationshipName, options) {
208
+ this._defineRelationship(relationshipName, Object.assign({type: "belongsTo"}, options))
173
209
  }
174
210
 
175
211
  static connection() {
@@ -219,11 +255,14 @@ class VelociousDatabaseRecord {
219
255
  return this._getConfiguration().getTranslator()(`velocious.database.record.attributes.${modelNameKey}.${attributeName}`, {defaultValue: inflection.camelize(attributeName)})
220
256
  }
221
257
 
258
+ static getDatabaseType() { return this._databaseType }
259
+
222
260
  static async initializeRecord({configuration}) {
223
261
  if (!configuration) throw new Error(`No configuration given for ${this.name}`)
224
262
 
225
263
  this._configuration = configuration
226
264
  this._configuration.registerModelClass(this)
265
+ this._databaseType = this.connection().getType()
227
266
 
228
267
  this._table = await this.connection().getTableByName(this.tableName())
229
268
  this._columns = await this._getTable().getColumns()
@@ -285,13 +324,13 @@ class VelociousDatabaseRecord {
285
324
  const nameCamelized = inflection.camelize(name)
286
325
  const setterMethodName = `set${nameCamelized}`
287
326
 
288
- this.prototype[name] = function () {
327
+ this.prototype[name] = function() {
289
328
  const locale = this._getConfiguration().getLocale()
290
329
 
291
330
  return this._getTranslatedAttributeWithFallback(name, locale)
292
331
  }
293
332
 
294
- this.prototype[setterMethodName] = function (newValue) {
333
+ this.prototype[setterMethodName] = function(newValue) {
295
334
  const locale = this._getConfiguration().getLocale()
296
335
 
297
336
  return this._setTranslatedAttribute(name, locale, newValue)
@@ -302,11 +341,11 @@ class VelociousDatabaseRecord {
302
341
  const getterMethodNameLocalized = `${name}${localeCamelized}`
303
342
  const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
304
343
 
305
- this.prototype[getterMethodNameLocalized] = function () {
344
+ this.prototype[getterMethodNameLocalized] = function() {
306
345
  return this._getTranslatedAttribute(name, locale)
307
346
  }
308
347
 
309
- this.prototype[setterMethodNameLocalized] = function (newValue) {
348
+ this.prototype[setterMethodNameLocalized] = function(newValue) {
310
349
  return this._setTranslatedAttribute(name, locale, newValue)
311
350
  }
312
351
  }
@@ -425,6 +464,10 @@ class VelociousDatabaseRecord {
425
464
  continue
426
465
  }
427
466
 
467
+ if (instanceRelationship.getAutoSave() === false) {
468
+ continue
469
+ }
470
+
428
471
  const model = instanceRelationship.loaded()
429
472
 
430
473
  if (model?.isChanged()) {
@@ -433,6 +476,7 @@ class VelociousDatabaseRecord {
433
476
  const foreignKey = instanceRelationship.getForeignKey()
434
477
 
435
478
  this.setAttribute(foreignKey, model.id())
479
+
436
480
  instanceRelationship.setPreloaded(true)
437
481
  instanceRelationship.setDirty(false)
438
482
 
@@ -453,6 +497,10 @@ class VelociousDatabaseRecord {
453
497
  continue
454
498
  }
455
499
 
500
+ if (instanceRelationship.getAutoSave() === false) {
501
+ continue
502
+ }
503
+
456
504
  let loaded
457
505
 
458
506
  if (instanceRelationship.getType() == "hasOne") {
@@ -469,14 +517,16 @@ class VelociousDatabaseRecord {
469
517
 
470
518
  let useRelationship = false
471
519
 
472
- for (const model of loaded) {
473
- const foreignKey = instanceRelationship.getForeignKey()
520
+ if (loaded) {
521
+ for (const model of loaded) {
522
+ const foreignKey = instanceRelationship.getForeignKey()
474
523
 
475
- model.setAttribute(foreignKey, this.id())
524
+ model.setAttribute(foreignKey, this.id())
476
525
 
477
- if (model.isChanged()) {
478
- useRelationship = true
479
- continue
526
+ if (model.isChanged()) {
527
+ useRelationship = true
528
+ continue
529
+ }
480
530
  }
481
531
  }
482
532
 
@@ -488,7 +538,7 @@ class VelociousDatabaseRecord {
488
538
 
489
539
  async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
490
540
  for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
491
- let loaded = instanceRelationship._loaded
541
+ let loaded = instanceRelationship.getLoadedOrNull()
492
542
 
493
543
  if (!Array.isArray(loaded)) loaded = [loaded]
494
544
 
@@ -792,6 +842,10 @@ class VelociousDatabaseRecord {
792
842
  const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
793
843
  let loaded = instanceRelationship._loaded
794
844
 
845
+ if (instanceRelationship.getAutoSave() === false) {
846
+ continue
847
+ }
848
+
795
849
  if (!loaded) continue
796
850
  if (!Array.isArray(loaded)) loaded = [loaded]
797
851
 
@@ -827,7 +881,7 @@ class VelociousDatabaseRecord {
827
881
  readAttribute(attributeName) {
828
882
  const columnName = this.constructor._attributeNameToColumnName[attributeName]
829
883
 
830
- if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName}`)
884
+ if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.constructor._attributeNameToColumnName)}`)
831
885
 
832
886
  return this.readColumn(columnName)
833
887
  }
@@ -844,7 +898,7 @@ class VelociousDatabaseRecord {
844
898
  throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
845
899
  }
846
900
 
847
- if (column && this.constructor.connection().getType() == "sqlite") {
901
+ if (column && this.constructor.getDatabaseType() == "sqlite") {
848
902
  if (column.getType() == "date" || column.getType() == "datetime") {
849
903
  result = new Date(Date.parse(result))
850
904
  }
@@ -903,6 +957,10 @@ class VelociousDatabaseRecord {
903
957
  for (const relationship of this.constructor.getRelationships()) {
904
958
  const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
905
959
 
960
+ if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrNull() === null) {
961
+ instanceRelationship.setLoaded([])
962
+ }
963
+
906
964
  instanceRelationship.setPreloaded(true)
907
965
  }
908
966
  }
@@ -929,9 +987,9 @@ class VelociousDatabaseRecord {
929
987
  }
930
988
  }
931
989
 
932
- id = () => this.readAttribute(this.constructor.primaryKey())
933
- isPersisted = () => !this._isNewRecord
934
- isNewRecord = () => this._isNewRecord
990
+ id() { return this.readAttribute(this.constructor._columnNameToAttributeName[this.constructor.primaryKey()]) }
991
+ isPersisted() { return !this._isNewRecord }
992
+ isNewRecord() { return this._isNewRecord }
935
993
 
936
994
  setIsNewRecord(newIsNewRecord) {
937
995
  this._isNewRecord = newIsNewRecord
@@ -1,15 +1,16 @@
1
1
  export default class VelociousDatabaseRecordBaseInstanceRelationship {
2
2
  constructor({model, relationship}) {
3
+ this._autoSave = null
3
4
  this._dirty = false
4
5
  this.model = model
5
6
  this.relationship = relationship
6
7
  }
7
8
 
8
- setDirty(newValue) {
9
- this._dirty = newValue
10
- }
11
-
9
+ getAutoSave() { return this._autoSave }
10
+ setAutoSave(newAutoSaveValue) { this._autoSave = newAutoSaveValue }
11
+ setDirty(newValue) { this._dirty = newValue }
12
12
  getDirty() { return this._dirty }
13
+ isLoaded() { return Boolean(this._loaded) }
13
14
 
14
15
  loaded() {
15
16
  if (!this._preloaded && this.model.isPersisted()) {
@@ -19,15 +20,11 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
19
20
  return this._loaded
20
21
  }
21
22
 
22
- setLoaded(model) {
23
- this._loaded = model
24
- }
25
-
26
- setPreloaded(preloadedValue) {
27
- this._preloaded = preloadedValue
28
- }
29
-
23
+ setLoaded(model) { this._loaded = model }
24
+ getPreloaded() { return this._preloaded }
25
+ setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
30
26
  getForeignKey() { return this.getRelationship().getForeignKey() }
27
+ getModel() { return this.model }
31
28
  getPrimaryKey() { return this.getRelationship().getPrimaryKey() }
32
29
  getRelationship() { return this.relationship }
33
30
  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)
@@ -14,7 +10,16 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
14
10
  return newInstance
15
11
  }
16
12
 
17
- getLoadedOrNull() {
18
- return this._loaded
13
+ getLoadedOrNull() { return this._loaded }
14
+
15
+ async load() {
16
+ const foreignKey = this.getForeignKey()
17
+ const foreignModelID = this.getModel().readColumn(foreignKey)
18
+ const TargetModelClass = this.getTargetModelClass()
19
+ const foreignModel = await TargetModelClass.find(foreignModelID)
20
+
21
+ this.setLoaded(foreignModel)
22
+ this.setDirty(false)
23
+ this.setPreloaded(true)
19
24
  }
20
25
  }
@@ -3,36 +3,76 @@ 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
+ // Spawn new model of the targeted class
10
11
  const targetModelClass = this.getTargetModelClass()
11
12
  const newInstance = new targetModelClass(data)
12
13
 
14
+
15
+ // Add it to the loaded models of this relationship
16
+ if (this._loaded === null) this._loaded = []
17
+
13
18
  this._loaded.push(newInstance)
14
19
 
20
+
21
+ // Set loaded on the models inversed relationship
22
+ const inverseOf = this.getRelationship().getInverseOf()
23
+
24
+ if (inverseOf) {
25
+ const inverseInstanceRelationship = newInstance.getRelationshipByName(inverseOf)
26
+
27
+ inverseInstanceRelationship.setAutoSave(false)
28
+ inverseInstanceRelationship.setLoaded(this.getModel())
29
+ }
30
+
31
+
32
+ // Return the new contructed model
15
33
  return newInstance
16
34
  }
17
35
 
36
+ async load() {
37
+ const foreignKey = this.getForeignKey()
38
+ const primaryKey = this.getPrimaryKey()
39
+ const primaryModelID = this.getModel().readColumn(primaryKey)
40
+ const TargetModelClass = this.getTargetModelClass()
41
+ const whereArgs = {}
42
+
43
+ whereArgs[foreignKey] = primaryModelID
44
+
45
+ const foreignModels = await TargetModelClass.where(whereArgs).toArray()
46
+
47
+ this.setLoaded(foreignModels)
48
+ this.setDirty(false)
49
+ this.setPreloaded(true)
50
+ }
51
+
18
52
  loaded() {
19
53
  if (!this._preloaded && this.model.isPersisted()) {
20
54
  throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
21
55
  }
22
56
 
23
- return this._loaded
24
- }
57
+ if (this._loaded === null && this.model.isNewRecord()) {
58
+ return []
59
+ }
25
60
 
26
- getLoadedOrNull() {
27
61
  return this._loaded
28
62
  }
29
63
 
64
+ getLoadedOrNull() { return this._loaded }
65
+
30
66
  addToLoaded(models) {
31
67
  if (Array.isArray(models)) {
32
68
  for (const model of models) {
69
+ if (this._loaded === null) this._loaded = []
70
+
33
71
  this._loaded.push(model)
34
72
  }
35
73
  } else {
74
+ if (this._loaded === null) this._loaded = []
75
+
36
76
  this._loaded.push(models)
37
77
  }
38
78
  }
@@ -43,5 +83,13 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
43
83
  this._loaded = models
44
84
  }
45
85
 
46
- getTargetModelClass = () => this.relationship.getTargetModelClass()
86
+ setPreloaded(preloaded) {
87
+ if (preloaded && !Array.isArray(this._loaded)) {
88
+ throw new Error("Trying to set preloaded without a loaded value")
89
+ }
90
+
91
+ this._preloaded = preloaded
92
+ }
93
+
94
+ getTargetModelClass() { return this.relationship.getTargetModelClass() }
47
95
  }
@@ -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`)
@@ -23,9 +39,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
23
39
  return this._loaded
24
40
  }
25
41
 
26
- getLoadedOrNull() {
27
- return this._loaded
28
- }
42
+ getLoadedOrNull() { return this._loaded }
29
43
 
30
44
  setLoaded(model) {
31
45
  if (Array.isArray(model)) throw new Error(`Argument given to setLoaded was an array: ${typeof model}`)
@@ -33,5 +47,5 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
33
47
  this._loaded = model
34
48
  }
35
49
 
36
- getTargetModelClass = () => this.relationship.getTargetModelClass()
50
+ getTargetModelClass() { return this.relationship.getTargetModelClass() }
37
51
  }
@@ -1,7 +1,7 @@
1
1
  import restArgsError from "../../../utils/rest-args-error.js"
2
2
 
3
3
  export default class VelociousDatabaseRecordBaseRelationship {
4
- constructor({className, configuration, dependent, foreignKey, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
4
+ constructor({className, configuration, dependent, foreignKey, inverseOf, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
5
5
  restArgsError(restArgs)
6
6
 
7
7
  if (!modelClass) throw new Error(`'modelClass' wasn't given for ${relationshipName}`)
@@ -11,6 +11,7 @@ export default class VelociousDatabaseRecordBaseRelationship {
11
11
  this.configuration = configuration
12
12
  this._dependent = dependent
13
13
  this.foreignKey = foreignKey
14
+ this._inverseOf
14
15
  this.klass = klass
15
16
  this.modelClass = modelClass
16
17
  this._primaryKey = primaryKey
@@ -9,4 +9,28 @@ export default class VelociousDatabaseRecordBelongsToRelationship extends BaseRe
9
9
 
10
10
  return this.foreignKey
11
11
  }
12
+
13
+ getInverseOf() {
14
+ if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
15
+ this._autoGenerateInverseOfAttempted = true
16
+
17
+ // Only make auto-inverse-of if the relationships name matches the target model class's name
18
+ const targetClassSimpleName = `${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`
19
+
20
+ if (targetClassSimpleName == this.getTargetModelClass().name) {
21
+ // Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
22
+ const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
23
+ const autoGeneratedHasOneInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
24
+ const autoGeneratedHasManyInverseOfName = inflection.pluralize(autoGeneratedHasOneInverseOfName)
25
+
26
+ if (targetClassRelationshipNames.includes(autoGeneratedHasOneInverseOfName)) {
27
+ this._inverseOf = autoGeneratedHasOneInverseOfName
28
+ } else if (targetClassRelationshipNames.includes(autoGeneratedHasManyInverseOfName)) {
29
+ this._inverseOf = autoGeneratedHasManyInverseOfName
30
+ }
31
+ }
32
+ }
33
+
34
+ return this._inverseOf
35
+ }
12
36
  }
@@ -9,4 +9,25 @@ export default class VelociousDatabaseRecordHasManyRelationship extends BaseRela
9
9
 
10
10
  return this.foreignKey
11
11
  }
12
+
13
+ getInverseOf() {
14
+ if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
15
+ this._autoGenerateInverseOfAttempted = true
16
+
17
+ // Only make auto-inverse-of if the relationships name matches the target model class's name
18
+ const targetClassSimpleName = inflection.singularize(`${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`)
19
+
20
+ if (targetClassSimpleName == this.getTargetModelClass().name) {
21
+ // Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
22
+ const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
23
+ const autoGeneratedBelongsToInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
24
+
25
+ if (targetClassRelationshipNames.includes(autoGeneratedBelongsToInverseOfName)) {
26
+ this._inverseOf = autoGeneratedBelongsToInverseOfName
27
+ }
28
+ }
29
+ }
30
+
31
+ return this._inverseOf
32
+ }
12
33
  }
@@ -9,4 +9,25 @@ export default class VelociousDatabaseRecordHasOneRelationship extends BaseRelat
9
9
 
10
10
  return this.foreignKey
11
11
  }
12
+
13
+ getInverseOf() {
14
+ if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
15
+ this._autoGenerateInverseOfAttempted = true
16
+
17
+ // Only make auto-inverse-of if the relationships name matches the target model class's name
18
+ const targetClassSimpleName = `${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`
19
+
20
+ if (targetClassSimpleName == this.getTargetModelClass().name) {
21
+ // Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
22
+ const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
23
+ const autoGeneratedBelongsToInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
24
+
25
+ if (targetClassRelationshipNames.includes(autoGeneratedBelongsToInverseOfName)) {
26
+ this._inverseOf = autoGeneratedBelongsToInverseOfName
27
+ }
28
+ }
29
+ }
30
+
31
+ return this._inverseOf
32
+ }
12
33
  }
@@ -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].send(output)
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].close()
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
  }