@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.
Files changed (249) hide show
  1. package/.github/workflows/publish.yml +40 -0
  2. package/dist/auth/auth.d.ts +19 -0
  3. package/dist/auth/auth.js +227 -0
  4. package/dist/auth/auth.js.map +1 -0
  5. package/dist/auth/index.d.ts +1 -0
  6. package/dist/auth/index.js +2 -0
  7. package/dist/auth/index.js.map +1 -0
  8. package/dist/auth.util.d.ts +19 -0
  9. package/dist/auth.util.js +183 -0
  10. package/dist/auth.util.js.map +1 -0
  11. package/dist/commands/cli.d.ts +1 -0
  12. package/dist/commands/cli.js +78 -0
  13. package/dist/commands/cli.js.map +1 -0
  14. package/dist/commands/make/basic-controller.d.ts +2 -0
  15. package/dist/commands/make/basic-controller.js +40 -0
  16. package/dist/commands/make/basic-controller.js.map +1 -0
  17. package/dist/commands/make/basic-migration.d.ts +5 -0
  18. package/dist/commands/make/basic-migration.js +60 -0
  19. package/dist/commands/make/basic-migration.js.map +1 -0
  20. package/dist/commands/make/basic-model.d.ts +2 -0
  21. package/dist/commands/make/basic-model.js +25 -0
  22. package/dist/commands/make/basic-model.js.map +1 -0
  23. package/dist/commands/make/basic-seeder.d.ts +3 -0
  24. package/dist/commands/make/basic-seeder.js +32 -0
  25. package/dist/commands/make/basic-seeder.js.map +1 -0
  26. package/dist/commands/make/blueprint.d.ts +2 -0
  27. package/dist/commands/make/blueprint.js +29 -0
  28. package/dist/commands/make/blueprint.js.map +1 -0
  29. package/dist/commands/make/da-migration.d.ts +5 -0
  30. package/dist/commands/make/da-migration.js +60 -0
  31. package/dist/commands/make/da-migration.js.map +1 -0
  32. package/dist/commands/make/light-controller.d.ts +3 -0
  33. package/dist/commands/make/light-controller.js +54 -0
  34. package/dist/commands/make/light-controller.js.map +1 -0
  35. package/dist/commands/make/light-model.d.ts +3 -0
  36. package/dist/commands/make/light-model.js +50 -0
  37. package/dist/commands/make/light-model.js.map +1 -0
  38. package/dist/commands/make/mail.d.ts +2 -0
  39. package/dist/commands/make/mail.js +41 -0
  40. package/dist/commands/make/mail.js.map +1 -0
  41. package/dist/commands/make/notification.d.ts +2 -0
  42. package/dist/commands/make/notification.js +33 -0
  43. package/dist/commands/make/notification.js.map +1 -0
  44. package/dist/commands/make/queue.d.ts +2 -0
  45. package/dist/commands/make/queue.js +35 -0
  46. package/dist/commands/make/queue.js.map +1 -0
  47. package/dist/commands/runner/barrels.d.ts +3 -0
  48. package/dist/commands/runner/barrels.js +78 -0
  49. package/dist/commands/runner/barrels.js.map +1 -0
  50. package/dist/commands/runner/blueprint/controller-generation.d.ts +1 -0
  51. package/dist/commands/runner/blueprint/controller-generation.js +147 -0
  52. package/dist/commands/runner/blueprint/controller-generation.js.map +1 -0
  53. package/dist/commands/runner/blueprint/documentation-generation.d.ts +6 -0
  54. package/dist/commands/runner/blueprint/documentation-generation.js +337 -0
  55. package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -0
  56. package/dist/commands/runner/blueprint/migration-generation.d.ts +1 -0
  57. package/dist/commands/runner/blueprint/migration-generation.js +120 -0
  58. package/dist/commands/runner/blueprint/migration-generation.js.map +1 -0
  59. package/dist/commands/runner/blueprint/model-generation.d.ts +1 -0
  60. package/dist/commands/runner/blueprint/model-generation.js +122 -0
  61. package/dist/commands/runner/blueprint/model-generation.js.map +1 -0
  62. package/dist/commands/runner/blueprint/runner.d.ts +23 -0
  63. package/dist/commands/runner/blueprint/runner.js +139 -0
  64. package/dist/commands/runner/blueprint/runner.js.map +1 -0
  65. package/dist/commands/runner/blueprint/seeder-generation.d.ts +1 -0
  66. package/dist/commands/runner/blueprint/seeder-generation.js +40 -0
  67. package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -0
  68. package/dist/commands/runner/da-migration.d.ts +39 -0
  69. package/dist/commands/runner/da-migration.js +262 -0
  70. package/dist/commands/runner/da-migration.js.map +1 -0
  71. package/dist/commands/runner/migration.d.ts +11 -0
  72. package/dist/commands/runner/migration.js +188 -0
  73. package/dist/commands/runner/migration.js.map +1 -0
  74. package/dist/commands/runner/seeder.d.ts +3 -0
  75. package/dist/commands/runner/seeder.js +40 -0
  76. package/dist/commands/runner/seeder.js.map +1 -0
  77. package/dist/commands/stubs/index.d.ts +14 -0
  78. package/dist/commands/stubs/index.js +277 -0
  79. package/dist/commands/stubs/index.js.map +1 -0
  80. package/dist/context/context.d.ts +7 -0
  81. package/dist/context/context.js +11 -0
  82. package/dist/context/context.js.map +1 -0
  83. package/dist/context/index.d.ts +1 -0
  84. package/dist/context/index.js +2 -0
  85. package/dist/context/index.js.map +1 -0
  86. package/dist/context.util.d.ts +7 -0
  87. package/dist/context.util.js +11 -0
  88. package/dist/context.util.js.map +1 -0
  89. package/dist/controller/controller.d.ts +118 -0
  90. package/dist/controller/controller.js +147 -0
  91. package/dist/controller/controller.js.map +1 -0
  92. package/dist/controller/index.d.ts +1 -0
  93. package/dist/controller/index.js +2 -0
  94. package/dist/controller/index.js.map +1 -0
  95. package/dist/controller.util.d.ts +118 -0
  96. package/dist/controller.util.js +144 -0
  97. package/dist/controller.util.js.map +1 -0
  98. package/dist/conversion/conversion.d.ts +8 -0
  99. package/dist/conversion/conversion.js +52 -0
  100. package/dist/conversion/conversion.js.map +1 -0
  101. package/dist/conversion/index.d.ts +1 -0
  102. package/dist/conversion/index.js +2 -0
  103. package/dist/conversion/index.js.map +1 -0
  104. package/dist/conversion.util.d.ts +8 -0
  105. package/dist/conversion.util.js +52 -0
  106. package/dist/conversion.util.js.map +1 -0
  107. package/dist/db/db.d.ts +84 -0
  108. package/dist/db/db.js +177 -0
  109. package/dist/db/db.js.map +1 -0
  110. package/dist/db/index.d.ts +1 -0
  111. package/dist/db/index.js +2 -0
  112. package/dist/db/index.js.map +1 -0
  113. package/dist/db.util.d.ts +84 -0
  114. package/dist/db.util.js +177 -0
  115. package/dist/db.util.js.map +1 -0
  116. package/dist/index.d.ts +21 -0
  117. package/dist/index.js +14 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/logger/index.d.ts +1 -0
  120. package/dist/logger/index.js +2 -0
  121. package/dist/logger/index.js.map +1 -0
  122. package/dist/logger/logger.d.ts +30 -0
  123. package/dist/logger/logger.js +126 -0
  124. package/dist/logger/logger.js.map +1 -0
  125. package/dist/logger.util.d.ts +30 -0
  126. package/dist/logger.util.js +126 -0
  127. package/dist/logger.util.js.map +1 -0
  128. package/dist/mail/index.d.ts +1 -0
  129. package/dist/mail/index.js +2 -0
  130. package/dist/mail/index.js.map +1 -0
  131. package/dist/mail/mail.d.ts +21 -0
  132. package/dist/mail/mail.js +53 -0
  133. package/dist/mail/mail.js.map +1 -0
  134. package/dist/mail.util.d.ts +21 -0
  135. package/dist/mail.util.js +53 -0
  136. package/dist/mail.util.js.map +1 -0
  137. package/dist/middleware/index.d.ts +1 -0
  138. package/dist/middleware/index.js +2 -0
  139. package/dist/middleware/index.js.map +1 -0
  140. package/dist/middleware/middleware.d.ts +263 -0
  141. package/dist/middleware/middleware.js +233 -0
  142. package/dist/middleware/middleware.js.map +1 -0
  143. package/dist/middleware.util.d.ts +263 -0
  144. package/dist/middleware.util.js +233 -0
  145. package/dist/middleware.util.js.map +1 -0
  146. package/dist/model/index.d.ts +3 -0
  147. package/dist/model/index.js +4 -0
  148. package/dist/model/index.js.map +1 -0
  149. package/dist/model/model.d.ts +204 -0
  150. package/dist/model/model.js +1495 -0
  151. package/dist/model/model.js.map +1 -0
  152. package/dist/model.util.d.ts +204 -0
  153. package/dist/model.util.js +1495 -0
  154. package/dist/model.util.js.map +1 -0
  155. package/dist/permission/index.d.ts +1 -0
  156. package/dist/permission/index.js +2 -0
  157. package/dist/permission/index.js.map +1 -0
  158. package/dist/permission/permission.d.ts +38 -0
  159. package/dist/permission/permission.js +91 -0
  160. package/dist/permission/permission.js.map +1 -0
  161. package/dist/permission.util.d.ts +38 -0
  162. package/dist/permission.util.js +91 -0
  163. package/dist/permission.util.js.map +1 -0
  164. package/dist/registry/index.d.ts +1 -0
  165. package/dist/registry/index.js +2 -0
  166. package/dist/registry/index.js.map +1 -0
  167. package/dist/registry/registry.d.ts +28 -0
  168. package/dist/registry/registry.js +19 -0
  169. package/dist/registry/registry.js.map +1 -0
  170. package/dist/registry.util.d.ts +28 -0
  171. package/dist/registry.util.js +19 -0
  172. package/dist/registry.util.js.map +1 -0
  173. package/dist/route/index.d.ts +1 -0
  174. package/dist/route/index.js +2 -0
  175. package/dist/route/index.js.map +1 -0
  176. package/dist/route/route.d.ts +1 -0
  177. package/dist/route/route.js +12 -0
  178. package/dist/route/route.js.map +1 -0
  179. package/dist/route.util.d.ts +1 -0
  180. package/dist/route.util.js +12 -0
  181. package/dist/route.util.js.map +1 -0
  182. package/dist/storage/index.d.ts +1 -0
  183. package/dist/storage/index.js +2 -0
  184. package/dist/storage/index.js.map +1 -0
  185. package/dist/storage/storage.d.ts +56 -0
  186. package/dist/storage/storage.js +86 -0
  187. package/dist/storage/storage.js.map +1 -0
  188. package/dist/storage.util.d.ts +56 -0
  189. package/dist/storage.util.js +82 -0
  190. package/dist/storage.util.js.map +1 -0
  191. package/dist/validation/index.d.ts +1 -0
  192. package/dist/validation/index.js +2 -0
  193. package/dist/validation/index.js.map +1 -0
  194. package/dist/validation/validation.d.ts +7 -0
  195. package/dist/validation/validation.js +245 -0
  196. package/dist/validation/validation.js.map +1 -0
  197. package/dist/validation.util.d.ts +7 -0
  198. package/dist/validation.util.js +237 -0
  199. package/dist/validation.util.js.map +1 -0
  200. package/package.json +34 -0
  201. package/src/auth/auth.ts +282 -0
  202. package/src/auth/index.ts +1 -0
  203. package/src/commands/cli.ts +89 -0
  204. package/src/commands/make/basic-controller.ts +49 -0
  205. package/src/commands/make/basic-migration.ts +89 -0
  206. package/src/commands/make/basic-model.ts +32 -0
  207. package/src/commands/make/basic-seeder.ts +38 -0
  208. package/src/commands/make/blueprint.ts +36 -0
  209. package/src/commands/make/da-migration.ts +90 -0
  210. package/src/commands/make/light-controller.ts +67 -0
  211. package/src/commands/make/light-model.ts +61 -0
  212. package/src/commands/make/mail.ts +51 -0
  213. package/src/commands/make/notification.ts +43 -0
  214. package/src/commands/make/queue.ts +45 -0
  215. package/src/commands/runner/barrels.ts +85 -0
  216. package/src/commands/runner/blueprint/controller-generation.ts +194 -0
  217. package/src/commands/runner/blueprint/documentation-generation.ts +463 -0
  218. package/src/commands/runner/blueprint/migration-generation.ts +153 -0
  219. package/src/commands/runner/blueprint/model-generation.ts +149 -0
  220. package/src/commands/runner/blueprint/runner.ts +181 -0
  221. package/src/commands/runner/blueprint/seeder-generation.ts +55 -0
  222. package/src/commands/runner/da-migration.ts +333 -0
  223. package/src/commands/runner/migration.ts +245 -0
  224. package/src/commands/runner/seeder.ts +44 -0
  225. package/src/commands/stubs/index.ts +289 -0
  226. package/src/context/context.ts +17 -0
  227. package/src/context/index.ts +1 -0
  228. package/src/controller/controller.ts +240 -0
  229. package/src/controller/index.ts +1 -0
  230. package/src/conversion/conversion.ts +65 -0
  231. package/src/conversion/index.ts +1 -0
  232. package/src/index.ts +22 -0
  233. package/src/logger/index.ts +1 -0
  234. package/src/logger/logger.ts +177 -0
  235. package/src/mail/index.ts +1 -0
  236. package/src/mail/mail.ts +86 -0
  237. package/src/middleware/index.ts +1 -0
  238. package/src/middleware/middleware.ts +289 -0
  239. package/src/permission/index.ts +1 -0
  240. package/src/permission/permission.ts +136 -0
  241. package/src/registry/index.ts +1 -0
  242. package/src/registry/registry.ts +37 -0
  243. package/src/route/index.ts +1 -0
  244. package/src/route/route.ts +12 -0
  245. package/src/storage/index.ts +1 -0
  246. package/src/storage/storage.ts +107 -0
  247. package/src/validation/index.ts +1 -0
  248. package/src/validation/validation.ts +346 -0
  249. 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
+ };