arkos 2.0.0-next.13 → 2.0.0-next.16

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 (246) hide show
  1. package/README.md +144 -145
  2. package/cli.js +1 -0
  3. package/dist/cjs/app.js +7 -0
  4. package/dist/cjs/app.js.map +1 -1
  5. package/dist/cjs/exports/error-handler/index.js +15 -0
  6. package/dist/cjs/exports/error-handler/index.js.map +1 -1
  7. package/dist/cjs/modules/auth/auth.router.js +3 -0
  8. package/dist/cjs/modules/auth/auth.router.js.map +1 -1
  9. package/dist/cjs/modules/auth/auth.service.js +2 -0
  10. package/dist/cjs/modules/auth/auth.service.js.map +1 -1
  11. package/dist/cjs/modules/base/base.controller.js +15 -3
  12. package/dist/cjs/modules/base/base.controller.js.map +1 -1
  13. package/dist/cjs/modules/base/base.middlewares.js +19 -12
  14. package/dist/cjs/modules/base/base.middlewares.js.map +1 -1
  15. package/dist/cjs/modules/base/base.service.js +5 -1
  16. package/dist/cjs/modules/base/base.service.js.map +1 -1
  17. package/dist/cjs/modules/base/utils/helpers/base.service.helpers.js +9 -0
  18. package/dist/cjs/modules/base/utils/helpers/base.service.helpers.js.map +1 -1
  19. package/dist/cjs/modules/error-handler/error-handler.controller.js +25 -42
  20. package/dist/cjs/modules/error-handler/error-handler.controller.js.map +1 -1
  21. package/dist/cjs/modules/error-handler/utils/app-error.js +0 -1
  22. package/dist/cjs/modules/error-handler/utils/app-error.js.map +1 -1
  23. package/dist/cjs/modules/error-handler/utils/error-handler.helpers.js +8 -9
  24. package/dist/cjs/modules/error-handler/utils/error-handler.helpers.js.map +1 -1
  25. package/dist/cjs/modules/error-handler/utils/errors.js +158 -0
  26. package/dist/cjs/modules/error-handler/utils/errors.js.map +1 -0
  27. package/dist/cjs/modules/error-handler/utils/multer-error-handler.js +39 -0
  28. package/dist/cjs/modules/error-handler/utils/multer-error-handler.js.map +1 -0
  29. package/dist/cjs/modules/file-upload/file-upload.controller.js +10 -14
  30. package/dist/cjs/modules/file-upload/file-upload.controller.js.map +1 -1
  31. package/dist/cjs/modules/file-upload/file-upload.router.js +2 -0
  32. package/dist/cjs/modules/file-upload/file-upload.router.js.map +1 -1
  33. package/dist/cjs/modules/swagger/swagger.router.js +8 -2
  34. package/dist/cjs/modules/swagger/swagger.router.js.map +1 -1
  35. package/dist/cjs/modules/swagger/utils/get-open-api-login-html.js +18 -0
  36. package/dist/cjs/modules/swagger/utils/get-open-api-login-html.js.map +1 -1
  37. package/dist/cjs/modules/swagger/utils/helpers/get-swagger-default-configs.js +5 -5
  38. package/dist/cjs/modules/swagger/utils/helpers/get-swagger-default-configs.js.map +1 -1
  39. package/dist/cjs/modules/swagger/utils/helpers/openapi-schema-converter.js +1 -1
  40. package/dist/cjs/modules/swagger/utils/helpers/openapi-schema-converter.js.map +1 -1
  41. package/dist/cjs/types/arkos-prisma-input.js.map +1 -1
  42. package/dist/cjs/types/index.js.map +1 -1
  43. package/dist/cjs/types/new-arkos-config.js.map +1 -1
  44. package/dist/cjs/types/router-config.js.map +1 -1
  45. package/dist/cjs/utils/arkos-router/arkos-router-openapi-manager.js +86 -28
  46. package/dist/cjs/utils/arkos-router/arkos-router-openapi-manager.js.map +1 -1
  47. package/dist/cjs/utils/arkos-router/index.js +11 -7
  48. package/dist/cjs/utils/arkos-router/index.js.map +1 -1
  49. package/dist/cjs/utils/arkos-router/types/index.js.map +1 -1
  50. package/dist/cjs/utils/arkos-router/types/upload-config.js.map +1 -1
  51. package/dist/cjs/utils/arkos-router/utils/helpers/apply-arkos-router-proxy.js +34 -28
  52. package/dist/cjs/utils/arkos-router/utils/helpers/apply-arkos-router-proxy.js.map +1 -1
  53. package/dist/cjs/utils/arkos-router/utils/helpers/index.js +9 -6
  54. package/dist/cjs/utils/arkos-router/utils/helpers/index.js.map +1 -1
  55. package/dist/cjs/utils/arkos-router/utils/helpers/upload-manager.js +334 -77
  56. package/dist/cjs/utils/arkos-router/utils/helpers/upload-manager.js.map +1 -1
  57. package/dist/cjs/utils/bundler.js.map +1 -1
  58. package/dist/cjs/utils/cli/build.js +2 -3
  59. package/dist/cjs/utils/cli/build.js.map +1 -1
  60. package/dist/cjs/utils/cli/dev.js +11 -6
  61. package/dist/cjs/utils/cli/dev.js.map +1 -1
  62. package/dist/cjs/utils/cli/export-auth-action.js +5 -4
  63. package/dist/cjs/utils/cli/export-auth-action.js.map +1 -1
  64. package/dist/cjs/utils/cli/generate.js +6 -8
  65. package/dist/cjs/utils/cli/generate.js.map +1 -1
  66. package/dist/cjs/utils/cli/index.js +22 -19
  67. package/dist/cjs/utils/cli/index.js.map +1 -1
  68. package/dist/cjs/utils/cli/start.js +4 -2
  69. package/dist/cjs/utils/cli/start.js.map +1 -1
  70. package/dist/cjs/utils/cli/utils/cli.helpers.js +1 -1
  71. package/dist/cjs/utils/cli/utils/template-generator/templates/generate-controller-template.js +19 -7
  72. package/dist/cjs/utils/cli/utils/template-generator/templates/generate-controller-template.js.map +1 -1
  73. package/dist/cjs/utils/cli/utils/template-generator/templates/generate-multiple-components.js +7 -6
  74. package/dist/cjs/utils/cli/utils/template-generator/templates/generate-multiple-components.js.map +1 -1
  75. package/dist/cjs/utils/cli/utils/template-generator/templates/policy-template.js +4 -4
  76. package/dist/cjs/utils/cli/utils/template-generator/templates/policy-template.js.map +1 -1
  77. package/dist/cjs/utils/cli/utils/template-generators.js +0 -6
  78. package/dist/cjs/utils/cli/utils/template-generators.js.map +1 -1
  79. package/dist/cjs/utils/define-config.js +5 -0
  80. package/dist/cjs/utils/define-config.js.map +1 -1
  81. package/dist/cjs/utils/dotenv.helpers.js +0 -6
  82. package/dist/cjs/utils/dotenv.helpers.js.map +1 -1
  83. package/dist/cjs/utils/features/api.features.js +23 -5
  84. package/dist/cjs/utils/features/api.features.js.map +1 -1
  85. package/dist/cjs/utils/helpers/arkos-config.helpers.js +22 -2
  86. package/dist/cjs/utils/helpers/arkos-config.helpers.js.map +1 -1
  87. package/dist/cjs/utils/helpers/exit-error.js +1 -0
  88. package/dist/cjs/utils/helpers/exit-error.js.map +1 -1
  89. package/dist/cjs/utils/helpers/fs.helpers.js +25 -24
  90. package/dist/cjs/utils/helpers/fs.helpers.js.map +1 -1
  91. package/dist/cjs/utils/helpers/global.helpers.js +3 -2
  92. package/dist/cjs/utils/helpers/global.helpers.js.map +1 -1
  93. package/dist/cjs/utils/helpers/prisma.helpers.js +4 -5
  94. package/dist/cjs/utils/helpers/prisma.helpers.js.map +1 -1
  95. package/dist/cjs/utils/helpers/url-helpers.js +14 -0
  96. package/dist/cjs/utils/helpers/url-helpers.js.map +1 -0
  97. package/dist/cjs/utils/initialize-app.js +35 -6
  98. package/dist/cjs/utils/initialize-app.js.map +1 -1
  99. package/dist/cjs/utils/prisma/prisma-json-schema-generator.js +12 -6
  100. package/dist/cjs/utils/prisma/prisma-json-schema-generator.js.map +1 -1
  101. package/dist/cjs/utils/prisma/prisma-schema-parser.js +10 -3
  102. package/dist/cjs/utils/prisma/prisma-schema-parser.js.map +1 -1
  103. package/dist/cjs/utils/setup-app.js +58 -41
  104. package/dist/cjs/utils/setup-app.js.map +1 -1
  105. package/dist/esm/app.js +6 -0
  106. package/dist/esm/app.js.map +1 -1
  107. package/dist/esm/exports/error-handler/index.js +1 -0
  108. package/dist/esm/exports/error-handler/index.js.map +1 -1
  109. package/dist/esm/modules/auth/auth.router.js +3 -0
  110. package/dist/esm/modules/auth/auth.router.js.map +1 -1
  111. package/dist/esm/modules/auth/auth.service.js +2 -0
  112. package/dist/esm/modules/auth/auth.service.js.map +1 -1
  113. package/dist/esm/modules/base/base.controller.js +15 -3
  114. package/dist/esm/modules/base/base.controller.js.map +1 -1
  115. package/dist/esm/modules/base/base.middlewares.js +19 -12
  116. package/dist/esm/modules/base/base.middlewares.js.map +1 -1
  117. package/dist/esm/modules/base/base.service.js +5 -1
  118. package/dist/esm/modules/base/base.service.js.map +1 -1
  119. package/dist/esm/modules/base/utils/helpers/base.service.helpers.js +9 -0
  120. package/dist/esm/modules/base/utils/helpers/base.service.helpers.js.map +1 -1
  121. package/dist/esm/modules/error-handler/error-handler.controller.js +22 -42
  122. package/dist/esm/modules/error-handler/error-handler.controller.js.map +1 -1
  123. package/dist/esm/modules/error-handler/utils/app-error.js +0 -1
  124. package/dist/esm/modules/error-handler/utils/app-error.js.map +1 -1
  125. package/dist/esm/modules/error-handler/utils/error-handler.helpers.js +8 -9
  126. package/dist/esm/modules/error-handler/utils/error-handler.helpers.js.map +1 -1
  127. package/dist/esm/modules/error-handler/utils/errors.js +127 -0
  128. package/dist/esm/modules/error-handler/utils/errors.js.map +1 -0
  129. package/dist/esm/modules/error-handler/utils/multer-error-handler.js +34 -0
  130. package/dist/esm/modules/error-handler/utils/multer-error-handler.js.map +1 -0
  131. package/dist/esm/modules/file-upload/file-upload.controller.js +10 -14
  132. package/dist/esm/modules/file-upload/file-upload.controller.js.map +1 -1
  133. package/dist/esm/modules/file-upload/file-upload.router.js +2 -0
  134. package/dist/esm/modules/file-upload/file-upload.router.js.map +1 -1
  135. package/dist/esm/modules/swagger/swagger.router.js +8 -2
  136. package/dist/esm/modules/swagger/swagger.router.js.map +1 -1
  137. package/dist/esm/modules/swagger/utils/get-open-api-login-html.js +18 -0
  138. package/dist/esm/modules/swagger/utils/get-open-api-login-html.js.map +1 -1
  139. package/dist/esm/modules/swagger/utils/helpers/get-swagger-default-configs.js +5 -5
  140. package/dist/esm/modules/swagger/utils/helpers/get-swagger-default-configs.js.map +1 -1
  141. package/dist/esm/modules/swagger/utils/helpers/openapi-schema-converter.js +1 -1
  142. package/dist/esm/modules/swagger/utils/helpers/openapi-schema-converter.js.map +1 -1
  143. package/dist/esm/types/arkos-prisma-input.js.map +1 -1
  144. package/dist/esm/types/index.js.map +1 -1
  145. package/dist/esm/types/new-arkos-config.js.map +1 -1
  146. package/dist/esm/types/router-config.js.map +1 -1
  147. package/dist/esm/utils/arkos-router/arkos-router-openapi-manager.js +86 -28
  148. package/dist/esm/utils/arkos-router/arkos-router-openapi-manager.js.map +1 -1
  149. package/dist/esm/utils/arkos-router/index.js +11 -7
  150. package/dist/esm/utils/arkos-router/index.js.map +1 -1
  151. package/dist/esm/utils/arkos-router/types/index.js.map +1 -1
  152. package/dist/esm/utils/arkos-router/types/upload-config.js.map +1 -1
  153. package/dist/esm/utils/arkos-router/utils/helpers/apply-arkos-router-proxy.js +34 -28
  154. package/dist/esm/utils/arkos-router/utils/helpers/apply-arkos-router-proxy.js.map +1 -1
  155. package/dist/esm/utils/arkos-router/utils/helpers/index.js +9 -6
  156. package/dist/esm/utils/arkos-router/utils/helpers/index.js.map +1 -1
  157. package/dist/esm/utils/arkos-router/utils/helpers/upload-manager.js +334 -77
  158. package/dist/esm/utils/arkos-router/utils/helpers/upload-manager.js.map +1 -1
  159. package/dist/esm/utils/bundler.js.map +1 -1
  160. package/dist/esm/utils/cli/build.js +3 -4
  161. package/dist/esm/utils/cli/build.js.map +1 -1
  162. package/dist/esm/utils/cli/dev.js +12 -7
  163. package/dist/esm/utils/cli/dev.js.map +1 -1
  164. package/dist/esm/utils/cli/export-auth-action.js +5 -4
  165. package/dist/esm/utils/cli/export-auth-action.js.map +1 -1
  166. package/dist/esm/utils/cli/generate.js +6 -8
  167. package/dist/esm/utils/cli/generate.js.map +1 -1
  168. package/dist/esm/utils/cli/index.js +22 -19
  169. package/dist/esm/utils/cli/index.js.map +1 -1
  170. package/dist/esm/utils/cli/start.js +4 -2
  171. package/dist/esm/utils/cli/start.js.map +1 -1
  172. package/dist/esm/utils/cli/utils/cli.helpers.js +1 -1
  173. package/dist/esm/utils/cli/utils/template-generator/templates/generate-controller-template.js +16 -7
  174. package/dist/esm/utils/cli/utils/template-generator/templates/generate-controller-template.js.map +1 -1
  175. package/dist/esm/utils/cli/utils/template-generator/templates/generate-multiple-components.js +7 -6
  176. package/dist/esm/utils/cli/utils/template-generator/templates/generate-multiple-components.js.map +1 -1
  177. package/dist/esm/utils/cli/utils/template-generator/templates/policy-template.js +4 -4
  178. package/dist/esm/utils/cli/utils/template-generator/templates/policy-template.js.map +1 -1
  179. package/dist/esm/utils/cli/utils/template-generators.js +0 -6
  180. package/dist/esm/utils/cli/utils/template-generators.js.map +1 -1
  181. package/dist/esm/utils/define-config.js +5 -0
  182. package/dist/esm/utils/define-config.js.map +1 -1
  183. package/dist/esm/utils/dotenv.helpers.js +0 -6
  184. package/dist/esm/utils/dotenv.helpers.js.map +1 -1
  185. package/dist/esm/utils/features/api.features.js +23 -5
  186. package/dist/esm/utils/features/api.features.js.map +1 -1
  187. package/dist/esm/utils/helpers/arkos-config.helpers.js +20 -2
  188. package/dist/esm/utils/helpers/arkos-config.helpers.js.map +1 -1
  189. package/dist/esm/utils/helpers/exit-error.js +1 -0
  190. package/dist/esm/utils/helpers/exit-error.js.map +1 -1
  191. package/dist/esm/utils/helpers/fs.helpers.js +25 -24
  192. package/dist/esm/utils/helpers/fs.helpers.js.map +1 -1
  193. package/dist/esm/utils/helpers/global.helpers.js +1 -1
  194. package/dist/esm/utils/helpers/global.helpers.js.map +1 -1
  195. package/dist/esm/utils/helpers/prisma.helpers.js +4 -5
  196. package/dist/esm/utils/helpers/prisma.helpers.js.map +1 -1
  197. package/dist/esm/utils/helpers/url-helpers.js +11 -0
  198. package/dist/esm/utils/helpers/url-helpers.js.map +1 -0
  199. package/dist/esm/utils/initialize-app.js +35 -6
  200. package/dist/esm/utils/initialize-app.js.map +1 -1
  201. package/dist/esm/utils/prisma/prisma-json-schema-generator.js +12 -6
  202. package/dist/esm/utils/prisma/prisma-json-schema-generator.js.map +1 -1
  203. package/dist/esm/utils/prisma/prisma-schema-parser.js +10 -3
  204. package/dist/esm/utils/prisma/prisma-schema-parser.js.map +1 -1
  205. package/dist/esm/utils/setup-app.js +59 -42
  206. package/dist/esm/utils/setup-app.js.map +1 -1
  207. package/dist/types/app.d.ts +5 -6
  208. package/dist/types/exports/error-handler/index.d.ts +1 -0
  209. package/dist/types/modules/auth/auth.service.d.ts +2 -6
  210. package/dist/types/modules/base/base.service.d.ts +2 -1
  211. package/dist/types/modules/error-handler/utils/app-error.d.ts +0 -2
  212. package/dist/types/modules/error-handler/utils/error-handler.helpers.d.ts +1 -1
  213. package/dist/types/modules/error-handler/utils/errors.d.ts +176 -0
  214. package/dist/types/modules/error-handler/utils/multer-error-handler.d.ts +7 -0
  215. package/dist/types/modules/file-upload/file-upload.controller.d.ts +0 -1
  216. package/dist/types/modules/swagger/utils/helpers/get-swagger-default-configs.d.ts +48 -2
  217. package/dist/types/types/arkos-prisma-input.d.ts +3 -2
  218. package/dist/types/types/index.d.ts +0 -21
  219. package/dist/types/types/new-arkos-config.d.ts +183 -14
  220. package/dist/types/types/router-config.d.ts +1 -1
  221. package/dist/types/utils/arkos-router/arkos-router-openapi-manager.d.ts +14 -1
  222. package/dist/types/utils/arkos-router/index.d.ts +76 -8
  223. package/dist/types/utils/arkos-router/types/index.d.ts +19 -6
  224. package/dist/types/utils/arkos-router/types/upload-config.d.ts +63 -7
  225. package/dist/types/utils/arkos-router/utils/helpers/apply-arkos-router-proxy.d.ts +1 -1
  226. package/dist/types/utils/arkos-router/utils/helpers/index.d.ts +2 -0
  227. package/dist/types/utils/arkos-router/utils/helpers/upload-manager.d.ts +0 -36
  228. package/dist/types/utils/bundler.d.ts +1 -1
  229. package/dist/types/utils/cli/generate.d.ts +0 -1
  230. package/dist/types/utils/cli/start.d.ts +1 -1
  231. package/dist/types/utils/helpers/arkos-config.helpers.d.ts +2 -0
  232. package/dist/types/utils/helpers/fs.helpers.d.ts +1 -1
  233. package/dist/types/utils/helpers/global.helpers.d.ts +1 -0
  234. package/dist/types/utils/helpers/url-helpers.d.ts +1 -0
  235. package/dist/types/utils/prisma/prisma-schema-parser.d.ts +1 -0
  236. package/package.json +15 -15
  237. package/dist/cjs/utils/cli/utils/template-generator/templates/route-hook.template.js +0 -39
  238. package/dist/cjs/utils/cli/utils/template-generator/templates/route-hook.template.js.map +0 -1
  239. package/dist/cjs/utils/cli/utils/template-generator/templates/service-hook.template.js +0 -32
  240. package/dist/cjs/utils/cli/utils/template-generator/templates/service-hook.template.js.map +0 -1
  241. package/dist/esm/utils/cli/utils/template-generator/templates/route-hook.template.js +0 -36
  242. package/dist/esm/utils/cli/utils/template-generator/templates/route-hook.template.js.map +0 -1
  243. package/dist/esm/utils/cli/utils/template-generator/templates/service-hook.template.js +0 -29
  244. package/dist/esm/utils/cli/utils/template-generator/templates/service-hook.template.js.map +0 -1
  245. package/dist/types/utils/cli/utils/template-generator/templates/route-hook.template.d.ts +0 -2
  246. package/dist/types/utils/cli/utils/template-generator/templates/service-hook.template.d.ts +0 -2
@@ -15,7 +15,6 @@ const app_error_1 = __importDefault(require("../../../../modules/error-handler/u
15
15
  const file_upload_helpers_1 = require("../../../../modules/file-upload/utils/helpers/file-upload.helpers.js");
16
16
  const deepmerge_helper_1 = __importDefault(require("../../../helpers/deepmerge.helper.js"));
17
17
  const error_handler_1 = require("../../../../exports/error-handler/index.js");
18
- const change_case_helpers_1 = require("../../../helpers/change-case.helpers.js");
19
18
  function determineUploadDir(file) {
20
19
  if (file.mimetype.includes?.("image"))
21
20
  return "/images";
@@ -46,6 +45,112 @@ const storage = multer_1.default.diskStorage({
46
45
  cb(null, `${file.originalname.replace(ext, "")}-${uniqueSuffix}${ext}`);
47
46
  },
48
47
  });
48
+ function isNestedArrayPath(fieldPath) {
49
+ return fieldPath.includes("[]");
50
+ }
51
+ function buildPathMatcher(pattern) {
52
+ const escaped = pattern
53
+ .replace(/\[]/g, "\x00")
54
+ .replace(/[\[\]]/g, "\\$&")
55
+ .replace(/\x00/g, "\\[\\d+\\]");
56
+ return new RegExp(`^${escaped}$`);
57
+ }
58
+ function extractIndex(segment) {
59
+ return parseInt(segment.replace(/\[|\]/g, ""), 10);
60
+ }
61
+ function extractIndices(key, pattern) {
62
+ const patternParts = pattern.match(/\[[^\]]*\]/g) || [];
63
+ const keyParts = key.match(/\[[^\]]*\]/g) || [];
64
+ const indices = [];
65
+ for (let i = 0; i < patternParts.length; i++) {
66
+ if (patternParts[i] === "[]") {
67
+ indices.push(extractIndex(keyParts[i]));
68
+ }
69
+ }
70
+ return indices;
71
+ }
72
+ function groupFilesByPattern(files, pattern) {
73
+ const matcher = buildPathMatcher(pattern);
74
+ const groups = new Map();
75
+ for (const key of Object.keys(files)) {
76
+ if (!matcher.test(key))
77
+ continue;
78
+ const indices = extractIndices(key, pattern);
79
+ const groupKey = indices.join(",");
80
+ if (!groups.has(groupKey))
81
+ groups.set(groupKey, []);
82
+ groups.get(groupKey).push(...files[key]);
83
+ }
84
+ return groups;
85
+ }
86
+ function validateFileConstraints(file, allowedFileTypes, maxSize) {
87
+ if (maxSize && file.size > maxSize) {
88
+ return `File '${file.originalname}' exceeds the maximum allowed size of ${maxSize} bytes`;
89
+ }
90
+ if (allowedFileTypes) {
91
+ const ext = path_1.default.extname(file.originalname).toLowerCase();
92
+ const allowed = Array.isArray(allowedFileTypes)
93
+ ? allowedFileTypes.includes(ext)
94
+ : allowedFileTypes.test(ext);
95
+ if (!allowed) {
96
+ return `File type '${ext}' is not allowed for '${file.originalname}'`;
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+ function flattenFieldsForMulter(fields) {
102
+ return fields.map((field) => ({
103
+ name: field.name,
104
+ maxCount: "type" in field && field.type === "single" ? 1 : (field.maxCount ?? 5),
105
+ }));
106
+ }
107
+ function configHasNestedArrayPaths(config) {
108
+ if (config.type === "single" || config.type === "array") {
109
+ return isNestedArrayPath(config.field);
110
+ }
111
+ if (config.type === "fields") {
112
+ return config.fields.some((f) => isNestedArrayPath(f.name));
113
+ }
114
+ return false;
115
+ }
116
+ function resolveFieldEntry(field, config = {}) {
117
+ const isLegacy = !("type" in field) || field.type === undefined;
118
+ if (isLegacy) {
119
+ return {
120
+ ...config,
121
+ name: field.name,
122
+ type: "array",
123
+ required: !!config.required,
124
+ minCount: field.minCount,
125
+ maxCount: field.maxCount,
126
+ allowedFileTypes: config.allowedFileTypes,
127
+ maxSize: config.maxSize,
128
+ attachToBody: config.attachToBody,
129
+ };
130
+ }
131
+ if (field.type === "single") {
132
+ return {
133
+ name: field.name,
134
+ type: "single",
135
+ required: field.required !== false,
136
+ minCount: undefined,
137
+ maxCount: undefined,
138
+ allowedFileTypes: field.allowedFileTypes,
139
+ maxSize: field.maxSize,
140
+ attachToBody: field.attachToBody,
141
+ };
142
+ }
143
+ return {
144
+ name: field.name,
145
+ type: "array",
146
+ required: field.required !== false,
147
+ minCount: field.minCount,
148
+ maxCount: field.maxCount,
149
+ allowedFileTypes: field.allowedFileTypes,
150
+ maxSize: field.maxSize,
151
+ attachToBody: field.attachToBody,
152
+ };
153
+ }
49
154
  class UploadManager {
50
155
  constructor() {
51
156
  this.fileFilter = (_, file, cb, allowedFileTypes) => {
@@ -69,22 +174,42 @@ class UploadManager {
69
174
  });
70
175
  }
71
176
  getMiddleware(config) {
72
- let upload;
73
- if (config.type === "single")
74
- upload = this.getUpload(config)[config.type](config.field);
75
- else if (config.type === "array")
76
- upload = this.getUpload(config)[config.type](config.field, config.maxCount || 5);
77
- else
78
- upload = this.getUpload(config)[config.type](config.fields);
79
- return upload;
177
+ if (configHasNestedArrayPaths(config)) {
178
+ return (0, multer_1.default)({ storage }).any();
179
+ }
180
+ if (config.type === "single") {
181
+ return this.getUpload(config).single(config.field);
182
+ }
183
+ else if (config.type === "array") {
184
+ return this.getUpload(config).array(config.field, config.maxCount ?? 5);
185
+ }
186
+ else {
187
+ const multerOptions = "maxSize" in config || "allowedFileTypes" in config
188
+ ? {
189
+ maxSize: config.maxSize,
190
+ allowedFileTypes: config.allowedFileTypes,
191
+ }
192
+ : {};
193
+ return this.getUpload(multerOptions).fields(flattenFieldsForMulter(config.fields));
194
+ }
80
195
  }
81
196
  handleUpload(config, oldFilePath) {
82
197
  return (0, error_handler_1.catchAsync)((req, res, next) => {
83
198
  const middleware = this.getMiddleware(config);
84
- req.headers["x-upload-dir"] = config.uploadDir;
199
+ req.headers["x-upload-dir"] =
200
+ "uploadDir" in config ? config.uploadDir : undefined;
85
201
  middleware(req, res, async (err) => {
86
202
  if (err)
87
203
  return next(err);
204
+ if (configHasNestedArrayPaths(config) && Array.isArray(req.files)) {
205
+ const normalized = {};
206
+ for (const file of req.files) {
207
+ if (!normalized[file.fieldname])
208
+ normalized[file.fieldname] = [];
209
+ normalized[file.fieldname].push(file);
210
+ }
211
+ req.files = normalized;
212
+ }
88
213
  if (oldFilePath) {
89
214
  const { fileUpload: configs } = (0, exports_1.getArkosConfig)();
90
215
  const filePath = path_1.default.resolve(process.cwd(), (0, text_helpers_1.removeBothSlashes)(configs?.baseUploadDir), (0, text_helpers_1.removeBothSlashes)(oldFilePath));
@@ -105,43 +230,98 @@ class UploadManager {
105
230
  return (0, error_handler_1.catchAsync)((req, _, next) => {
106
231
  const errors = [];
107
232
  const errorCodes = [];
108
- if (uploadConfig.required === false)
109
- return next();
110
- if (uploadConfig.type === "single") {
111
- if (!req.file) {
112
- errors.push(`Required upload field '${uploadConfig.field}' is missing`);
113
- errorCodes.push(uploadConfig.field);
233
+ const addError = (msg, code) => {
234
+ errors.push(msg);
235
+ errorCodes.push(code);
236
+ };
237
+ const validateField = (field, type, required, minCount, maxCount, allowedFileTypes, maxSize) => {
238
+ const isNested = isNestedArrayPath(field);
239
+ if (isNested) {
240
+ const filesObj = req.files;
241
+ if (!filesObj || Array.isArray(filesObj)) {
242
+ if (required)
243
+ addError(`Required field '${field}' is missing`, field);
244
+ return;
245
+ }
246
+ const groups = groupFilesByPattern(filesObj, field);
247
+ if (groups.size === 0) {
248
+ if (required)
249
+ addError(`Required field '${field}' is missing`, field);
250
+ return;
251
+ }
252
+ for (const [groupKey, groupFiles] of groups) {
253
+ const label = `'${field}' (group ${groupKey})`;
254
+ if (type === "single" && groupFiles.length !== 1) {
255
+ addError(`Field ${label} must have exactly 1 file`, field);
256
+ }
257
+ if (type === "array") {
258
+ if (minCount && groupFiles.length < minCount) {
259
+ addError(`Field ${label} requires at least ${minCount} files, got ${groupFiles.length}`, field);
260
+ }
261
+ if (maxCount && groupFiles.length > maxCount) {
262
+ addError(`Field ${label} allows at most ${maxCount} files, got ${groupFiles.length}`, field);
263
+ }
264
+ }
265
+ for (const file of groupFiles) {
266
+ const err = validateFileConstraints(file, allowedFileTypes, maxSize);
267
+ if (err)
268
+ addError(err, field);
269
+ }
270
+ }
271
+ }
272
+ else {
273
+ if (type === "single") {
274
+ if (required && !req.file) {
275
+ addError(`Required upload field '${field}' is missing`, field);
276
+ }
277
+ }
278
+ else {
279
+ const filesObj = req.files;
280
+ const files = Array.isArray(req.files)
281
+ ? req.files
282
+ : filesObj?.[field];
283
+ if (!files || files.length === 0) {
284
+ if (required)
285
+ addError(`Required upload field '${field}' is missing or empty`, field);
286
+ }
287
+ else if (!Array.isArray(files)) {
288
+ if (required)
289
+ addError(`Malformed upload field '${field}'`, field);
290
+ }
291
+ else {
292
+ if (minCount && files.length < minCount) {
293
+ addError(`Field '${field}' requires at least ${minCount} files, got ${files.length}`, field);
294
+ }
295
+ }
296
+ }
114
297
  }
298
+ };
299
+ if (uploadConfig.type === "single") {
300
+ validateField(uploadConfig.field, "single", uploadConfig.required !== false, undefined, undefined, isNestedArrayPath(uploadConfig.field)
301
+ ? uploadConfig.allowedFileTypes
302
+ : undefined, isNestedArrayPath(uploadConfig.field)
303
+ ? uploadConfig.maxSize
304
+ : undefined);
115
305
  }
116
306
  else if (uploadConfig.type === "array") {
117
- if (!req.files || !Array.isArray(req.files) || req.files.length === 0) {
118
- errors.push(`Required upload field '${uploadConfig.field}' is missing or empty`);
119
- errorCodes.push(uploadConfig.field);
120
- }
307
+ validateField(uploadConfig.field, "array", uploadConfig.required !== false, uploadConfig.minCount, uploadConfig.maxCount, isNestedArrayPath(uploadConfig.field)
308
+ ? uploadConfig.allowedFileTypes
309
+ : undefined, isNestedArrayPath(uploadConfig.field)
310
+ ? uploadConfig.maxSize
311
+ : undefined);
121
312
  }
122
313
  else if (uploadConfig.type === "fields") {
123
- if (!req.files ||
124
- typeof req.files !== "object" ||
125
- Array.isArray(req.files)) {
126
- errors.push(`Required upload fields are missing. Expected an object with fields: ${uploadConfig.fields.map((f) => f.name).join(", ")}`);
127
- errorCodes.push(...uploadConfig.fields.map((f) => f.name));
128
- }
129
- else {
130
- for (const field of uploadConfig.fields) {
131
- const filesForField = req.files[field.name];
132
- if (!filesForField ||
133
- !Array.isArray(filesForField) ||
134
- filesForField.length === 0) {
135
- errors.push(`Required upload field '${field.name}' is missing or empty`);
136
- errorCodes.push(field.name);
137
- }
138
- }
314
+ for (const fieldEntry of uploadConfig.fields) {
315
+ const resolved = resolveFieldEntry(fieldEntry, uploadConfig);
316
+ validateField(resolved.name, resolved.type, resolved.required, resolved.minCount, resolved.maxCount, isNestedArrayPath(resolved.name)
317
+ ? resolved.allowedFileTypes
318
+ : undefined, isNestedArrayPath(resolved.name) ? resolved.maxSize : undefined);
139
319
  }
140
320
  }
141
321
  if (errors.length > 0) {
142
- throw new app_error_1.default(errors[0], 400, `Missing${(0, change_case_helpers_1.pascalCase)(errorCodes[0]).replaceAll("_", "")}FileField`, {
143
- errors,
144
- });
322
+ throw new app_error_1.default(errors[0], 400, uploadConfig.type !== "single"
323
+ ? `MissingUploadFields`
324
+ : `MissingUploadField`, { errors });
145
325
  }
146
326
  next();
147
327
  });
@@ -161,7 +341,8 @@ class UploadManager {
161
341
  await deleteFile(req.file);
162
342
  delete req.file;
163
343
  }
164
- else if (config.type === "array" && Array.isArray(req.files)) {
344
+ else if ((config.type === "array" || configHasNestedArrayPaths(config)) &&
345
+ Array.isArray(req.files)) {
165
346
  for (const file of req.files) {
166
347
  await deleteFile(file);
167
348
  }
@@ -201,23 +382,21 @@ class UploadManager {
201
382
  const relativePath = (0, file_upload_helpers_1.generateRelativePath)(file.path, req.headers["x-upload-dir"]);
202
383
  return `${baseURL}${baseRoute === "/" ? "" : baseRoute}${relativePath.startsWith("/") ? relativePath : `/${relativePath}`}`;
203
384
  };
204
- const getAttachValue = (file) => {
385
+ const getAttachValue = (file, attachToBody) => {
205
386
  const url = buildFileURL(file);
206
387
  file.url = url;
207
388
  file.pathname = normalizePath(file.path);
208
- if (config.attachToBody === false)
389
+ if (attachToBody === false)
209
390
  return undefined;
210
- if (config.attachToBody === "pathname" || !config.attachToBody)
211
- return ((baseRoute === "/"
212
- ? ""
213
- : baseRoute.startsWith("/")
214
- ? baseRoute
215
- : `/${baseRoute}`) + normalizePath(file.path));
216
- if (config.attachToBody === "url")
391
+ if (attachToBody === "url")
217
392
  return url;
218
- if (config.attachToBody === "file")
393
+ if (attachToBody === "file")
219
394
  return file;
220
- return undefined;
395
+ return ((baseRoute === "/"
396
+ ? ""
397
+ : baseRoute.startsWith("/")
398
+ ? baseRoute
399
+ : `/${baseRoute}`) + normalizePath(file.path));
221
400
  };
222
401
  const setNestedValue = (obj, path, value) => {
223
402
  const keys = path.match(/[^\[\]]+/g) || [];
@@ -233,41 +412,119 @@ class UploadManager {
233
412
  const lastKey = keys[keys.length - 1];
234
413
  current[lastKey] = value;
235
414
  };
236
- try {
237
- if (config.type === "single" && req.file) {
238
- const value = getAttachValue(req.file);
239
- if (value !== undefined && config.field) {
240
- const bodyUpdate = {};
241
- setNestedValue(bodyUpdate, config.field, value);
242
- req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
415
+ const reconstructNestedArrayPath = (pattern, filesObj, attachToBody, type, sharedBodyUpdate) => {
416
+ const groups = groupFilesByPattern(filesObj, pattern);
417
+ if (groups.size === 0)
418
+ return;
419
+ const segments = pattern.match(/[^\[\]]+|\[\]/g) || [];
420
+ const sortedGroups = [...groups.entries()].sort((a, b) => {
421
+ const ai = a[0].split(",").map(Number);
422
+ const bi = b[0].split(",").map(Number);
423
+ for (let i = 0; i < Math.min(ai.length, bi.length); i++) {
424
+ if (ai[i] !== bi[i])
425
+ return ai[i] - bi[i];
243
426
  }
244
- }
245
- else if (config.type === "array" && Array.isArray(req.files)) {
246
- const values = req.files
247
- .map((file) => getAttachValue(file))
427
+ return 0;
428
+ });
429
+ const bodyUpdate = sharedBodyUpdate ?? {};
430
+ for (const [groupKey, groupFiles] of sortedGroups) {
431
+ const indices = groupKey.split(",").map(Number);
432
+ let indexCounter = 0;
433
+ const concretePath = segments
434
+ .map((seg) => {
435
+ if (seg === "[]")
436
+ return `[${indices[indexCounter++]}]`;
437
+ return seg;
438
+ })
439
+ .reduce((acc, seg, i) => {
440
+ if (i === 0)
441
+ return seg;
442
+ if (seg.startsWith("["))
443
+ return acc + seg;
444
+ return acc + `[${seg}]`;
445
+ }, "");
446
+ const values = groupFiles
447
+ .map((f) => getAttachValue(f, attachToBody))
248
448
  .filter((v) => v !== undefined);
249
- if (values.length > 0 && config.field) {
250
- const bodyUpdate = {};
251
- setNestedValue(bodyUpdate, config.field, values);
252
- req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
449
+ if (values.length === 0)
450
+ continue;
451
+ const valueToSet = type === "single" ? values[0] : values;
452
+ setNestedValue(bodyUpdate, concretePath, valueToSet);
453
+ }
454
+ if (!sharedBodyUpdate) {
455
+ req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
456
+ }
457
+ };
458
+ try {
459
+ if (config.type === "single") {
460
+ if (isNestedArrayPath(config.field)) {
461
+ const filesObj = req.files;
462
+ if (filesObj && !Array.isArray(filesObj)) {
463
+ reconstructNestedArrayPath(config.field, filesObj, config.attachToBody, "single");
464
+ }
465
+ }
466
+ else if (req.file) {
467
+ const value = getAttachValue(req.file, config.attachToBody);
468
+ if (value !== undefined) {
469
+ const bodyUpdate = {};
470
+ setNestedValue(bodyUpdate, config.field, value);
471
+ req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
472
+ }
253
473
  }
254
474
  }
255
- else if (config.type === "fields" &&
256
- req.files &&
257
- !Array.isArray(req.files)) {
258
- const bodyUpdate = {};
259
- for (const fieldName in req.files) {
260
- const files = req.files[fieldName];
261
- const values = files
262
- .map((file) => getAttachValue(file))
475
+ else if (config.type === "array") {
476
+ if (isNestedArrayPath(config.field)) {
477
+ const filesObj = req.files;
478
+ if (filesObj && !Array.isArray(filesObj)) {
479
+ reconstructNestedArrayPath(config.field, filesObj, config.attachToBody, "array");
480
+ }
481
+ }
482
+ else if (Array.isArray(req.files)) {
483
+ const values = req.files
484
+ .map((file) => getAttachValue(file, config.attachToBody))
263
485
  .filter((v) => v !== undefined);
264
486
  if (values.length > 0) {
265
- const valueToAttach = values.length === 1 ? values[0] : values;
266
- setNestedValue(bodyUpdate, fieldName, valueToAttach);
487
+ const bodyUpdate = {};
488
+ setNestedValue(bodyUpdate, config.field, values);
489
+ req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
490
+ }
491
+ }
492
+ }
493
+ else if (config.type === "fields") {
494
+ const filesObj = req.files;
495
+ if (!filesObj || Array.isArray(filesObj))
496
+ return next();
497
+ const bodyUpdate = {};
498
+ const configAttachToBody = "attachToBody" in config ? config.attachToBody : undefined;
499
+ for (const fieldEntry of config.fields) {
500
+ const resolved = resolveFieldEntry(fieldEntry);
501
+ if (isNestedArrayPath(resolved.name)) {
502
+ reconstructNestedArrayPath(resolved.name, filesObj, resolved.attachToBody ?? configAttachToBody, resolved.type, bodyUpdate);
503
+ }
504
+ else {
505
+ const files = filesObj[resolved.name];
506
+ if (!files || files.length === 0)
507
+ continue;
508
+ const attachTo = resolved.attachToBody ?? configAttachToBody;
509
+ const values = files
510
+ .map((f) => getAttachValue(f, attachTo))
511
+ .filter((v) => v !== undefined);
512
+ if (values.length === 0)
513
+ continue;
514
+ const valueToAttach = resolved.type === "single" ? values[0] : values;
515
+ setNestedValue(bodyUpdate, resolved.name, valueToAttach);
267
516
  }
268
517
  }
269
518
  if (Object.keys(bodyUpdate).length > 0) {
270
- req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate);
519
+ req.body = (0, deepmerge_helper_1.default)(req.body || {}, bodyUpdate, {
520
+ arrayMerge: (target, source) => {
521
+ const result = [...target];
522
+ source.forEach((item, i) => {
523
+ result[i] = result[i] ? (0, deepmerge_helper_1.default)(result[i], item) : item;
524
+ });
525
+ return result;
526
+ },
527
+ });
271
528
  }
272
529
  }
273
530
  }