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,72 @@
|
|
|
1
|
+
import fetch from "node-fetch"
|
|
2
|
+
import Dummy from "../dummy/index.mjs"
|
|
3
|
+
import querystring from "querystring"
|
|
4
|
+
import Task from "../dummy/src/models/task.mjs"
|
|
5
|
+
|
|
6
|
+
describe("HttpServer", () => {
|
|
7
|
+
it("handles post requests", async () => {
|
|
8
|
+
await Dummy.run(async () => {
|
|
9
|
+
const postData = querystring.stringify({"task[name]": "Test create task"})
|
|
10
|
+
const response = await fetch(
|
|
11
|
+
"http://localhost:3006/tasks",
|
|
12
|
+
{
|
|
13
|
+
body: postData,
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
16
|
+
"Content-Length": Buffer.byteLength(postData)
|
|
17
|
+
},
|
|
18
|
+
method: "POST"
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
const text = await response.text()
|
|
22
|
+
const createdTask = await Task.last()
|
|
23
|
+
|
|
24
|
+
expect(text).toEqual('{"status":"success"}')
|
|
25
|
+
expect(createdTask.readAttribute("name")).toEqual("Test create task")
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("handles post json requests", async () => {
|
|
30
|
+
await Dummy.run(async () => {
|
|
31
|
+
const postData = JSON.stringify({task: {name: "Test create task"}})
|
|
32
|
+
const response = await fetch(
|
|
33
|
+
"http://localhost:3006/tasks",
|
|
34
|
+
{
|
|
35
|
+
body: postData,
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"Content-Length": Buffer.byteLength(postData)
|
|
39
|
+
},
|
|
40
|
+
method: "POST"
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
const text = await response.text()
|
|
44
|
+
const createdTask = await Task.last()
|
|
45
|
+
|
|
46
|
+
expect(text).toEqual('{"status":"success"}')
|
|
47
|
+
expect(createdTask.readAttribute("name")).toEqual("Test create task")
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("handles post form-data requests", async () => {
|
|
52
|
+
await Dummy.run(async () => {
|
|
53
|
+
const body = new FormData()
|
|
54
|
+
|
|
55
|
+
body.append("task[name]", "Test create task")
|
|
56
|
+
body.append("task[description]", "This is a task")
|
|
57
|
+
|
|
58
|
+
const response = await fetch(
|
|
59
|
+
"http://localhost:3006/tasks",
|
|
60
|
+
{
|
|
61
|
+
body,
|
|
62
|
+
method: "POST"
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
const text = await response.text()
|
|
66
|
+
const createdTask = await Task.last()
|
|
67
|
+
|
|
68
|
+
expect(text).toEqual('{"status":"success"}')
|
|
69
|
+
expect(createdTask.readAttribute("name")).toEqual("Test create task")
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"spec_dir": "spec",
|
|
3
3
|
"spec_files": [
|
|
4
|
-
"
|
|
4
|
+
"**/*-spec.mjs"
|
|
5
5
|
],
|
|
6
6
|
"helpers": [
|
|
7
|
-
"helpers/**/*.
|
|
7
|
+
"helpers/**/*.mjs"
|
|
8
8
|
],
|
|
9
9
|
"stopSpecOnExpectationFailure": false,
|
|
10
|
-
"random": false
|
|
10
|
+
"random": false,
|
|
11
|
+
"jsLoader": "import"
|
|
11
12
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {digs} from "diggerize"
|
|
2
|
+
import logger from "./logger.mjs"
|
|
3
|
+
import HttpServer from "./http-server/index.mjs"
|
|
4
|
+
|
|
5
|
+
export default class VelociousApplication {
|
|
6
|
+
constructor({configuration, httpServer}) {
|
|
7
|
+
this.configuration = configuration
|
|
8
|
+
this.httpServerConfiguration = httpServer ?? {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async initialize() {
|
|
12
|
+
await this.configuration.initialize()
|
|
13
|
+
|
|
14
|
+
if (!this.configuration.isDatabasePoolInitialized()) {
|
|
15
|
+
await this.configuration.initializeDatabasePool()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
isActive() {
|
|
20
|
+
return this.httpServer?.isActive()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async run(callback) {
|
|
24
|
+
await this.start()
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await callback()
|
|
28
|
+
} finally {
|
|
29
|
+
this.stop()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async startHttpServer() {
|
|
34
|
+
const {configuration, httpServerConfiguration} = digs(this, "configuration", "httpServerConfiguration")
|
|
35
|
+
|
|
36
|
+
const port = httpServerConfiguration.port || 3006
|
|
37
|
+
|
|
38
|
+
logger(this, `Starting server on port ${port}`)
|
|
39
|
+
|
|
40
|
+
this.httpServer = new HttpServer({configuration, port})
|
|
41
|
+
|
|
42
|
+
await this.httpServer.start()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async stop() {
|
|
46
|
+
logger(this, "Stopping server")
|
|
47
|
+
|
|
48
|
+
await this.httpServer.stop()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {digg} from "diggerize"
|
|
2
|
+
|
|
3
|
+
export default class VelociousCliBaseCommand {
|
|
4
|
+
constructor(args) {
|
|
5
|
+
this.args = args
|
|
6
|
+
this.configuration = this.args.configuration
|
|
7
|
+
this.processArgs = args.processArgs
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
directory = () => digg(this, "configuration", "directory")
|
|
11
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.mjs"
|
|
2
|
+
import {digg} from "diggerize"
|
|
3
|
+
import TableData from "../../../database/table-data/index.mjs"
|
|
4
|
+
|
|
5
|
+
export default class DbCreate extends BaseCommand{
|
|
6
|
+
async execute() {
|
|
7
|
+
this.databasePool = this.configuration.getDatabasePool()
|
|
8
|
+
this.newConfiguration = Object.assign({}, this.databasePool.getConfiguration())
|
|
9
|
+
|
|
10
|
+
if (this.args.testing) this.result = []
|
|
11
|
+
|
|
12
|
+
// Use a database known to exist. Since we are creating the database, it shouldn't actually exist which would make connecting fail.
|
|
13
|
+
this.newConfiguration.database = this.newConfiguration.useDatabase || "mysql"
|
|
14
|
+
|
|
15
|
+
this.databaseConnection = await this.databasePool.spawnConnectionWithConfiguration(this.newConfiguration)
|
|
16
|
+
await this.databaseConnection.connect()
|
|
17
|
+
|
|
18
|
+
this.createDatabase()
|
|
19
|
+
await this.createSchemaMigrationsTable()
|
|
20
|
+
|
|
21
|
+
await this.databaseConnection.close()
|
|
22
|
+
|
|
23
|
+
if (this.args.testing) return this.result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async createDatabase() {
|
|
27
|
+
const databaseName = digg(this.databasePool.getConfiguration(), "database")
|
|
28
|
+
const sql = this.databaseConnection.createDatabaseSql(databaseName, {ifNotExists: true})
|
|
29
|
+
|
|
30
|
+
if (this.args.testing) {
|
|
31
|
+
this.result.push({databaseName, sql})
|
|
32
|
+
} else {
|
|
33
|
+
await this.databaseConnection.query(sql)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async createSchemaMigrationsTable() {
|
|
38
|
+
const schemaMigrationsTable = new TableData("schema_migrations", {ifNotExists: true})
|
|
39
|
+
|
|
40
|
+
schemaMigrationsTable.string("version", {null: false, primaryKey: true})
|
|
41
|
+
|
|
42
|
+
const createSchemaMigrationsTableSql = this.databaseConnection.createTableSql(schemaMigrationsTable)
|
|
43
|
+
|
|
44
|
+
if (this.args.testing) {
|
|
45
|
+
this.result.push({createSchemaMigrationsTableSql})
|
|
46
|
+
} else {
|
|
47
|
+
await this.databaseConnection.query(createSchemaMigrationsTableSql)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.mjs"
|
|
2
|
+
import {digg} from "diggerize"
|
|
3
|
+
import fs from "node:fs/promises"
|
|
4
|
+
import inflection from "inflection"
|
|
5
|
+
|
|
6
|
+
export default class DbMigrate extends BaseCommand {
|
|
7
|
+
async execute() {
|
|
8
|
+
const projectPath = digg(this.configuration, "directory")
|
|
9
|
+
const migrationsPath = `${projectPath}/src/database/migrations`
|
|
10
|
+
let files = await fs.readdir(migrationsPath)
|
|
11
|
+
|
|
12
|
+
files = files
|
|
13
|
+
.map((file) => {
|
|
14
|
+
const match = file.match(/^(\d{14})-(.+)\.mjs$/)
|
|
15
|
+
|
|
16
|
+
if (!match) return null
|
|
17
|
+
|
|
18
|
+
const date = parseInt(match[1])
|
|
19
|
+
const migrationName = match[2]
|
|
20
|
+
const migrationClassName = inflection.camelize(migrationName)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
file,
|
|
24
|
+
fullPath: `${migrationsPath}/${file}`,
|
|
25
|
+
date,
|
|
26
|
+
migrationClassName
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
.filter((migration) => Boolean(migration))
|
|
30
|
+
.sort((migration1, migration2) => migration1.date - migration2.date)
|
|
31
|
+
|
|
32
|
+
for (const migration of files) {
|
|
33
|
+
await this.runMigrationFile(migration)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async runMigrationFile(migration) {
|
|
38
|
+
if (!this.configuration.isDatabasePoolInitialized()) {
|
|
39
|
+
await this.configuration.initializeDatabasePool()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await this.configuration.databasePool.withConnection(async () => {
|
|
43
|
+
const migrationImport = await import(migration.fullPath)
|
|
44
|
+
const MigrationClass = migrationImport.default
|
|
45
|
+
const migrationInstance = new MigrationClass({
|
|
46
|
+
configuration: this.configuration
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (migrationInstance.change) {
|
|
50
|
+
await migrationInstance.change()
|
|
51
|
+
} else if (migrationInstance.up) {
|
|
52
|
+
await migrationInstance.up()
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`'change' or 'up' didn't exist on migration: ${migration.file}`)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.mjs"
|
|
2
|
+
import fs from "node:fs/promises"
|
|
3
|
+
|
|
4
|
+
export default class DbDestroyMigration extends BaseCommand {
|
|
5
|
+
async execute() {
|
|
6
|
+
const migrationName = this.processArgs[1]
|
|
7
|
+
const migrationDir = `${this.configuration.directory}/src/database/migrations`
|
|
8
|
+
const migrationFiles = await fs.readdir(migrationDir)
|
|
9
|
+
const destroyed = []
|
|
10
|
+
|
|
11
|
+
for (const migrationFile of migrationFiles) {
|
|
12
|
+
const match = migrationFile.match(/^(\d{14})-(.+)\.mjs$/)
|
|
13
|
+
|
|
14
|
+
if (!match) {
|
|
15
|
+
continue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fileName = match[2]
|
|
19
|
+
|
|
20
|
+
if (fileName != migrationName) continue
|
|
21
|
+
|
|
22
|
+
const fullFilePath = `${migrationDir}/${migrationFile}`
|
|
23
|
+
destroyed.push(fileName)
|
|
24
|
+
|
|
25
|
+
if (!this.args.testing) {
|
|
26
|
+
console.log(`Destroy src/database/migrations/${migrationFile}`)
|
|
27
|
+
await fs.unlink(fullFilePath)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (this.args.testing) {
|
|
32
|
+
return {destroyed}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.mjs"
|
|
2
|
+
import {dirname} from "path"
|
|
3
|
+
import {fileURLToPath} from "url"
|
|
4
|
+
import fileExists from "../../../utils/file-exists.mjs"
|
|
5
|
+
import fs from "node:fs/promises"
|
|
6
|
+
import inflection from "inflection"
|
|
7
|
+
import strftime from "strftime"
|
|
8
|
+
|
|
9
|
+
export default class DbGenerateMigration extends BaseCommand {
|
|
10
|
+
async execute() {
|
|
11
|
+
const migrationName = this.processArgs[1]
|
|
12
|
+
const migrationNameCamelized = inflection.camelize(migrationName.replaceAll("-", "_"))
|
|
13
|
+
const date = new Date()
|
|
14
|
+
const migrationNumber = strftime("%Y%m%d%H%M%S")
|
|
15
|
+
const migrationFileName = `${migrationNumber}-${migrationName}.mjs`
|
|
16
|
+
const __filename = fileURLToPath(`${import.meta.url}/../../..`)
|
|
17
|
+
const __dirname = dirname(__filename)
|
|
18
|
+
const templateFilePath = `${__dirname}/templates/generate-migration.mjs`
|
|
19
|
+
const migrationContentBuffer = await fs.readFile(templateFilePath)
|
|
20
|
+
const migrationContent = migrationContentBuffer.toString().replaceAll("__MIGRATION_NAME__", migrationNameCamelized)
|
|
21
|
+
const migrationDir = `${process.cwd()}/src/database/migrations`
|
|
22
|
+
const migrationPath = `${migrationDir}/${migrationFileName}`
|
|
23
|
+
|
|
24
|
+
if (this.args.testing) {
|
|
25
|
+
return {date, migrationContent, migrationName, migrationNameCamelized, migrationNumber, migrationPath }
|
|
26
|
+
} else {
|
|
27
|
+
if (!await fileExists(migrationDir)) {
|
|
28
|
+
await fs.mkdir(migrationDir, {recursive: true})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await fs.writeFile(migrationPath, migrationContent)
|
|
32
|
+
|
|
33
|
+
console.log(`create src/database/migrations/${migrationFileName}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import BaseCommand from "../base-command.mjs"
|
|
2
|
+
import {dirname} from "path"
|
|
3
|
+
import fileExists from "../../utils/file-exists.mjs"
|
|
4
|
+
import {fileURLToPath} from "url"
|
|
5
|
+
import fs from "node:fs/promises"
|
|
6
|
+
|
|
7
|
+
export default class VelociousCliCommandsInit extends BaseCommand {
|
|
8
|
+
async execute() {
|
|
9
|
+
const __filename = fileURLToPath(`${import.meta.url}/../../..`)
|
|
10
|
+
const velocipusPath = dirname(__filename)
|
|
11
|
+
const projectPath = this.configuration?.directory || process.cwd()
|
|
12
|
+
const projectConfigPath = `${projectPath}/src/config`
|
|
13
|
+
const fileMappings = [
|
|
14
|
+
{
|
|
15
|
+
source: `${velocipusPath}/src/templates/configuration.mjs`,
|
|
16
|
+
target: `${projectConfigPath}/configuration.mjs`
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
source: `${velocipusPath}/src/templates/routes.mjs`,
|
|
20
|
+
target: `${projectConfigPath}/routes.mjs`
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
const paths = [
|
|
24
|
+
projectConfigPath,
|
|
25
|
+
`${projectPath}/database/migrations`
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
if (this.args.testing) {
|
|
29
|
+
return {
|
|
30
|
+
fileMappings
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const path of paths) {
|
|
35
|
+
if (await fileExists(path)) {
|
|
36
|
+
console.log(`Config dir already exists: ${path}`)
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`Config dir doesn't exists: ${path}`)
|
|
39
|
+
await fs.mkdir(path, {recursive: true})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const fileMapping of fileMappings) {
|
|
44
|
+
if (!await fileExists(fileMapping.source)) {
|
|
45
|
+
throw new Error(`Template doesn't exist: ${fileMapping.source}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (await fileExists(fileMapping.target)) {
|
|
49
|
+
console.log(`File already exists: ${fileMapping.target}`)
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`File doesnt exist: ${fileMapping.target}`)
|
|
52
|
+
await fs.copyFile(fileMapping.source, fileMapping.target)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dontLoadConfiguration = true
|
|
59
|
+
|
|
60
|
+
export {dontLoadConfiguration}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import BaseCommand from "../../base-command.mjs"
|
|
2
|
+
import TestFilesFinder from "./test-files-finder.mjs"
|
|
3
|
+
import TestRunner from "./test-runner.mjs"
|
|
4
|
+
|
|
5
|
+
export default class VelociousCliCommandsInit extends BaseCommand {
|
|
6
|
+
async execute() {
|
|
7
|
+
const testFilesFinder = new TestFilesFinder({directory: this.directory(), processArgs: this.processArgs})
|
|
8
|
+
const testFiles = await testFilesFinder.findTestFiles()
|
|
9
|
+
|
|
10
|
+
const testRunner = new TestRunner(testFiles)
|
|
11
|
+
|
|
12
|
+
await testRunner.run()
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from "fs/promises"
|
|
2
|
+
|
|
3
|
+
// Incredibly complex class to find files in multiple simultanious running promises to do it as fast as possible.
|
|
4
|
+
export default class TestFilesFinder {
|
|
5
|
+
static IGNORED_NAMES = [".git", "node_modules"]
|
|
6
|
+
|
|
7
|
+
constructor({directory, processArgs}) {
|
|
8
|
+
this.directory = directory
|
|
9
|
+
this.foundFiles = []
|
|
10
|
+
this.findingCount = 0
|
|
11
|
+
this.findingPromises = {}
|
|
12
|
+
this.processArgs = processArgs
|
|
13
|
+
this.testArgs = this.processArgs.filter((processArg, index) => index != 0)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async findTestFiles() {
|
|
17
|
+
await this.withFindingCount(async () => {
|
|
18
|
+
await this.findTestFilesInDir(this.directory)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
await this.waitForFindingPromises()
|
|
22
|
+
|
|
23
|
+
return this.foundFiles
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
findingPromisesLength = () => Object.keys(this.findingPromises).length
|
|
27
|
+
|
|
28
|
+
async waitForFindingPromises() {
|
|
29
|
+
while (this.findingPromisesLength() > 0) {
|
|
30
|
+
await this.waitForFindingPromisesIteration()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async waitForFindingPromisesIteration() {
|
|
35
|
+
const unfinishedPromises = []
|
|
36
|
+
|
|
37
|
+
for (const findingPromiseId in this.findingPromises) {
|
|
38
|
+
const findingPromise = this.findingPromises[findingPromiseId]
|
|
39
|
+
|
|
40
|
+
unfinishedPromises.push(findingPromise)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await Promise.all(unfinishedPromises)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
withFindingCount(callback) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const findingPromise = callback()
|
|
49
|
+
const findingCount = this.findingCount
|
|
50
|
+
|
|
51
|
+
this.findingCount += 1
|
|
52
|
+
this.findingPromises[findingCount] = findingPromise
|
|
53
|
+
|
|
54
|
+
findingPromise.finally(() => {
|
|
55
|
+
delete this.findingPromises[findingCount]
|
|
56
|
+
|
|
57
|
+
resolve()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async findTestFilesInDir(dir) {
|
|
63
|
+
await this.withFindingCount(async () => {
|
|
64
|
+
const files = await fs.readdir(dir)
|
|
65
|
+
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (TestFilesFinder.IGNORED_NAMES.includes(file)) {
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const fullPath = `${dir}/${file}`
|
|
72
|
+
const localPath = fullPath.replace(`${this.directory}/`, "")
|
|
73
|
+
const isDir = (await fs.stat(fullPath)).isDirectory()
|
|
74
|
+
|
|
75
|
+
if (isDir) {
|
|
76
|
+
this.findTestFilesInDir(fullPath)
|
|
77
|
+
} else {
|
|
78
|
+
if (this.isFileMatchingRequirements(file, localPath, fullPath)) {
|
|
79
|
+
this.foundFiles.push(fullPath)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
isFileMatchingRequirements(file, localPath, fullPath) {
|
|
87
|
+
if (this.testArgs.length > 0) {
|
|
88
|
+
for (const testArg of this.testArgs) {
|
|
89
|
+
if (testArg == localPath) {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else if (file.match(/-spec\.mjs/)) {
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default class TestRunner {
|
|
2
|
+
constructor(testFiles) {
|
|
3
|
+
this.testFiles = testFiles
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
async importTestFiles() {
|
|
7
|
+
for (const testFile of this.testFiles) {
|
|
8
|
+
const importTestFile = await import(testFile)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run() {
|
|
13
|
+
await this.importTestFiles()
|
|
14
|
+
|
|
15
|
+
console.log({foundTestFiles: this.testFiles})
|
|
16
|
+
|
|
17
|
+
throw new Error("stub")
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import configurationResolver from "../configuration-resolver.mjs"
|
|
2
|
+
import {dirname} from "path"
|
|
3
|
+
import {fileURLToPath} from "url"
|
|
4
|
+
import fileExists from "../utils/file-exists.mjs"
|
|
5
|
+
|
|
6
|
+
export default class VelociousCli {
|
|
7
|
+
constructor(args = {}) {
|
|
8
|
+
this.args = args
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute() {
|
|
12
|
+
const __filename = fileURLToPath(`${import.meta.url}/../..`)
|
|
13
|
+
const __dirname = dirname(__filename)
|
|
14
|
+
const commandParts = this.args.processArgs[0].split(":")
|
|
15
|
+
const filePaths = []
|
|
16
|
+
let filePath = `${__dirname}/src/cli/commands`
|
|
17
|
+
|
|
18
|
+
for (let commandPart of commandParts) {
|
|
19
|
+
if (commandPart == "d") commandPart = "destroy"
|
|
20
|
+
if (commandPart == "g") commandPart = "generate"
|
|
21
|
+
|
|
22
|
+
filePath += `/${commandPart}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
filePaths.push(`${filePath}/index.mjs`)
|
|
26
|
+
filePath += ".mjs"
|
|
27
|
+
filePaths.push(filePath)
|
|
28
|
+
|
|
29
|
+
let fileFound
|
|
30
|
+
|
|
31
|
+
for (const aFilePath of filePaths) {
|
|
32
|
+
if (await fileExists(aFilePath)) {
|
|
33
|
+
fileFound = aFilePath
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!fileFound) throw new Error(`Unknown command: ${this.args.processArgs[0]} which should have been one of ${filePaths.join(", ")}`)
|
|
39
|
+
|
|
40
|
+
const commandClassImport = await import(fileFound)
|
|
41
|
+
const CommandClass = commandClassImport.default
|
|
42
|
+
|
|
43
|
+
await this.loadConfiguration()
|
|
44
|
+
|
|
45
|
+
const commandInstance = new CommandClass(this.args)
|
|
46
|
+
|
|
47
|
+
if (commandInstance.initialize) {
|
|
48
|
+
await commandInstance.initialize()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return await commandInstance.execute()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async loadConfiguration() {
|
|
55
|
+
this.configuration = await configurationResolver({directory: this.args.directory})
|
|
56
|
+
this.configuration.setCurrent()
|
|
57
|
+
this.args.configuration = this.configuration
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Configuration from "./configuration.mjs"
|
|
2
|
+
|
|
3
|
+
const configurationResolver = async (args) => {
|
|
4
|
+
if (global.velociousConfiguration) {
|
|
5
|
+
return global.velociousConfiguration
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const directory = args.directory || process.cwd()
|
|
9
|
+
const configurationPath = `${directory}/src/config/configuration.mjs`
|
|
10
|
+
let configuration
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const configurationImport = await import(configurationPath)
|
|
14
|
+
|
|
15
|
+
configuration = configurationImport.default
|
|
16
|
+
} catch (error) {
|
|
17
|
+
// This might happen during an "init" CLI command where we copy a sample configuration file.
|
|
18
|
+
if (error.code != "ERR_MODULE_NOT_FOUND") throw error
|
|
19
|
+
|
|
20
|
+
configuration = new Configuration(args)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return configuration
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default configurationResolver
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import DatabasePool from "./database/pool/index.mjs"
|
|
2
|
+
import {digg} from "diggerize"
|
|
3
|
+
|
|
4
|
+
export default class VelociousConfiguration {
|
|
5
|
+
static current() {
|
|
6
|
+
if (!global.velociousConfiguration) throw new Error("A Velocious configuration hasn't been set")
|
|
7
|
+
|
|
8
|
+
return global.velociousConfiguration
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
constructor({database, debug, directory}) {
|
|
12
|
+
if (!directory) directory = process.cwd()
|
|
13
|
+
|
|
14
|
+
this.database = database
|
|
15
|
+
this.debug = debug
|
|
16
|
+
this.directory = directory
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async initialize() {
|
|
20
|
+
await this.initializeRoutes()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getDatabasePool() {
|
|
24
|
+
if (!this.isDatabasePoolInitialized()) this.initializeDatabasePool()
|
|
25
|
+
|
|
26
|
+
return this.databasePool
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
initializeDatabasePool() {
|
|
30
|
+
if (!this.database) throw new Error("No 'database' was given")
|
|
31
|
+
if (this.databasePool) throw new Error("DatabasePool has already been initialized")
|
|
32
|
+
|
|
33
|
+
this.databasePool = new DatabasePool({configuration: this})
|
|
34
|
+
this.databasePool.setCurrent()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isDatabasePoolInitialized = () => Boolean(this.databasePool)
|
|
38
|
+
|
|
39
|
+
async initializeRoutes() {
|
|
40
|
+
// Every client need to make their own routes because they probably can't be shared across different worker threads
|
|
41
|
+
const routesImport = await import(`${this.directory}/src/config/routes.mjs`)
|
|
42
|
+
|
|
43
|
+
this.routes = digg(routesImport, "default", "routes")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setCurrent() {
|
|
47
|
+
global.velociousConfiguration = this
|
|
48
|
+
}
|
|
49
|
+
}
|