velocious 1.0.67 → 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/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-parser/where-parser.js +1 -1
- 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
|
@@ -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
|
|
|
@@ -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
|
|