@harperfast/harper 5.2.13 → 5.3.0-beta.1

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 (655) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +58 -9
  4. package/bin/copyDb.ts +282 -59
  5. package/bin/deploySetup.ts +16 -5
  6. package/bin/harper.ts +1 -1
  7. package/bin/help.ts +4 -1
  8. package/bin/lite.ts +4 -1
  9. package/bin/restart.ts +120 -4
  10. package/bin/run.ts +6 -11
  11. package/bin/upgrade.js +7 -3
  12. package/bin/workloadIdentity.ts +119 -0
  13. package/components/Application.ts +3436 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +440 -98
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +56 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +381 -32
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/deploymentOperations.ts +4 -1
  23. package/components/deploymentRecorder.ts +9 -2
  24. package/components/mcp/adapters/harperHttp.ts +4 -0
  25. package/components/mcp/listChanged.ts +4 -0
  26. package/components/mcp/toolRegistry.ts +2 -0
  27. package/components/mcp/tools/operations.ts +9 -0
  28. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  29. package/components/operations.js +537 -113
  30. package/components/operationsValidation.js +98 -3
  31. package/components/packageComponent.ts +25 -1
  32. package/components/requestRestart.ts +11 -0
  33. package/components/status/ComponentStatusRegistry.ts +59 -0
  34. package/config/RootConfigWatcher.ts +240 -40
  35. package/config/configReadRetry.ts +62 -0
  36. package/config/configUtils.ts +357 -48
  37. package/config/harperConfigEnvVars.ts +170 -27
  38. package/config/parseConfigFile.ts +34 -0
  39. package/config/readConfigFileSync.ts +44 -0
  40. package/config/watcherArming.ts +59 -0
  41. package/config-root.schema.json +33 -0
  42. package/dataLayer/blobBackup.ts +160 -50
  43. package/dataLayer/delete.ts +6 -1
  44. package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
  45. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  46. package/dataLayer/hdbInfoController.ts +34 -1
  47. package/dataLayer/insert.ts +44 -1
  48. package/dataLayer/rocksdbBackup.ts +53 -10
  49. package/dataLayer/schema.ts +11 -1
  50. package/dataLayer/schemaDescribe.ts +8 -1
  51. package/dist/agent/mcpTools.js +1 -1
  52. package/dist/agent/mcpTools.js.map +1 -1
  53. package/dist/agent/session.d.ts +22 -0
  54. package/dist/agent/session.js +26 -15
  55. package/dist/agent/session.js.map +1 -1
  56. package/dist/bin/cliOperations.js +61 -9
  57. package/dist/bin/cliOperations.js.map +1 -1
  58. package/dist/bin/copyDb.d.ts +12 -1
  59. package/dist/bin/copyDb.js +248 -60
  60. package/dist/bin/copyDb.js.map +1 -1
  61. package/dist/bin/deploySetup.d.ts +2 -0
  62. package/dist/bin/deploySetup.js +11 -3
  63. package/dist/bin/deploySetup.js.map +1 -1
  64. package/dist/bin/harper.js +1 -1
  65. package/dist/bin/harper.js.map +1 -1
  66. package/dist/bin/help.js +4 -1
  67. package/dist/bin/help.js.map +1 -1
  68. package/dist/bin/lite.js +4 -1
  69. package/dist/bin/lite.js.map +1 -1
  70. package/dist/bin/restart.js +91 -6
  71. package/dist/bin/restart.js.map +1 -1
  72. package/dist/bin/run.js +4 -10
  73. package/dist/bin/run.js.map +1 -1
  74. package/dist/bin/upgrade.js +4 -3
  75. package/dist/bin/upgrade.js.map +1 -1
  76. package/dist/bin/workloadIdentity.d.ts +18 -0
  77. package/dist/bin/workloadIdentity.js +100 -0
  78. package/dist/bin/workloadIdentity.js.map +1 -0
  79. package/dist/components/Application.d.ts +235 -17
  80. package/dist/components/Application.js +3038 -238
  81. package/dist/components/Application.js.map +1 -1
  82. package/dist/components/ApplicationScope.d.ts +8 -0
  83. package/dist/components/ApplicationScope.js +7 -0
  84. package/dist/components/ApplicationScope.js.map +1 -1
  85. package/dist/components/EntryHandler.js +26 -10
  86. package/dist/components/EntryHandler.js.map +1 -1
  87. package/dist/components/OptionsWatcher.d.ts +4 -0
  88. package/dist/components/OptionsWatcher.js +440 -99
  89. package/dist/components/OptionsWatcher.js.map +1 -1
  90. package/dist/components/RuntimeModuleTracker.js +40 -6
  91. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  92. package/dist/components/Scope.js +52 -13
  93. package/dist/components/Scope.js.map +1 -1
  94. package/dist/components/awaitRestart.d.ts +33 -0
  95. package/dist/components/awaitRestart.js +61 -0
  96. package/dist/components/awaitRestart.js.map +1 -0
  97. package/dist/components/componentLoader.d.ts +38 -1
  98. package/dist/components/componentLoader.js +313 -24
  99. package/dist/components/componentLoader.js.map +1 -1
  100. package/dist/components/componentPreparationLock.d.ts +5 -0
  101. package/dist/components/componentPreparationLock.js +14 -6
  102. package/dist/components/componentPreparationLock.js.map +1 -1
  103. package/dist/components/deploymentOperations.js +4 -1
  104. package/dist/components/deploymentOperations.js.map +1 -1
  105. package/dist/components/deploymentRecorder.d.ts +4 -2
  106. package/dist/components/deploymentRecorder.js +1 -0
  107. package/dist/components/deploymentRecorder.js.map +1 -1
  108. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  109. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  110. package/dist/components/mcp/listChanged.js +5 -0
  111. package/dist/components/mcp/listChanged.js.map +1 -1
  112. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  113. package/dist/components/mcp/toolRegistry.js.map +1 -1
  114. package/dist/components/mcp/tools/operations.d.ts +5 -0
  115. package/dist/components/mcp/tools/operations.js +9 -0
  116. package/dist/components/mcp/tools/operations.js.map +1 -1
  117. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  118. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  119. package/dist/components/operations.d.ts +28 -0
  120. package/dist/components/operations.js +476 -113
  121. package/dist/components/operations.js.map +1 -1
  122. package/dist/components/operationsValidation.js +97 -3
  123. package/dist/components/operationsValidation.js.map +1 -1
  124. package/dist/components/packageComponent.js +24 -0
  125. package/dist/components/packageComponent.js.map +1 -1
  126. package/dist/components/requestRestart.d.ts +1 -0
  127. package/dist/components/requestRestart.js +7 -0
  128. package/dist/components/requestRestart.js.map +1 -1
  129. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  130. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  131. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  132. package/dist/config/RootConfigWatcher.d.ts +9 -1
  133. package/dist/config/RootConfigWatcher.js +228 -37
  134. package/dist/config/RootConfigWatcher.js.map +1 -1
  135. package/dist/config/configReadRetry.d.ts +8 -0
  136. package/dist/config/configReadRetry.js +62 -0
  137. package/dist/config/configReadRetry.js.map +1 -0
  138. package/dist/config/configUtils.d.ts +11 -2
  139. package/dist/config/configUtils.js +304 -47
  140. package/dist/config/configUtils.js.map +1 -1
  141. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  142. package/dist/config/harperConfigEnvVars.js +162 -25
  143. package/dist/config/harperConfigEnvVars.js.map +1 -1
  144. package/dist/config/parseConfigFile.d.ts +4 -0
  145. package/dist/config/parseConfigFile.js +35 -0
  146. package/dist/config/parseConfigFile.js.map +1 -0
  147. package/dist/config/readConfigFileSync.d.ts +1 -0
  148. package/dist/config/readConfigFileSync.js +47 -0
  149. package/dist/config/readConfigFileSync.js.map +1 -0
  150. package/dist/config/watcherArming.d.ts +15 -0
  151. package/dist/config/watcherArming.js +59 -0
  152. package/dist/config/watcherArming.js.map +1 -0
  153. package/dist/dataLayer/blobBackup.d.ts +49 -20
  154. package/dist/dataLayer/blobBackup.js +139 -50
  155. package/dist/dataLayer/blobBackup.js.map +1 -1
  156. package/dist/dataLayer/delete.js +1 -1
  157. package/dist/dataLayer/delete.js.map +1 -1
  158. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  159. package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
  160. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  161. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  162. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  163. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  164. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  165. package/dist/dataLayer/hdbInfoController.js +30 -1
  166. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  167. package/dist/dataLayer/insert.d.ts +9 -1
  168. package/dist/dataLayer/insert.js +30 -0
  169. package/dist/dataLayer/insert.js.map +1 -1
  170. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  171. package/dist/dataLayer/rocksdbBackup.js +45 -8
  172. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  173. package/dist/dataLayer/schema.js +8 -0
  174. package/dist/dataLayer/schema.js.map +1 -1
  175. package/dist/dataLayer/schemaDescribe.js +8 -1
  176. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  177. package/dist/index.d.ts +2 -1
  178. package/dist/index.js +4 -1
  179. package/dist/index.js.map +1 -1
  180. package/dist/json/systemSchema.json +55 -0
  181. package/dist/resources/DatabaseTransaction.d.ts +77 -0
  182. package/dist/resources/DatabaseTransaction.js +586 -54
  183. package/dist/resources/DatabaseTransaction.js.map +1 -1
  184. package/dist/resources/LMDBTransaction.d.ts +2 -1
  185. package/dist/resources/LMDBTransaction.js +43 -6
  186. package/dist/resources/LMDBTransaction.js.map +1 -1
  187. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  188. package/dist/resources/PrimaryRocksDatabase.js +49 -5
  189. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  190. package/dist/resources/RecordEncoder.d.ts +20 -0
  191. package/dist/resources/RecordEncoder.js +110 -9
  192. package/dist/resources/RecordEncoder.js.map +1 -1
  193. package/dist/resources/RequestTarget.d.ts +2 -0
  194. package/dist/resources/RequestTarget.js.map +1 -1
  195. package/dist/resources/Resource.js +117 -24
  196. package/dist/resources/Resource.js.map +1 -1
  197. package/dist/resources/ResourceInterface.d.ts +28 -1
  198. package/dist/resources/ResourceInterface.js.map +1 -1
  199. package/dist/resources/RocksIndexStore.d.ts +6 -1
  200. package/dist/resources/RocksIndexStore.js +24 -9
  201. package/dist/resources/RocksIndexStore.js.map +1 -1
  202. package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
  203. package/dist/resources/RocksTransactionLogStore.js +160 -49
  204. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  205. package/dist/resources/Table.d.ts +165 -10
  206. package/dist/resources/Table.js +1805 -303
  207. package/dist/resources/Table.js.map +1 -1
  208. package/dist/resources/analytics/write.js +10 -3
  209. package/dist/resources/analytics/write.js.map +1 -1
  210. package/dist/resources/auditStore.d.ts +181 -2
  211. package/dist/resources/auditStore.js +640 -29
  212. package/dist/resources/auditStore.js.map +1 -1
  213. package/dist/resources/blob.d.ts +129 -9
  214. package/dist/resources/blob.js +992 -114
  215. package/dist/resources/blob.js.map +1 -1
  216. package/dist/resources/branchDatabase.d.ts +48 -0
  217. package/dist/resources/branchDatabase.js +892 -0
  218. package/dist/resources/branchDatabase.js.map +1 -0
  219. package/dist/resources/crdt.js +50 -12
  220. package/dist/resources/crdt.js.map +1 -1
  221. package/dist/resources/dataLoader.js +3 -4
  222. package/dist/resources/dataLoader.js.map +1 -1
  223. package/dist/resources/databases.d.ts +157 -14
  224. package/dist/resources/databases.js +1462 -325
  225. package/dist/resources/databases.js.map +1 -1
  226. package/dist/resources/defineTable.d.ts +10 -2
  227. package/dist/resources/defineTable.js +9 -1
  228. package/dist/resources/defineTable.js.map +1 -1
  229. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  230. package/dist/resources/derivedIndexRegistry.js +68 -0
  231. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  232. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  233. package/dist/resources/derivedIndexRuntime.js +2027 -0
  234. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  235. package/dist/resources/graphql.d.ts +1 -1
  236. package/dist/resources/graphql.js +52 -16
  237. package/dist/resources/graphql.js.map +1 -1
  238. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
  239. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
  240. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  241. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  242. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  243. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  244. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  245. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  246. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  247. package/dist/resources/longLivedTransactions.d.ts +71 -0
  248. package/dist/resources/longLivedTransactions.js +358 -0
  249. package/dist/resources/longLivedTransactions.js.map +1 -0
  250. package/dist/resources/models/backendRegistry.d.ts +26 -0
  251. package/dist/resources/models/backendRegistry.js +60 -2
  252. package/dist/resources/models/backendRegistry.js.map +1 -1
  253. package/dist/resources/models/bootstrap.d.ts +33 -1
  254. package/dist/resources/models/bootstrap.js +416 -31
  255. package/dist/resources/models/bootstrap.js.map +1 -1
  256. package/dist/resources/nodeIdMapping.d.ts +5 -0
  257. package/dist/resources/nodeIdMapping.js +49 -0
  258. package/dist/resources/nodeIdMapping.js.map +1 -1
  259. package/dist/resources/recordLock.d.ts +123 -0
  260. package/dist/resources/recordLock.js +315 -0
  261. package/dist/resources/recordLock.js.map +1 -0
  262. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  263. package/dist/resources/recordLockCoordinator.js +2565 -0
  264. package/dist/resources/recordLockCoordinator.js.map +1 -0
  265. package/dist/resources/replayLogs.d.ts +14 -1
  266. package/dist/resources/replayLogs.js +157 -24
  267. package/dist/resources/replayLogs.js.map +1 -1
  268. package/dist/resources/replayLogsGuards.d.ts +94 -7
  269. package/dist/resources/replayLogsGuards.js +111 -7
  270. package/dist/resources/replayLogsGuards.js.map +1 -1
  271. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  272. package/dist/resources/replicatedApplyFailure.js +63 -0
  273. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  274. package/dist/resources/scheduler/scheduler.js +3 -3
  275. package/dist/resources/scheduler/scheduler.js.map +1 -1
  276. package/dist/resources/search.d.ts +10 -4
  277. package/dist/resources/search.js +283 -37
  278. package/dist/resources/search.js.map +1 -1
  279. package/dist/resources/tracked.d.ts +5 -1
  280. package/dist/resources/tracked.js +74 -23
  281. package/dist/resources/tracked.js.map +1 -1
  282. package/dist/resources/transactionBroadcast.d.ts +1 -1
  283. package/dist/resources/transactionBroadcast.js +2 -2
  284. package/dist/resources/transactionBroadcast.js.map +1 -1
  285. package/dist/security/auth.js +61 -30
  286. package/dist/security/auth.js.map +1 -1
  287. package/dist/security/authn/oidc/claims.d.ts +22 -0
  288. package/dist/security/authn/oidc/claims.js +71 -0
  289. package/dist/security/authn/oidc/claims.js.map +1 -0
  290. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  291. package/dist/security/authn/oidc/identityToken.js +111 -0
  292. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  293. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  294. package/dist/security/authn/oidc/jwks.js +261 -0
  295. package/dist/security/authn/oidc/jwks.js.map +1 -0
  296. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  297. package/dist/security/authn/oidc/providers/generic.js +34 -0
  298. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  299. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  300. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  301. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  302. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  303. package/dist/security/authn/oidc/providers/index.js +24 -0
  304. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  305. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  306. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  307. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  308. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  309. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  310. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  311. package/dist/security/authn/oidc/types.d.ts +38 -0
  312. package/dist/security/authn/oidc/types.js +6 -0
  313. package/dist/security/authn/oidc/types.js.map +1 -0
  314. package/dist/security/certificateVerification/index.js +40 -11
  315. package/dist/security/certificateVerification/index.js.map +1 -1
  316. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  317. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  318. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  319. package/dist/security/certificateVerification/types.d.ts +1 -0
  320. package/dist/security/credentialProvenance.d.ts +35 -0
  321. package/dist/security/credentialProvenance.js +51 -0
  322. package/dist/security/credentialProvenance.js.map +1 -0
  323. package/dist/security/credentialRejection.d.ts +4 -0
  324. package/dist/security/credentialRejection.js +24 -0
  325. package/dist/security/credentialRejection.js.map +1 -0
  326. package/dist/security/deferredAuthentication.d.ts +36 -0
  327. package/dist/security/deferredAuthentication.js +70 -0
  328. package/dist/security/deferredAuthentication.js.map +1 -0
  329. package/dist/security/impersonation.d.ts +21 -0
  330. package/dist/security/impersonation.js +108 -9
  331. package/dist/security/impersonation.js.map +1 -1
  332. package/dist/security/jsLoader.d.ts +6 -0
  333. package/dist/security/jsLoader.js +77 -15
  334. package/dist/security/jsLoader.js.map +1 -1
  335. package/dist/security/keys.js +301 -71
  336. package/dist/security/keys.js.map +1 -1
  337. package/dist/security/operationScope.d.ts +21 -0
  338. package/dist/security/operationScope.js +36 -0
  339. package/dist/security/operationScope.js.map +1 -0
  340. package/dist/security/permissionsTranslator.js +21 -0
  341. package/dist/security/permissionsTranslator.js.map +1 -1
  342. package/dist/security/tokenAuthentication.d.ts +19 -1
  343. package/dist/security/tokenAuthentication.js +191 -10
  344. package/dist/security/tokenAuthentication.js.map +1 -1
  345. package/dist/security/user.js +4 -3
  346. package/dist/security/user.js.map +1 -1
  347. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  348. package/dist/server/DurableSubscriptionsSession.js +67 -10
  349. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  350. package/dist/server/REST.js +106 -2
  351. package/dist/server/REST.js.map +1 -1
  352. package/dist/server/graphqlQuerying.js +4 -0
  353. package/dist/server/graphqlQuerying.js.map +1 -1
  354. package/dist/server/http.d.ts +16 -1
  355. package/dist/server/http.js +122 -17
  356. package/dist/server/http.js.map +1 -1
  357. package/dist/server/itc/serverHandlers.js +8 -1
  358. package/dist/server/itc/serverHandlers.js.map +1 -1
  359. package/dist/server/jobs/jobProcess.js +6 -2
  360. package/dist/server/jobs/jobProcess.js.map +1 -1
  361. package/dist/server/jobs/jobs.js +4 -1
  362. package/dist/server/jobs/jobs.js.map +1 -1
  363. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  364. package/dist/server/liveSubscriptionAuth.js +105 -39
  365. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  366. package/dist/server/loadRootComponents.js +49 -10
  367. package/dist/server/loadRootComponents.js.map +1 -1
  368. package/dist/server/mqtt.d.ts +2 -0
  369. package/dist/server/mqtt.js +165 -30
  370. package/dist/server/mqtt.js.map +1 -1
  371. package/dist/server/nodeName.d.ts +2 -0
  372. package/dist/server/nodeName.js +107 -23
  373. package/dist/server/nodeName.js.map +1 -1
  374. package/dist/server/serverHelpers/Headers.d.ts +27 -0
  375. package/dist/server/serverHelpers/Headers.js +145 -1
  376. package/dist/server/serverHelpers/Headers.js.map +1 -1
  377. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  378. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  379. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  380. package/dist/server/serverHelpers/Request.d.ts +5 -10
  381. package/dist/server/serverHelpers/Request.js +38 -136
  382. package/dist/server/serverHelpers/Request.js.map +1 -1
  383. package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
  384. package/dist/server/serverHelpers/contentTypes.js +221 -40
  385. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  386. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  387. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  388. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  389. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  390. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  391. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  392. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  393. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  394. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  395. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  396. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  397. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  398. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  399. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  400. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  401. package/dist/server/serverHelpers/serverUtilities.js +178 -29
  402. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  403. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  404. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  405. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  406. package/dist/server/serverHelpers/uwsServer.js +19 -1
  407. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  408. package/dist/server/static.js +24 -28
  409. package/dist/server/static.js.map +1 -1
  410. package/dist/server/storageReclamation.d.ts +5 -0
  411. package/dist/server/storageReclamation.js +17 -1
  412. package/dist/server/storageReclamation.js.map +1 -1
  413. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  414. package/dist/server/threads/isolatedApplications.js +171 -0
  415. package/dist/server/threads/isolatedApplications.js.map +1 -0
  416. package/dist/server/threads/itc.d.ts +7 -2
  417. package/dist/server/threads/itc.js +5 -1
  418. package/dist/server/threads/itc.js.map +1 -1
  419. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  420. package/dist/server/threads/logRotationTransport.js +33 -0
  421. package/dist/server/threads/logRotationTransport.js.map +1 -0
  422. package/dist/server/threads/manageThreads.d.ts +83 -3
  423. package/dist/server/threads/manageThreads.js +769 -75
  424. package/dist/server/threads/manageThreads.js.map +1 -1
  425. package/dist/server/threads/socketRouter.d.ts +1 -0
  426. package/dist/server/threads/socketRouter.js +277 -31
  427. package/dist/server/threads/socketRouter.js.map +1 -1
  428. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  429. package/dist/server/threads/threadHeapMemory.js +31 -0
  430. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  431. package/dist/server/threads/threadServer.js +76 -22
  432. package/dist/server/threads/threadServer.js.map +1 -1
  433. package/dist/sqlEngine/config.d.ts +1 -3
  434. package/dist/sqlEngine/config.js +19 -16
  435. package/dist/sqlEngine/config.js.map +1 -1
  436. package/dist/sqlTranslator/index.d.ts +1 -1
  437. package/dist/sqlTranslator/index.js +30 -7
  438. package/dist/sqlTranslator/index.js.map +1 -1
  439. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  440. package/dist/upgrade/directives/5-3-0.js +148 -0
  441. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  442. package/dist/upgrade/directives/directivesController.js +2 -1
  443. package/dist/upgrade/directives/directivesController.js.map +1 -1
  444. package/dist/utility/OperationFunctionCaller.js +2 -1
  445. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  446. package/dist/utility/common_utils.d.ts +16 -0
  447. package/dist/utility/common_utils.js +32 -6
  448. package/dist/utility/common_utils.js.map +1 -1
  449. package/dist/utility/componentNames.d.ts +8 -0
  450. package/dist/utility/componentNames.js +12 -1
  451. package/dist/utility/componentNames.js.map +1 -1
  452. package/dist/utility/environment/environmentManager.js +3 -6
  453. package/dist/utility/environment/environmentManager.js.map +1 -1
  454. package/dist/utility/environment/systemInformation.d.ts +1 -0
  455. package/dist/utility/environment/systemInformation.js +1 -0
  456. package/dist/utility/environment/systemInformation.js.map +1 -1
  457. package/dist/utility/errors/commonErrors.d.ts +2 -0
  458. package/dist/utility/errors/commonErrors.js +2 -0
  459. package/dist/utility/errors/commonErrors.js.map +1 -1
  460. package/dist/utility/errors/hdbError.d.ts +33 -0
  461. package/dist/utility/errors/hdbError.js +58 -1
  462. package/dist/utility/errors/hdbError.js.map +1 -1
  463. package/dist/utility/globalSchema.d.ts +18 -0
  464. package/dist/utility/hdbTerms.d.ts +18 -0
  465. package/dist/utility/hdbTerms.js +20 -2
  466. package/dist/utility/hdbTerms.js.map +1 -1
  467. package/dist/utility/logging/harper_logger.d.ts +2 -0
  468. package/dist/utility/logging/harper_logger.js +286 -30
  469. package/dist/utility/logging/harper_logger.js.map +1 -1
  470. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  471. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  472. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  473. package/dist/utility/logging/logRotation.d.ts +46 -0
  474. package/dist/utility/logging/logRotation.js +365 -0
  475. package/dist/utility/logging/logRotation.js.map +1 -0
  476. package/dist/utility/logging/logRotator.d.ts +1 -1
  477. package/dist/utility/logging/logRotator.js +192 -85
  478. package/dist/utility/logging/logRotator.js.map +1 -1
  479. package/dist/utility/nodeIdentity.d.ts +9 -0
  480. package/dist/utility/nodeIdentity.js +58 -0
  481. package/dist/utility/nodeIdentity.js.map +1 -0
  482. package/dist/utility/npmUtilities.js +11 -7
  483. package/dist/utility/npmUtilities.js.map +1 -1
  484. package/dist/utility/operationPermissions.d.ts +3 -1
  485. package/dist/utility/operationPermissions.js +16 -1
  486. package/dist/utility/operationPermissions.js.map +1 -1
  487. package/dist/utility/operation_authorization.d.ts +10 -7
  488. package/dist/utility/operation_authorization.js +212 -41
  489. package/dist/utility/operation_authorization.js.map +1 -1
  490. package/dist/utility/watchPath.d.ts +29 -0
  491. package/dist/utility/watchPath.js +68 -0
  492. package/dist/utility/watchPath.js.map +1 -0
  493. package/dist/utility/watcherFallback.d.ts +41 -0
  494. package/dist/utility/watcherFallback.js +153 -0
  495. package/dist/utility/watcherFallback.js.map +1 -1
  496. package/dist/validation/configValidator.d.ts +12 -0
  497. package/dist/validation/configValidator.js +205 -75
  498. package/dist/validation/configValidator.js.map +1 -1
  499. package/dist/validation/installValidator.js +12 -0
  500. package/dist/validation/installValidator.js.map +1 -1
  501. package/dist/validation/validationWrapper.d.ts +11 -0
  502. package/dist/validation/validationWrapper.js +16 -3
  503. package/dist/validation/validationWrapper.js.map +1 -1
  504. package/index.ts +8 -0
  505. package/json/systemSchema.json +55 -0
  506. package/npm-shrinkwrap.json +286 -194
  507. package/package.json +15 -7
  508. package/resources/DESIGN.md +219 -52
  509. package/resources/DatabaseTransaction.ts +657 -52
  510. package/resources/LMDBTransaction.ts +46 -6
  511. package/resources/PrimaryRocksDatabase.ts +46 -7
  512. package/resources/RecordEncoder.ts +124 -9
  513. package/resources/RequestTarget.ts +2 -0
  514. package/resources/Resource.ts +114 -22
  515. package/resources/ResourceInterface.ts +31 -0
  516. package/resources/RocksIndexStore.ts +30 -9
  517. package/resources/RocksTransactionLogStore.ts +188 -51
  518. package/resources/Table.ts +1960 -301
  519. package/resources/analytics/write.ts +10 -3
  520. package/resources/auditStore.ts +647 -32
  521. package/resources/blob.ts +1029 -110
  522. package/resources/branchDatabase.ts +941 -0
  523. package/resources/crdt.ts +76 -12
  524. package/resources/dataLoader.ts +3 -4
  525. package/resources/databases.ts +1584 -320
  526. package/resources/defineTable.ts +18 -2
  527. package/resources/derivedIndexRegistry.ts +56 -0
  528. package/resources/derivedIndexRuntime.ts +2292 -0
  529. package/resources/graphql.ts +72 -17
  530. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
  531. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  532. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  533. package/resources/longLivedTransactions.ts +360 -0
  534. package/resources/models/backendRegistry.ts +84 -2
  535. package/resources/models/bootstrap.ts +473 -28
  536. package/resources/nodeIdMapping.ts +50 -0
  537. package/resources/recordLock.ts +419 -0
  538. package/resources/recordLockCoordinator.ts +3043 -0
  539. package/resources/replayLogs.ts +152 -26
  540. package/resources/replayLogsGuards.ts +171 -8
  541. package/resources/replicatedApplyFailure.ts +77 -0
  542. package/resources/scheduler/scheduler.ts +4 -4
  543. package/resources/search.ts +288 -46
  544. package/resources/tracked.ts +73 -22
  545. package/resources/transactionBroadcast.ts +3 -3
  546. package/security/auth.ts +68 -29
  547. package/security/authn/oidc/claims.ts +72 -0
  548. package/security/authn/oidc/identityToken.ts +129 -0
  549. package/security/authn/oidc/jwks.ts +260 -0
  550. package/security/authn/oidc/providers/generic.ts +40 -0
  551. package/security/authn/oidc/providers/githubActions.ts +137 -0
  552. package/security/authn/oidc/providers/index.ts +52 -0
  553. package/security/authn/oidc/tokenExchange.ts +300 -0
  554. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  555. package/security/authn/oidc/types.ts +41 -0
  556. package/security/certificateVerification/index.ts +54 -13
  557. package/security/certificateVerification/trustedIssuers.ts +76 -0
  558. package/security/certificateVerification/types.ts +1 -0
  559. package/security/credentialProvenance.ts +47 -0
  560. package/security/credentialRejection.ts +22 -0
  561. package/security/deferredAuthentication.ts +71 -0
  562. package/security/impersonation.ts +117 -12
  563. package/security/jsLoader.ts +83 -18
  564. package/security/keys.ts +298 -72
  565. package/security/operationScope.ts +33 -0
  566. package/security/permissionsTranslator.js +23 -0
  567. package/security/tokenAuthentication.ts +233 -12
  568. package/security/user.ts +4 -3
  569. package/server/DESIGN.md +194 -16
  570. package/server/DurableSubscriptionsSession.ts +71 -11
  571. package/server/REST.ts +115 -4
  572. package/server/graphqlQuerying.ts +4 -0
  573. package/server/http.ts +133 -20
  574. package/server/itc/serverHandlers.js +8 -1
  575. package/server/jobs/jobProcess.ts +8 -2
  576. package/server/jobs/jobs.ts +4 -1
  577. package/server/liveSubscriptionAuth.ts +129 -46
  578. package/server/loadRootComponents.js +50 -8
  579. package/server/mqtt.ts +179 -38
  580. package/server/nodeName.ts +103 -21
  581. package/server/serverHelpers/Headers.ts +135 -0
  582. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  583. package/server/serverHelpers/Request.ts +33 -131
  584. package/server/serverHelpers/contentTypes.ts +217 -36
  585. package/server/serverHelpers/deployValidationState.ts +24 -13
  586. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  587. package/server/serverHelpers/registeredOperations.ts +79 -22
  588. package/server/serverHelpers/requestSanitization.ts +15 -0
  589. package/server/serverHelpers/serverHandlers.js +6 -3
  590. package/server/serverHelpers/serverUtilities.ts +232 -40
  591. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  592. package/server/serverHelpers/uwsServer.ts +17 -2
  593. package/server/static.ts +23 -29
  594. package/server/storageReclamation.ts +15 -2
  595. package/server/threads/isolatedApplications.ts +157 -0
  596. package/server/threads/itc.js +11 -1
  597. package/server/threads/logRotationTransport.ts +40 -0
  598. package/server/threads/manageThreads.js +771 -66
  599. package/server/threads/socketRouter.ts +291 -30
  600. package/server/threads/threadHeapMemory.ts +26 -0
  601. package/server/threads/threadServer.js +73 -22
  602. package/sqlTranslator/index.ts +31 -8
  603. package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
  604. package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
  605. package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
  606. package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
  607. package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
  608. package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
  609. package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
  610. package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
  611. package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
  612. package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
  613. package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
  614. package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
  615. package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
  616. package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  617. package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
  618. package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
  619. package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
  620. package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
  621. package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
  622. package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
  623. package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
  624. package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
  625. package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
  626. package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
  627. package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
  628. package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  629. package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
  630. package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
  631. package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
  632. package/studio/web/index.html +1 -1
  633. package/upgrade/directives/5-3-0.ts +132 -0
  634. package/upgrade/directives/directivesController.ts +2 -1
  635. package/utility/OperationFunctionCaller.ts +2 -1
  636. package/utility/common_utils.ts +30 -5
  637. package/utility/componentNames.ts +12 -0
  638. package/utility/environment/environmentManager.ts +3 -7
  639. package/utility/environment/systemInformation.ts +7 -0
  640. package/utility/errors/commonErrors.ts +4 -0
  641. package/utility/errors/hdbError.ts +57 -0
  642. package/utility/hdbTerms.ts +19 -0
  643. package/utility/logging/harper_logger.ts +278 -26
  644. package/utility/logging/logGenerationCoordinator.ts +196 -0
  645. package/utility/logging/logRotation.ts +367 -0
  646. package/utility/logging/logRotator.ts +213 -81
  647. package/utility/nodeIdentity.ts +45 -0
  648. package/utility/npmUtilities.ts +12 -8
  649. package/utility/operationPermissions.ts +18 -1
  650. package/utility/operation_authorization.ts +231 -42
  651. package/utility/watchPath.ts +63 -0
  652. package/utility/watcherFallback.ts +148 -0
  653. package/validation/configValidator.ts +215 -75
  654. package/validation/installValidator.ts +15 -0
  655. package/validation/validationWrapper.ts +18 -4
@@ -3,6 +3,7 @@ import type { RequestParams } from 'graphql-http';
3
3
  import { getDeserializer } from './serverHelpers/contentTypes.ts';
4
4
  import { resources } from '../resources/Resources.ts';
5
5
  import logger from '../utility/logging/harper_logger.ts';
6
+ import { settleDeferredCredentialRejection } from '../security/deferredAuthentication.ts';
6
7
 
7
8
  // This code makes heavy use of the word "node" to refer to a node in the GraphQL AST.
8
9
 
@@ -580,6 +581,9 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
580
581
  return nextLayer(request);
581
582
  }
582
583
 
584
+ const settledCredentialRejection = settleDeferredCredentialRejection(request);
585
+ if (settledCredentialRejection) return settledCredentialRejection;
586
+
583
587
  try {
584
588
  // Await the `graphqlHandler` call here so that errors are caught.
585
589
  return await graphqlQueryingHandler(request as any);
package/server/http.ts CHANGED
@@ -12,13 +12,24 @@ import * as env from '../utility/environment/environmentManager.ts';
12
12
  import * as terms from '../utility/hdbTerms.ts';
13
13
  import { getConfigPath } from '../config/configUtils.ts';
14
14
  import { getTicketKeys, getWorkerIndex } from './threads/manageThreads.js';
15
+ import {
16
+ applicationSocketName,
17
+ isolatedApplicationRoute,
18
+ thisThreadsIsolatedApplication,
19
+ } from './threads/isolatedApplications.ts';
15
20
  import { createTLSSelector, getEffectiveTlsCiphers } from '../security/keys.ts';
16
21
  import { createSecureServer, createServer as createH2CServer } from 'node:http2';
17
22
  import { createServer as createSecureServerHttp1 } from 'node:https';
18
23
  import { createServer, IncomingMessage, validateHeaderName, validateHeaderValue } from 'node:http';
19
24
  import { createServer as createNetServer } from 'node:net';
20
25
  import { Request, BunRequest, UwsRequest, isBun } from './serverHelpers/Request.ts';
21
- import { appendHeader, Headers, toWriteHeadHeaders } from './serverHelpers/Headers.ts';
26
+ import {
27
+ appendHeader,
28
+ bridgeChainHeadersToNodeResponse,
29
+ Headers,
30
+ mergeChainHeadersIntoFallback,
31
+ toWriteHeadHeaders,
32
+ } from './serverHelpers/Headers.ts';
22
33
  import {
23
34
  decodeProxyHeader,
24
35
  applyProxyHeader,
@@ -34,7 +45,7 @@ import { server, type ServerOptions, type HttpOptions, type UpgradeOptions, Upgr
34
45
  import { setPortServerMap, SERVERS, socketOptionDefaults } from './serverRegistry.ts';
35
46
  import { getComponentName } from '../components/componentLoader.ts';
36
47
  import { throttle } from './throttle.ts';
37
- import { makeCallbackChain as buildCallbackChain, describeChains } from './middlewareChain.ts';
48
+ import { makeCallbackChain as buildCallbackChain, describeChains, type HttpEntry } from './middlewareChain.ts';
38
49
  import { WebSocketServer } from 'ws';
39
50
 
40
51
  const { errorToString, errorForLog } = harperLogger;
@@ -190,10 +201,21 @@ export function writeUdsMetadata(
190
201
  port: number | string,
191
202
  secureServer: any,
192
203
  protocol?: string,
193
- mtlsForwarding = true
204
+ mtlsForwarding = true,
205
+ route: { application: string; hosts: string[] } | undefined = isolatedApplicationRoute()
194
206
  ) {
195
207
  const contexts = secureServer.secureContexts;
196
208
  let yaml = `pid: ${process.pid}\ntid: ${currentThreadId()}\nport: ${port}\n`;
209
+ // Routing identity, separate from certificate coverage: a proxy sends this application's hosts to
210
+ // this socket alone, whatever hostnames the (shared, possibly wildcard) certificates carry.
211
+ if (route) {
212
+ yaml += `application: ${JSON.stringify(route.application)}\n`;
213
+ if (route.hosts.length === 0) yaml += `applicationHosts: []\n`;
214
+ else {
215
+ yaml += `applicationHosts:\n`;
216
+ for (const host of route.hosts) yaml += ` - ${JSON.stringify(host)}\n`;
217
+ }
218
+ }
197
219
  // Which application protocol this socket speaks (absent = http/1.1, the historical
198
220
  // default) — lets a fronting proxy route by negotiated ALPN.
199
221
  if (protocol) yaml += `protocol: ${protocol}\n`;
@@ -261,6 +283,19 @@ export function cleanupSocketsDirectory() {
261
283
  } catch {}
262
284
  }
263
285
 
286
+ export function cleanupApplicationSockets(application: string) {
287
+ const socketsDir = join(env.getHdbBasePath(), 'sockets');
288
+ const prefix = applicationSocketName(application, '');
289
+ try {
290
+ for (const file of readdirSync(socketsDir)) {
291
+ if (!file.startsWith(prefix) || (!file.endsWith('.sock') && !file.endsWith('.yaml'))) continue;
292
+ try {
293
+ unlinkSync(join(socketsDir, file));
294
+ } catch {}
295
+ }
296
+ } catch {}
297
+ }
298
+
264
299
  // Entries in `universalHeaders` that were pushed by `applySecurityHeaders`, so a config
265
300
  // hot-reload can remove exactly the entries it owns without clobbering entries pushed by
266
301
  // other components.
@@ -490,7 +525,7 @@ export function httpServer(listener, options) {
490
525
  httpResponders[options?.runFirst ? 'unshift' : 'push'](entry);
491
526
  } else if (isBun) {
492
527
  // On Bun, store non-function listeners (e.g. Fastify's http.Server) for fallback delegation
493
- fallbackServers[port] = listener;
528
+ registerFallbackServer(port, listener);
494
529
  } else if ((httpServers[port] as any)?.uws) {
495
530
  // uWS HTTP path (#914, HARPER_UWS_HTTP): the port is backed by uWebSockets.js, not a Node
496
531
  // http server, so a raw non-function listener (e.g. Fastify's http.Server via
@@ -499,12 +534,12 @@ export function httpServer(listener, options) {
499
534
  // port. Divert it to the fallback map like the Bun path; makeUwsHandler delegates unhandled
500
535
  // requests to it via inject(). The { uws: true } marker is guaranteed present here: the
501
536
  // getServer(port) call above (same loop iteration) sets it before this branch runs.
502
- fallbackServers[port] = listener;
537
+ registerFallbackServer(port, listener);
503
538
  } else {
504
539
  listener.isSecure = secure;
505
540
  registerServer(listener, port, false);
506
541
  }
507
- httpChain[port] = makeCallbackChain(httpResponders, port);
542
+ buildChains(httpChain, httpResponders, port);
508
543
  }
509
544
 
510
545
  return servers;
@@ -543,10 +578,47 @@ export function pipeBodyToResponse(
543
578
  } else {
544
579
  recordAction(performance.now() - endTime, 'transfer', handlerPath, method);
545
580
  recordAction(bytesSent, 'bytes-sent', handlerPath, method);
581
+ endConnectionIfClientExpectsClose(nodeResponse);
546
582
  }
547
583
  });
548
584
  }
549
585
 
586
+ /**
587
+ * Bun's node:http never derives keep-alive from the request, so a stream-ended response delivers its
588
+ * full body and then leaves the client attached until its own timeout (#2210). Ending the *request's*
589
+ * socket is the only remedy that works there, and it is graceful, so nothing is truncated (measured
590
+ * on plain TCP and TLS). DESIGN.md carries the measurements and the alternatives that don't work.
591
+ */
592
+ function endConnectionIfClientExpectsClose(nodeResponse: any) {
593
+ if (!isBun) return;
594
+ const nodeRequest = nodeResponse.req;
595
+ if (nodeRequest?.httpVersionMajor !== 1) return;
596
+ const socket = nodeRequest.socket;
597
+ // a peer that RSTs right after the terminal chunk can leave this callback holding a destroyed
598
+ // socket; end()ing it would throw from inside stream internals, i.e. an uncaughtException
599
+ if (!socket || socket.destroyed) return;
600
+ const connection = nodeRequest.headers?.connection;
601
+ const tokens = typeof connection === 'string' ? connection.split(',') : [];
602
+ if (hasConnectionToken(tokens, 'close')) {
603
+ socket.end();
604
+ return;
605
+ }
606
+ // HTTP/1.0 persistence needs an explicit keep-alive AND a length to read to, so a 1.0 response
607
+ // that got no Content-Length is delimited by the close — the same line Node draws
608
+ if (
609
+ nodeRequest.httpVersionMinor === 0 &&
610
+ !(hasConnectionToken(tokens, 'keep-alive') && nodeResponse.hasHeader?.('content-length'))
611
+ )
612
+ socket.end();
613
+ }
614
+
615
+ function hasConnectionToken(tokens: string[], wanted: string) {
616
+ for (const token of tokens) {
617
+ if (token.trim().toLowerCase() === wanted) return true;
618
+ }
619
+ return false;
620
+ }
621
+
550
622
  function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
551
623
  const { mtls: isMtls, usageType } = options || {};
552
624
  const isOperationsServer = usageType === 'operations-api';
@@ -642,9 +714,7 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
642
714
  // This means the HDB stack didn't handle the request, and we can then cascade the request
643
715
  // to the server-level handler, forming the bridge to the slower legacy fastify framework that expects
644
716
  // to interact with a node HTTP server object.
645
- for (const headerPair of response.headers || []) {
646
- nodeResponse.setHeader(headerPair[0], headerPair[1]);
647
- }
717
+ bridgeChainHeadersToNodeResponse(response.headers, nodeResponse);
648
718
  nodeRequest.baseRequest = request;
649
719
  nodeResponse.baseResponse = response;
650
720
  return httpServers[port].emit('unhandled', nodeRequest, nodeResponse);
@@ -834,7 +904,10 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
834
904
  if (secure && env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) {
835
905
  const socketsDir = join(env.getHdbBasePath(), 'sockets');
836
906
  mkdirSync(socketsDir, { recursive: true });
837
- const socketName = `${getWorkerIndex()}-${port}`;
907
+ const isolatedApplication = thisThreadsIsolatedApplication();
908
+ const socketName = isolatedApplication
909
+ ? applicationSocketName(isolatedApplication, port)
910
+ : `${getWorkerIndex()}-${port}`;
838
911
  const udsPath = join(socketsDir, `${socketName}.sock`);
839
912
  const yamlPath = join(socketsDir, `${socketName}.yaml`);
840
913
 
@@ -947,7 +1020,7 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
947
1020
  * and a Fastify fallback is registered for the port, it delegates via inject() (see injectToFastify),
948
1021
  * mirroring the Bun path — so legacy Fastify routes work behind uWS too.
949
1022
  */
950
- function makeUwsHandler(port: number | string, isOperationsServer: boolean, requestQueueLimit?: number) {
1023
+ export function makeUwsHandler(port: number | string, isOperationsServer: boolean, requestQueueLimit?: number) {
951
1024
  // Build a fresh response descriptor rather than mutating what the chain returned: a handler may
952
1025
  // return a WHATWG `Response` (read-only `status`/`body` accessors), which the Bun path also never
953
1026
  // mutates. `headers` is normalized in place the same way the Bun path does.
@@ -983,6 +1056,7 @@ function makeUwsHandler(port: number | string, isOperationsServer: boolean, requ
983
1056
  if (Array.isArray(v)) respHeaders.set(k, k.toLowerCase() === 'set-cookie' ? v : v.join(', '));
984
1057
  else respHeaders.set(k, String(v));
985
1058
  }
1059
+ mergeChainHeadersIntoFallback(headers, respHeaders);
986
1060
  if (universalHeaders.length > 0) applyUniversalHeaders(respHeaders);
987
1061
  logHttpRequest(request, injectResult.statusCode, requestId, performance.now() - startTime);
988
1062
  const responseStream = injectResult.stream();
@@ -1006,6 +1080,7 @@ function makeUwsHandler(port: number | string, isOperationsServer: boolean, requ
1006
1080
  }
1007
1081
  logHttpRequest(request, 404, requestId, performance.now() - startTime);
1008
1082
  const notFoundHeaders = new Headers({ 'content-type': 'text/plain' });
1083
+ mergeChainHeadersIntoFallback(headers, notFoundHeaders);
1009
1084
  if (universalHeaders.length > 0) applyUniversalHeaders(notFoundHeaders);
1010
1085
  return { status: 404, headers: notFoundHeaders, body: 'Not found\n' };
1011
1086
  }
@@ -1180,10 +1255,11 @@ function getBunHTTPServer(port: number, secure: boolean, options: ServerOptions)
1180
1255
  // Delegate to the fallback server (e.g. Fastify) via node:http compatibility.
1181
1256
  // We create a Node-compatible IncomingMessage/ServerResponse and emit 'request'
1182
1257
  // on the fallback server, then capture the response.
1183
- return await bunDelegateToNodeServer(fallbackServer, webRequest, request);
1258
+ return await bunDelegateToNodeServer(fallbackServer, webRequest, request, response.headers);
1184
1259
  }
1185
1260
  logHttpRequest(request, 404, requestId, performance.now() - startTime);
1186
1261
  const notFoundHeaders = new globalThis.Headers();
1262
+ mergeChainHeadersIntoFallback(response.headers, notFoundHeaders);
1187
1263
  if (universalHeaders.length > 0) applyUniversalHeaders(notFoundHeaders);
1188
1264
  return new Response('Not found\n', { status: 404, headers: notFoundHeaders });
1189
1265
  }
@@ -1367,6 +1443,10 @@ let fastifyInstances: Record<string | number, any> = {};
1367
1443
  export function registerFastifyInstance(port: string | number, instance: any) {
1368
1444
  fastifyInstances[port] = instance;
1369
1445
  }
1446
+
1447
+ export function registerFallbackServer(port: string | number, listener: any) {
1448
+ fallbackServers[port] = listener;
1449
+ }
1370
1450
  const INTERNAL_USER_HEADER = 'x-harper-internal-pre-auth-user';
1371
1451
 
1372
1452
  /**
@@ -1397,10 +1477,11 @@ function injectToFastify(
1397
1477
  return fastify.inject({ method: req.method, url: req.url, headers, payload: req.body, payloadAsStream: true });
1398
1478
  }
1399
1479
 
1400
- async function bunDelegateToNodeServer(
1480
+ export async function bunDelegateToNodeServer(
1401
1481
  nodeServer: any,
1402
1482
  webRequest: globalThis.Request,
1403
- bunRequest?: any
1483
+ bunRequest?: any,
1484
+ chainHeaders?: any
1404
1485
  ): Promise<Response> {
1405
1486
  // Check if there's a Fastify instance registered for this port (preferred path)
1406
1487
  for (const port in fallbackServers) {
@@ -1421,13 +1502,19 @@ async function bunDelegateToNodeServer(
1421
1502
  });
1422
1503
  const webHeaders = new globalThis.Headers();
1423
1504
  for (const [k, v] of Object.entries(injectResult.headers)) {
1424
- if (v != null) webHeaders.set(k, Array.isArray(v) ? v.join(', ') : String(v));
1505
+ if (v == null) continue;
1506
+ if (Array.isArray(v)) {
1507
+ if (k.toLowerCase() === 'set-cookie') {
1508
+ for (const single of v) webHeaders.append(k, String(single));
1509
+ } else webHeaders.set(k, v.join(', '));
1510
+ } else webHeaders.set(k, String(v));
1425
1511
  }
1426
1512
  // Propagate Connection: close so Bun closes the TCP connection after this response,
1427
1513
  // preventing stale keep-alive sockets from causing silent hangs on subsequent requests.
1428
1514
  if (webRequest.headers.get('connection')?.toLowerCase() === 'close') {
1429
1515
  webHeaders.set('connection', 'close');
1430
1516
  }
1517
+ mergeChainHeadersIntoFallback(chainHeaders, webHeaders);
1431
1518
  if (universalHeaders.length > 0) applyUniversalHeaders(webHeaders);
1432
1519
  const responseStream = injectResult.stream();
1433
1520
  // Event-stream responses (MCP SSE) must reach the client incrementally — return
@@ -1462,6 +1549,7 @@ async function bunDelegateToNodeServer(
1462
1549
  }
1463
1550
  // No Fastify instance found — return 404
1464
1551
  const notFoundHeaders = new globalThis.Headers();
1552
+ mergeChainHeadersIntoFallback(chainHeaders, notFoundHeaders);
1465
1553
  if (universalHeaders.length > 0) applyUniversalHeaders(notFoundHeaders);
1466
1554
  return new Response('Not found\n', { status: 404, headers: notFoundHeaders });
1467
1555
  }
@@ -1470,14 +1558,39 @@ type SerializedRoute = { host?: string; urlPath?: string; order: string[] };
1470
1558
  // Resolved order captured at chain-build time, keyed identically to httpChain/upgradeChains/
1471
1559
  // websocketChains (kind → port → routes). Reporting the stored build-time order rather than
1472
1560
  // recomputing from current responders guarantees get_status matches the callback chain actually
1473
- // serving that port — including cases where a late `port: 'all'` registration rebuilds only the
1474
- // 'all' chain and leaves a concrete port's chain (and this description) unchanged (#1573).
1561
+ // serving that port: buildChains() writes both in the same pass, so a description can never
1562
+ // describe an order the port isn't running (#1573).
1475
1563
  const resolvedChainDescriptions: Record<string, Record<string, SerializedRoute[]>> = {
1476
1564
  http: {},
1477
1565
  upgrade: {},
1478
1566
  websocket: {},
1479
1567
  };
1480
1568
 
1569
+ // Preserve the original port type because route selection uses strict equality.
1570
+ const builtChainPorts: Record<string, Map<string, number | string>> = {
1571
+ http: new Map(),
1572
+ upgrade: new Map(),
1573
+ websocket: new Map(),
1574
+ };
1575
+
1576
+ // A late registration on 'all' must rebuild every already-bound concrete port (#2418).
1577
+ function buildChains(
1578
+ chains: Record<string, Function>,
1579
+ listeners: HttpEntry[],
1580
+ port: number | string,
1581
+ requestArgIndex: number = 0,
1582
+ kind: string = 'http'
1583
+ ) {
1584
+ builtChainPorts[kind].set(String(port), port);
1585
+ if (port !== 'all') {
1586
+ chains[port] = makeCallbackChain(listeners, port, requestArgIndex, kind);
1587
+ return;
1588
+ }
1589
+ for (const builtPort of builtChainPorts[kind].values()) {
1590
+ chains[builtPort] = makeCallbackChain(listeners, builtPort, requestArgIndex, kind);
1591
+ }
1592
+ }
1593
+
1481
1594
  function makeCallbackChain(
1482
1595
  responders: typeof httpResponders,
1483
1596
  portNum: number | string,
@@ -1603,7 +1716,7 @@ function onUpgrade(listener: UpgradeListener, options: UpgradeOptions) {
1603
1716
  host: options?.host || undefined,
1604
1717
  };
1605
1718
  upgradeListeners[options?.runFirst ? 'unshift' : 'push'](entry);
1606
- upgradeChains[port] = makeCallbackChain(upgradeListeners, port, 0, 'upgrade');
1719
+ buildChains(upgradeChains, upgradeListeners, port, 0, 'upgrade');
1607
1720
  }
1608
1721
  }
1609
1722
 
@@ -1742,10 +1855,10 @@ function onWebSocket(listener: (ws: WebSocket) => void, options: OnWebSocketOpti
1742
1855
  host: options?.host || undefined,
1743
1856
  };
1744
1857
  websocketListeners[options?.runFirst ? 'unshift' : 'push'](wsEntry);
1745
- websocketChains[port] = makeCallbackChain(websocketListeners, port, 1, 'websocket');
1858
+ buildChains(websocketChains, websocketListeners, port, 1, 'websocket');
1746
1859
 
1747
1860
  // mqtt doesn't invoke the http handler so this needs to be here to load up the http chains.
1748
- httpChain[port] = makeCallbackChain(httpResponders, port);
1861
+ buildChains(httpChain, httpResponders, port);
1749
1862
  }
1750
1863
 
1751
1864
  return servers;
@@ -12,7 +12,7 @@ const harperBridge =
12
12
  require('../../dataLayer/harperBridge/harperBridge.ts');
13
13
  const process = require('process');
14
14
  const { isMainThread, threadId, workerData } = require('node:worker_threads');
15
- const { resetDatabases, closeDatabase } = require('../../resources/databases.ts');
15
+ const { resetDatabases, closeDatabase, reloadBranchAt } = require('../../resources/databases.ts');
16
16
 
17
17
  /**
18
18
  * This object/functions are passed to the ITC client instance and dynamically added as event handlers.
@@ -76,6 +76,13 @@ schemaHandler.addListener = function (listener) {
76
76
  */
77
77
  async function syncSchemaMetadata(msg) {
78
78
  try {
79
+ // A change to a scope-private branch is not a change to any database in the global map, so the
80
+ // rescan below has nothing to find; a thread holding that branch open reloads it instead.
81
+ if (msg.branchPath) {
82
+ // No write barrier here: the symbol-keyed put below has never been one (harper#2522).
83
+ reloadBranchAt(msg.branchPath);
84
+ return;
85
+ }
79
86
  // TODO: Eventually should indicate which database/table changed so we don't have to scan everything
80
87
  let databases = resetDatabases();
81
88
  if (msg.table && msg.database)
@@ -9,6 +9,8 @@ import harperLogger from '../../utility/logging/harper_logger.ts';
9
9
  import * as globalSchema from '../../utility/globalSchema.ts';
10
10
  import * as user from '../../security/user.ts';
11
11
  import * as serverUtils from '../serverHelpers/serverUtilities.ts';
12
+ import { runWithDispatchedOperation } from '../serverHelpers/operationAuthorizationState.ts';
13
+ import { stripSuppliedParsedSqlObject } from '../serverHelpers/requestSanitization.ts';
12
14
  import moment from 'moment';
13
15
  import * as jobs from './jobs.ts';
14
16
  import { cloneDeep } from 'lodash';
@@ -54,12 +56,16 @@ const JOB_ID = JOB_NAME.substring(4);
54
56
  throw new Error('Did not find job request in hdb_job table, unable to proceed');
55
57
  }
56
58
  request = cloneDeep(request);
59
+ // The worker re-enters from the persisted row rather than re-dispatching, so a row queued before
60
+ // the dispatch-time strip existed — or written directly — is sanitized here.
61
+ stripSuppliedParsedSqlObject(request);
57
62
 
58
63
  const operation = serverUtils.getOperationFunction(request);
59
64
  harperLogger.trace('Running operation:', request.operation, 'for job', JOB_ID);
60
65
 
61
- // Run the job operation.
62
- const results = await operation.job_operation_function(request);
66
+ const results = await runWithDispatchedOperation(request.operation, () =>
67
+ operation.job_operation_function(request)
68
+ );
63
69
  harperLogger.trace('Result from job:', JOB_ID, results);
64
70
 
65
71
  jobObj.status = hdbTerms.JOB_STATUS_ENUM.COMPLETE;
@@ -11,6 +11,7 @@ import * as search from '../../dataLayer/search.ts';
11
11
  import Search_Object from '../../dataLayer/SearchObject.ts';
12
12
  import searchByHashObj from '../../dataLayer/SearchByHashObject.ts';
13
13
  import SQL_Search_Object from '../../dataLayer/SqlSearchObject.ts';
14
+ import { runWithOperationAuthorizationBypass } from '../serverHelpers/operationAuthorizationState.ts';
14
15
  import * as hdbTerms from '../../utility/hdbTerms.ts';
15
16
  import JobObject from './JobObject.ts';
16
17
  import UpdateObject from '../../dataLayer/UpdateObject.ts';
@@ -249,7 +250,9 @@ export async function getJobsInDateRange(jsonBody: any) {
249
250
  const hdbSql = require('../../sqlTranslator/index');
250
251
  pSqlEvaluate = promisify(hdbSql.evaluateSQL);
251
252
  }
252
- return await pSqlEvaluate(sqlSearchObj);
253
+ // Harper's own statement, not the caller's. A carrier is not equivalent here — see DESIGN.md.
254
+ // Wraps only this statement: a later caller-dependent one must not inherit the bypass.
255
+ return await runWithOperationAuthorizationBypass(true, () => pSqlEvaluate(sqlSearchObj));
253
256
  } catch (e) {
254
257
  log.error(
255
258
  `there was a problem searching for jobs from date ${jsonBody.from_date} to date ${jsonBody.to_date} ${e}`
@@ -23,8 +23,24 @@ interface LiveSubscription {
23
23
  authExpiresAt?: number;
24
24
  /** Returns true if the principal is still authorized for this subscription. */
25
25
  recheck: () => Promise<boolean>;
26
- /** Stop delivery and tear down the subscription. */
27
- terminate: () => void;
26
+ /** Stop delivery and tear down. May be async (e.g. a shared-feed refcount release). */
27
+ terminate: () => void | Promise<void>;
28
+ }
29
+
30
+ function errorMessage(error: unknown): string {
31
+ try {
32
+ return error instanceof Error ? error.message : String(error);
33
+ } catch {
34
+ return '<error message unavailable>';
35
+ }
36
+ }
37
+
38
+ function safeLog(log: ((message: string) => void) | undefined, message: string): void {
39
+ try {
40
+ log?.(message);
41
+ } catch {
42
+ /* a broken logger must not turn a contained failure into a new one */
43
+ }
28
44
  }
29
45
 
30
46
  const registry = new Set<LiveSubscription>();
@@ -32,9 +48,15 @@ let sweepTimer: any = null;
32
48
  let itcListenerInstalled = false;
33
49
  let sweeping = false;
34
50
 
51
+ const NOOP_HANDLE = { unregister: () => {} };
52
+
53
+ function triggerSweep(): void {
54
+ void sweep().catch((error) => safeLog(hdbLogger.error, `liveSubscriptionAuth: sweep failed: ${errorMessage(error)}`));
55
+ }
56
+
35
57
  function ensureStarted(): void {
36
58
  if (!sweepTimer) {
37
- sweepTimer = setInterval(() => void sweep(), RECHECK_INTERVAL_MS);
59
+ sweepTimer = setInterval(triggerSweep, RECHECK_INTERVAL_MS);
38
60
  // don't keep the worker alive solely for the recheck timer
39
61
  sweepTimer.unref?.();
40
62
  }
@@ -44,7 +66,7 @@ function ensureStarted(): void {
44
66
  // user/role cache before invoking listeners, so recheck() observes the new permissions.
45
67
  const handlers = require('./itc/serverHandlers.js');
46
68
  if (handlers?.userHandler?.addListener) {
47
- handlers.userHandler.addListener(() => void sweep());
69
+ handlers.userHandler.addListener(triggerSweep);
48
70
  itcListenerInstalled = true;
49
71
  }
50
72
  } catch (error) {
@@ -61,32 +83,49 @@ function stopIfIdle(): void {
61
83
  }
62
84
 
63
85
  /**
64
- * Register a live subscription for continuous re-authorization. The returned subscription's normal
65
- * teardown (end()/close) automatically unregisters it, so callers don't need to.
86
+ * Register a live subscription for continuous re-authorization.
87
+ *
88
+ * Without `revoke`, teardown is end()/close()/emit('close') on `subscription`, and the
89
+ * subscription's own teardown unregisters the entry so callers need not.
90
+ *
91
+ * With `revoke`, that becomes the teardown and `subscription` is never touched — no `end` wrapping,
92
+ * no 'close' listener — because a feed shared by many subscribers must stay revocable per subscriber,
93
+ * so the registry can neither own the shared object nor let every registrant mutate it. A `revoke`
94
+ * caller owns the entry's lifetime: nothing detects a leaked registration, and a forgotten
95
+ * `unregister()` degrades sweep latency for every other tracked subscriber. It also owns teardown
96
+ * recovery — the entry is untracked before `revoke` runs and `revoke` is invoked exactly once, so one
97
+ * that throws, rejects or never settles is logged and never retried. A `recheck` shared across
98
+ * subscribers must not mutate state shared across them: `registerLiveSubscriptionForContext` in
99
+ * resources/Resource.ts mutates `context.user`, which is safe only while each context has exactly
100
+ * one subscriber.
66
101
  */
67
- export function registerLiveSubscription(opts: {
68
- subscription: any;
69
- username: string;
70
- authExpiresAt?: number;
71
- recheck: () => Promise<boolean>;
72
- }): void {
73
- const { subscription, username, authExpiresAt, recheck } = opts;
74
- if (!subscription || typeof subscription !== 'object' || subscription.closed) return;
102
+ export function registerLiveSubscription(
103
+ opts: {
104
+ username: string;
105
+ authExpiresAt?: number;
106
+ recheck: () => Promise<boolean>;
107
+ } & (
108
+ | { subscription: any; revoke?: undefined }
109
+ // requiring one of the two modes stops a caller that supplies neither from type-checking
110
+ // into a silent no-op registration; a revoke-only registrant may own no subscription object
111
+ | { subscription?: any; revoke: () => void | Promise<void> }
112
+ )
113
+ ): { unregister: () => void } {
114
+ const { subscription, username, authExpiresAt, recheck, revoke } = opts;
115
+ if (!revoke && (!subscription || typeof subscription !== 'object' || subscription.closed)) return NOOP_HANDLE;
75
116
 
76
117
  const entry: LiveSubscription = {
77
118
  username,
78
119
  authExpiresAt,
79
120
  recheck,
80
- terminate: () => {
81
- // end() removes the subscription from the broadcast loop and closes its iterable queue.
82
- try {
121
+ terminate:
122
+ revoke ??
123
+ (() => {
124
+ // end() removes the subscription from the broadcast loop and closes its iterable queue.
83
125
  if (subscription.end) subscription.end();
84
126
  else if (subscription.close) subscription.close();
85
127
  else subscription.emit?.('close');
86
- } catch {
87
- /* ignore */
88
- }
89
- },
128
+ }),
90
129
  };
91
130
  registry.add(entry);
92
131
 
@@ -94,51 +133,90 @@ export function registerLiveSubscription(opts: {
94
133
  registry.delete(entry);
95
134
  stopIfIdle();
96
135
  };
97
- // Both transports ultimately call end() on normal teardown (MQTT unsubscribe/disconnect; SSE close
98
- // is wired to end()); wrap it so a closed stream never leaks a registry entry. Also listen for
99
- // 'close' to cover any iterable that closes without an end().
100
- const originalEnd = typeof subscription.end === 'function' ? subscription.end.bind(subscription) : null;
101
- if (originalEnd) {
102
- subscription.end = function (...args: any[]) {
103
- unregister();
104
- return originalEnd(...args);
105
- };
136
+
137
+ if (!revoke) {
138
+ // Both transports ultimately call end() on normal teardown (MQTT unsubscribe/disconnect; SSE close
139
+ // is wired to end()); wrap it so a closed stream never leaks a registry entry. Also listen for
140
+ // 'close' to cover any iterable that closes without an end(). Skipped when `revoke` is supplied:
141
+ // the caller owns unregistration, and a subscription shared by many subscribers must not be
142
+ // mutated once per registrant.
143
+ const originalEnd = typeof subscription.end === 'function' ? subscription.end.bind(subscription) : null;
144
+ if (originalEnd) {
145
+ subscription.end = function (...args: any[]) {
146
+ unregister();
147
+ return originalEnd(...args);
148
+ };
149
+ }
150
+ subscription.on?.('close', unregister);
106
151
  }
107
- subscription.on?.('close', unregister);
108
152
 
109
153
  ensureStarted();
154
+ return { unregister };
155
+ }
156
+
157
+ /** Untrack first: a `terminate` that hangs or fails must not wedge the sweep or be re-entered by a later one. */
158
+ function terminateEntry(
159
+ entry: LiveSubscription,
160
+ reason: string,
161
+ notice: ((message: string) => void) | undefined = hdbLogger.info
162
+ ): void {
163
+ registry.delete(entry);
164
+ safeLog(notice, `liveSubscriptionAuth: revoking subscription for ${entry.username} (${reason})`);
165
+ const failed = (error: unknown) =>
166
+ safeLog(
167
+ hdbLogger.error,
168
+ `liveSubscriptionAuth: terminate failed for ${entry.username} (${reason}): ${errorMessage(error)}`
169
+ );
170
+ try {
171
+ // an async terminate's rejection would otherwise surface as an unhandled rejection on the timer's stack
172
+ Promise.resolve(entry.terminate()).catch(failed);
173
+ } catch (error) {
174
+ failed(error);
175
+ }
110
176
  }
111
177
 
112
178
  async function sweep(): Promise<void> {
113
179
  if (sweeping) return; // a slow recheck must not overlap with the next tick/event
114
180
  sweeping = true;
181
+ // the per-subscriber lines are info, which the shipped default (logging.level: warn) drops; one
182
+ // aggregate keeps the pass visible without making a mass role change a warn per subscriber
183
+ const revokedByReason = new Map<string, number>();
184
+ const countRevocation = (reason: string) => revokedByReason.set(reason, (revokedByReason.get(reason) ?? 0) + 1);
115
185
  try {
116
- const now = Date.now();
117
- for (const entry of registry) {
186
+ // snapshot bounds the pass to entries present at its start; the has() guards cover the rest
187
+ for (const entry of Array.from(registry)) {
188
+ if (!registry.has(entry)) continue;
118
189
  try {
119
- const expired = entry.authExpiresAt != null && now >= entry.authExpiresAt * 1000;
190
+ const expired = entry.authExpiresAt != null && Date.now() >= entry.authExpiresAt * 1000;
120
191
  const stillAuthorized = expired ? false : await entry.recheck();
121
- const revoke = expired || !stillAuthorized;
122
- if (revoke) {
123
- registry.delete(entry);
124
- entry.terminate();
192
+ if (!registry.has(entry)) continue;
193
+ if (expired || !stillAuthorized) {
194
+ const reason = expired ? 'token expired' : 'no longer authorized';
195
+ terminateEntry(entry, reason);
196
+ countRevocation(reason);
125
197
  }
126
198
  } catch (error) {
127
199
  // fail closed: if authorization can't be confirmed, revoke
128
- registry.delete(entry);
129
- try {
130
- entry.terminate();
131
- } catch {
132
- /* ignore */
200
+ if (registry.has(entry)) {
201
+ terminateEntry(entry, `recheck error: ${errorMessage(error)}`, hdbLogger.warn);
202
+ countRevocation('recheck error');
133
203
  }
134
- hdbLogger.warn?.(
135
- `liveSubscriptionAuth: revoked subscription for ${entry.username} after recheck error: ${(error as Error).message}`
136
- );
137
204
  }
138
205
  }
139
206
  } finally {
140
207
  sweeping = false;
141
208
  stopIfIdle();
209
+ if (revokedByReason.size > 0) {
210
+ let total = 0;
211
+ for (const count of revokedByReason.values()) total += count;
212
+ const breakdown = Array.from(revokedByReason, ([reason, count]) => `${reason}: ${count}`).join(', ');
213
+ safeLog(
214
+ hdbLogger.warn,
215
+ // "revoking", like the per-subscriber line: teardown is dispatched, not awaited, and a
216
+ // failure surfaces on its own error line
217
+ `liveSubscriptionAuth: revoking ${total} live subscription${total === 1 ? '' : 's'} (${breakdown})`
218
+ );
219
+ }
142
220
  }
143
221
  }
144
222
 
@@ -146,3 +224,8 @@ async function sweep(): Promise<void> {
146
224
  export function _liveSubscriptionCount(): number {
147
225
  return registry.size;
148
226
  }
227
+
228
+ /** Test-only: run a sweep synchronously, bypassing the interval/ITC triggers. */
229
+ export function _sweepNow(): Promise<void> {
230
+ return sweep();
231
+ }