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 +101 -5
- package/package.json +1 -1
- package/spec/database/record/create-spec.js +42 -1
- package/spec/database/record/find-or-create-spec.js +59 -0
- package/src/database/drivers/base.js +1 -1
- package/src/database/query/index.js +7 -3
- package/src/database/query/preloader/has-many.js +2 -2
- package/src/database/query-parser/where-parser.js +1 -1
- package/src/database/record/index.js +51 -5
- package/src/database/record/instance-relationships/base.js +4 -0
- package/src/database/record/instance-relationships/belongs-to.js +1 -3
- package/src/database/record/instance-relationships/has-many.js +32 -4
- package/src/database/record/instance-relationships/has-one.js +2 -4
- package/src/database/record/relationships/base.js +2 -1
- package/src/database/record/relationships/belongs-to.js +24 -0
- package/src/database/record/relationships/has-many.js +21 -0
- package/src/database/record/relationships/has-one.js +21 -0
- package/src/database/record/validators/uniqueness.js +11 -3
- package/src/http-server/client/index.js +3 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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
|
+
})
|
|
@@ -108,8 +108,8 @@ export default class VelociousDatabaseQuery {
|
|
|
108
108
|
return await this.clone().where(newConditions).first()
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
async findOrCreateBy(
|
|
112
|
-
const record = await this.findOrInitializeBy(
|
|
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
|
|
|
@@ -85,8 +85,24 @@ class VelociousDatabaseRecord {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
88
|
-
const
|
|
89
|
-
const record =
|
|
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
|
|
123
|
-
const record =
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
13
|
-
.select(
|
|
15
|
+
let existingRecordQuery = model.constructor
|
|
16
|
+
.select(modelClass.primaryKey())
|
|
14
17
|
.where(whereArgs)
|
|
15
|
-
|
|
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
|
-
|
|
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
|
|