velocious 1.0.66 → 1.0.68

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 CHANGED
@@ -62,14 +62,51 @@ await project.loadTasks()
62
62
  const tasks = project.tasks().loaded()
63
63
  ```
64
64
 
65
+ ### Create records
66
+
67
+ ```js
68
+ const task = new Task({identifier: "task-4"})
69
+
70
+ task.assign({name: "New task})
71
+
72
+ await task.save()
73
+ ```
74
+
75
+ ```js
76
+ const task = await Task.create({name: "Task 4"})
77
+ ```
78
+
79
+ ### Find or create records
80
+
81
+ ```js
82
+ const task = await Task.findOrInitializeBy({identifier: "task-5"})
83
+
84
+ if (task.isNewRecord()) {
85
+ console.log("Task didn't already exist")
86
+
87
+ await task.save()
88
+ }
89
+
90
+ if (task.isPersisted()) {
91
+ console.log("Task already exist")
92
+ }
93
+ ```
94
+
95
+ ```js
96
+ const task = await Task.findOrCreateBy({identifier: "task-5"}, (newTask) => {
97
+ newTask.assign({description: "This callback only happens if not already existing"})
98
+ })
99
+ ```
100
+
65
101
  # Migrations
66
102
 
67
- Make a new migration from a template like this:
103
+ ## Make a new migration from a template
68
104
 
69
105
  ```bash
70
106
  npx velocious g:migration create-tasks
71
107
  ```
72
108
 
109
+ ## Write a migration
73
110
  ```js
74
111
  import Migration from "velocious/src/database/migration/index.js"
75
112
 
@@ -96,12 +133,13 @@ export default class CreateEvents extends Migration {
96
133
  }
97
134
  ```
98
135
 
99
- Run migrations from the command line like this:
136
+ ## Run migrations from the command line
137
+
100
138
  ```bash
101
139
  npx velocious db:migrate
102
140
  ```
103
141
 
104
- Run migrations from anywhere if you want to:
142
+ ## Run migrations from anywhere if you want to:
105
143
 
106
144
  ```js
107
145
  const migrationsPath = `/some/dir/migrations`
@@ -122,7 +160,10 @@ import {Task} from "@/src/models/task"
122
160
 
123
161
  const tasks = await Task
124
162
  .preload({project: {account: true}})
163
+ .joins({project: true})
125
164
  .where({projects: {public: true}})
165
+ .joins("JOIN task_details ON task_details.task_id = tasks.id")
166
+ .where("task_details.id IS NOT NULL")
126
167
  .order("name")
127
168
  .limit(5)
128
169
  .toArray()
@@ -138,7 +179,7 @@ npx velocious test
138
179
  If you are developing on Velocious, you can run the tests with:
139
180
 
140
181
  ```bash
141
- npm run test
182
+ ./run-tests.sh
142
183
  ```
143
184
 
144
185
  # Writing a request test
@@ -169,8 +210,63 @@ await describe("accounts - create", {type: "request"}, async () => {
169
210
  })
170
211
  ```
171
212
 
213
+ # Routes
214
+
215
+ Create or edit the file `src/config/routes.js` and do something like this:
216
+
217
+ ```js
218
+ import Routes from "velocious/src/routes/index.js"
219
+
220
+ const routes = new Routes()
221
+
222
+ routes.draw((route) => {
223
+ route.resources("projects")
224
+
225
+ route.resources("tasks", (route) => {
226
+ route.get("users")
227
+ })
228
+
229
+ route.namespace("testing", (route) => {
230
+ route.post("truncate")
231
+ })
232
+
233
+ route.get("ping")
234
+ })
235
+
236
+ export default {routes}
237
+ ```
238
+
239
+ # Controllers
240
+
241
+ Create the file `src/routes/testing/controller.js` and do something like this:
242
+
243
+ ```js
244
+ import Controller from "velocious/src/controller.js"
245
+
246
+ export default class TestingController extends Controller {
247
+ async truncate() {
248
+ await doSomething()
249
+ this.renderJson({status: "database-truncated"})
250
+ }
251
+
252
+ async anotherAction() {
253
+ render("test-view")
254
+ }
255
+ }
256
+ ```
257
+
258
+ # Views
259
+
260
+ Create the file `src/routes/testing/another-action.ejs` and so something like this:
261
+
262
+ ```ejs
263
+ <p>
264
+ View for path: <%= controller.getRequest().path() %>
265
+ </p>
266
+ ```
267
+
172
268
  # Running a server
173
269
 
174
270
  ```bash
175
- npx velocious server
271
+ npx velocious server --host 0.0.0.0 --port 8082
176
272
  ```
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.66",
6
+ "version": "1.0.68",
7
7
  "main": "index.js",
8
8
  "scripts": {
9
9
  "test": "VELOCIOUS_TEST_DIR=../ cd spec/dummy && npx velocious test",
@@ -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,59 @@
1
+ import Dummy from "../../dummy/index.js"
2
+ import Task from "../../dummy/src/models/task.js"
3
+
4
+ describe("Record - find or create", () => {
5
+ it("doesnt find but then creates a record", async () => {
6
+ await Dummy.run(async () => {
7
+ const task = await Task.findOrCreateBy({name: "Test task"}, (newTask) => {
8
+ const project = newTask.buildProject({nameEn: "Test project", nameDe: "Test projekt"})
9
+
10
+ project.buildProjectDetail({note: "Test note"})
11
+ })
12
+
13
+ const project = task.project()
14
+
15
+ expect(task.isPersisted()).toBeTrue()
16
+ expect(task.isNewRecord()).toBeFalse()
17
+ expect(task.id()).not.toBeUndefined()
18
+ expect(task.name()).toEqual("Test task")
19
+ expect(task.project().id()).toEqual(project.id())
20
+ expect(task.project()).toEqual(project)
21
+
22
+ expect(project.id()).not.toBeUndefined()
23
+ expect(project.name()).toEqual("Test project")
24
+ expect(project.nameDe()).toEqual("Test projekt")
25
+ expect(project.nameEn()).toEqual("Test project")
26
+ expect(project.createdAt()).toBeInstanceOf(Date)
27
+ expect(project.updatedAt()).toBeInstanceOf(Date)
28
+
29
+ // 'name' is not a column but rather a column on the translation data model.
30
+ expect(() => project.readColumn("name")).toThrowError("No such attribute or not selected Project#name")
31
+
32
+
33
+ // It saves a project note through a has one relationship
34
+ const projectDetail = project.projectDetail()
35
+
36
+ expect(projectDetail.isNewRecord()).toBeFalse()
37
+ expect(projectDetail.isPersisted()).toBeTrue()
38
+ expect(projectDetail.note()).toEqual("Test note")
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()])
57
+ })
58
+ })
59
+ })
@@ -125,7 +125,7 @@ export default class VelociousDatabaseDriversBase {
125
125
  }
126
126
 
127
127
  quoteTable(tableName) {
128
- return this.options().quoteColumnName(tableName)
128
+ return this.options().quoteTableName(tableName)
129
129
  }
130
130
 
131
131
  newQuery() {
@@ -108,8 +108,8 @@ export default class VelociousDatabaseQuery {
108
108
  return await this.clone().where(newConditions).first()
109
109
  }
110
110
 
111
- async findOrCreateBy(conditions) {
112
- const record = await this.findOrInitializeBy(conditions)
111
+ async findOrCreateBy(...args) {
112
+ const record = await this.findOrInitializeBy(...args)
113
113
 
114
114
  if (record.isNewRecord()) {
115
115
  await record.save()
@@ -118,13 +118,17 @@ export default class VelociousDatabaseQuery {
118
118
  return record
119
119
  }
120
120
 
121
- async findOrInitializeBy(conditions) {
121
+ async findOrInitializeBy(conditions, callback) {
122
122
  const record = await this.findBy(conditions)
123
123
 
124
124
  if (record) return record
125
125
 
126
126
  const newRecord = new this.modelClass(conditions)
127
127
 
128
+ if (callback) {
129
+ callback(newRecord)
130
+ }
131
+
128
132
  return newRecord
129
133
  }
130
134
 
@@ -46,13 +46,13 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
46
46
  for (const model of modelsByPrimaryKeyValue[modelValue]) {
47
47
  const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
48
48
 
49
- modelRelationship.setPreloaded(true)
50
-
51
49
  if (preloadedCollection.length == 0) {
52
50
  modelRelationship.setLoaded([])
53
51
  } else {
54
52
  modelRelationship.addToLoaded(preloadedCollection)
55
53
  }
54
+
55
+ modelRelationship.setPreloaded(true)
56
56
  }
57
57
  }
58
58
 
@@ -23,7 +23,7 @@ export default class VelocuiousDatabaseQueryParserWhereParser {
23
23
  for (const whereKey in query._wheres) {
24
24
  const where = query._wheres[whereKey]
25
25
 
26
- if (whereKey > 0) sql += " &&"
26
+ if (whereKey > 0) sql += " AND"
27
27
 
28
28
  if (pretty) {
29
29
  sql += "\n "
@@ -85,8 +85,24 @@ class VelociousDatabaseRecord {
85
85
  }
86
86
 
87
87
  this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
88
- const relationship = this.getRelationshipByName(relationshipName)
89
- const record = relationship.build(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
+ }
90
106
 
91
107
  return record
92
108
  }
@@ -119,8 +135,17 @@ class VelociousDatabaseRecord {
119
135
  }
120
136
 
121
137
  this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
122
- const relationship = this.getRelationshipByName(relationshipName)
123
- const record = relationship.build(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
+ }
124
149
 
125
150
  return record
126
151
  }
@@ -151,6 +176,10 @@ class VelociousDatabaseRecord {
151
176
  return []
152
177
  }
153
178
 
179
+ static getRelationshipNames() {
180
+ return this.getRelationships().map((relationship) => relationship.getRelationshipName())
181
+ }
182
+
154
183
  getRelationshipByName(relationshipName) {
155
184
  if (!this._instanceRelationships) this._instanceRelationships = {}
156
185
 
@@ -435,6 +464,10 @@ class VelociousDatabaseRecord {
435
464
  continue
436
465
  }
437
466
 
467
+ if (instanceRelationship.getAutoSave() === false) {
468
+ continue
469
+ }
470
+
438
471
  const model = instanceRelationship.loaded()
439
472
 
440
473
  if (model?.isChanged()) {
@@ -443,6 +476,7 @@ class VelociousDatabaseRecord {
443
476
  const foreignKey = instanceRelationship.getForeignKey()
444
477
 
445
478
  this.setAttribute(foreignKey, model.id())
479
+
446
480
  instanceRelationship.setPreloaded(true)
447
481
  instanceRelationship.setDirty(false)
448
482
 
@@ -463,6 +497,10 @@ class VelociousDatabaseRecord {
463
497
  continue
464
498
  }
465
499
 
500
+ if (instanceRelationship.getAutoSave() === false) {
501
+ continue
502
+ }
503
+
466
504
  let loaded
467
505
 
468
506
  if (instanceRelationship.getType() == "hasOne") {
@@ -500,7 +538,7 @@ class VelociousDatabaseRecord {
500
538
 
501
539
  async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
502
540
  for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
503
- let loaded = instanceRelationship._loaded
541
+ let loaded = instanceRelationship.getLoadedOrNull()
504
542
 
505
543
  if (!Array.isArray(loaded)) loaded = [loaded]
506
544
 
@@ -804,6 +842,10 @@ class VelociousDatabaseRecord {
804
842
  const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
805
843
  let loaded = instanceRelationship._loaded
806
844
 
845
+ if (instanceRelationship.getAutoSave() === false) {
846
+ continue
847
+ }
848
+
807
849
  if (!loaded) continue
808
850
  if (!Array.isArray(loaded)) loaded = [loaded]
809
851
 
@@ -915,6 +957,10 @@ class VelociousDatabaseRecord {
915
957
  for (const relationship of this.constructor.getRelationships()) {
916
958
  const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
917
959
 
960
+ if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrNull() === null) {
961
+ instanceRelationship.setLoaded([])
962
+ }
963
+
918
964
  instanceRelationship.setPreloaded(true)
919
965
  }
920
966
  }
@@ -1,10 +1,13 @@
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
 
9
+ getAutoSave() { return this._autoSave }
10
+ setAutoSave(newAutoSaveValue) { this._autoSave = newAutoSaveValue }
8
11
  setDirty(newValue) { this._dirty = newValue }
9
12
  getDirty() { return this._dirty }
10
13
  isLoaded() { return Boolean(this._loaded) }
@@ -18,6 +21,7 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
18
21
  }
19
22
 
20
23
  setLoaded(model) { this._loaded = model }
24
+ getPreloaded() { return this._preloaded }
21
25
  setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
22
26
  getForeignKey() { return this.getRelationship().getForeignKey() }
23
27
  getModel() { return this.model }
@@ -10,9 +10,7 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
10
10
  return newInstance
11
11
  }
12
12
 
13
- getLoadedOrNull() {
14
- return this._loaded
15
- }
13
+ getLoadedOrNull() { return this._loaded }
16
14
 
17
15
  async load() {
18
16
  const foreignKey = this.getForeignKey()
@@ -7,13 +7,29 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
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
13
16
  if (this._loaded === null) this._loaded = []
14
17
 
15
18
  this._loaded.push(newInstance)
16
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
17
33
  return newInstance
18
34
  }
19
35
 
@@ -38,13 +54,15 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
38
54
  throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
39
55
  }
40
56
 
41
- return this._loaded
42
- }
57
+ if (this._loaded === null && this.model.isNewRecord()) {
58
+ return []
59
+ }
43
60
 
44
- getLoadedOrNull() {
45
61
  return this._loaded
46
62
  }
47
63
 
64
+ getLoadedOrNull() { return this._loaded }
65
+
48
66
  addToLoaded(models) {
49
67
  if (Array.isArray(models)) {
50
68
  for (const model of models) {
@@ -53,6 +71,8 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
53
71
  this._loaded.push(model)
54
72
  }
55
73
  } else {
74
+ if (this._loaded === null) this._loaded = []
75
+
56
76
  this._loaded.push(models)
57
77
  }
58
78
  }
@@ -63,5 +83,13 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
63
83
  this._loaded = models
64
84
  }
65
85
 
66
- 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() }
67
95
  }
@@ -39,9 +39,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
39
39
  return this._loaded
40
40
  }
41
41
 
42
- getLoadedOrNull() {
43
- return this._loaded
44
- }
42
+ getLoadedOrNull() { return this._loaded }
45
43
 
46
44
  setLoaded(model) {
47
45
  if (Array.isArray(model)) throw new Error(`Argument given to setLoaded was an array: ${typeof model}`)
@@ -49,5 +47,5 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
49
47
  this._loaded = model
50
48
  }
51
49
 
52
- getTargetModelClass = () => this.relationship.getTargetModelClass()
50
+ getTargetModelClass() { return this.relationship.getTargetModelClass() }
53
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
  }
@@ -3,16 +3,24 @@ import * as inflection from "inflection"
3
3
 
4
4
  export default class VelociousDatabaseRecordValidatorsUniqueness extends Base {
5
5
  async validate({model, attributeName}) {
6
+ const modelClass = model.constructor
7
+ const connection = modelClass.connection()
8
+ const tableName = modelClass._getTable().getName()
6
9
  const attributeValue = model.readAttribute(attributeName)
7
10
  const attributeNameUnderscore = inflection.underscore(attributeName)
8
11
  const whereArgs = {}
9
12
 
10
13
  whereArgs[attributeNameUnderscore] = attributeValue
11
14
 
12
- const existingRecord = await model.constructor
13
- .select(model.constructor.primaryKey())
15
+ let existingRecordQuery = model.constructor
16
+ .select(modelClass.primaryKey())
14
17
  .where(whereArgs)
15
- .first()
18
+
19
+ if (model.isPersisted()) {
20
+ existingRecordQuery.where(`${connection.quoteTable(tableName)}.${connection.quoteColumn(modelClass.primaryKey())} != ${connection.quote(model.id())}`)
21
+ }
22
+
23
+ const existingRecord = await existingRecordQuery.first()
16
24
 
17
25
  if (existingRecord) {
18
26
  if (!(attributeName in model._validationErrors)) model._validationErrors[attributeName] = []
@@ -92,7 +92,9 @@ export default class VeoliciousHttpServerClient {
92
92
  response.addHeader("Connection", "Close")
93
93
  }
94
94
 
95
- response.addHeader("Content-Length", response.body.length)
95
+ const textEncoded = new TextEncoder().encode(body)
96
+
97
+ response.addHeader("Content-Length", textEncoded.length)
96
98
  response.addHeader("Date", date.toUTCString())
97
99
  response.addHeader("Server", "Velocious")
98
100