@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,37 @@
1
+ export interface RedisService {
2
+ get(key: string): Promise<string | null>;
3
+ setex(key: string, ttl: number, value: string): Promise<any>;
4
+ del(key: string | string[]): Promise<any>;
5
+ [key: string]: any;
6
+ }
7
+
8
+ export interface QueueService {
9
+ add(name: string, data: any, options?: any): Promise<any>;
10
+ [key: string]: any;
11
+ }
12
+
13
+ export interface Registry {
14
+ redis?: RedisService;
15
+ queue?: QueueService;
16
+ [key: string]: any;
17
+ }
18
+
19
+ class ServiceRegistry {
20
+ private services: Registry = {};
21
+
22
+ /**
23
+ * Register an optional service instance.
24
+ */
25
+ register<K extends keyof Registry>(name: K, service: Registry[K]): void {
26
+ this.services[name] = service;
27
+ }
28
+
29
+ /**
30
+ * Retrieve a registered service instance.
31
+ */
32
+ get<K extends keyof Registry>(name: K): Registry[K] {
33
+ return this.services[name];
34
+ }
35
+ }
36
+
37
+ export const registry = new ServiceRegistry();
@@ -0,0 +1 @@
1
+ export * from "./route";
@@ -0,0 +1,12 @@
1
+ // ================================>
2
+ // ## Route: Basic api routers
3
+ // ================================>
4
+ export function api(app: any, basePath: string, controller: any) {
5
+ return app.group(basePath, (group: any) => group
6
+ .get("/", controller.index)
7
+ .post("/", controller.store)
8
+ .get("/:id", controller.show)
9
+ .put("/:id", controller.update)
10
+ .delete("/:id", controller.destroy)
11
+ )
12
+ }
@@ -0,0 +1 @@
1
+ export * from "./storage";
@@ -0,0 +1,107 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { Elysia } from "elysia";
4
+ import { db } from "@skalfa/skalfa-orm";
5
+
6
+
7
+
8
+ // ================================>
9
+ // ## Storage: Middleware storage handler
10
+ // ================================>
11
+ export const storage = (app: Elysia) => app.get("/storage/*", async ({ params, set, user }: Record<string, any>) => {
12
+ const requestedPath = params["*"];
13
+ const baseDir = path.resolve("storage", "public");
14
+ const targetPath = path.resolve(baseDir, requestedPath);
15
+
16
+ if (!targetPath.startsWith(baseDir)) {
17
+ set.status = 400;
18
+ return { error: "Invalid path" };
19
+ }
20
+
21
+ if (fs.existsSync(targetPath)) {
22
+ const ext = path.extname(targetPath).toLowerCase();
23
+ const mimeTypes: Record<string, string> = {
24
+ ".jpg" : "image/jpeg",
25
+ ".jpeg" : "image/jpeg",
26
+ ".png" : "image/png",
27
+ ".webp" : "image/webp",
28
+ ".gif" : "image/gif",
29
+ ".pdf" : "application/pdf",
30
+ ".txt" : "text/plain",
31
+ ".json" : "application/json",
32
+ ".svg" : "image/svg+xml",
33
+ };
34
+
35
+ const buffer = fs.readFileSync(targetPath);
36
+
37
+ set.headers["Content-Type"] = mimeTypes[ext] || "application/octet-stream";
38
+ set.headers["Content-Length"] = buffer.length.toString();
39
+
40
+ return new Response(buffer);
41
+ } else {
42
+ const baseDir = path.resolve("storage", "private");
43
+ const targetPath = path.resolve(baseDir, requestedPath);
44
+
45
+ if (fs.existsSync(targetPath)) {
46
+ if (!user) {
47
+ set.status = 404
48
+ return { error: "File not found" };
49
+ }
50
+
51
+ if (!db) {
52
+ set.status = 404;
53
+ return { error: "File not found" };
54
+ }
55
+
56
+ const file = await db("storages").where({ path: requestedPath, disk: "private" }).first()
57
+
58
+ if (!file) {
59
+ set.status = 404
60
+ return { error: "File not found" }
61
+ }
62
+
63
+ let hasAccess = file.user_id === user.id
64
+
65
+ if (!hasAccess) {
66
+ hasAccess = await db("storage_permissions").where("storage_id", file.id)
67
+ .andWhere((q: any) => {
68
+ q.where("user_id", user.id)
69
+ .orWhere("role_id", user.role_id)
70
+ })
71
+ .first().then(Boolean)
72
+ }
73
+
74
+
75
+ if (!hasAccess) {
76
+ set.status = 404
77
+ return { error: "File not found" }
78
+ }
79
+
80
+ } else {
81
+ set.status = 404;
82
+ return { error: "File not found" };
83
+ }
84
+
85
+ const ext = path.extname(targetPath).toLowerCase();
86
+ const mimeTypes: Record<string, string> = {
87
+ ".jpg" : "image/jpeg",
88
+ ".jpeg" : "image/jpeg",
89
+ ".png" : "image/png",
90
+ ".webp" : "image/webp",
91
+ ".gif" : "image/gif",
92
+ ".pdf" : "application/pdf",
93
+ ".txt" : "text/plain",
94
+ ".json" : "application/json",
95
+ ".svg" : "image/svg+xml",
96
+ };
97
+
98
+ const buffer = fs.readFileSync(targetPath);
99
+
100
+ set.headers["Content-Type"] = mimeTypes[ext] || "application/octet-stream";
101
+ set.headers["Content-Length"] = buffer.length.toString();
102
+
103
+ return new Response(buffer);
104
+ }
105
+
106
+
107
+ });
@@ -0,0 +1 @@
1
+ export * from "./validation";
@@ -0,0 +1,346 @@
1
+ import validator from "validator"
2
+ import { db } from "@skalfa/skalfa-orm"
3
+
4
+ // ==========================>
5
+ // ## Validation: Rules of validation
6
+ // ==========================>
7
+
8
+ export type ValidationRule =
9
+ | "required"
10
+ | "string"
11
+ | "numeric"
12
+ | "number"
13
+ | "boolean"
14
+ | "email"
15
+ | "url"
16
+ | "date"
17
+ | "confirmed"
18
+ | "array"
19
+ | `min:`
20
+ | `min:${number}`
21
+ | `max:`
22
+ | `max:${number}`
23
+ | `between:`
24
+ | `between:${number},${number}`
25
+ | `in:`
26
+ | `in:${string}`
27
+ | `not_in:`
28
+ | `not_in:${string}`
29
+ | `same:`
30
+ | `same:${string}`
31
+ | `different:`
32
+ | `different:${string}`
33
+ | `regex:`
34
+ | `regex:${string}`
35
+ | `unique:`
36
+ | `unique:${string},${string}`
37
+ | `exists:`
38
+ | `exists:${string},${string}`
39
+
40
+ export type ValidationRules = Record<string, ValidationRule[] | string>
41
+
42
+ export interface ValidationResult {
43
+ valid : boolean
44
+ errors : Record<string, string[]>
45
+ }
46
+
47
+ // ==================================>
48
+ // ## Check validate field from rules
49
+ // ==================================>
50
+ export async function validate(
51
+ data: Record<string, any>,
52
+ rules: ValidationRules
53
+ ): Promise<ValidationResult> {
54
+ const errors: Record<string, string[]> = {}
55
+
56
+ for (const field in rules) {
57
+ const fieldRules = normalizeRules(rules[field])
58
+
59
+ if (field.includes("*")) {
60
+ // const [arrayPath, childPath] = field.split(".*.")
61
+ // const arr = getNestedValue(data, arrayPath)
62
+
63
+ // if (!Array.isArray(arr)) {
64
+ // addError(errors, arrayPath, `${arrayPath} harus berupa array`)
65
+ // continue
66
+ // }
67
+
68
+ // for (let i = 0; i < arr.length; i++) {
69
+ // const value = childPath
70
+ // ? getNestedValue(arr[i], childPath)
71
+ // : arr[i]
72
+
73
+ // const itemField = childPath
74
+ // ? `${arrayPath}.${i}.${childPath}`
75
+ // : `${arrayPath}.${i}`
76
+
77
+ // await checkRules({ field: itemField, value, rules: fieldRules, data, errors })
78
+ // }
79
+ const segments = field.split(".")
80
+
81
+ await nestedValidation({ value: data, segments, rules: fieldRules, fieldPath: "", data, errors })
82
+
83
+ continue
84
+ }
85
+
86
+
87
+ const value = getNestedValue(data, field) ?? ""
88
+
89
+ await checkRules({ field, value, rules: fieldRules, data, errors })
90
+ }
91
+
92
+ return {
93
+ valid: Object.keys(errors).length === 0,
94
+ errors
95
+ }
96
+ }
97
+
98
+
99
+ async function checkRules({ field, value, rules, data, errors } : { field: string, value: any, rules: ValidationRule[], data: any, errors: Record<string, string[]> }) {
100
+ for (const rule of rules) {
101
+ const [name, param] = rule.split(":") as [string, string | undefined]
102
+
103
+ switch (name) {
104
+ // === BASIC ===
105
+ case "required":
106
+ if (validator.isEmpty(String(value).trim())) {
107
+ addError(errors, field, `${field} wajib diisi`)
108
+ }
109
+ break
110
+
111
+ case "string":
112
+ case "text":
113
+ if (typeof value !== "string") {
114
+ addError(errors, field, `${field} harus berupa string`)
115
+ }
116
+ break
117
+
118
+ case "numeric":
119
+ case "number":
120
+ if (!validator.isNumeric(String(value))) {
121
+ addError(errors, field, `${field} harus berupa angka`)
122
+ }
123
+ break
124
+
125
+ case "boolean":
126
+ if (!(value === true || value === false || value === "true" || value === "false" || value === 1 || value === 0)) {
127
+ addError(errors, field, `${field} harus berupa boolean`)
128
+ }
129
+ break
130
+
131
+ case "email":
132
+ if (!validator.isEmail(String(value))) {
133
+ addError(errors, field, `${field} harus berupa email yang valid`)
134
+ }
135
+ break
136
+
137
+ case "url":
138
+ if (!validator.isURL(String(value))) {
139
+ addError(errors, field, `${field} harus berupa URL yang valid`)
140
+ }
141
+ break
142
+
143
+ case "date":
144
+ if (!validator.isDate(String(value))) {
145
+ addError(errors, field, `${field} harus berupa tanggal yang valid`)
146
+ }
147
+ break
148
+
149
+ // === LENGTH ===
150
+ case "min": {
151
+ const min = parseInt(param!)
152
+ if (!validator.isLength(String(value), { min })) {
153
+ addError(errors, field, `${field} minimal ${min} karakter`)
154
+ }
155
+ break
156
+ }
157
+
158
+ case "max": {
159
+ const max = parseInt(param!)
160
+ if (!validator.isLength(String(value), { max })) {
161
+ addError(errors, field, `${field} maksimal ${max} karakter`)
162
+ }
163
+ break
164
+ }
165
+
166
+ case "between": {
167
+ const [minVal, maxVal] = param!.split(",").map(Number)
168
+ if (!validator.isLength(String(value), { min: minVal, max: maxVal })) {
169
+ addError(errors, field, `${field} harus antara ${minVal} - ${maxVal} karakter`)
170
+ }
171
+ break
172
+ }
173
+
174
+ // === SET MEMBERSHIP ===
175
+ case "in": {
176
+ const allowed = param!.split(",")
177
+ if (!allowed.includes(String(value))) {
178
+ addError(errors, field, `${field} harus salah satu dari: ${allowed.join(", ")}`)
179
+ }
180
+ break
181
+ }
182
+
183
+ case "not_in": {
184
+ const notAllowed = param!.split(",")
185
+ if (notAllowed.includes(String(value))) {
186
+ addError(errors, field, `${field} tidak boleh salah satu dari: ${notAllowed.join(", ")}`)
187
+ }
188
+ break
189
+ }
190
+
191
+ case "array":
192
+ if (!Array.isArray(value)) {
193
+ addError(errors, field, `${field} harus berupa array`)
194
+ }
195
+ break
196
+
197
+ // === RELATIONAL ===
198
+ case "confirmed":
199
+ if (value !== getNestedValue(data, `${field}_confirmation`)) {
200
+ addError(errors, field, `${field} tidak sama dengan konfirmasi`)
201
+ }
202
+ break
203
+
204
+ case "same":
205
+ if (value !== getNestedValue(data, param!)) {
206
+ addError(errors, field, `${field} harus sama dengan ${param}`)
207
+ }
208
+ break
209
+
210
+ case "different":
211
+ if (value === getNestedValue(data, param!)) {
212
+ addError(errors, field, `${field} harus berbeda dengan ${param}`)
213
+ }
214
+ break
215
+
216
+ // === REGEX ===
217
+ case "regex":
218
+ try {
219
+ const pattern = new RegExp(param!)
220
+ if (!pattern.test(String(value))) {
221
+ addError(errors, field, `${field} tidak sesuai format`)
222
+ }
223
+ } catch {
224
+ addError(errors, field, `Regex rule untuk ${field} tidak valid`)
225
+ }
226
+ break
227
+
228
+ // === DATABASE VALIDATION ===
229
+ case "unique": {
230
+ if (!db) {
231
+ console.warn(`[Validation Warning] "unique" rule skipped on field "${field}" because database is not configured/installed.`);
232
+ break;
233
+ }
234
+ const [table, column, exceptId] = param!.split(",")
235
+ const query = db.table(table).where(column, value)
236
+ if (exceptId) query.whereNot("id", exceptId)
237
+ const existing = await query.first()
238
+ if (existing) {
239
+ addError(errors, field, `${field} sudah digunakan`)
240
+ }
241
+ break;
242
+ }
243
+
244
+ case "exists": {
245
+ if (!db) {
246
+ console.warn(`[Validation Warning] "exists" rule skipped on field "${field}" because database is not configured/installed.`);
247
+ break;
248
+ }
249
+ const [table, column] = param!.split(",")
250
+ const existing = await db.table(table).where(column, value).first()
251
+ if (!existing) {
252
+ addError(errors, field, `${field} tidak ditemukan di ${table}`)
253
+ }
254
+ break;
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+
261
+
262
+ async function nestedValidation({
263
+ value,
264
+ segments,
265
+ rules,
266
+ fieldPath,
267
+ data,
268
+ errors
269
+ }: {
270
+ value: any
271
+ segments: string[]
272
+ rules: ValidationRule[]
273
+ fieldPath: string
274
+ data: any
275
+ errors: Record<string, string[]>
276
+ }) {
277
+ if (segments.length === 0) {
278
+ await checkRules({
279
+ field: fieldPath,
280
+ value,
281
+ rules,
282
+ data,
283
+ errors
284
+ })
285
+ return
286
+ }
287
+
288
+ const [segment, ...rest] = segments
289
+
290
+ if (segment === "*") {
291
+ if (!Array.isArray(value)) {
292
+ addError(errors, fieldPath, `${fieldPath} harus berupa array`)
293
+ return
294
+ }
295
+
296
+ for (let i = 0; i < value.length; i++) {
297
+ await nestedValidation({
298
+ value: value[i],
299
+ segments: rest,
300
+ rules,
301
+ fieldPath: `${fieldPath}.${i}`,
302
+ data,
303
+ errors
304
+ })
305
+ }
306
+ } else {
307
+ await nestedValidation({
308
+ value: value?.[segment],
309
+ segments: rest,
310
+ rules,
311
+ fieldPath: fieldPath ? `${fieldPath}.${segment}` : segment,
312
+ data,
313
+ errors
314
+ })
315
+ }
316
+ }
317
+
318
+
319
+
320
+ // ==================================>
321
+ // ## Validation helpers
322
+ // ==================================>
323
+ function getNestedValue(obj: any, path: string): any {
324
+ if (!obj || typeof obj !== "object") return undefined
325
+
326
+ const normalizedPath = path
327
+ .replace(/\[(\w+)\]/g, '.$1')
328
+ .replace(/\['([^']+)'\]/g, '.$1')
329
+ .replace(/\["([^"]+)"\]/g, '.$1')
330
+
331
+ return normalizedPath.split('.').reduce((acc, key) => {
332
+ if (acc && Object.prototype.hasOwnProperty.call(acc, key)) {
333
+ return acc[key]
334
+ }
335
+ return undefined
336
+ }, obj)
337
+ }
338
+
339
+ function normalizeRules(rules: ValidationRule[] | string): ValidationRule[] {
340
+ if (Array.isArray(rules)) return rules
341
+ return rules.split("|") as ValidationRule[]
342
+ }
343
+
344
+ function addError(errors: Record<string, string[]>, field: string, message: string) {
345
+ errors[field] = [...(errors[field] || []), message]
346
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "ignoreDeprecations": "5.0",
4
+ "target": "ES2021",
5
+ "module": "ES2022",
6
+ "moduleResolution": "node",
7
+ "declaration": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "baseUrl": ".",
16
+ "types": ["bun-types", "elysia"],
17
+ "paths": {
18
+ "@utils": ["src/index.ts"],
19
+ "@utils/*": ["src/*"]
20
+ }
21
+ },
22
+ "include": ["src/**/*"]
23
+ }