@skalfa/skalfa-api-core 1.0.2
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/.github/workflows/publish.yml +40 -0
- package/dist/auth/auth.d.ts +19 -0
- package/dist/auth/auth.js +227 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth.util.d.ts +19 -0
- package/dist/auth.util.js +183 -0
- package/dist/auth.util.js.map +1 -0
- package/dist/commands/cli.d.ts +1 -0
- package/dist/commands/cli.js +78 -0
- package/dist/commands/cli.js.map +1 -0
- package/dist/commands/make/basic-controller.d.ts +2 -0
- package/dist/commands/make/basic-controller.js +40 -0
- package/dist/commands/make/basic-controller.js.map +1 -0
- package/dist/commands/make/basic-migration.d.ts +5 -0
- package/dist/commands/make/basic-migration.js +60 -0
- package/dist/commands/make/basic-migration.js.map +1 -0
- package/dist/commands/make/basic-model.d.ts +2 -0
- package/dist/commands/make/basic-model.js +25 -0
- package/dist/commands/make/basic-model.js.map +1 -0
- package/dist/commands/make/basic-seeder.d.ts +3 -0
- package/dist/commands/make/basic-seeder.js +32 -0
- package/dist/commands/make/basic-seeder.js.map +1 -0
- package/dist/commands/make/blueprint.d.ts +2 -0
- package/dist/commands/make/blueprint.js +29 -0
- package/dist/commands/make/blueprint.js.map +1 -0
- package/dist/commands/make/da-migration.d.ts +5 -0
- package/dist/commands/make/da-migration.js +60 -0
- package/dist/commands/make/da-migration.js.map +1 -0
- package/dist/commands/make/light-controller.d.ts +3 -0
- package/dist/commands/make/light-controller.js +54 -0
- package/dist/commands/make/light-controller.js.map +1 -0
- package/dist/commands/make/light-model.d.ts +3 -0
- package/dist/commands/make/light-model.js +50 -0
- package/dist/commands/make/light-model.js.map +1 -0
- package/dist/commands/make/mail.d.ts +2 -0
- package/dist/commands/make/mail.js +41 -0
- package/dist/commands/make/mail.js.map +1 -0
- package/dist/commands/make/notification.d.ts +2 -0
- package/dist/commands/make/notification.js +33 -0
- package/dist/commands/make/notification.js.map +1 -0
- package/dist/commands/make/queue.d.ts +2 -0
- package/dist/commands/make/queue.js +35 -0
- package/dist/commands/make/queue.js.map +1 -0
- package/dist/commands/runner/barrels.d.ts +3 -0
- package/dist/commands/runner/barrels.js +78 -0
- package/dist/commands/runner/barrels.js.map +1 -0
- package/dist/commands/runner/blueprint/controller-generation.d.ts +1 -0
- package/dist/commands/runner/blueprint/controller-generation.js +147 -0
- package/dist/commands/runner/blueprint/controller-generation.js.map +1 -0
- package/dist/commands/runner/blueprint/documentation-generation.d.ts +6 -0
- package/dist/commands/runner/blueprint/documentation-generation.js +337 -0
- package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -0
- package/dist/commands/runner/blueprint/migration-generation.d.ts +1 -0
- package/dist/commands/runner/blueprint/migration-generation.js +120 -0
- package/dist/commands/runner/blueprint/migration-generation.js.map +1 -0
- package/dist/commands/runner/blueprint/model-generation.d.ts +1 -0
- package/dist/commands/runner/blueprint/model-generation.js +122 -0
- package/dist/commands/runner/blueprint/model-generation.js.map +1 -0
- package/dist/commands/runner/blueprint/runner.d.ts +23 -0
- package/dist/commands/runner/blueprint/runner.js +139 -0
- package/dist/commands/runner/blueprint/runner.js.map +1 -0
- package/dist/commands/runner/blueprint/seeder-generation.d.ts +1 -0
- package/dist/commands/runner/blueprint/seeder-generation.js +40 -0
- package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -0
- package/dist/commands/runner/da-migration.d.ts +39 -0
- package/dist/commands/runner/da-migration.js +262 -0
- package/dist/commands/runner/da-migration.js.map +1 -0
- package/dist/commands/runner/migration.d.ts +11 -0
- package/dist/commands/runner/migration.js +188 -0
- package/dist/commands/runner/migration.js.map +1 -0
- package/dist/commands/runner/seeder.d.ts +3 -0
- package/dist/commands/runner/seeder.js +40 -0
- package/dist/commands/runner/seeder.js.map +1 -0
- package/dist/commands/stubs/index.d.ts +14 -0
- package/dist/commands/stubs/index.js +277 -0
- package/dist/commands/stubs/index.js.map +1 -0
- package/dist/context/context.d.ts +7 -0
- package/dist/context/context.js +11 -0
- package/dist/context/context.js.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context.util.d.ts +7 -0
- package/dist/context.util.js +11 -0
- package/dist/context.util.js.map +1 -0
- package/dist/controller/controller.d.ts +118 -0
- package/dist/controller/controller.js +147 -0
- package/dist/controller/controller.js.map +1 -0
- package/dist/controller/index.d.ts +1 -0
- package/dist/controller/index.js +2 -0
- package/dist/controller/index.js.map +1 -0
- package/dist/controller.util.d.ts +118 -0
- package/dist/controller.util.js +144 -0
- package/dist/controller.util.js.map +1 -0
- package/dist/conversion/conversion.d.ts +8 -0
- package/dist/conversion/conversion.js +52 -0
- package/dist/conversion/conversion.js.map +1 -0
- package/dist/conversion/index.d.ts +1 -0
- package/dist/conversion/index.js +2 -0
- package/dist/conversion/index.js.map +1 -0
- package/dist/conversion.util.d.ts +8 -0
- package/dist/conversion.util.js +52 -0
- package/dist/conversion.util.js.map +1 -0
- package/dist/db/db.d.ts +84 -0
- package/dist/db/db.js +177 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db.util.d.ts +84 -0
- package/dist/db.util.js +177 -0
- package/dist/db.util.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.js +2 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +30 -0
- package/dist/logger/logger.js +126 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger.util.d.ts +30 -0
- package/dist/logger.util.js +126 -0
- package/dist/logger.util.js.map +1 -0
- package/dist/mail/index.d.ts +1 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/mail.d.ts +21 -0
- package/dist/mail/mail.js +53 -0
- package/dist/mail/mail.js.map +1 -0
- package/dist/mail.util.d.ts +21 -0
- package/dist/mail.util.js +53 -0
- package/dist/mail.util.js.map +1 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/middleware.d.ts +263 -0
- package/dist/middleware/middleware.js +233 -0
- package/dist/middleware/middleware.js.map +1 -0
- package/dist/middleware.util.d.ts +263 -0
- package/dist/middleware.util.js +233 -0
- package/dist/middleware.util.js.map +1 -0
- package/dist/model/index.d.ts +3 -0
- package/dist/model/index.js +4 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/model.d.ts +204 -0
- package/dist/model/model.js +1495 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model.util.d.ts +204 -0
- package/dist/model.util.js +1495 -0
- package/dist/model.util.js.map +1 -0
- package/dist/permission/index.d.ts +1 -0
- package/dist/permission/index.js +2 -0
- package/dist/permission/index.js.map +1 -0
- package/dist/permission/permission.d.ts +38 -0
- package/dist/permission/permission.js +91 -0
- package/dist/permission/permission.js.map +1 -0
- package/dist/permission.util.d.ts +38 -0
- package/dist/permission.util.js +91 -0
- package/dist/permission.util.js.map +1 -0
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry/index.js +2 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/registry.d.ts +28 -0
- package/dist/registry/registry.js +19 -0
- package/dist/registry/registry.js.map +1 -0
- package/dist/registry.util.d.ts +28 -0
- package/dist/registry.util.js +19 -0
- package/dist/registry.util.js.map +1 -0
- package/dist/route/index.d.ts +1 -0
- package/dist/route/index.js +2 -0
- package/dist/route/index.js.map +1 -0
- package/dist/route/route.d.ts +1 -0
- package/dist/route/route.js +12 -0
- package/dist/route/route.js.map +1 -0
- package/dist/route.util.d.ts +1 -0
- package/dist/route.util.js +12 -0
- package/dist/route.util.js.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +56 -0
- package/dist/storage/storage.js +86 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage.util.d.ts +56 -0
- package/dist/storage.util.js +82 -0
- package/dist/storage.util.js.map +1 -0
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/validation.d.ts +7 -0
- package/dist/validation/validation.js +245 -0
- package/dist/validation/validation.js.map +1 -0
- package/dist/validation.util.d.ts +7 -0
- package/dist/validation.util.js +237 -0
- package/dist/validation.util.js.map +1 -0
- package/package.json +34 -0
- package/src/auth/auth.ts +282 -0
- package/src/auth/index.ts +1 -0
- package/src/commands/cli.ts +89 -0
- package/src/commands/make/basic-controller.ts +49 -0
- package/src/commands/make/basic-migration.ts +89 -0
- package/src/commands/make/basic-model.ts +32 -0
- package/src/commands/make/basic-seeder.ts +38 -0
- package/src/commands/make/blueprint.ts +36 -0
- package/src/commands/make/da-migration.ts +90 -0
- package/src/commands/make/light-controller.ts +67 -0
- package/src/commands/make/light-model.ts +61 -0
- package/src/commands/make/mail.ts +51 -0
- package/src/commands/make/notification.ts +43 -0
- package/src/commands/make/queue.ts +45 -0
- package/src/commands/runner/barrels.ts +85 -0
- package/src/commands/runner/blueprint/controller-generation.ts +194 -0
- package/src/commands/runner/blueprint/documentation-generation.ts +463 -0
- package/src/commands/runner/blueprint/migration-generation.ts +153 -0
- package/src/commands/runner/blueprint/model-generation.ts +149 -0
- package/src/commands/runner/blueprint/runner.ts +181 -0
- package/src/commands/runner/blueprint/seeder-generation.ts +55 -0
- package/src/commands/runner/da-migration.ts +333 -0
- package/src/commands/runner/migration.ts +245 -0
- package/src/commands/runner/seeder.ts +44 -0
- package/src/commands/stubs/index.ts +289 -0
- package/src/context/context.ts +17 -0
- package/src/context/index.ts +1 -0
- package/src/controller/controller.ts +240 -0
- package/src/controller/index.ts +1 -0
- package/src/conversion/conversion.ts +65 -0
- package/src/conversion/index.ts +1 -0
- package/src/index.ts +22 -0
- package/src/logger/index.ts +1 -0
- package/src/logger/logger.ts +177 -0
- package/src/mail/index.ts +1 -0
- package/src/mail/mail.ts +86 -0
- package/src/middleware/index.ts +1 -0
- package/src/middleware/middleware.ts +289 -0
- package/src/permission/index.ts +1 -0
- package/src/permission/permission.ts +136 -0
- package/src/registry/index.ts +1 -0
- package/src/registry/registry.ts +37 -0
- package/src/route/index.ts +1 -0
- package/src/route/route.ts +12 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/storage.ts +107 -0
- package/src/validation/index.ts +1 -0
- package/src/validation/validation.ts +346 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { logger } from "@utils";
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import * as utils from "@utils";
|
|
7
|
+
|
|
8
|
+
const da = (utils as any).da;
|
|
9
|
+
const daClient = (utils as any).daClient;
|
|
10
|
+
|
|
11
|
+
const DW_MIGRATIONS_DIR = path.resolve("./src/database/da.migrations");
|
|
12
|
+
|
|
13
|
+
const MIGRATION_TABLE = `${process.env.DA_DATABASE || "default"}.migrations`;
|
|
14
|
+
|
|
15
|
+
// ================================
|
|
16
|
+
// ## Create Migration Table
|
|
17
|
+
// ================================
|
|
18
|
+
async function ensureMigrationTable() {
|
|
19
|
+
if (!da) {
|
|
20
|
+
logger.error("ClickHouse connection (da) is not available. Ensure @skalfa/da is installed.");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
await da.exec(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS ${MIGRATION_TABLE} (
|
|
25
|
+
name String,
|
|
26
|
+
executed_at DateTime DEFAULT now()
|
|
27
|
+
)
|
|
28
|
+
ENGINE = MergeTree()
|
|
29
|
+
ORDER BY (name)
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ================================
|
|
34
|
+
// ## Ensure OLAP Database Exists
|
|
35
|
+
// ================================
|
|
36
|
+
async function ensureDatabaseExists() {
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
const { createClient } = await import("@clickhouse/client");
|
|
39
|
+
let dac = createClient({
|
|
40
|
+
url: "http://" + (process.env.DA_HOST || '127.0.0.1') + ':' + (process.env.DA_PORT || '8123'),
|
|
41
|
+
username: process.env.DA_USERNAME || 'default',
|
|
42
|
+
password: process.env.DA_PASSWORD || '',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
logger.info(`Database ${process.env.DA_DATABASE || "default"} not found. Create new database...`);
|
|
46
|
+
await dac.query({ query: `CREATE DATABASE IF NOT EXISTS ${process.env.DA_DATABASE || "default"}` });
|
|
47
|
+
logger.info(`Database ${process.env.DA_DATABASE || "default"} successfully created.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ================================
|
|
51
|
+
// ## GET LIST OF APPLIED MIGRATIONS
|
|
52
|
+
// ================================
|
|
53
|
+
async function getMigratedNames(): Promise<string[]> {
|
|
54
|
+
try {
|
|
55
|
+
const rows = (await da.from("migrations").select("name").get()) as any[];
|
|
56
|
+
return rows.map((r: any) => r.name);
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ================================
|
|
63
|
+
// ## SAVE MIGRATION RECORD
|
|
64
|
+
// ================================
|
|
65
|
+
async function recordMigration(name: string) {
|
|
66
|
+
await daClient.insert({ table: "migrations", values: [{ name }], format: "JSONEachRow" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ================================
|
|
70
|
+
// ## RUN ALL MIGRATIONS
|
|
71
|
+
// ================================
|
|
72
|
+
export const daMigrateCommand = new Command("da:migrate")
|
|
73
|
+
.description("Run all OLAP (ClickHouse) migrations")
|
|
74
|
+
.action(async () => {
|
|
75
|
+
logger.info("Preparing run migration...");
|
|
76
|
+
|
|
77
|
+
await ensureDatabaseExists();
|
|
78
|
+
await ensureMigrationTable();
|
|
79
|
+
|
|
80
|
+
const applied = await getMigratedNames();
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(DW_MIGRATIONS_DIR)) {
|
|
83
|
+
logger.info("No OLAP migrations directory found.");
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
const files = getMigrationFiles(DW_MIGRATIONS_DIR).sort((a, b) => a.localeCompare(b));
|
|
87
|
+
|
|
88
|
+
let count = 0;
|
|
89
|
+
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
if (applied.includes(file)) continue;
|
|
92
|
+
const relative = path.relative(DW_MIGRATIONS_DIR, file);
|
|
93
|
+
|
|
94
|
+
if (applied.includes(relative)) continue;
|
|
95
|
+
|
|
96
|
+
const filePath = file;
|
|
97
|
+
const migrationPath = path.relative(DW_MIGRATIONS_DIR, filePath);
|
|
98
|
+
|
|
99
|
+
if (applied.includes(migrationPath)) continue;
|
|
100
|
+
|
|
101
|
+
const mod = await import(filePath);
|
|
102
|
+
|
|
103
|
+
if (!mod.default) continue;
|
|
104
|
+
|
|
105
|
+
const migration = new mod.default();
|
|
106
|
+
|
|
107
|
+
if (typeof migration.up !== "function") {
|
|
108
|
+
logger.error(`Migration file ${file} missing up()`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await migration.up();
|
|
113
|
+
await recordMigration(migrationPath);
|
|
114
|
+
|
|
115
|
+
logger.info(`Migrated: ${migrationPath}`);
|
|
116
|
+
count++;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (count === 0) logger.info("Nothing to migrate.");
|
|
120
|
+
else logger.info(`Success run all migration!`);
|
|
121
|
+
|
|
122
|
+
process.exit(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ================================
|
|
126
|
+
// ## FRESH MIGRATE (DROP ALL)
|
|
127
|
+
// ================================
|
|
128
|
+
export const daMigrateFreshCommand = new Command("da:migrate:fresh")
|
|
129
|
+
.description("DROP ALL OLAP TABLES and rerun migrations")
|
|
130
|
+
.action(async () => {
|
|
131
|
+
logger.info("Preparing run migrations...");
|
|
132
|
+
|
|
133
|
+
if (!da) {
|
|
134
|
+
logger.error("ClickHouse connection (da) is not available.");
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const tables = (await da.select("name").from("system.tables").where("database", "=", process.env.DA_DATABASE || "elysia_light").get()) as any[];
|
|
139
|
+
|
|
140
|
+
for (const t of tables) {
|
|
141
|
+
await da.exec(`DROP TABLE IF EXISTS ${t.name}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await ensureMigrationTable();
|
|
145
|
+
logger.info("Database has been freshed......");
|
|
146
|
+
|
|
147
|
+
if (!fs.existsSync(DW_MIGRATIONS_DIR)) {
|
|
148
|
+
logger.info("No OLAP migrations directory found.");
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
const files = getMigrationFiles(DW_MIGRATIONS_DIR).sort((a, b) => a.localeCompare(b));
|
|
152
|
+
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
const filePath = file;
|
|
155
|
+
const migrationPath = path.relative(DW_MIGRATIONS_DIR, filePath);
|
|
156
|
+
|
|
157
|
+
const mod = await import(filePath);
|
|
158
|
+
|
|
159
|
+
if (!mod.default) continue;
|
|
160
|
+
|
|
161
|
+
const migration = new mod.default();
|
|
162
|
+
|
|
163
|
+
await migration.up();
|
|
164
|
+
await recordMigration(migrationPath);
|
|
165
|
+
logger.info(`Migrated: ${migrationPath}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
logger.info(`Success run all migration!`);
|
|
169
|
+
process.exit(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
function getMigrationFiles(dir: string): string[] {
|
|
173
|
+
if (!fs.existsSync(dir)) return [];
|
|
174
|
+
|
|
175
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
|
|
177
|
+
let files: string[] = [];
|
|
178
|
+
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const fullPath = path.join(dir, entry.name);
|
|
181
|
+
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
files = files.concat(getMigrationFiles(fullPath));
|
|
184
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
185
|
+
files.push(fullPath);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return files;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ==============================>
|
|
193
|
+
// ## DA / OLAP : Migration
|
|
194
|
+
// ==============================>
|
|
195
|
+
export abstract class DAMigration {
|
|
196
|
+
raw(query: string) {
|
|
197
|
+
return this.exec(query);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected async exec(query: string) {
|
|
201
|
+
if (!daClient) {
|
|
202
|
+
logger.error("daClient is not available.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
await daClient.query({ query });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
createTable(
|
|
210
|
+
name: string,
|
|
211
|
+
callback: (table: TableBuilder) => void,
|
|
212
|
+
options?: Partial<TableOptions>
|
|
213
|
+
) {
|
|
214
|
+
const builder = new TableBuilder(name);
|
|
215
|
+
callback(builder);
|
|
216
|
+
const sql = builder.build(options);
|
|
217
|
+
return this.exec(sql);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
dropTable(name: string) {
|
|
221
|
+
return this.exec(`DROP TABLE IF EXISTS ${name}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
alterTable(
|
|
225
|
+
name: string,
|
|
226
|
+
callback: (alter: AlterBuilder) => void
|
|
227
|
+
) {
|
|
228
|
+
const builder = new AlterBuilder(name);
|
|
229
|
+
callback(builder);
|
|
230
|
+
const sql = builder.build();
|
|
231
|
+
return this.exec(sql);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ====================================>
|
|
236
|
+
// ## DA / OLAP : Table Builder (CREATE TABLE)
|
|
237
|
+
// ====================================>
|
|
238
|
+
export interface TableOptions {
|
|
239
|
+
engine: string;
|
|
240
|
+
orderBy: string[];
|
|
241
|
+
partitionBy?: string;
|
|
242
|
+
ttl?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export class TableBuilder {
|
|
246
|
+
private table: string;
|
|
247
|
+
private columns: string[] = [];
|
|
248
|
+
|
|
249
|
+
constructor(table: string) {
|
|
250
|
+
this.table = table;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
uuid(name: string = "id") {
|
|
254
|
+
this.columns.push(`${name} UUID DEFAULT generateUUIDv7()`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
string(name: string) {
|
|
258
|
+
this.columns.push(`${name} String`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
uint64(name: string) {
|
|
262
|
+
this.columns.push(`${name} UInt64`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
int32(name: string) {
|
|
266
|
+
this.columns.push(`${name} Int32`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
json(name: string) {
|
|
270
|
+
this.columns.push(`${name} JSON`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
dateTime(name: string) {
|
|
274
|
+
this.columns.push(`${name} DateTime`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
build(options?: Partial<TableOptions>) {
|
|
278
|
+
const engine = options?.engine || "MergeTree";
|
|
279
|
+
const orderBy = options?.orderBy?.join(", ") || "id";
|
|
280
|
+
const partition = options?.partitionBy ? `PARTITION BY ${options.partitionBy}` : "";
|
|
281
|
+
const ttl = options?.ttl ? `TTL ${options.ttl}` : "";
|
|
282
|
+
|
|
283
|
+
return `
|
|
284
|
+
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
285
|
+
${this.columns.join(",\n ")}
|
|
286
|
+
)
|
|
287
|
+
ENGINE = ${engine}
|
|
288
|
+
${partition}
|
|
289
|
+
ORDER BY (${orderBy})
|
|
290
|
+
${ttl}
|
|
291
|
+
`.trim();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ====================================>
|
|
296
|
+
// ## DA / OLAP : Alter Builder (ALTER TABLE)
|
|
297
|
+
// ====================================>
|
|
298
|
+
export class AlterBuilder {
|
|
299
|
+
private table: string;
|
|
300
|
+
private actions: string[] = [];
|
|
301
|
+
|
|
302
|
+
constructor(table: string) {
|
|
303
|
+
this.table = table;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addColumn(name: string, type: string, defaultValue?: any) {
|
|
307
|
+
const def = defaultValue !== undefined ? ` DEFAULT ${this.format(defaultValue)}` : "";
|
|
308
|
+
this.actions.push(`ADD COLUMN IF NOT EXISTS ${name} ${type}${def}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
dropColumn(name: string) {
|
|
312
|
+
this.actions.push(`DROP COLUMN IF EXISTS ${name}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
modifyColumn(name: string, newType: string) {
|
|
316
|
+
this.actions.push(`MODIFY COLUMN ${name} ${newType}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
commentColumn(name: string, comment: string) {
|
|
320
|
+
this.actions.push(`COMMENT COLUMN ${name} '${comment.replace(/'/g, "''")}'`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private format(value: any) {
|
|
324
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
325
|
+
if (value === null) return "NULL";
|
|
326
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
build() {
|
|
331
|
+
return `ALTER TABLE ${this.table} \n${this.actions.join("\n")} `;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import knex, { Knex } from "knex";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { conversion, logger } from "@utils";
|
|
6
|
+
import { runSeeder } from "./seeder";
|
|
7
|
+
|
|
8
|
+
declare module "knex" {
|
|
9
|
+
namespace Knex {
|
|
10
|
+
interface CreateTableBuilder {
|
|
11
|
+
foreignIdFor(tableName: string, column?: string): Knex.ColumnBuilder;
|
|
12
|
+
softDelete(column?: string): Knex.ColumnBuilder;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TableBuilder = require("knex/lib/schema/tablebuilder");
|
|
18
|
+
|
|
19
|
+
TableBuilder.prototype.foreignIdFor = function (
|
|
20
|
+
this: Knex.CreateTableBuilder,
|
|
21
|
+
tableName: string,
|
|
22
|
+
column = `${conversion.strSingular(tableName)}_id`
|
|
23
|
+
) {
|
|
24
|
+
return this.bigInteger(column).unsigned().index();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
TableBuilder.prototype.softDelete = function (
|
|
28
|
+
this: Knex.CreateTableBuilder,
|
|
29
|
+
column = `deleted_at`
|
|
30
|
+
) {
|
|
31
|
+
return this.timestamp(column).index();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// =====================================>
|
|
35
|
+
// ## Command: migrate
|
|
36
|
+
// =====================================>
|
|
37
|
+
export const migrateCommand = new Command("migrate")
|
|
38
|
+
.description("Run all migration")
|
|
39
|
+
.option("--seed", "Run seeder after migrate")
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
await ensureDatabaseExists(process.env.DB_DATABASE || "db_elysia_light");
|
|
42
|
+
|
|
43
|
+
const { db } = await import("@skalfa/skalfa-orm");
|
|
44
|
+
|
|
45
|
+
if (!db) {
|
|
46
|
+
logger.error("Database connection (db) is not available. Ensure @skalfa/skalfa-orm is installed.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hasTable = await db.schema.hasTable("migrations");
|
|
51
|
+
if (!hasTable) {
|
|
52
|
+
await db.schema.createTable("migrations", (table: any) => {
|
|
53
|
+
table.increments("id").primary();
|
|
54
|
+
table.string("name").notNullable();
|
|
55
|
+
table.timestamp("batch").defaultTo(db.raw("CURRENT_TIMESTAMP"));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await runMigrationFile();
|
|
60
|
+
|
|
61
|
+
if (options.seed) {
|
|
62
|
+
await runSeeder();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.exit(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// =====================================>
|
|
69
|
+
// ## Command: migrate:fresh
|
|
70
|
+
// =====================================>
|
|
71
|
+
export const migrateFreshCommand = new Command("migrate:fresh")
|
|
72
|
+
.description("Fresh and run all migration")
|
|
73
|
+
.option("--seed", "Run seeder after migrate")
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
await ensureDatabaseExists(process.env.DB_DATABASE || "db_elysia_light");
|
|
76
|
+
|
|
77
|
+
const { db } = await import("@skalfa/skalfa-orm");
|
|
78
|
+
|
|
79
|
+
if (!db) {
|
|
80
|
+
logger.error("Database connection (db) is not available. Ensure @skalfa/skalfa-orm is installed.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await db.raw(`DROP SCHEMA public CASCADE;`);
|
|
85
|
+
await db.raw(`CREATE SCHEMA public;`);
|
|
86
|
+
|
|
87
|
+
logger.info("Database schema has been freshed...");
|
|
88
|
+
|
|
89
|
+
await db.schema.createTable("migrations", (table: any) => {
|
|
90
|
+
table.increments("id").primary();
|
|
91
|
+
table.string("name").notNullable();
|
|
92
|
+
table.timestamp("batch").defaultTo(db.raw("CURRENT_TIMESTAMP"));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await runMigrationFile();
|
|
96
|
+
|
|
97
|
+
if (options.seed) {
|
|
98
|
+
await runSeeder();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
process.exit(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// =====================================>
|
|
105
|
+
// ## Command: migration helpers
|
|
106
|
+
// =====================================>
|
|
107
|
+
async function runMigrationFile() {
|
|
108
|
+
const { db } = await import("@skalfa/skalfa-orm");
|
|
109
|
+
|
|
110
|
+
if (!db) {
|
|
111
|
+
logger.error("Database connection (db) is not available.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const migrations = await db.table("migrations").select("name");
|
|
116
|
+
const migrated = migrations.map((row: any) => row.name);
|
|
117
|
+
|
|
118
|
+
const migrationsDir = fs.existsSync(path.resolve("./database/migrations"))
|
|
119
|
+
? path.resolve("./database/migrations")
|
|
120
|
+
: path.resolve("./src/database/migrations");
|
|
121
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
122
|
+
logger.info("No migrations directory found.");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const files = getMigrationFiles(migrationsDir).sort((a, b) => a.localeCompare(b));
|
|
126
|
+
|
|
127
|
+
let countMigrated = 0;
|
|
128
|
+
|
|
129
|
+
logger.info("Running migrations...");
|
|
130
|
+
|
|
131
|
+
for (const file of files) {
|
|
132
|
+
const migrationFile = path.relative(migrationsDir, file);
|
|
133
|
+
|
|
134
|
+
if (migrated.includes(migrationFile)) continue;
|
|
135
|
+
|
|
136
|
+
const mod = await import(file);
|
|
137
|
+
|
|
138
|
+
if (mod.up) {
|
|
139
|
+
await mod.up(db);
|
|
140
|
+
await db.table("migrations").insert({ name: migrationFile });
|
|
141
|
+
logger.info(`Migrated: ${migrationFile}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
countMigrated++;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (countMigrated > 0) {
|
|
148
|
+
logger.info(`Success run all migration!`);
|
|
149
|
+
} else {
|
|
150
|
+
logger.info(`Nothing to migrate!`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getMigrationFiles(dir: string, baseDir = dir): string[] {
|
|
155
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
156
|
+
|
|
157
|
+
let files: string[] = [];
|
|
158
|
+
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
const fullPath = path.join(dir, entry.name);
|
|
161
|
+
|
|
162
|
+
if (entry.isDirectory()) {
|
|
163
|
+
files = files.concat(getMigrationFiles(fullPath));
|
|
164
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
165
|
+
files.push(fullPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return files;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function ensureDatabaseExists(databaseName: string) {
|
|
173
|
+
const driver = (process.env.DB_CONNECTION || "pg").toLowerCase();
|
|
174
|
+
|
|
175
|
+
switch (driver) {
|
|
176
|
+
case "pg":
|
|
177
|
+
case "pgsql": {
|
|
178
|
+
const tempDb = knex({
|
|
179
|
+
client: "pg",
|
|
180
|
+
connection: {
|
|
181
|
+
host: process.env.DB_HOST || "127.0.0.1",
|
|
182
|
+
port: Number(process.env.DB_PORT) || 5432,
|
|
183
|
+
user: process.env.DB_USERNAME || "postgres",
|
|
184
|
+
password: process.env.DB_PASSWORD || "password",
|
|
185
|
+
database: "postgres",
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const result = await tempDb
|
|
191
|
+
.select("datname")
|
|
192
|
+
.from("pg_database")
|
|
193
|
+
.where("datname", databaseName)
|
|
194
|
+
.first();
|
|
195
|
+
|
|
196
|
+
if (!result) {
|
|
197
|
+
logger.info(`Database ${databaseName} not found. Create new database...`);
|
|
198
|
+
await tempDb.raw(`CREATE DATABASE "${databaseName}"`);
|
|
199
|
+
logger.info(`Database ${databaseName} successfully created.`);
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
logger.error(`Check or create database error: ${err}`);
|
|
203
|
+
} finally {
|
|
204
|
+
await tempDb.destroy();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
case "mysql":
|
|
211
|
+
case "mysql2": {
|
|
212
|
+
const tempDb = knex({
|
|
213
|
+
client: "mysql2",
|
|
214
|
+
connection: {
|
|
215
|
+
host: process.env.DB_HOST || "127.0.0.1",
|
|
216
|
+
port: Number(process.env.DB_PORT) || 3306,
|
|
217
|
+
user: process.env.DB_USERNAME || "root",
|
|
218
|
+
password: process.env.DB_PASSWORD || "",
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const [rows]: any = await tempDb.raw(
|
|
224
|
+
`SHOW DATABASES LIKE ?`,
|
|
225
|
+
[databaseName]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (!rows || rows.length === 0) {
|
|
229
|
+
logger.info(`Database ${databaseName} not found. Create new database...`);
|
|
230
|
+
await tempDb.raw(`CREATE DATABASE \`${databaseName}\``);
|
|
231
|
+
logger.info(`Database ${databaseName} successfully created.`);
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.error(`Check or create database error: ${err}`);
|
|
235
|
+
} finally {
|
|
236
|
+
await tempDb.destroy();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
throw new Error(`Driver ${driver} belum didukung oleh ensureDatabaseExists().`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { logger } from "@utils";
|
|
5
|
+
|
|
6
|
+
// =====================================>
|
|
7
|
+
// ## Command: seeder
|
|
8
|
+
// =====================================>
|
|
9
|
+
export const seederCommand = new Command("seeder")
|
|
10
|
+
.description("Run all database seeders")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
try {
|
|
13
|
+
await runSeeder();
|
|
14
|
+
process.exit(0);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
logger.error(`Error running seeds: ${error}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const runSeeder = async () => {
|
|
22
|
+
const seedersDir = fs.existsSync(path.join(process.cwd(), "database", "seeders"))
|
|
23
|
+
? path.join(process.cwd(), "database", "seeders")
|
|
24
|
+
: path.join(process.cwd(), "src", "database", "seeders");
|
|
25
|
+
if (!fs.existsSync(seedersDir)) {
|
|
26
|
+
logger.info("No seeders directory found.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const files = fs.readdirSync(seedersDir).filter(f => f.endsWith(".ts"));
|
|
30
|
+
|
|
31
|
+
logger.info("Running seeders...");
|
|
32
|
+
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const seederPath = path.join(seedersDir, file);
|
|
35
|
+
const { default: seeder } = await import(seederPath);
|
|
36
|
+
|
|
37
|
+
if (typeof seeder === "function") {
|
|
38
|
+
await seeder();
|
|
39
|
+
logger.info(`Planted: ${file}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.info("Success run all seeders!");
|
|
44
|
+
};
|