@rapidrest/service-core 1.0.0-rc.9 → 1.0.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 (380) hide show
  1. package/LICENSE +382 -23
  2. package/README.md +79 -5
  3. package/dist/lib/ApiErrors.js +10 -0
  4. package/dist/lib/ApiErrors.js.map +1 -1
  5. package/dist/lib/BackgroundService.js +1 -4
  6. package/dist/lib/BackgroundService.js.map +1 -1
  7. package/dist/lib/BackgroundServiceManager.js +47 -6
  8. package/dist/lib/BackgroundServiceManager.js.map +1 -1
  9. package/dist/lib/BulkError.js +4 -0
  10. package/dist/lib/BulkError.js.map +1 -1
  11. package/dist/lib/EventListenerManager.js +53 -18
  12. package/dist/lib/EventListenerManager.js.map +1 -1
  13. package/dist/lib/NetUtils.js +1 -0
  14. package/dist/lib/NetUtils.js.map +1 -1
  15. package/dist/lib/NotificationUtils.js +27 -8
  16. package/dist/lib/NotificationUtils.js.map +1 -1
  17. package/dist/lib/ObjectFactory.js +25 -44
  18. package/dist/lib/ObjectFactory.js.map +1 -1
  19. package/dist/lib/OpenApiSpec.js +31 -11
  20. package/dist/lib/OpenApiSpec.js.map +1 -1
  21. package/dist/lib/Server.js +74 -110
  22. package/dist/lib/Server.js.map +1 -1
  23. package/dist/lib/auth/AuthMiddleware.js +11 -16
  24. package/dist/lib/auth/AuthMiddleware.js.map +1 -1
  25. package/dist/lib/auth/JWTStrategy.js +68 -17
  26. package/dist/lib/auth/JWTStrategy.js.map +1 -1
  27. package/dist/lib/auth/index.js +0 -1
  28. package/dist/lib/auth/index.js.map +1 -1
  29. package/dist/lib/database/ConnectionKinds.js +15 -0
  30. package/dist/lib/database/ConnectionKinds.js.map +1 -1
  31. package/dist/lib/database/ConnectionManager.js +140 -61
  32. package/dist/lib/database/ConnectionManager.js.map +1 -1
  33. package/dist/lib/database/MongoConnection.js +13 -3
  34. package/dist/lib/database/MongoConnection.js.map +1 -1
  35. package/dist/lib/database/MongoRepository.js +61 -15
  36. package/dist/lib/database/MongoRepository.js.map +1 -1
  37. package/dist/lib/database/MongoSchemaSync.js +3 -3
  38. package/dist/lib/database/MongoSchemaSync.js.map +1 -1
  39. package/dist/lib/database/NamingUtils.js +3 -2
  40. package/dist/lib/database/NamingUtils.js.map +1 -1
  41. package/dist/lib/database/RedisCache.js +64 -0
  42. package/dist/lib/database/RedisCache.js.map +1 -0
  43. package/dist/lib/database/TypeOrmSupport.js +53 -13
  44. package/dist/lib/database/TypeOrmSupport.js.map +1 -1
  45. package/dist/lib/database/index.js +1 -0
  46. package/dist/lib/database/index.js.map +1 -1
  47. package/dist/lib/decorators/DatabaseDecorators.js +264 -20
  48. package/dist/lib/decorators/DatabaseDecorators.js.map +1 -1
  49. package/dist/lib/decorators/DocDecorators.js +30 -9
  50. package/dist/lib/decorators/DocDecorators.js.map +1 -1
  51. package/dist/lib/decorators/EventDecorators.js +1 -0
  52. package/dist/lib/decorators/EventDecorators.js.map +1 -1
  53. package/dist/lib/decorators/ModelDecorators.js +39 -23
  54. package/dist/lib/decorators/ModelDecorators.js.map +1 -1
  55. package/dist/lib/decorators/PersistenceDecorators.js +4 -3
  56. package/dist/lib/decorators/PersistenceDecorators.js.map +1 -1
  57. package/dist/lib/decorators/RouteDecorators.js +139 -36
  58. package/dist/lib/decorators/RouteDecorators.js.map +1 -1
  59. package/dist/lib/http/IWebSocketShim.js +2 -0
  60. package/dist/lib/http/IWebSocketShim.js.map +1 -0
  61. package/dist/lib/http/MiddlewareChain.js +167 -0
  62. package/dist/lib/http/MiddlewareChain.js.map +1 -0
  63. package/dist/lib/http/RuntimeDetect.js +9 -0
  64. package/dist/lib/http/RuntimeDetect.js.map +1 -0
  65. package/dist/lib/http/bun/BunAdapters.js +326 -0
  66. package/dist/lib/http/bun/BunAdapters.js.map +1 -0
  67. package/dist/lib/http/bun/BunRouter.js +391 -0
  68. package/dist/lib/http/bun/BunRouter.js.map +1 -0
  69. package/dist/lib/http/bun/BunWebSocket.js +49 -0
  70. package/dist/lib/http/bun/BunWebSocket.js.map +1 -0
  71. package/dist/lib/http/index.js +16 -3
  72. package/dist/lib/http/index.js.map +1 -1
  73. package/dist/lib/http/session/RedisSessionStore.js +30 -0
  74. package/dist/lib/http/session/RedisSessionStore.js.map +1 -0
  75. package/dist/lib/http/session/SessionManager.js +113 -0
  76. package/dist/lib/http/session/SessionManager.js.map +1 -0
  77. package/dist/lib/http/session/SessionStore.js +6 -0
  78. package/dist/lib/http/session/SessionStore.js.map +1 -0
  79. package/dist/lib/http/session/sessionMiddleware.js +42 -0
  80. package/dist/lib/http/session/sessionMiddleware.js.map +1 -0
  81. package/dist/lib/http/types.js +0 -3
  82. package/dist/lib/http/types.js.map +1 -1
  83. package/dist/lib/http/uWS/Adapters.js +421 -0
  84. package/dist/lib/http/uWS/Adapters.js.map +1 -0
  85. package/dist/lib/http/{Router.js → uWS/Router.js} +73 -173
  86. package/dist/lib/http/uWS/Router.js.map +1 -0
  87. package/dist/lib/http/{WebSocket.js → uWS/WebSocket.js} +4 -1
  88. package/dist/lib/http/uWS/WebSocket.js.map +1 -0
  89. package/dist/lib/models/BaseEntity.js +2 -1
  90. package/dist/lib/models/BaseEntity.js.map +1 -1
  91. package/dist/lib/models/BaseMongoEntity.js +2 -2
  92. package/dist/lib/models/BaseMongoEntity.js.map +1 -1
  93. package/dist/lib/models/ModelUtils.js +248 -101
  94. package/dist/lib/models/ModelUtils.js.map +1 -1
  95. package/dist/lib/models/RecoverableBaseEntity.js +6 -1
  96. package/dist/lib/models/RecoverableBaseEntity.js.map +1 -1
  97. package/dist/lib/models/RecoverableBaseMongoEntity.js +1 -0
  98. package/dist/lib/models/RecoverableBaseMongoEntity.js.map +1 -1
  99. package/dist/lib/models/RepoUtils.js +641 -213
  100. package/dist/lib/models/RepoUtils.js.map +1 -1
  101. package/dist/lib/models/SimpleEntity.js +1 -0
  102. package/dist/lib/models/SimpleEntity.js.map +1 -1
  103. package/dist/lib/models/SimpleMongoEntity.js +2 -1
  104. package/dist/lib/models/SimpleMongoEntity.js.map +1 -1
  105. package/dist/lib/models/StatusExtraData.js +4 -0
  106. package/dist/lib/models/StatusExtraData.js.map +1 -1
  107. package/dist/lib/routes/{AdminRoute.js → BaseAdminRoute.js} +98 -80
  108. package/dist/lib/routes/BaseAdminRoute.js.map +1 -0
  109. package/dist/lib/routes/{MetricsRoute.js → BaseMetricsRoute.js} +29 -15
  110. package/dist/lib/routes/BaseMetricsRoute.js.map +1 -0
  111. package/dist/lib/routes/{OpenAPIRoute.js → BaseOpenAPIRoute.js} +32 -32
  112. package/dist/lib/routes/BaseOpenAPIRoute.js.map +1 -0
  113. package/dist/lib/routes/BasePushRoute.js +305 -0
  114. package/dist/lib/routes/BasePushRoute.js.map +1 -0
  115. package/dist/lib/routes/BaseStaticRoute.js +153 -0
  116. package/dist/lib/routes/BaseStaticRoute.js.map +1 -0
  117. package/dist/lib/routes/BaseStatusRoute.js +80 -0
  118. package/dist/lib/routes/BaseStatusRoute.js.map +1 -0
  119. package/dist/lib/routes/CRUDRoute.js +284 -0
  120. package/dist/lib/routes/CRUDRoute.js.map +1 -0
  121. package/dist/lib/routes/ModelRoute.js +84 -57
  122. package/dist/lib/routes/ModelRoute.js.map +1 -1
  123. package/dist/lib/routes/RouteUtils.js +108 -20
  124. package/dist/lib/routes/RouteUtils.js.map +1 -1
  125. package/dist/lib/routes/index.js +7 -4
  126. package/dist/lib/routes/index.js.map +1 -1
  127. package/dist/lib/security/ACLUtils.js +297 -170
  128. package/dist/lib/security/ACLUtils.js.map +1 -1
  129. package/dist/lib/security/AccessControlList.js +18 -10
  130. package/dist/lib/security/AccessControlList.js.map +1 -1
  131. package/dist/lib/security/AccessControlListMongo.js +19 -68
  132. package/dist/lib/security/AccessControlListMongo.js.map +1 -1
  133. package/dist/lib/security/AccessControlListSQL.js +21 -70
  134. package/dist/lib/security/AccessControlListSQL.js.map +1 -1
  135. package/dist/lib/security/BaseACLRoute.js +239 -0
  136. package/dist/lib/security/BaseACLRoute.js.map +1 -0
  137. package/dist/lib/security/index.js +3 -0
  138. package/dist/lib/security/index.js.map +1 -1
  139. package/dist/lib/test/request.js +62 -5
  140. package/dist/lib/test/request.js.map +1 -1
  141. package/dist/lib/test/requestws.js +34 -10
  142. package/dist/lib/test/requestws.js.map +1 -1
  143. package/dist/types/ApiErrors.d.ts +8 -2
  144. package/dist/types/BackgroundService.d.ts +0 -5
  145. package/dist/types/BackgroundServiceManager.d.ts +2 -1
  146. package/dist/types/EventListenerManager.d.ts +7 -6
  147. package/dist/types/NotificationUtils.d.ts +5 -3
  148. package/dist/types/OpenApiSpec.d.ts +2 -2
  149. package/dist/types/Server.d.ts +28 -10
  150. package/dist/types/auth/AuthMiddleware.d.ts +3 -3
  151. package/dist/types/auth/AuthStrategy.d.ts +10 -7
  152. package/dist/types/auth/JWTStrategy.d.ts +6 -4
  153. package/dist/types/auth/index.d.ts +0 -1
  154. package/dist/types/database/ConnectionKinds.d.ts +7 -0
  155. package/dist/types/database/ConnectionManager.d.ts +29 -4
  156. package/dist/types/database/MongoConnection.d.ts +16 -4
  157. package/dist/types/database/MongoRepository.d.ts +32 -11
  158. package/dist/types/database/RedisCache.d.ts +23 -0
  159. package/dist/types/database/TypeOrmSupport.d.ts +4 -4
  160. package/dist/types/database/index.d.ts +1 -0
  161. package/dist/types/decorators/DatabaseDecorators.d.ts +139 -8
  162. package/dist/types/decorators/DocDecorators.d.ts +11 -2
  163. package/dist/types/decorators/ModelDecorators.d.ts +12 -5
  164. package/dist/types/decorators/PersistenceDecorators.d.ts +11 -4
  165. package/dist/types/decorators/RouteDecorators.d.ts +36 -6
  166. package/dist/types/http/IWebSocketShim.d.ts +11 -0
  167. package/dist/types/http/MiddlewareChain.d.ts +47 -0
  168. package/dist/types/http/RuntimeDetect.d.ts +2 -0
  169. package/dist/types/http/bun/BunAdapters.d.ts +139 -0
  170. package/dist/types/http/bun/BunRouter.d.ts +79 -0
  171. package/dist/types/http/bun/BunWebSocket.d.ts +35 -0
  172. package/dist/types/http/index.d.ts +16 -6
  173. package/dist/types/http/session/RedisSessionStore.d.ts +15 -0
  174. package/dist/types/http/session/SessionManager.d.ts +35 -0
  175. package/dist/types/http/session/SessionStore.d.ts +12 -0
  176. package/dist/types/http/session/sessionMiddleware.d.ts +10 -0
  177. package/dist/types/http/types.d.ts +42 -3
  178. package/dist/types/http/uWS/Adapters.d.ts +129 -0
  179. package/dist/types/http/{Router.d.ts → uWS/Router.d.ts} +14 -38
  180. package/dist/types/http/{WebSocket.d.ts → uWS/WebSocket.d.ts} +6 -5
  181. package/dist/types/models/BaseEntity.d.ts +1 -1
  182. package/dist/types/models/ModelUtils.d.ts +47 -14
  183. package/dist/types/models/RecoverableBaseEntity.d.ts +3 -1
  184. package/dist/types/models/RepoUtils.d.ts +84 -14
  185. package/dist/types/routes/BaseAdminRoute.d.ts +65 -0
  186. package/dist/types/routes/BaseMetricsRoute.d.ts +37 -0
  187. package/dist/types/routes/BaseOpenAPIRoute.d.ts +33 -0
  188. package/dist/types/routes/BasePushRoute.d.ts +53 -0
  189. package/dist/types/routes/BaseStaticRoute.d.ts +44 -0
  190. package/dist/types/routes/BaseStatusRoute.d.ts +37 -0
  191. package/dist/types/routes/CRUDRoute.d.ts +57 -0
  192. package/dist/types/routes/ModelRoute.d.ts +39 -36
  193. package/dist/types/routes/RouteUtils.d.ts +19 -0
  194. package/dist/types/routes/index.d.ts +7 -4
  195. package/dist/types/security/ACLUtils.d.ts +94 -18
  196. package/dist/types/security/AccessControlList.d.ts +37 -59
  197. package/dist/types/security/AccessControlListMongo.d.ts +3 -8
  198. package/dist/types/security/AccessControlListSQL.d.ts +3 -8
  199. package/dist/types/security/BaseACLRoute.d.ts +73 -0
  200. package/dist/types/security/index.d.ts +3 -0
  201. package/dist/types/test/request.d.ts +18 -1
  202. package/package.json +135 -129
  203. package/tsconfig.export.json +17 -0
  204. package/dist/lib/auth/BasicStrategy.js +0 -113
  205. package/dist/lib/auth/BasicStrategy.js.map +0 -1
  206. package/dist/lib/http/Adapters.js +0 -241
  207. package/dist/lib/http/Adapters.js.map +0 -1
  208. package/dist/lib/http/Router.js.map +0 -1
  209. package/dist/lib/http/WebSocket.js.map +0 -1
  210. package/dist/lib/routes/AdminRoute.js.map +0 -1
  211. package/dist/lib/routes/MetricsRoute.js.map +0 -1
  212. package/dist/lib/routes/OpenAPIRoute.js.map +0 -1
  213. package/dist/lib/routes/StatusRoute.js +0 -55
  214. package/dist/lib/routes/StatusRoute.js.map +0 -1
  215. package/dist/lib/security/ACLRouteMongo.js +0 -194
  216. package/dist/lib/security/ACLRouteMongo.js.map +0 -1
  217. package/dist/lib/security/ACLRouteSQL.js +0 -193
  218. package/dist/lib/security/ACLRouteSQL.js.map +0 -1
  219. package/dist/types/auth/BasicStrategy.d.ts +0 -37
  220. package/dist/types/http/Adapters.d.ts +0 -68
  221. package/dist/types/routes/AdminRoute.d.ts +0 -47
  222. package/dist/types/routes/MetricsRoute.d.ts +0 -15
  223. package/dist/types/routes/OpenAPIRoute.d.ts +0 -17
  224. package/dist/types/routes/StatusRoute.d.ts +0 -11
  225. package/dist/types/security/ACLRouteMongo.d.ts +0 -19
  226. package/dist/types/security/ACLRouteSQL.d.ts +0 -19
  227. package/docs/Makefile +0 -20
  228. package/docs/conf.py +0 -58
  229. package/docs/index.rst +0 -17
  230. package/docs/make.bat +0 -35
  231. package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/README.md +0 -13
  232. package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/MongoRepository.md +0 -25
  233. package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/RedisConnection.md +0 -25
  234. package/docs/reference/@rapidrest/namespaces/DatabaseDecorators/functions/Repository.md +0 -25
  235. package/docs/reference/@rapidrest/namespaces/DocDecorators/README.md +0 -23
  236. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Default.md +0 -25
  237. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Description.md +0 -25
  238. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Document.md +0 -25
  239. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Example.md +0 -25
  240. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Format.md +0 -25
  241. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Returns.md +0 -28
  242. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Summary.md +0 -25
  243. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/Tags.md +0 -25
  244. package/docs/reference/@rapidrest/namespaces/DocDecorators/functions/TypeInfo.md +0 -28
  245. package/docs/reference/@rapidrest/namespaces/DocDecorators/interfaces/DocumentsData.md +0 -57
  246. package/docs/reference/@rapidrest/namespaces/EventDecorators/README.md +0 -12
  247. package/docs/reference/@rapidrest/namespaces/EventDecorators/functions/EventListener.md +0 -17
  248. package/docs/reference/@rapidrest/namespaces/EventDecorators/functions/OnEvent.md +0 -26
  249. package/docs/reference/@rapidrest/namespaces/ModelDecorators/README.md +0 -26
  250. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Cache.md +0 -25
  251. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/ChildEntity.md +0 -18
  252. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/DataStore.md +0 -25
  253. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Identifier.md +0 -27
  254. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Protect.md +0 -35
  255. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Reference.md +0 -25
  256. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/Shard.md +0 -27
  257. package/docs/reference/@rapidrest/namespaces/ModelDecorators/functions/TrackChanges.md +0 -26
  258. package/docs/reference/@rapidrest/namespaces/ModelDecorators/interfaces/PendingTypeOrmColumn.md +0 -45
  259. package/docs/reference/@rapidrest/namespaces/ModelDecorators/variables/pendingTypeOrmColumns.md +0 -14
  260. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/README.md +0 -27
  261. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Column.md +0 -25
  262. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Entity.md +0 -25
  263. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Index.md +0 -119
  264. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/PrimaryColumn.md +0 -25
  265. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/Unique.md +0 -68
  266. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getColumnMetadata.md +0 -26
  267. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getEntityName.md +0 -26
  268. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/functions/getIndexMetadata.md +0 -27
  269. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/CollationOptions.md +0 -79
  270. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/ColumnInfo.md +0 -41
  271. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/ColumnOptions.md +0 -51
  272. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/EntityOptions.md +0 -32
  273. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/IndexInfo.md +0 -41
  274. package/docs/reference/@rapidrest/namespaces/PersistenceDecorators/interfaces/IndexOptions.md +0 -61
  275. package/docs/reference/@rapidrest/namespaces/RouteDecorators/README.md +0 -36
  276. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/After.md +0 -26
  277. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Auth.md +0 -33
  278. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/AuthResult.md +0 -31
  279. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Before.md +0 -25
  280. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/ContentType.md +0 -25
  281. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Delete.md +0 -25
  282. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Get.md +0 -25
  283. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Head.md +0 -25
  284. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Header.md +0 -25
  285. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Method.md +0 -31
  286. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Model.md +0 -25
  287. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Options.md +0 -25
  288. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Param.md +0 -26
  289. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Patch.md +0 -25
  290. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Post.md +0 -25
  291. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Protect.md +0 -31
  292. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Put.md +0 -25
  293. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Query.md +0 -26
  294. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Request.md +0 -31
  295. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/RequiresRole.md +0 -26
  296. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Response.md +0 -31
  297. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Route.md +0 -25
  298. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Socket.md +0 -33
  299. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/User.md +0 -31
  300. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/Validate.md +0 -25
  301. package/docs/reference/@rapidrest/namespaces/RouteDecorators/functions/WebSocket.md +0 -25
  302. package/docs/reference/README.md +0 -20
  303. package/docs/reference/classes/ACLUtils.md +0 -263
  304. package/docs/reference/classes/AdminRoute.md +0 -51
  305. package/docs/reference/classes/AuthMiddleware.md +0 -173
  306. package/docs/reference/classes/BackgroundService.md +0 -117
  307. package/docs/reference/classes/BackgroundServiceManager.md +0 -172
  308. package/docs/reference/classes/BaseEntity.md +0 -82
  309. package/docs/reference/classes/BaseMongoEntity.md +0 -107
  310. package/docs/reference/classes/BasicStrategy.md +0 -131
  311. package/docs/reference/classes/BasicStrategyOptions.md +0 -115
  312. package/docs/reference/classes/BulkError.md +0 -271
  313. package/docs/reference/classes/ConnectionManager.md +0 -75
  314. package/docs/reference/classes/EventListenerManager.md +0 -88
  315. package/docs/reference/classes/HttpRouter.md +0 -343
  316. package/docs/reference/classes/JWTStrategy.md +0 -100
  317. package/docs/reference/classes/JWTStrategyOptions.md +0 -97
  318. package/docs/reference/classes/MetricsRoute.md +0 -27
  319. package/docs/reference/classes/ModelRoute.md +0 -527
  320. package/docs/reference/classes/ModelUtils.md +0 -446
  321. package/docs/reference/classes/MongoConnection.md +0 -218
  322. package/docs/reference/classes/MongoRepository.md +0 -390
  323. package/docs/reference/classes/MongoSchemaSync.md +0 -97
  324. package/docs/reference/classes/NetUtils.md +0 -90
  325. package/docs/reference/classes/NotificationUtils.md +0 -77
  326. package/docs/reference/classes/ObjectFactory.md +0 -336
  327. package/docs/reference/classes/OpenAPIRoute.md +0 -77
  328. package/docs/reference/classes/OpenApiSpec.md +0 -892
  329. package/docs/reference/classes/RecoverableBaseEntity.md +0 -114
  330. package/docs/reference/classes/RecoverableBaseMongoEntity.md +0 -124
  331. package/docs/reference/classes/RedisTransport.md +0 -2202
  332. package/docs/reference/classes/RepoUtils.md +0 -482
  333. package/docs/reference/classes/RouteUtils.md +0 -191
  334. package/docs/reference/classes/Server.md +0 -408
  335. package/docs/reference/classes/SimpleEntity.md +0 -48
  336. package/docs/reference/classes/SimpleMongoEntity.md +0 -66
  337. package/docs/reference/classes/StatusExtraData.md +0 -57
  338. package/docs/reference/classes/StatusRoute.md +0 -26
  339. package/docs/reference/classes/UWSRequest.md +0 -226
  340. package/docs/reference/classes/UWSResponse.md +0 -257
  341. package/docs/reference/classes/UWSWebSocketShim.md +0 -958
  342. package/docs/reference/enumerations/ACLAction.md +0 -63
  343. package/docs/reference/enumerations/ApiErrorMessages.md +0 -123
  344. package/docs/reference/enumerations/ApiErrors.md +0 -123
  345. package/docs/reference/functions/createWebSocketStream.md +0 -27
  346. package/docs/reference/functions/isSqlDataSource.md +0 -26
  347. package/docs/reference/functions/readBody.md +0 -29
  348. package/docs/reference/functions/resolveCollectionName.md +0 -33
  349. package/docs/reference/functions/runChain.md +0 -40
  350. package/docs/reference/functions/snakeCase.md +0 -28
  351. package/docs/reference/globals.md +0 -106
  352. package/docs/reference/interfaces/ACLRecord.md +0 -96
  353. package/docs/reference/interfaces/AccessControlList.md +0 -76
  354. package/docs/reference/interfaces/AuthResult.md +0 -55
  355. package/docs/reference/interfaces/AuthStrategy.md +0 -95
  356. package/docs/reference/interfaces/CreateRequestOptions.md +0 -121
  357. package/docs/reference/interfaces/DeleteRequestOptions.md +0 -137
  358. package/docs/reference/interfaces/FindRequestOptions.md +0 -133
  359. package/docs/reference/interfaces/HttpRequest.md +0 -147
  360. package/docs/reference/interfaces/HttpResponse.md +0 -164
  361. package/docs/reference/interfaces/JWTAuthResult.md +0 -84
  362. package/docs/reference/interfaces/RepoCreateOptions.md +0 -95
  363. package/docs/reference/interfaces/RepoDeleteOptions.md +0 -105
  364. package/docs/reference/interfaces/RepoFindOptions.md +0 -125
  365. package/docs/reference/interfaces/RepoOperationOptions.md +0 -69
  366. package/docs/reference/interfaces/RepoUpdateOptions.md +0 -101
  367. package/docs/reference/interfaces/RequestOptions.md +0 -112
  368. package/docs/reference/interfaces/RequestWS.md +0 -225
  369. package/docs/reference/interfaces/TruncateRequestOptions.md +0 -161
  370. package/docs/reference/interfaces/UpdateRequestOptions.md +0 -139
  371. package/docs/reference/type-aliases/ErrorHandler.md +0 -35
  372. package/docs/reference/type-aliases/NextFunction.md +0 -23
  373. package/docs/reference/type-aliases/OneOrMany.md +0 -19
  374. package/docs/reference/type-aliases/OneOrNull.md +0 -19
  375. package/docs/reference/type-aliases/PartialBaseEntity.md +0 -17
  376. package/docs/reference/type-aliases/PartialSimpleEntity.md +0 -17
  377. package/docs/reference/type-aliases/RequestHandler.md +0 -31
  378. package/docs/reference/type-aliases/UpdateObject.md +0 -19
  379. package/docs/reference/type-aliases/WsUpgradeAuth.md +0 -27
  380. package/docs/reference/type-aliases/WsUpgradeAuthResult.md +0 -47
@@ -9,6 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  ///////////////////////////////////////////////////////////////////////////////
11
11
  // Copyright (C) 2020-2026 Jean-Philippe Steinmetz. All rights reserved.
12
+ // SPDX-License-Identifier: MPL-2.0
12
13
  ///////////////////////////////////////////////////////////////////////////////
13
14
  import { ObjectDecorators, UserUtils, sleep } from "@rapidrest/core";
14
15
  import { AccessControlListSQL } from "./AccessControlListSQL.js";
@@ -18,20 +19,19 @@ import { ConnectionManager } from "../database/ConnectionManager.js";
18
19
  import { isSqlDataSource } from "../database/ConnectionKinds.js";
19
20
  import { MongoConnection } from "../database/MongoConnection.js";
20
21
  import { MongoRepository } from "../database/MongoRepository.js";
22
+ import { RedisCache } from "../database/RedisCache.js";
23
+ import { Transactional, transactionContext } from "../decorators/DatabaseDecorators.js";
21
24
  const { Config, Init, Inject, Logger } = ObjectDecorators;
22
- const CACHE_BASE_KEY = "db.cache.AccessControlList";
23
25
  /**
24
26
  * Common utility functions for working with `AccessControlList` objects and validating user permissions.
27
+ *
28
+ * @author Jean-Philippe Steinmetz
25
29
  */
26
30
  export class ACLUtils {
27
31
  constructor() {
28
32
  this.enabled = true;
29
- this.cacheTTL = 30;
30
33
  this.trustedRoles = ["admin"];
31
34
  }
32
- get cacheClient() {
33
- return this.connMgr?.connections.get("cache");
34
- }
35
35
  get repo() {
36
36
  const conn = this.connMgr?.connections.get("acl");
37
37
  if (conn instanceof MongoConnection) {
@@ -42,10 +42,19 @@ export class ACLUtils {
42
42
  }
43
43
  return undefined;
44
44
  }
45
- init() {
45
+ async init() {
46
46
  if (this.enabled) {
47
47
  if (!this.repo) {
48
- throw new Error("Failed to initialize ACLUtils. Did you forget to configure the `acl` datastore?");
48
+ throw new Error("Failed to initialize ACLUtils. Did you forget to configure the `acl` datasource?");
49
+ }
50
+ // Create the cache store if caching is enabled for this entity type
51
+ if (!this.cache) {
52
+ const conn = this.connMgr?.connections.get("acl");
53
+ let modelClass = conn instanceof MongoConnection ? AccessControlListMongo : AccessControlListSQL;
54
+ this.cache = await this._objectFactory?.newInstance(RedisCache, {
55
+ name: modelClass.fqn ?? modelClass.name,
56
+ args: [modelClass],
57
+ });
49
58
  }
50
59
  this.logger?.info("RBAC system is enabled and ready.");
51
60
  }
@@ -54,50 +63,49 @@ export class ACLUtils {
54
63
  }
55
64
  }
56
65
  /**
57
- * Checks to see if the provided user matches the providedUserOrRoleId.
66
+ * Classifies how specifically the given user matches the provided ACL record id. Used by `getRecord()` to
67
+ * prefer the most specific match among a record's siblings, rather than the first one in array order.
58
68
  * @param user The user to check.
59
69
  * @param userOrRoleId The ACL record id to check against.
60
- * @returns `true` if the user contains a `uid` or `role` that matches the `userOrRoleId`, otherwise `false`.
70
+ * @returns `"exact"` for a direct uid/anonymous match, `"role"` for a role match, `"wildcard"` for a `.*`/`*`
71
+ * match, or `"none"` if the user doesn't match this id at all.
61
72
  */
62
- userMatchesId(user, userOrRoleId) {
73
+ matchSpecificity(user, userOrRoleId) {
63
74
  if (!user?.uid) {
64
- return userOrRoleId === "anonymous";
75
+ // Wildcards only ever match an *authenticated* user (see the check below) — an anonymous caller
76
+ // can only match a record explicitly keyed "anonymous".
77
+ return userOrRoleId === "anonymous" ? "exact" : "none";
65
78
  }
79
+ if (user.uid === userOrRoleId)
80
+ return "exact";
81
+ if (user.roles?.includes(userOrRoleId))
82
+ return "role";
66
83
  // Explicit wildcards — match any authenticated user; no regex engine involved
67
84
  if (userOrRoleId === ".*" || userOrRoleId === "*")
68
- return true;
69
- if (user.uid === userOrRoleId)
70
- return true;
71
- if (user.roles) {
72
- for (const role of user.roles) {
73
- if (role === userOrRoleId)
74
- return true;
75
- }
76
- }
77
- return false;
85
+ return "wildcard";
86
+ return "none";
78
87
  }
79
88
  /**
80
89
  * Validates that the user has permission to perform the request operation against the URL path for the
81
- * provided request. If ACLUtils has not been initialized or the `acl` datastore has not been configured
90
+ * provided request. If ACLUtils has not been initialized or the `acl` datasource has not been configured
82
91
  * then always returns `true`.
83
92
  *
84
93
  * @param uid The uid of the access control list to verify against.
85
94
  * @param user The user to validate.
86
- * @param req The request whose URL path and method will be verified.
95
+ * @param req The HTTP request to check permissions for.
87
96
  */
88
97
  async checkRequestPerms(uid, user, req) {
89
- let result = true;
90
98
  // If RBAC is disabled just return
91
99
  if (!this.enabled) {
92
- return result;
100
+ return true;
93
101
  }
94
- // Request-scoped cache avoids duplicate Redis/DB fetches when the same ACL uid is
95
- // checked more than once within a single request (e.g. class + method @Protect).
96
- if (!req._aclCache) {
97
- req._aclCache = new Map();
102
+ if (!req) {
103
+ throw new Error("options.request must be set to call this function.");
98
104
  }
99
- const reqCache = req._aclCache;
100
- let acl = await this.findACL(uid, [], reqCache);
105
+ // Deny by default — if the ACL record can't be found (e.g. it failed to persist at
106
+ // registration time) a `@Protect`-ed route must not silently become open to everyone.
107
+ let result = false;
108
+ let acl = await this.findACL(uid, []);
101
109
  if (acl) {
102
110
  // Make sure all parents are populated
103
111
  if (!acl.parent) {
@@ -109,26 +117,18 @@ export class ACLUtils {
109
117
  result = true;
110
118
  }
111
119
  else {
112
- // Check for the FULL permission. If granted it will supersede any others. Otherwise, we'll
113
- // check individually based on the request method.
114
- result = await this.hasPermission(user, acl, ACLAction.FULL);
115
- if (!result) {
116
- // Map the request method to an ACLAction and test for permission
117
- switch (req.method.toLowerCase()) {
118
- case "delete":
119
- result = await this.hasPermission(user, acl, ACLAction.DELETE);
120
- break;
121
- case "get":
122
- result = await this.hasPermission(user, acl, ACLAction.READ);
123
- break;
124
- case "post":
125
- result = await this.hasPermission(user, acl, ACLAction.CREATE);
126
- break;
127
- case "put":
128
- result = await this.hasPermission(user, acl, ACLAction.UPDATE);
129
- break;
130
- }
131
- }
120
+ // Map the request method to an ACLAction and test for permission. `hasPermission` already
121
+ // treats `ACLAction.FULL` as a wildcard that supersedes any specific action requested.
122
+ const methodToAction = {
123
+ delete: ACLAction.DELETE,
124
+ get: ACLAction.READ,
125
+ head: ACLAction.COUNT,
126
+ patch: ACLAction.UPDATE,
127
+ post: ACLAction.CREATE,
128
+ put: ACLAction.UPDATE,
129
+ };
130
+ const action = methodToAction[req.method.toLowerCase()];
131
+ result = action ? await this.hasPermission(user, acl, action) : false;
132
132
  }
133
133
  }
134
134
  return result;
@@ -142,7 +142,6 @@ export class ACLUtils {
142
142
  * @returns `true` if the user has at least one of the permissions granted for the given entity, otherwise `false`.
143
143
  */
144
144
  async hasPermission(user, acl, action) {
145
- let result = null;
146
145
  // If the repo isn't available, no acl was provided or the ACL string is empty just return, assume always true
147
146
  if (!this.enabled || !this.repo || !acl || acl === "") {
148
147
  return true;
@@ -154,117 +153,119 @@ export class ACLUtils {
154
153
  }
155
154
  // If a uid has been given look up the ACL associated with it and then process
156
155
  if (typeof acl === "string") {
157
- const entry = await this.findACL(acl);
156
+ const entry = await this.findACL(acl, []);
158
157
  return entry ? await this.hasPermission(user, entry, action) : false;
159
158
  }
160
159
  // Look for the first available record for the given user
161
160
  const record = this.getRecord(acl, user);
162
- // Validate the requested action against the record.
163
- if (record) {
164
- // A `FULL` permission grant overrides everything else
165
- result = record.full;
166
- if (!result) {
167
- switch (action) {
168
- case ACLAction.CREATE:
169
- result = record.create;
170
- break;
171
- case ACLAction.DELETE:
172
- result = record.delete;
173
- break;
174
- case ACLAction.FULL:
175
- result =
176
- record.full ||
177
- (record.create && record.delete && record.read && record.special && record.update);
178
- break;
179
- case ACLAction.READ:
180
- result = record.read;
181
- break;
182
- case ACLAction.SPECIAL:
183
- result = record.special;
184
- break;
185
- case ACLAction.UPDATE:
186
- result = record.update;
187
- break;
188
- }
189
- }
190
- }
191
- // No matching record found — deny by default
192
- return result !== null && result !== undefined ? result : false;
161
+ // A `FULL` ("*") grant supersedes any specific action requested.
162
+ return record ? record.actions.includes(ACLAction.FULL) || record.actions.includes(action) : false;
193
163
  }
194
164
  /**
195
165
  * Retrieves the access control list with the associated identifier and populates the parent(s).
196
166
  *
167
+ * Deliberately not `@Transactional` and never participates in a caller's transaction: it's on the hot path
168
+ * for every permission check (`hasPermission`/`checkRequestPerms`), not just writes, so wrapping it would
169
+ * add a session open/close to every authorization check. It also has no way to safely reuse a *caller's*
170
+ * transaction context.
171
+ *
197
172
  * @param entityId The unique identifier of the ACL to retrieve.
198
173
  * @param parentUids The list of already found parent UIDs. This is used to break circular dependencies.
174
+ * @param options Set `skipCache: true` to bypass the cache and always read the current database state —
175
+ * mirrors `RepoUtils`'s `RepoFindOptions.skipCache`. The result is still written back into the cache
176
+ * afterward, same as a normal (non-skipped) lookup.
199
177
  */
200
- async findACL(entityId, parentUids = [], reqCache) {
178
+ async findACL(entityId, parentUids = [], options) {
201
179
  if (!this.enabled || !this.repo) {
202
- return null;
203
- }
204
- // Check request-scoped cache first — eliminates redundant Redis/DB round trips
205
- // when the same ACL uid is visited more than once within one request.
206
- if (reqCache?.has(entityId)) {
207
- return reqCache.get(entityId) ?? null;
208
- }
209
- let acl = null;
210
- // Retrieve the ACL from the cache if present
211
- if (this.cacheClient) {
212
- const json = await this.cacheClient.get(`${CACHE_BASE_KEY}.${entityId}`);
213
- if (json) {
214
- try {
215
- acl = JSON.parse(json);
216
- }
217
- catch (err) {
218
- // We don't care if this fails
219
- }
220
- }
221
- }
222
- // If the acl wasn't found in the cache look in the database
180
+ return undefined;
181
+ }
182
+ // Retrieve the ACL from the cache if present. A cache hit must still fall through to the parent-chain
183
+ // population below (not return early) — a cached ACL was stored via its own plain, unpopulated `.parent`
184
+ // (see the cache write below), so skipping that step here would silently return an ACL with no parent
185
+ // chain, which `hasPermission()` needs to find inherited records.
186
+ let acl = options?.skipCache ? undefined : await this.cache?.load(entityId);
187
+ // If the acl wasn't found in the cache (or the cache was skipped) look in the database
223
188
  if (!acl) {
224
189
  if (this.repo instanceof MongoRepository) {
225
190
  acl = await this.repo.findOne({ uid: entityId });
226
- acl = acl ? new AccessControlListMongo(acl) : null;
191
+ acl = acl ? new AccessControlListMongo(acl) : undefined;
227
192
  }
228
193
  else {
229
- acl = await this.repo.findOne({ uid: entityId });
230
- acl = acl ? new AccessControlListSQL(acl) : null;
231
- }
232
- // Store a copy in the cache for faster retrieval next time
233
- if (acl && this.cacheClient) {
234
- void this.cacheClient.setex(`${CACHE_BASE_KEY}.${entityId}`, this.cacheTTL, JSON.stringify(acl));
194
+ acl = await this.repo.findOne({ where: { uid: entityId } });
195
+ acl = acl ? new AccessControlListSQL(acl) : undefined;
235
196
  }
236
197
  }
237
- // Populate request-scoped cache before fetching the parent chain so recursive
238
- // calls for the same uid are served from memory.
239
- if (reqCache) {
240
- reqCache.set(entityId, acl);
198
+ // Store a copy in the cache for faster retrieval next time.
199
+ if (acl && this.cache) {
200
+ this.cache.save(entityId, acl).catch((err) => {
201
+ this.logger?.warn(`ACLUtils: Cache save failed for ACL ${entityId}.`);
202
+ this.logger?.debug(err);
203
+ });
241
204
  }
242
205
  // Retrieve the parent ACL and assign it if available. Don't populate parents we've
243
206
  // already found to prevent a circular dependency.
244
207
  if (acl && acl.parentUid && !parentUids.includes(acl.parentUid)) {
245
208
  parentUids.push(acl.parentUid);
246
- acl.parent = await this.findACL(acl.parentUid, parentUids, reqCache);
209
+ acl.parent = await this.findACL(acl.parentUid, parentUids, options);
247
210
  }
248
211
  return acl;
249
212
  }
250
213
  /**
251
- * Deletes the ACL with the given identifier from the database.
214
+ * Deletes the ACL with the given identifier from the database, returning the document that was actually
215
+ * deleted (or `undefined` if none existed) — captured atomically as part of the delete itself (Mongo:
216
+ * `findOneAndDelete`; SQL: a `findOne` immediately followed by the `delete`, both inside this method's own
217
+ * `acl` transaction), not a separate, earlier read. A caller using the return value as a restore snapshot
218
+ * (see `RepoUtils.delete()`/`truncate()`'s `registerRollbackHook()` usage) always gets the exact row that
219
+ * was removed, never a possibly-stale one raced by a concurrent legitimate write between an earlier read
220
+ * and this delete — and restoring it later still goes through `saveACL()`'s own optimistic-lock version
221
+ * check, so a restore attempt can't clobber a *newer* row that appeared after this delete either.
222
+ *
223
+ * Runs in its own transaction scoped to the `acl` connection (see `Transactional`) rather than trying to
224
+ * join whatever transaction the caller is itself in — `acl` is frequently configured as its own, separate
225
+ * datastore (a documented, supported deployment shape), so a caller's session/entityManager almost never
226
+ * actually belongs to the same physical connection this repo does. Reusing it directly used to throw
227
+ * (Mongo) or silently target the wrong connection (SQL).
228
+ *
252
229
  * @param uid The unique identifier of the ACL to remove.
253
230
  */
254
231
  async removeACL(uid) {
255
- if (this.enabled) {
256
- try {
257
- if (this.repo instanceof MongoRepository) {
258
- await this.repo.deleteOne({ uid });
259
- }
260
- else if (this.repo) {
261
- await this.repo.delete({ uid });
232
+ if (!this.enabled) {
233
+ return undefined;
234
+ }
235
+ if (!this.repo) {
236
+ throw new Error("repo is not set.");
237
+ }
238
+ const ctx = transactionContext.getStore();
239
+ let removed;
240
+ try {
241
+ if (this.repo instanceof MongoRepository) {
242
+ const deleted = await this.repo.findOneAndDelete({ uid }, { session: ctx?.session });
243
+ removed = deleted ? new AccessControlListMongo(deleted) : undefined;
244
+ }
245
+ else {
246
+ const repo = ctx?.entityManager ? ctx.entityManager.getRepository(AccessControlListSQL) : this.repo;
247
+ const existing = await repo.findOne({ where: { uid } });
248
+ if (existing) {
249
+ await repo.delete({ uid });
250
+ removed = new AccessControlListSQL(existing);
262
251
  }
263
252
  }
264
- catch (err) {
265
- // It's okay if this fails because no document exists
253
+ }
254
+ catch (err) {
255
+ // The error "ns not found" occurs when the collection doesn't exist yet (e.g. a fresh deployment
256
+ // that has never written an ACL) - matches RepoUtils.truncate()'s handling of the same driver
257
+ // quirk. Any other error is a genuine failure and must propagate, not be silently treated as "this
258
+ // ACL is already gone".
259
+ if (err?.message !== "ns not found") {
260
+ throw err;
266
261
  }
267
262
  }
263
+ // Without this, a deleted ACL stays readable from the cache for up to `cacheTTL` seconds,
264
+ // so permissions the deletion was meant to revoke would continue to apply during that window.
265
+ if (this.cache) {
266
+ await this.cache.delete(uid);
267
+ }
268
+ return removed;
268
269
  }
269
270
  /**
270
271
  * Compares two ACLs to see if they have been modified and returns the total number of changes between them.
@@ -290,13 +291,8 @@ export class ACLUtils {
290
291
  }
291
292
  }
292
293
  if (foundRecord) {
293
- // Check to see if any of the permissions changed for this record
294
- result += foundRecord.create !== recordA.create ? 1 : 0;
295
- result += foundRecord.delete !== recordA.delete ? 1 : 0;
296
- result += foundRecord.full !== recordA.full ? 1 : 0;
297
- result += foundRecord.read !== recordA.read ? 1 : 0;
298
- result += foundRecord.special !== recordA.special ? 1 : 0;
299
- result += foundRecord.update !== recordA.update ? 1 : 0;
294
+ // Check to see if the granted actions changed for this record
295
+ result += this.actionsChanged(foundRecord.actions, recordA.actions) ? 1 : 0;
300
296
  }
301
297
  else {
302
298
  result++;
@@ -313,13 +309,8 @@ export class ACLUtils {
313
309
  }
314
310
  }
315
311
  if (foundRecord) {
316
- // Check to see if any of the permissions changed for this record
317
- result += foundRecord.create !== recordB.create ? 1 : 0;
318
- result += foundRecord.delete !== recordB.delete ? 1 : 0;
319
- result += foundRecord.full !== recordB.full ? 1 : 0;
320
- result += foundRecord.read !== recordB.read ? 1 : 0;
321
- result += foundRecord.special !== recordB.special ? 1 : 0;
322
- result += foundRecord.update !== recordB.update ? 1 : 0;
312
+ // Check to see if the granted actions changed for this record
313
+ result += this.actionsChanged(foundRecord.actions, recordB.actions) ? 1 : 0;
323
314
  }
324
315
  else {
325
316
  result++;
@@ -328,55 +319,107 @@ export class ACLUtils {
328
319
  return result;
329
320
  }
330
321
  /**
331
- * Stores the given access control list into the ACL database.
322
+ * Compares two lists of granted actions, ignoring order, to see if they differ.
323
+ */
324
+ actionsChanged(a, b) {
325
+ if (a.length !== b.length) {
326
+ return true;
327
+ }
328
+ const sortedA = [...a].sort();
329
+ const sortedB = [...b].sort();
330
+ return sortedA.some((action, i) => action !== sortedB[i]);
331
+ }
332
+ /**
333
+ * Stores the given access control list into the ACL database. By default, bumps the version for optimistic
334
+ * locking (see below) — pass `preserveVersion: true` to instead write `acl` back with its own version
335
+ * exactly as given, for restoring a known-good snapshot (see `RepoUtils.delete()`/`truncate()`'s
336
+ * `registerRollbackHook()` usage) rather than applying a new change on top of whatever's currently stored.
337
+ * A restore only ever targets a uid with nothing currently at it (that's the point — the entity-side
338
+ * transaction it was tied to is being rolled back, so the row it deleted should no longer exist); if
339
+ * something *does* exist there already, the restore is refused rather than silently clobbering it.
340
+ *
341
+ * Runs in its own transaction scoped to the `acl` connection (see `Transactional`) rather than trying to
342
+ * join whatever transaction the caller is itself in — see `removeACL`'s doc comment for why. Callers that
343
+ * need the entity-side write and this ACL save to stay consistent should register a compensating action
344
+ * via `registerRollbackHook()`.
332
345
  *
333
346
  * @param acl The ACL to store.
347
+ * @param options Set `preserveVersion: true` to restore `acl` exactly as given (see above) instead of the
348
+ * normal optimistic-locking update semantics.
334
349
  * @return Returns the ACL that was stored in the database.
335
350
  */
336
- async saveACL(acl) {
351
+ async saveACL(acl, options) {
337
352
  let result = null;
338
353
  if (!this.enabled || !acl) {
339
354
  return result;
340
355
  }
356
+ if (!this.repo) {
357
+ throw new Error("repo is not set.");
358
+ }
359
+ const ctx = transactionContext.getStore();
360
+ const preserveVersion = !!options?.preserveVersion;
341
361
  if (this.repo instanceof MongoRepository) {
342
362
  const mACL = new AccessControlListMongo(acl);
343
- const existing = await this.repo.findOne({ uid: acl.uid });
344
- // If no changes have been made between versions ignore this request
345
- if (existing && this.diffACL(existing, acl) === 0) {
346
- return existing;
363
+ const existing = await this.repo.findOne({ uid: acl.uid }, {
364
+ session: ctx?.session,
365
+ });
366
+ if (preserveVersion) {
367
+ if (existing) {
368
+ throw new Error(`Cannot restore ACL ${acl.uid}: a document already exists at this uid.`);
369
+ }
347
370
  }
348
- // Make sure that the versions match before we proceed
349
- if (existing && existing.version !== mACL.version) {
350
- throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${mACL.version}`);
371
+ else {
372
+ // If no changes have been made between versions ignore this request
373
+ if (existing && this.diffACL(existing, acl) === 0) {
374
+ return existing;
375
+ }
376
+ // Make sure that the versions match before we proceed
377
+ if (existing && existing.version !== mACL.version) {
378
+ throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${mACL.version}`);
379
+ }
351
380
  }
352
381
  const aclMongo = new AccessControlListMongo({
353
382
  ...acl,
354
383
  dateModifed: new Date(),
355
- version: existing ? mACL.version + 1 : 0,
384
+ version: preserveVersion ? mACL.version : existing ? mACL.version + 1 : 0,
356
385
  });
357
- result = await this.repo.save(aclMongo);
386
+ // ACLs are single-row-per-uid (never trackChanges) - merge by `uid` rather than requiring `_id` to
387
+ // have survived the `{...acl, ...}` spread above, or a stale/missing `_id` would insert a
388
+ // duplicate document instead of updating the one `existing` just found.
389
+ result = await this.repo.save(aclMongo, { session: ctx?.session, mergeByUid: true });
358
390
  }
359
- else if (this.repo) {
391
+ else {
392
+ const repo = ctx?.entityManager ? ctx.entityManager.getRepository(AccessControlListSQL) : this.repo;
360
393
  const sACL = new AccessControlListSQL(acl);
361
- const existing = await this.repo.findOne({ uid: acl.uid });
362
- // If no changes have been made between versions ignore this request
363
- if (existing && this.diffACL(existing, acl) === 0) {
364
- return existing;
394
+ const existing = await repo.findOne({ where: { uid: acl.uid } });
395
+ if (preserveVersion) {
396
+ if (existing) {
397
+ throw new Error(`Cannot restore ACL ${acl.uid}: a document already exists at this uid.`);
398
+ }
365
399
  }
366
- // Make sure that the versions match before we proceed
367
- if (existing && existing.version !== sACL.version) {
368
- throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${sACL.version}`);
400
+ else {
401
+ // If no changes have been made between versions ignore this request
402
+ if (existing && this.diffACL(existing, acl) === 0) {
403
+ return existing;
404
+ }
405
+ // Make sure that the versions match before we proceed
406
+ if (existing && existing.version !== sACL.version) {
407
+ throw new Error(`The acl to save must be of the same version. ACL=${acl.uid}, Expected=${existing.version}, Actual=${sACL.version}`);
408
+ }
369
409
  }
370
410
  const aclSQL = new AccessControlListSQL({
371
411
  ...acl,
372
412
  dateModifed: new Date(),
373
- version: existing ? sACL.version + 1 : 0,
413
+ version: preserveVersion ? sACL.version : existing ? sACL.version + 1 : 0,
374
414
  });
375
- result = await this.repo.save(aclSQL);
415
+ result = await repo.save(aclSQL);
376
416
  }
377
- // Store a copy in the cache for faster retrieval next time
378
- if (this.cacheClient && result) {
379
- void this.cacheClient.setex(`${CACHE_BASE_KEY}.${result.uid}`, this.cacheTTL, JSON.stringify(result));
417
+ // Store a copy in the cache for faster retrieval next time.
418
+ if (this.cache && result) {
419
+ this.cache.save(result.uid, result).catch((err) => {
420
+ this.logger?.warn(`ACLUtils: Cache save failed for ACL ${result?.uid}.`);
421
+ this.logger?.debug(err);
422
+ });
380
423
  }
381
424
  return result;
382
425
  }
@@ -416,6 +459,9 @@ export class ACLUtils {
416
459
  // Copy over the new records from code
417
460
  existing.records = defaultAcl.records;
418
461
  defaultAcl = existing;
462
+ // The user-defined override record was already created on a previous run. Look it
463
+ // up so callers still receive a valid ACL to register routes against instead of `null`.
464
+ result = (await this.findACL(acl.uid)) ?? null;
419
465
  }
420
466
  else {
421
467
  // Create the user-defined override record
@@ -425,7 +471,7 @@ export class ACLUtils {
425
471
  records: [],
426
472
  });
427
473
  }
428
- // Always save the ACL into the datastore
474
+ // Always save the ACL into the datasource
429
475
  await this.saveACL(defaultAcl);
430
476
  attempts = maxAttempts;
431
477
  }
@@ -443,7 +489,49 @@ export class ACLUtils {
443
489
  return result;
444
490
  }
445
491
  /**
446
- * Retrieves the first available record in the provided ACL associated with the provided user.
492
+ * Atomic removal of multiple ACLs in a single `acl`-scoped transaction (see `@Transactional`), returning
493
+ * whichever of them actually existed to be deleted (see `removeACL()`'s doc comment for why the returned
494
+ * documents — not a separate, earlier read — are the right thing to snapshot for a possible restore).
495
+ *
496
+ * Deliberately sequential, not batched-concurrent: every `removeACL()` call below resolves to this same
497
+ * `acl` datasource, so `@Transactional`'s merge logic joins all of them onto the one MongoDB
498
+ * `ClientSession`/SQL `EntityManager` this method opened. Running these with `Promise.all`
499
+ * would issue overlapping commands against one session, which drivers reject or misorder.
500
+ *
501
+ * @param uids The unique identifiers of the ACLs to remove.
502
+ */
503
+ async removeACLs(uids) {
504
+ const removed = [];
505
+ for (const uid of uids) {
506
+ const acl = await this.removeACL(uid);
507
+ if (acl) {
508
+ removed.push(acl);
509
+ }
510
+ }
511
+ return removed;
512
+ }
513
+ /**
514
+ * Atomic save of multiple ACLs in a single `acl`-scoped transaction (see `Transactional`). Used to restore
515
+ * a batch of ACL snapshots — e.g. by a `registerRollbackHook()` compensating action — if the entity-side
516
+ * transaction they were removed alongside subsequently fails. Always restores each ACL's own version as
517
+ * given (see `saveACL()`'s `preserveVersion` option) rather than bumping it, since this is a restore, not
518
+ * a new change.
519
+ *
520
+ * Deliberately sequential. See `removeACLs()`'s doc comment for why.
521
+ *
522
+ * @param acls The ACLs to store.
523
+ */
524
+ async saveACLs(acls) {
525
+ for (const acl of acls) {
526
+ await this.saveACL(acl, { preserveVersion: true });
527
+ }
528
+ }
529
+ /**
530
+ * Retrieves the most specific record in the provided ACL associated with the provided user: an exact
531
+ * uid/anonymous match beats a role match, which beats a wildcard (`.*`/`*`) match — regardless of where
532
+ * each record sits in `acl.records`. Without this, a wildcard grant authored before a specific record
533
+ * (a natural authoring order) would silently shadow that more specific record. Only falls back to the
534
+ * parent ACL when nothing in this ACL's own records matches at all.
447
535
  *
448
536
  * @param acl The access control list that will be searched.
449
537
  * @param user The user to find a record for.
@@ -453,10 +541,25 @@ export class ACLUtils {
453
541
  if (!acl) {
454
542
  return null;
455
543
  }
544
+ let roleMatch = null;
545
+ let wildcardMatch = null;
456
546
  for (const record of acl.records) {
457
- if (this.userMatchesId(user, record.userOrRoleId)) {
547
+ const specificity = this.matchSpecificity(user, record.userOrRoleId);
548
+ if (specificity === "exact") {
458
549
  return record;
459
550
  }
551
+ else if (specificity === "role") {
552
+ roleMatch = roleMatch ?? record;
553
+ }
554
+ else if (specificity === "wildcard") {
555
+ wildcardMatch = wildcardMatch ?? record;
556
+ }
557
+ }
558
+ if (roleMatch) {
559
+ return roleMatch;
560
+ }
561
+ if (wildcardMatch) {
562
+ return wildcardMatch;
460
563
  }
461
564
  return acl.parent ? this.getRecord(acl.parent, user) : null;
462
565
  }
@@ -493,6 +596,30 @@ __decorate([
493
596
  Init,
494
597
  __metadata("design:type", Function),
495
598
  __metadata("design:paramtypes", []),
496
- __metadata("design:returntype", void 0)
599
+ __metadata("design:returntype", Promise)
497
600
  ], ACLUtils.prototype, "init", null);
601
+ __decorate([
602
+ Transactional("acl"),
603
+ __metadata("design:type", Function),
604
+ __metadata("design:paramtypes", [String]),
605
+ __metadata("design:returntype", Promise)
606
+ ], ACLUtils.prototype, "removeACL", null);
607
+ __decorate([
608
+ Transactional("acl"),
609
+ __metadata("design:type", Function),
610
+ __metadata("design:paramtypes", [Object, Object]),
611
+ __metadata("design:returntype", Promise)
612
+ ], ACLUtils.prototype, "saveACL", null);
613
+ __decorate([
614
+ Transactional("acl"),
615
+ __metadata("design:type", Function),
616
+ __metadata("design:paramtypes", [Array]),
617
+ __metadata("design:returntype", Promise)
618
+ ], ACLUtils.prototype, "removeACLs", null);
619
+ __decorate([
620
+ Transactional("acl"),
621
+ __metadata("design:type", Function),
622
+ __metadata("design:paramtypes", [Array]),
623
+ __metadata("design:returntype", Promise)
624
+ ], ACLUtils.prototype, "saveACLs", null);
498
625
  //# sourceMappingURL=ACLUtils.js.map