@skalfa/skalfa-api-core 1.0.3 → 1.0.8

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 (284) hide show
  1. package/CONTRIBUTING.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -0
  4. package/dist/auth/auth.d.ts +18 -15
  5. package/dist/auth/auth.js +20 -203
  6. package/dist/auth/auth.js.map +1 -1
  7. package/dist/auth/create-access-token.d.ts +4 -0
  8. package/dist/auth/create-access-token.js +26 -0
  9. package/dist/auth/create-access-token.js.map +1 -0
  10. package/dist/auth/create-user-mail-token.d.ts +4 -0
  11. package/dist/auth/create-user-mail-token.js +19 -0
  12. package/dist/auth/create-user-mail-token.js.map +1 -0
  13. package/dist/auth/helpers/generate-agent-id.d.ts +1 -0
  14. package/dist/auth/helpers/generate-agent-id.js +7 -0
  15. package/dist/auth/helpers/generate-agent-id.js.map +1 -0
  16. package/dist/auth/helpers/get-request-ip.d.ts +1 -0
  17. package/dist/auth/helpers/get-request-ip.js +4 -0
  18. package/dist/auth/helpers/get-request-ip.js.map +1 -0
  19. package/dist/auth/helpers/get-user-permissions.d.ts +1 -0
  20. package/dist/auth/helpers/get-user-permissions.js +9 -0
  21. package/dist/auth/helpers/get-user-permissions.js.map +1 -0
  22. package/dist/auth/helpers/index.d.ts +3 -0
  23. package/dist/auth/helpers/index.js +4 -0
  24. package/dist/auth/helpers/index.js.map +1 -0
  25. package/dist/auth/list-user-sessions.d.ts +1 -0
  26. package/dist/auth/list-user-sessions.js +10 -0
  27. package/dist/auth/list-user-sessions.js.map +1 -0
  28. package/dist/auth/revalidate-user-permissions-by-role.d.ts +1 -0
  29. package/dist/auth/revalidate-user-permissions-by-role.js +12 -0
  30. package/dist/auth/revalidate-user-permissions-by-role.js.map +1 -0
  31. package/dist/auth/revalidate-user-permissions.d.ts +1 -0
  32. package/dist/auth/revalidate-user-permissions.js +21 -0
  33. package/dist/auth/revalidate-user-permissions.js.map +1 -0
  34. package/dist/auth/revoke-access-token.d.ts +1 -0
  35. package/dist/auth/revoke-access-token.js +5 -0
  36. package/dist/auth/revoke-access-token.js.map +1 -0
  37. package/dist/auth/verify-access-token.d.ts +1 -0
  38. package/dist/auth/verify-access-token.js +47 -0
  39. package/dist/auth/verify-access-token.js.map +1 -0
  40. package/dist/auth/verify-user-mail-token.d.ts +1 -0
  41. package/dist/auth/verify-user-mail-token.js +21 -0
  42. package/dist/auth/verify-user-mail-token.js.map +1 -0
  43. package/dist/commands/cli.js +18 -27
  44. package/dist/commands/cli.js.map +1 -1
  45. package/dist/commands/make/basic-controller.js +1 -1
  46. package/dist/commands/make/basic-controller.js.map +1 -1
  47. package/dist/commands/make/basic-migration.d.ts +1 -1
  48. package/dist/commands/make/basic-migration.js +2 -2
  49. package/dist/commands/make/basic-migration.js.map +1 -1
  50. package/dist/commands/make/basic-model.js +1 -1
  51. package/dist/commands/make/basic-model.js.map +1 -1
  52. package/dist/commands/make/basic-seeder.js +1 -1
  53. package/dist/commands/make/basic-seeder.js.map +1 -1
  54. package/dist/commands/make/blueprint.js +1 -1
  55. package/dist/commands/make/blueprint.js.map +1 -1
  56. package/dist/commands/make/da-migration.js +3 -3
  57. package/dist/commands/make/da-migration.js.map +1 -1
  58. package/dist/commands/make/mail.js +2 -2
  59. package/dist/commands/make/mail.js.map +1 -1
  60. package/dist/commands/make/notification.js +1 -1
  61. package/dist/commands/make/notification.js.map +1 -1
  62. package/dist/commands/make/queue.js +1 -1
  63. package/dist/commands/make/queue.js.map +1 -1
  64. package/dist/commands/make/resource.d.ts +2 -0
  65. package/dist/commands/make/resource.js +19 -0
  66. package/dist/commands/make/resource.js.map +1 -0
  67. package/dist/commands/make/skalfa-controller.d.ts +3 -0
  68. package/dist/commands/make/{light-controller.js → skalfa-controller.js} +9 -9
  69. package/dist/commands/make/skalfa-controller.js.map +1 -0
  70. package/dist/commands/make/skalfa-model.d.ts +3 -0
  71. package/dist/commands/make/{light-model.js → skalfa-model.js} +11 -11
  72. package/dist/commands/make/skalfa-model.js.map +1 -0
  73. package/dist/commands/runner/barrels.js.map +1 -1
  74. package/dist/commands/runner/blueprint/controller-generation.js +2 -2
  75. package/dist/commands/runner/blueprint/controller-generation.js.map +1 -1
  76. package/dist/commands/runner/blueprint/documentation-generation.js.map +1 -1
  77. package/dist/commands/runner/blueprint/migration-generation.js +3 -3
  78. package/dist/commands/runner/blueprint/migration-generation.js.map +1 -1
  79. package/dist/commands/runner/blueprint/model-generation.js +2 -2
  80. package/dist/commands/runner/blueprint/model-generation.js.map +1 -1
  81. package/dist/commands/runner/blueprint/runner.js +7 -8
  82. package/dist/commands/runner/blueprint/runner.js.map +1 -1
  83. package/dist/commands/runner/blueprint/seeder-generation.js +3 -3
  84. package/dist/commands/runner/blueprint/seeder-generation.js.map +1 -1
  85. package/dist/commands/runner/da-migration.js +1 -2
  86. package/dist/commands/runner/da-migration.js.map +1 -1
  87. package/dist/commands/runner/generate-docs.d.ts +2 -0
  88. package/dist/commands/runner/generate-docs.js +400 -0
  89. package/dist/commands/runner/generate-docs.js.map +1 -0
  90. package/dist/commands/runner/migration.js +1 -1
  91. package/dist/commands/runner/migration.js.map +1 -1
  92. package/dist/commands/runner/seeder.js +1 -1
  93. package/dist/commands/runner/seeder.js.map +1 -1
  94. package/dist/commands/stubs/index.d.ts +4 -4
  95. package/dist/commands/stubs/index.js +4 -4
  96. package/dist/commands/stubs/index.js.map +1 -1
  97. package/dist/context/context.js +6 -0
  98. package/dist/context/context.js.map +1 -1
  99. package/dist/controller/controller.d.ts +17 -30
  100. package/dist/controller/controller.js +39 -121
  101. package/dist/controller/controller.js.map +1 -1
  102. package/dist/controller/response.d.ts +6 -0
  103. package/dist/controller/response.js +63 -0
  104. package/dist/controller/response.js.map +1 -0
  105. package/dist/controller/storage.d.ts +9 -0
  106. package/dist/controller/storage.js +56 -0
  107. package/dist/controller/storage.js.map +1 -0
  108. package/dist/conversion/conversion.d.ts +3 -0
  109. package/dist/conversion/conversion.js +28 -4
  110. package/dist/conversion/conversion.js.map +1 -1
  111. package/dist/conversion/date.d.ts +1 -0
  112. package/dist/conversion/date.js +77 -0
  113. package/dist/conversion/date.js.map +1 -0
  114. package/dist/index.d.ts +2 -0
  115. package/dist/index.js +2 -0
  116. package/dist/index.js.map +1 -1
  117. package/dist/logger/logger.js +33 -0
  118. package/dist/logger/logger.js.map +1 -1
  119. package/dist/mail/mail.js +6 -6
  120. package/dist/mail/mail.js.map +1 -1
  121. package/dist/middleware/access-log.d.ts +31 -0
  122. package/dist/middleware/access-log.js +13 -0
  123. package/dist/middleware/access-log.js.map +1 -0
  124. package/dist/middleware/auth.d.ts +37 -0
  125. package/dist/middleware/auth.js +16 -0
  126. package/dist/middleware/auth.js.map +1 -0
  127. package/dist/middleware/body-parse.d.ts +35 -0
  128. package/dist/middleware/body-parse.js +87 -0
  129. package/dist/middleware/body-parse.js.map +1 -0
  130. package/dist/middleware/context.d.ts +29 -0
  131. package/dist/middleware/context.js +8 -0
  132. package/dist/middleware/context.js.map +1 -0
  133. package/dist/middleware/cors.d.ts +31 -0
  134. package/dist/middleware/cors.js +27 -0
  135. package/dist/middleware/cors.js.map +1 -0
  136. package/dist/middleware/error-handler.d.ts +33 -0
  137. package/dist/middleware/error-handler.js +17 -0
  138. package/dist/middleware/error-handler.js.map +1 -0
  139. package/dist/middleware/middleware.d.ts +31 -10
  140. package/dist/middleware/middleware.js +41 -209
  141. package/dist/middleware/middleware.js.map +1 -1
  142. package/dist/middleware/private.d.ts +29 -0
  143. package/dist/middleware/private.js +8 -0
  144. package/dist/middleware/private.js.map +1 -0
  145. package/dist/middleware/rate-limiter.d.ts +32 -0
  146. package/dist/middleware/rate-limiter.js +30 -0
  147. package/dist/middleware/rate-limiter.js.map +1 -0
  148. package/dist/notification/index.d.ts +1 -0
  149. package/dist/notification/index.js +2 -0
  150. package/dist/notification/index.js.map +1 -0
  151. package/dist/notification/notification.d.ts +16 -0
  152. package/dist/notification/notification.js +64 -0
  153. package/dist/notification/notification.js.map +1 -0
  154. package/dist/permission/permission.js +9 -0
  155. package/dist/permission/permission.js.map +1 -1
  156. package/dist/registry/registry.d.ts +0 -6
  157. package/dist/registry/registry.js +6 -6
  158. package/dist/registry/registry.js.map +1 -1
  159. package/dist/storage/storage.d.ts +3 -3
  160. package/dist/storage/storage.js.map +1 -1
  161. package/dist/validation/validation.js +43 -51
  162. package/dist/validation/validation.js.map +1 -1
  163. package/package.json +4 -4
  164. package/src/auth/auth.ts +21 -252
  165. package/src/auth/create-access-token.ts +29 -0
  166. package/src/auth/create-user-mail-token.ts +24 -0
  167. package/src/auth/helpers/generate-agent-id.ts +8 -0
  168. package/src/auth/helpers/get-request-ip.ts +3 -0
  169. package/src/auth/helpers/get-user-permissions.ts +15 -0
  170. package/src/auth/helpers/index.ts +3 -0
  171. package/src/auth/list-user-sessions.ts +11 -0
  172. package/src/auth/revalidate-user-permissions-by-role.ts +13 -0
  173. package/src/auth/revalidate-user-permissions.ts +26 -0
  174. package/src/auth/revoke-access-token.ts +5 -0
  175. package/src/auth/verify-access-token.ts +56 -0
  176. package/src/auth/verify-user-mail-token.ts +24 -0
  177. package/src/commands/cli.ts +19 -29
  178. package/src/commands/make/basic-controller.ts +4 -2
  179. package/src/commands/make/basic-migration.ts +5 -3
  180. package/src/commands/make/basic-model.ts +3 -1
  181. package/src/commands/make/basic-seeder.ts +3 -1
  182. package/src/commands/make/blueprint.ts +3 -1
  183. package/src/commands/make/da-migration.ts +6 -5
  184. package/src/commands/make/mail.ts +4 -2
  185. package/src/commands/make/notification.ts +3 -1
  186. package/src/commands/make/queue.ts +3 -1
  187. package/src/commands/make/resource.ts +21 -0
  188. package/src/commands/make/{light-controller.ts → skalfa-controller.ts} +10 -8
  189. package/src/commands/make/{light-model.ts → skalfa-model.ts} +12 -10
  190. package/src/commands/runner/barrels.ts +4 -0
  191. package/src/commands/runner/blueprint/controller-generation.ts +4 -2
  192. package/src/commands/runner/blueprint/documentation-generation.ts +2 -0
  193. package/src/commands/runner/blueprint/migration-generation.ts +5 -3
  194. package/src/commands/runner/blueprint/model-generation.ts +4 -2
  195. package/src/commands/runner/blueprint/runner.ts +15 -8
  196. package/src/commands/runner/blueprint/seeder-generation.ts +5 -3
  197. package/src/commands/runner/da-migration.ts +3 -2
  198. package/src/commands/runner/generate-docs.ts +495 -0
  199. package/src/commands/runner/migration.ts +1 -1
  200. package/src/commands/runner/seeder.ts +1 -1
  201. package/src/commands/stubs/index.ts +4 -4
  202. package/src/context/context.ts +23 -17
  203. package/src/controller/controller.ts +124 -239
  204. package/src/controller/response.ts +78 -0
  205. package/src/controller/storage.ts +78 -0
  206. package/src/conversion/conversion.ts +90 -64
  207. package/src/conversion/date.ts +74 -0
  208. package/src/index.ts +2 -0
  209. package/src/logger/logger.ts +217 -176
  210. package/src/mail/mail.ts +85 -85
  211. package/src/middleware/access-log.ts +15 -0
  212. package/src/middleware/auth.ts +19 -0
  213. package/src/middleware/body-parse.ts +83 -0
  214. package/src/middleware/context.ts +11 -0
  215. package/src/middleware/cors.ts +31 -0
  216. package/src/middleware/error-handler.ts +20 -0
  217. package/src/middleware/middleware.ts +91 -288
  218. package/src/middleware/private.ts +8 -0
  219. package/src/middleware/rate-limiter.ts +41 -0
  220. package/src/notification/index.ts +1 -0
  221. package/src/notification/notification.ts +86 -0
  222. package/src/permission/permission.ts +140 -136
  223. package/src/registry/registry.ts +17 -15
  224. package/src/route/route.ts +11 -11
  225. package/src/storage/storage.ts +104 -106
  226. package/src/validation/validation.ts +322 -346
  227. package/dist/auth.util.d.ts +0 -19
  228. package/dist/auth.util.js +0 -183
  229. package/dist/auth.util.js.map +0 -1
  230. package/dist/commands/make/light-controller.d.ts +0 -3
  231. package/dist/commands/make/light-controller.js.map +0 -1
  232. package/dist/commands/make/light-model.d.ts +0 -3
  233. package/dist/commands/make/light-model.js.map +0 -1
  234. package/dist/context.util.d.ts +0 -7
  235. package/dist/context.util.js +0 -11
  236. package/dist/context.util.js.map +0 -1
  237. package/dist/controller.util.d.ts +0 -118
  238. package/dist/controller.util.js +0 -144
  239. package/dist/controller.util.js.map +0 -1
  240. package/dist/conversion.util.d.ts +0 -8
  241. package/dist/conversion.util.js +0 -52
  242. package/dist/conversion.util.js.map +0 -1
  243. package/dist/db/db.d.ts +0 -84
  244. package/dist/db/db.js +0 -177
  245. package/dist/db/db.js.map +0 -1
  246. package/dist/db/index.d.ts +0 -1
  247. package/dist/db/index.js +0 -2
  248. package/dist/db/index.js.map +0 -1
  249. package/dist/db.util.d.ts +0 -84
  250. package/dist/db.util.js +0 -177
  251. package/dist/db.util.js.map +0 -1
  252. package/dist/logger.util.d.ts +0 -30
  253. package/dist/logger.util.js +0 -126
  254. package/dist/logger.util.js.map +0 -1
  255. package/dist/mail.util.d.ts +0 -21
  256. package/dist/mail.util.js +0 -53
  257. package/dist/mail.util.js.map +0 -1
  258. package/dist/middleware.util.d.ts +0 -263
  259. package/dist/middleware.util.js +0 -233
  260. package/dist/middleware.util.js.map +0 -1
  261. package/dist/model/index.d.ts +0 -3
  262. package/dist/model/index.js +0 -4
  263. package/dist/model/index.js.map +0 -1
  264. package/dist/model/model.d.ts +0 -204
  265. package/dist/model/model.js +0 -1495
  266. package/dist/model/model.js.map +0 -1
  267. package/dist/model.util.d.ts +0 -204
  268. package/dist/model.util.js +0 -1495
  269. package/dist/model.util.js.map +0 -1
  270. package/dist/permission.util.d.ts +0 -38
  271. package/dist/permission.util.js +0 -91
  272. package/dist/permission.util.js.map +0 -1
  273. package/dist/registry.util.d.ts +0 -28
  274. package/dist/registry.util.js +0 -19
  275. package/dist/registry.util.js.map +0 -1
  276. package/dist/route.util.d.ts +0 -1
  277. package/dist/route.util.js +0 -12
  278. package/dist/route.util.js.map +0 -1
  279. package/dist/storage.util.d.ts +0 -56
  280. package/dist/storage.util.js +0 -82
  281. package/dist/storage.util.js.map +0 -1
  282. package/dist/validation.util.d.ts +0 -7
  283. package/dist/validation.util.js +0 -237
  284. package/dist/validation.util.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { conversion, logger } from "@utils";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { lightMigrationStub } from "../../stubs";
4
+ import { skalfaMigrationStub } from "../../stubs";
5
+
6
+
5
7
 
6
8
  // ==================================>
7
9
  // ## Command: Blueprint migration generation
@@ -13,7 +15,7 @@ export async function migrationGeneration(
13
15
  marker: string
14
16
  ): Promise<boolean> {
15
17
  const name = conversion.strSnake(conversion.strPlural(model.split("/").pop() || ""));
16
- const basePath = path.join(process.cwd(), "src", "database", "migrations", "0000_00");
18
+ const basePath = path.join(process.cwd(), "database", "migrations", "0000_00");
17
19
  const filename = `${name}.ts`;
18
20
  const filePath = path.join(basePath, filename);
19
21
  const className = `create${conversion.strPascal(name)}Table`;
@@ -109,7 +111,7 @@ export async function migrationGeneration(
109
111
 
110
112
  const migrationSchema = migrationFields.map((f) => ` ${f}`).join("\n");
111
113
 
112
- let stub = lightMigrationStub;
114
+ let stub = skalfaMigrationStub;
113
115
 
114
116
  stub = stub
115
117
  .replace(/{{\s*marker\s*}}/g, marker)
@@ -2,7 +2,9 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { conversion } from "@utils";
4
4
  import { resolveBlueprintPath } from "./runner";
5
- import { lightModelStub } from "../../stubs";
5
+ import { skalfaModelStub } from "../../stubs";
6
+
7
+
6
8
 
7
9
  // ============================>
8
10
  // ## Command: blueprint model generation
@@ -103,7 +105,7 @@ export async function modelGeneration(
103
105
  imports.push(`import { ${importRelations.join(", ")} } from '@models'`);
104
106
  }
105
107
 
106
- let stub = lightModelStub;
108
+ let stub = skalfaModelStub;
107
109
  const strImportUtils = importUtils?.length ? ", " + importUtils.join(", ") : "";
108
110
 
109
111
  stub = stub
@@ -6,10 +6,11 @@ import { modelGeneration } from "./model-generation";
6
6
  import { migrationGeneration } from "./migration-generation";
7
7
  import { controllerGeneration } from "./controller-generation";
8
8
  import { seederGeneration } from "./seeder-generation";
9
- import { generateDrawioEntityDocumentation, generateMermaidEntityDocumentation, generatePostmanAPIDocumentation } from "./documentation-generation";
10
9
  import { exec as execCb } from "child_process";
11
10
  import { promisify } from "util";
12
11
 
12
+
13
+
13
14
  const exec = promisify(execCb);
14
15
 
15
16
  export interface BlueprintSchema {
@@ -32,6 +33,8 @@ const blueprintMarker = `// ============================================
32
33
 
33
34
  `;
34
35
 
36
+
37
+
35
38
  // =====================================>
36
39
  // ## Command: blueprint
37
40
  // =====================================>
@@ -50,6 +53,8 @@ export const blueprintCommand = new Command("blueprint")
50
53
  process.exit(0);
51
54
  });
52
55
 
56
+
57
+
53
58
  // =======================>
54
59
  // ## Command: Blueprint engine
55
60
  // =======================>
@@ -105,16 +110,18 @@ export async function runBlueprints(options?: { only?: string[] }) {
105
110
  await seederGeneration(struct.model, schema, seeders, marker);
106
111
  }
107
112
 
108
- if (struct.mermaid !== false) {
109
- await generateMermaidEntityDocumentation(file.file, file.blueprints);
110
- }
113
+ // if (struct.mermaid !== false) {
114
+ // await generateMermaidEntityDocumentation(file.file, file.blueprints);
115
+ // }
111
116
  }
112
117
  }
113
118
 
114
- await generateDrawioEntityDocumentation(loaded);
115
- await generatePostmanAPIDocumentation(postmanSchemas);
119
+ // await generateDrawioEntityDocumentation(loaded);
120
+ // await generatePostmanAPIDocumentation(postmanSchemas);
116
121
  }
117
122
 
123
+
124
+
118
125
  // =======================>
119
126
  // ## Command: Blueprint load json files
120
127
  // =======================>
@@ -146,11 +153,11 @@ type BlueprintFileType = "model" | "controller";
146
153
 
147
154
  const TYPE_MAP: Record<BlueprintFileType, { root: string; suffix: string }> = {
148
155
  model: {
149
- root: "src/models",
156
+ root: "app/models",
150
157
  suffix: ".model",
151
158
  },
152
159
  controller: {
153
- root: "src/controllers",
160
+ root: "app/controllers",
154
161
  suffix: ".controller",
155
162
  },
156
163
  };
@@ -1,7 +1,9 @@
1
1
  import { conversion, logger } from "@utils";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { lightSeederStub } from "../../stubs";
4
+ import { skalfaSeederStub } from "../../stubs";
5
+
6
+
5
7
 
6
8
  // =================================>
7
9
  // ## Command: Blueprint seeder generation
@@ -14,7 +16,7 @@ export async function seederGeneration(
14
16
  ): Promise<boolean> {
15
17
  const modelName = conversion.strPascal(model?.split("/")?.pop() || "");
16
18
  const name = modelName + "Seeder";
17
- const basePath = path.join(process.cwd(), "src", "database", "seeders");
19
+ const basePath = path.join(process.cwd(), "database", "seeders");
18
20
  const filename = conversion.strSlug(modelName);
19
21
  const filePath = path.join(basePath, `${filename}.seeder.ts`);
20
22
 
@@ -37,7 +39,7 @@ export async function seederGeneration(
37
39
  })
38
40
  .join(", ")}}`).join(",\n ");
39
41
 
40
- let stub = lightSeederStub;
42
+ let stub = skalfaSeederStub;
41
43
 
42
44
  stub = stub
43
45
  .replace(/{{\s*marker\s*}}/g, marker)
@@ -2,16 +2,17 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { Command } from "commander";
4
4
  import { logger } from "@utils";
5
- // @ts-ignore
6
5
  import * as utils from "@utils";
7
6
 
8
7
  const da = (utils as any).da;
9
8
  const daClient = (utils as any).daClient;
10
9
 
11
- const DW_MIGRATIONS_DIR = path.resolve("./src/database/da.migrations");
10
+ const DW_MIGRATIONS_DIR = path.resolve("./analytic/migrations");
12
11
 
13
12
  const MIGRATION_TABLE = `${process.env.DA_DATABASE || "default"}.migrations`;
14
13
 
14
+
15
+
15
16
  // ================================
16
17
  // ## Create Migration Table
17
18
  // ================================
@@ -0,0 +1,495 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { Command } from "commander";
4
+
5
+ // Helper to recursively find files with specific extensions
6
+ function getFiles(dir: string, extList: string[]): string[] {
7
+ let results: string[] = [];
8
+ if (!fs.existsSync(dir)) return results;
9
+ const list = fs.readdirSync(dir);
10
+ for (const file of list) {
11
+ const filePath = path.join(dir, file);
12
+ const stat = fs.statSync(filePath);
13
+ if (stat.isDirectory()) {
14
+ if (file !== "node_modules" && file !== ".next" && file !== ".git") {
15
+ results = results.concat(getFiles(filePath, extList));
16
+ }
17
+ } else {
18
+ if (extList.includes(path.extname(file))) {
19
+ results.push(filePath);
20
+ }
21
+ }
22
+ }
23
+ return results;
24
+ }
25
+
26
+ // Simple parser for object literal string (e.g. c.validation block)
27
+ function parseObjectLiteral(str: string): Record<string, string> {
28
+ const result: Record<string, string> = {};
29
+ // Strip single-line and multi-line comments
30
+ const cleanStr = str
31
+ .replace(/\/\*[\s\S]*?\*\//g, "")
32
+ .replace(/\/\/.*$/gm, "");
33
+
34
+ // Regex to match key: value
35
+ const regex = /(?:(\w+)|["']([^"']+)["'])\s*:\s*([^,}\n]+)/g;
36
+ let match;
37
+ while ((match = regex.exec(cleanStr)) !== null) {
38
+ const key = match[1] || match[2];
39
+ const val = match[3].trim().replace(/['"\[\]]/g, "").replace(/\s+/g, " ");
40
+ result[key] = val;
41
+ }
42
+ return result;
43
+ }
44
+
45
+ interface RouteInfo {
46
+ method: string;
47
+ path: string;
48
+ controllerName: string;
49
+ methodName: string;
50
+ routesFile: string; // e.g. "base.routes.ts" or "admin.routes.ts"
51
+ }
52
+
53
+ interface ModelField {
54
+ name: string;
55
+ type: string;
56
+ decorators: string[]; // e.g. ["fillable", "selectable", "searchable"]
57
+ }
58
+
59
+ interface ModelRelation {
60
+ name: string;
61
+ type: string; // e.g. "HasMany", "BelongsTo", "HasOne"
62
+ targetModel: string;
63
+ }
64
+
65
+ interface ModelInfo {
66
+ name: string;
67
+ fields: ModelField[];
68
+ relations: ModelRelation[];
69
+ }
70
+
71
+ interface ControllerMethodInfo {
72
+ methodName: string;
73
+ isMagicResolve: boolean;
74
+ isMagicPump: boolean;
75
+ validationModel?: string;
76
+ validationRules: Record<string, string>;
77
+ customQueryParams: string[];
78
+ customPayloadFields: string[];
79
+ }
80
+
81
+ interface ControllerInfo {
82
+ name: string;
83
+ methods: Record<string, ControllerMethodInfo>;
84
+ }
85
+
86
+ export const generateDocsCommand = new Command("generate:docs")
87
+ .description("Generate API Markdown documentation from routes, controllers, and models")
88
+ .option("-p, --path <path>", "Filter by specific route path (e.g. --path=/users)")
89
+ .action(async (options) => {
90
+ const projectRoot = process.cwd();
91
+ const filterPath = options.path;
92
+
93
+ console.log(`🔍 Scanning project in: ${projectRoot}`);
94
+ if (filterPath) {
95
+ console.log(`Filter path: ${filterPath}`);
96
+ }
97
+
98
+ // 1. Parse Models
99
+ const modelFiles = getFiles(path.join(projectRoot, "app", "models"), [".ts"]);
100
+ const models: Record<string, ModelInfo> = {};
101
+
102
+ for (const file of modelFiles) {
103
+ const content = fs.readFileSync(file, "utf8");
104
+ // Find class User extends Model
105
+ const classMatch = /class\s+(\w+)\s+extends\s+Model/g.exec(content);
106
+ if (!classMatch) continue;
107
+ const modelName = classMatch[1];
108
+
109
+ const fields: ModelField[] = [];
110
+ const relations: ModelRelation[] = [];
111
+
112
+ // Parse fields: @Field(["fillable", "selectable", "searchable"]) name!: string
113
+ const fieldRegex = /@Field\(\s*\[([\s\S]*?)\]\s*\)\s*(\w+)\s*!?:?\s*(\w+)/g;
114
+ let fieldMatch;
115
+ while ((fieldMatch = fieldRegex.exec(content)) !== null) {
116
+ const decorators = fieldMatch[1]
117
+ .split(",")
118
+ .map((d) => d.trim().replace(/['"]/g, ""))
119
+ .filter(Boolean);
120
+ fields.push({
121
+ name: fieldMatch[2],
122
+ type: fieldMatch[3],
123
+ decorators,
124
+ });
125
+ }
126
+
127
+ // Parse relations: @HasMany(() => Role, "user_id") roles!: Role[]
128
+ const relationRegex = /@(HasMany|HasOne|BelongsTo)\(\s*\(\s*\)\s*=>\s*(\w+)[^]*?\)\s*(\w+)/g;
129
+ let relationMatch;
130
+ while ((relationMatch = relationRegex.exec(content)) !== null) {
131
+ relations.push({
132
+ type: relationMatch[1],
133
+ targetModel: relationMatch[2],
134
+ name: relationMatch[3],
135
+ });
136
+ }
137
+
138
+ models[modelName] = {
139
+ name: modelName,
140
+ fields,
141
+ relations,
142
+ };
143
+ }
144
+
145
+ // 2. Parse Controllers
146
+ const controllerFiles = getFiles(path.join(projectRoot, "app", "controllers"), [".ts"]);
147
+ const controllers: Record<string, ControllerInfo> = {};
148
+
149
+ for (const file of controllerFiles) {
150
+ const content = fs.readFileSync(file, "utf8");
151
+ const classMatch = /class\s+(\w+)/g.exec(content);
152
+ if (!classMatch) continue;
153
+ const controllerName = classMatch[1];
154
+
155
+ const methods: Record<string, ControllerMethodInfo> = {};
156
+
157
+ // Simple method splitter by looking for static async or static methods
158
+ const methodRegex = /static\s+(?:async\s+)?(\w+)\s*\(\s*c\s*:\s*(\w+|any)\s*\)\s*\{([\s\S]*?)(?=\s+static|\s+\/\/|\s*\}\s*$)/g;
159
+ let methodMatch;
160
+ while ((methodMatch = methodRegex.exec(content)) !== null) {
161
+ const methodName = methodMatch[1];
162
+ const body = methodMatch[3];
163
+
164
+ const isMagicResolve = body.includes(".resolve(c)");
165
+ const isMagicPump = body.includes(".pump(");
166
+
167
+ // Parse c.validation<ModelName>({ ... })
168
+ let validationModel: string | undefined;
169
+ let validationRules: Record<string, string> = {};
170
+ const valRegex = /c\.validation(?:<(\w+)>)?\(\s*(\{[\s\S]*?\})\s*\)/g;
171
+ const valMatch = valRegex.exec(body);
172
+ if (valMatch) {
173
+ validationModel = valMatch[1];
174
+ validationRules = parseObjectLiteral(valMatch[2]);
175
+ }
176
+
177
+ // Parse custom query parameters: c.getQuery.field, c.query.field, or destructuring
178
+ const customQueryParams: string[] = [];
179
+ const queryFieldRegex = /\bc\.(?:getQuery|query)\.(\w+)\b/g;
180
+ let qfMatch;
181
+ while ((qfMatch = queryFieldRegex.exec(body)) !== null) {
182
+ customQueryParams.push(qfMatch[1]);
183
+ }
184
+ // Destructuring: const { a, b } = c.getQuery
185
+ const queryDestructRegex = /const\s*\{\s*([\w\s,]+)\s*\}\s*=\s*c\.(?:getQuery|query)/g;
186
+ let qdMatch;
187
+ while ((qdMatch = queryDestructRegex.exec(body)) !== null) {
188
+ qdMatch[1].split(",").forEach((p) => {
189
+ const trimmed = p.trim();
190
+ if (trimmed) customQueryParams.push(trimmed);
191
+ });
192
+ }
193
+
194
+ // Parse custom payload fields: c.payload.field, c.body.field, or destructuring
195
+ const customPayloadFields: string[] = [];
196
+ const payloadFieldRegex = /\bc\.(?:payload|body)\.(\w+)\b/g;
197
+ let pfMatch;
198
+ while ((pfMatch = payloadFieldRegex.exec(body)) !== null) {
199
+ customPayloadFields.push(pfMatch[1]);
200
+ }
201
+ const payloadDestructRegex = /const\s*\{\s*([\w\s,]+)\s*\}\s*=\s*c\.(?:payload|body)/g;
202
+ let pdMatch;
203
+ while ((pdMatch = payloadDestructRegex.exec(body)) !== null) {
204
+ pdMatch[1].split(",").forEach((p) => {
205
+ const trimmed = p.trim();
206
+ if (trimmed) customPayloadFields.push(trimmed);
207
+ });
208
+ }
209
+
210
+ methods[methodName] = {
211
+ methodName,
212
+ isMagicResolve,
213
+ isMagicPump,
214
+ validationModel,
215
+ validationRules,
216
+ customQueryParams: Array.from(new Set(customQueryParams)),
217
+ customPayloadFields: Array.from(new Set(customPayloadFields)),
218
+ };
219
+ }
220
+
221
+ controllers[controllerName] = {
222
+ name: controllerName,
223
+ methods,
224
+ };
225
+ }
226
+
227
+ // 3. Parse Routes
228
+ const routeFiles = getFiles(path.join(projectRoot, "app", "routes"), [".ts"]);
229
+ const routesList: RouteInfo[] = [];
230
+
231
+ for (const file of routeFiles) {
232
+ const content = fs.readFileSync(file, "utf8");
233
+ const filename = path.basename(file);
234
+
235
+ // We want to find Elysia route registrations and api() helper registrations
236
+ // Group: app.group('/api', (route) => { ... })
237
+ // Let's capture the group path
238
+ const groupRegex = /\.group\(\s*['"]([^'"]+)['"]/g;
239
+ let groupPrefix = "";
240
+ const groupMatch = groupRegex.exec(content);
241
+ if (groupMatch) {
242
+ groupPrefix = groupMatch[1]; // e.g. "/api"
243
+ }
244
+
245
+ // 1. Standard routes: route.get('/path', Controller.method)
246
+ const routeRegex = /route\.(\w+)\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)\.(\w+)\)/g;
247
+ let rMatch;
248
+ while ((rMatch = routeRegex.exec(content)) !== null) {
249
+ routesList.push({
250
+ method: rMatch[1].toUpperCase(),
251
+ path: `${groupPrefix}${rMatch[2]}`.replace(/\/+/g, "/"),
252
+ controllerName: rMatch[3],
253
+ methodName: rMatch[4],
254
+ routesFile: filename,
255
+ });
256
+ }
257
+
258
+ // 2. Resource routes: api(route, "/users", UserController)
259
+ const resourceRegex = /api\(\s*\w+\s*,\s*['"]([^'"]+)['"]\s*,\s*(\w+)\)/g;
260
+ let resMatch;
261
+ while ((resMatch = resourceRegex.exec(content)) !== null) {
262
+ const basePath = `${groupPrefix}${resMatch[1]}`.replace(/\/+/g, "/");
263
+ const controllerName = resMatch[2];
264
+
265
+ // Standard RESTful mappings for api()
266
+ const resourceMethods = [
267
+ { method: "GET", path: basePath, methodName: "index" },
268
+ { method: "POST", path: basePath, methodName: "store" },
269
+ { method: "PUT", path: `${basePath}/:id`.replace(/\/+/g, "/"), methodName: "update" },
270
+ { method: "DELETE", path: `${basePath}/:id`.replace(/\/+/g, "/"), methodName: "destroy" },
271
+ ];
272
+
273
+ for (const rm of resourceMethods) {
274
+ routesList.push({
275
+ method: rm.method,
276
+ path: rm.path,
277
+ controllerName,
278
+ methodName: rm.methodName,
279
+ routesFile: filename,
280
+ });
281
+ }
282
+ }
283
+ }
284
+
285
+ // 4. Generate Markdown Documentation
286
+ let generatedCount = 0;
287
+ const docsDir = path.join(projectRoot, "docs");
288
+
289
+ if (!fs.existsSync(docsDir)) {
290
+ fs.mkdirSync(docsDir, { recursive: true });
291
+ }
292
+
293
+ for (const route of routesList) {
294
+ const fullPath = route.path;
295
+
296
+ // Filter by path if requested
297
+ if (filterPath && !fullPath.startsWith(filterPath)) {
298
+ continue;
299
+ }
300
+
301
+ const controller = controllers[route.controllerName];
302
+ if (!controller) continue;
303
+
304
+ const methodInfo = controller.methods[route.methodName];
305
+ if (!methodInfo) continue;
306
+
307
+ // Find associated model
308
+ // First check if validation specified a model, otherwise guess from controller name or model usages
309
+ let modelName = methodInfo.validationModel;
310
+ if (!modelName) {
311
+ // Guess from controller name (e.g., UserController -> User)
312
+ const guessed = route.controllerName.replace("Controller", "");
313
+ if (models[guessed]) {
314
+ modelName = guessed;
315
+ }
316
+ }
317
+
318
+ const model = modelName ? models[modelName] : undefined;
319
+
320
+ // Determine output directory based on routes file and path
321
+ // If routesFile is not "base.routes.ts", prefix folder with routesFile prefix (e.g. admin.routes.ts -> admin)
322
+ let modulePrefix = "";
323
+ if (route.routesFile !== "base.routes.ts") {
324
+ modulePrefix = route.routesFile.replace(".routes.ts", "");
325
+ }
326
+
327
+ // First path segment after the group/api prefix
328
+ const cleanPath = fullPath.replace(/^\/(api|v1)\//, "").replace(/^\//, "");
329
+ const firstSegment = cleanPath.split("/")[0] || "general";
330
+
331
+ const targetFolder = modulePrefix
332
+ ? path.join(docsDir, modulePrefix, firstSegment)
333
+ : path.join(docsDir, firstSegment);
334
+
335
+ if (!fs.existsSync(targetFolder)) {
336
+ fs.mkdirSync(targetFolder, { recursive: true });
337
+ }
338
+
339
+ // File Naming: [METHOD]_[SAFE_PATH].md
340
+ // Replace :param with [param], and other unsafe chars with _
341
+ const safePathName = fullPath
342
+ .replace(/:(\w+)/g, "[$1]") // replace :id with [id]
343
+ .replace(/^\//, "")
344
+ .replace(/\/+/g, "_"); // replace slashes with underscore
345
+
346
+ const fileName = `${route.method}_${safePathName}.md`;
347
+ const filePath = path.join(targetFolder, fileName);
348
+
349
+ // Start building Markdown content
350
+ let md = `# ${route.method} ${fullPath}\n\n`;
351
+ md += `* **Controller**: \`${route.controllerName}.${route.methodName}\`\n`;
352
+ if (model) {
353
+ md += `* **Model**: \`${model.name}\`\n`;
354
+ }
355
+ md += `* **Features**: ${
356
+ [
357
+ methodInfo.isMagicResolve ? "**[Magic Resolve]**" : "",
358
+ methodInfo.isMagicPump ? "**[Magic Pump]**" : "",
359
+ ]
360
+ .filter(Boolean)
361
+ .join(", ") || "None"
362
+ }\n\n`;
363
+
364
+ // 1. Headers Table (Only if GET and supports option mode, or custom headers)
365
+ if (route.method === "GET" && methodInfo.isMagicResolve) {
366
+ md += `## Request Headers (Optional)\n\n`;
367
+ md += `| Key | Example Value |\n`;
368
+ md += `| :--- | :--- |\n`;
369
+ md += `| \`x-option\` | \`"true"\` |\n\n`;
370
+ }
371
+
372
+ // 2. Query Parameters Table
373
+ const queryParams: Array<{ key: string; example: string; desc: string }> = [];
374
+ if (route.method === "GET" && methodInfo.isMagicResolve) {
375
+ queryParams.push(
376
+ { key: "isOption", example: "true", desc: "Alternative to activate **Dropdown/Option Mode**." },
377
+ { key: "option[]", example: '["id", "name"]', desc: "Customizes the columns mapped to `value` and `label` in Option Mode." },
378
+ { key: "page", example: "1", desc: "The page number for pagination." },
379
+ { key: "paginate", example: "15", desc: "Number of records per page." },
380
+ { key: "search", example: '"john"', desc: "Performs a global search across `searchable` fields in the model." },
381
+ { key: "filter[column]", example: '"active"', desc: "Filters records by a specific column value." }
382
+ );
383
+
384
+ // Add expandable relations
385
+ if (model && model.relations.length > 0) {
386
+ const relationNames = model.relations.map((r) => `\`"${r.name}"\``).join(", ");
387
+ queryParams.push({
388
+ key: "expand",
389
+ example: `"${model.relations[0].name}"`,
390
+ desc: `Eager-loads related models. Available relations: ${relationNames}. Can also be passed as \`expand[]\`.`,
391
+ });
392
+ }
393
+
394
+ queryParams.push({ key: "sort", example: '"-created_at"', desc: "Sorts by a column. Prefix with `-` for descending order." });
395
+ }
396
+
397
+ // Add custom query parameters found in controller
398
+ for (const customQ of methodInfo.customQueryParams) {
399
+ // Skip if already in standard params
400
+ if (["page", "paginate", "search", "filter", "expand", "sort", "isOption", "option"].includes(customQ)) continue;
401
+ queryParams.push({
402
+ key: customQ,
403
+ example: `"value"`,
404
+ desc: "Custom query parameter extracted from controller logic.",
405
+ });
406
+ }
407
+
408
+ if (queryParams.length > 0) {
409
+ md += `## Query Parameters\n\n`;
410
+ md += `| Key | Example Value |\n`;
411
+ md += `| :--- | :--- |\n`;
412
+ for (const q of queryParams) {
413
+ md += `| \`${q.key}\` | \`${q.example}\` |\n`;
414
+ }
415
+ md += `\n`;
416
+ }
417
+
418
+ // 3. Payload Table (For POST/PUT/PATCH)
419
+ if (["POST", "PUT", "PATCH"].includes(route.method)) {
420
+ md += `## Payload (JSON Body)\n\n`;
421
+ md += `| Key | Example Value | Required | Validation |\n`;
422
+ md += `| :--- | :--- | :--- | :--- |\n`;
423
+
424
+ const payloadFieldsSet = new Set<string>();
425
+
426
+ // First add fields from validation rules
427
+ for (const [field, rule] of Object.entries(methodInfo.validationRules)) {
428
+ payloadFieldsSet.add(field);
429
+ const isRequired = rule.includes("required") ? "Yes" : "No";
430
+ md += `| \`${field}\` | \`""\` | ${isRequired} | \`${rule}\` |\n`;
431
+ }
432
+
433
+ // Add other fillable fields from Model if not already in validation
434
+ if (model) {
435
+ for (const f of model.fields) {
436
+ if (f.decorators.includes("fillable") && !payloadFieldsSet.has(f.name)) {
437
+ payloadFieldsSet.add(f.name);
438
+ md += `| \`${f.name}\` | \`""\` | No | \`None\` |\n`;
439
+ }
440
+ }
441
+
442
+ // Add pumpable relations
443
+ if (methodInfo.isMagicPump) {
444
+ for (const r of model.relations) {
445
+ if (!payloadFieldsSet.has(r.name)) {
446
+ payloadFieldsSet.add(r.name);
447
+ const isArray = r.type === "HasMany" ? "[]" : "";
448
+ md += `| \`${r.name}\` | \`${r.type === "HasMany" ? "[]" : "{}"}\` | No | \`Relation (${r.type})\` |\n`;
449
+ }
450
+ }
451
+ }
452
+ }
453
+
454
+ // Add custom payload fields found in controller
455
+ for (const customP of methodInfo.customPayloadFields) {
456
+ if (!payloadFieldsSet.has(customP)) {
457
+ payloadFieldsSet.add(customP);
458
+ md += `| \`${customP}\` | \`""\` | No | \`Custom\` |\n`;
459
+ }
460
+ }
461
+ md += `\n`;
462
+ }
463
+
464
+ // 4. Response Table
465
+ md += `## Response\n\n`;
466
+ md += `| Key | Example Result |\n`;
467
+ md += `| :--- | :--- |\n`;
468
+
469
+ if (route.method === "GET" && methodInfo.isMagicResolve) {
470
+ md += `| \`data\` | \`[]\` |\n`;
471
+ md += `| \`total\` | \`0\` |\n`;
472
+ } else if (model) {
473
+ // Show selectable fields of the model
474
+ for (const f of model.fields) {
475
+ if (f.decorators.includes("selectable")) {
476
+ let exampleVal = `""`;
477
+ if (f.type === "number") exampleVal = `0`;
478
+ else if (f.type === "boolean") exampleVal = `true`;
479
+ else if (f.type === "Date") exampleVal = `"${new Date().toISOString()}"`;
480
+
481
+ md += `| \`${f.name}\` | \`${exampleVal}\` |\n`;
482
+ }
483
+ }
484
+ } else {
485
+ md += `| \`success\` | \`true\` |\n`;
486
+ }
487
+ md += `\n`;
488
+
489
+ fs.writeFileSync(filePath, md, "utf8");
490
+ console.log(`Generated: docs/${path.relative(docsDir, filePath)}`);
491
+ generatedCount++;
492
+ }
493
+
494
+ console.log(`🎉 API Documentation generated successfully! Total files: ${generatedCount}`);
495
+ });
@@ -117,7 +117,7 @@ async function runMigrationFile() {
117
117
 
118
118
  const migrationsDir = fs.existsSync(path.resolve("./database/migrations"))
119
119
  ? path.resolve("./database/migrations")
120
- : path.resolve("./src/database/migrations");
120
+ : path.resolve("./database/migrations");
121
121
  if (!fs.existsSync(migrationsDir)) {
122
122
  logger.info("No migrations directory found.");
123
123
  return;
@@ -21,7 +21,7 @@ export const seederCommand = new Command("seeder")
21
21
  export const runSeeder = async () => {
22
22
  const seedersDir = fs.existsSync(path.join(process.cwd(), "database", "seeders"))
23
23
  ? path.join(process.cwd(), "database", "seeders")
24
- : path.join(process.cwd(), "src", "database", "seeders");
24
+ : path.join(process.cwd(), "database", "seeders");
25
25
  if (!fs.existsSync(seedersDir)) {
26
26
  logger.info("No seeders directory found.");
27
27
  return;