velocious 1.0.1 → 1.0.3
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 +36 -0
- package/bin/velocious.mjs +8 -0
- package/index.mjs +21 -0
- package/package.json +15 -7
- package/peak_flow.yml +12 -5
- package/spec/cli/commands/db/create-spec.mjs +25 -0
- package/spec/cli/commands/db/migrate-spec.mjs +37 -0
- package/spec/cli/commands/destroy/migration-spec.mjs +15 -0
- package/spec/cli/commands/generate/migration-spec.mjs +18 -0
- package/spec/cli/commands/init-spec.mjs +19 -0
- package/spec/cli/commands/test/test-files-finder-spec.mjs +12 -0
- package/spec/database/connection/drivers/mysql/{query-parser-spec.cjs → query-parser-spec.mjs} +12 -8
- package/spec/database/drivers/mysql/connection-spec.mjs +21 -0
- package/spec/database/record/create-spec.mjs +14 -0
- package/spec/database/record/destroy-spec.mjs +17 -0
- package/spec/database/record/find-spec.mjs +29 -0
- package/spec/database/record/update-spec.mjs +15 -0
- package/spec/dummy/dummy-directory.mjs +11 -0
- package/spec/dummy/index.mjs +63 -0
- package/spec/dummy/src/config/configuration.example.mjs +19 -0
- package/spec/dummy/src/config/configuration.peakflow.mjs +20 -0
- package/spec/dummy/src/{routes.cjs → config/routes.mjs} +3 -2
- package/spec/dummy/src/database/migrations/20230728075328-create-projects.mjs +11 -0
- package/spec/dummy/src/database/migrations/20230728075329-create-tasks.mjs +13 -0
- package/spec/dummy/src/models/task.mjs +4 -0
- package/spec/dummy/src/routes/tasks/controller.mjs +26 -0
- package/spec/http-server/{client-spec.cjs → client-spec.mjs} +7 -10
- package/spec/http-server/{get-spec.cjs → get-spec.mjs} +2 -2
- package/spec/http-server/post-spec.mjs +72 -0
- package/spec/support/jasmine.json +4 -3
- package/src/application.mjs +50 -0
- package/src/cli/base-command.mjs +11 -0
- package/src/cli/commands/db/create.mjs +50 -0
- package/src/cli/commands/db/migrate.mjs +58 -0
- package/src/cli/commands/destroy/migration.mjs +35 -0
- package/src/cli/commands/generate/migration.mjs +36 -0
- package/src/cli/commands/init.mjs +60 -0
- package/src/cli/commands/test/index.mjs +14 -0
- package/src/cli/commands/test/test-files-finder.mjs +99 -0
- package/src/cli/commands/test/test-runner.mjs +19 -0
- package/src/cli/index.mjs +59 -0
- package/src/configuration-resolver.mjs +26 -0
- package/src/configuration.mjs +49 -0
- package/src/{controller.cjs → controller.mjs} +21 -4
- package/src/database/drivers/base.mjs +17 -0
- package/src/database/drivers/index.mjs +5 -0
- package/src/database/drivers/mysql/connect-connection.mjs +12 -0
- package/src/database/drivers/mysql/index.mjs +102 -0
- package/src/database/drivers/mysql/options.mjs +17 -0
- package/src/database/drivers/mysql/query-parser.mjs +25 -0
- package/src/database/drivers/mysql/query.mjs +26 -0
- package/src/database/drivers/mysql/sql/create-database.mjs +4 -0
- package/src/database/drivers/mysql/sql/create-table.mjs +4 -0
- package/src/database/drivers/mysql/sql/delete.mjs +19 -0
- package/src/database/drivers/mysql/sql/insert.mjs +29 -0
- package/src/database/drivers/mysql/sql/update.mjs +31 -0
- package/src/database/drivers/sqlite/options.mjs +17 -0
- package/src/database/drivers/sqlite/query-parser.mjs +25 -0
- package/src/database/drivers/sqlite/sql/create-database.mjs +4 -0
- package/src/database/drivers/sqlite/sql/create-table.mjs +4 -0
- package/src/database/drivers/sqlite/sql/delete.mjs +19 -0
- package/src/database/drivers/sqlite/sql/insert.mjs +29 -0
- package/src/database/drivers/sqlite/sql/update.mjs +31 -0
- package/src/database/drivers/sqlite-expo/index.mjs +100 -0
- package/src/database/drivers/sqlite-expo/query.mjs +9 -0
- package/src/database/handler.mjs +7 -0
- package/src/database/index.mjs +15 -0
- package/src/database/migration/index.mjs +18 -0
- package/src/database/migrator/index.mjs +15 -0
- package/src/database/pool/index.mjs +112 -0
- package/src/database/query/base.mjs +11 -0
- package/src/database/query/create-database-base.mjs +20 -0
- package/src/database/query/create-table-base.mjs +69 -0
- package/src/database/query/delete-base.mjs +9 -0
- package/src/database/query/from-base.mjs +9 -0
- package/src/database/query/from-plain.mjs +10 -0
- package/src/database/query/from-table.mjs +12 -0
- package/src/database/query/index.mjs +144 -0
- package/src/database/query/insert-base.mjs +15 -0
- package/src/database/query/join-base.mjs +9 -0
- package/src/database/query/join-plain.mjs +12 -0
- package/src/database/query/order-base.mjs +9 -0
- package/src/database/query/order-plain.mjs +21 -0
- package/src/database/query/select-base.mjs +9 -0
- package/src/database/query/select-plain.mjs +12 -0
- package/src/database/query/{select-table-and-column.cjs → select-table-and-column.mjs} +4 -4
- package/src/database/query/update-base.mjs +16 -0
- package/src/database/query-parser/{from-parser.cjs → from-parser.mjs} +3 -6
- package/src/database/query-parser/{joins-parser.cjs → joins-parser.mjs} +3 -6
- package/src/database/query-parser/{options.cjs → options.mjs} +13 -2
- package/src/database/query-parser/{select-parser.cjs → select-parser.mjs} +7 -6
- package/src/database/record/index.mjs +187 -0
- package/src/database/record/record-not-found-error.mjs +1 -0
- package/src/database/table-data/index.mjs +83 -0
- package/src/{error-logger.js → error-logger.mjs} +1 -1
- package/src/http-server/client/{index.cjs → index.mjs} +10 -11
- package/src/http-server/client/params-to-object.mjs +68 -0
- package/src/http-server/client/request-buffer/form-data-part.mjs +42 -0
- package/src/http-server/client/request-buffer/header.mjs +7 -0
- package/src/http-server/client/request-buffer/index.mjs +229 -0
- package/src/http-server/client/request-parser.mjs +47 -0
- package/src/http-server/client/{request-runner.cjs → request-runner.mjs} +5 -5
- package/src/http-server/client/request.mjs +15 -0
- package/src/http-server/client/{response.cjs → response.mjs} +1 -1
- package/src/http-server/index.mjs +137 -0
- package/src/http-server/server-client.mjs +47 -0
- package/src/http-server/worker-handler/index.mjs +79 -0
- package/src/http-server/worker-handler/worker-script.mjs +4 -0
- package/src/http-server/worker-handler/worker-thread.mjs +65 -0
- package/src/{logger.cjs → logger.mjs} +2 -2
- package/src/routes/base-route.mjs +34 -0
- package/src/routes/{get-route.cjs → get-route.mjs} +5 -3
- package/src/routes/index.mjs +9 -0
- package/src/routes/{resolver.cjs → resolver.mjs} +17 -9
- package/src/routes/{resource-route.cjs → resource-route.mjs} +9 -5
- package/src/routes/root-route.mjs +6 -0
- package/src/spec/index.mjs +5 -0
- package/src/templates/configuration.mjs +17 -0
- package/src/templates/generate-migration.mjs +11 -0
- package/src/templates/routes.mjs +11 -0
- package/src/utils/file-exists.mjs +13 -0
- package/bin/velocious +0 -14
- package/index.cjs +0 -13
- package/spec/dummy/config/databases.example.json +0 -10
- package/spec/dummy/config/databases.json +0 -0
- package/spec/dummy/config/databases.peakflow.json +0 -11
- package/spec/dummy/index.cjs +0 -40
- package/spec/dummy/src/models/task.cjs +0 -4
- package/spec/dummy/src/routes/tasks/controller.cjs +0 -18
- package/src/application.cjs +0 -36
- package/src/configuration.cjs +0 -14
- package/src/database/connection/drivers/mysql/index.cjs +0 -5
- package/src/database/connection/drivers/mysql/options.cjs +0 -7
- package/src/database/connection/drivers/mysql/query-parser.cjs +0 -26
- package/src/database/connection/index.cjs +0 -2
- package/src/database/handler.cjs +0 -5
- package/src/database/index.cjs +0 -9
- package/src/database/pool/index.cjs +0 -2
- package/src/database/query/from-base.cjs +0 -15
- package/src/database/query/from-plain.cjs +0 -12
- package/src/database/query/from-table.cjs +0 -12
- package/src/database/query/index.cjs +0 -59
- package/src/database/query/join-base.cjs +0 -15
- package/src/database/query/join-plain.cjs +0 -12
- package/src/database/query/select-base.cjs +0 -15
- package/src/database/query/select-plain.cjs +0 -12
- package/src/database/record/index.cjs +0 -5
- package/src/http-server/client/request-parser.cjs +0 -92
- package/src/http-server/client/request.cjs +0 -25
- package/src/http-server/index.cjs +0 -78
- package/src/http-server/worker-handler/index.cjs +0 -78
- package/src/http-server/worker-handler/socket-handler.cjs +0 -35
- package/src/http-server/worker-handler/worker-script.cjs +0 -4
- package/src/http-server/worker-handler/worker-thread.cjs +0 -49
- package/src/routes/base-route.cjs +0 -25
- package/src/routes/index.cjs +0 -9
- package/src/routes/root-route.cjs +0 -4
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import DatabasePool from "../pool/index.mjs"
|
|
2
|
+
import Handler from "../handler.mjs"
|
|
3
|
+
import inflection from "inflection"
|
|
4
|
+
import Query from "../query/index.mjs"
|
|
5
|
+
import RecordNotFoundError from "./record-not-found-error.mjs"
|
|
6
|
+
|
|
7
|
+
export default class VelociousDatabaseRecord {
|
|
8
|
+
static connection() {
|
|
9
|
+
const connection = DatabasePool.current().getCurrentConnection()
|
|
10
|
+
|
|
11
|
+
if (!connection) throw new Error("No connection?")
|
|
12
|
+
|
|
13
|
+
return connection
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static async find(recordId) {
|
|
17
|
+
const conditions = {}
|
|
18
|
+
|
|
19
|
+
conditions[this.primaryKey()] = recordId
|
|
20
|
+
|
|
21
|
+
const record = await this.where(conditions).first()
|
|
22
|
+
|
|
23
|
+
if (!record) {
|
|
24
|
+
throw new RecordNotFoundError(`Couldn't find ${this.name} with '${this.primaryKey()}'=${recordId}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return record
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async last() {
|
|
31
|
+
const query = this._newQuery().order(this.primaryKey()).limit(1)
|
|
32
|
+
const record = await query.last()
|
|
33
|
+
|
|
34
|
+
return record
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static primaryKey() {
|
|
38
|
+
return "id"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async save() {
|
|
42
|
+
if (this.isPersisted()) {
|
|
43
|
+
return await this._updateRecordWithChanges()
|
|
44
|
+
} else {
|
|
45
|
+
return await this._createNewRecord()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static tableName() {
|
|
50
|
+
return inflection.underscore(inflection.pluralize(this.name))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static _newQuery() {
|
|
54
|
+
const handler = new Handler()
|
|
55
|
+
const query = new Query({
|
|
56
|
+
driver: this.connection(),
|
|
57
|
+
handler,
|
|
58
|
+
modelClass: this
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return query.from(this.tableName())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static orderableColumn() {
|
|
65
|
+
// Allow to change to 'created_at' if using UUID?
|
|
66
|
+
|
|
67
|
+
return this.primaryKey()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static where(object) {
|
|
71
|
+
const query = this._newQuery().where(object)
|
|
72
|
+
|
|
73
|
+
return query
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
constructor(attributes = {}) {
|
|
77
|
+
this._attributes = attributes
|
|
78
|
+
this._changes = {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
assign(attributesToAssign) {
|
|
82
|
+
for (const attributeToAssign in attributesToAssign) {
|
|
83
|
+
this._changes[attributeToAssign] = attributesToAssign[attributeToAssign]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
attributes() {
|
|
88
|
+
return Object.assign({}, this._attributes, this._changes)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_connection() {
|
|
92
|
+
if (this.__connection) return this.__connection
|
|
93
|
+
|
|
94
|
+
return this.constructor.connection()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async destroy() {
|
|
98
|
+
const conditions = {}
|
|
99
|
+
|
|
100
|
+
conditions[this.constructor.primaryKey()] = this.id()
|
|
101
|
+
|
|
102
|
+
const sql = this._connection().deleteSql({
|
|
103
|
+
conditions,
|
|
104
|
+
tableName: this._tableName()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
await this._connection().query(sql)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_tableName() {
|
|
111
|
+
if (this.__tableName) return this.__tableName
|
|
112
|
+
|
|
113
|
+
return this.constructor.tableName()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
readAttribute(attributeName) {
|
|
117
|
+
if (attributeName in this._changes) return this._changes[attributeName]
|
|
118
|
+
|
|
119
|
+
return this._attributes[attributeName]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async _createNewRecord() {
|
|
123
|
+
if (!this.constructor.connection()["insertSql"]) {
|
|
124
|
+
throw new Error(`No insertSql on ${this.constructor.connection().constructor.name}`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const sql = this._connection().insertSql({
|
|
128
|
+
tableName: this._tableName(),
|
|
129
|
+
data: this.attributes()
|
|
130
|
+
})
|
|
131
|
+
const result = await this._connection().query(sql)
|
|
132
|
+
const id = result.insertId
|
|
133
|
+
|
|
134
|
+
await this._reloadWithId(id)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async _updateRecordWithChanges() {
|
|
138
|
+
const conditions = {}
|
|
139
|
+
|
|
140
|
+
conditions[this.constructor.primaryKey()] = this.id()
|
|
141
|
+
|
|
142
|
+
const sql = this._connection().updateSql({
|
|
143
|
+
tableName: this._tableName(),
|
|
144
|
+
data: this._changes,
|
|
145
|
+
conditions
|
|
146
|
+
})
|
|
147
|
+
await this._connection().query(sql)
|
|
148
|
+
await this._reloadWithId(this.id())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
id() {
|
|
152
|
+
return this.readAttribute(this.constructor.primaryKey())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
isPersisted() {
|
|
156
|
+
if (this.id()) return true
|
|
157
|
+
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isNewRecord() {
|
|
162
|
+
return !this.isPersisted()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async _reloadWithId(id) {
|
|
166
|
+
const primaryKey = this.constructor.primaryKey()
|
|
167
|
+
const whereObject = {}
|
|
168
|
+
|
|
169
|
+
whereObject[primaryKey] = id
|
|
170
|
+
|
|
171
|
+
const query = this.constructor.where(whereObject)
|
|
172
|
+
const reloadedModel = await query.first()
|
|
173
|
+
|
|
174
|
+
this._attributes = reloadedModel.attributes()
|
|
175
|
+
this._changes = {}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async reload() {
|
|
179
|
+
this._reloadWithId(this.readAttribute("id"))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async update(attributesToAssign) {
|
|
183
|
+
if (attributesToAssign) this.assign(attributesToAssign)
|
|
184
|
+
|
|
185
|
+
await this.save()
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default class RecordNotFoundError extends Error {}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
class TableColumn {
|
|
2
|
+
constructor(name, args) {
|
|
3
|
+
this.args = args
|
|
4
|
+
this.name = name
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
class TableIndex {
|
|
9
|
+
constructor(columns, args) {
|
|
10
|
+
this.args = args
|
|
11
|
+
this.columns = columns
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getColumns = () => this.columns
|
|
15
|
+
getName = () => this.args.name
|
|
16
|
+
getUnique = () => Boolean(this.args.unique)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class TableReference {
|
|
20
|
+
constructor(name, args) {
|
|
21
|
+
this.args = args
|
|
22
|
+
this.name = name
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default class TableData {
|
|
27
|
+
_columns = []
|
|
28
|
+
_indexes = []
|
|
29
|
+
_references = []
|
|
30
|
+
|
|
31
|
+
constructor(name, args = {}) {
|
|
32
|
+
this.args = args
|
|
33
|
+
this._name = name
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getColumns = () => this._columns
|
|
37
|
+
getName = () => this._name
|
|
38
|
+
getIfNotExists = () => this.args.ifNotExists
|
|
39
|
+
getIndexes = () => this._indexes
|
|
40
|
+
getReferences = () => this._references
|
|
41
|
+
|
|
42
|
+
bigint(name, args = {}) {
|
|
43
|
+
const columnArgs = Object.assign({type: "bigint"}, args)
|
|
44
|
+
const column = new TableColumn(name, columnArgs)
|
|
45
|
+
|
|
46
|
+
this._columns.push(column)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
references(name, args = {}) {
|
|
50
|
+
const columnName = `${name}_id`
|
|
51
|
+
const indexName = `index_on_${columnName}`
|
|
52
|
+
const reference = new TableReference(name, args)
|
|
53
|
+
const columnArgs = Object.assign({type: "bigint"}, args)
|
|
54
|
+
const column = new TableColumn(columnName, columnArgs)
|
|
55
|
+
const index = new TableIndex([column], {name: indexName})
|
|
56
|
+
|
|
57
|
+
this._columns.push(column)
|
|
58
|
+
this._indexes.push(index)
|
|
59
|
+
this._references.push(reference)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
string(name, args) {
|
|
63
|
+
const columnArgs = Object.assign({type: "string"}, args)
|
|
64
|
+
const column = new TableColumn(name, columnArgs)
|
|
65
|
+
|
|
66
|
+
this._columns.push(column)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
text(name, args) {
|
|
70
|
+
const columnArgs = Object.assign({type: "text"}, args)
|
|
71
|
+
const column = new TableColumn(name, columnArgs)
|
|
72
|
+
|
|
73
|
+
this._columns.push(column)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
timestamps() {
|
|
77
|
+
const createdAtColumn = new TableColumn("created_at", {type: "datetime"})
|
|
78
|
+
const updatedAtColumn = new TableColumn("updated_at", {type: "datetime"})
|
|
79
|
+
|
|
80
|
+
this._columns.push(createdAtColumn)
|
|
81
|
+
this._columns.push(updatedAtColumn)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const RequestRunner = require("./request-runner.cjs")
|
|
1
|
+
import {digg} from "diggerize"
|
|
2
|
+
import {EventEmitter} from "events"
|
|
3
|
+
import logger from "../../logger.mjs"
|
|
4
|
+
import Request from "./request.mjs"
|
|
5
|
+
import RequestRunner from "./request-runner.mjs"
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
export default class VeoliciousHttpServerClient {
|
|
9
8
|
events = new EventEmitter()
|
|
10
9
|
state = "initial"
|
|
11
10
|
|
|
@@ -17,7 +16,7 @@ module.exports = class VeoliciousHttpServerClient {
|
|
|
17
16
|
this.onExecuteRequest = onExecuteRequest
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
executeCurrentRequest() {
|
|
19
|
+
executeCurrentRequest = () => {
|
|
21
20
|
logger(this, "executeCurrentRequest")
|
|
22
21
|
|
|
23
22
|
this.state = "response"
|
|
@@ -28,7 +27,7 @@ module.exports = class VeoliciousHttpServerClient {
|
|
|
28
27
|
routes: this.routes
|
|
29
28
|
})
|
|
30
29
|
|
|
31
|
-
requestRunner.events.on("done",
|
|
30
|
+
requestRunner.events.on("done", this.sendResponse)
|
|
32
31
|
requestRunner.run()
|
|
33
32
|
}
|
|
34
33
|
|
|
@@ -37,7 +36,7 @@ module.exports = class VeoliciousHttpServerClient {
|
|
|
37
36
|
this.currentRequest = new Request({
|
|
38
37
|
configuration: this.configuration
|
|
39
38
|
})
|
|
40
|
-
this.currentRequest.requestParser.events.on("done",
|
|
39
|
+
this.currentRequest.requestParser.events.on("done", this.executeCurrentRequest)
|
|
41
40
|
this.currentRequest.feed(data)
|
|
42
41
|
this.state = "requestStarted"
|
|
43
42
|
} else if (this.state == "requestStarted") {
|
|
@@ -47,7 +46,7 @@ module.exports = class VeoliciousHttpServerClient {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
sendResponse(requestRunner) {
|
|
49
|
+
sendResponse = (requestRunner) => {
|
|
51
50
|
const response = digg(requestRunner, "response")
|
|
52
51
|
const body = response.getBody()
|
|
53
52
|
const date = new Date()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export default class ParamsToObject {
|
|
2
|
+
constructor(object) {
|
|
3
|
+
this.object = object
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
toObject() {
|
|
7
|
+
const result = {}
|
|
8
|
+
|
|
9
|
+
for(const key in this.object) {
|
|
10
|
+
const value = this.object[key]
|
|
11
|
+
|
|
12
|
+
this.treatInitial(key, value, result)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return result
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
treatInitial(key, value, result) {
|
|
19
|
+
const firstMatch = key.match(/^(.+?)(\[([\s\S]+$))/)
|
|
20
|
+
|
|
21
|
+
if (firstMatch) {
|
|
22
|
+
const inputName = firstMatch[1]
|
|
23
|
+
const rest = firstMatch[2]
|
|
24
|
+
|
|
25
|
+
let newResult
|
|
26
|
+
|
|
27
|
+
if (inputName in result) {
|
|
28
|
+
newResult = result[inputName]
|
|
29
|
+
} else if (rest == "[]") {
|
|
30
|
+
newResult = []
|
|
31
|
+
result[inputName] = newResult
|
|
32
|
+
} else {
|
|
33
|
+
newResult = {}
|
|
34
|
+
result[inputName] = newResult
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.treatSecond(value, rest, newResult)
|
|
38
|
+
} else {
|
|
39
|
+
result[key] = value
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
treatSecond(value, rest, result) {
|
|
44
|
+
const secondMatch = rest.match(/^\[(.*?)\]([\s\S]*)$/)
|
|
45
|
+
const key = secondMatch[1]
|
|
46
|
+
const newRest = secondMatch[2]
|
|
47
|
+
|
|
48
|
+
let newResult
|
|
49
|
+
|
|
50
|
+
if (rest == "[]") {
|
|
51
|
+
result.push(value)
|
|
52
|
+
} else if (newRest == "") {
|
|
53
|
+
result[key] = value
|
|
54
|
+
} else {
|
|
55
|
+
if (typeof result == "object" && key in result) {
|
|
56
|
+
newResult = result[key]
|
|
57
|
+
} else if (newRest == "[]") {
|
|
58
|
+
newResult = []
|
|
59
|
+
result[key] = newResult
|
|
60
|
+
} else {
|
|
61
|
+
newResult = {}
|
|
62
|
+
result[key] = newResult
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.treatSecond(value, newRest, newResult)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export default class FormDataPart {
|
|
2
|
+
headers = {}
|
|
3
|
+
body = []
|
|
4
|
+
|
|
5
|
+
addHeader(header) {
|
|
6
|
+
const name = header.formattedName
|
|
7
|
+
|
|
8
|
+
this.headers[name] = header
|
|
9
|
+
|
|
10
|
+
if (name == "content-disposition") {
|
|
11
|
+
const match = header.value.match(/^form-data; name="(.+)"$/)
|
|
12
|
+
|
|
13
|
+
if (match) {
|
|
14
|
+
this.name = match[1]
|
|
15
|
+
} else {
|
|
16
|
+
console.error(`Couldn't match name from content-disposition: ${header.value}`)
|
|
17
|
+
}
|
|
18
|
+
} else if (name == "content-length") {
|
|
19
|
+
this.contentLength = parseInt(header.value)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
finish() {
|
|
24
|
+
this.value = String.fromCharCode.apply(null, this.body)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getName() {
|
|
28
|
+
if (!this.name) throw new Error("Name hasn't been set")
|
|
29
|
+
|
|
30
|
+
return this.name
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getValue() {
|
|
34
|
+
if (!this.value) throw new Error("Value hasn't been set")
|
|
35
|
+
|
|
36
|
+
return this.value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
removeFromBody(text) {
|
|
40
|
+
this.body = this.body.slice(0, this.body.length - text.length)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {EventEmitter} from "events"
|
|
2
|
+
import FormDataPart from "./form-data-part.mjs"
|
|
3
|
+
import Header from "./header.mjs"
|
|
4
|
+
import Incorporator from "incorporator"
|
|
5
|
+
import logger from "../../../logger.mjs"
|
|
6
|
+
import ParamsToObject from "../params-to-object.mjs"
|
|
7
|
+
import querystring from "querystring"
|
|
8
|
+
|
|
9
|
+
export default class RequestBuffer {
|
|
10
|
+
bodyLength = 0
|
|
11
|
+
data = []
|
|
12
|
+
events = new EventEmitter()
|
|
13
|
+
headers = []
|
|
14
|
+
headersByName = {}
|
|
15
|
+
params = {}
|
|
16
|
+
postBody = ""
|
|
17
|
+
postBodyChars = []
|
|
18
|
+
readingBody = false
|
|
19
|
+
state = "status"
|
|
20
|
+
|
|
21
|
+
constructor({configuration}) {
|
|
22
|
+
this.configuration = configuration
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
feed(data) {
|
|
26
|
+
for (const char of data) {
|
|
27
|
+
if (this.readingBody) this.bodyLength += 1
|
|
28
|
+
|
|
29
|
+
switch(this.state) {
|
|
30
|
+
case "status":
|
|
31
|
+
case "headers":
|
|
32
|
+
case "multi-part-form-data":
|
|
33
|
+
case "multi-part-form-data-header":
|
|
34
|
+
this.data.push(char)
|
|
35
|
+
|
|
36
|
+
if (char == 10) {
|
|
37
|
+
const line = String.fromCharCode.apply(null, this.data)
|
|
38
|
+
|
|
39
|
+
this.data = []
|
|
40
|
+
this.parse(line)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
break
|
|
44
|
+
case "multi-part-form-data-body":
|
|
45
|
+
const body = this.formDataPart.body
|
|
46
|
+
|
|
47
|
+
body.push(char)
|
|
48
|
+
|
|
49
|
+
const possibleBoundaryEndPosition = body.length - this.boundaryLineEnd.length
|
|
50
|
+
const possibleBoundaryEndChars = body.slice(possibleBoundaryEndPosition, body.length)
|
|
51
|
+
const possibleBoundaryEnd = String.fromCharCode.apply(null, possibleBoundaryEndChars)
|
|
52
|
+
|
|
53
|
+
const possibleBoundaryNextPosition = body.length - this.boundaryLineNext.length
|
|
54
|
+
const possibleBoundaryNextChars = body.slice(possibleBoundaryNextPosition, body.length)
|
|
55
|
+
const possibleBoundaryNext = String.fromCharCode.apply(null, possibleBoundaryNextChars)
|
|
56
|
+
|
|
57
|
+
if (possibleBoundaryEnd == this.boundaryLineEnd) {
|
|
58
|
+
this.formDataPart.removeFromBody(possibleBoundaryEnd)
|
|
59
|
+
this.formDataPartDone()
|
|
60
|
+
this.completeRequest()
|
|
61
|
+
} else if (possibleBoundaryNext == this.boundaryLineNext) {
|
|
62
|
+
this.formDataPart.removeFromBody(possibleBoundaryNext)
|
|
63
|
+
this.formDataPartDone()
|
|
64
|
+
this.newFormDataPart()
|
|
65
|
+
} else if (this.contentLength && this.bodyLength >= this.contentLength) {
|
|
66
|
+
this.formDataPartDone()
|
|
67
|
+
this.completeRequest()
|
|
68
|
+
} else if (this.formDataPart.contentLength && this.bodyLength >= this.formDataPart.contentLength) {
|
|
69
|
+
this.formDataPartDone()
|
|
70
|
+
|
|
71
|
+
throw new Error("stub")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
break
|
|
75
|
+
case "post-body":
|
|
76
|
+
this.bodyLength += 1
|
|
77
|
+
this.postBodyChars.push(char)
|
|
78
|
+
|
|
79
|
+
if (this.contentLength && this.postBodyChars.length >= this.contentLength) {
|
|
80
|
+
this.postRequestDone()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
break
|
|
84
|
+
default:
|
|
85
|
+
console.error(`Unknown state: ${this.state}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getHeader = (name) => this.headersByName[name.toLowerCase().trim()]
|
|
91
|
+
|
|
92
|
+
formDataPartDone() {
|
|
93
|
+
const formDataPart = this.formDataPart
|
|
94
|
+
|
|
95
|
+
this.formDataPart = undefined
|
|
96
|
+
formDataPart.finish()
|
|
97
|
+
|
|
98
|
+
this.events.emit("form-data-part", formDataPart)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
isMultiPartyFormData = () => this.multiPartyFormData
|
|
102
|
+
|
|
103
|
+
newFormDataPart() {
|
|
104
|
+
this.formDataPart = new FormDataPart()
|
|
105
|
+
this.setState("multi-part-form-data-header")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
parse(line) {
|
|
109
|
+
if (this.state == "status") {
|
|
110
|
+
this.parseStatusLine(line)
|
|
111
|
+
} else if (this.state == "headers") {
|
|
112
|
+
this.parseHeader(line)
|
|
113
|
+
} else if (this.state == "multi-part-form-data") {
|
|
114
|
+
if (line == this.boundaryLine) {
|
|
115
|
+
this.newFormDataPart()
|
|
116
|
+
} else if (line == "\r\n") {
|
|
117
|
+
this.setState("done")
|
|
118
|
+
} else {
|
|
119
|
+
throw new Error(`Expected boundary line but didn't get it: ${line}`)
|
|
120
|
+
}
|
|
121
|
+
} else if (this.state == "multi-part-form-data-header") {
|
|
122
|
+
const header = this.readHeaderFromLine(line)
|
|
123
|
+
|
|
124
|
+
if (header) {
|
|
125
|
+
this.formDataPart.addHeader(header)
|
|
126
|
+
this.state == "multi-part-form-data"
|
|
127
|
+
} else if (line == "\r\n") {
|
|
128
|
+
this.setState("multi-part-form-data-body")
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error(`Unknown state: ${this.state}`)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
readHeaderFromLine(line) {
|
|
136
|
+
let match
|
|
137
|
+
|
|
138
|
+
if (match = line.match(/^(.+): (.+)\r\n/)) {
|
|
139
|
+
const header = new Header(match[1], match[2])
|
|
140
|
+
|
|
141
|
+
return header
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parseHeader(line) {
|
|
146
|
+
const header = this.readHeaderFromLine(line)
|
|
147
|
+
|
|
148
|
+
if (header) {
|
|
149
|
+
this.headersByName[header.formattedName] = header
|
|
150
|
+
|
|
151
|
+
if (header.formattedName == "content-length") this.contentLength = parseInt(header.value)
|
|
152
|
+
|
|
153
|
+
this.events.emit("header", header)
|
|
154
|
+
} else if (line == "\r\n") {
|
|
155
|
+
if (this.httpMethod.toUpperCase() == "GET") {
|
|
156
|
+
this.completeRequest()
|
|
157
|
+
} else if (this.httpMethod.toUpperCase() == "POST") {
|
|
158
|
+
this.readingBody = true
|
|
159
|
+
|
|
160
|
+
const match = this.getHeader("content-type").value.match(/^multipart\/form-data;\s*boundary=(.+)$/i)
|
|
161
|
+
|
|
162
|
+
if (match) {
|
|
163
|
+
this.boundary = match[1]
|
|
164
|
+
this.boundaryLine = `--${this.boundary}\r\n`
|
|
165
|
+
this.boundaryLineNext = `\r\n--${this.boundary}\r\n`
|
|
166
|
+
this.boundaryLineEnd = `\r\n--${this.boundary}--`
|
|
167
|
+
this.multiPartyFormData = true
|
|
168
|
+
this.setState("multi-part-form-data")
|
|
169
|
+
} else {
|
|
170
|
+
this.setState("post-body")
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
throw new Error(`Unknown HTTP method: ${this.httpMethod}`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
parseStatusLine(line) {
|
|
179
|
+
const match = line.match(/^(GET|POST) (.+?) HTTP\/1\.1\r\n/)
|
|
180
|
+
|
|
181
|
+
if (!match) {
|
|
182
|
+
throw new Error(`Couldn't match status line from: ${line}`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.httpMethod = match[1]
|
|
186
|
+
this.path = match[2]
|
|
187
|
+
this.setState("headers")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
postRequestDone() {
|
|
191
|
+
this.postBody += String.fromCharCode.apply(null, this.postBodyChars)
|
|
192
|
+
this.parseQueryStringPostParams()
|
|
193
|
+
this.completeRequest()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setState(newState) {
|
|
197
|
+
logger(this, `Changing state from ${this.state} to ${newState}`)
|
|
198
|
+
|
|
199
|
+
this.state = newState
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
completeRequest = () => {
|
|
203
|
+
this.state = "completed"
|
|
204
|
+
|
|
205
|
+
if (this.getHeader("content-type")?.value?.startsWith("application/json")) {
|
|
206
|
+
this.parseApplicationJsonParams()
|
|
207
|
+
} else if (this.multiPartyFormData) {
|
|
208
|
+
// Done after each new form data part
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.events.emit("completed")
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
parseApplicationJsonParams() {
|
|
215
|
+
const newParams = JSON.parse(this.postBody)
|
|
216
|
+
const incorporator = new Incorporator({objects: [this.params, newParams]})
|
|
217
|
+
|
|
218
|
+
incorporator.merge()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
parseQueryStringPostParams() {
|
|
222
|
+
const unparsedParams = querystring.parse(this.postBody)
|
|
223
|
+
const paramsToObject = new ParamsToObject(unparsedParams)
|
|
224
|
+
const newParams = paramsToObject.toObject()
|
|
225
|
+
const incorporator = new Incorporator({objects: [this.params, newParams]})
|
|
226
|
+
|
|
227
|
+
incorporator.merge()
|
|
228
|
+
}
|
|
229
|
+
}
|