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