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.
Files changed (27) 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/instance-relationships/belongs-to-relationship-spec.js +21 -0
  5. package/spec/database/record/instance-relationships/has-many-relationship-spec.js +24 -0
  6. package/spec/database/record/instance-relationships/has-one-relationship-spec.js +24 -0
  7. package/spec/database/record/preloader/belongs-to-spec.js +15 -0
  8. package/spec/database/record/preloader/has-many-spec.js +29 -0
  9. package/spec/database/record/preloader/has-one-spec.js +15 -0
  10. package/spec/database/record/query-spec.js +24 -0
  11. package/spec/dummy/src/database/migrations/20230728075328-create-projects.js +3 -2
  12. package/spec/dummy/src/database/migrations/20250912183605-create-users.js +1 -0
  13. package/spec/dummy/src/models/account.js +8 -0
  14. package/spec/dummy/src/models/project.js +1 -0
  15. package/spec/dummy/src/models/user.js +3 -0
  16. package/src/database/initializer-from-require-context.js +2 -0
  17. package/src/database/query/preloader/belongs-to.js +6 -10
  18. package/src/database/query/preloader/has-many.js +20 -14
  19. package/src/database/query/preloader/has-one.js +14 -13
  20. package/src/database/query/where-hash.js +25 -10
  21. package/src/database/record/index.js +40 -28
  22. package/src/database/record/instance-relationships/base.js +5 -12
  23. package/src/database/record/instance-relationships/belongs-to.js +11 -4
  24. package/src/database/record/instance-relationships/has-many.js +21 -1
  25. package/src/database/record/instance-relationships/has-one.js +16 -0
  26. package/src/http-server/worker-handler/index.js +2 -2
  27. 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.66",
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
 
@@ -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
 
48
49
  modelRelationship.setPreloaded(true)
49
- modelRelationship.addToLoaded(preloadedCollection)
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 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,24 @@ 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) {
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[setMethodName] = function (model) {
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[buildMethodName] = function (attributes) {
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 (newValue) {
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 (newValue) {
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
- for (const model of loaded) {
473
- const foreignKey = instanceRelationship.getForeignKey()
482
+ if (loaded) {
483
+ for (const model of loaded) {
484
+ const foreignKey = instanceRelationship.getForeignKey()
474
485
 
475
- model.setAttribute(foreignKey, this.id())
486
+ model.setAttribute(foreignKey, this.id())
476
487
 
477
- if (model.isChanged()) {
478
- useRelationship = true
479
- continue
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.connection().getType() == "sqlite") {
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 = () => this.readAttribute(this.constructor.primaryKey())
933
- isPersisted = () => !this._isNewRecord
934
- isNewRecord = () => this._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
- this._loaded = model
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].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
  }