@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
package/server/DESIGN.md CHANGED
@@ -47,7 +47,8 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
47
47
  | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
48
48
  | `serverHelpers/Request.ts` | Wraps `IncomingMessage` with Harper-specific fields (user, response, headers). |
49
49
  | `serverHelpers/Headers.ts` | Header mutation/merge utilities. |
50
- | `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getDeserializer`. |
50
+ | `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getMessageSerializer`, `getDeserializer`. |
51
+ | `serverHelpers/sharedMessageEncoding.ts` | Per-message encoding shared across the subscribers of a topic — one serialization per (message, content type) and, for MQTT QoS 0, one PUBLISH packet per (payload, topic, protocol version). Keyed on the message object, which `transactionBroadcast` dispatches by identity to every subscription of a key. **Identity contract:** see below. |
51
52
  | `serverHelpers/serverUtilities.ts` | `OperationDefinition` and shared helpers. |
52
53
  | `serverHelpers/OperationFunctionObject.ts` | Wraps an operation handler with metadata. |
53
54
  | `serverHelpers/JSONStream.ts` | Streaming JSON output for large responses. |
@@ -58,6 +59,20 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
58
59
  | `serverRegistry.ts` | Trivial registry export. |
59
60
  | `status/` | Server status reporting (cluster status, per-port info). |
60
61
 
62
+ > **Subscription message identity contract.** `sharedMessageEncoding.ts` encodes a message once and
63
+ > reuses the bytes for every subscriber of the topic, keyed on the message object's identity. That is
64
+ > sound for every internal producer because each yields a fresh object per version — `transactionBroadcast`
65
+ > hands one `auditRecord` to every subscription, `auditStore`'s `getValue` memoizes the decode in its
66
+ > closure, and `primaryStore.getEntry` is version-guarded — so identity implies equal content. Pinned by
67
+ > `unitTests/resources/subscriptionValueIdentity.test.js`.
68
+ >
69
+ > Rather than leave that as a rule custom Resources have to know, the event's record `version` is part
70
+ > of the cache key: `DurableSubscriptionsSession` forwards it to the delivery listener, and an entry
71
+ > whose version does not match is re-encoded. A Resource that reuses one envelope but advances the
72
+ > version is therefore correct, not merely disallowed; one that supplies no version is not shared at
73
+ > all. Only mutating an object _without_ changing its version can serve stale bytes, and that is
74
+ > indistinguishable from re-sending the same message.
75
+
61
76
  ### Threads
62
77
 
63
78
  | File | Purpose |
@@ -74,6 +89,17 @@ calls fail with `ERR_HARPER_PROCESS_SHUTTING_DOWN`; scoped worker-type restarts
74
89
  `shutdownWorkersNow()` remains an immediate teardown: its worker shutdown messages are best-effort,
75
90
  and it force-terminates the remaining worker set rather than waiting for application drain hooks.
76
91
 
92
+ A rolling restart serves the _old_ code until each worker is replaced, and where the OS grants
93
+ `SO_REUSEPORT` the not-yet-replaced workers keep accepting connections for the whole restart — so a
94
+ component deploy is not live pool-wide until `restartWorkers()` resolves. For the overlapping types
95
+ (HTTP) it waits for each replacement to report `CHILD_STARTED` — including the ones that can only be
96
+ started after their predecessor releases its exclusive ports (Windows/macOS/Bun) — and reports how
97
+ many workers it left on the old code because a replacement never came up, versus how many
98
+ replacements never started after their predecessor was already gone. Other thread types start their
99
+ replacement without being awaited. Each wait is bounded by a per-worker startup backstop, so
100
+ resolution means "the restart finished", not "every worker is new". A caller that treats its own success as
101
+ "the component is live" must await it (see `deployComponent` in `components/operations.js`).
102
+
77
103
  > Workers receive `workerData.noServerStart = true` — never start the server inside a worker.
78
104
  >
79
105
  > `threadServer.listenOnDomainSocket()` skips a listener only when its path exceeds the platform's
@@ -105,7 +131,8 @@ Every entry is a top-level function or named const. Jump via go-to-symbol or `gr
105
131
  | `getPorts()` | Resolves listener options → list of `{port, secure}`. |
106
132
  | `httpServer()` | Main listener registration entry point. |
107
133
  | `getHTTPServer(port, secure, options)` | **The largest function in the file.** Creates/retrieves the underlying Node HTTP/HTTPS server. Wires `request`, `upgrade`, error handlers, TLS context, and the per-port middleware chain. |
108
- | `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`. |
134
+ | `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`, and records its resolved order for `get_status`. |
135
+ | `buildChains()` | Stores a built chain, and rebuilds every other already-built chain of that kind when the registration is on the `'all'` pseudo-port. See "Middleware ordering" below. |
109
136
  | `unhandled()` | Terminal 404 handler. |
110
137
  | `onRequest()` | Thin alias of `httpServer({requestOnly: true})`. |
111
138
  | `onUpgrade()` / `upgradeListeners` (const) | Register HTTP upgrade listener; underlying list. |
@@ -125,6 +152,19 @@ Components register listeners with optional `before: 'name'` / `after: 'name'` o
125
152
 
126
153
  The default WebSocket upgrade handler is registered automatically inside `onWebSocket()` the first time it runs for a given port.
127
154
 
155
+ **`port: 'all'` is a pseudo-port, and its chain is not the one that serves traffic.** Chain building
156
+ folds every `'all'` entry into each concrete port's chain, so `chains.all` exists but nothing
157
+ dispatches through it — `httpChain[port]`/`upgradeChains[port]`/`websocketChains[port]` are looked up
158
+ by the bound port on every request (Node, Bun, and uWS alike). A registration therefore has to
159
+ rebuild the chains it affects, not just its own key: `http.ts → buildChains()` rebuilds every
160
+ already-built chain of that kind whenever the registration is on `'all'`. Without that, an entry
161
+ registered on `'all'` after the concrete port's chain was built — the shape an application catch-all
162
+ mounted `after: 'rest'` takes, since `rest` registers first — updates only `chains.all` and never
163
+ reaches a request (#2418). Rebuilding is a pure function of the listener list and the port, so the
164
+ extra passes can only reproduce a port's order or extend it. `buildChains()` also writes the
165
+ `get_status` chain description in the same pass, which is what keeps that report from ever
166
+ describing an order a port isn't running (#1573).
167
+
128
168
  ### Application mounts (`host` / `urlPath` in the root config)
129
169
 
130
170
  An operator mounts an application by putting `host`/`urlPath` on its entry in the **root** config; `components/scopeMount.ts` models it and the loader threads it into every `Scope` for that application (both load paths — the root-config `package` recursion and the components-root directory scan).
@@ -154,16 +194,152 @@ must run on a worker, `registeredOperations.ts` carries that state in the same-p
154
194
  separately from the structured-cloned body. Never attach trusted dispatch state to an operation
155
195
  payload.
156
196
 
197
+ `server.registerOperation()` runs per-worker, so anything the **main** thread must later know about a
198
+ registered op has to ride the OPERATION_REGISTERED announcement — a module-local registry populated
199
+ during registration exists only in the worker that registered. The bridge carries two such facts
200
+ today: name→thread routing (for execution forwarding) and `grantable` (so `validateOperations` on
201
+ main will accept the name in a role's `operations` allowlist, for add_role/alter_role, impersonation,
202
+ and OIDC trust policies). Adding a third main-thread consumer of a worker-registered fact means
203
+ extending that message, not reading a registry that main never populated. Grantability is safe to
204
+ mirror because it only widens what an allowlist may _name_; enforcement stays on the worker's
205
+ `chooseOperation`.
206
+
157
207
  ## Resource ↔ HTTP boundary
158
208
 
159
209
  `REST.ts → http(request, nextHandler)` is the chief integration point: it takes a `Request`, asks the `Resources` registry for a match, builds a `RequestTarget`, and dispatches into the Resource class's static method. Cache headers are translated to `request.expiresAt` / `onlyIfCached` / `noCache` flags within the same function.
160
210
 
211
+ ### Streaming startup errors
212
+
213
+ SSE and NDJSON serializers eagerly take and hold their first iterator step for GET requests. `REST.ts` waits through the next event-loop turn for that step: an immediate rejection remains an HTTP error rendered as Problem Details, while a first item or the cutoff commits the stream. Mutating requests do not use the startup-status gate because their transaction has already committed by this point; all of their stream failures use the in-band form. Later GET failures are terminal, format-valid records (`event: harper-error` for SSE and a reserved control record for NDJSON). Keep the decision in the serializer/REST boundary so Node, uWS, Bun, compression, and injection share one contract; transports must not independently prefetch the iterator.
214
+
215
+ SSE terminal event data uses `{ error: <code-or-class>, message: <message>, status?: <status> }`; NDJSON wraps the same object as `{ "$harperStreamError": { ... } }` so it cannot be mistaken for an ordinary row with an `error` field.
216
+ Sources with `mapError` remain unmapped for these two formats so their failures reach this single contract instead of becoming legacy data records.
217
+ generic JSON-array streaming retains its older `{ error: "<name>: <message>" }` element shape.
218
+ The `error` value is the stable programmatic discriminator; `message` is diagnostic and follows the same thrown-message exposure policy as pre-commit Problem Details.
219
+
220
+ Clean stream completion does not prove completeness; clients must inspect SSE for the `harper-error` event and NDJSON for the `$harperStreamError` control record.
221
+
222
+ ### Deferred credential rejection (#2418)
223
+
224
+ `authentication` runs before route matching, so when it meets an `Authorization` header it cannot
225
+ resolve it does not yet know whether Harper or an application owns the URL. Rejecting there forces
226
+ an application to choose between two things it needs: Harper owning its own routes (status, REST
227
+ resources) and its own routes receiving their own credential scheme untouched.
228
+
229
+ So a rejection is recorded rather than answered:
230
+
231
+ - **Valid Harper credentials** authenticate normally and populate `request.user`. Unchanged.
232
+ - **No credentials** continue anonymously. Unchanged.
233
+ - **A syntactically valid credential Harper does not recognize** leaves `request.user` unset, leaves
234
+ the inbound `Authorization` header byte-for-byte intact, and records request-local state through
235
+ `security/deferredAuthentication.ts`. The state lives behind a module-private `Symbol`. Its
236
+ descriptor and value are immutable and non-enumerable, so downstream middleware cannot clear it
237
+ and it does not leak through request copies or serialization.
238
+ - **An internal authentication fault** — unreadable or malformed JWT key material, a storage failure,
239
+ an unexpected error type — is never deferred, and fails closed with the in-line 401.
240
+ - **The operations API** never defers: `request.isOperationsServer` short-circuits to the in-line
241
+ 401, because every operations route is Harper-owned and there is nothing to defer to.
242
+
243
+ **Rejection provenance is asserted, never inferred.** `security/credentialRejection.ts` holds a
244
+ module-private `Symbol` tag; only the code that actually concludes "this credential is unacceptable"
245
+ sets it, and `isCredentialRejection()` reads nothing else. Status ranges cannot carry that meaning:
246
+ `findAndValidateUser()` lazily loads the user cache, whose system-table searches raise a
247
+ default-status-400 `ClientError` when `system.hdb_role`/`system.hdb_user` is unavailable, so a 4xx
248
+ test would classify a storage outage as an unknown credential and hand it to application
249
+ authorization. The tag is set at exactly these points:
250
+
251
+ | Tagged rejection | Where |
252
+ | ------------------------------------------------------------------ | ------------------------------------------ |
253
+ | unknown user, inactive user, bad password | `security/user.ts → findAndValidateUser()` |
254
+ | JWT syntax, signature, expiry, not-before, subject/claim rejection | `tokenAuthentication.ts → validateToken()` |
255
+ | refresh-token hash mismatch, malformed scoped-token claims | `tokenAuthentication.ts` |
256
+ | an `Authorization` scheme Harper does not implement | `security/auth.ts` |
257
+
258
+ `validateToken()` separates the two in the same catch: `jsonwebtoken` reports unusable key material
259
+ through the very same `JsonWebTokenError` type it uses for a forged token, so the public key is
260
+ validated as key material before `jwt.verify()` runs and a residual key-material message is treated
261
+ as a fault. An untagged error propagates unmasked.
262
+
263
+ The Bearer path's refresh-token probe follows the same rule. When operation-token validation says
264
+ `invalid token`, authentication retries the credential as a refresh token; a fault raised by that
265
+ retry propagates, and only an ordinary _tagged_ refresh rejection restores the original
266
+ operation-token rejection for deferral. An in-line fail-closed response logs the original fault
267
+ server-side and returns the same generic authentication failure a rejected credential gets, so
268
+ internal detail never reaches an unauthenticated client.
269
+
270
+ Any layer that establishes Harper owns the route then settles the deferred state before doing work:
271
+
272
+ | Layer | Where |
273
+ | --------------------------------------- | ------------------------------------------------------------------ |
274
+ | `REST.ts → http()` | after `resources.getMatch` succeeds (and for the OpenAPI document) |
275
+ | `REST.ts` WebSocket handler | after `resources.getMatch(url, 'ws')` succeeds |
276
+ | `graphqlQuerying.ts` | after the `/graphql` prefix match, ahead of its error mapping |
277
+ | `static.ts` | after a static file entry matches |
278
+ | `mqtt.ts` WebSocket handler | once the pending HTTP chain settles, before the first packet |
279
+ | `components/mcp/adapters/harperHttp.ts` | after the WebSocket hand-off, before the body is read |
280
+
281
+ **Every Harper-owned handler registered `after: 'authentication'` owes this settlement**, because
282
+ declining to call `nextHandler` is precisely the moment ownership is settled. A handler that skips
283
+ it serves an unrecognized credential as anonymous — the MCP application mount did exactly that, and
284
+ returned 200 for an `initialize` the base revision answered with 401.
285
+
286
+ Settlement goes through `settleDeferredCredentialRejection()`, which returns the response descriptor
287
+ `security/auth.ts` used to return in line: status 401 and `serializeMessage({error: message}, request)`
288
+ in the request's negotiated content type. That matters because an owner's own error mapping is not
289
+ that contract — REST renders a thrown error as an RFC 9457 Problem Details document and GraphQL as
290
+ `{errors:[{message}]}` — and a rejected credential never reached either before deferral existed.
291
+ `assertNoDeferredCredentialRejection()` is the throwing form, for a WebSocket upgrade that has no
292
+ descriptor to return. A WebSocket owner cannot read the state synchronously: `server/http.ts` starts
293
+ `httpChain[port](request)` and invokes the WebSocket chain with the still-pending completion, so
294
+ authentication has not yet classified the credential. `mqtt.ts` therefore settles on that promise —
295
+ the same one the session's principal resolves from — and closes the socket from its rejection, while
296
+ its frame handlers still attach synchronously. The deferred status is pinned to 401 regardless of the underlying error's own
297
+ status, so a 403 `token expired` reads exactly as it did before. A Harper-owned route therefore behaves identically to the pre-deferral
298
+ build, protected or public: an unknown credential can never buy access an anonymous caller would
299
+ have received, and can never reach an application catch-all. Only a URL that reached
300
+ `nextHandler` — one no Harper route owns — carries the original header onward.
301
+
302
+ The contract is route-ownership-based, not path-based. There is no exemption list, no carrier
303
+ header, no credential rename, and no pre-auth stripping shim.
304
+
305
+ **Authentication does not re-decorate a 401 it did not raise.** `security/auth.ts` post-processes any
306
+ 401 coming back up the chain — overwriting `WWW-Authenticate` with `Basic`, or rewriting the status to
307
+ a 302 at `resources.loginPath` for a browser. The in-line rejection deferral replaced returned before
308
+ that code, so a settled rejection must skip it to stay wire-identical; and a 401 an application
309
+ catch-all raised for its own scheme (a WooCommerce or Bearer challenge) is that application's to make.
310
+ Both cases are keyed on the deferred state, so a request that deferred nothing keeps the existing
311
+ behavior exactly. The #1565 identity floor still applies either way — it is stamped in
312
+ `applyResponseHeaders`, not in the challenge rewriting.
313
+
314
+ **The identity cache floor survives the legacy Fastify fallbacks.** A response produced under a
315
+ deferred credential is credential-dependent (#1565), so `authentication` stamps
316
+ `Cache-Control: private, no-cache` and `Vary: Authorization, Cookie` on it. Before deferral an
317
+ unrecognized credential could not reach a fallback at all, so this was unreachable. All three adapters
318
+ now reconcile through one policy, `Headers.ts → mergeChainHeadersIntoFallback()`: Fastify wins every
319
+ single-valued header it set, `Vary` is unioned, and the private scope is re-applied unless the final
320
+ response explicitly opts into shared caching (`public`/`s-maxage`).
321
+
322
+ `Set-Cookie` is the deliberate exception to Fastify-wins. It is a list-valued field, so the chain's
323
+ cookies are appended beside Fastify's and de-duplicated by exact value, never by cookie name. A cookie
324
+ is identified by name _plus_ `Domain` _plus_ `Path` (RFC 6265 §5.3), so collapsing by name drops
325
+ legitimately distinct cookies — a same-name pair scoped to `/` and `/wp-admin`, or a `Max-Age=0`
326
+ deletion paired with a set. Exact value is also what makes Node's `writeHead` re-merge idempotent,
327
+ because that path sees the chain's own cookie already on the response. Two cookies that do share a
328
+ full identity both reach the client, chain last, and the user agent resolves them last-wins.
329
+
330
+ Bun and uWS rebuild their headers from Fastify's reply and merge once. Node hands Fastify the same
331
+ `ServerResponse` the chain's headers were copied onto, so copying is not enough — a route calling
332
+ `reply.header('Cache-Control', …)` replaces the floor outright and can make a credential-dependent
333
+ response shared-cacheable. `bridgeChainHeadersToNodeResponse()` therefore runs the same merge from a
334
+ `writeHead` interception, the last point the header set is still mutable and the one Node also routes
335
+ implicit headers through.
336
+
161
337
  ### Response Cache-Control / Vary policy (#1518, #1565)
162
338
 
163
339
  Three tiers, applied in two places:
164
340
 
165
341
  1. **App/resource explicit** — a `Cache-Control` set by the resource (or `@table(cacheControl: "...")` for anonymous reads, emitted in `REST.ts → http()`) always wins. The declaration is required: anonymous readability alone never emits shared-cache headers, because a request-attribute-gated `allowRead` (IP, headers) would make inferred `public` unsound.
166
- 2. **Identity floor** — `security/auth.ts → applyResponseHeaders` stamps `Cache-Control: private, no-cache` + `Vary: Authorization` (+ `Cookie` when sessions are on) on any response where a principal was resolved or credentials were rejected (401), _unless_ the app opted into shared caching with `public`/`s-maxage` (the RFC 9111 opt-in).
342
+ 2. **Identity floor** — `security/auth.ts → applyResponseHeaders` stamps `Cache-Control: private, no-cache` + `Vary: Authorization` (+ `Cookie` when sessions are on) on any response where a principal was resolved, credentials were rejected (401), or a credential rejection was deferred (#2418 — the application answered using the header Harper passed through, so the response is credential-dependent at a plain 200), _unless_ the app opted into shared caching with `public`/`s-maxage` (the RFC 9111 opt-in).
167
343
  3. **CORS partitioning** — when CORS is enabled, every response gets `Vary: Origin` (the ACAO header is reflected per-origin, and its absence on no-Origin requests is origin-dependent too).
168
344
 
169
345
  The `@table(cacheControl:)` value is persisted on the primary-key attribute (like `expiration`), so all threads and future boots see it; `resources/databases.ts → table()` treats `null` as "schema explicitly has none" (clears on reload) and `undefined` as "caller is not schema-defining" (no clobber from `add_attribute`/cluster schema events).
@@ -172,19 +348,21 @@ The `@table(cacheControl:)` value is persisted on the primary-key attribute (lik
172
348
 
173
349
  ## "Where is X" cheat sheet
174
350
 
175
- | Question | Where |
176
- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
- | Where do I register a new HTTP handler? | `http.ts → httpServer()` (or `onRequest()` for the request-only form) |
178
- | Where do I register a WebSocket handler? | `http.ts → onWebSocket()` |
179
- | How does `before`/`after` middleware ordering work? | `middlewareChain.ts → topoSort` |
180
- | Where does PROXY protocol get parsed? | `serverHelpers/proxyProtocol.ts` (applied by `http.ts → enableProxyProtocol` / `createH2CProxyFront`) |
181
- | How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)? | `request.connectionInfo` (`ConnectionInfo` from `serverHelpers/proxyProtocol.ts`); only set from a trusted PROXY v2 header on the UDS mirror, never from a request header |
182
- | Where is the REST request → Resource dispatch? | `REST.ts → http()` |
183
- | Where is the operations API request handled? | `operationsServer.ts → handler` |
184
- | How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
185
- | Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
186
- | How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
187
- | Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
351
+ | Question | Where |
352
+ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
353
+ | Where do I register a new HTTP handler? | `http.ts → httpServer()` (or `onRequest()` for the request-only form) |
354
+ | Where do I register a WebSocket handler? | `http.ts → onWebSocket()` |
355
+ | How does `before`/`after` middleware ordering work? | `middlewareChain.ts → topoSort` |
356
+ | Where does PROXY protocol get parsed? | `serverHelpers/proxyProtocol.ts` (applied by `http.ts → enableProxyProtocol` / `createH2CProxyFront`) |
357
+ | How does an app read forwarded TLS facts (JA3/JA4, ALPN, SNI, mTLS cert)? | `request.connectionInfo` (`ConnectionInfo` from `serverHelpers/proxyProtocol.ts`); only set from a trusted PROXY v2 header on the UDS mirror, never from a request header |
358
+ | Why does mTLS revocation checking look up the client cert's issuer itself? | `security/certificateVerification/trustedIssuers.ts` — Node only completes a peer chain from the _listener_ context's store, but Harper's client CAs live on SNI contexts (`security/keys.ts → createTLSSelector`), so a resumed TLS session (and Node 26.8.0/26.8.1, nodejs/node#65579) exposes the leaf alone; `verifyCertificate` recovers the issuer from the published CA set and otherwise applies `failureMode` |
359
+ | Where is the REST request → Resource dispatch? | `REST.ts → http()` |
360
+ | Where is the operations API request handled? | `operationsServer.ts → handler` |
361
+ | How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
362
+ | Why doesn't every MQTT subscriber re-serialize the message it receives? | `serverHelpers/sharedMessageEncoding.ts` (memoized on the message object the fan-out shares); consumed by the outbound listener in `mqtt.ts` |
363
+ | Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
364
+ | How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
365
+ | Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
188
366
 
189
367
  ---
190
368
 
@@ -32,6 +32,24 @@ function getDurableSession() {
32
32
  return _DurableSession;
33
33
  }
34
34
  let _LastWill: any;
35
+ /**
36
+ * A scoped token's only revocation is expiry, so its will must not publish past it. Keyed off the
37
+ * persisted will principal (not any live session user) so both will paths agree on one source.
38
+ * Fails closed: a scoped will with no recorded expiry is treated as expired rather than published.
39
+ */
40
+ function isWillFromExpiredScopedToken(will: any): boolean {
41
+ const user = will?.user;
42
+ if (!user?._scopedToken) return false;
43
+ return !user.authExpiresAt || user.authExpiresAt * 1000 <= Date.now();
44
+ }
45
+
46
+ /** Drops the runtime-only pre-expanded operations Set so the permission set is storage-safe. */
47
+ function stripRuntimePermissionState(permission: any): any {
48
+ if (!permission || typeof permission !== 'object') return permission;
49
+ const { _expandedOperations, ...durable } = permission;
50
+ return durable;
51
+ }
52
+
35
53
  function getLastWill() {
36
54
  if (!_LastWill) {
37
55
  _LastWill = table({
@@ -56,13 +74,23 @@ if (getWorkerIndex() === 0) {
56
74
  for await (const will of getLastWill().search({})) {
57
75
  const data = will.data;
58
76
  const message = { ...will };
59
- if (message.user?.username) message.user = await (server as any).getUser(message.user.username);
60
77
  try {
61
- await publishMessage(message, data, message);
78
+ if (message.user?._scopedToken) {
79
+ // A scoped token's username is attribution only; never rehydrate it by name (that could
80
+ // substitute a real principal). The will carries the token's own downgraded role.
81
+ if (!isWillFromExpiredScopedToken(message)) {
82
+ await publishMessage(message, data, message);
83
+ } else warn('Dropping will from an expired scoped token', data);
84
+ } else {
85
+ if (message.user?.username) message.user = await (server as any).getUser(message.user.username);
86
+ await publishMessage(message, data, message);
87
+ }
88
+ await getLastWill().delete(will.id);
62
89
  } catch {
90
+ // One will's publish/delete failure must not abort replay of the rest; the row stays and
91
+ // is retried on the next restart.
63
92
  warn('Failed to publish will', data);
64
93
  }
65
- getLastWill().delete(will.id);
66
94
  }
67
95
  })();
68
96
  }
@@ -125,7 +153,22 @@ export async function getSession({
125
153
  }
126
154
  if (will) {
127
155
  will.id = sessionId;
128
- will.user = { username: user?.username };
156
+ // A scoped-token bearer's will must carry the token's own role and expiry: its username is
157
+ // attribution only and cannot be rehydrated from hdb_user at replay time. Persist only the
158
+ // durable permission fields — not the runtime-only _expandedOperations Set, which is rebuilt
159
+ // on read and would not round-trip through storage.
160
+ will.user = user?._scopedToken
161
+ ? {
162
+ username: user.username,
163
+ _scopedToken: true,
164
+ authExpiresAt: user.authExpiresAt,
165
+ role: user.role && {
166
+ role: user.role.role,
167
+ id: user.role.id,
168
+ permission: stripRuntimePermissionState(user.role.permission),
169
+ },
170
+ }
171
+ : { username: user?.username };
129
172
  // Must be durably persisted before CONNACK is sent (getSession() resolving is what lets
130
173
  // mqtt.ts send CONNACK). Otherwise a client that connects and then disconnects abruptly
131
174
  // (no DISCONNECT packet) can race ahead of this write: SubscriptionsSession.disconnect()
@@ -155,7 +198,7 @@ type Acknowledgement = {
155
198
  };
156
199
 
157
200
  class SubscriptionsSession {
158
- listener: (message, subscription, timestamp, qos) => any;
201
+ listener: (topic, message, messageId, subscription, version?) => any;
159
202
  sessionId: any;
160
203
  user: any;
161
204
  request: any;
@@ -303,7 +346,16 @@ class SubscriptionsSession {
303
346
  let path = update.id;
304
347
  if (Array.isArray(path)) path = keyArrayToString(path);
305
348
  if (path == null) path = '';
306
- const result = await this.listener(resourcePath + '/' + path, update.value, messageId, subscriptionRequest);
349
+ // the version is forwarded so the delivery side can tell a store-sourced event (whose
350
+ // value is a fresh object per version) from an app-yielded one that may be a reused
351
+ // mutable envelope — only the former is safe to encode once and share
352
+ const result = await this.listener(
353
+ resourcePath + '/' + path,
354
+ update.value,
355
+ messageId,
356
+ subscriptionRequest,
357
+ update.version
358
+ );
307
359
  if (result === false) break;
308
360
  if (this.awaitingAcks?.size > AWAITING_ACKS_HIGH_WATER_MARK) {
309
361
  // slow it down if we are getting too far ahead in acks
@@ -372,7 +424,7 @@ class SubscriptionsSession {
372
424
  }
373
425
  return context;
374
426
  }
375
- setListener(listener: (message) => any) {
427
+ setListener(listener: (topic, message, messageId, subscription, version?) => any) {
376
428
  this.listener = listener;
377
429
  }
378
430
  disconnect(clientTerminated) {
@@ -382,8 +434,11 @@ class SubscriptionsSession {
382
434
  try {
383
435
  if (!clientTerminated) {
384
436
  const will = await getLastWill().get(this.sessionId);
385
- if (will) {
386
- await publishMessage(will, will.data, context);
437
+ if (will && !isWillFromExpiredScopedToken(will)) {
438
+ // A scoped will authorizes under its own embedded role, never the disconnecting
439
+ // session's user (which may be a later same-clientId reconnect).
440
+ const willContext = will.user?._scopedToken ? { ...context, user: will.user } : context;
441
+ await publishMessage(will, will.data, willContext);
387
442
  }
388
443
  }
389
444
  } finally {
@@ -413,10 +468,15 @@ async function publishMessage(message: any, data: any, context: any) {
413
468
  message = { ...message, data, async: true };
414
469
  context.authorize = true;
415
470
  const entry = resources.getMatch(topic, 'mqtt');
416
- if (!entry)
417
- throw new Error(
471
+ if (!entry) {
472
+ // Typed like addSubscription's identical miss, so a protocol layer can map it to a specific
473
+ // code rather than a generic failure.
474
+ const notFoundError: any = new Error(
418
475
  `Can not publish to topic ${topic} as it does not exist, no resource has been defined to handle this topic`
419
476
  );
477
+ notFoundError.statusCode = 404;
478
+ throw notFoundError;
479
+ }
420
480
  message.url = entry.relativeURL;
421
481
  const target = new RequestTarget(entry.relativeURL);
422
482
  if (entry.params) Object.assign(target, entry.params); // bind parameterised path segments (e.g. :id, *rest)
package/server/REST.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
- import { serialize, serializeMessage, getDeserializer } from '../server/serverHelpers/contentTypes.ts';
3
+ import {
4
+ serialize,
5
+ serializeMessage,
6
+ getDeserializer,
7
+ waitForStreamStartup,
8
+ discardSerializedStream,
9
+ } from '../server/serverHelpers/contentTypes.ts';
4
10
  import { addAnalyticsListener, recordAction, recordActionBinary } from '../resources/analytics/write.ts';
5
11
  import * as harperLogger from '../utility/logging/harper_logger.ts';
6
12
  import { ServerError, ClientError } from '../utility/errors/hdbError.ts';
@@ -8,13 +14,17 @@ import { Resources } from '../resources/Resources.ts';
8
14
  import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
9
15
  import { IterableEventQueue } from '../resources/IterableEventQueue.ts';
10
16
  import { transaction } from '../resources/transaction.ts';
11
- import { Headers, mergeHeaders } from '../server/serverHelpers/Headers.ts';
17
+ import { Headers, mergeHeaders, addVaryHeader } from '../server/serverHelpers/Headers.ts';
12
18
  import { generateJsonApi } from '../resources/openApi.ts';
13
19
  import { getConfigPath } from '../config/configUtils.ts';
14
20
  import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
15
21
  import { ASIDE_STAGING_DIR } from '../components/Application.ts';
16
22
  import { COMPONENT_PREPARATION_LOCK_DIR } from '../components/componentPreparationLock.ts';
17
23
  import { restartNeeded } from '../components/requestRestart.ts';
24
+ import {
25
+ assertNoDeferredCredentialRejection,
26
+ settleDeferredCredentialRejection,
27
+ } from '../security/deferredAuthentication.ts';
18
28
 
19
29
  import { Request } from '../server/serverHelpers/Request.ts';
20
30
  import { RequestTarget } from '../resources/RequestTarget';
@@ -117,6 +127,39 @@ async function findInactiveComponent(url: string): Promise<string | undefined> {
117
127
  }
118
128
  }
119
129
 
130
+ /**
131
+ * Emit RFC 7233-style pagination headers for a `Prefer: count=` request. Table.search returns the page
132
+ * with a `recordCount` (total matching records, or null when an exact scan hit its guardrail or an
133
+ * estimate was suppressed by an opaque filter). `Content-Range: items <start>-<end>/<total>` lets a
134
+ * client paginate — `<total>` is `*` when unavailable. `Preference-Applied` echoes the count mode the
135
+ * server actually applied (`exact` or `estimated`, after any per-mount downgrade), so a `.../*` total
136
+ * reads as "that mode was applied but the total is unavailable" rather than "no count was requested".
137
+ * All three headers are added to `Access-Control-Expose-Headers` so a browser can read them cross-origin
138
+ * (they aren't safelisted).
139
+ */
140
+ function setCountHeaders(headers: Headers, offset: number, mode: string, page: any) {
141
+ const total = page.recordCount;
142
+ const len = Array.isArray(page) ? page.length : 0;
143
+ const range = len > 0 ? `${offset}-${offset + len - 1}` : '*';
144
+ const totalStr = typeof total === 'number' ? String(total) : '*';
145
+ headers.set('Range-Unit', 'items');
146
+ headers.set('Content-Range', `items ${range}/${totalStr}`);
147
+ headers.set('Preference-Applied', `count=${mode}`);
148
+ // Append (don't overwrite) so a resource that already exposed its own headers keeps them. Compare
149
+ // case-insensitive comma tokens, not substrings, so an unrelated existing token (e.g.
150
+ // `X-Content-Range-Metadata`) doesn't suppress the real `Content-Range` token.
151
+ const exposed = headers.get('Access-Control-Expose-Headers');
152
+ const existing = new Set(
153
+ (Array.isArray(exposed) ? exposed.join(',') : exposed || '')
154
+ .split(',')
155
+ .map((token) => token.trim().toLowerCase())
156
+ .filter(Boolean)
157
+ );
158
+ for (const name of ['Content-Range', 'Range-Unit', 'Preference-Applied']) {
159
+ if (!existing.has(name.toLowerCase())) headers.append('Access-Control-Expose-Headers', name, true);
160
+ }
161
+ }
162
+
120
163
  async function http(request: Request, nextHandler, resources: Resources, httpOptions: any) {
121
164
  const headersObject = request.headers.asObject;
122
165
  const isSse = headersObject.accept === 'text/event-stream';
@@ -165,7 +208,32 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
165
208
 
166
209
  (target as any).async = true;
167
210
  resource = entry.Resource;
211
+ // Pagination total-count opt-in (no default): `Prefer: count=exact|estimated`. Table.search
212
+ // reads target.count to compute the total emitted as Content-Range below. Only honored on
213
+ // GET/HEAD reads: setting it for other methods would hand their `search()` a materialized array
214
+ // instead of the AsyncIterable they iterate (e.g. a collection DELETE at Table.ts).
215
+ const prefer = headersObject['prefer'];
216
+ if (prefer && (method === 'GET' || method === 'HEAD')) {
217
+ // Exact counting scans the full matched set, so it is opt-in per mount via
218
+ // `rest: { exactCount: true }` (default off); count=exact is otherwise served as a cheap
219
+ // estimate. Estimated is always available. Accept a string `"true"` too, since not every
220
+ // config source coerces to a boolean.
221
+ const exactEnabled = (httpOptions as any)?.exactCount === true || (httpOptions as any)?.exactCount === 'true';
222
+ for (const pref of parseHeaderValue(prefer as any)) {
223
+ // The header parser can hand back a non-string value (e.g. a bare `Prefer: count` with no
224
+ // `=`), so coerce before lower-casing rather than calling `.toLowerCase()` on a boolean.
225
+ const mode = String(pref?.value ?? '').toLowerCase();
226
+ if (pref?.name === 'count' && (mode === 'exact' || mode === 'estimated')) {
227
+ // A count=exact request on a mount that hasn't opted in is downgraded to estimated,
228
+ // signaled back to the client via Preference-Applied.
229
+ (target as any).count = mode === 'exact' && !exactEnabled ? 'estimated' : mode;
230
+ break;
231
+ }
232
+ }
233
+ }
168
234
  }
235
+ const settledCredentialRejection = settleDeferredCredentialRejection(request);
236
+ if (settledCredentialRejection) return settledCredentialRejection;
169
237
  if ((resource as any)?.isCaching) {
170
238
  const cacheControl = headersObject['cache-control'];
171
239
  if (cacheControl) {
@@ -286,7 +354,15 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
286
354
  headers.setIfNone('Last-Modified', new Date(lastModification).toUTCString());
287
355
  } else if (responseData.headers) {
288
356
  // if response is a Response object (or response-like envelope with headers), use it as the response
289
- return finalizeResponse(responseData, headers, status, request);
357
+ const response = finalizeResponse(responseData, headers, status, request);
358
+ if (request.method === 'HEAD') {
359
+ discardSerializedStream(response.body);
360
+ if (!(response instanceof Response)) response.body = undefined;
361
+ } else if (request.method === 'GET') {
362
+ const startup = waitForStreamStartup(response.body);
363
+ if (startup) await startup;
364
+ }
365
+ return response;
290
366
  } else if (isFinite(lastModification)) {
291
367
  etagFloat[0] = lastModification;
292
368
  // base64 encoding of the 64-bit float encoding of the date in ms (with quotes)
@@ -348,9 +424,30 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
348
424
  }
349
425
  // TODO: Handle 201 Created
350
426
  if (responseData !== undefined) {
427
+ if (
428
+ (target as any)?.count &&
429
+ (method === 'GET' || method === 'HEAD') &&
430
+ Array.isArray(responseData) &&
431
+ (responseData as any).recordCount !== undefined
432
+ ) {
433
+ // Array.isArray guards the single-record path: a record that happens to carry a
434
+ // `recordCount` attribute must not be mistaken for a count page (Table.search only ever
435
+ // returns the count as an array).
436
+ setCountHeaders(headers, (target as any).offset || 0, (target as any).count, responseData);
437
+ }
351
438
  responseObject.body = serialize(responseData, request, responseObject);
352
- if (method === 'HEAD') responseObject.body = undefined; // we want everything else to be the same as GET, but then omit the body
439
+ if (request.method === 'HEAD') {
440
+ discardSerializedStream(responseObject.body);
441
+ responseObject.body = undefined; // we want everything else to be the same as GET, but then omit the body
442
+ } else if (request.method === 'GET') {
443
+ const startup = waitForStreamStartup(responseObject.body);
444
+ if (startup) await startup;
445
+ }
353
446
  }
447
+ // A collection read's count headers vary by the request's `Prefer` value; serialize() just reset
448
+ // `Vary`, so declare it here (after serialization) — otherwise a shared cache could serve count
449
+ // headers to a request that didn't ask, or a cached non-count response to one that did.
450
+ if ((method === 'GET' || method === 'HEAD') && (target as any)?.isCollection) addVaryHeader(headers, 'Prefer');
354
451
  return responseObject;
355
452
  } catch (error) {
356
453
  error ??= new Error('Unknown error occurred');
@@ -389,6 +486,19 @@ async function http(request: Request, nextHandler, resources: Resources, httpOpt
389
486
  headers,
390
487
  body: undefined,
391
488
  };
489
+ // The error body is a different representation, so success representation and caching headers
490
+ // accumulated before the startup failure must not survive its serialization.
491
+ for (const header of [
492
+ 'Content-Encoding',
493
+ 'Cache-Control',
494
+ 'ETag',
495
+ 'Last-Modified',
496
+ 'Content-Range',
497
+ 'Range-Unit',
498
+ 'Preference-Applied',
499
+ 'Age',
500
+ ])
501
+ headers.delete(header);
392
502
  responseObject.body = serialize(problemDetail, request, responseObject);
393
503
  return responseObject;
394
504
  }
@@ -482,6 +592,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
482
592
  // TODO: Ideally we would like to have a 404 response before upgrading to WebSocket protocol, probably
483
593
  return ws.close(1011, `No resource was found to handle ${request.pathname}`);
484
594
  } else {
595
+ assertNoDeferredCredentialRejection(request);
485
596
  request.handlerPath = entry.path;
486
597
  recordAction(
487
598
  (action) => ({