kuzzle 2.56.0 → 2.57.0-beta.2

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 (408) hide show
  1. package/dist/bin/copy-binaries.js +71 -4
  2. package/dist/bin/start-kuzzle-server +191 -97
  3. package/dist/index.d.ts +31 -1
  4. package/dist/index.js +45 -2
  5. package/dist/lib/api/controllers/adminController.d.ts +4 -3
  6. package/dist/lib/api/controllers/adminController.js +1 -2
  7. package/dist/lib/api/controllers/authController.d.ts +37 -16
  8. package/dist/lib/api/controllers/authController.js +88 -39
  9. package/dist/lib/api/controllers/baseController.d.ts +10 -5
  10. package/dist/lib/api/controllers/baseController.js +36 -15
  11. package/dist/lib/api/controllers/bulkController.d.ts +11 -7
  12. package/dist/lib/api/controllers/bulkController.js +28 -5
  13. package/dist/lib/api/controllers/clusterController.d.ts +2 -2
  14. package/dist/lib/api/controllers/clusterController.js +3 -3
  15. package/dist/lib/api/controllers/collectionController.d.ts +57 -21
  16. package/dist/lib/api/controllers/collectionController.js +40 -11
  17. package/dist/lib/api/controllers/debugController.d.ts +1 -1
  18. package/dist/lib/api/controllers/documentController.d.ts +137 -28
  19. package/dist/lib/api/controllers/documentController.js +156 -125
  20. package/dist/lib/api/controllers/index.d.ts +27 -12
  21. package/dist/lib/api/controllers/index.js +28 -13
  22. package/dist/lib/api/controllers/indexController.d.ts +17 -10
  23. package/dist/lib/api/controllers/indexController.js +13 -11
  24. package/dist/lib/api/controllers/memoryStorageController.d.ts +2 -5
  25. package/dist/lib/api/controllers/memoryStorageController.js +199 -127
  26. package/dist/lib/api/controllers/realtimeController.d.ts +19 -9
  27. package/dist/lib/api/controllers/realtimeController.js +8 -4
  28. package/dist/lib/api/controllers/securityController.d.ts +42 -29
  29. package/dist/lib/api/controllers/securityController.js +35 -19
  30. package/dist/lib/api/controllers/serverController.d.ts +114 -19
  31. package/dist/lib/api/controllers/serverController.js +69 -29
  32. package/dist/lib/api/documentExtractor.d.ts +9 -7
  33. package/dist/lib/api/documentExtractor.js +105 -41
  34. package/dist/lib/api/funnel.d.ts +131 -29
  35. package/dist/lib/api/funnel.js +436 -225
  36. package/dist/lib/api/httpRoutes.d.ts +16 -11
  37. package/dist/lib/api/httpRoutes.js +272 -2
  38. package/dist/lib/api/openapi/OpenApiManager.d.ts +1 -1
  39. package/dist/lib/api/openapi/openApiGenerator.d.ts +1 -1
  40. package/dist/lib/api/rateLimiter.d.ts +15 -5
  41. package/dist/lib/api/rateLimiter.js +42 -14
  42. package/dist/lib/api/request/kuzzleRequest.d.ts +83 -12
  43. package/dist/lib/api/request/kuzzleRequest.js +88 -23
  44. package/dist/lib/api/request/requestContext.d.ts +20 -5
  45. package/dist/lib/api/request/requestContext.js +14 -4
  46. package/dist/lib/api/request/requestInput.d.ts +37 -6
  47. package/dist/lib/api/request/requestInput.js +22 -9
  48. package/dist/lib/api/request/requestResponse.d.ts +45 -9
  49. package/dist/lib/api/request/requestResponse.js +34 -3
  50. package/dist/lib/cluster/command.d.ts +131 -37
  51. package/dist/lib/cluster/command.js +243 -71
  52. package/dist/lib/cluster/idCardHandler.d.ts +18 -5
  53. package/dist/lib/cluster/idCardHandler.js +52 -19
  54. package/dist/lib/cluster/index.d.ts +8 -2
  55. package/dist/lib/cluster/index.js +6 -2
  56. package/dist/lib/cluster/node.d.ts +127 -49
  57. package/dist/lib/cluster/node.js +412 -179
  58. package/dist/lib/cluster/protobuf/command.proto +18 -0
  59. package/dist/lib/cluster/protobuf/commandMessages.d.ts +104 -0
  60. package/dist/lib/cluster/protobuf/commandMessages.js +30 -0
  61. package/dist/lib/cluster/protobuf/syncMessages.d.ts +184 -0
  62. package/dist/lib/cluster/protobuf/syncMessages.js +23 -0
  63. package/dist/lib/cluster/publisher.d.ts +118 -78
  64. package/dist/lib/cluster/publisher.js +174 -72
  65. package/dist/lib/cluster/state.d.ts +15 -9
  66. package/dist/lib/cluster/state.js +12 -8
  67. package/dist/lib/cluster/subscriber.d.ts +185 -131
  68. package/dist/lib/cluster/subscriber.js +357 -95
  69. package/dist/lib/cluster/workers/IDCardRenewer.d.ts +39 -8
  70. package/dist/lib/cluster/workers/IDCardRenewer.js +88 -23
  71. package/dist/lib/config/default.config.d.ts +2 -2
  72. package/dist/lib/config/default.config.js +10 -3
  73. package/dist/lib/config/documentEventAliases.d.ts +1 -1
  74. package/dist/lib/config/index.d.ts +2 -5
  75. package/dist/lib/config/index.js +30 -17
  76. package/dist/lib/core/auth/formatProcessing.d.ts +3 -3
  77. package/dist/lib/core/auth/passportResponse.d.ts +1 -1
  78. package/dist/lib/core/auth/passportWrapper.d.ts +1 -1
  79. package/dist/lib/core/auth/passportWrapper.js +14 -13
  80. package/dist/lib/core/auth/tokenManager.d.ts +24 -3
  81. package/dist/lib/core/auth/tokenManager.js +47 -19
  82. package/dist/lib/core/backend/applicationManager.d.ts +2 -2
  83. package/dist/lib/core/backend/backend.d.ts +16 -5
  84. package/dist/lib/core/backend/backend.js +14 -6
  85. package/dist/lib/core/backend/backendCluster.d.ts +1 -1
  86. package/dist/lib/core/backend/backendConfig.d.ts +17 -5
  87. package/dist/lib/core/backend/backendConfig.js +1 -0
  88. package/dist/lib/core/backend/backendController.d.ts +1 -1
  89. package/dist/lib/core/backend/backendController.js +8 -1
  90. package/dist/lib/core/backend/backendErrors.d.ts +15 -8
  91. package/dist/lib/core/backend/backendErrors.js +5 -0
  92. package/dist/lib/core/backend/backendHook.d.ts +1 -1
  93. package/dist/lib/core/backend/backendImport.d.ts +1 -1
  94. package/dist/lib/core/backend/backendOpenApi.d.ts +3 -2
  95. package/dist/lib/core/backend/backendPipe.d.ts +1 -1
  96. package/dist/lib/core/backend/backendPipe.js +9 -1
  97. package/dist/lib/core/backend/backendPlugin.d.ts +2 -2
  98. package/dist/lib/core/backend/backendSubscription.d.ts +2 -2
  99. package/dist/lib/core/backend/backendVault.d.ts +1 -1
  100. package/dist/lib/core/backend/backendVault.js +8 -1
  101. package/dist/lib/core/cache/cacheEngine.d.ts +7 -4
  102. package/dist/lib/core/cache/cacheEngine.js +25 -8
  103. package/dist/lib/core/debug/kuzzleDebugger.d.ts +2 -1
  104. package/dist/lib/core/debug/kuzzleDebugger.js +31 -17
  105. package/dist/lib/core/network/accessLogger.d.ts +33 -18
  106. package/dist/lib/core/network/accessLogger.js +135 -73
  107. package/dist/lib/core/network/clientConnection.d.ts +13 -11
  108. package/dist/lib/core/network/clientConnection.js +33 -9
  109. package/dist/lib/core/network/context.d.ts +17 -22
  110. package/dist/lib/core/network/context.js +43 -26
  111. package/dist/lib/core/network/entryPoint.d.ts +38 -16
  112. package/dist/lib/core/network/entryPoint.js +119 -48
  113. package/dist/lib/core/network/httpRouter/index.d.ts +20 -50
  114. package/dist/lib/core/network/httpRouter/index.js +61 -75
  115. package/dist/lib/core/network/httpRouter/routeHandler.d.ts +17 -32
  116. package/dist/lib/core/network/httpRouter/routeHandler.js +26 -36
  117. package/dist/lib/core/network/httpRouter/routePart.d.ts +9 -18
  118. package/dist/lib/core/network/httpRouter/routePart.js +61 -43
  119. package/dist/lib/core/network/httpRouter/routeTypes.d.ts +3 -0
  120. package/dist/lib/core/network/httpRouter/routeTypes.js +23 -0
  121. package/dist/lib/core/network/networkEntryPoint.d.ts +20 -0
  122. package/dist/lib/core/network/networkEntryPoint.js +23 -0
  123. package/dist/lib/core/network/protocolManifest.d.ts +5 -4
  124. package/dist/lib/core/network/protocolManifest.js +6 -3
  125. package/dist/lib/core/network/protocols/httpMessage.d.ts +25 -18
  126. package/dist/lib/core/network/protocols/httpMessage.js +14 -11
  127. package/dist/lib/core/network/protocols/httpwsProtocol.d.ts +84 -93
  128. package/dist/lib/core/network/protocols/httpwsProtocol.js +284 -129
  129. package/dist/lib/core/network/protocols/internalProtocol.d.ts +26 -15
  130. package/dist/lib/core/network/protocols/internalProtocol.js +17 -16
  131. package/dist/lib/core/network/protocols/mqttProtocol.d.ts +56 -32
  132. package/dist/lib/core/network/protocols/mqttProtocol.js +91 -44
  133. package/dist/lib/core/network/protocols/protocol.d.ts +85 -21
  134. package/dist/lib/core/network/protocols/protocol.js +83 -27
  135. package/dist/lib/core/network/router.d.ts +13 -22
  136. package/dist/lib/core/network/router.js +59 -46
  137. package/dist/lib/core/plugin/plugin.d.ts +57 -51
  138. package/dist/lib/core/plugin/plugin.js +218 -103
  139. package/dist/lib/core/plugin/pluginContext.d.ts +54 -11
  140. package/dist/lib/core/plugin/pluginContext.js +99 -58
  141. package/dist/lib/core/plugin/pluginManifest.d.ts +6 -2
  142. package/dist/lib/core/plugin/pluginManifest.js +18 -11
  143. package/dist/lib/core/plugin/pluginRepository.d.ts +35 -44
  144. package/dist/lib/core/plugin/pluginRepository.js +35 -50
  145. package/dist/lib/core/plugin/pluginsManager.d.ts +128 -46
  146. package/dist/lib/core/plugin/pluginsManager.js +468 -231
  147. package/dist/lib/core/plugin/privilegedContext.d.ts +4 -9
  148. package/dist/lib/core/plugin/privilegedContext.js +5 -10
  149. package/dist/lib/core/realtime/actionEnum.d.ts +12 -21
  150. package/dist/lib/core/realtime/actionEnum.js +6 -7
  151. package/dist/lib/core/realtime/channel.d.ts +11 -5
  152. package/dist/lib/core/realtime/channel.js +17 -2
  153. package/dist/lib/core/realtime/connectionRooms.d.ts +2 -2
  154. package/dist/lib/core/realtime/hotelClerk.d.ts +12 -6
  155. package/dist/lib/core/realtime/hotelClerk.js +29 -11
  156. package/dist/lib/core/realtime/index.d.ts +8 -3
  157. package/dist/lib/core/realtime/index.js +12 -5
  158. package/dist/lib/core/realtime/notification/document.d.ts +54 -25
  159. package/dist/lib/core/realtime/notification/document.js +10 -13
  160. package/dist/lib/core/realtime/notification/index.d.ts +9 -3
  161. package/dist/lib/core/realtime/notification/index.js +10 -4
  162. package/dist/lib/core/realtime/notification/server.d.ts +7 -10
  163. package/dist/lib/core/realtime/notification/server.js +4 -7
  164. package/dist/lib/core/realtime/notification/user.d.ts +45 -21
  165. package/dist/lib/core/realtime/notification/user.js +8 -9
  166. package/dist/lib/core/realtime/notifier.d.ts +52 -40
  167. package/dist/lib/core/realtime/notifier.js +60 -25
  168. package/dist/lib/core/realtime/room.d.ts +1 -1
  169. package/dist/lib/core/realtime/subscription.d.ts +11 -6
  170. package/dist/lib/core/realtime/subscription.js +5 -1
  171. package/dist/lib/core/security/index.d.ts +14 -6
  172. package/dist/lib/core/security/index.js +27 -13
  173. package/dist/lib/core/security/profileRepository.d.ts +24 -19
  174. package/dist/lib/core/security/profileRepository.js +23 -10
  175. package/dist/lib/core/security/roleRepository.d.ts +60 -23
  176. package/dist/lib/core/security/roleRepository.js +122 -53
  177. package/dist/lib/core/security/securityLoader.d.ts +33 -18
  178. package/dist/lib/core/security/securityLoader.js +43 -16
  179. package/dist/lib/core/security/tokenRepository.d.ts +21 -7
  180. package/dist/lib/core/security/tokenRepository.js +50 -23
  181. package/dist/lib/core/security/userRepository.d.ts +44 -21
  182. package/dist/lib/core/security/userRepository.js +59 -19
  183. package/dist/lib/core/shared/KoncordeWrapper.d.ts +1 -1
  184. package/dist/lib/core/shared/ObjectRepository.d.ts +80 -18
  185. package/dist/lib/core/shared/ObjectRepository.js +57 -29
  186. package/dist/lib/core/shared/abstractManifest.d.ts +21 -8
  187. package/dist/lib/core/shared/abstractManifest.js +57 -17
  188. package/dist/lib/core/shared/sdk/embeddedSdk.d.ts +3 -1
  189. package/dist/lib/core/shared/sdk/embeddedSdk.js +8 -7
  190. package/dist/lib/core/shared/sdk/funnelProtocol.d.ts +2 -1
  191. package/dist/lib/core/shared/sdk/funnelProtocol.js +12 -3
  192. package/dist/lib/core/shared/sdk/impersonatedSdk.d.ts +11 -5
  193. package/dist/lib/core/shared/sdk/impersonatedSdk.js +22 -9
  194. package/dist/lib/core/shared/store.d.ts +45 -29
  195. package/dist/lib/core/shared/store.js +48 -27
  196. package/dist/lib/core/statistics/statistics.d.ts +1 -1
  197. package/dist/lib/core/storage/clientAdapter.d.ts +39 -21
  198. package/dist/lib/core/storage/clientAdapter.js +88 -48
  199. package/dist/lib/core/storage/indexCache.d.ts +8 -0
  200. package/dist/lib/core/storage/indexCache.js +16 -7
  201. package/dist/lib/core/storage/storageEngine.d.ts +7 -6
  202. package/dist/lib/core/storage/storageEngine.js +11 -8
  203. package/dist/lib/core/validation/baseType.d.ts +22 -22
  204. package/dist/lib/core/validation/baseType.js +11 -26
  205. package/dist/lib/core/validation/specification.d.ts +148 -0
  206. package/dist/lib/core/validation/specification.js +34 -0
  207. package/dist/lib/core/validation/typeOptions.d.ts +62 -0
  208. package/dist/lib/core/validation/typeOptions.js +23 -0
  209. package/dist/lib/core/validation/types/anything.d.ts +3 -6
  210. package/dist/lib/core/validation/types/anything.js +7 -7
  211. package/dist/lib/core/validation/types/boolean.d.ts +5 -12
  212. package/dist/lib/core/validation/types/boolean.js +7 -12
  213. package/dist/lib/core/validation/types/date.d.ts +19 -11
  214. package/dist/lib/core/validation/types/date.js +212 -161
  215. package/dist/lib/core/validation/types/email.d.ts +7 -11
  216. package/dist/lib/core/validation/types/email.js +13 -17
  217. package/dist/lib/core/validation/types/enum.d.ts +7 -11
  218. package/dist/lib/core/validation/types/enum.js +39 -20
  219. package/dist/lib/core/validation/types/geoPoint.d.ts +5 -13
  220. package/dist/lib/core/validation/types/geoPoint.js +10 -15
  221. package/dist/lib/core/validation/types/geoShape.d.ts +22 -21
  222. package/dist/lib/core/validation/types/geoShape.js +113 -129
  223. package/dist/lib/core/validation/types/geoShapeUtils.d.ts +15 -0
  224. package/dist/lib/core/validation/types/geoShapeUtils.js +75 -0
  225. package/dist/lib/core/validation/types/integer.d.ts +7 -5
  226. package/dist/lib/core/validation/types/integer.js +7 -13
  227. package/dist/lib/core/validation/types/ipAddress.d.ts +7 -11
  228. package/dist/lib/core/validation/types/ipAddress.js +13 -17
  229. package/dist/lib/core/validation/types/numeric.d.ts +7 -11
  230. package/dist/lib/core/validation/types/numeric.js +22 -29
  231. package/dist/lib/core/validation/types/object.d.ts +8 -15
  232. package/dist/lib/core/validation/types/object.js +12 -21
  233. package/dist/lib/core/validation/types/string.d.ts +7 -11
  234. package/dist/lib/core/validation/types/string.js +22 -29
  235. package/dist/lib/core/validation/types/url.d.ts +7 -11
  236. package/dist/lib/core/validation/types/url.js +14 -18
  237. package/dist/lib/core/validation/validation.d.ts +119 -80
  238. package/dist/lib/core/validation/validation.js +470 -412
  239. package/dist/lib/core/validation/validationUtils.d.ts +31 -0
  240. package/dist/lib/core/validation/validationUtils.js +190 -0
  241. package/dist/lib/kerror/codes/4-plugin.json +0 -12
  242. package/dist/lib/kerror/codes/index.d.ts +12 -2
  243. package/dist/lib/kerror/codes/index.js +5 -4
  244. package/dist/lib/kerror/errors/badRequestError.d.ts +1 -1
  245. package/dist/lib/kerror/errors/badRequestError.js +1 -1
  246. package/dist/lib/kerror/errors/externalServiceError.d.ts +1 -1
  247. package/dist/lib/kerror/errors/externalServiceError.js +1 -1
  248. package/dist/lib/kerror/errors/forbiddenError.d.ts +1 -1
  249. package/dist/lib/kerror/errors/forbiddenError.js +1 -1
  250. package/dist/lib/kerror/errors/gatewayTimeoutError.d.ts +1 -1
  251. package/dist/lib/kerror/errors/gatewayTimeoutError.js +1 -1
  252. package/dist/lib/kerror/errors/internalError.d.ts +1 -1
  253. package/dist/lib/kerror/errors/internalError.js +1 -1
  254. package/dist/lib/kerror/errors/kuzzleError.d.ts +32 -3
  255. package/dist/lib/kerror/errors/kuzzleError.js +21 -7
  256. package/dist/lib/kerror/errors/multipleErrorsError.d.ts +2 -2
  257. package/dist/lib/kerror/errors/multipleErrorsError.js +2 -2
  258. package/dist/lib/kerror/errors/notFoundError.d.ts +1 -1
  259. package/dist/lib/kerror/errors/notFoundError.js +1 -1
  260. package/dist/lib/kerror/errors/partialError.d.ts +8 -2
  261. package/dist/lib/kerror/errors/partialError.js +22 -9
  262. package/dist/lib/kerror/errors/pluginImplementationError.d.ts +9 -1
  263. package/dist/lib/kerror/errors/pluginImplementationError.js +9 -3
  264. package/dist/lib/kerror/errors/preconditionError.d.ts +1 -1
  265. package/dist/lib/kerror/errors/preconditionError.js +1 -1
  266. package/dist/lib/kerror/errors/serviceUnavailableError.d.ts +1 -1
  267. package/dist/lib/kerror/errors/serviceUnavailableError.js +1 -1
  268. package/dist/lib/kerror/errors/sizeLimitError.d.ts +1 -1
  269. package/dist/lib/kerror/errors/sizeLimitError.js +1 -1
  270. package/dist/lib/kerror/errors/tooManyRequestsError.d.ts +1 -1
  271. package/dist/lib/kerror/errors/tooManyRequestsError.js +1 -1
  272. package/dist/lib/kerror/errors/unauthorizedError.d.ts +1 -1
  273. package/dist/lib/kerror/errors/unauthorizedError.js +1 -1
  274. package/dist/lib/kerror/index.d.ts +19 -13
  275. package/dist/lib/kerror/index.js +60 -19
  276. package/dist/lib/kuzzle/Logger.d.ts +2 -2
  277. package/dist/lib/kuzzle/dumpGenerator.d.ts +12 -8
  278. package/dist/lib/kuzzle/dumpGenerator.js +130 -62
  279. package/dist/lib/kuzzle/event/KuzzleEventEmitter.d.ts +18 -9
  280. package/dist/lib/kuzzle/event/KuzzleEventEmitter.js +45 -18
  281. package/dist/lib/kuzzle/event/pipeRunner.d.ts +37 -16
  282. package/dist/lib/kuzzle/event/pipeRunner.js +73 -26
  283. package/dist/lib/kuzzle/event/waterfall.d.ts +11 -1
  284. package/dist/lib/kuzzle/event/waterfall.js +38 -13
  285. package/dist/lib/kuzzle/internalIndexHandler.d.ts +11 -174
  286. package/dist/lib/kuzzle/internalIndexHandler.js +58 -29
  287. package/dist/lib/kuzzle/kuzzle.d.ts +25 -10
  288. package/dist/lib/kuzzle/kuzzle.js +74 -17
  289. package/dist/lib/kuzzle/kuzzleStateEnum.d.ts +16 -12
  290. package/dist/lib/kuzzle/kuzzleStateEnum.js +5 -7
  291. package/dist/lib/kuzzle/vault.d.ts +5 -1
  292. package/dist/lib/kuzzle/vault.js +29 -17
  293. package/dist/lib/model/security/profile.d.ts +34 -5
  294. package/dist/lib/model/security/profile.js +6 -5
  295. package/dist/lib/model/security/rights.d.ts +12 -4
  296. package/dist/lib/model/security/rights.js +7 -6
  297. package/dist/lib/model/security/role.d.ts +8 -4
  298. package/dist/lib/model/security/role.js +12 -6
  299. package/dist/lib/model/security/token.d.ts +8 -0
  300. package/dist/lib/model/security/user.d.ts +8 -2
  301. package/dist/lib/model/security/user.js +7 -4
  302. package/dist/lib/model/storage/apiKey.d.ts +55 -26
  303. package/dist/lib/model/storage/apiKey.js +97 -24
  304. package/dist/lib/model/storage/baseModel.d.ts +77 -66
  305. package/dist/lib/model/storage/baseModel.js +21 -34
  306. package/dist/lib/service/cache/redis.d.ts +53 -36
  307. package/dist/lib/service/cache/redis.js +148 -71
  308. package/dist/lib/service/service.d.ts +14 -19
  309. package/dist/lib/service/service.js +41 -22
  310. package/dist/lib/service/storage/7/elasticsearch.d.ts +136 -93
  311. package/dist/lib/service/storage/7/elasticsearch.js +218 -165
  312. package/dist/lib/service/storage/7/esWrapper.d.ts +30 -13
  313. package/dist/lib/service/storage/7/esWrapper.js +58 -37
  314. package/dist/lib/service/storage/8/elasticsearch.d.ts +153 -119
  315. package/dist/lib/service/storage/8/elasticsearch.js +286 -213
  316. package/dist/lib/service/storage/8/esWrapper.d.ts +30 -13
  317. package/dist/lib/service/storage/8/esWrapper.js +59 -38
  318. package/dist/lib/service/storage/commons/queryTranslator.d.ts +3 -3
  319. package/dist/lib/service/storage/commons/queryTranslator.js +71 -35
  320. package/dist/lib/types/ApiRoute.d.ts +12 -0
  321. package/dist/lib/types/ApiRoute.js +23 -0
  322. package/dist/lib/types/ClientConnection.d.ts +1 -1
  323. package/dist/lib/types/EventHandler.d.ts +10 -0
  324. package/dist/lib/types/Global.d.ts +8 -3
  325. package/dist/lib/types/HttpMessage.d.ts +2 -2
  326. package/dist/lib/types/HttpStream.d.ts +1 -1
  327. package/dist/lib/types/HttpStream.js +3 -1
  328. package/dist/lib/types/JSONObject.d.ts +9 -0
  329. package/dist/lib/types/JSONObject.js +23 -0
  330. package/dist/lib/types/Kuzzle.d.ts +5 -3
  331. package/dist/lib/types/KuzzleDocument.d.ts +1 -1
  332. package/dist/lib/types/KuzzleWebSocket.d.ts +24 -0
  333. package/dist/lib/types/KuzzleWebSocket.js +23 -0
  334. package/dist/lib/types/OpenApiDefinition.d.ts +1 -1
  335. package/dist/lib/types/Plugin.d.ts +33 -8
  336. package/dist/lib/types/PluginInstance.d.ts +67 -0
  337. package/dist/lib/types/PluginInstance.js +23 -0
  338. package/dist/lib/types/Policy.d.ts +1 -1
  339. package/dist/lib/types/Target.d.ts +8 -0
  340. package/dist/lib/types/Token.d.ts +14 -2
  341. package/dist/lib/types/config/HttpConfiguration.d.ts +5 -0
  342. package/dist/lib/types/config/KuzzleConfiguration.d.ts +102 -1
  343. package/dist/lib/types/config/PluginsConfiguration.d.ts +3 -2
  344. package/dist/lib/types/config/SecurityConfiguration.d.ts +6 -2
  345. package/dist/lib/types/config/ServerConfiguration.d.ts +2 -2
  346. package/dist/lib/types/config/ServicesConfiguration.d.ts +1 -1
  347. package/dist/lib/types/config/cache/BaseCacheRedisConfiguration.d.ts +131 -0
  348. package/dist/lib/types/config/cache/BaseCacheRedisConfiguration.js +3 -0
  349. package/dist/lib/types/config/internalCache/InternalCacheRedisConfiguration.d.ts +2 -106
  350. package/dist/lib/types/config/publicCache/PublicCacheRedisConfiguration.d.ts +2 -26
  351. package/dist/lib/types/config/storageEngine/StorageEngineElasticsearchConfiguration.d.ts +3 -0
  352. package/dist/lib/types/controllers/Controller.d.ts +12 -3
  353. package/dist/lib/types/controllers/ControllerDefinition.d.ts +2 -2
  354. package/dist/lib/types/controllers/{adminControlller.type.js → adminController.type.js} +1 -1
  355. package/dist/lib/types/controllers/authController.type.d.ts +2 -2
  356. package/dist/lib/types/core/auth/formatProcessing.type.d.ts +7 -1
  357. package/dist/lib/types/errors/ErrorDomains.d.ts +1 -1
  358. package/dist/lib/types/events/EventGenericDocument.d.ts +10 -9
  359. package/dist/lib/types/events/EventProtocol.d.ts +4 -4
  360. package/dist/lib/types/index.d.ts +7 -0
  361. package/dist/lib/types/index.js +7 -0
  362. package/dist/lib/types/shared/StoreCollectionsDefinition.d.ts +1 -1
  363. package/dist/lib/types/storage/7/Elasticsearch.d.ts +62 -13
  364. package/dist/lib/types/storage/8/Elasticsearch.d.ts +62 -13
  365. package/dist/lib/util/Inflector.d.ts +4 -1
  366. package/dist/lib/util/Inflector.js +0 -3
  367. package/dist/lib/util/assertType.d.ts +24 -27
  368. package/dist/lib/util/assertType.js +28 -39
  369. package/dist/lib/util/async.js +19 -6
  370. package/dist/lib/util/asyncStore.js +1 -1
  371. package/dist/lib/util/bufferedPassThrough.d.ts +9 -0
  372. package/dist/lib/util/bufferedPassThrough.js +27 -13
  373. package/dist/lib/util/bytes.d.ts +1 -1
  374. package/dist/lib/util/bytes.js +6 -2
  375. package/dist/lib/util/debug.d.ts +3 -1
  376. package/dist/lib/util/debug.js +42 -10
  377. package/dist/lib/util/deprecate.d.ts +9 -1
  378. package/dist/lib/util/deprecate.js +10 -6
  379. package/dist/lib/util/didYouMean.d.ts +2 -1
  380. package/dist/lib/util/didYouMean.js +8 -4
  381. package/dist/lib/util/distributedLock.d.ts +56 -0
  382. package/dist/lib/util/distributedLock.js +166 -0
  383. package/dist/lib/util/dump-collection.d.ts +1 -1
  384. package/dist/lib/util/dump-collection.js +4 -5
  385. package/dist/lib/util/extractFields.d.ts +15 -11
  386. package/dist/lib/util/extractFields.js +8 -9
  387. package/dist/lib/util/koncordeCompat.d.ts +2 -2
  388. package/dist/lib/util/koncordeCompat.js +33 -0
  389. package/dist/lib/util/memoize.d.ts +7 -6
  390. package/dist/lib/util/memoize.js +3 -3
  391. package/dist/lib/util/mutex.d.ts +22 -2
  392. package/dist/lib/util/mutex.js +39 -1
  393. package/dist/lib/util/name-generator.js +14 -2
  394. package/dist/lib/util/promback.d.ts +20 -11
  395. package/dist/lib/util/promback.js +14 -7
  396. package/dist/lib/util/requestAssertions.d.ts +30 -10
  397. package/dist/lib/util/requestAssertions.js +125 -121
  398. package/dist/lib/util/safeObject.d.ts +3 -3
  399. package/dist/lib/util/safeObject.js +7 -3
  400. package/dist/lib/util/stackTrace.d.ts +13 -5
  401. package/dist/lib/util/stackTrace.js +17 -12
  402. package/dist/lib/util/thrown.d.ts +13 -0
  403. package/dist/lib/util/thrown.js +46 -0
  404. package/dist/package.json +26 -19
  405. package/package.json +26 -19
  406. package/dist/lib/util/wildcard.d.ts +0 -1
  407. package/dist/lib/util/wildcard.js +0 -46
  408. /package/dist/lib/types/controllers/{adminControlller.type.d.ts → adminController.type.d.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /*
2
3
  * Kuzzle, a backend software, self-hostable and ready to use
3
4
  * to power modern apps
@@ -18,22 +19,51 @@
18
19
  * See the License for the specific language governing permissions and
19
20
  * limitations under the License.
20
21
  */
21
- "use strict";
22
- const os = require("os");
23
- const net = require("net");
24
- const assert = require("assert");
25
- const Bluebird = require("bluebird");
26
- const EventEmitter = require("eventemitter3");
27
- const _ = require("lodash");
28
- const debug = require("../util/debug")("kuzzle:cluster:sync");
29
- const { Mutex } = require("../util/mutex");
30
- const { ClusterIdCardHandler } = require("./idCardHandler");
31
- const ClusterPublisher = require("./publisher");
32
- const ClusterSubscriber = require("./subscriber");
33
- const ClusterState = require("./state");
34
- const ClusterCommand = require("./command");
35
- const kuzzleStateEnum = require("../kuzzle/kuzzleStateEnum");
36
- const { fromKoncordeIndex } = require("../util/koncordeCompat");
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ // Bare specifiers, not the `node:` prefix: `node.test.js` intercepts `os` with
26
+ // `mock-require` to feed `getIP()` a fixed set of network interfaces, and a
27
+ // `node:os` import compiles to `require("node:os")`, which that interception
28
+ // does not see. A conversion must not change how a module is resolved — see
29
+ // J3a, where a hard-coded `.js` next to a renamed file took the whole
30
+ // functional matrix down.
31
+ const assert_1 = __importDefault(require("assert")); // NOSONAR
32
+ const util_1 = require("util"); // NOSONAR
33
+ const net_1 = __importDefault(require("net")); // NOSONAR
34
+ const os_1 = __importDefault(require("os")); // NOSONAR
35
+ const bluebird_1 = __importDefault(require("bluebird"));
36
+ const eventemitter3_1 = __importDefault(require("eventemitter3"));
37
+ const intersection_1 = __importDefault(require("lodash/intersection"));
38
+ const xor_1 = __importDefault(require("lodash/xor"));
39
+ const long_1 = __importDefault(require("long"));
40
+ const kuzzleStateEnum_1 = __importDefault(require("../kuzzle/kuzzleStateEnum"));
41
+ const debug_1 = __importDefault(require("../util/debug"));
42
+ const koncordeCompat_1 = require("../util/koncordeCompat");
43
+ // NOSONAR: `Mutex` is deprecated in favour of `withLock`, but the two use
44
+ // incompatible acquisition/TTL formats and must not contend on the same key —
45
+ // every node takes "clusterHandshake" with `Mutex`. Deferred to #2894.
46
+ const mutex_1 = require("../util/mutex"); // NOSONAR
47
+ const command_1 = __importDefault(require("./command"));
48
+ const idCardHandler_1 = require("./idCardHandler");
49
+ const publisher_1 = __importDefault(require("./publisher"));
50
+ const state_1 = __importDefault(require("./state"));
51
+ const subscriber_1 = __importDefault(require("./subscriber"));
52
+ const debug = (0, debug_1.default)("kuzzle:cluster:sync");
53
+ /**
54
+ * How long `addNode()` may wait for a joining node's first sync message before
55
+ * it answers that node's handshake anyway.
56
+ *
57
+ * `addNode()` runs inside the handshake command handler, and the joining node
58
+ * stops waiting for the answer after 2000 ms — hard-coded in
59
+ * `ClusterCommand._sendSingleHandshake`, and in every release before it, so a
60
+ * node joining during a rolling upgrade has the same limit. Waiting up to
61
+ * `2 × cluster.heartbeat` (4 s by default) there made the joiner give up on a
62
+ * node that had already added it: a one-sided membership (step 15, F-03).
63
+ * Half the joiner's limit leaves the rest for the Redis round trip and the
64
+ * command socket.
65
+ */
66
+ const HANDSHAKE_PROOF_BUDGET = 1000;
37
67
  /**
38
68
  * Test an IP address and determine if it's in the public or private range.
39
69
  *
@@ -41,15 +71,19 @@ const { fromKoncordeIndex } = require("../util/koncordeCompat");
41
71
  * @return {Boolean}
42
72
  */
43
73
  function isPrivateIP(ip) {
44
- if (net.isIPv6(ip)) {
45
- const prefix = ip.split(":")[0];
74
+ if (net_1.default.isIPv6(ip)) {
75
+ // `split` always yields a first element, empty string included.
76
+ const prefix = ip.split(":")[0] ?? "";
46
77
  return ((prefix.startsWith("fd") && prefix.length === 4) || prefix === "fe80");
47
78
  }
48
- // IPv4
79
+ // IPv4. `?? Number.NaN` on the octets a malformed address does not have: every
80
+ // comparison below is then false, which is what comparing `undefined` did.
49
81
  const exploded = ip.split(".").map((s) => Number.parseInt(s));
50
- return (exploded[0] === 10 ||
51
- (exploded[0] === 172 && exploded[1] >= 16 && exploded[1] <= 31) ||
52
- (exploded[0] === 192 && exploded[1] === 168));
82
+ const first = exploded[0] ?? Number.NaN;
83
+ const second = exploded[1] ?? Number.NaN;
84
+ return (first === 10 ||
85
+ (first === 172 && second >= 16 && second <= 31) ||
86
+ (first === 192 && second === 168));
53
87
  }
54
88
  /**
55
89
  * Some IPv4 addresses are reserved for internal uses only, and cannot be
@@ -60,18 +94,20 @@ function isPrivateIP(ip) {
60
94
  function isInternalIP(ip) {
61
95
  // To my knowledge, there aren't any reserved, non-loopback and non-routable
62
96
  // IPv6 addresses
63
- if (net.isIPv6(ip)) {
97
+ if (net_1.default.isIPv6(ip)) {
64
98
  return false;
65
99
  }
66
100
  const exploded = ip.split(".").map((s) => Number.parseInt(s));
101
+ const first = exploded[0] ?? Number.NaN;
102
+ const second = exploded[1] ?? Number.NaN;
67
103
  // 127.x.x: loopback addresses are already flagged as "internal" by
68
104
  // os.networkInterfaces.
69
- return (exploded[0] === 127 ||
105
+ return (first === 127 ||
70
106
  // 169.254.x.x addresses are APIPA addresses: temporary and non-routable.
71
107
  // We need to remove them from the accepted list of IP addresses
72
108
  // (this is a "just in case" scenario: APIPA addresses are obsolete and
73
109
  // should not be used anymore, but we never know...)
74
- (exploded[0] === 169 && exploded[1] === 254));
110
+ (first === 169 && second === 254));
75
111
  }
76
112
  /**
77
113
  * Return the first IP address matching the provided configuration
@@ -81,11 +117,12 @@ function isInternalIP(ip) {
81
117
  * @param {String} [options.ip] Used to target public or private addresses
82
118
  * @return {String|null}
83
119
  */
84
- function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
120
+ function getIP({ family = "IPv4", interface: netInterface, ip, } = {}) {
85
121
  const mustBePrivate = ip === "private";
86
122
  let interfaces = [];
87
- for (const [key, value] of Object.entries(os.networkInterfaces())) {
88
- for (const _interface of value) {
123
+ for (const [key, value] of Object.entries(os_1.default.networkInterfaces())) {
124
+ // `os.networkInterfaces()` declares its values optional.
125
+ for (const _interface of value ?? []) {
89
126
  interfaces.push({
90
127
  interface: key,
91
128
  ..._interface,
@@ -100,12 +137,13 @@ function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
100
137
  (!ip || mustBePrivate === isPrivateIP(n.address)));
101
138
  });
102
139
  debug("Filtered interfaces %o", interfaces);
103
- if (interfaces.length === 0) {
140
+ const [firstInterface] = interfaces;
141
+ if (firstInterface === undefined) {
104
142
  return null;
105
143
  }
106
144
  // take the first IP from the list if no interface has been defined
107
145
  if (!netInterface) {
108
- return interfaces[0].address;
146
+ return firstInterface.address;
109
147
  }
110
148
  for (const i of interfaces) {
111
149
  if ([i.interface, i.address, i.mac].includes(netInterface)) {
@@ -114,9 +152,7 @@ function getIP({ family = "IPv4", interface: netInterface, ip } = {}) {
114
152
  }
115
153
  return null;
116
154
  }
117
- /**
118
- * @typedef {nodeActivityEnum}
119
- */
155
+ /** What `trackActivity` records: a node joined, or a node was evicted. */
120
156
  const nodeActivityEnum = Object.freeze({
121
157
  ADDED: 1,
122
158
  EVICTED: 2,
@@ -124,34 +160,43 @@ const nodeActivityEnum = Object.freeze({
124
160
  // Handles the node logic: discovery, eviction, heartbeat, ...
125
161
  // Dependencies: core:cache module must be started
126
162
  class ClusterNode {
163
+ /**
164
+ * The id of the ID card this node reserved during `handshake()`.
165
+ *
166
+ * Null until the handshake gets that far, and read as a `string` at
167
+ * seventeen sites here plus `subscriber.ts` and `command.ts` — all of which
168
+ * run after it. The accessor says once where that can fail.
169
+ */
170
+ get nodeId() {
171
+ if (this._nodeId === null) {
172
+ throw new Error("[CLUSTER] No node id yet: the handshake has not run");
173
+ }
174
+ return this._nodeId;
175
+ }
176
+ set nodeId(id) {
177
+ this._nodeId = id;
178
+ }
127
179
  constructor() {
180
+ this._nodeId = null;
128
181
  this.config = global.kuzzle.config.cluster;
129
182
  this.logger = global.kuzzle.log.child("cluster:node");
130
183
  this.heartbeatDelay = this.config.heartbeat;
131
184
  const family = this.config.ipv6 ? "IPv6" : "IPv4";
132
- this.ip = getIP({
185
+ const ip = getIP({
133
186
  family,
134
187
  interface: this.config.interface,
135
188
  ip: this.config.ip,
136
189
  });
137
- debug("Found IP address: %s with config %o", this.ip, this.config);
138
- assert(this.ip !== null, `[CLUSTER] No suitable IP address found with the provided configuration (family: ${family}, interface: ${this.config.interface}, ip: ${this.config.ip})`);
139
- this.nodeId = null;
190
+ debug("Found IP address: %s with config %o", ip, this.config);
191
+ (0, assert_1.default)(ip !== null, `[CLUSTER] No suitable IP address found with the provided configuration (family: ${family}, interface: ${this.config.interface}, ip: ${this.config.ip})`);
192
+ this.ip = ip;
140
193
  this.heartbeatTimer = null;
141
- this.idCardHandler = new ClusterIdCardHandler(this);
142
- this.publisher = new ClusterPublisher(this);
143
- this.fullState = new ClusterState();
144
- this.command = new ClusterCommand(this);
145
- this.eventEmitter = new EventEmitter();
146
- /**
147
- * Links remote node IDs with their subscriber counterpart
148
- * @type {Map.<string, ClusterSubscriber>}
149
- */
194
+ this.idCardHandler = new idCardHandler_1.ClusterIdCardHandler(this);
195
+ this.publisher = new publisher_1.default(this);
196
+ this.fullState = new state_1.default();
197
+ this.command = new command_1.default(this);
198
+ this.eventEmitter = new eventemitter3_1.default();
150
199
  this.remoteNodes = new Map();
151
- /**
152
- * Cluster nodes activity, used to keep track of nodes being added or
153
- * removed, to give more insights to cluster statuses
154
- */
155
200
  this.activityMaxLength = this.config.activityDepth;
156
201
  this.activity = [];
157
202
  }
@@ -174,7 +219,7 @@ class ClusterNode {
174
219
  if (this.countActiveNodes() < this.config.minimumNodes) {
175
220
  this.logger.info("[CLUSTER] Not enough nodes active. Waiting for other nodes to join the cluster...");
176
221
  while (this.countActiveNodes() < this.config.minimumNodes) {
177
- await Bluebird.delay(100);
222
+ await bluebird_1.default.delay(100);
178
223
  }
179
224
  }
180
225
  return this.nodeId;
@@ -184,12 +229,18 @@ class ClusterNode {
184
229
  * nodes, and removes entries from the cache
185
230
  */
186
231
  async shutdown() {
187
- clearInterval(this.heartbeatTimer);
232
+ clearInterval(this.heartbeatTimer ?? undefined);
188
233
  await this.idCardHandler.dispose();
189
234
  for (const subscriber of this.remoteNodes.values()) {
190
235
  subscriber.dispose();
191
236
  }
192
- this.publisher.sendNodeShutdown(this.nodeId);
237
+ // `_nodeId`, not the accessor, which throws before the handshake: a SIGTERM
238
+ // during cluster init must still reach the two disposals below, as it did
239
+ // on v2.56.0. No peer knows this node before it has an id, so there is
240
+ // nobody to announce the shutdown to.
241
+ if (this._nodeId !== null) {
242
+ this.publisher.sendNodeShutdown(this._nodeId);
243
+ }
193
244
  await this.publisher.dispose();
194
245
  this.command.dispose();
195
246
  }
@@ -205,9 +256,7 @@ class ClusterNode {
205
256
  // when debugging.
206
257
  // Otherwise when recovering from a debug session, all the other nodes will be evicted.
207
258
  for (const subscriber of this.remoteNodes.values()) {
208
- subscriber.handleNodePreventEviction({
209
- evictionPrevented,
210
- });
259
+ subscriber.setEvictionPrevented(evictionPrevented);
211
260
  }
212
261
  }
213
262
  /**
@@ -221,16 +270,37 @@ class ClusterNode {
221
270
  if (this.remoteNodes.has(id)) {
222
271
  return false;
223
272
  }
224
- const subscriber = new ClusterSubscriber(this, id, ip);
273
+ const subscriber = new subscriber_1.default(this, id, ip);
225
274
  this.remoteNodes.set(id, subscriber);
226
275
  await subscriber.init();
276
+ // Same ordering `handshake()` needs, and for the same reason: `lastMessageId`
277
+ // reached this node over the *command* channel, in the joining node's
278
+ // handshake, while this subscription lives on the *sync* channel. Until the
279
+ // subscription takes effect the remote's PUB socket drops what it publishes,
280
+ // and `sync()` below would resume from just before the dropped message.
281
+ // TD-65 (#2773).
282
+ //
283
+ // What the wait protects is the *answer*: the joining node publishes
284
+ // nothing but heartbeats until every node has answered its handshake, so
285
+ // answering once the subscription is live keeps its first real messages
286
+ // from being dropped. It is bounded well below the joiner's handshake
287
+ // timeout (see HANDSHAKE_PROOF_BUDGET): the subscription itself is live
288
+ // within milliseconds, and what takes up to a heartbeat round is only the
289
+ // joiner's next heartbeat, the proof. Missing the proof is then routine
290
+ // rather than a warning, and a message lost anyway is a gap, which is
291
+ // retransmitted (#2785).
292
+ const proofTimeout = Math.min(this.heartbeatDelay * 2, HANDSHAKE_PROOF_BUDGET);
293
+ const live = await subscriber.waitForSubscription(proofTimeout);
294
+ if (!live) {
295
+ debug("[CLUSTER] No sync message received from node %s within %dms: answering its handshake with a subscription not proven live yet", id, proofTimeout);
296
+ }
227
297
  await subscriber.sync(lastMessageId);
228
298
  await this.idCardHandler.addNode(id);
229
299
  this.logger.info(`[CLUSTER] Node "${id}" joined the cluster`);
230
300
  this.trackActivity(id, ip, nodeActivityEnum.ADDED);
231
- if (global.kuzzle.state === kuzzleStateEnum.NOT_ENOUGH_NODES &&
301
+ if (global.kuzzle.state === kuzzleStateEnum_1.default.NOT_ENOUGH_NODES &&
232
302
  this.countActiveNodes() >= this.config.minimumNodes) {
233
- global.kuzzle.state = kuzzleStateEnum.RUNNING;
303
+ global.kuzzle.state = kuzzleStateEnum_1.default.RUNNING;
234
304
  this.logger.warn(`[CLUSTER] Minimum number of nodes reached (${this.countActiveNodes()}). This node is now accepting requests again.`);
235
305
  }
236
306
  return true;
@@ -247,7 +317,22 @@ class ClusterNode {
247
317
  if (error) {
248
318
  this.logger.error(error.stack);
249
319
  }
250
- this.publisher.sendNodeEvicted(this.nodeId, this.nodeId, reason);
320
+ // Same guard as `shutdown()`: before the handshake there is no id to
321
+ // broadcast, and throwing here would skip the shutdown below.
322
+ if (this._nodeId !== null) {
323
+ this.publisher.sendNodeEvicted(this._nodeId, this._nodeId, reason);
324
+ }
325
+ // The broadcast above tells the *other* nodes to forget this one. It cannot
326
+ // tell this one: a ZeroMQ PUB socket does not deliver to its own process, so
327
+ // `ClusterSubscriber.handleNodeEviction`'s `message.nodeId === localNode
328
+ // .nodeId` branch — the one that shuts the node down — is unreachable by the
329
+ // only node that needs it. Without this call the node leaves the cluster and
330
+ // keeps answering requests behind the load balancer, from state that has
331
+ // stopped advancing. See TD-67 (#2776).
332
+ //
333
+ // Exit code 1: this is a failure, and a node that exits 0 is not restarted
334
+ // by an `on-failure` policy (#2785).
335
+ global.kuzzle.shutdown(1);
251
336
  }
252
337
  /**
253
338
  * Evicts a remote from the list
@@ -256,7 +341,7 @@ class ClusterNode {
256
341
  * @param {boolean} [options.broadcast] - broadcast the eviction to the cluster
257
342
  * @param {string} [options.reason] - reason of eviction
258
343
  */
259
- async evictNode(nodeId, { broadcast = false, reason = "" }) {
344
+ async evictNode(nodeId, { broadcast = false, reason = "", }) {
260
345
  const subscriber = this.remoteNodes.get(nodeId);
261
346
  if (!subscriber) {
262
347
  this.logger.warn(`[CLUSTER] Node "${nodeId}" with no subscriber evicted. Reason: ${reason}`);
@@ -272,7 +357,7 @@ class ClusterNode {
272
357
  this.publisher.sendNodeEvicted(this.nodeId, nodeId, reason);
273
358
  }
274
359
  if (this.countActiveNodes() < this.config.minimumNodes) {
275
- global.kuzzle.state = kuzzleStateEnum.NOT_ENOUGH_NODES;
360
+ global.kuzzle.state = kuzzleStateEnum_1.default.NOT_ENOUGH_NODES;
276
361
  this.logger.warn(`[CLUSTER] Not enough nodes active (expected: ${this.config.minimumNodes}, active: ${this.countActiveNodes()}). Deactivating node until new ones are added.`);
277
362
  }
278
363
  }
@@ -286,80 +371,21 @@ class ClusterNode {
286
371
  async enforceClusterConsistency() {
287
372
  // Delay the check to 1 heartbeat round, to allow all nodes to update
288
373
  // their ID cards
289
- await Bluebird.delay(this.heartbeatDelay);
374
+ await bluebird_1.default.delay(this.heartbeatDelay);
290
375
  try {
291
376
  const idCards = await this.idCardHandler.getRemoteIdCards();
292
- idCards.push(this.idCardHandler.idCard);
293
- let splits = [];
294
- for (const idCard of idCards) {
295
- let topology = Array.from(idCard.topology);
296
- topology.push(idCard.id);
297
- if (topology.length !== idCards.length) {
298
- topology = topology.sort();
299
- const found = splits.some((split) => {
300
- if (split.length !== topology.length) {
301
- return false;
302
- }
303
- return split.every((id, index) => id === topology[index]);
304
- });
305
- if (!found) {
306
- splits.push(topology);
307
- }
308
- }
377
+ const ownIdCard = this.idCardHandler.idCard;
378
+ // A node with no ID card contributes no topology. Pushing `null` is
379
+ // what `detectSplits` then read `.topology` off, one line later.
380
+ if (ownIdCard !== null) {
381
+ idCards.push(ownIdCard);
309
382
  }
383
+ const splits = this.detectSplits(idCards);
310
384
  // No split detected, the cluster is consistent
311
385
  if (splits.length === 0) {
312
386
  return;
313
387
  }
314
- // There is at least 1 cluster split detected.
315
- //
316
- // First we elect the smallest split possible.
317
- // If multiple splits are eligibles, we choose amongst them the split
318
- // containing the youngest isolated node (isolated = the node is in this
319
- // split alone, and in no other splits). If no split is eligible using
320
- // this method, we fall back to the smallest split containing the youngest
321
- // node (isolated or not).
322
- //
323
- // The goal of this process is to force at least 1 node to kill itself,
324
- // with all nodes concluding on their own on the same list of nodes to
325
- // shut down.
326
- // First remove every non existing node from topologies
327
- splits = splits.map((topology) => topology.filter((nodeId) => idCards.find((card) => card.id === nodeId)));
328
- splits = splits.sort((a, b) => a.length - b.length);
329
- const eligibleSplits = splits.filter((split) => split.length === splits[0].length);
330
- let candidates;
331
- if (eligibleSplits.length === 1) {
332
- candidates = eligibleSplits[0];
333
- }
334
- else {
335
- // Beware: search isolated nodes in ALL the splits, not only the
336
- // smallest ones
337
- let isolatedNodes = _.xor(...splits);
338
- const eligibleNodes = _.uniq(_.flatten(eligibleSplits));
339
- isolatedNodes = _.intersection(isolatedNodes, eligibleNodes);
340
- const isIsolated = isolatedNodes.length > 0;
341
- // safety measure: this should never happen
342
- if (isolatedNodes.length === 0) {
343
- isolatedNodes = eligibleNodes;
344
- }
345
- let youngestNode;
346
- for (let i = 0; i < isolatedNodes.length; i++) {
347
- const idCard = idCards.find((card) => card.id === isolatedNodes[i]);
348
- if (!youngestNode || idCard.birthdate > youngestNode.birthdate) {
349
- youngestNode = idCard;
350
- }
351
- }
352
- if (isIsolated) {
353
- for (let i = 0; !candidates && i < eligibleSplits.length; i++) {
354
- if (eligibleSplits[i].includes(youngestNode.id)) {
355
- candidates = _.intersection(eligibleSplits[i], isolatedNodes);
356
- }
357
- }
358
- }
359
- else {
360
- candidates = [youngestNode.id];
361
- }
362
- }
388
+ const candidates = this.electShutdownCandidates(splits, idCards);
363
389
  if (candidates.includes(this.nodeId)) {
364
390
  this.logger.error("[CLUSTER] Network split detected. This node is outside the cluster: shutting down.");
365
391
  global.kuzzle.shutdown();
@@ -368,10 +394,160 @@ class ClusterNode {
368
394
  }
369
395
  catch (err) {
370
396
  this.logger.error("[CLUSTER] Unexpected exception caught during a cluster consistency check. Shutting down...");
371
- this.logger.error(err.stack);
397
+ this.logger.error(err instanceof Error ? err.stack : (0, util_1.inspect)(err));
372
398
  global.kuzzle.shutdown();
373
399
  }
374
400
  }
401
+ /**
402
+ * Builds the distinct topologies the cluster's ID cards disagree on. A
403
+ * topology shorter than the number of known cards is a split: that card sees
404
+ * fewer nodes than exist.
405
+ *
406
+ * Extracted from `enforceClusterConsistency()` unchanged — a pure function of
407
+ * the cards it is given, which is what makes moving it safe.
408
+ */
409
+ detectSplits(idCards) {
410
+ const splits = [];
411
+ for (const idCard of idCards) {
412
+ let topology = Array.from(idCard.topology);
413
+ topology.push(idCard.id);
414
+ if (topology.length !== idCards.length) {
415
+ // Explicit comparator rather than the default: identical for these
416
+ // ASCII node ids, and the split election compares the sorted arrays
417
+ // element by element, so the ordering is load-bearing.
418
+ topology = topology.sort((a, b) => {
419
+ if (a === b) {
420
+ return 0;
421
+ }
422
+ return a < b ? -1 : 1;
423
+ });
424
+ const found = splits.some((split) => {
425
+ if (split.length !== topology.length) {
426
+ return false;
427
+ }
428
+ return split.every((id, index) => id === topology[index]);
429
+ });
430
+ if (!found) {
431
+ splits.push(topology);
432
+ }
433
+ }
434
+ }
435
+ return splits;
436
+ }
437
+ /**
438
+ * Decides which nodes must shut themselves down, given the detected splits.
439
+ *
440
+ * Extracted from `enforceClusterConsistency()` unchanged. Every node runs
441
+ * this on its own and must reach the same list, so it is a pure function of
442
+ * `splits` and `idCards` — no clock, no randomness, no state.
443
+ */
444
+ electShutdownCandidates(splits, idCards) {
445
+ // There is at least 1 cluster split detected.
446
+ //
447
+ // First we elect the smallest split possible.
448
+ // If multiple splits are eligibles, we choose amongst them the split
449
+ // containing the youngest isolated node (isolated = the node is in this
450
+ // split alone, and in no other splits). If no split is eligible using
451
+ // this method, we fall back to the smallest split containing the youngest
452
+ // node (isolated or not).
453
+ //
454
+ // The goal of this process is to force at least 1 node to kill itself,
455
+ // with all nodes concluding on their own on the same list of nodes to
456
+ // shut down.
457
+ // First remove every non existing node from topologies
458
+ splits = splits.map((topology) => topology.filter((nodeId) => idCards.find((card) => card.id === nodeId)));
459
+ splits = splits.sort((a, b) => a.length - b.length);
460
+ const [smallest] = splits;
461
+ // The caller only reaches here with at least one split — the empty case
462
+ // returns before — and the sort then makes `splits[0]` the smallest.
463
+ if (smallest === undefined) {
464
+ return [];
465
+ }
466
+ const eligibleSplits = splits.filter((split) => split.length === smallest.length);
467
+ const [onlyEligible] = eligibleSplits;
468
+ if (eligibleSplits.length === 1 && onlyEligible !== undefined) {
469
+ return onlyEligible;
470
+ }
471
+ return this.electFromTiedSplits(eligibleSplits, splits, idCards);
472
+ }
473
+ /**
474
+ * Breaks a tie between equally small splits: prefer the one holding the
475
+ * youngest *isolated* node (present in that split and no other), and fall
476
+ * back to the youngest node overall.
477
+ *
478
+ * Extracted from `electShutdownCandidates()` unchanged, and pure like it —
479
+ * every node must reach the same answer from the same inputs.
480
+ */
481
+ electFromTiedSplits(eligibleSplits, splits, idCards) {
482
+ // Beware: search isolated nodes in ALL the splits, not only the
483
+ // smallest ones
484
+ let isolatedNodes = (0, xor_1.default)(...splits);
485
+ const eligibleNodes = [...new Set(eligibleSplits.flat())];
486
+ isolatedNodes = (0, intersection_1.default)(isolatedNodes, eligibleNodes);
487
+ const isIsolated = isolatedNodes.length > 0;
488
+ // safety measure: this should never happen
489
+ if (isolatedNodes.length === 0) {
490
+ isolatedNodes = eligibleNodes;
491
+ }
492
+ let youngestNode;
493
+ for (const isolatedNode of isolatedNodes) {
494
+ const idCard = idCards.find((card) => card.id === isolatedNode);
495
+ // `find` answers undefined for a node whose ID card has expired since
496
+ // the topologies were read. It was dereferenced for its birthdate.
497
+ if (idCard === undefined) {
498
+ continue;
499
+ }
500
+ if (!youngestNode || idCard.birthdate > youngestNode.birthdate) {
501
+ youngestNode = idCard;
502
+ }
503
+ }
504
+ // Every isolated node's ID card is gone: there is nobody left to elect.
505
+ if (youngestNode === undefined) {
506
+ return [];
507
+ }
508
+ if (!isIsolated) {
509
+ return [youngestNode.id];
510
+ }
511
+ for (const split of eligibleSplits) {
512
+ if (split.includes(youngestNode.id)) {
513
+ return (0, intersection_1.default)(split, isolatedNodes);
514
+ }
515
+ }
516
+ return [];
517
+ }
518
+ /**
519
+ * Handles a full-state round that no node answered: gives up if this was
520
+ * already the second attempt, otherwise drops every subscriber and waits a
521
+ * redis heartbeat round so the discovery can be redone against ID cards that
522
+ * may since have expired.
523
+ *
524
+ * Extracted from `handshake()` unchanged.
525
+ *
526
+ * @returns false when the caller must stop — the cluster is split and this
527
+ * node is shutting down
528
+ */
529
+ async retryFullState(retried) {
530
+ // Uh oh... no node was able to give us the full state.
531
+ // We must retry later, to check if the redis keys have expired. If they
532
+ // are still there and we still aren't able to fetch a full state, this
533
+ // means we're probably facing a network split, and we must then shut
534
+ // down.
535
+ if (retried) {
536
+ this.logger.error("[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.");
537
+ global.kuzzle.shutdown();
538
+ return false;
539
+ }
540
+ // Disposes all subscribers
541
+ for (const subscriber of this.remoteNodes.values()) {
542
+ subscriber.dispose();
543
+ }
544
+ this.remoteNodes.clear();
545
+ // Waits for a redis heartbeat round
546
+ const retryDelay = this.heartbeatDelay * 1.5;
547
+ this.logger.warn(`[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`);
548
+ await bluebird_1.default.delay(retryDelay);
549
+ return true;
550
+ }
375
551
  /**
376
552
  * Discovers other active nodes from the cluster and, if other nodes exist,
377
553
  * starts a handshake procedure to sync this node and to make it able to
@@ -384,9 +560,11 @@ class ClusterNode {
384
560
  this.logger.error(`[CLUSTER] Failed to join the cluster: timed out (joinTimeout: ${this.config.joinTimeout}ms)`);
385
561
  global.kuzzle.shutdown();
386
562
  }, this.config.joinTimeout);
387
- const mutex = new Mutex("clusterHandshake", {
388
- timeout: this.config.joinTimeout,
389
- });
563
+ // NOSONAR (S1874): same deferral as the import above — every node takes
564
+ // "clusterHandshake" with `Mutex`, so converting one site to `withLock`
565
+ // would remove the exclusion it exists for. #2894.
566
+ const lockOptions = { timeout: this.config.joinTimeout };
567
+ const mutex = new mutex_1.Mutex("clusterHandshake", lockOptions); // NOSONAR
390
568
  try {
391
569
  await mutex.lock();
392
570
  // Create the ID Key AFTER the handshake mutex is actually locked,
@@ -394,8 +572,10 @@ class ClusterNode {
394
572
  // node while it's still initializing)
395
573
  await this.idCardHandler.createIdCard();
396
574
  debug("[CLUSTER] ID Card created");
397
- this.nodeId = this.idCardHandler.nodeId;
398
- await this.startHeartbeat();
575
+ const nodeId = this.idCardHandler.nodeId;
576
+ (0, assert_1.default)(nodeId !== null, "[CLUSTER] createIdCard() returned without reserving a node id");
577
+ this.nodeId = nodeId;
578
+ await this.startHeartbeat(); // NOSONAR: TD-26
399
579
  debug("[CLUSTER] Start heartbeat");
400
580
  let retried = false;
401
581
  let fullState = null;
@@ -412,64 +592,35 @@ class ClusterNode {
412
592
  // Verify that no other node share the same IP address as this one
413
593
  const duplicate = nodes.filter((node) => node.ip === this.ip);
414
594
  if (duplicate.length > 0) {
415
- this.logger.error(`[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0].id}. Shutting down.`);
595
+ this.logger.error(`[CLUSTER] Another node share the same IP address as this one (${this.ip}): ${duplicate[0]?.id}. Shutting down.`);
416
596
  global.kuzzle.shutdown();
417
597
  return;
418
598
  }
419
- // Subscribe to remote nodes and start buffering sync messages
420
- await Bluebird.map(nodes, ({ id, ip }) => {
421
- const subscriber = new ClusterSubscriber(this, id, ip);
422
- this.remoteNodes.set(id, subscriber);
423
- return subscriber.init();
424
- });
599
+ await this.subscribeToNodes(nodes);
425
600
  debug("[CLUSTER] Successfully subscribed to nodes");
426
601
  fullState = await this.command.getFullState(nodes);
427
- // Uh oh... no node was able to give us the full state.
428
- // We must retry later, to check if the redis keys have expired. If they
429
- // are still there and we still aren't able to fetch a full state, this
430
- // means we're probably facing a network split, and we must then shut
431
- // down.
432
602
  if (fullState === null) {
433
- if (retried) {
434
- this.logger.error("[CLUSTER] Could not connect to discovered cluster nodes (network split detected). Shutting down.");
435
- global.kuzzle.shutdown();
603
+ if (!(await this.retryFullState(retried))) {
436
604
  return;
437
605
  }
438
- // Disposes all subscribers
439
- for (const subscriber of this.remoteNodes.values()) {
440
- subscriber.dispose();
441
- }
442
- this.remoteNodes.clear();
443
- // Waits for a redis heartbeat round
444
606
  retried = true;
445
- const retryDelay = this.heartbeatDelay * 1.5;
446
- this.logger.warn(`[CLUSTER] Unable to connect to discovered cluster nodes. Retrying in ${retryDelay}ms...`);
447
- await Bluebird.delay(retryDelay);
448
607
  }
449
608
  } while (fullState === null);
450
609
  debug("[CLUSTER] Fullstate retrieved, loading into node..");
451
- await this.fullState.loadFullState(fullState);
610
+ await this.fullState.loadFullState(fullState); // NOSONAR: TD-26
452
611
  this.activity = fullState.activity ? fullState.activity : this.activity;
453
612
  debug("[CLUSTER] Fullstate loaded.");
454
613
  const handshakeResponses = await this.command.broadcastHandshake(nodes);
455
614
  debug("[CLUSTER] Successful handshakes with other nodes.");
456
- // Update subscribers: start synchronizing, or unsubscribes from nodes who
457
- // didn't respond
458
- for (const [nodeId, handshakeData] of Object.entries(handshakeResponses)) {
459
- const subscriber = this.remoteNodes.get(nodeId);
460
- if (handshakeData === null) {
461
- subscriber.dispose();
462
- this.remoteNodes.delete(nodeId);
463
- }
464
- else {
465
- await this.idCardHandler.addNode(nodeId);
466
- const nodesStates = fullState.nodesState || [];
467
- const nodeStatus = nodesStates.find((node) => node.id === nodeId);
468
- subscriber.sync(nodeStatus ? nodeStatus.lastMessageId : handshakeData.lastMessageId);
469
- this.logger.info(`[CLUSTER] Successfully completed the handshake with node ${nodeId}`);
470
- }
471
- }
472
- this.logger.info("[CLUSTER] Successfully joined the cluster.");
615
+ await this.syncWithPeers(handshakeResponses, fullState);
616
+ // `createIdCard()` adopts `global.nodeId`, so these are normally the same
617
+ // string and the line reads as one name (TD-59, #2764). They still differ
618
+ // in the two cases the adoption cannot cover — a stale IdCard holding the
619
+ // key, and a Kuzzle running without a Backend — and those are exactly the
620
+ // cases where a reader needs to be told, so the line says both.
621
+ this.logger.info(global.nodeId && global.nodeId !== this.nodeId
622
+ ? `[CLUSTER] Successfully joined the cluster as "${this.nodeId}" (process node id: ${global.nodeId}).`
623
+ : `[CLUSTER] Successfully joined the cluster as "${this.nodeId}".`);
473
624
  this.trackActivity(this.nodeId, this.ip, nodeActivityEnum.ADDED);
474
625
  }
475
626
  finally {
@@ -477,6 +628,85 @@ class ClusterNode {
477
628
  await mutex.unlock();
478
629
  }
479
630
  }
631
+ /**
632
+ * Subscribes to every discovered node and starts buffering their sync
633
+ * messages.
634
+ *
635
+ * Extracted from `handshake()` unchanged, including its awaits: the wait
636
+ * below is what makes the `getFullState` that follows it safe, so the
637
+ * ordering is the point of the method, not an implementation detail.
638
+ */
639
+ async subscribeToNodes(nodes) {
640
+ // Subscribe to remote nodes and start buffering sync messages.
641
+ //
642
+ // The wait is what makes `getFullState` below safe: it snapshots each
643
+ // node's `lastMessageId` on the *command* channel, and this
644
+ // subscription lives on the *sync* channel, so nothing orders the
645
+ // snapshot after the subscription taking effect. Until it does, the
646
+ // remote's PUB socket drops what it publishes — and this node is then
647
+ // told to resume from just before the dropped message. See TD-65
648
+ // (#2773).
649
+ await bluebird_1.default.map(nodes, async ({ id, ip }) => {
650
+ const subscriber = new subscriber_1.default(this, id, ip);
651
+ this.remoteNodes.set(id, subscriber);
652
+ await subscriber.init();
653
+ const live = await subscriber.waitForSubscription(this.heartbeatDelay * 2);
654
+ if (!live) {
655
+ // Not fatal: a node whose ID card outlived it is exactly what the
656
+ // `fullState === null` retry below exists for. Say so, and let that
657
+ // path decide.
658
+ this.logger.warn(`[CLUSTER] No sync message received from node ${id} within ${this.heartbeatDelay * 2}ms: proceeding with a subscription that is not proven live.`);
659
+ }
660
+ });
661
+ }
662
+ /**
663
+ * Starts synchronizing with each peer that answered the handshake, and drops
664
+ * the ones that did not.
665
+ *
666
+ * Extracted from `handshake()` unchanged. `sync()` is awaited here for the
667
+ * reason written inside: a gap found during its replay evicts this node, and
668
+ * the success line must not be printed before that is known.
669
+ */
670
+ async syncWithPeers(handshakeResponses,
671
+ // `NonNullable`: the caller loops until the full state is not null, and
672
+ // this reads `nodesState` off it.
673
+ fullState) {
674
+ // Update subscribers: start synchronizing, or unsubscribes from nodes who
675
+ // didn't respond
676
+ for (const [nodeId, handshakeData] of Object.entries(handshakeResponses)) {
677
+ const subscriber = this.remoteNodes.get(nodeId);
678
+ // A response from a node this one never subscribed to: there is nothing
679
+ // to dispose of and nothing to sync. `subscribeToNodes` fills the map
680
+ // from the same list the handshake was broadcast to, so this is the
681
+ // gap between the two — and both branches below dereferenced it.
682
+ if (subscriber === undefined) {
683
+ this.remoteNodes.delete(nodeId);
684
+ continue;
685
+ }
686
+ if (handshakeData === null) {
687
+ subscriber.dispose();
688
+ this.remoteNodes.delete(nodeId);
689
+ }
690
+ else {
691
+ await this.idCardHandler.addNode(nodeId);
692
+ const nodesStates = fullState.nodesState || [];
693
+ const nodeStatus = nodesStates.find((node) => node.id === nodeId);
694
+ // Protobuf leaves an absent field undefined, and zero is where a
695
+ // subscriber that has seen nothing starts.
696
+ const lastMessageId = nodeStatus?.lastMessageId ??
697
+ handshakeData.lastMessageId ??
698
+ new long_1.default(0, 0, true);
699
+ // Awaited: `sync()` replays the buffered messages, and a gap found
700
+ // there evicts this node. Fired and forgotten, it used to print
701
+ // "Successfully completed the handshake" 2ms AFTER the eviction that
702
+ // contradicts it — a log describing a state the code is not in.
703
+ const synced = await subscriber.sync(lastMessageId);
704
+ if (synced) {
705
+ this.logger.info(`[CLUSTER] Successfully completed the handshake with node ${nodeId}`);
706
+ }
707
+ }
708
+ }
709
+ }
480
710
  countActiveNodes() {
481
711
  return this.remoteNodes.size + 1;
482
712
  }
@@ -522,7 +752,10 @@ class ClusterNode {
522
752
  nodes: [],
523
753
  };
524
754
  const idCards = await this.idCardHandler.getRemoteIdCards();
525
- idCards.push(this.idCardHandler.idCard);
755
+ const ownIdCard = this.idCardHandler.idCard;
756
+ if (ownIdCard !== null) {
757
+ idCards.push(ownIdCard);
758
+ }
526
759
  for (const idCard of idCards) {
527
760
  status.nodes.push({
528
761
  address: idCard.ip,
@@ -665,7 +898,7 @@ class ClusterNode {
665
898
  onNewRealtimeRoom(payload) {
666
899
  const roomMessageId = this.publisher.sendNewRealtimeRoom(payload);
667
900
  debug("[%s] Broadcasting new realtime room %s (message: %d)", this.nodeId, payload.id, roomMessageId);
668
- const icpair = fromKoncordeIndex(payload.index);
901
+ const icpair = (0, koncordeCompat_1.fromKoncordeIndex)(payload.index);
669
902
  this.fullState.addRealtimeRoom(payload.id, icpair.index, icpair.collection, payload.filter, {
670
903
  messageId: roomMessageId,
671
904
  nodeId: this.nodeId,