@venizia/ignis 0.2.0-8 → 0.2.0

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 (369) hide show
  1. package/LICENSE.md +0 -1
  2. package/README.md +118 -158
  3. package/dist/base/applications/base.d.ts +19 -13
  4. package/dist/base/applications/base.d.ts.map +1 -1
  5. package/dist/base/applications/base.js +95 -74
  6. package/dist/base/applications/base.js.map +1 -1
  7. package/dist/base/applications/boot-steps.d.ts +13 -0
  8. package/dist/base/applications/boot-steps.d.ts.map +1 -0
  9. package/dist/base/applications/boot-steps.js +23 -0
  10. package/dist/base/applications/boot-steps.js.map +1 -0
  11. package/dist/base/applications/common/index.d.ts +2 -0
  12. package/dist/base/applications/common/index.d.ts.map +1 -0
  13. package/dist/base/applications/common/index.js +18 -0
  14. package/dist/base/applications/common/index.js.map +1 -0
  15. package/dist/base/applications/{types.d.ts → common/types.d.ts} +11 -0
  16. package/dist/base/applications/common/types.d.ts.map +1 -0
  17. package/dist/base/applications/common/types.js.map +1 -0
  18. package/dist/base/applications/index.d.ts +2 -1
  19. package/dist/base/applications/index.d.ts.map +1 -1
  20. package/dist/base/applications/index.js +2 -1
  21. package/dist/base/applications/index.js.map +1 -1
  22. package/dist/base/applications/server.d.ts +6 -2
  23. package/dist/base/applications/server.d.ts.map +1 -1
  24. package/dist/base/applications/server.js +34 -2
  25. package/dist/base/applications/server.js.map +1 -1
  26. package/dist/base/controllers/grpc/adapter.js +2 -2
  27. package/dist/base/controllers/grpc/adapter.js.map +1 -1
  28. package/dist/base/middlewares/app-error/app-error.middleware.d.ts +3 -2
  29. package/dist/base/middlewares/app-error/app-error.middleware.d.ts.map +1 -1
  30. package/dist/base/middlewares/app-error/app-error.middleware.js +4 -3
  31. package/dist/base/middlewares/app-error/app-error.middleware.js.map +1 -1
  32. package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts +1 -1
  33. package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts.map +1 -1
  34. package/dist/base/middlewares/request-spy/request-spy.middleware.js +13 -4
  35. package/dist/base/middlewares/request-spy/request-spy.middleware.js.map +1 -1
  36. package/dist/common/constants.d.ts.map +1 -1
  37. package/dist/common/constants.js +2 -1
  38. package/dist/common/constants.js.map +1 -1
  39. package/dist/common/environments.d.ts +31 -10
  40. package/dist/common/environments.d.ts.map +1 -1
  41. package/dist/common/environments.js +33 -10
  42. package/dist/common/environments.js.map +1 -1
  43. package/dist/components/api-reference/component.d.ts.map +1 -1
  44. package/dist/components/api-reference/component.js +4 -3
  45. package/dist/components/api-reference/component.js.map +1 -1
  46. package/dist/components/api-reference/index.d.ts +1 -1
  47. package/dist/components/api-reference/index.d.ts.map +1 -1
  48. package/dist/components/api-reference/index.js +1 -1
  49. package/dist/components/api-reference/index.js.map +1 -1
  50. package/dist/components/api-reference/ui/factory.d.ts +14 -0
  51. package/dist/components/api-reference/ui/factory.d.ts.map +1 -0
  52. package/dist/components/api-reference/ui/factory.js +54 -0
  53. package/dist/components/api-reference/ui/factory.js.map +1 -0
  54. package/dist/components/api-reference/ui/index.d.ts +4 -0
  55. package/dist/components/api-reference/ui/index.d.ts.map +1 -0
  56. package/dist/components/api-reference/ui/index.js +20 -0
  57. package/dist/components/api-reference/ui/index.js.map +1 -0
  58. package/dist/components/api-reference/ui/scalar.d.ts +8 -0
  59. package/dist/components/api-reference/ui/scalar.d.ts.map +1 -0
  60. package/dist/components/api-reference/ui/scalar.js +28 -0
  61. package/dist/components/api-reference/ui/scalar.js.map +1 -0
  62. package/dist/components/api-reference/ui/swagger.d.ts +8 -0
  63. package/dist/components/api-reference/ui/swagger.d.ts.map +1 -0
  64. package/dist/components/api-reference/ui/swagger.js +28 -0
  65. package/dist/components/api-reference/ui/swagger.js.map +1 -0
  66. package/dist/components/auth/authenticate/component.d.ts +5 -0
  67. package/dist/components/auth/authenticate/component.d.ts.map +1 -1
  68. package/dist/components/auth/authenticate/component.js +23 -0
  69. package/dist/components/auth/authenticate/component.js.map +1 -1
  70. package/dist/components/auth/authenticate/controllers/factory.d.ts +35 -3
  71. package/dist/components/auth/authenticate/controllers/factory.d.ts.map +1 -1
  72. package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts +40 -20
  73. package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts.map +1 -1
  74. package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts +40 -20
  75. package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts.map +1 -1
  76. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts +2 -2
  77. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts.map +1 -1
  78. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js +12 -8
  79. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js.map +1 -1
  80. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts +1 -1
  81. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts.map +1 -1
  82. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js +5 -3
  83. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js.map +1 -1
  84. package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts +1 -2
  85. package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts.map +1 -1
  86. package/dist/components/auth/authenticate/services/bearer/jws.service.js +5 -3
  87. package/dist/components/auth/authenticate/services/bearer/jws.service.js.map +1 -1
  88. package/dist/components/auth/authenticate/services/jose-loader.d.ts +16 -0
  89. package/dist/components/auth/authenticate/services/jose-loader.d.ts.map +1 -0
  90. package/dist/components/auth/authenticate/services/jose-loader.js +25 -0
  91. package/dist/components/auth/authenticate/services/jose-loader.js.map +1 -0
  92. package/dist/components/auth/authenticate/services/service/signer.service.d.ts.map +1 -1
  93. package/dist/components/auth/authenticate/services/service/signer.service.js +8 -5
  94. package/dist/components/auth/authenticate/services/service/signer.service.js.map +1 -1
  95. package/dist/components/auth/authenticate/services/service/verifier.service.d.ts.map +1 -1
  96. package/dist/components/auth/authenticate/services/service/verifier.service.js +10 -9
  97. package/dist/components/auth/authenticate/services/service/verifier.service.js.map +1 -1
  98. package/dist/components/auth/authenticate/strategies/index.d.ts +1 -1
  99. package/dist/components/auth/authenticate/strategies/index.d.ts.map +1 -1
  100. package/dist/components/auth/authenticate/strategies/index.js +1 -1
  101. package/dist/components/auth/authenticate/strategies/index.js.map +1 -1
  102. package/dist/components/auth/authenticate/strategies/jwks/index.d.ts +3 -0
  103. package/dist/components/auth/authenticate/strategies/jwks/index.d.ts.map +1 -0
  104. package/dist/components/auth/authenticate/strategies/jwks/index.js +19 -0
  105. package/dist/components/auth/authenticate/strategies/jwks/index.js.map +1 -0
  106. package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts +13 -0
  107. package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts.map +1 -0
  108. package/dist/components/auth/authenticate/strategies/{jwks.strategy.js → jwks/issuer.js} +4 -27
  109. package/dist/components/auth/authenticate/strategies/jwks/issuer.js.map +1 -0
  110. package/dist/components/auth/authenticate/strategies/{jwks.strategy.d.ts → jwks/verifier.d.ts} +2 -10
  111. package/dist/components/auth/authenticate/strategies/jwks/verifier.d.ts.map +1 -0
  112. package/dist/components/auth/authenticate/strategies/jwks/verifier.js +45 -0
  113. package/dist/components/auth/authenticate/strategies/jwks/verifier.js.map +1 -0
  114. package/dist/components/auth/authorize/adapters/base-filtered.d.ts +1 -1
  115. package/dist/components/auth/authorize/adapters/base-filtered.d.ts.map +1 -1
  116. package/dist/components/auth/authorize/adapters/common/index.d.ts +2 -0
  117. package/dist/components/auth/authorize/adapters/common/index.d.ts.map +1 -0
  118. package/dist/components/auth/authorize/adapters/common/index.js +18 -0
  119. package/dist/components/auth/authorize/adapters/common/index.js.map +1 -0
  120. package/dist/components/auth/authorize/adapters/{types.d.ts → common/types.d.ts} +1 -1
  121. package/dist/components/auth/authorize/adapters/common/types.d.ts.map +1 -0
  122. package/dist/components/auth/authorize/adapters/common/types.js.map +1 -0
  123. package/dist/components/auth/authorize/adapters/connector.d.ts +1 -1
  124. package/dist/components/auth/authorize/adapters/connector.d.ts.map +1 -1
  125. package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts +1 -1
  126. package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts.map +1 -1
  127. package/dist/components/auth/authorize/adapters/index.d.ts +2 -2
  128. package/dist/components/auth/authorize/adapters/index.d.ts.map +1 -1
  129. package/dist/components/auth/authorize/adapters/index.js +2 -2
  130. package/dist/components/auth/authorize/adapters/index.js.map +1 -1
  131. package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.d.ts → scoped-casbin/adapter.d.ts} +11 -55
  132. package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.d.ts.map +1 -0
  133. package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.js → scoped-casbin/adapter.js} +27 -22
  134. package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.js.map +1 -0
  135. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts +2 -0
  136. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts.map +1 -0
  137. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js +5 -0
  138. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js.map +1 -0
  139. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts +3 -0
  140. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts.map +1 -0
  141. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js +19 -0
  142. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js.map +1 -0
  143. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts +50 -0
  144. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts.map +1 -0
  145. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js +3 -0
  146. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js.map +1 -0
  147. package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts +4 -0
  148. package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts.map +1 -0
  149. package/dist/components/auth/authorize/adapters/scoped-casbin/index.js +19 -0
  150. package/dist/components/auth/authorize/adapters/scoped-casbin/index.js.map +1 -0
  151. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts +17 -0
  152. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts.map +1 -0
  153. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js +11 -0
  154. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js.map +1 -0
  155. package/dist/components/auth/authorize/enforcers/policy-line-codec.d.ts.map +1 -1
  156. package/dist/components/auth/authorize/enforcers/policy-line-codec.js +4 -2
  157. package/dist/components/auth/authorize/enforcers/policy-line-codec.js.map +1 -1
  158. package/dist/components/auth/authorize/role-managers/base.d.ts.map +1 -1
  159. package/dist/components/auth/authorize/role-managers/base.js +2 -1
  160. package/dist/components/auth/authorize/role-managers/base.js.map +1 -1
  161. package/dist/components/auth/models/entities/policy-definition.model.d.ts +30 -9
  162. package/dist/components/auth/models/entities/policy-definition.model.d.ts.map +1 -1
  163. package/dist/components/auth/models/entities/policy-definition.model.js +36 -10
  164. package/dist/components/auth/models/entities/policy-definition.model.js.map +1 -1
  165. package/dist/components/auth/models/entities/user.model.d.ts +2 -2
  166. package/dist/components/controller/grpc/grpc.component.d.ts.map +1 -1
  167. package/dist/components/controller/grpc/grpc.component.js +1 -5
  168. package/dist/components/controller/grpc/grpc.component.js.map +1 -1
  169. package/dist/components/health-check/common/constants.d.ts +5 -0
  170. package/dist/components/health-check/common/constants.d.ts.map +1 -0
  171. package/dist/components/health-check/common/constants.js +9 -0
  172. package/dist/components/health-check/common/constants.js.map +1 -0
  173. package/dist/components/health-check/common/index.d.ts +1 -0
  174. package/dist/components/health-check/common/index.d.ts.map +1 -1
  175. package/dist/components/health-check/common/index.js +1 -0
  176. package/dist/components/health-check/common/index.js.map +1 -1
  177. package/dist/components/health-check/common/keys.d.ts +2 -0
  178. package/dist/components/health-check/common/keys.d.ts.map +1 -1
  179. package/dist/components/health-check/common/keys.js +2 -0
  180. package/dist/components/health-check/common/keys.js.map +1 -1
  181. package/dist/components/health-check/common/rest-paths.d.ts +1 -0
  182. package/dist/components/health-check/common/rest-paths.d.ts.map +1 -1
  183. package/dist/components/health-check/common/rest-paths.js +1 -0
  184. package/dist/components/health-check/common/rest-paths.js.map +1 -1
  185. package/dist/components/health-check/common/types.d.ts +22 -0
  186. package/dist/components/health-check/common/types.d.ts.map +1 -1
  187. package/dist/components/health-check/component.d.ts +9 -3
  188. package/dist/components/health-check/component.d.ts.map +1 -1
  189. package/dist/components/health-check/component.js +38 -1
  190. package/dist/components/health-check/component.js.map +1 -1
  191. package/dist/components/health-check/controller.d.ts +10 -2
  192. package/dist/components/health-check/controller.d.ts.map +1 -1
  193. package/dist/components/health-check/controller.js +79 -13
  194. package/dist/components/health-check/controller.js.map +1 -1
  195. package/dist/components/health-check/index.d.ts +1 -0
  196. package/dist/components/health-check/index.d.ts.map +1 -1
  197. package/dist/components/health-check/index.js +1 -0
  198. package/dist/components/health-check/index.js.map +1 -1
  199. package/dist/components/health-check/reporter.d.ts +98 -0
  200. package/dist/components/health-check/reporter.d.ts.map +1 -0
  201. package/dist/components/health-check/reporter.js +138 -0
  202. package/dist/components/health-check/reporter.js.map +1 -0
  203. package/dist/components/mail/common/constants.d.ts +8 -0
  204. package/dist/components/mail/common/constants.d.ts.map +1 -1
  205. package/dist/components/mail/common/constants.js +9 -1
  206. package/dist/components/mail/common/constants.js.map +1 -1
  207. package/dist/components/mail/common/types/index.d.ts +6 -0
  208. package/dist/components/mail/common/types/index.d.ts.map +1 -0
  209. package/dist/components/mail/common/types/index.js +22 -0
  210. package/dist/components/mail/common/types/index.js.map +1 -0
  211. package/dist/components/mail/common/types/message.d.ts +49 -0
  212. package/dist/components/mail/common/types/message.d.ts.map +1 -0
  213. package/dist/components/mail/common/types/message.js +3 -0
  214. package/dist/components/mail/common/types/message.js.map +1 -0
  215. package/dist/components/mail/common/types/options.d.ts +65 -0
  216. package/dist/components/mail/common/types/options.d.ts.map +1 -0
  217. package/dist/components/mail/common/types/options.js +3 -0
  218. package/dist/components/mail/common/types/options.js.map +1 -0
  219. package/dist/components/mail/common/types/queue.d.ts +56 -0
  220. package/dist/components/mail/common/types/queue.d.ts.map +1 -0
  221. package/dist/components/mail/common/types/queue.js +3 -0
  222. package/dist/components/mail/common/types/queue.js.map +1 -0
  223. package/dist/components/mail/common/types/template.d.ts +33 -0
  224. package/dist/components/mail/common/types/template.d.ts.map +1 -0
  225. package/dist/components/mail/common/types/template.js +3 -0
  226. package/dist/components/mail/common/types/template.js.map +1 -0
  227. package/dist/components/mail/common/types/verification.d.ts +26 -0
  228. package/dist/components/mail/common/types/verification.d.ts.map +1 -0
  229. package/dist/components/mail/common/types/verification.js +3 -0
  230. package/dist/components/mail/common/types/verification.js.map +1 -0
  231. package/dist/components/mail/component.d.ts +4 -1
  232. package/dist/components/mail/component.d.ts.map +1 -1
  233. package/dist/components/mail/component.js +12 -0
  234. package/dist/components/mail/component.js.map +1 -1
  235. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts +1 -0
  236. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts.map +1 -1
  237. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js +3 -0
  238. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js.map +1 -1
  239. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.d.ts.map +1 -1
  240. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js +2 -1
  241. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js.map +1 -1
  242. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts +19 -0
  243. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts.map +1 -0
  244. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js +112 -0
  245. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js.map +1 -0
  246. package/dist/components/mail/helpers/transporters/index.d.ts +2 -1
  247. package/dist/components/mail/helpers/transporters/index.d.ts.map +1 -1
  248. package/dist/components/mail/helpers/transporters/index.js +2 -1
  249. package/dist/components/mail/helpers/transporters/index.js.map +1 -1
  250. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.d.ts.map +1 -1
  251. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js +4 -3
  252. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js.map +1 -1
  253. package/dist/components/mail/providers/mail-transporter.provider.d.ts +2 -0
  254. package/dist/components/mail/providers/mail-transporter.provider.d.ts.map +1 -1
  255. package/dist/components/mail/providers/mail-transporter.provider.js +35 -17
  256. package/dist/components/mail/providers/mail-transporter.provider.js.map +1 -1
  257. package/dist/components/mail/services/generators/index.d.ts +4 -0
  258. package/dist/components/mail/services/generators/index.d.ts.map +1 -0
  259. package/dist/components/mail/services/generators/index.js +20 -0
  260. package/dist/components/mail/services/generators/index.js.map +1 -0
  261. package/dist/components/mail/services/generators/numeric-code.d.ts +7 -0
  262. package/dist/components/mail/services/generators/numeric-code.d.ts.map +1 -0
  263. package/dist/components/mail/services/generators/numeric-code.js +20 -0
  264. package/dist/components/mail/services/generators/numeric-code.js.map +1 -0
  265. package/dist/components/mail/services/generators/random-token.d.ts +7 -0
  266. package/dist/components/mail/services/generators/random-token.d.ts.map +1 -0
  267. package/dist/components/mail/services/generators/random-token.js +18 -0
  268. package/dist/components/mail/services/generators/random-token.js.map +1 -0
  269. package/dist/components/mail/services/{generator.service.d.ts → generators/verification-data.d.ts} +2 -10
  270. package/dist/components/mail/services/generators/verification-data.d.ts.map +1 -0
  271. package/dist/components/mail/services/{generator.service.js → generators/verification-data.js} +5 -29
  272. package/dist/components/mail/services/generators/verification-data.js.map +1 -0
  273. package/dist/components/mail/services/index.d.ts +1 -1
  274. package/dist/components/mail/services/index.d.ts.map +1 -1
  275. package/dist/components/mail/services/index.js +1 -1
  276. package/dist/components/mail/services/index.js.map +1 -1
  277. package/dist/components/mail/services/mail.service.d.ts.map +1 -1
  278. package/dist/components/mail/services/mail.service.js +18 -17
  279. package/dist/components/mail/services/mail.service.js.map +1 -1
  280. package/dist/components/mail/utilities/address.utility.d.ts +9 -0
  281. package/dist/components/mail/utilities/address.utility.d.ts.map +1 -0
  282. package/dist/components/mail/utilities/address.utility.js +36 -0
  283. package/dist/components/mail/utilities/address.utility.js.map +1 -0
  284. package/dist/components/mail/utilities/index.d.ts +2 -0
  285. package/dist/components/mail/utilities/index.d.ts.map +1 -1
  286. package/dist/components/mail/utilities/index.js +2 -0
  287. package/dist/components/mail/utilities/index.js.map +1 -1
  288. package/dist/components/mail/utilities/mime.utility.d.ts +19 -0
  289. package/dist/components/mail/utilities/mime.utility.d.ts.map +1 -0
  290. package/dist/components/mail/utilities/mime.utility.js +414 -0
  291. package/dist/components/mail/utilities/mime.utility.js.map +1 -0
  292. package/dist/components/static-asset/common/commit-token.d.ts +23 -0
  293. package/dist/components/static-asset/common/commit-token.d.ts.map +1 -0
  294. package/dist/components/static-asset/common/commit-token.js +40 -0
  295. package/dist/components/static-asset/common/commit-token.js.map +1 -0
  296. package/dist/components/static-asset/common/constants.d.ts +13 -1
  297. package/dist/components/static-asset/common/constants.d.ts.map +1 -1
  298. package/dist/components/static-asset/common/constants.js +29 -2
  299. package/dist/components/static-asset/common/constants.js.map +1 -1
  300. package/dist/components/static-asset/common/errors.d.ts +43 -0
  301. package/dist/components/static-asset/common/errors.d.ts.map +1 -1
  302. package/dist/components/static-asset/common/errors.js +37 -0
  303. package/dist/components/static-asset/common/errors.js.map +1 -1
  304. package/dist/components/static-asset/common/index.d.ts +1 -0
  305. package/dist/components/static-asset/common/index.d.ts.map +1 -1
  306. package/dist/components/static-asset/common/index.js +1 -0
  307. package/dist/components/static-asset/common/index.js.map +1 -1
  308. package/dist/components/static-asset/common/keys.d.ts +1 -0
  309. package/dist/components/static-asset/common/keys.d.ts.map +1 -1
  310. package/dist/components/static-asset/common/keys.js +1 -0
  311. package/dist/components/static-asset/common/keys.js.map +1 -1
  312. package/dist/components/static-asset/common/types.d.ts +87 -30
  313. package/dist/components/static-asset/common/types.d.ts.map +1 -1
  314. package/dist/components/static-asset/component.d.ts +4 -1
  315. package/dist/components/static-asset/component.d.ts.map +1 -1
  316. package/dist/components/static-asset/component.js +21 -10
  317. package/dist/components/static-asset/component.js.map +1 -1
  318. package/dist/components/static-asset/controller/base.definition.d.ts +27 -554
  319. package/dist/components/static-asset/controller/base.definition.d.ts.map +1 -1
  320. package/dist/components/static-asset/controller/base.definition.js +250 -286
  321. package/dist/components/static-asset/controller/base.definition.js.map +1 -1
  322. package/dist/components/static-asset/controller/factory.d.ts +76 -7
  323. package/dist/components/static-asset/controller/factory.d.ts.map +1 -1
  324. package/dist/components/static-asset/controller/factory.js +565 -138
  325. package/dist/components/static-asset/controller/factory.js.map +1 -1
  326. package/dist/components/static-asset/index.d.ts +1 -0
  327. package/dist/components/static-asset/index.d.ts.map +1 -1
  328. package/dist/components/static-asset/index.js +1 -0
  329. package/dist/components/static-asset/index.js.map +1 -1
  330. package/dist/components/static-asset/ingest.d.ts +58 -0
  331. package/dist/components/static-asset/ingest.d.ts.map +1 -0
  332. package/dist/components/static-asset/ingest.js +82 -0
  333. package/dist/components/static-asset/ingest.js.map +1 -0
  334. package/dist/components/static-asset/models/base.model.d.ts +44 -6
  335. package/dist/components/static-asset/models/base.model.d.ts.map +1 -1
  336. package/dist/components/static-asset/models/base.model.js +7 -1
  337. package/dist/components/static-asset/models/base.model.js.map +1 -1
  338. package/dist/components/websocket/handlers/bun.handler.d.ts.map +1 -1
  339. package/dist/components/websocket/handlers/bun.handler.js +2 -1
  340. package/dist/components/websocket/handlers/bun.handler.js.map +1 -1
  341. package/dist/connectors/postgres/drivers/postgres-js.d.mts +3 -0
  342. package/dist/connectors/postgres/drivers/postgres-js.d.mts.map +1 -0
  343. package/dist/connectors/postgres/drivers/postgres-js.mjs +3 -0
  344. package/dist/connectors/postgres/drivers/postgres-js.mjs.map +1 -0
  345. package/dist/helpers/index.d.ts.map +1 -1
  346. package/dist/helpers/index.js.map +1 -1
  347. package/dist/index.d.ts +1 -1
  348. package/dist/index.d.ts.map +1 -1
  349. package/package.json +29 -26
  350. package/dist/base/applications/types.d.ts.map +0 -1
  351. package/dist/base/applications/types.js.map +0 -1
  352. package/dist/components/api-reference/ui-factory.d.ts +0 -24
  353. package/dist/components/api-reference/ui-factory.d.ts.map +0 -1
  354. package/dist/components/api-reference/ui-factory.js +0 -97
  355. package/dist/components/api-reference/ui-factory.js.map +0 -1
  356. package/dist/components/auth/authenticate/strategies/jwks.strategy.d.ts.map +0 -1
  357. package/dist/components/auth/authenticate/strategies/jwks.strategy.js.map +0 -1
  358. package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.d.ts.map +0 -1
  359. package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.js.map +0 -1
  360. package/dist/components/auth/authorize/adapters/types.d.ts.map +0 -1
  361. package/dist/components/auth/authorize/adapters/types.js.map +0 -1
  362. package/dist/components/mail/common/types.d.ts +0 -201
  363. package/dist/components/mail/common/types.d.ts.map +0 -1
  364. package/dist/components/mail/common/types.js +0 -10
  365. package/dist/components/mail/common/types.js.map +0 -1
  366. package/dist/components/mail/services/generator.service.d.ts.map +0 -1
  367. package/dist/components/mail/services/generator.service.js.map +0 -1
  368. /package/dist/base/applications/{types.js → common/types.js} +0 -0
  369. /package/dist/components/auth/authorize/adapters/{types.js → common/types.js} +0 -0
@@ -9,22 +9,133 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AssetControllerFactory = void 0;
12
+ exports.AssetControllerFactory = exports.resolveServedContentType = exports.buildObjectLink = exports.findLabelsInQuery = void 0;
13
13
  const controllers_1 = require("../../../base/controllers");
14
14
  const metadata_1 = require("../../../base/metadata");
15
- const core_1 = require("@venizia/ignis-helpers/core");
16
- const common_1 = require("@venizia/ignis-helpers/common");
17
15
  const ignis_helpers_1 = require("@venizia/ignis-helpers");
16
+ const common_1 = require("@venizia/ignis-helpers/common");
17
+ const core_1 = require("@venizia/ignis-helpers/core");
18
18
  const node_fs_1 = require("node:fs");
19
19
  const common_2 = require("../common");
20
+ const common_3 = require("../common");
20
21
  const base_definition_1 = require("./base.definition");
21
22
  /** Hono ALREADY percent-decodes path params - a second decodeURIComponent throws on `report_100%.pdf` and turns `a%2Fb.png` into a DIFFERENT object; `isValidName`/`isValidPath` still run on this value, so traversal is still rejected. */
23
+ /** The upload labels, read off the multipart form. Every value arrives as text, so `sequence` is the one that needs converting. */
24
+ /** One list: the reader below and the stale-caller warning must agree. */
25
+ const UPLOAD_LABEL_NAMES = [
26
+ 'principalType',
27
+ 'principalId',
28
+ 'variant',
29
+ 'sequence',
30
+ 'folderPath',
31
+ ];
32
+ /** Names any label a caller put in the query - where the upload routes refuse it. */
33
+ const findLabelsInQuery = (opts) => (opts.names ?? UPLOAD_LABEL_NAMES).filter(name => opts.query[name] !== undefined);
34
+ exports.findLabelsInQuery = findLabelsInQuery;
35
+ /** Refused before the body is read: a route that ignored them would store an unlabelled row. */
36
+ const refuseLabelsInQuery = (opts) => {
37
+ const names = (0, exports.findLabelsInQuery)(opts);
38
+ if (names.length > 0) {
39
+ throw (0, core_1.getError)({
40
+ error: common_2.StaticAssetErrors.LABELS_IN_QUERY,
41
+ message: `${opts.reason ?? 'Upload labels belong in the request body, not the query'} | names: ${names.join(', ')}`,
42
+ });
43
+ }
44
+ };
45
+ /** The commit body carries four of the five: the object key - and so `folderPath` - was fixed when the policy was issued. */
46
+ const COMMIT_LABEL_NAMES = UPLOAD_LABEL_NAMES.filter(name => name !== 'folderPath');
47
+ // A multipart form hands strings; a JSON commit body may hand `sequence` as a number.
48
+ const readUploadLabels = (opts) => {
49
+ const { fields } = opts;
50
+ const read = (name) => {
51
+ const value = fields[name];
52
+ return value === undefined || value === '' ? undefined : String(value);
53
+ };
54
+ const sequence = read('sequence');
55
+ return {
56
+ principalType: read('principalType'),
57
+ principalId: read('principalId'),
58
+ variant: read('variant'),
59
+ sequence: sequence === undefined ? undefined : Number(sequence),
60
+ folderPath: read('folderPath'),
61
+ };
62
+ };
22
63
  const readObjectName = rawObjectName => rawObjectName;
23
64
  /** Encodes an object path into a SINGLE url segment: `{objectName}` matches one segment only, so `/` must be percent-encoded too (Hono decodes it back before the handler reads the param). */
24
65
  const encodeObjectPath = objectPath => {
25
66
  return encodeURIComponent(objectPath);
26
67
  };
27
- /** Sets whitelisted metadata headers on the response context. */
68
+ /** The bucket one request works on: a configured value wins - called on every request, so an application may read an environment variable lazily - else the path param, which is only registered when there is no configured bucket. */
69
+ const resolveBucket = async (opts) => {
70
+ const { configured, param } = opts;
71
+ if (configured === undefined) {
72
+ return param ?? '';
73
+ }
74
+ return (0, common_1.resolveValueAsync)({ value: configured });
75
+ };
76
+ /** The URL of one object, in the shape its controller serves: no `/buckets/<name>` with a configured bucket, and a raw path with `rawObjectPath`. */
77
+ const buildObjectLink = (opts) => {
78
+ const { basePath, bucket, object } = opts;
79
+ const { hasConfiguredBucket = false, rawObjectPath = false } = opts;
80
+ const bucketSegment = hasConfiguredBucket ? '' : `/buckets/${bucket.name}`;
81
+ const objectSegment = rawObjectPath ? object.key : encodeObjectPath(object.key);
82
+ return `${basePath}${bucketSegment}/objects/${objectSegment}`;
83
+ };
84
+ exports.buildObjectLink = buildObjectLink;
85
+ /** Mirrors `BaseStorageHelper.normalizeObjectName`, which is protected: `resolveObjectName` is offered the very name the helper would otherwise have written. */
86
+ const defaultObjectName = ({ file }) => {
87
+ const { originalName, folderPath } = file;
88
+ const normalizedFileName = originalName.toLowerCase().replace(/ /g, '_');
89
+ if (!folderPath) {
90
+ return normalizedFileName;
91
+ }
92
+ return `${folderPath.toLowerCase().replace(/ /g, '_')}/${normalizedFileName}`;
93
+ };
94
+ /**
95
+ * The served type comes from the object NAME, never the backend or the uploader: a renderable type on
96
+ * the API origin is stored XSS. Exported so a hand-written route makes the same decision - the
97
+ * backend's own content-type is dropped by `WHITELIST_HEADERS`.
98
+ */
99
+ const resolveServedContentType = (opts) => {
100
+ const { helper, object } = opts;
101
+ const candidate = helper.getMimeType({ filename: object.key }).toLowerCase().split(';')[0].trim();
102
+ if (!common_2.RENDERABLE_CONTENT_TYPES.has(candidate)) {
103
+ return { contentType: common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM, isRenderable: false };
104
+ }
105
+ return { contentType: candidate, isRenderable: true };
106
+ };
107
+ exports.resolveServedContentType = resolveServedContentType;
108
+ /**
109
+ * Reads one `Range` header. Only a single byte range is honoured - multipart ranges need a multipart
110
+ * body no browser asks for here. An unsatisfiable or malformed range returns `null`, and the caller
111
+ * serves the whole object, which is what RFC 9110 allows.
112
+ */
113
+ const readByteRange = (opts) => {
114
+ const { header, size } = opts;
115
+ if (!header || size <= 0) {
116
+ return null;
117
+ }
118
+ const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
119
+ if (!match) {
120
+ return null;
121
+ }
122
+ const [, rawStart, rawEnd] = match;
123
+ // `bytes=-500` means the LAST 500 bytes, not "from 0 to 500".
124
+ if (!rawStart) {
125
+ const suffixLength = Number(rawEnd);
126
+ if (!Number.isInteger(suffixLength) || suffixLength <= 0) {
127
+ return null;
128
+ }
129
+ return { start: Math.max(size - suffixLength, 0), end: size - 1 };
130
+ }
131
+ const start = Number(rawStart);
132
+ const end = rawEnd ? Number(rawEnd) : size - 1;
133
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start > end || start >= size) {
134
+ return null;
135
+ }
136
+ return { start, end: Math.min(end, size - 1) };
137
+ };
138
+ /** Sets whitelisted metadata headers on the response context. `content-type` is not among them. */
28
139
  const applyMetadataHeaders = ({ ctx, metadata }) => {
29
140
  Object.entries(metadata).forEach(([key, value]) => {
30
141
  if (!common_2.WHITELIST_HEADERS.includes(key.toLowerCase())) {
@@ -33,16 +144,63 @@ const applyMetadataHeaders = ({ ctx, metadata }) => {
33
144
  ctx.header(key.toLowerCase(), String(value).replace(/[\r\n]/g, ''));
34
145
  });
35
146
  };
147
+ /** The MetaLink row for one uploaded object. Both upload paths go through here, so a direct upload lands exactly the row an ordinary one does. */
148
+ const createMetaLinkRow = async (opts) => {
149
+ const { metaLink, helper, storage, uploadResult, fileStat, query } = opts;
150
+ if (metaLink.createMetaLink) {
151
+ const created = await metaLink.createMetaLink({ uploadResult, fileStat, query });
152
+ return created.data;
153
+ }
154
+ // Resolved per call so an application that swaps the repository is not pinned to the first one.
155
+ const repository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
156
+ const created = await repository.create({
157
+ data: {
158
+ bucketName: uploadResult.bucket.name,
159
+ objectName: uploadResult.object.key,
160
+ link: uploadResult.link,
161
+ // The column is NOT NULL and a backend may report nothing.
162
+ mimetype: fileStat.metadata?.mimetype ?? helper.getMimeType({ filename: uploadResult.object.key }),
163
+ size: fileStat.size,
164
+ etag: fileStat.etag,
165
+ metadata: fileStat.metadata,
166
+ storageType: storage,
167
+ isSynced: true,
168
+ principalId: query.principalId ? String(query.principalId) : undefined,
169
+ principalType: query.principalType ? String(query.principalType) : undefined,
170
+ variant: query.variant ? String(query.variant) : undefined,
171
+ // Absent leaves the column default rather than writing 0, so a row the caller never
172
+ // ordered is indistinguishable from a legacy one.
173
+ sequence: query.sequence === undefined ? undefined : Number(query.sequence),
174
+ },
175
+ });
176
+ return created.data;
177
+ };
36
178
  class AssetControllerFactory extends core_1.BaseHelper {
37
179
  constructor() {
38
180
  super({ scope: AssetControllerFactory.name });
39
181
  }
40
182
  static defineAssetController(opts) {
41
183
  const { controller, helper, options, useMetaLink, metaLink, storage } = opts;
42
- const { name, basePath, routes, isStrict = true } = controller;
184
+ const { resolveObjectName, defineRoutesBefore, defineExtraRoutes } = opts;
185
+ const { directUpload } = controller;
186
+ const { name, basePath, routes, isStrict = true, bucket, rawObjectPath = false } = controller;
43
187
  const maxFolderDepth = options?.maxFolderDepth ?? ignis_helpers_1.BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH;
188
+ const maxBytes = options?.maxBytes;
189
+ const hasConfiguredBucket = bucket !== undefined;
190
+ const definitions = (0, base_definition_1.buildAssetDefinitions)({ hasConfiguredBucket, rawObjectPath });
44
191
  // The mount path may be declared with or without a leading slash; a link must carry exactly one.
45
192
  const normalizedBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;
193
+ /** Only when the URL shape deviates: with neither option the storage helper's own link is left untouched. */
194
+ const shapedLinkFn = hasConfiguredBucket || rawObjectPath
195
+ ? (linkOptions) => (0, exports.buildObjectLink)({
196
+ basePath: normalizedBasePath,
197
+ bucket: linkOptions.bucket,
198
+ object: linkOptions.object,
199
+ hasConfiguredBucket,
200
+ rawObjectPath,
201
+ })
202
+ : undefined;
203
+ const normalizeLinkFn = options?.normalizeLinkFn ?? shapedLinkFn;
46
204
  let GeneratedStaticAssetController = class GeneratedStaticAssetController extends controllers_1.BaseRestController {
47
205
  constructor() {
48
206
  super({
@@ -65,79 +223,161 @@ class AssetControllerFactory extends core_1.BaseHelper {
65
223
  }
66
224
  }
67
225
  binding() {
226
+ // First, so a literal path can win over the built-in catch-all `rawObjectPath` registers.
227
+ defineRoutesBefore?.({ controller: this, helper, basePath: normalizedBasePath });
228
+ // A single-bucket application has no reason to expose bucket creation or deletion, so the
229
+ // four bucket-management routes are not registered once `bucket` is configured.
230
+ if (!hasConfiguredBucket) {
231
+ this.bindRoute({
232
+ configs: { ...definitions.GET_BUCKETS, ...routes?.getBuckets },
233
+ }).to({
234
+ handler: async (ctx) => {
235
+ const buckets = await helper.getBuckets();
236
+ return ctx.json(buckets, common_1.HTTP.ResultCodes.RS_2.Ok);
237
+ },
238
+ });
239
+ this.bindRoute({
240
+ configs: { ...definitions.GET_BUCKET_BY_NAME, ...routes?.getBucketByName },
241
+ }).to({
242
+ handler: async (ctx) => {
243
+ const params = ctx.req.valid('param');
244
+ const bucketName = await resolveBucket({
245
+ configured: bucket,
246
+ param: params.bucketName,
247
+ });
248
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
249
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
250
+ }
251
+ const found = await helper.getBucket({ bucket: { name: bucketName } });
252
+ return ctx.json(found, common_1.HTTP.ResultCodes.RS_2.Ok);
253
+ },
254
+ });
255
+ this.bindRoute({
256
+ configs: { ...definitions.CREATE_BUCKET, ...routes?.createBucket },
257
+ }).to({
258
+ handler: async (ctx) => {
259
+ const params = ctx.req.valid('param');
260
+ const bucketName = await resolveBucket({
261
+ configured: bucket,
262
+ param: params.bucketName,
263
+ });
264
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
265
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
266
+ }
267
+ const createdBucket = await helper.createBucket({ bucket: { name: bucketName } });
268
+ return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
269
+ },
270
+ });
271
+ this.bindRoute({
272
+ configs: { ...definitions.DELETE_BUCKET, ...routes?.deleteBucket },
273
+ }).to({
274
+ handler: async (ctx) => {
275
+ const params = ctx.req.valid('param');
276
+ const bucketName = await resolveBucket({
277
+ configured: bucket,
278
+ param: params.bucketName,
279
+ });
280
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
281
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
282
+ }
283
+ const isRemovedBucket = await helper.removeBucket({
284
+ bucket: { name: bucketName },
285
+ });
286
+ return ctx.json({ isDeleted: isRemovedBucket }, common_1.HTTP.ResultCodes.RS_2.Ok);
287
+ },
288
+ });
289
+ }
68
290
  this.bindRoute({
69
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_BUCKETS, ...routes?.getBuckets },
70
- }).to({
71
- handler: async (ctx) => {
72
- const bucket = await helper.getBuckets();
73
- return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
74
- },
75
- });
76
- this.bindRoute({
77
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_BUCKET_BY_NAME, ...routes?.getBucketByName },
78
- }).to({
79
- handler: async (ctx) => {
80
- const { bucketName } = ctx.req.valid('param');
81
- if (!helper.isValidName(bucketName)) {
82
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
83
- }
84
- const bucket = await helper.getBucket({ name: bucketName });
85
- return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
86
- },
87
- });
88
- this.bindRoute({
89
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_OBJECT_BY_NAME, ...routes?.getObjectByName },
291
+ configs: { ...definitions.GET_OBJECT_BY_NAME, ...routes?.getObjectByName },
90
292
  }).to({
91
293
  handler: async (ctx) => {
92
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
93
- const objectName = readObjectName(rawObjectName);
94
- if (!helper.isValidName(bucketName)) {
294
+ const params = ctx.req.valid('param');
295
+ const bucketName = await resolveBucket({
296
+ configured: bucket,
297
+ param: params.bucketName,
298
+ });
299
+ const objectName = readObjectName(params.objectName);
300
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
95
301
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
96
302
  }
97
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
303
+ if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
98
304
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
99
305
  }
100
- const fileStat = await helper.getStat({ bucket: bucketName, name: objectName });
306
+ const fileStat = await helper.getStat({
307
+ bucket: { name: bucketName },
308
+ object: { key: objectName },
309
+ });
101
310
  const { size, metadata } = fileStat;
102
311
  applyMetadataHeaders({ ctx, metadata });
103
- if (!ctx.res.headers.has(common_1.HTTP.Headers.CONTENT_TYPE)) {
104
- ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM);
105
- }
106
- ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
312
+ const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
313
+ ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
107
314
  ctx.header('x-content-type-options', 'nosniff');
108
- const stream = await helper.getFile({ bucket: bucketName, name: objectName });
315
+ // Sandboxed even when renderable, so an unforeseen renderable type cannot reach this origin.
316
+ ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
317
+ // Advertised unconditionally: a player only offers seeking once it sees this.
318
+ ctx.header('accept-ranges', 'bytes');
319
+ // A type outside the allow-list downloads instead of rendering; that is the XSS boundary.
320
+ if (!served.isRenderable) {
321
+ const inlineName = objectName.split('/').pop() ?? objectName;
322
+ ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: inlineName, type: 'attachment' }));
323
+ }
324
+ const range = readByteRange({ header: ctx.req.header('range'), size });
325
+ if (range) {
326
+ ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, String(range.end - range.start + 1));
327
+ ctx.header(common_1.HTTP.Headers.CONTENT_RANGE, `bytes ${range.start}-${range.end}/${size}`);
328
+ }
329
+ else {
330
+ ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
331
+ }
332
+ // The web stream is what a Response body wants, so no Node Readable sits in between.
333
+ const stream = await helper.getObjectStream({
334
+ bucket: { name: bucketName },
335
+ object: { key: objectName },
336
+ ...(range ? { range } : {}),
337
+ });
109
338
  return new Response(stream, {
110
339
  headers: ctx.res.headers,
111
- status: common_1.HTTP.ResultCodes.RS_2.Ok,
340
+ status: range ? common_1.HTTP.ResultCodes.RS_2.PartialContent : common_1.HTTP.ResultCodes.RS_2.Ok,
112
341
  });
113
342
  },
114
343
  });
115
344
  this.bindRoute({
116
345
  configs: {
117
- ...base_definition_1.StaticAssetDefinitions.DOWNLOAD_OBJECT_BY_NAME,
346
+ ...definitions.DOWNLOAD_OBJECT_BY_NAME,
118
347
  ...routes?.downloadObjectByName,
119
348
  },
120
349
  }).to({
121
350
  handler: async (ctx) => {
122
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
123
- const objectName = readObjectName(rawObjectName);
124
- if (!helper.isValidName(bucketName)) {
351
+ const params = ctx.req.valid('param');
352
+ const bucketName = await resolveBucket({
353
+ configured: bucket,
354
+ param: params.bucketName,
355
+ });
356
+ const objectName = readObjectName(params.objectName);
357
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
125
358
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
126
359
  }
127
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
360
+ if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
128
361
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
129
362
  }
130
- const fileStat = await helper.getStat({ bucket: bucketName, name: objectName });
363
+ const fileStat = await helper.getStat({
364
+ bucket: { name: bucketName },
365
+ object: { key: objectName },
366
+ });
131
367
  const { size, metadata } = fileStat;
132
368
  applyMetadataHeaders({ ctx, metadata });
133
- if (!ctx.res.headers.has(common_1.HTTP.Headers.CONTENT_TYPE)) {
134
- ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM);
135
- }
369
+ // Download always attaches, so the served type only has to be honest, never renderable.
370
+ const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
371
+ ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
136
372
  ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
137
373
  const fileName = objectName.split('/').pop() ?? objectName;
138
374
  ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: fileName, type: 'attachment' }));
139
375
  ctx.header('x-content-type-options', 'nosniff');
140
- const stream = await helper.getFile({ bucket: bucketName, name: objectName });
376
+ ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
377
+ const stream = await helper.getObjectStream({
378
+ bucket: { name: bucketName },
379
+ object: { key: objectName },
380
+ });
141
381
  return new Response(stream, {
142
382
  headers: ctx.res.headers,
143
383
  status: common_1.HTTP.ResultCodes.RS_2.Ok,
@@ -145,28 +385,18 @@ class AssetControllerFactory extends core_1.BaseHelper {
145
385
  },
146
386
  });
147
387
  this.bindRoute({
148
- configs: { ...base_definition_1.StaticAssetDefinitions.CREATE_BUCKET, ...routes?.createBucket },
149
- }).to({
150
- handler: async (ctx) => {
151
- const { bucketName } = ctx.req.valid('param');
152
- if (!helper.isValidName(bucketName)) {
153
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
154
- }
155
- const createdBucket = await helper.createBucket({ name: bucketName });
156
- return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
157
- },
158
- });
159
- this.bindRoute({
160
- configs: { ...base_definition_1.StaticAssetDefinitions.UPLOAD, ...routes?.upload },
388
+ configs: { ...definitions.UPLOAD, ...routes?.upload },
161
389
  }).to({
162
390
  handler: async (ctx) => {
163
- const { bucketName } = ctx.req.valid('param');
164
- const query = ctx.req.valid('query');
165
- if (!helper.isValidName(bucketName)) {
391
+ // A configured bucket drops `params` from this route entirely, so it is read only when it exists.
392
+ const bucketParam = hasConfiguredBucket
393
+ ? undefined
394
+ : ctx.req.valid('param').bucketName;
395
+ const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
396
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
166
397
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
167
398
  }
168
- const folderPath = query.folderPath;
169
- if (folderPath) {
399
+ const validateFolderPath = (folderPath) => {
170
400
  const normalizedFolder = folderPath.replace(/^\/+|\/+$/g, '');
171
401
  if (!normalizedFolder) {
172
402
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.FOLDER_PATH_INVALID });
@@ -179,19 +409,37 @@ class AssetControllerFactory extends core_1.BaseHelper {
179
409
  message: `Folder path exceeds max depth of ${maxFolderDepth}`,
180
410
  });
181
411
  }
182
- const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.isValidName(segment));
412
+ const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.isValidSegment({ segment }));
183
413
  if (invalidSegmentIndex !== -1) {
184
414
  throw (0, core_1.getError)({
185
415
  error: common_2.StaticAssetErrors.FOLDER_SEGMENT_INVALID,
186
416
  message: `Invalid folder path segment: ${folderSegments[invalidSegmentIndex]}`,
187
417
  });
188
418
  }
419
+ };
420
+ refuseLabelsInQuery({ query: ctx.req.query() });
421
+ // The early exit, before the body is spooled. Not authoritative: a multipart envelope is
422
+ // larger than its files, so the per-file check below is the one that decides.
423
+ if (maxBytes !== undefined) {
424
+ const declaredLength = Number(ctx.req.header(common_1.HTTP.Headers.CONTENT_LENGTH));
425
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
426
+ throw (0, core_1.getError)({
427
+ error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
428
+ message: `Request body of ${declaredLength} bytes exceeds the maximum of ${maxBytes}`,
429
+ });
430
+ }
189
431
  }
190
- const filesArray = await (0, ignis_helpers_1.parseMultipartBody)({
432
+ const { files: filesArray, fields } = await (0, ignis_helpers_1.parseMultipartBody)({
191
433
  context: ctx,
192
434
  storage: options?.parseMultipartBody?.storage,
193
435
  uploadDir: options?.parseMultipartBody?.uploadDir,
194
436
  });
437
+ // Labels travel with the payload, not the URL, so the folder check waits for the parse.
438
+ const query = readUploadLabels({ fields });
439
+ const folderPath = query.folderPath;
440
+ if (folderPath) {
441
+ validateFolderPath(folderPath);
442
+ }
195
443
  const spoolPaths = filesArray
196
444
  .map(file => file.path)
197
445
  .filter((filePath) => Boolean(filePath));
@@ -206,6 +454,13 @@ class AssetControllerFactory extends core_1.BaseHelper {
206
454
  message: `Empty file content | name: ${file.originalname}`,
207
455
  });
208
456
  }
457
+ // Authoritative: `buffer.length`, not the client-declared `size`.
458
+ if (maxBytes !== undefined && buffer.length > maxBytes) {
459
+ throw (0, core_1.getError)({
460
+ error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
461
+ message: `File ${file.originalname} of ${buffer.length} bytes exceeds the maximum of ${maxBytes}`,
462
+ });
463
+ }
209
464
  return {
210
465
  originalName: file.originalname,
211
466
  mimetype: file.mimetype,
@@ -215,11 +470,25 @@ class AssetControllerFactory extends core_1.BaseHelper {
215
470
  folderPath: folderPath ?? undefined,
216
471
  };
217
472
  });
473
+ // Built per request: the hook is offered the bucket, which only the route knows. No
474
+ // hook leaves `normalizeNameFn` exactly as configured, so the stored name is unchanged.
475
+ const normalizeNameFn = resolveObjectName
476
+ ? (naming) => {
477
+ const defaultKey = options?.normalizeNameFn
478
+ ? options.normalizeNameFn(naming)
479
+ : defaultObjectName(naming);
480
+ return resolveObjectName({
481
+ bucket: { name: bucketName },
482
+ file: naming.file,
483
+ defaultKey,
484
+ });
485
+ }
486
+ : options?.normalizeNameFn;
218
487
  uploaded = await helper.upload({
219
- bucket: bucketName,
488
+ bucket: { name: bucketName },
220
489
  files: modifiedFiles,
221
- normalizeNameFn: options?.normalizeNameFn,
222
- normalizeLinkFn: options?.normalizeLinkFn,
490
+ normalizeNameFn,
491
+ normalizeLinkFn,
223
492
  // Without this the helper re-validates against its own hard default of 2, so an app configured for a deeper tree spools the body and only then fails inside the helper.
224
493
  maxFolderDepth,
225
494
  });
@@ -234,47 +503,30 @@ class AssetControllerFactory extends core_1.BaseHelper {
234
503
  for (const uploadResult of uploaded) {
235
504
  try {
236
505
  const fileStat = await helper.getStat({
237
- bucket: uploadResult.bucketName,
238
- name: uploadResult.objectName,
506
+ bucket: uploadResult.bucket,
507
+ object: uploadResult.object,
239
508
  });
240
- const { data: createdMetaLink } = metaLink.createMetaLink
241
- ? await metaLink.createMetaLink({
242
- uploadResult,
243
- fileStat,
244
- query,
245
- })
246
- : await metaLink.repository.create({
247
- data: {
248
- bucketName: uploadResult.bucketName,
249
- objectName: uploadResult.objectName,
250
- link: uploadResult.link,
251
- mimetype: fileStat.metadata?.['mimetype'],
252
- size: fileStat.size,
253
- etag: fileStat.etag,
254
- metadata: fileStat.metadata,
255
- storageType: storage,
256
- isSynced: true,
257
- principalId: query.principalId ? String(query.principalId) : undefined,
258
- principalType: query.principalType
259
- ? String(query.principalType)
260
- : undefined,
261
- variant: query.variant ? String(query.variant) : undefined,
262
- },
263
- });
264
- results.push({ ...uploadResult, metaLink: createdMetaLink });
509
+ const createdMetaLink = await createMetaLinkRow({
510
+ metaLink,
511
+ helper,
512
+ storage,
513
+ uploadResult,
514
+ fileStat,
515
+ query,
516
+ });
517
+ results.push({ ...uploadResult, metaLink: { data: createdMetaLink } });
265
518
  }
266
519
  catch (error) {
267
520
  this.logger
268
521
  .for('UPLOAD')
269
- .error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.objectName, error);
522
+ .error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.object.key, error);
270
523
  results.push({
271
524
  ...uploadResult,
272
- metaLink: null,
273
525
  // A CODE, never the driver text. This handler returns 200, so it bypasses the
274
526
  // error middleware and `database.handler` - the two places that strip
275
527
  // `detail`/`table`/`constraint` - and shipped raw constraint names to the client.
276
528
  // The real error is already logged in full immediately above.
277
- metaLinkError: 'META_LINK_CREATE_FAILED',
529
+ metaLink: { error: common_2.META_LINK_CREATE_FAILED },
278
530
  });
279
531
  }
280
532
  }
@@ -282,36 +534,39 @@ class AssetControllerFactory extends core_1.BaseHelper {
282
534
  },
283
535
  });
284
536
  this.bindRoute({
285
- configs: { ...base_definition_1.StaticAssetDefinitions.DELETE_BUCKET, ...routes?.deleteBucket },
537
+ configs: { ...definitions.DELETE_OBJECT, ...routes?.deleteObject },
286
538
  }).to({
287
539
  handler: async (ctx) => {
288
- const { bucketName } = ctx.req.valid('param');
289
- if (!helper.isValidName(bucketName)) {
290
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
291
- }
292
- const isRemovedBucket = await helper.removeBucket({
293
- name: bucketName,
540
+ const params = ctx.req.valid('param');
541
+ const bucketName = await resolveBucket({
542
+ configured: bucket,
543
+ param: params.bucketName,
294
544
  });
295
- return ctx.json({ isDeleted: isRemovedBucket }, common_1.HTTP.ResultCodes.RS_2.Ok);
296
- },
297
- });
298
- this.bindRoute({
299
- configs: { ...base_definition_1.StaticAssetDefinitions.DELETE_OBJECT, ...routes?.deleteObject },
300
- }).to({
301
- handler: async (ctx) => {
302
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
303
- const objectName = readObjectName(rawObjectName);
304
- if (!helper.isValidName(bucketName)) {
545
+ const objectName = readObjectName(params.objectName);
546
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
305
547
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
306
548
  }
307
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
549
+ if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
308
550
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
309
551
  }
310
- await helper.removeObject({ bucket: bucketName, name: objectName });
552
+ // Deliberately idempotent: S3 answers 200 for a key that was never there, and consumers
553
+ // already depend on it. `disk` throws instead, so the two are reconciled here, not below.
554
+ try {
555
+ await helper.removeObject({
556
+ bucket: { name: bucketName },
557
+ object: { key: objectName },
558
+ });
559
+ }
560
+ catch (error) {
561
+ if (!(0, ignis_helpers_1.isNotFoundError)({ error })) {
562
+ throw error;
563
+ }
564
+ }
311
565
  if (!useMetaLink || !metaLink) {
312
566
  return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
313
567
  }
314
- metaLink.repository
568
+ const deleteRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
569
+ deleteRepository
315
570
  .deleteAll({
316
571
  where: {
317
572
  bucketName,
@@ -332,12 +587,16 @@ class AssetControllerFactory extends core_1.BaseHelper {
332
587
  },
333
588
  });
334
589
  this.bindRoute({
335
- configs: { ...base_definition_1.StaticAssetDefinitions.LIST_OBJECTS, ...routes?.listObjects },
590
+ configs: { ...definitions.LIST_OBJECTS, ...routes?.listObjects },
336
591
  }).to({
337
592
  handler: async (ctx) => {
338
- const { bucketName } = ctx.req.valid('param');
593
+ // A configured bucket drops `params` from this route entirely, so it is read only when it exists.
594
+ const bucketParam = hasConfiguredBucket
595
+ ? undefined
596
+ : ctx.req.valid('param').bucketName;
597
+ const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
339
598
  const { prefix, recursive, maxKeys } = ctx.req.valid('query');
340
- if (!helper.isValidName(bucketName)) {
599
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
341
600
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
342
601
  }
343
602
  // A NaN or 0 maxKeys is silently treated as "unlimited" by the storage backends, so an unparsable value must be rejected instead of forwarded.
@@ -352,7 +611,7 @@ class AssetControllerFactory extends core_1.BaseHelper {
352
611
  }
353
612
  }
354
613
  const objects = await helper.listObjects({
355
- bucket: bucketName,
614
+ bucket: { name: bucketName },
356
615
  prefix,
357
616
  useRecursive: recursive === 'true',
358
617
  maxKeys: resolvedMaxKeys,
@@ -360,27 +619,190 @@ class AssetControllerFactory extends core_1.BaseHelper {
360
619
  return ctx.json(objects, common_1.HTTP.ResultCodes.RS_2.Ok);
361
620
  },
362
621
  });
622
+ if (directUpload) {
623
+ if (!hasConfiguredBucket) {
624
+ throw (0, core_1.getError)({
625
+ message: `[${name}] directUpload needs controller.bucket | a policy names one bucket, and a bucket in the URL is a bucket the caller chooses`,
626
+ });
627
+ }
628
+ const pendingPrefix = directUpload.pendingPrefix ?? common_2.DEFAULT_PENDING_PREFIX;
629
+ this.bindRoute({
630
+ configs: { ...definitions.UPLOAD_POLICY, ...routes?.uploadPolicy },
631
+ }).to({
632
+ handler: async (ctx) => {
633
+ if (!(await directUpload.authorize({ context: ctx }))) {
634
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.UPLOAD_NOT_AUTHORIZED });
635
+ }
636
+ const { files } = ctx.req.valid('json');
637
+ const bucketName = await resolveBucket({ configured: bucket });
638
+ const policies = [];
639
+ for (const file of files) {
640
+ if (file.size !== undefined && file.size > directUpload.maxBytes) {
641
+ throw (0, core_1.getError)({
642
+ error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
643
+ message: `File '${file.fileName}' is ${file.size} bytes, over the ${directUpload.maxBytes} the policy allows`,
644
+ });
645
+ }
646
+ // Generated, never taken from the caller: a key the caller picks is a key the caller
647
+ // can collide with, and the pending object is the one thing a policy authorizes.
648
+ const objectName = `${pendingPrefix}${core_1.UuidHelper.getInstance().v4()}/${file.fileName}`;
649
+ const policy = await helper.presignPost({
650
+ bucket: { name: bucketName },
651
+ keyPrefix: pendingPrefix,
652
+ maxBytes: directUpload.maxBytes,
653
+ contentType: file.contentType,
654
+ expiresIn: directUpload.expiresIn,
655
+ });
656
+ policies.push({
657
+ ...policy,
658
+ objectName,
659
+ commitToken: (0, common_3.buildCommitToken)({
660
+ payload: {
661
+ bucket: bucketName,
662
+ key: objectName,
663
+ expiresAt: new Date(policy.expiresAt).getTime(),
664
+ },
665
+ secretKey: directUpload.secretKey,
666
+ }),
667
+ });
668
+ }
669
+ return ctx.json(policies, common_1.HTTP.ResultCodes.RS_2.Ok);
670
+ },
671
+ });
672
+ this.bindRoute({
673
+ configs: { ...definitions.UPLOAD_COMMIT, ...routes?.uploadCommit },
674
+ }).to({
675
+ handler: async (ctx) => {
676
+ const commitQuery = ctx.req.query();
677
+ refuseLabelsInQuery({ query: commitQuery, names: COMMIT_LABEL_NAMES });
678
+ refuseLabelsInQuery({
679
+ query: commitQuery,
680
+ names: ['folderPath'],
681
+ reason: 'folderPath was decided when the upload policy was issued; the commit route cannot move the object',
682
+ });
683
+ const { commitToken, ...labels } = ctx.req.valid('json');
684
+ // Verified BEFORE any storage call. That is what keeps this route from being a name
685
+ // prober: a forged token fails without a round trip, so timing says nothing.
686
+ const payload = (0, common_3.readCommitToken)({
687
+ token: commitToken,
688
+ secretKey: directUpload.secretKey,
689
+ });
690
+ // Labels, not authorization - the token deliberately carries no business fields, so
691
+ // they travel beside it rather than inside it.
692
+ const query = readUploadLabels({ fields: labels });
693
+ const bucketRef = { name: payload.bucket };
694
+ const pendingObject = { key: payload.key };
695
+ const object = { key: payload.key.slice(pendingPrefix.length) };
696
+ // Before the copy, so a hook that throws leaves the object under the pending prefix
697
+ // for a lifecycle rule, rather than stranding it at the final key.
698
+ await directUpload.onCommit?.({ bucket: bucketRef, pendingObject, object });
699
+ await helper.copyObject({
700
+ bucket: bucketRef,
701
+ source: pendingObject,
702
+ destination: object,
703
+ });
704
+ // The copy IS the commit; removing the temporary object is cleanup. A failure here
705
+ // leaves one object for the lifecycle rule, and failing the request would tell the
706
+ // caller their upload did not work when it did.
707
+ await helper
708
+ .removeObject({ bucket: bucketRef, object: pendingObject })
709
+ .catch(error => {
710
+ this.logger
711
+ .for('UPLOAD_COMMIT')
712
+ .warn('Committed, but the pending object remains | key: %s | error: %s', pendingObject.key, error);
713
+ });
714
+ const link = (0, exports.buildObjectLink)({
715
+ basePath: normalizedBasePath,
716
+ bucket: bucketRef,
717
+ object,
718
+ hasConfiguredBucket,
719
+ rawObjectPath,
720
+ });
721
+ // The two paths have to agree: without this a client reading `metaLink` off the
722
+ // response renders an empty state with no error - the failure nobody reports.
723
+ let metaLinkResult;
724
+ if (useMetaLink && metaLink) {
725
+ try {
726
+ const fileStat = await helper.getStat({ bucket: bucketRef, object });
727
+ const data = await createMetaLinkRow({
728
+ metaLink,
729
+ helper,
730
+ storage,
731
+ uploadResult: {
732
+ bucket: bucketRef,
733
+ object: {
734
+ key: object.key,
735
+ size: fileStat.size,
736
+ contentType: fileStat.metadata?.mimetype ??
737
+ helper.getMimeType({ filename: object.key }),
738
+ },
739
+ link,
740
+ },
741
+ fileStat,
742
+ query,
743
+ });
744
+ metaLinkResult = { data };
745
+ }
746
+ catch (error) {
747
+ // The object IS committed by now. A 500 would send the caller back with a token
748
+ // whose source no longer exists, so this rides the body as a code, like UPLOAD.
749
+ this.logger
750
+ .for('UPLOAD_COMMIT')
751
+ .error('Failed to create MetaLink | objectName: %s | Error: %s', object.key, error);
752
+ metaLinkResult = { error: common_2.META_LINK_CREATE_FAILED };
753
+ }
754
+ }
755
+ return ctx.json({
756
+ bucket: bucketRef,
757
+ object,
758
+ link,
759
+ ...(metaLinkResult === undefined ? {} : { metaLink: metaLinkResult }),
760
+ }, common_1.HTTP.ResultCodes.RS_2.Ok);
761
+ },
762
+ });
763
+ }
363
764
  if (useMetaLink && metaLink) {
364
765
  this.bindRoute({
365
- configs: { ...base_definition_1.StaticAssetDefinitions.RECREATE_METALINK, ...routes?.recreateMetaLink },
766
+ configs: { ...definitions.RECREATE_METALINK, ...routes?.recreateMetaLink },
366
767
  }).to({
367
768
  handler: async (ctx) => {
368
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
369
- const objectName = readObjectName(rawObjectName);
370
- if (!helper.isValidName(bucketName)) {
769
+ // This route takes no labels anywhere: one in the query would land an unlabelled row.
770
+ refuseLabelsInQuery({
771
+ query: ctx.req.query(),
772
+ reason: 'This route recreates a MetaLink for an existing object and takes no labels',
773
+ });
774
+ const params = ctx.req.valid('param');
775
+ const bucketName = await resolveBucket({
776
+ configured: bucket,
777
+ param: params.bucketName,
778
+ });
779
+ const objectName = readObjectName(params.objectName);
780
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
371
781
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
372
782
  }
373
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
783
+ if (!helper.isValidObjectKey({ object: { key: objectName }, maxDepth: maxFolderDepth })) {
374
784
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
375
785
  }
376
786
  const fileStat = await helper.getStat({
377
- bucket: bucketName,
378
- name: objectName,
787
+ bucket: { name: bucketName },
788
+ object: { key: objectName },
379
789
  });
380
790
  const link = options?.normalizeLinkFn
381
- ? options.normalizeLinkFn({ bucketName, normalizeName: objectName })
382
- : `${normalizedBasePath}/buckets/${bucketName}/objects/${encodeObjectPath(objectName)}`;
383
- const existing = await metaLink.repository.findOne({
791
+ ? options.normalizeLinkFn({
792
+ bucket: { name: bucketName },
793
+ object: { key: objectName },
794
+ })
795
+ : (0, exports.buildObjectLink)({
796
+ basePath: normalizedBasePath,
797
+ bucket: { name: bucketName },
798
+ object: { key: objectName },
799
+ hasConfiguredBucket,
800
+ rawObjectPath,
801
+ });
802
+ // Read per call, never once per handler: an application may rebind the repository
803
+ // between two of these, and a copy held across them would answer from the old binding.
804
+ const findRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
805
+ const existing = await findRepository.findOne({
384
806
  filter: {
385
807
  where: {
386
808
  bucketName,
@@ -389,7 +811,8 @@ class AssetControllerFactory extends core_1.BaseHelper {
389
811
  },
390
812
  });
391
813
  if (existing) {
392
- await metaLink.repository.updateById({
814
+ const updateRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
815
+ await updateRepository.updateById({
393
816
  id: existing.id,
394
817
  data: {
395
818
  link,
@@ -401,15 +824,17 @@ class AssetControllerFactory extends core_1.BaseHelper {
401
824
  isSynced: true,
402
825
  },
403
826
  });
404
- const updatedMetaLink = await metaLink.repository.findById({ id: existing.id });
827
+ const readRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
828
+ const updatedMetaLink = await readRepository.findById({ id: existing.id });
405
829
  return ctx.json({ success: true, metaLink: updatedMetaLink }, common_1.HTTP.ResultCodes.RS_2.Ok);
406
830
  }
407
- const createdMetaLink = await metaLink.repository.create({
831
+ const createRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
832
+ const createdMetaLink = await createRepository.create({
408
833
  data: {
409
834
  bucketName,
410
835
  objectName,
411
836
  link,
412
- mimetype: fileStat.metadata?.['mimetype'],
837
+ mimetype: fileStat.metadata?.mimetype ?? helper.getMimeType({ filename: objectName }),
413
838
  size: fileStat.size,
414
839
  etag: fileStat.etag,
415
840
  metadata: fileStat.metadata,
@@ -421,6 +846,8 @@ class AssetControllerFactory extends core_1.BaseHelper {
421
846
  },
422
847
  });
423
848
  }
849
+ // Last, so a built-in route always wins a path collision with an application's own.
850
+ defineExtraRoutes?.({ controller: this, helper, basePath: normalizedBasePath });
424
851
  }
425
852
  };
426
853
  GeneratedStaticAssetController = __decorate([