@venizia/ignis 0.2.0-4 → 0.2.0-41

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