harper 5.1.20 → 5.2.0-alpha.5

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 (395) hide show
  1. package/agent/operations.ts +13 -16
  2. package/components/Application.ts +188 -7
  3. package/components/ApplicationScope.ts +3 -0
  4. package/components/Scope.ts +12 -0
  5. package/components/anthropic/index.ts +9 -2
  6. package/components/bedrock/index.ts +25 -2
  7. package/components/componentLoader.ts +56 -7
  8. package/components/componentSecrets.ts +451 -0
  9. package/components/deploymentRecorder.ts +7 -1
  10. package/components/mcp/adapters/fastify.ts +3 -0
  11. package/components/mcp/adapters/harperHttp.ts +3 -0
  12. package/components/mcp/audit.ts +21 -6
  13. package/components/mcp/customResourceRegistry.ts +17 -38
  14. package/components/mcp/listChanged.ts +37 -20
  15. package/components/mcp/quota.ts +145 -0
  16. package/components/mcp/rateLimit.ts +152 -14
  17. package/components/mcp/resources.ts +2 -4
  18. package/components/mcp/toolRegistry.ts +73 -4
  19. package/components/mcp/tools/application.ts +77 -8
  20. package/components/mcp/tools/operations.ts +106 -36
  21. package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
  22. package/components/mcp/tools/schemas/operations.ts +5 -0
  23. package/components/mcp/transport.ts +55 -4
  24. package/components/ollama/index.ts +6 -2
  25. package/components/openai/index.ts +9 -2
  26. package/components/operations.js +204 -10
  27. package/components/operationsValidation.js +173 -0
  28. package/components/secretOperations.ts +498 -0
  29. package/config/configUtils.ts +25 -1
  30. package/config-app.schema.json +81 -0
  31. package/config-root.schema.json +8 -1
  32. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  33. package/dataLayer/readAuditLog.ts +14 -0
  34. package/dataLayer/schemaDescribe.ts +1 -0
  35. package/dist/agent/operations.d.ts +7 -4
  36. package/dist/agent/operations.js +13 -15
  37. package/dist/agent/operations.js.map +1 -1
  38. package/dist/components/Application.d.ts +25 -2
  39. package/dist/components/Application.js +166 -7
  40. package/dist/components/Application.js.map +1 -1
  41. package/dist/components/ApplicationScope.d.ts +2 -0
  42. package/dist/components/ApplicationScope.js +3 -0
  43. package/dist/components/ApplicationScope.js.map +1 -1
  44. package/dist/components/Scope.d.ts +8 -0
  45. package/dist/components/Scope.js +11 -0
  46. package/dist/components/Scope.js.map +1 -1
  47. package/dist/components/anthropic/index.d.ts +4 -1
  48. package/dist/components/anthropic/index.js +4 -2
  49. package/dist/components/anthropic/index.js.map +1 -1
  50. package/dist/components/bedrock/index.d.ts +4 -1
  51. package/dist/components/bedrock/index.js +22 -2
  52. package/dist/components/bedrock/index.js.map +1 -1
  53. package/dist/components/componentLoader.d.ts +2 -0
  54. package/dist/components/componentLoader.js +52 -10
  55. package/dist/components/componentLoader.js.map +1 -1
  56. package/dist/components/componentSecrets.d.ts +63 -0
  57. package/dist/components/componentSecrets.js +405 -0
  58. package/dist/components/componentSecrets.js.map +1 -0
  59. package/dist/components/deploymentRecorder.d.ts +3 -0
  60. package/dist/components/deploymentRecorder.js +4 -3
  61. package/dist/components/deploymentRecorder.js.map +1 -1
  62. package/dist/components/mcp/adapters/fastify.d.ts +2 -0
  63. package/dist/components/mcp/adapters/fastify.js +1 -0
  64. package/dist/components/mcp/adapters/fastify.js.map +1 -1
  65. package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
  66. package/dist/components/mcp/adapters/harperHttp.js +1 -0
  67. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  68. package/dist/components/mcp/audit.d.ts +2 -2
  69. package/dist/components/mcp/audit.js +20 -5
  70. package/dist/components/mcp/audit.js.map +1 -1
  71. package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
  72. package/dist/components/mcp/customResourceRegistry.js +15 -30
  73. package/dist/components/mcp/customResourceRegistry.js.map +1 -1
  74. package/dist/components/mcp/listChanged.d.ts +7 -2
  75. package/dist/components/mcp/listChanged.js +28 -0
  76. package/dist/components/mcp/listChanged.js.map +1 -1
  77. package/dist/components/mcp/quota.d.ts +31 -0
  78. package/dist/components/mcp/quota.js +155 -0
  79. package/dist/components/mcp/quota.js.map +1 -0
  80. package/dist/components/mcp/rateLimit.d.ts +19 -3
  81. package/dist/components/mcp/rateLimit.js +129 -13
  82. package/dist/components/mcp/rateLimit.js.map +1 -1
  83. package/dist/components/mcp/resources.js.map +1 -1
  84. package/dist/components/mcp/toolRegistry.d.ts +39 -1
  85. package/dist/components/mcp/toolRegistry.js +60 -5
  86. package/dist/components/mcp/toolRegistry.js.map +1 -1
  87. package/dist/components/mcp/tools/application.d.ts +8 -1
  88. package/dist/components/mcp/tools/application.js +65 -5
  89. package/dist/components/mcp/tools/application.js.map +1 -1
  90. package/dist/components/mcp/tools/operations.d.ts +15 -3
  91. package/dist/components/mcp/tools/operations.js +99 -38
  92. package/dist/components/mcp/tools/operations.js.map +1 -1
  93. package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
  94. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  95. package/dist/components/mcp/tools/schemas/operations.js +4 -0
  96. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  97. package/dist/components/mcp/transport.d.ts +6 -0
  98. package/dist/components/mcp/transport.js +47 -3
  99. package/dist/components/mcp/transport.js.map +1 -1
  100. package/dist/components/ollama/index.d.ts +4 -1
  101. package/dist/components/ollama/index.js +4 -2
  102. package/dist/components/ollama/index.js.map +1 -1
  103. package/dist/components/openai/index.d.ts +4 -1
  104. package/dist/components/openai/index.js +4 -2
  105. package/dist/components/openai/index.js.map +1 -1
  106. package/dist/components/operations.d.ts +39 -0
  107. package/dist/components/operations.js +194 -2
  108. package/dist/components/operations.js.map +1 -1
  109. package/dist/components/operationsValidation.d.ts +38 -0
  110. package/dist/components/operationsValidation.js +155 -0
  111. package/dist/components/operationsValidation.js.map +1 -1
  112. package/dist/components/secretOperations.d.ts +113 -0
  113. package/dist/components/secretOperations.js +475 -0
  114. package/dist/components/secretOperations.js.map +1 -0
  115. package/dist/config/configUtils.d.ts +4 -1
  116. package/dist/config/configUtils.js +18 -1
  117. package/dist/config/configUtils.js.map +1 -1
  118. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  119. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  120. package/dist/dataLayer/readAuditLog.js +11 -0
  121. package/dist/dataLayer/readAuditLog.js.map +1 -1
  122. package/dist/dataLayer/schemaDescribe.js +2 -0
  123. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  124. package/dist/globals.d.ts +1 -1
  125. package/dist/globals.js +1 -0
  126. package/dist/globals.js.map +1 -1
  127. package/dist/index.d.ts +3 -0
  128. package/dist/index.js +1 -0
  129. package/dist/index.js.map +1 -1
  130. package/dist/json/systemSchema.json +38 -0
  131. package/dist/resources/DatabaseTransaction.d.ts +31 -3
  132. package/dist/resources/DatabaseTransaction.js +190 -29
  133. package/dist/resources/DatabaseTransaction.js.map +1 -1
  134. package/dist/resources/LMDBTransaction.d.ts +1 -1
  135. package/dist/resources/LMDBTransaction.js +43 -5
  136. package/dist/resources/LMDBTransaction.js.map +1 -1
  137. package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
  138. package/dist/resources/PrimaryRocksDatabase.js +194 -0
  139. package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
  140. package/dist/resources/RecordEncoder.d.ts +1 -0
  141. package/dist/resources/RecordEncoder.js +81 -75
  142. package/dist/resources/RecordEncoder.js.map +1 -1
  143. package/dist/resources/RequestTarget.d.ts +19 -0
  144. package/dist/resources/RequestTarget.js.map +1 -1
  145. package/dist/resources/Resource.d.ts +0 -1
  146. package/dist/resources/Resource.js +103 -16
  147. package/dist/resources/Resource.js.map +1 -1
  148. package/dist/resources/ResourceInterface.d.ts +6 -2
  149. package/dist/resources/ResourceInterface.js.map +1 -1
  150. package/dist/resources/Resources.js.map +1 -1
  151. package/dist/resources/Table.d.ts +6 -4
  152. package/dist/resources/Table.js +247 -24
  153. package/dist/resources/Table.js.map +1 -1
  154. package/dist/resources/analytics/read.js +25 -28
  155. package/dist/resources/analytics/read.js.map +1 -1
  156. package/dist/resources/blob.js +165 -22
  157. package/dist/resources/blob.js.map +1 -1
  158. package/dist/resources/crdt.d.ts +2 -0
  159. package/dist/resources/crdt.js +45 -36
  160. package/dist/resources/crdt.js.map +1 -1
  161. package/dist/resources/databases.d.ts +1 -0
  162. package/dist/resources/databases.js +134 -7
  163. package/dist/resources/databases.js.map +1 -1
  164. package/dist/resources/graphql.js +8 -0
  165. package/dist/resources/graphql.js.map +1 -1
  166. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
  167. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
  168. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  169. package/dist/resources/jsResource.js +6 -0
  170. package/dist/resources/jsResource.js.map +1 -1
  171. package/dist/resources/loadEnv.js +30 -3
  172. package/dist/resources/loadEnv.js.map +1 -1
  173. package/dist/resources/login.js +2 -2
  174. package/dist/resources/login.js.map +1 -1
  175. package/dist/resources/models/Models.js +14 -3
  176. package/dist/resources/models/Models.js.map +1 -1
  177. package/dist/resources/models/embedHook.js +10 -2
  178. package/dist/resources/models/embedHook.js.map +1 -1
  179. package/dist/resources/models/openaiStream.d.ts +56 -0
  180. package/dist/resources/models/openaiStream.js +94 -0
  181. package/dist/resources/models/openaiStream.js.map +1 -0
  182. package/dist/resources/replayLogs.js +4 -3
  183. package/dist/resources/replayLogs.js.map +1 -1
  184. package/dist/resources/search.d.ts +1 -1
  185. package/dist/resources/search.js +104 -11
  186. package/dist/resources/search.js.map +1 -1
  187. package/dist/resources/secretDecryptor.d.ts +54 -0
  188. package/dist/resources/secretDecryptor.js +131 -0
  189. package/dist/resources/secretDecryptor.js.map +1 -0
  190. package/dist/resources/tracked.js +15 -5
  191. package/dist/resources/tracked.js.map +1 -1
  192. package/dist/security/auth.js +71 -8
  193. package/dist/security/auth.js.map +1 -1
  194. package/dist/security/jsLoader.js +11 -0
  195. package/dist/security/jsLoader.js.map +1 -1
  196. package/dist/security/tokenAuthentication.d.ts +9 -1
  197. package/dist/security/tokenAuthentication.js +31 -1
  198. package/dist/security/tokenAuthentication.js.map +1 -1
  199. package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
  200. package/dist/server/DurableSubscriptionsSession.js +8 -4
  201. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  202. package/dist/server/REST.js +28 -9
  203. package/dist/server/REST.js.map +1 -1
  204. package/dist/server/fastifyRoutes.js +11 -1
  205. package/dist/server/fastifyRoutes.js.map +1 -1
  206. package/dist/server/graphqlQuerying.js +4 -3
  207. package/dist/server/graphqlQuerying.js.map +1 -1
  208. package/dist/server/http.d.ts +40 -2
  209. package/dist/server/http.js +560 -77
  210. package/dist/server/http.js.map +1 -1
  211. package/dist/server/itc/serverHandlers.js +64 -0
  212. package/dist/server/itc/serverHandlers.js.map +1 -1
  213. package/dist/server/liveSubscriptionAuth.d.ts +12 -0
  214. package/dist/server/liveSubscriptionAuth.js +140 -0
  215. package/dist/server/liveSubscriptionAuth.js.map +1 -0
  216. package/dist/server/loadRootComponents.js +2 -1
  217. package/dist/server/loadRootComponents.js.map +1 -1
  218. package/dist/server/middlewareChain.d.ts +44 -1
  219. package/dist/server/middlewareChain.js +86 -10
  220. package/dist/server/middlewareChain.js.map +1 -1
  221. package/dist/server/mqtt.js +2 -1
  222. package/dist/server/mqtt.js.map +1 -1
  223. package/dist/server/operationsServer.js +1 -1
  224. package/dist/server/operationsServer.js.map +1 -1
  225. package/dist/server/serverHelpers/Headers.d.ts +9 -0
  226. package/dist/server/serverHelpers/Headers.js +22 -0
  227. package/dist/server/serverHelpers/Headers.js.map +1 -1
  228. package/dist/server/serverHelpers/JSONStream.js +3 -3
  229. package/dist/server/serverHelpers/JSONStream.js.map +1 -1
  230. package/dist/server/serverHelpers/Request.d.ts +52 -1
  231. package/dist/server/serverHelpers/Request.js +122 -1
  232. package/dist/server/serverHelpers/Request.js.map +1 -1
  233. package/dist/server/serverHelpers/contentTypes.js +3 -8
  234. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  235. package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
  236. package/dist/server/serverHelpers/progressEmitter.js +52 -4
  237. package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
  238. package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
  239. package/dist/server/serverHelpers/registeredOperations.js +286 -0
  240. package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
  241. package/dist/server/serverHelpers/serverHandlers.js +11 -5
  242. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  243. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
  244. package/dist/server/serverHelpers/serverUtilities.js +67 -5
  245. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  246. package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
  247. package/dist/server/serverHelpers/uwsServer.js +433 -0
  248. package/dist/server/serverHelpers/uwsServer.js.map +1 -0
  249. package/dist/server/static.d.ts +0 -15
  250. package/dist/server/static.js +161 -2
  251. package/dist/server/static.js.map +1 -1
  252. package/dist/server/status/index.d.ts +4 -1
  253. package/dist/server/status/index.js +64 -4
  254. package/dist/server/status/index.js.map +1 -1
  255. package/dist/server/threads/manageThreads.d.ts +10 -0
  256. package/dist/server/threads/manageThreads.js +107 -4
  257. package/dist/server/threads/manageThreads.js.map +1 -1
  258. package/dist/server/threads/socketRouter.js +5 -1
  259. package/dist/server/threads/socketRouter.js.map +1 -1
  260. package/dist/server/threads/threadServer.js +92 -14
  261. package/dist/server/threads/threadServer.js.map +1 -1
  262. package/dist/upgrade/directives/5-2-0.d.ts +7 -0
  263. package/dist/upgrade/directives/5-2-0.js +108 -0
  264. package/dist/upgrade/directives/5-2-0.js.map +1 -0
  265. package/dist/upgrade/directives/directivesController.js +2 -1
  266. package/dist/upgrade/directives/directivesController.js.map +1 -1
  267. package/dist/utility/common_utils.js +5 -0
  268. package/dist/utility/common_utils.js.map +1 -1
  269. package/dist/utility/envFile.d.ts +41 -0
  270. package/dist/utility/envFile.js +221 -0
  271. package/dist/utility/envFile.js.map +1 -0
  272. package/dist/utility/globalSchema.d.ts +9 -0
  273. package/dist/utility/hdbTerms.d.ts +25 -0
  274. package/dist/utility/hdbTerms.js +31 -0
  275. package/dist/utility/hdbTerms.js.map +1 -1
  276. package/dist/utility/logging/harper_logger.d.ts +21 -0
  277. package/dist/utility/logging/harper_logger.js +160 -8
  278. package/dist/utility/logging/harper_logger.js.map +1 -1
  279. package/dist/utility/logging/logRotator.js +29 -17
  280. package/dist/utility/logging/logRotator.js.map +1 -1
  281. package/dist/utility/logging/readLog.d.ts +1 -1
  282. package/dist/utility/logging/readLog.js +305 -2
  283. package/dist/utility/logging/readLog.js.map +1 -1
  284. package/dist/utility/operationPermissions.d.ts +18 -0
  285. package/dist/utility/operationPermissions.js +35 -1
  286. package/dist/utility/operationPermissions.js.map +1 -1
  287. package/dist/utility/operation_authorization.d.ts +17 -0
  288. package/dist/utility/operation_authorization.js +38 -0
  289. package/dist/utility/operation_authorization.js.map +1 -1
  290. package/dist/utility/secretEnvelope.d.ts +30 -0
  291. package/dist/utility/secretEnvelope.js +94 -0
  292. package/dist/utility/secretEnvelope.js.map +1 -0
  293. package/dist/utility/signalling.d.ts +7 -0
  294. package/dist/utility/signalling.js +16 -0
  295. package/dist/utility/signalling.js.map +1 -1
  296. package/dist/validation/configValidator.js +70 -24
  297. package/dist/validation/configValidator.js.map +1 -1
  298. package/index.ts +4 -0
  299. package/json/systemSchema.json +38 -0
  300. package/npm-shrinkwrap.json +15066 -9347
  301. package/package.json +13 -8
  302. package/resources/DESIGN.md +15 -12
  303. package/resources/DatabaseTransaction.ts +206 -37
  304. package/resources/LMDBTransaction.ts +43 -9
  305. package/resources/PrimaryRocksDatabase.ts +201 -0
  306. package/resources/RecordEncoder.ts +81 -69
  307. package/resources/RequestTarget.ts +21 -0
  308. package/resources/Resource.ts +109 -25
  309. package/resources/ResourceInterface.ts +6 -2
  310. package/resources/Resources.ts +1 -3
  311. package/resources/Table.ts +238 -29
  312. package/resources/analytics/read.ts +25 -30
  313. package/resources/blob.ts +159 -23
  314. package/resources/crdt.ts +37 -31
  315. package/resources/databases.ts +143 -6
  316. package/resources/graphql.ts +8 -0
  317. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
  318. package/resources/jsResource.ts +6 -0
  319. package/resources/loadEnv.ts +34 -3
  320. package/resources/login.ts +2 -2
  321. package/resources/models/Models.ts +19 -3
  322. package/resources/models/embedHook.ts +13 -2
  323. package/resources/models/openaiStream.ts +133 -0
  324. package/resources/replayLogs.ts +4 -3
  325. package/resources/search.ts +113 -11
  326. package/resources/secretDecryptor.ts +159 -0
  327. package/resources/tracked.ts +24 -17
  328. package/security/auth.ts +70 -10
  329. package/security/jsLoader.ts +12 -0
  330. package/security/tokenAuthentication.ts +42 -1
  331. package/server/DESIGN.md +10 -0
  332. package/server/DurableSubscriptionsSession.ts +8 -4
  333. package/server/REST.ts +26 -9
  334. package/server/fastifyRoutes.ts +10 -1
  335. package/server/graphqlQuerying.ts +4 -3
  336. package/server/http.ts +563 -77
  337. package/server/itc/serverHandlers.js +70 -0
  338. package/server/liveSubscriptionAuth.ts +152 -0
  339. package/server/loadRootComponents.js +2 -1
  340. package/server/middlewareChain.ts +107 -17
  341. package/server/mqtt.ts +2 -1
  342. package/server/operationsServer.ts +2 -2
  343. package/server/serverHelpers/Headers.ts +24 -0
  344. package/server/serverHelpers/JSONStream.ts +3 -3
  345. package/server/serverHelpers/Request.ts +127 -0
  346. package/server/serverHelpers/contentTypes.ts +3 -8
  347. package/server/serverHelpers/progressEmitter.ts +55 -5
  348. package/server/serverHelpers/registeredOperations.ts +259 -0
  349. package/server/serverHelpers/serverHandlers.js +11 -5
  350. package/server/serverHelpers/serverUtilities.ts +88 -5
  351. package/server/serverHelpers/uwsServer.ts +488 -0
  352. package/server/static.ts +180 -2
  353. package/server/status/index.ts +72 -4
  354. package/server/threads/manageThreads.js +102 -4
  355. package/server/threads/socketRouter.ts +5 -1
  356. package/server/threads/threadServer.js +93 -10
  357. package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
  358. package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
  359. package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
  360. package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
  361. package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
  362. package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
  363. package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
  364. package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
  365. package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
  366. package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
  367. package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
  368. package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
  369. package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
  370. package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
  371. package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
  372. package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
  373. package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
  374. package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
  375. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
  376. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
  377. package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
  378. package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
  379. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
  380. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
  381. package/studio/web/index.html +1 -1
  382. package/upgrade/directives/5-2-0.ts +82 -0
  383. package/upgrade/directives/directivesController.ts +2 -1
  384. package/utility/common_utils.ts +5 -0
  385. package/utility/envFile.ts +218 -0
  386. package/utility/hdbTerms.ts +31 -0
  387. package/utility/logging/harper_logger.ts +156 -8
  388. package/utility/logging/logRotator.ts +24 -14
  389. package/utility/logging/readLog.ts +323 -2
  390. package/utility/operationPermissions.ts +35 -1
  391. package/utility/operation_authorization.ts +74 -1
  392. package/utility/secretEnvelope.ts +113 -0
  393. package/utility/signalling.ts +15 -0
  394. package/validation/configValidator.ts +78 -25
  395. package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
package/server/static.ts CHANGED
@@ -2,6 +2,8 @@ import { realpathSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { Scope } from '../components/Scope';
4
4
  import { resolveBaseURLPath } from '../components/resolveBaseURLPath.ts';
5
+ import { convertToMS } from '../utility/common_utils.ts';
6
+ import { isMatch } from 'micromatch';
5
7
  import send from 'send';
6
8
 
7
9
  /**
@@ -12,13 +14,134 @@ import send from 'send';
12
14
  * - `index`: If enabled, it will serve `index.html` files from directories.
13
15
  * - `extensions`: An array of file extensions to try when serving files. If a file is not found, it will try appending each extension in order. For example, if set to `['html'], and the request is `/page`, it will try `/page.html` if `/page` is not found.
14
16
  * - `fallthrough`: If true, it will fall through to the next handler if the file is not found. If false, it will return a 404 error.
17
+ * - `maxAge`: Freshness lifetime for served files — a number of seconds or a duration string
18
+ * (`'5m'`, `'1d'`). Emitted as `Cache-Control: public, max-age=<seconds>`. Defaults to 0
19
+ * (revalidate every request via the ETag/Last-Modified that are always emitted).
20
+ * - `immutable`: If true, adds the `immutable` directive to `Cache-Control` — for content-hashed
21
+ * assets that never change under the same URL. Requires `maxAge` to be meaningful.
22
+ * - `cacheControl`: Full `Cache-Control` override string (takes precedence over `maxAge`/`immutable`),
23
+ * or `false` to suppress the header entirely. Static files are served before authentication, so
24
+ * they are public by construction — do not put per-user content behind this handler.
25
+ * - `cacheOverrides`: A map of glob pattern → partial cache options (`maxAge` / `immutable` /
26
+ * `cacheControl`), letting specific files opt out of the top-level defaults. The typical case is
27
+ * long-lived `immutable` defaults for content-hashed assets while `index.html` gets a short window
28
+ * or `stale-while-revalidate`. Patterns are matched (via `micromatch`, same engine as `files`)
29
+ * against the mount-relative URL path **and** the served file's basename — so `index.html` also
30
+ * targets the directory-index (`/`) serve. Entries are tested in config order and the first match
31
+ * wins; each is a partial (keys present replace the default, absent keys inherit), with the same
32
+ * `cacheControl`-beats-`maxAge`/`immutable` precedence as the top level. Example:
33
+ * ```yaml
34
+ * static:
35
+ * files: 'web/**'
36
+ * maxAge: 1y
37
+ * immutable: true
38
+ * cacheOverrides:
39
+ * 'index.html': { cacheControl: 'public, max-age=0, stale-while-revalidate=60' }
40
+ * '*.html': { maxAge: 5m, immutable: false }
41
+ * ```
15
42
  * - `notFound`: Can be specified as a string to serve a custom 404 page, or an object with `file` and `statusCode` properties to serve a custom file with a specific status code. This is useful for hosting SPAs that use client-side routing. Make sure to set `fallthrough` to `false`!
43
+ * - `before` / `after`: Position this handler in the HTTP middleware chain relative to another named
44
+ * handler. By default the handler runs `before: 'authentication'` — and therefore before the REST
45
+ * handler — so plain file requests skip credential parsing. That default means a `fallthrough: false`
46
+ * catch-all answers GETs for exported REST resources too; an SPA with history-mode routing should set
47
+ * `after: 'rest'` so the API is matched first and only unmatched URLs receive the `notFound` fallback.
48
+ * `before: false` clears the default without adding a new constraint (registration order applies).
49
+ * Handler names are the component config keys as registered (e.g. `rest`, not the legacy `REST` alias);
50
+ * a name that matches no registered handler is ignored, with a warning logged by the middleware chain.
51
+ * Ordering is applied when the component loads; changing `before`/`after` triggers a component restart.
16
52
  *
17
53
  * This plugin dynamically updates its behavior based on the current configuration file. Users can make updates and immediately see the changes reflect in the next request.
18
54
  *
19
55
  * Updates to the `files` option will clear the in-memory maps and allow them to regenerate based on the new configuration (since the default EntryHandler will regenerate anyways).
20
56
  * Updates to `urlPath` request a restart: the HTTP route mount is registered once at load and cannot be re-registered on a live server (#1583).
21
57
  */
58
+ /**
59
+ * Resolve the effective cache-header inputs for a given served file: the live top-level
60
+ * `maxAge`/`immutable`/`cacheControl` options, with the first matching `cacheOverrides` entry layered
61
+ * on top (a partial — keys present replace the default, absent keys inherit). Patterns are matched
62
+ * (via `micromatch`, same engine as `files`) against the mount-relative URL path and the file's
63
+ * basename, so `index.html` also targets the directory-index (`/`) serve.
64
+ */
65
+ function resolveCacheOptions(scope: Scope, urlKey: string, basename: string) {
66
+ let maxAge = scope.options.get(['maxAge']);
67
+ let immutable = scope.options.get(['immutable']);
68
+ let cacheControl = scope.options.get(['cacheControl']);
69
+
70
+ const overrides = scope.options.get(['cacheOverrides']);
71
+ if (overrides !== undefined) {
72
+ if (typeof overrides !== 'object' || overrides === null || Array.isArray(overrides)) {
73
+ throw new Error(`Invalid cacheOverrides option: ${overrides}. Must be a map of glob pattern to cache options.`);
74
+ }
75
+ for (const pattern of Object.keys(overrides)) {
76
+ if (isMatch(urlKey, pattern) || isMatch(basename, pattern)) {
77
+ const override = overrides[pattern];
78
+ if (typeof override !== 'object' || override === null || Array.isArray(override)) {
79
+ throw new Error(
80
+ `Invalid cacheOverrides['${pattern}'] value: ${override}. Must be an object with maxAge/immutable/cacheControl.`
81
+ );
82
+ }
83
+ // partial merge: only keys present in the override replace the top-level default
84
+ if ('maxAge' in override) maxAge = override.maxAge;
85
+ if ('immutable' in override) immutable = override.immutable;
86
+ if ('cacheControl' in override) cacheControl = override.cacheControl;
87
+ break; // first match wins
88
+ }
89
+ }
90
+ }
91
+
92
+ immutable = immutable ?? false;
93
+ if (maxAge !== undefined && typeof maxAge !== 'number' && typeof maxAge !== 'string') {
94
+ throw new Error(`Invalid maxAge option: ${maxAge}. Must be a number of seconds or a duration string like '5m'.`);
95
+ }
96
+ const maxAgeMs = maxAge === undefined ? 0 : convertToMS(maxAge);
97
+ if (Number.isNaN(maxAgeMs)) {
98
+ throw new Error(`Invalid maxAge option: ${maxAge}. Must be a number of seconds or a duration string like '5m'.`);
99
+ }
100
+ if (typeof immutable !== 'boolean') {
101
+ throw new Error(`Invalid immutable option: ${immutable}. Must be a boolean.`);
102
+ }
103
+ if (cacheControl !== undefined && typeof cacheControl !== 'string' && cacheControl !== false) {
104
+ throw new Error(`Invalid cacheControl option: ${cacheControl}. Must be a string or false.`);
105
+ }
106
+ const customCacheControl = typeof cacheControl === 'string' ? cacheControl : undefined;
107
+ return { cacheControlDisabled: cacheControl === false, maxAgeMs, immutable, customCacheControl };
108
+ }
109
+
110
+ /**
111
+ * Serve a file through `send`, applying the plugin's cache-header options (read live from the
112
+ * component config, like `fallthrough`, and layered with any matching `cacheOverrides` entry).
113
+ * `cacheControl` as a string overrides `maxAge`/`immutable` via send's `headers` event;
114
+ * `cacheControl: false` suppresses the header entirely. Applied only to the main file serve — the
115
+ * `notFound` fallback keeps send's default `max-age=0`, which is the right policy for SPA index
116
+ * fallbacks.
117
+ */
118
+ function serveFile(req, path: string, scope: Scope) {
119
+ // The staticFiles map keys are the mount-relative URL path (leading slash stripped for matching);
120
+ // for a directory-index serve req.pathname is the directory, so also match the served basename.
121
+ const urlKey = typeof req.pathname === 'string' ? req.pathname.replace(/^\//, '') : '';
122
+ const basename = path.slice(Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')) + 1);
123
+ const { cacheControlDisabled, maxAgeMs, immutable, customCacheControl } = resolveCacheOptions(
124
+ scope,
125
+ urlKey,
126
+ basename
127
+ );
128
+
129
+ const stream = send(req, path, {
130
+ // suppress send's own header when we set a full override below (or when disabled)
131
+ cacheControl: cacheControlDisabled || customCacheControl !== undefined ? false : true,
132
+ maxAge: maxAgeMs,
133
+ immutable,
134
+ });
135
+ // an empty-string override intentionally behaves like `false` (send's header is suppressed
136
+ // above and no override is written)
137
+ if (customCacheControl) {
138
+ stream.on('headers', (response) => {
139
+ response.setHeader('Cache-Control', customCacheControl);
140
+ });
141
+ }
142
+ return stream;
143
+ }
144
+
22
145
  export function handleApplication(scope: Scope) {
23
146
  // in-memory map of static files
24
147
  // keys are the URL paths relative to the mount base, values are the absolute paths to the files
@@ -30,6 +153,34 @@ export function handleApplication(scope: Scope) {
30
153
  // with the registered route (#1583).
31
154
  const baseURLPath = resolveBaseURLPath(scope.pluginName, (scope.options.getAll() as any)?.urlPath);
32
155
 
156
+ // A bare `before:` / `after:` key in YAML parses as null — treat it as unset, like before this
157
+ // option was validated.
158
+ const before = scope.options.get(['before']) ?? undefined;
159
+ const after = scope.options.get(['after']) ?? undefined;
160
+ validateOrderingOption('before', before, true);
161
+ validateOrderingOption('after', after, false);
162
+ // Default to the pre-authentication hoist only when no ordering was configured at all.
163
+ const noOrderingConfigured = before === undefined && after === undefined;
164
+
165
+ // With the default ordering this handler answers unmatched GETs ahead of the REST handler, so a
166
+ // `fallthrough: false` catch-all makes any exported REST resources unreachable over GET.
167
+ // Reads the live option values (not the registration-time ones above): a config save can change
168
+ // the ordering options together with `fallthrough`, and this re-runs from the change listener.
169
+ const warnIfBlockingRest = () => {
170
+ const liveBefore = scope.options.get(['before']) ?? undefined;
171
+ const liveAfter = scope.options.get(['after']) ?? undefined;
172
+ if (
173
+ liveAfter === undefined &&
174
+ (liveBefore === undefined || liveBefore === 'authentication') &&
175
+ scope.options.get(['fallthrough']) === false
176
+ ) {
177
+ scope.logger.warn(
178
+ `The static handler runs before authentication and REST by default, so \`fallthrough: false\` answers every unmatched GET itself — including GETs for any exported REST resources. If this application serves an API, add \`after: 'rest'\` to the static options so API requests are matched first, or remove \`fallthrough: false\`.`
179
+ );
180
+ }
181
+ };
182
+ warnIfBlockingRest();
183
+
33
184
  scope.options.on('change', (key) => {
34
185
  if (key[0] === 'files') {
35
186
  // If the files option changes, clear the maps and let the entry handler regenerate them
@@ -43,6 +194,16 @@ export function handleApplication(scope: Scope) {
43
194
  scope.requestRestart();
44
195
  return;
45
196
  }
197
+ if (key[0] === 'fallthrough') {
198
+ warnIfBlockingRest();
199
+ return;
200
+ }
201
+ // `before`/`after` are consumed once at registration; the middleware chain can only pick
202
+ // up a new ordering by reloading the component. (Scope's own auto-restart on option
203
+ // changes is bypassed once a plugin registers its own 'change' listener, as we do above.)
204
+ if (key[0] === 'before' || key[0] === 'after') {
205
+ scope.requestRestart();
206
+ }
46
207
  });
47
208
 
48
209
  // Handle entry events for the default entry handler based on the `files` and `urlPath` options
@@ -171,7 +332,7 @@ export function handleApplication(scope: Scope) {
171
332
  // The benefit to using `send` is that it handles a lot of edge cases and headers for us.
172
333
  return {
173
334
  handlesHeaders: true,
174
- body: send(req, realpathSync(staticFile)),
335
+ body: serveFile(req, realpathSync(staticFile), scope),
175
336
  };
176
337
  }
177
338
 
@@ -206,7 +367,24 @@ export function handleApplication(scope: Scope) {
206
367
  body: send(req, realpathSync(notFoundPath)),
207
368
  };
208
369
  },
209
- { before: (scope.options.get(['before']) as string) ?? 'authentication' }
370
+ {
371
+ // `after` (e.g. `after: 'rest'`) must suppress the default pre-authentication hoist —
372
+ // combining the two constraints would be a cycle, which falls back to registration order.
373
+ before: typeof before === 'string' ? before : noOrderingConfigured ? 'authentication' : undefined,
374
+ after: after as string | undefined,
375
+ }
376
+ );
377
+ }
378
+
379
+ function validateOrderingOption(
380
+ name: string,
381
+ value: any,
382
+ allowFalse: boolean
383
+ ): asserts value is undefined | string | false {
384
+ if (value === undefined || (typeof value === 'string' && value.length > 0)) return;
385
+ if (allowFalse && value === false) return;
386
+ throw new Error(
387
+ `Invalid \`${name}\` option: ${value}. Must be the name of another handler${allowFalse ? ', or false to clear the default ordering' : ''}.`
210
388
  );
211
389
  }
212
390
 
@@ -5,6 +5,9 @@ import { validateStatus } from '../../validation/statusValidator.ts';
5
5
  import { type StatusId, type StatusValueMap, type StatusRecord, DEFAULT_STATUS_ID } from './definitions.ts';
6
6
  import { internal as statusInternal, type AggregatedComponentStatus } from '../../components/status/index.ts';
7
7
  import { restartNeeded } from '../../components/requestRestart.ts';
8
+ import { sendItcEvent } from '../threads/itc.js';
9
+ import { onMessageByType, workers } from '../threads/manageThreads.js';
10
+ import { ITC_EVENT_TYPES, THREAD_TYPES } from '../../utility/hdbTerms.ts';
8
11
 
9
12
  export { clearStatus as clear, getStatus as get, setStatus as set };
10
13
 
@@ -18,6 +21,9 @@ const { HTTP_STATUS_CODES } = hdbErrors;
18
21
  // For direct function calls, we don't need the operation fields
19
22
  type StatusRequestBody = {
20
23
  id: StatusId;
24
+ // Opt in to the resolved HTTP/upgrade/WebSocket middleware chains in the aggregated (no-id)
25
+ // response (#1573). Off by default so routine polling avoids the cross-thread lookup.
26
+ middleware?: boolean;
21
27
  };
22
28
 
23
29
  type StatusWriteRequestBody<T extends StatusId = StatusId> = {
@@ -76,9 +82,69 @@ interface AllStatusSummary {
76
82
  systemStatus: Promise<AsyncIterable<StatusRecord>>;
77
83
  componentStatus: AggregatedComponentStatusWithName[];
78
84
  restartRequired: boolean;
85
+ // Only present when the request opts in with `middleware: true`.
86
+ middlewareChains?: MiddlewareChainsSummary | null;
79
87
  }
80
88
 
81
- async function getAllStatus(): Promise<AllStatusSummary> {
89
+ type MiddlewareChainsSummary = ReturnType<typeof import('../http.ts').describeMiddlewareChains>;
90
+
91
+ let nextChainsRequestId = 1;
92
+ let chainsResponseListenerAttached = false;
93
+ const pendingChainsRequests = new Map<number, (chains: MiddlewareChainsSummary) => void>();
94
+
95
+ function attachChainsResponseListener(): void {
96
+ if (chainsResponseListenerAttached) return;
97
+ onMessageByType(ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_RESPONSE, ({ message }: any) => {
98
+ const resolve = pendingChainsRequests.get(message.requestId);
99
+ if (resolve) {
100
+ pendingChainsRequests.delete(message.requestId);
101
+ resolve(message.chains);
102
+ }
103
+ });
104
+ chainsResponseListenerAttached = true;
105
+ }
106
+
107
+ // App HTTP middleware is only registered on worker threads, so when get_status runs on the main
108
+ // thread we ask an HTTP worker for its resolved chains (all workers register identically). Returns
109
+ // null if no worker answers within the timeout — mirrors queryWorkerForOpenApi in operationsServer.
110
+ function queryWorkerForMiddlewareChains(): Promise<MiddlewareChainsSummary | null> {
111
+ attachChainsResponseListener();
112
+ const requestId = nextChainsRequestId++;
113
+ return new Promise((resolve) => {
114
+ const timeoutHandle = setTimeout(() => {
115
+ pendingChainsRequests.delete(requestId);
116
+ resolve(null);
117
+ }, 5000);
118
+ pendingChainsRequests.set(requestId, (chains) => {
119
+ clearTimeout(timeoutHandle);
120
+ resolve(chains);
121
+ });
122
+ sendItcEvent({ type: ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_REQUEST, message: { requestId } }).catch(() => {
123
+ clearTimeout(timeoutHandle);
124
+ pendingChainsRequests.delete(requestId);
125
+ resolve(null);
126
+ });
127
+ });
128
+ }
129
+
130
+ // Introspect the resolved HTTP/upgrade/WebSocket middleware order (#1573). In a multi-worker
131
+ // deployment the app middleware is registered on the HTTP worker threads while the main thread
132
+ // carries only the operations-API middleware, so when an HTTP worker exists we fetch the chains from
133
+ // one over ITC. With no HTTP worker this thread is the app server itself (single-thread mode, or a
134
+ // worker serving the request) and reports locally. Job workers don't serve HTTP, so they're ignored.
135
+ async function getMiddlewareChains(): Promise<MiddlewareChainsSummary | null> {
136
+ try {
137
+ if (workers.some((worker: { name?: string }) => worker.name === THREAD_TYPES.HTTP))
138
+ return await queryWorkerForMiddlewareChains();
139
+ const { describeMiddlewareChains } = await import('../http.ts');
140
+ return describeMiddlewareChains();
141
+ } catch (error) {
142
+ statusLogger.debug?.('getMiddlewareChains failed', error);
143
+ return null;
144
+ }
145
+ }
146
+
147
+ async function getAllStatus(includeMiddleware = false): Promise<AllStatusSummary> {
82
148
  statusLogger.debug?.('getAllStatus');
83
149
  const statusRecords = getStatusTable().search([]);
84
150
 
@@ -94,17 +160,19 @@ async function getAllStatus(): Promise<AllStatusSummary> {
94
160
  // Get restart flag status
95
161
  const restartRequired = restartNeeded();
96
162
 
97
- return {
163
+ const summary: AllStatusSummary = {
98
164
  systemStatus: statusRecords as Promise<AsyncIterable<StatusRecord>>,
99
165
  componentStatus: componentStatusArray,
100
166
  restartRequired,
101
167
  };
168
+ if (includeMiddleware) summary.middlewareChains = await getMiddlewareChains();
169
+ return summary;
102
170
  }
103
171
 
104
- function getStatus({ id }: Partial<StatusRequestBody>): Promise<StatusRecord | AllStatusSummary> {
172
+ function getStatus({ id, middleware }: Partial<StatusRequestBody>): Promise<StatusRecord | AllStatusSummary> {
105
173
  if (!id) {
106
174
  statusLogger.debug?.('getStatus', 'all');
107
- return getAllStatus();
175
+ return getAllStatus(middleware === true);
108
176
  }
109
177
 
110
178
  statusLogger.debug?.('getStatus', id);
@@ -136,6 +136,8 @@ module.exports = {
136
136
  setTerminateTimeout,
137
137
  extendShutdownDeadline,
138
138
  restoreShutdownDeadline,
139
+ registerWorkerDataProvider,
140
+ onThreadExit,
139
141
  restartNumber: workerData?.restartNumber || 1,
140
142
  };
141
143
 
@@ -178,6 +180,59 @@ function setMainIsWorker(isWorker) {
178
180
  module.exports.threadsHaveStarted();
179
181
  }
180
182
  let workerCount = 1; // should be assigned when workers are created
183
+
184
+ // Every workerData key core itself produces or consumes — providers may not collide with these.
185
+ // Covers the keys startWorker spreads below plus keys read elsewhere: `noServerStart` is set by
186
+ // the embedding entry point (index.ts) and read by threadServer.js to skip startServers(); a
187
+ // provider shadowing it would wedge HTTP worker startup.
188
+ const RESERVED_WORKER_DATA_KEYS = [
189
+ 'addPorts',
190
+ 'addThreadIds',
191
+ 'workerIndex',
192
+ 'workerCount',
193
+ 'name',
194
+ 'restartNumber',
195
+ 'ticketKeys',
196
+ 'noServerStart',
197
+ '__proto__', // never a legitimate payload name; spread would define it as an own property
198
+ ];
199
+ const workerDataProviders = new Map();
200
+ /**
201
+ * Register a provider that contributes an extra `workerData` property to every worker spawned
202
+ * from this thread. `provider(options)` receives the startWorker options (`options.name` is the
203
+ * thread type, e.g. 'http' or 'job') and returns the value to place at `workerData[name]`, or
204
+ * undefined to skip that worker. Values must be structured-cloneable; a provider that throws or
205
+ * returns a non-cloneable value is logged and skipped so it can never break a spawn.
206
+ * Returns a function that unregisters the provider.
207
+ */
208
+ function registerWorkerDataProvider(name, provider) {
209
+ if (RESERVED_WORKER_DATA_KEYS.includes(name) || workerDataProviders.has(name)) {
210
+ throw new Error(`workerData provider name '${name}' is already in use`);
211
+ }
212
+ if (typeof provider !== 'function') throw new Error('workerData provider must be a function');
213
+ workerDataProviders.set(name, provider);
214
+ return () => {
215
+ if (workerDataProviders.get(name) === provider) workerDataProviders.delete(name);
216
+ };
217
+ }
218
+ function collectProvidedWorkerData(options) {
219
+ if (workerDataProviders.size === 0) return undefined;
220
+ let provided;
221
+ for (const [name, provider] of workerDataProviders) {
222
+ try {
223
+ const value = provider(options);
224
+ if (value === undefined) continue;
225
+ // Use the clone, not the original: this both pre-flights cloneability (so a bad value
226
+ // can't break the Worker spawn) and detaches the payload from accessor-backed objects
227
+ // or later mutation that could still throw inside new Worker(). Null prototype so a
228
+ // provider name can never collide with Object.prototype members.
229
+ (provided ??= Object.create(null))[name] = structuredClone(value);
230
+ } catch (error) {
231
+ harperLogger.error(`workerData provider '${name}' failed and will be skipped for this worker:`, error);
232
+ }
233
+ }
234
+ return provided;
235
+ }
181
236
  let ticketKeys;
182
237
  function getTicketKeys() {
183
238
  if (ticketKeys) return ticketKeys;
@@ -212,6 +267,11 @@ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.USER, null);
212
267
  listenersByType.set(hdbTerms.ITC_EVENT_TYPES.COMPONENT_STATUS_REQUEST, null);
213
268
  listenersByType.set(hdbTerms.ITC_EVENT_TYPES.RESOURCE_OPENAPI_REQUEST, null);
214
269
  listenersByType.set(hdbTerms.ITC_EVENT_TYPES.RESOURCE_OPENAPI_RESPONSE, null);
270
+ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_REQUEST, null);
271
+ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_RESPONSE, null);
272
+ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.OPERATION_REGISTERED, null);
273
+ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.OPERATION_EXECUTE_REQUEST, null);
274
+ listenersByType.set(hdbTerms.ITC_EVENT_TYPES.OPERATION_EXECUTE_RESPONSE, null);
215
275
 
216
276
  function startWorker(path, options = {}) {
217
277
  // Take a percentage of total memory to determine the max memory for each thread. The percentage is based
@@ -281,6 +341,7 @@ function startWorker(path, options = {}) {
281
341
  argv: process.argv.slice(2),
282
342
  // pass these in synchronously to the worker so it has them on startup:
283
343
  workerData: {
344
+ ...collectProvidedWorkerData(options),
284
345
  addPorts: portsToSend,
285
346
  addThreadIds: channelsToConnect.map((channel) => channel.existingPort.threadId),
286
347
  workerIndex: options.workerIndex,
@@ -380,10 +441,16 @@ async function restartWorkers(
380
441
  // make a copy of the workers before iterating them, as the workers array mutates a lot during this
381
442
  let waitingToFinish = []; // promises for workers we have shut down and are waiting to exit
382
443
  // We can only start the replacement *before* the old worker releases its port when the OS lets
383
- // both listen on the same port at once (SO_REUSEPORT). Without that — Windows (no SO_REUSEPORT)
384
- // and Bun the replacement can't bind until the old worker exits, so there we keep the original
385
- // ordering: shut the old worker down first, then start the replacement (an unavoidable brief gap).
386
- const canPreStartReplacement = process.platform !== 'win32' && !isBun;
444
+ // both listen on the same port at once (SO_REUSEPORT). Without that — Windows (no SO_REUSEPORT),
445
+ // macOS (unreliable SO_REUSEPORT, so workers bind exclusively), and Bun the replacement can't
446
+ // bind a port the old worker still holds: its EADDRINUSE would be swallowed and the port left
447
+ // unbound once the old worker exits (this silently killed worker-owned listeners like MQTT on
448
+ // macOS after every component-reload restart). So there we keep the original ordering: shut the
449
+ // old worker down first (server.close() releases its ports immediately), then start the
450
+ // replacement — an unavoidable brief gap, but only for worker-owned listeners, since the main
451
+ // thread keeps serving the HTTP ports throughout. This ordering is also what lets
452
+ // listenOnPorts() treat a dedicated listener's EADDRINUSE as an external conflict.
453
+ const canPreStartReplacement = process.platform !== 'win32' && process.platform !== 'darwin' && !isBun;
387
454
  for (let worker of workers.slice(0)) {
388
455
  if ((name && worker.name !== name) || worker.wasShutdown) continue; // filter by type, if specified
389
456
  const overlapping = OVERLAPPING_RESTART_TYPES.indexOf(worker.name) > -1;
@@ -766,10 +833,36 @@ if (parentPort && workerData?.addPorts) {
766
833
  }
767
834
  module.exports.getThreadInfo = getThreadInfo;
768
835
 
836
+ // Listeners notified when a connected thread's port closes (worker exit/restart), so
837
+ // modules holding per-thread state (e.g. registeredOperations' registry and in-flight
838
+ // forwards) can clean up.
839
+ const threadExitListeners = [];
840
+ function onThreadExit(listener) {
841
+ threadExitListeners.push(listener);
842
+ }
843
+
844
+ // Thread ids already reported to threadExitListeners, so a dead worker is only reported once
845
+ // regardless of which of the two removal paths below observes it first. Node worker_threads
846
+ // ids are monotonically increasing and never reused within a process, so this never needs
847
+ // pruning (unbounded growth is one entry per worker restart over the process lifetime).
848
+ const notifiedDeadThreadIds = new Set();
849
+ function notifyThreadExit(deadThreadId) {
850
+ if (deadThreadId == null || notifiedDeadThreadIds.has(deadThreadId)) return;
851
+ notifiedDeadThreadIds.add(deadThreadId);
852
+ for (const listener of threadExitListeners) {
853
+ try {
854
+ listener(deadThreadId);
855
+ } catch (error) {
856
+ harperLogger.error(error);
857
+ }
858
+ }
859
+ }
860
+
769
861
  function removePort(port, deadThreadId) {
770
862
  const idx = connectedPorts.indexOf(port);
771
863
  if (idx === -1) return;
772
864
  connectedPorts.splice(idx, 1);
865
+ if (deadThreadId != null) notifyThreadExit(deadThreadId);
773
866
  // Notify remaining peers to remove this dead sibling port. In Bun, sibling
774
867
  // MessagePorts don't emit 'close' when a peer worker exits, so we broadcast
775
868
  // a REMOVE_PORT message from here (which fires reliably on Worker 'exit')
@@ -804,6 +897,11 @@ function addPort(port, keepRef, isJobWorker) {
804
897
  } else if (message.type === REMOVE_PORT) {
805
898
  const idx = connectedPorts.findIndex((p) => p.threadId === message.threadId);
806
899
  if (idx !== -1) connectedPorts.splice(idx, 1);
900
+ // A sibling's port-to-the-dead-worker can close (and broadcast this) before this
901
+ // thread's OWN port to that worker fires its 'close'/'exit' — at which point
902
+ // removePort() would no-op (already spliced) and threadExitListeners would never
903
+ // fire. Notify here too; notifyThreadExit dedupes so it isn't reported twice.
904
+ notifyThreadExit(message.threadId);
807
905
  } else {
808
906
  notifyMessageListeners(message, port);
809
907
  }
@@ -18,7 +18,7 @@ if (isMainThread) {
18
18
  harperLogger.disableStdio();
19
19
  return;
20
20
  }
21
- console.error('uncaughtException', error);
21
+ console.error('uncaughtException', harperLogger.errorForLog(error));
22
22
  });
23
23
  }
24
24
 
@@ -30,6 +30,10 @@ export async function startHTTPThreads(threadCount = 2, dynamicThreads?: boolean
30
30
  startTransactionLogCooling();
31
31
  try {
32
32
  if (dynamicThreads) {
33
+ // No caller currently passes dynamicThreads. If one ever does, note that the main thread
34
+ // does not bind ports in this mode, so on platforms without SO_REUSEPORT (macOS/Windows)
35
+ // worker 0's exclusive HTTP bind would silently swallow an external EADDRINUSE — the
36
+ // external-conflict detection in listenOnPorts() assumes the main thread binds first.
33
37
  startHTTPWorker(0, 1);
34
38
  } else {
35
39
  const { loadRootComponents } = require('../loadRootComponents.js');