@harperfast/harper 5.2.0-beta.4 → 5.2.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 (320) hide show
  1. package/bin/backup.ts +169 -0
  2. package/bin/cliOperations.ts +92 -73
  3. package/bin/copyDb.ts +21 -4
  4. package/bin/harper.ts +43 -55
  5. package/bin/help.ts +216 -0
  6. package/components/Application.ts +236 -46
  7. package/components/ApplicationScope.ts +26 -0
  8. package/components/EntryHandler.ts +410 -105
  9. package/components/RuntimeModuleTracker.ts +189 -0
  10. package/components/Scope.ts +120 -30
  11. package/components/componentLoader.ts +177 -25
  12. package/components/deployLifecycle.ts +119 -33
  13. package/components/mcp/toolRegistry.ts +10 -0
  14. package/components/mcp/tools/application.ts +12 -5
  15. package/components/mcp/tools/operations.ts +9 -0
  16. package/components/mcp/tools/schemas/operationDescriptions.ts +3 -0
  17. package/components/mcp/tools/schemas/operations.ts +9 -0
  18. package/components/operations.js +5 -6
  19. package/components/operationsValidation.js +32 -2
  20. package/components/scopeMount.ts +150 -0
  21. package/config/configUtils.ts +12 -9
  22. package/config-root.schema.json +14 -0
  23. package/dataLayer/backupManifest.ts +102 -0
  24. package/dataLayer/blobBackup.ts +286 -0
  25. package/dataLayer/harperBridge/ResourceBridge.ts +69 -5
  26. package/dataLayer/hdbInfoController.ts +8 -0
  27. package/dataLayer/restoreMarker.ts +276 -0
  28. package/dataLayer/rocksdbBackup.ts +1100 -0
  29. package/dataLayer/schemaDescribe.ts +2 -1
  30. package/dist/bin/backup.d.ts +9 -0
  31. package/dist/bin/backup.js +192 -0
  32. package/dist/bin/backup.js.map +1 -0
  33. package/dist/bin/cliOperations.d.ts +13 -0
  34. package/dist/bin/cliOperations.js +89 -70
  35. package/dist/bin/cliOperations.js.map +1 -1
  36. package/dist/bin/copyDb.js +13 -2
  37. package/dist/bin/copyDb.js.map +1 -1
  38. package/dist/bin/harper.d.ts +13 -0
  39. package/dist/bin/harper.js +45 -56
  40. package/dist/bin/harper.js.map +1 -1
  41. package/dist/bin/help.d.ts +8 -0
  42. package/dist/bin/help.js +192 -0
  43. package/dist/bin/help.js.map +1 -0
  44. package/dist/components/Application.d.ts +16 -1
  45. package/dist/components/Application.js +210 -38
  46. package/dist/components/Application.js.map +1 -1
  47. package/dist/components/ApplicationScope.d.ts +7 -0
  48. package/dist/components/ApplicationScope.js +22 -0
  49. package/dist/components/ApplicationScope.js.map +1 -1
  50. package/dist/components/EntryHandler.d.ts +4 -4
  51. package/dist/components/EntryHandler.js +386 -95
  52. package/dist/components/EntryHandler.js.map +1 -1
  53. package/dist/components/RuntimeModuleTracker.d.ts +11 -0
  54. package/dist/components/RuntimeModuleTracker.js +189 -0
  55. package/dist/components/RuntimeModuleTracker.js.map +1 -0
  56. package/dist/components/Scope.d.ts +38 -1
  57. package/dist/components/Scope.js +117 -28
  58. package/dist/components/Scope.js.map +1 -1
  59. package/dist/components/componentLoader.d.ts +2 -6
  60. package/dist/components/componentLoader.js +160 -18
  61. package/dist/components/componentLoader.js.map +1 -1
  62. package/dist/components/deployLifecycle.d.ts +6 -2
  63. package/dist/components/deployLifecycle.js +109 -31
  64. package/dist/components/deployLifecycle.js.map +1 -1
  65. package/dist/components/mcp/toolRegistry.d.ts +7 -0
  66. package/dist/components/mcp/toolRegistry.js +10 -0
  67. package/dist/components/mcp/toolRegistry.js.map +1 -1
  68. package/dist/components/mcp/tools/application.js +11 -5
  69. package/dist/components/mcp/tools/application.js.map +1 -1
  70. package/dist/components/mcp/tools/operations.js +9 -0
  71. package/dist/components/mcp/tools/operations.js.map +1 -1
  72. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -0
  73. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  74. package/dist/components/mcp/tools/schemas/operations.js +9 -0
  75. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  76. package/dist/components/operations.js +6 -6
  77. package/dist/components/operations.js.map +1 -1
  78. package/dist/components/operationsValidation.js +34 -2
  79. package/dist/components/operationsValidation.js.map +1 -1
  80. package/dist/components/scopeMount.d.ts +86 -0
  81. package/dist/components/scopeMount.js +131 -0
  82. package/dist/components/scopeMount.js.map +1 -0
  83. package/dist/config/configUtils.js +13 -9
  84. package/dist/config/configUtils.js.map +1 -1
  85. package/dist/dataLayer/backupManifest.d.ts +26 -0
  86. package/dist/dataLayer/backupManifest.js +97 -0
  87. package/dist/dataLayer/backupManifest.js.map +1 -0
  88. package/dist/dataLayer/blobBackup.d.ts +87 -0
  89. package/dist/dataLayer/blobBackup.js +282 -0
  90. package/dist/dataLayer/blobBackup.js.map +1 -0
  91. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +3 -0
  92. package/dist/dataLayer/harperBridge/ResourceBridge.js +45 -5
  93. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  94. package/dist/dataLayer/hdbInfoController.js +4 -0
  95. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  96. package/dist/dataLayer/restoreMarker.d.ts +122 -0
  97. package/dist/dataLayer/restoreMarker.js +261 -0
  98. package/dist/dataLayer/restoreMarker.js.map +1 -0
  99. package/dist/dataLayer/rocksdbBackup.d.ts +127 -0
  100. package/dist/dataLayer/rocksdbBackup.js +1039 -0
  101. package/dist/dataLayer/rocksdbBackup.js.map +1 -0
  102. package/dist/dataLayer/schemaDescribe.js +2 -1
  103. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  104. package/dist/resources/DatabaseTransaction.d.ts +55 -0
  105. package/dist/resources/DatabaseTransaction.js +282 -109
  106. package/dist/resources/DatabaseTransaction.js.map +1 -1
  107. package/dist/resources/ResourceInterface.d.ts +8 -5
  108. package/dist/resources/ResourceInterface.js.map +1 -1
  109. package/dist/resources/Resources.js +22 -4
  110. package/dist/resources/Resources.js.map +1 -1
  111. package/dist/resources/Table.d.ts +6 -5
  112. package/dist/resources/Table.js +114 -37
  113. package/dist/resources/Table.js.map +1 -1
  114. package/dist/resources/analytics/write.js +6 -6
  115. package/dist/resources/analytics/write.js.map +1 -1
  116. package/dist/resources/blob.d.ts +8 -1
  117. package/dist/resources/blob.js +31 -14
  118. package/dist/resources/blob.js.map +1 -1
  119. package/dist/resources/databases.d.ts +59 -1
  120. package/dist/resources/databases.js +386 -41
  121. package/dist/resources/databases.js.map +1 -1
  122. package/dist/resources/jsResource.d.ts +4 -26
  123. package/dist/resources/jsResource.js +5 -59
  124. package/dist/resources/jsResource.js.map +1 -1
  125. package/dist/resources/models/Models.d.ts +11 -1
  126. package/dist/resources/models/Models.js +10 -1
  127. package/dist/resources/models/Models.js.map +1 -1
  128. package/dist/resources/models/backendRegistry.d.ts +9 -0
  129. package/dist/resources/models/backendRegistry.js +10 -0
  130. package/dist/resources/models/backendRegistry.js.map +1 -1
  131. package/dist/resources/models/openaiStream.d.ts +16 -1
  132. package/dist/resources/models/openaiStream.js +113 -21
  133. package/dist/resources/models/openaiStream.js.map +1 -1
  134. package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
  135. package/dist/resources/models/v1/chatCompletions.js +115 -0
  136. package/dist/resources/models/v1/chatCompletions.js.map +1 -0
  137. package/dist/resources/models/v1/embeddings.d.ts +11 -0
  138. package/dist/resources/models/v1/embeddings.js +71 -0
  139. package/dist/resources/models/v1/embeddings.js.map +1 -0
  140. package/dist/resources/models/v1/errors.d.ts +54 -0
  141. package/dist/resources/models/v1/errors.js +130 -0
  142. package/dist/resources/models/v1/errors.js.map +1 -0
  143. package/dist/resources/models/v1/index.d.ts +36 -0
  144. package/dist/resources/models/v1/index.js +75 -0
  145. package/dist/resources/models/v1/index.js.map +1 -0
  146. package/dist/resources/models/v1/models.d.ts +26 -0
  147. package/dist/resources/models/v1/models.js +44 -0
  148. package/dist/resources/models/v1/models.js.map +1 -0
  149. package/dist/resources/models/v1/translation.d.ts +133 -0
  150. package/dist/resources/models/v1/translation.js +298 -0
  151. package/dist/resources/models/v1/translation.js.map +1 -0
  152. package/dist/resources/roles.d.ts +1 -1
  153. package/dist/resources/roles.js +54 -7
  154. package/dist/resources/roles.js.map +1 -1
  155. package/dist/resources/transaction.js +0 -3
  156. package/dist/resources/transaction.js.map +1 -1
  157. package/dist/security/jsLoader.js +84 -33
  158. package/dist/security/jsLoader.js.map +1 -1
  159. package/dist/security/role.js +4 -0
  160. package/dist/security/role.js.map +1 -1
  161. package/dist/security/superUserGuard.d.ts +7 -0
  162. package/dist/security/superUserGuard.js +23 -0
  163. package/dist/security/superUserGuard.js.map +1 -0
  164. package/dist/security/tokenAuthentication.d.ts +0 -1
  165. package/dist/security/tokenAuthentication.js +6 -2
  166. package/dist/security/tokenAuthentication.js.map +1 -1
  167. package/dist/security/user.d.ts +6 -1
  168. package/dist/security/user.js +23 -1
  169. package/dist/security/user.js.map +1 -1
  170. package/dist/server/REST.js +25 -9
  171. package/dist/server/REST.js.map +1 -1
  172. package/dist/server/fastifyRoutes.js +15 -1
  173. package/dist/server/fastifyRoutes.js.map +1 -1
  174. package/dist/server/http.d.ts +23 -2
  175. package/dist/server/http.js +99 -12
  176. package/dist/server/http.js.map +1 -1
  177. package/dist/server/itc/serverHandlers.js +7 -1
  178. package/dist/server/itc/serverHandlers.js.map +1 -1
  179. package/dist/server/jobs/jobProcess.js +20 -1
  180. package/dist/server/jobs/jobProcess.js.map +1 -1
  181. package/dist/server/jobs/jobRunner.js +10 -0
  182. package/dist/server/jobs/jobRunner.js.map +1 -1
  183. package/dist/server/jobs/jobs.js +11 -0
  184. package/dist/server/jobs/jobs.js.map +1 -1
  185. package/dist/server/middlewareChain.d.ts +10 -1
  186. package/dist/server/middlewareChain.js +81 -21
  187. package/dist/server/middlewareChain.js.map +1 -1
  188. package/dist/server/operationsServer.d.ts +0 -2
  189. package/dist/server/operationsServer.js.map +1 -1
  190. package/dist/server/serverHelpers/multipartParser.js +9 -0
  191. package/dist/server/serverHelpers/multipartParser.js.map +1 -1
  192. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
  193. package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
  194. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
  195. package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
  196. package/dist/server/serverHelpers/registeredOperations.js +14 -13
  197. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  198. package/dist/server/serverHelpers/serverHandlers.js +27 -7
  199. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  200. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
  201. package/dist/server/serverHelpers/serverUtilities.js +21 -7
  202. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  203. package/dist/server/static.js +68 -21
  204. package/dist/server/static.js.map +1 -1
  205. package/dist/server/storageReclamation.d.ts +17 -0
  206. package/dist/server/storageReclamation.js +90 -6
  207. package/dist/server/storageReclamation.js.map +1 -1
  208. package/dist/server/threads/socketRouter.js +20 -0
  209. package/dist/server/threads/socketRouter.js.map +1 -1
  210. package/dist/server/threads/threadServer.js +12 -0
  211. package/dist/server/threads/threadServer.js.map +1 -1
  212. package/dist/sqlEngine/diff/differential.js +7 -3
  213. package/dist/sqlEngine/diff/differential.js.map +1 -1
  214. package/dist/sqlTranslator/index.js +6 -1
  215. package/dist/sqlTranslator/index.js.map +1 -1
  216. package/dist/upgrade/upgradePrompt.d.ts +2 -2
  217. package/dist/upgrade/upgradePrompt.js +22 -3
  218. package/dist/upgrade/upgradePrompt.js.map +1 -1
  219. package/dist/utility/OperationFunctionCaller.js +25 -6
  220. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  221. package/dist/utility/errors/commonErrors.d.ts +1 -0
  222. package/dist/utility/errors/commonErrors.js +1 -0
  223. package/dist/utility/errors/commonErrors.js.map +1 -1
  224. package/dist/utility/hdbTerms.d.ts +13 -1
  225. package/dist/utility/hdbTerms.js +13 -1
  226. package/dist/utility/hdbTerms.js.map +1 -1
  227. package/dist/utility/install/installer.js +32 -0
  228. package/dist/utility/install/installer.js.map +1 -1
  229. package/dist/utility/logging/harper_logger.d.ts +47 -0
  230. package/dist/utility/logging/harper_logger.js +803 -38
  231. package/dist/utility/logging/harper_logger.js.map +1 -1
  232. package/dist/utility/operation_authorization.js +13 -3
  233. package/dist/utility/operation_authorization.js.map +1 -1
  234. package/npm-shrinkwrap.json +198 -193
  235. package/package.json +12 -7
  236. package/resources/DESIGN.md +2 -0
  237. package/resources/DatabaseTransaction.ts +310 -104
  238. package/resources/ResourceInterface.ts +8 -5
  239. package/resources/Resources.ts +22 -4
  240. package/resources/Table.ts +341 -263
  241. package/resources/analytics/write.ts +22 -20
  242. package/resources/blob.ts +31 -14
  243. package/resources/databases.ts +387 -46
  244. package/resources/jsResource.ts +5 -62
  245. package/resources/models/Models.ts +14 -1
  246. package/resources/models/backendRegistry.ts +10 -0
  247. package/resources/models/openaiStream.ts +131 -19
  248. package/resources/models/v1/chatCompletions.ts +128 -0
  249. package/resources/models/v1/embeddings.ts +70 -0
  250. package/resources/models/v1/errors.ts +141 -0
  251. package/resources/models/v1/index.ts +72 -0
  252. package/resources/models/v1/models.ts +53 -0
  253. package/resources/models/v1/translation.ts +362 -0
  254. package/resources/roles.ts +67 -7
  255. package/resources/transaction.ts +0 -3
  256. package/security/jsLoader.ts +84 -30
  257. package/security/role.ts +7 -0
  258. package/security/superUserGuard.ts +20 -0
  259. package/security/tokenAuthentication.ts +6 -3
  260. package/security/user.ts +26 -1
  261. package/server/DESIGN.md +61 -34
  262. package/server/REST.ts +25 -9
  263. package/server/fastifyRoutes.ts +20 -1
  264. package/server/http.ts +100 -13
  265. package/server/itc/serverHandlers.js +7 -1
  266. package/server/jobs/jobProcess.ts +18 -1
  267. package/server/jobs/jobRunner.ts +10 -0
  268. package/server/jobs/jobs.ts +11 -0
  269. package/server/middlewareChain.ts +79 -20
  270. package/server/operationsServer.ts +0 -2
  271. package/server/serverHelpers/multipartParser.ts +9 -0
  272. package/server/serverHelpers/operationAuthorizationState.ts +11 -0
  273. package/server/serverHelpers/registeredOperations.ts +19 -15
  274. package/server/serverHelpers/serverHandlers.js +28 -7
  275. package/server/serverHelpers/serverUtilities.ts +29 -7
  276. package/server/static.ts +87 -23
  277. package/server/storageReclamation.ts +104 -8
  278. package/server/threads/socketRouter.ts +20 -0
  279. package/server/threads/threadServer.js +11 -0
  280. package/sqlTranslator/index.ts +6 -1
  281. package/static/defaultConfig.yaml +3 -0
  282. package/studio/web/assets/{Chat-DK3GlWEb.js → Chat-aApwhRmz.js} +2 -2
  283. package/studio/web/assets/{Chat-DK3GlWEb.js.map → Chat-aApwhRmz.js.map} +1 -1
  284. package/studio/web/assets/{FloatingChat-fBcC1Ew_.js → FloatingChat-sC0H91n9.js} +4 -4
  285. package/studio/web/assets/{FloatingChat-fBcC1Ew_.js.map → FloatingChat-sC0H91n9.js.map} +1 -1
  286. package/studio/web/assets/{apiToken-DJo1nakA.js → apiToken-DZ8JhHOI.js} +2 -2
  287. package/studio/web/assets/{apiToken-DJo1nakA.js.map → apiToken-DZ8JhHOI.js.map} +1 -1
  288. package/studio/web/assets/{applications-BDfH8urd.js → applications-9SEVLIO5.js} +2 -2
  289. package/studio/web/assets/{applications-BDfH8urd.js.map → applications-9SEVLIO5.js.map} +1 -1
  290. package/studio/web/assets/{index-BHo3c2Gk.js → index-Dy3uDGXb.js} +6 -6
  291. package/studio/web/assets/index-Dy3uDGXb.js.map +1 -0
  292. package/studio/web/assets/{index.lazy-BTo0y6UM.js → index.lazy-CN1zq4I4.js} +4 -4
  293. package/studio/web/assets/{index.lazy-BTo0y6UM.js.map → index.lazy-CN1zq4I4.js.map} +1 -1
  294. package/studio/web/assets/{notifications-CMxvWNnz.js → notifications-BFAF07xr.js} +2 -2
  295. package/studio/web/assets/{notifications-CMxvWNnz.js.map → notifications-BFAF07xr.js.map} +1 -1
  296. package/studio/web/assets/{notifications-D3GoB26g.js → notifications-CKlYVvVN.js} +2 -2
  297. package/studio/web/assets/{notifications-D3GoB26g.js.map → notifications-CKlYVvVN.js.map} +1 -1
  298. package/studio/web/assets/{profile-Doj5FVDE.js → profile-A1zhEdFG.js} +2 -2
  299. package/studio/web/assets/{profile-Doj5FVDE.js.map → profile-A1zhEdFG.js.map} +1 -1
  300. package/studio/web/assets/{setComponentFile-yinsqJy0.js → setComponentFile-SEtBt_GV.js} +2 -2
  301. package/studio/web/assets/{setComponentFile-yinsqJy0.js.map → setComponentFile-SEtBt_GV.js.map} +1 -1
  302. package/studio/web/assets/{setup-DJwR0BHd.js → setup-BisINqdH.js} +2 -2
  303. package/studio/web/assets/{setup-DJwR0BHd.js.map → setup-BisINqdH.js.map} +1 -1
  304. package/studio/web/assets/{status-Br_AbsJs.js → status-BuQoCc7l.js} +2 -2
  305. package/studio/web/assets/{status-Br_AbsJs.js.map → status-BuQoCc7l.js.map} +1 -1
  306. package/studio/web/assets/{swagger-ui-react-02XH5sVf.js → swagger-ui-react-CAi_s1PC.js} +2 -2
  307. package/studio/web/assets/{swagger-ui-react-02XH5sVf.js.map → swagger-ui-react-CAi_s1PC.js.map} +1 -1
  308. package/studio/web/assets/{tsMode-D1DMKY7h.js → tsMode-DT74tlkM.js} +2 -2
  309. package/studio/web/assets/{tsMode-D1DMKY7h.js.map → tsMode-DT74tlkM.js.map} +1 -1
  310. package/studio/web/assets/{useEntityRestURL-DtRblRgw.js → useEntityRestURL-CGRGc1n7.js} +2 -2
  311. package/studio/web/assets/{useEntityRestURL-DtRblRgw.js.map → useEntityRestURL-CGRGc1n7.js.map} +1 -1
  312. package/studio/web/index.html +1 -1
  313. package/upgrade/upgradePrompt.ts +22 -3
  314. package/utility/OperationFunctionCaller.ts +24 -3
  315. package/utility/errors/commonErrors.ts +2 -0
  316. package/utility/hdbTerms.ts +13 -1
  317. package/utility/install/installer.ts +37 -0
  318. package/utility/logging/harper_logger.ts +786 -41
  319. package/utility/operation_authorization.ts +31 -3
  320. package/studio/web/assets/index-BHo3c2Gk.js.map +0 -1
@@ -6,7 +6,7 @@ import { models as harperModelsSingleton } from '../resources/models/Models.ts';
6
6
  import { defineTable, types } from '../resources/defineTable.ts';
7
7
  import { defineResource, t, schemaOf, projectTableFragment } from '../resources/defineResource.ts';
8
8
  import { readFile } from 'node:fs/promises';
9
- import { dirname, isAbsolute } from 'node:path';
9
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
10
10
  import { pathToFileURL, fileURLToPath } from 'node:url';
11
11
  import { SourceTextModule, SyntheticModule, createContext, runInContext, runInThisContext } from 'node:vm';
12
12
  import { ApplicationScope } from '../components/ApplicationScope.ts';
@@ -28,7 +28,6 @@ import {
28
28
  statSync,
29
29
  realpathSync,
30
30
  } from 'node:fs';
31
- import { join } from 'node:path';
32
31
  import { EventEmitter } from 'node:events';
33
32
  import { whenComponentsLoaded } from '../server/threads/threadServer.js';
34
33
 
@@ -105,6 +104,7 @@ async function importScoped(moduleUrl: string, scope?: ApplicationScope) {
105
104
  return await loadModuleWithVM(moduleUrl, scope, true);
106
105
  }
107
106
  }
107
+ if (scope) scope.markNativeRuntime?.();
108
108
  // important! we need to await the import, otherwise the error will not be caught
109
109
  return await import(moduleUrl);
110
110
  } catch (err) {
@@ -178,7 +178,10 @@ function walkExportsConditions(entry: unknown, conditions: readonly string[]): s
178
178
  * ERR_PACKAGE_PATH_NOT_EXPORTED for pure-ESM packages (exports map with only "import"
179
179
  * conditions and no "require").
180
180
  */
181
- function resolveESMPackageExports(specifier: string, fromDir: string): string | null {
181
+ function resolveESMPackageExports(
182
+ specifier: string,
183
+ fromDir: string
184
+ ): { resolvedUrl: string; packageJsonUrl: string; packageJsonSource: Buffer } | null {
182
185
  const isScoped = specifier.startsWith('@');
183
186
  const parts = specifier.split('/');
184
187
  const packageName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0];
@@ -189,8 +192,11 @@ function resolveESMPackageExports(specifier: string, fromDir: string): string |
189
192
  while (true) {
190
193
  const pkgRoot = join(dir, 'node_modules', packageName);
191
194
  let pkgJson: any;
195
+ let packageJsonSource: Buffer;
196
+ const packageJsonPath = join(pkgRoot, 'package.json');
192
197
  try {
193
- pkgJson = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf-8'));
198
+ packageJsonSource = readFileSync(packageJsonPath);
199
+ pkgJson = JSON.parse(packageJsonSource.toString());
194
200
  } catch {
195
201
  const parent = dirname(dir);
196
202
  if (parent === dir) return null;
@@ -220,10 +226,25 @@ function resolveESMPackageExports(specifier: string, fromDir: string): string |
220
226
  const relative = walkExportsConditions(entry, ['import', 'node', 'default']);
221
227
  if (!relative) return null;
222
228
 
223
- return pathToFileURL(join(pkgRoot, relative)).toString();
229
+ return {
230
+ resolvedUrl: pathToFileURL(realpathSync(join(pkgRoot, relative))).toString(),
231
+ packageJsonUrl: pathToFileURL(realpathSync(packageJsonPath)).toString(),
232
+ packageJsonSource,
233
+ };
224
234
  }
225
235
  }
226
236
 
237
+ function normalizeImportedModule(importedModule: any): any {
238
+ const cjsModule = importedModule['module.exports'];
239
+ if (cjsModule) {
240
+ importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
241
+ }
242
+ if (!importedModule.default) {
243
+ importedModule = { default: importedModule, ...importedModule };
244
+ }
245
+ return importedModule;
246
+ }
247
+
227
248
  /**
228
249
  * Load a module using Node's vm.Module API with optional sandboxing
229
250
  * @param moduleUrl - The URL of the module to load
@@ -286,11 +307,18 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
286
307
  try {
287
308
  const resolved = createRequire(resolveReferrer).resolve(specifier);
288
309
  if (isAbsolute(resolved)) {
289
- return pathToFileURL(resolved).toString();
310
+ const resolvedUrl = pathToFileURL(resolved).toString();
311
+ scope.recordModuleResolution?.(specifier, resolveReferrer, resolvedUrl);
312
+ return resolvedUrl;
290
313
  }
291
314
  return resolved;
292
315
  } catch (err) {
293
- if ((err as any)?.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
316
+ const errorCode = (err as { code?: string })?.code;
317
+ const isBarePackage = !specifier.startsWith('.') && !isAbsolute(specifier) && !specifier.includes(':');
318
+ if (
319
+ isBarePackage &&
320
+ (errorCode === 'ERR_PACKAGE_PATH_NOT_EXPORTED' || (process.versions.bun && errorCode === 'MODULE_NOT_FOUND'))
321
+ ) {
294
322
  // Pure-ESM package: CJS resolver cannot match an exports map that only has
295
323
  // "import" conditions (no "require"). Resolve the entry file by walking
296
324
  // the filesystem and evaluating the exports map with ESM import conditions.
@@ -298,7 +326,10 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
298
326
  ? dirname(fileURLToPath(resolveReferrer))
299
327
  : dirname(resolveReferrer);
300
328
  const esmResolved = resolveESMPackageExports(specifier, referrerDir);
301
- if (esmResolved) return esmResolved;
329
+ if (esmResolved) {
330
+ scope.recordLoadedModule?.(esmResolved.packageJsonUrl, esmResolved.packageJsonSource);
331
+ return esmResolved.resolvedUrl;
332
+ }
302
333
  }
303
334
  throw err;
304
335
  }
@@ -336,8 +367,15 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
336
367
  return getHarperExports(scope);
337
368
  }
338
369
  if (resolvedUrl.startsWith('file://')) {
339
- const source = readFileSync(new URL(resolvedUrl), { encoding: 'utf-8' });
340
- return loadCJS(resolvedUrl, source).exports;
370
+ if (resolvedUrl.endsWith('.node')) {
371
+ checkAllowedModulePath(resolvedUrl, scope.allowedPath);
372
+ const nativeModule = require(fileURLToPath(resolvedUrl));
373
+ scope.markNativeRuntime?.();
374
+ return nativeModule;
375
+ }
376
+ const contents = readFileSync(new URL(resolvedUrl));
377
+ scope.recordLoadedModule?.(resolvedUrl, contents);
378
+ return loadCJS(resolvedUrl, contents.toString('utf-8')).exports;
341
379
  }
342
380
  return require(resolvedUrl);
343
381
  };
@@ -456,11 +494,11 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
456
494
  if (specifier.startsWith('.')) {
457
495
  return true;
458
496
  }
459
-
460
497
  // Check the dependencyLoader for definitive settings, if it is native we always use native loader
461
498
  if (scope.dependencyLoader === 'native') {
462
499
  return false;
463
500
  }
501
+ if (isApplicationLocalModule(resolvedUrl)) return true;
464
502
 
465
503
  // If it is set to always use the app module loader
466
504
  if (scope.dependencyLoader === 'app') {
@@ -474,6 +512,16 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
474
512
  return false;
475
513
  }
476
514
 
515
+ function isApplicationLocalModule(url: string): boolean {
516
+ if (!scope.runtimeRoot || !url.startsWith('file://') || url.includes('/node_modules/')) return false;
517
+ const modulePath = fileURLToPath(url);
518
+ const relativePath = relative(resolve(scope.runtimeRoot), modulePath);
519
+ return (
520
+ relativePath === '' ||
521
+ (!relativePath.startsWith(`..${sep}`) && relativePath !== '..' && !isAbsolute(relativePath))
522
+ );
523
+ }
524
+
477
525
  /**
478
526
  * Linker function for module resolution during instantiation.
479
527
  * This is synchronous because Node's module.link() requires the linker
@@ -544,22 +592,6 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
544
592
  );
545
593
  }
546
594
 
547
- /**
548
- * Normalize imported module to ensure it has proper exports including default
549
- */
550
- function normalizeImportedModule(importedModule: any): any {
551
- const cjsModule = importedModule['module.exports'];
552
- if (cjsModule) {
553
- // back-compat import
554
- importedModule = importedModule.default ? { default: importedModule.default, ...cjsModule } : cjsModule;
555
- }
556
- // Ensure there's a default export for ESM imports that expect it
557
- if (!importedModule.default) {
558
- importedModule = { default: importedModule, ...importedModule };
559
- }
560
- return importedModule;
561
- }
562
-
563
595
  /**
564
596
  * Create a SourceTextModule or SyntheticModule from source code
565
597
  */
@@ -638,8 +670,14 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
638
670
 
639
671
  if (url.startsWith('file://') && usePrivateGlobal) {
640
672
  checkAllowedModulePath(url, scope.allowedPath);
641
- const source = readFileSync(new URL(url), { encoding: 'utf-8' });
642
- return createModuleFromSource(url, source, usePrivateGlobal);
673
+ if (url.endsWith('.node')) {
674
+ const nativeModule = createRequire(url)(fileURLToPath(url));
675
+ scope.markNativeRuntime?.();
676
+ return createSyntheticModule(url, normalizeImportedModule(nativeModule));
677
+ }
678
+ const contents = readFileSync(new URL(url));
679
+ scope.recordLoadedModule?.(url, contents);
680
+ return createModuleFromSource(url, contents.toString('utf-8'), usePrivateGlobal);
643
681
  }
644
682
 
645
683
  // For Node.js built-in modules (node:) and npm packages without application loader for dependency
@@ -647,6 +685,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
647
685
  if (replacedModule) {
648
686
  return createSyntheticModule(url, normalizeImportedModule(replacedModule));
649
687
  }
688
+ if (isApplicationLocalModule(url)) scope.markNativeRuntime?.();
650
689
  return import(url).then((importedModule) => createSyntheticModule(url, normalizeImportedModule(importedModule)));
651
690
  }
652
691
  // Load the entry module
@@ -679,6 +718,7 @@ async function getCompartment(scope: ApplicationScope, globals) {
679
718
  const resolved = createRequire(moduleReferrer).resolve(moduleSpecifier);
680
719
  if (isAbsolute(resolved)) {
681
720
  const resolvedURL = pathToFileURL(resolved).toString();
721
+ scope.recordModuleResolution?.(moduleSpecifier, moduleReferrer, resolvedURL);
682
722
  return resolvedURL;
683
723
  }
684
724
  return moduleSpecifier;
@@ -693,8 +733,22 @@ async function getCompartment(scope: ApplicationScope, globals) {
693
733
  Object.assign(exports, harperExports);
694
734
  },
695
735
  };
736
+ } else if (moduleSpecifier.startsWith('file:') && moduleSpecifier.endsWith('.node')) {
737
+ checkAllowedModulePath(moduleSpecifier, scope.allowedPath);
738
+ const nativeModule = createRequire(moduleSpecifier)(fileURLToPath(moduleSpecifier));
739
+ scope.markNativeRuntime?.();
740
+ const moduleExports = normalizeImportedModule(nativeModule);
741
+ return {
742
+ imports: [],
743
+ exports: Object.keys(moduleExports),
744
+ execute(exports) {
745
+ Object.assign(exports, moduleExports);
746
+ },
747
+ };
696
748
  } else if (moduleSpecifier.startsWith('file:') && !moduleSpecifier.includes('node_modules')) {
697
- let moduleText = await readFile(new URL(moduleSpecifier), { encoding: 'utf-8' });
749
+ const moduleContents = await readFile(new URL(moduleSpecifier));
750
+ scope.recordLoadedModule?.(moduleSpecifier, moduleContents);
751
+ let moduleText = moduleContents.toString('utf-8');
698
752
  // Handle JSON files in compartment mode the same way as in VM mode
699
753
  if (moduleSpecifier.endsWith('.json')) {
700
754
  const jsonData = parseJsonModule(moduleText, moduleSpecifier);
package/security/role.ts CHANGED
@@ -16,6 +16,7 @@ import SearchObject from '../dataLayer/SearchObject.ts';
16
16
  import SearchByHashObject from '../dataLayer/SearchByHashObject.ts';
17
17
  import { handleHDBError } from '../utility/errors/hdbError.ts';
18
18
  import { HDB_ERROR_MSGS, HTTP_STATUS_CODES } from '../utility/errors/commonErrors.ts';
19
+ import { assertActiveSuperUserRemains } from './user.ts';
19
20
 
20
21
  import { UserEventMsg } from '../server/threads/itc.js';
21
22
 
@@ -104,6 +105,12 @@ export async function alterRole(role: any) {
104
105
 
105
106
  role = scrubRoleDetails(role);
106
107
 
108
+ if (role.permission) {
109
+ await assertActiveSuperUserRemains((user) =>
110
+ user.role?.id === role.id ? { ...user, role: { ...user.role, permission: role.permission } } : user
111
+ );
112
+ }
113
+
107
114
  let updateObject = {
108
115
  operation: 'update',
109
116
  schema: 'system',
@@ -0,0 +1,20 @@
1
+ import type { User } from './user.ts';
2
+
3
+ function isActiveSuperUser(user?: User): boolean {
4
+ return Boolean(user?.active && user.role?.permission?.super_user);
5
+ }
6
+
7
+ /**
8
+ * Whether an active super_user still exists once `simulate` is applied to every user; `simulate`
9
+ * returns undefined for a user the change removes. True when none exists beforehand — there is no
10
+ * last one to protect, and refusing would block the repair that restores one.
11
+ */
12
+ export function activeSuperUserRemains(users: Iterable<User>, simulate: (user: User) => User | undefined): boolean {
13
+ let present = false;
14
+ let remains = false;
15
+ for (const user of users) {
16
+ if (isActiveSuperUser(user)) present = true;
17
+ if (isActiveSuperUser(simulate(user))) remains = true;
18
+ }
19
+ return remains || !present;
20
+ }
@@ -18,6 +18,7 @@ import { findAndValidateUser, type User } from './user.ts';
18
18
  import { update } from '../dataLayer/insert.ts';
19
19
  import UpdateObject from '../dataLayer/UpdateObject.ts';
20
20
  import * as signalling from '../utility/signalling.ts';
21
+ import { isOperationAuthorizationBypassed } from '../server/serverHelpers/operationAuthorizationState.ts';
21
22
  import { UserEventMsg } from '../server/threads/itc.js';
22
23
  import * as env from '../utility/environment/environmentManager.ts';
23
24
  env.initSync();
@@ -52,7 +53,6 @@ interface AuthObject {
52
53
  password?: string;
53
54
  role?: string;
54
55
  expires_in?: string | number;
55
- bypass_auth?: boolean;
56
56
  hdb_user?: User;
57
57
  // 'login' mints a single short-lived, login-scoped token instead of an operation/refresh pair —
58
58
  // see TOKEN_TYPE.LOGIN.
@@ -135,8 +135,11 @@ export async function createTokens(authObj: AuthObject): Promise<JWTTokens> {
135
135
 
136
136
  let user: any;
137
137
  try {
138
- // bypassAuth will be set to true if this is called from a component
139
- let validatePassword: boolean = authObj.bypass_auth !== true;
138
+ // Trusted bypass is dispatch/async-context state (set by a component calling
139
+ // server.operation(..., false)), never a body field — authObj.bypass_auth is
140
+ // caller-controlled and would let anyone mint tokens for an arbitrary username
141
+ // without a password (see operationAuthorizationState.ts).
142
+ let validatePassword: boolean = !isOperationAuthorizationBypassed();
140
143
  if (!authObj.username && !authObj.password) {
141
144
  // if the username and password are not provided, use the hdb_user making the request.
142
145
  authObj.username = authObj.hdb_user?.username;
package/security/user.ts CHANGED
@@ -9,6 +9,7 @@ const ACTIVE_BOOLEAN = 'active must be true or false';
9
9
  export {
10
10
  addUser,
11
11
  alterUser,
12
+ assertActiveSuperUserRemains,
12
13
  dropUser,
13
14
  getSuperUser,
14
15
  userInfo,
@@ -105,6 +106,7 @@ import * as password from '../utility/password.ts';
105
106
  import { server } from '../server/Server.ts';
106
107
  import * as terms from '../utility/hdbTerms.ts';
107
108
  import { expandOperationsPerms } from '../utility/operationPermissions.ts';
109
+ import { activeSuperUserRemains } from './superUserGuard.ts';
108
110
 
109
111
  server.getUser = (username: string, password?: string | null): Promise<User> => {
110
112
  return findAndValidateUser(username, password, password != null);
@@ -202,6 +204,7 @@ async function alterUser(jsonMessage) {
202
204
  throw new Error(EMPTY_ROLE);
203
205
  }
204
206
  // Invalid roles will be found in the role search
207
+ let nextRole;
205
208
  if (cleanUser.role) {
206
209
  const roleData = await search.searchByValue({
207
210
  schema: 'system',
@@ -217,7 +220,16 @@ async function alterUser(jsonMessage) {
217
220
  if (roleData.length > 1)
218
221
  throw new ClientError(HDB_ERROR_MSGS.DUP_ROLES_FOUND(cleanUser.role), HTTP_STATUS_CODES.CONFLICT);
219
222
 
220
- cleanUser.role = roleData[0].id;
223
+ nextRole = roleData[0];
224
+ cleanUser.role = nextRole.id;
225
+ }
226
+
227
+ if (nextRole !== undefined || cleanUser.active !== undefined) {
228
+ await assertActiveSuperUserRemains((user) =>
229
+ user.username === cleanUser.username
230
+ ? { ...user, role: nextRole ?? user.role, active: cleanUser.active ?? user.active }
231
+ : user
232
+ );
221
233
  }
222
234
 
223
235
  const updateResponse = await insert.update({
@@ -240,6 +252,8 @@ async function dropUser(user: User | any): Promise<string> {
240
252
  if (usersWithRolesMap.get(user.username) === undefined)
241
253
  throw new ClientError(HDB_ERROR_MSGS.USER_NOT_EXIST(user.username), HTTP_STATUS_CODES.NOT_FOUND);
242
254
 
255
+ await assertActiveSuperUserRemains((existing) => (existing.username === user.username ? undefined : existing));
256
+
243
257
  const deleteResponse = await promiseDelete({
244
258
  table: 'hdb_user',
245
259
  schema: 'system',
@@ -381,6 +395,17 @@ async function getUsersWithRolesCache() {
381
395
  return usersWithRolesMap;
382
396
  }
383
397
 
398
+ /**
399
+ * `simulate` maps each user to what the pending change would make it; undefined means removed.
400
+ * Local view only — `system` is replicated, so a lagging node can approve what another rejects.
401
+ */
402
+ async function assertActiveSuperUserRemains(simulate: (user: User) => User | undefined): Promise<void> {
403
+ const users = await getUsersWithRolesCache();
404
+ if (!users) return;
405
+ if (activeSuperUserRemains(users.values(), simulate)) return;
406
+ throw new ClientError(HDB_ERROR_MSGS.LAST_SUPER_USER, HTTP_STATUS_CODES.CONFLICT);
407
+ }
408
+
384
409
  /**
385
410
  * iterates global.hdb_users to find and validate the username & optionally the password as well as if they are active.
386
411
  * @param {string} username
package/server/DESIGN.md CHANGED
@@ -43,20 +43,20 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
43
43
 
44
44
  ### Helpers
45
45
 
46
- | File | Purpose |
47
- | ------------------------------------------ | ------------------------------------------------------------------------------- |
48
- | `serverHelpers/Request.ts` | Wraps `IncomingMessage` with Harper-specific fields (user, response, headers). |
49
- | `serverHelpers/Headers.ts` | Header mutation/merge utilities. |
50
- | `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getDeserializer`. |
51
- | `serverHelpers/serverUtilities.ts` | `OperationDefinition` and shared helpers. |
52
- | `serverHelpers/OperationFunctionObject.ts` | Wraps an operation handler with metadata. |
53
- | `serverHelpers/JSONStream.ts` | Streaming JSON output for large responses. |
54
- | `nodeName.ts` | Resolves this node's name (config → hostname). |
55
- | `static.ts` | Static file serving for component-bundled assets. |
56
- | `throttle.ts` | Per-IP / per-user request throttling. |
57
- | `storageReclamation.ts` | Disk-pressure signals to downstream consumers. |
58
- | `serverRegistry.ts` | Trivial registry export. |
59
- | `status/` | Server status reporting (cluster status, per-port info). |
46
+ | File | Purpose |
47
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
48
+ | `serverHelpers/Request.ts` | Wraps `IncomingMessage` with Harper-specific fields (user, response, headers). |
49
+ | `serverHelpers/Headers.ts` | Header mutation/merge utilities. |
50
+ | `serverHelpers/contentTypes.ts` | (de)serialization registry; `serialize`, `serializeMessage`, `getDeserializer`. |
51
+ | `serverHelpers/serverUtilities.ts` | `OperationDefinition` and shared helpers. |
52
+ | `serverHelpers/OperationFunctionObject.ts` | Wraps an operation handler with metadata. |
53
+ | `serverHelpers/JSONStream.ts` | Streaming JSON output for large responses. |
54
+ | `nodeName.ts` | Resolves this node's name (config → hostname). |
55
+ | `static.ts` | Static file serving for component-bundled assets. |
56
+ | `throttle.ts` | Per-IP / per-user request throttling. |
57
+ | `storageReclamation.ts` | Disk-pressure signals to downstream consumers; `getStorageSpaceStats()` is the shared quota-aware (falls back to `statfs`) source of available/free/size storage numbers — used by `Table.getStorageStats()` (#1976). NOT used for blob storage path weighting (`resources/blob.ts`): quota-status.json is a single instance-wide figure, so it can't distinguish between multiple `STORAGE_BLOBPATHS` disks — that still needs raw per-path `statfs`. |
58
+ | `serverRegistry.ts` | Trivial registry export. |
59
+ | `status/` | Server status reporting (cluster status, per-port info). |
60
60
 
61
61
  ### Threads
62
62
 
@@ -88,26 +88,26 @@ Single-instance background tasks pick their thread by what state they touch:
88
88
 
89
89
  Every entry is a top-level function or named const. Jump via go-to-symbol or `grep -n 'function <name>' server/http.ts`.
90
90
 
91
- | Symbol | What it does |
92
- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
- | `registerUdsCleanupPaths`, `cleanupUdsFiles`, `writeUdsMetadata`, `cleanupSocketsDirectory` | UDS socket / metadata file lifecycle. |
94
- | `handleApplication(scope)` | Component entry point — captures `httpOptions` for the scope. |
95
- | `getHttpOptions()` | Returns the current scope's `HttpOptions`. |
96
- | `deliverSocket()` | IPC-delivered socket handoff from `socketRouter`. |
97
- | `proxyRequest()` | Cross-port request routing. |
98
- | `registerServer()` | Records a server for a port in the `SERVERS` map. |
99
- | `getPorts()` | Resolves listener options → list of `{port, secure}`. |
100
- | `httpServer()` | Main listener registration entry point. |
101
- | `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. |
102
- | `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`. |
103
- | `unhandled()` | Terminal 404 handler. |
104
- | `onRequest()` | Thin alias of `httpServer({requestOnly: true})`. |
105
- | `onUpgrade()` / `upgradeListeners` (const) | Register HTTP upgrade listener; underlying list. |
106
- | `onWebSocket()` / `websocketListeners` (const) | Register WebSocket listener; auto-adds default upgrade handler the first time it runs for a port. Underlying list of registrations. |
107
- | `enableProxyProtocol()` | PROXY v1/v2 stripping on UDS mirrors (Node 24+-compatible workaround). Decoding lives in `serverHelpers/proxyProtocol.ts`; v2 TLVs forward the client source address plus the connection's TLS facts a fronting proxy (symphony) observed — ALPN, SNI authority, TLS version/cipher, JA3/JA4 fingerprints, and the mTLS client cert chain. These are surfaced on `request.connectionInfo` (see below); the verified cert chain is additionally exposed with TLSSocket semantics (`authorized`, `getPeerCertificate()`) so HTTP/MQTT mTLS auth works unchanged, and the SSL TLV lets `request.protocol` report `https` on the plaintext UDS mirror. A peer that stalls mid-header is destroyed after `prehandoffTimeout` (default 10s), matching `withProxyProtocol`'s guard on the raw-socket path. |
108
- | `defaultNotFound()` | Default 404 response. |
109
- | `logRequest()` | Per-request access log line. |
110
- | `getRequestId()` | Generates the per-request correlation ID. |
91
+ | Symbol | What it does |
92
+ | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `registerUdsCleanupPaths`, `recordUdsBindSuccess`, `cleanupUdsFiles`, `markUdsBindFailed`, `writeUdsMetadata`, `cleanupSocketsDirectory` | UDS socket / metadata file lifecycle. Ownership-aware: `recordUdsBindSuccess` captures the inode a worker's own bind confirmed; `cleanupUdsFiles`/`markUdsBindFailed` only unlink a path when the inode on disk still matches, so an overlapping restart's outgoing worker can never delete the replacement that already rebound the same path (see restartWorkers() in manageThreads.js). `cleanupSocketsDirectory` is the separate crash-path sweep, run once from `socketRouter.ts`'s `startHTTPThreads` on main-thread startup, before any worker can bind. |
94
+ | `handleApplication(scope)` | Component entry point — captures `httpOptions` for the scope. |
95
+ | `getHttpOptions()` | Returns the current scope's `HttpOptions`. |
96
+ | `deliverSocket()` | IPC-delivered socket handoff from `socketRouter`. |
97
+ | `proxyRequest()` | Cross-port request routing. |
98
+ | `registerServer()` | Records a server for a port in the `SERVERS` map. |
99
+ | `getPorts()` | Resolves listener options → list of `{port, secure}`. |
100
+ | `httpServer()` | Main listener registration entry point. |
101
+ | `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. |
102
+ | `makeCallbackChain()` | Builds the per-port handler chain via `middlewareChain.topoSort`. |
103
+ | `unhandled()` | Terminal 404 handler. |
104
+ | `onRequest()` | Thin alias of `httpServer({requestOnly: true})`. |
105
+ | `onUpgrade()` / `upgradeListeners` (const) | Register HTTP upgrade listener; underlying list. |
106
+ | `onWebSocket()` / `websocketListeners` (const) | Register WebSocket listener; auto-adds default upgrade handler the first time it runs for a port. Underlying list of registrations. |
107
+ | `enableProxyProtocol()` | PROXY v1/v2 stripping on UDS mirrors (Node 24+-compatible workaround). Decoding lives in `serverHelpers/proxyProtocol.ts`; v2 TLVs forward the client source address plus the connection's TLS facts a fronting proxy (symphony) observed — ALPN, SNI authority, TLS version/cipher, JA3/JA4 fingerprints, and the mTLS client cert chain. These are surfaced on `request.connectionInfo` (see below); the verified cert chain is additionally exposed with TLSSocket semantics (`authorized`, `getPeerCertificate()`) so HTTP/MQTT mTLS auth works unchanged, and the SSL TLV lets `request.protocol` report `https` on the plaintext UDS mirror. A peer that stalls mid-header is destroyed after `prehandoffTimeout` (default 10s), matching `withProxyProtocol`'s guard on the raw-socket path. |
108
+ | `defaultNotFound()` | Default 404 response. |
109
+ | `logRequest()` | Per-request access log line. |
110
+ | `getRequestId()` | Generates the per-request correlation ID. |
111
111
 
112
112
  ### Middleware ordering (`before` / `after`)
113
113
 
@@ -119,8 +119,35 @@ Components register listeners with optional `before: 'name'` / `after: 'name'` o
119
119
 
120
120
  The default WebSocket upgrade handler is registered automatically inside `onWebSocket()` the first time it runs for a given port.
121
121
 
122
+ ### Application mounts (`host` / `urlPath` in the root config)
123
+
124
+ 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).
125
+
126
+ **The mount is applied at exactly one place: `Scope.routeFor()`, used by the `scope.server` proxy.** Do not push it anywhere else. In particular, do not compose it into the plugin config the entry pipeline reads: `entry.urlPath` is what `graphqlSchema` and `jsResource` derive **resource** paths from, and the router strips the mount _before_ REST resolves them. Composing it there registers a table at `/v1/Thing` while REST looks up `Thing`, and every mounted REST route 404s. A single-app `static` test will not catch it — static de-prefixes its own map keys, so it stays self-consistent either way.
127
+
128
+ Consequences worth knowing:
129
+
130
+ - Everything inside an application addresses itself **mount-relative**. Only two things need the absolute path: code that emits a URL back to the client (use `Scope.externalBasePath()` — static's redirect `Location`), and code that bypasses the routed chain (legacy fastify registers on the bare server, so its route prefix must be the full external path). `static.ts`'s mount-root redirect gates on the _external_ base path, not the plugin-local one — a root-level static plugin (`baseURLPath === '/'`) still needs the redirect when the application itself carries a mount, since the client-visible mount root is then `externalBaseURLPath`, not `/`.
131
+ - A plugin registering per-mount state must key it on `Scope.routeFor()`'s resolved route, not on the parts it composes from — distinct `(mount, pluginUrlPath)` pairs can flatten to the same string (`/a`+`bc` and `/ab`+`c`). `REST.ts`'s `startedMounts` does this; it replaced a process-global `started` flag that silently 404'd the second mounted application's REST API. `handleApplication` also closes over `resources`/`httpOptions` per call rather than a module-level var, and skips deploy pre-flight validation scopes (`scope.isTransientValidation`) entirely — registering handlers from a throwaway validation scope would splice a validation run into the live request path and permanently mark that mount started, silently skipping the real scope's later registration.
132
+ - A mount is routing, **not** isolation: exported resources stay instance-wide, and a `host` mount cannot constrain legacy fastify routes — `fastifyRoutes.ts` refuses to load (throws) rather than warn when a `host` mount is configured, since the fallback really is reachable on every host.
133
+ - An invalid mount (unparseable `host`/`urlPath`) fails the application **closed**: `componentLoader.tryRootConfigMount` skips loading it entirely rather than falling back to unmounted access — loading unconstrained would silently drop the isolation the operator asked for, which is worse than not loading at all.
134
+ - Two applications mounted at different routes can register same-named middleware (e.g. both enable `rest`) without colliding: `middlewareChain.resolveRoutedChains` resolves `before`/`after` name references against a registry scoped to that route's own group, falling back to a _global_ registry that only holds genuinely unmounted entries (e.g. `authentication`) — never another mounted route's entries.
135
+ - `host` matching reads `request.host` (Harper's `Request.host` getter), not the raw `Host` header — HTTP/2 clients send `:authority`, never `Host`, so reading the header directly silently 404s every host-mounted app under h2 while h1 keeps working. `hostnameFromHeader` also strips a trailing dot (`api.example.com.`, the absolute-FQDN form some resolvers emit) since it names the same origin.
136
+ - `scopeMount.normalizeMountHost` validates against the same grammar as the `deploy_component` operation's `host` field (bare DNS hostname or IPv6 literal) and throws otherwise, so a hand-typed root-config `host` with a port/scheme/path fails the application closed too, instead of loading it unreachably. `nestScopeMount` logs a warning when a child's `host` is discarded by the parent-authority rule, so that isn't silent.
137
+
122
138
  ---
123
139
 
140
+ ## Operations authorization boundary
141
+
142
+ Operations request bodies are untrusted data. `serverHandlers.js → handlePostRequest()` rejects
143
+ prototype-mutating property names and strips the legacy `bypass_auth` property before dispatch.
144
+ `serverUtilities.ts → chooseOperation()` never reads authorization control from the body: trusted
145
+ internal callers pass bypass state as a separate argument and expose it to operation handlers only
146
+ through `operationAuthorizationState.ts`'s async context. When an operation registered by a component
147
+ must run on a worker, `registeredOperations.ts` carries that state in the same-process ITC envelope,
148
+ separately from the structured-cloned body. Never attach trusted dispatch state to an operation
149
+ payload.
150
+
124
151
  ## Resource ↔ HTTP boundary
125
152
 
126
153
  `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.