@harperfast/harper 5.3.0-alpha.1 → 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 (291) hide show
  1. package/bin/cliOperations.ts +12 -0
  2. package/bin/restart.ts +66 -6
  3. package/components/Application.ts +1134 -112
  4. package/components/OptionsWatcher.ts +368 -102
  5. package/components/Scope.ts +20 -1
  6. package/components/componentLoader.ts +43 -4
  7. package/components/deploymentOperations.ts +4 -1
  8. package/components/deploymentRecorder.ts +9 -2
  9. package/components/operations.js +284 -52
  10. package/components/operationsValidation.js +49 -2
  11. package/components/packageComponent.ts +25 -1
  12. package/components/requestRestart.ts +11 -0
  13. package/config/RootConfigWatcher.ts +191 -37
  14. package/config/configReadRetry.ts +62 -0
  15. package/config/configUtils.ts +78 -26
  16. package/config/parseConfigFile.ts +34 -0
  17. package/config/readConfigFileSync.ts +44 -0
  18. package/config/watcherArming.ts +59 -0
  19. package/config-root.schema.json +4 -0
  20. package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
  21. package/dist/bin/cliOperations.js +13 -0
  22. package/dist/bin/cliOperations.js.map +1 -1
  23. package/dist/bin/restart.js +42 -6
  24. package/dist/bin/restart.js.map +1 -1
  25. package/dist/components/Application.d.ts +104 -9
  26. package/dist/components/Application.js +954 -102
  27. package/dist/components/Application.js.map +1 -1
  28. package/dist/components/OptionsWatcher.d.ts +4 -1
  29. package/dist/components/OptionsWatcher.js +378 -104
  30. package/dist/components/OptionsWatcher.js.map +1 -1
  31. package/dist/components/Scope.js +15 -1
  32. package/dist/components/Scope.js.map +1 -1
  33. package/dist/components/componentLoader.js +35 -3
  34. package/dist/components/componentLoader.js.map +1 -1
  35. package/dist/components/deploymentOperations.js +4 -1
  36. package/dist/components/deploymentOperations.js.map +1 -1
  37. package/dist/components/deploymentRecorder.d.ts +4 -2
  38. package/dist/components/deploymentRecorder.js +1 -0
  39. package/dist/components/deploymentRecorder.js.map +1 -1
  40. package/dist/components/operations.d.ts +28 -0
  41. package/dist/components/operations.js +263 -54
  42. package/dist/components/operations.js.map +1 -1
  43. package/dist/components/operationsValidation.js +48 -2
  44. package/dist/components/operationsValidation.js.map +1 -1
  45. package/dist/components/packageComponent.js +24 -0
  46. package/dist/components/packageComponent.js.map +1 -1
  47. package/dist/components/requestRestart.d.ts +1 -0
  48. package/dist/components/requestRestart.js +7 -0
  49. package/dist/components/requestRestart.js.map +1 -1
  50. package/dist/config/RootConfigWatcher.d.ts +2 -0
  51. package/dist/config/RootConfigWatcher.js +189 -35
  52. package/dist/config/RootConfigWatcher.js.map +1 -1
  53. package/dist/config/configReadRetry.d.ts +8 -0
  54. package/dist/config/configReadRetry.js +62 -0
  55. package/dist/config/configReadRetry.js.map +1 -0
  56. package/dist/config/configUtils.d.ts +10 -9
  57. package/dist/config/configUtils.js +63 -27
  58. package/dist/config/configUtils.js.map +1 -1
  59. package/dist/config/parseConfigFile.d.ts +4 -0
  60. package/dist/config/parseConfigFile.js +35 -0
  61. package/dist/config/parseConfigFile.js.map +1 -0
  62. package/dist/config/readConfigFileSync.d.ts +1 -0
  63. package/dist/config/readConfigFileSync.js +47 -0
  64. package/dist/config/readConfigFileSync.js.map +1 -0
  65. package/dist/config/watcherArming.d.ts +15 -0
  66. package/dist/config/watcherArming.js +59 -0
  67. package/dist/config/watcherArming.js.map +1 -0
  68. package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
  69. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.js +4 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/json/systemSchema.json +3 -0
  74. package/dist/resources/DatabaseTransaction.d.ts +25 -0
  75. package/dist/resources/DatabaseTransaction.js +224 -18
  76. package/dist/resources/DatabaseTransaction.js.map +1 -1
  77. package/dist/resources/LMDBTransaction.d.ts +2 -1
  78. package/dist/resources/LMDBTransaction.js +22 -3
  79. package/dist/resources/LMDBTransaction.js.map +1 -1
  80. package/dist/resources/PrimaryRocksDatabase.js +22 -6
  81. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  82. package/dist/resources/RecordEncoder.d.ts +1 -1
  83. package/dist/resources/RecordEncoder.js +21 -5
  84. package/dist/resources/RecordEncoder.js.map +1 -1
  85. package/dist/resources/Resource.js +97 -13
  86. package/dist/resources/Resource.js.map +1 -1
  87. package/dist/resources/ResourceInterface.d.ts +8 -0
  88. package/dist/resources/RocksIndexStore.js +2 -1
  89. package/dist/resources/RocksIndexStore.js.map +1 -1
  90. package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
  91. package/dist/resources/RocksTransactionLogStore.js +104 -33
  92. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  93. package/dist/resources/Table.d.ts +58 -7
  94. package/dist/resources/Table.js +1095 -346
  95. package/dist/resources/Table.js.map +1 -1
  96. package/dist/resources/analytics/write.js +10 -3
  97. package/dist/resources/analytics/write.js.map +1 -1
  98. package/dist/resources/auditStore.d.ts +170 -0
  99. package/dist/resources/auditStore.js +457 -11
  100. package/dist/resources/auditStore.js.map +1 -1
  101. package/dist/resources/dataLoader.js +3 -4
  102. package/dist/resources/dataLoader.js.map +1 -1
  103. package/dist/resources/databases.d.ts +16 -13
  104. package/dist/resources/databases.js +624 -176
  105. package/dist/resources/databases.js.map +1 -1
  106. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  107. package/dist/resources/derivedIndexRegistry.js +68 -0
  108. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  109. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  110. package/dist/resources/derivedIndexRuntime.js +2027 -0
  111. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  112. package/dist/resources/graphql.js +3 -2
  113. package/dist/resources/graphql.js.map +1 -1
  114. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
  115. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +848 -39
  116. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  117. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  118. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  119. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  120. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  121. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  122. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  123. package/dist/resources/nodeIdMapping.d.ts +5 -0
  124. package/dist/resources/nodeIdMapping.js +49 -0
  125. package/dist/resources/nodeIdMapping.js.map +1 -1
  126. package/dist/resources/recordLock.d.ts +47 -4
  127. package/dist/resources/recordLock.js +138 -7
  128. package/dist/resources/recordLock.js.map +1 -1
  129. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  130. package/dist/resources/recordLockCoordinator.js +2565 -0
  131. package/dist/resources/recordLockCoordinator.js.map +1 -0
  132. package/dist/resources/replayLogs.js +5 -0
  133. package/dist/resources/replayLogs.js.map +1 -1
  134. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  135. package/dist/resources/replicatedApplyFailure.js +63 -0
  136. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  137. package/dist/resources/scheduler/scheduler.js +3 -3
  138. package/dist/resources/scheduler/scheduler.js.map +1 -1
  139. package/dist/resources/search.d.ts +10 -4
  140. package/dist/resources/search.js +160 -40
  141. package/dist/resources/search.js.map +1 -1
  142. package/dist/resources/tracked.d.ts +5 -1
  143. package/dist/resources/tracked.js +74 -23
  144. package/dist/resources/tracked.js.map +1 -1
  145. package/dist/security/jsLoader.js +6 -4
  146. package/dist/security/jsLoader.js.map +1 -1
  147. package/dist/server/REST.js +33 -2
  148. package/dist/server/REST.js.map +1 -1
  149. package/dist/server/http.d.ts +5 -1
  150. package/dist/server/http.js +34 -2
  151. package/dist/server/http.js.map +1 -1
  152. package/dist/server/serverHelpers/Headers.d.ts +2 -0
  153. package/dist/server/serverHelpers/Headers.js +6 -0
  154. package/dist/server/serverHelpers/Headers.js.map +1 -1
  155. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  156. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  157. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  158. package/dist/server/serverHelpers/Request.d.ts +5 -10
  159. package/dist/server/serverHelpers/Request.js +38 -136
  160. package/dist/server/serverHelpers/Request.js.map +1 -1
  161. package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
  162. package/dist/server/serverHelpers/contentTypes.js +189 -15
  163. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  164. package/dist/server/serverHelpers/serverUtilities.js +96 -17
  165. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  166. package/dist/server/storageReclamation.js +1 -1
  167. package/dist/server/storageReclamation.js.map +1 -1
  168. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  169. package/dist/server/threads/isolatedApplications.js +171 -0
  170. package/dist/server/threads/isolatedApplications.js.map +1 -0
  171. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  172. package/dist/server/threads/logRotationTransport.js +33 -0
  173. package/dist/server/threads/logRotationTransport.js.map +1 -0
  174. package/dist/server/threads/manageThreads.d.ts +64 -6
  175. package/dist/server/threads/manageThreads.js +261 -12
  176. package/dist/server/threads/manageThreads.js.map +1 -1
  177. package/dist/server/threads/socketRouter.d.ts +1 -0
  178. package/dist/server/threads/socketRouter.js +196 -13
  179. package/dist/server/threads/socketRouter.js.map +1 -1
  180. package/dist/server/threads/threadServer.js +30 -7
  181. package/dist/server/threads/threadServer.js.map +1 -1
  182. package/dist/utility/errors/hdbError.d.ts +24 -0
  183. package/dist/utility/errors/hdbError.js +58 -1
  184. package/dist/utility/errors/hdbError.js.map +1 -1
  185. package/dist/utility/hdbTerms.d.ts +2 -0
  186. package/dist/utility/hdbTerms.js +2 -0
  187. package/dist/utility/hdbTerms.js.map +1 -1
  188. package/dist/utility/logging/harper_logger.js +217 -38
  189. package/dist/utility/logging/harper_logger.js.map +1 -1
  190. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  191. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  192. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  193. package/dist/utility/logging/logRotation.d.ts +46 -0
  194. package/dist/utility/logging/logRotation.js +365 -0
  195. package/dist/utility/logging/logRotation.js.map +1 -0
  196. package/dist/utility/logging/logRotator.d.ts +1 -1
  197. package/dist/utility/logging/logRotator.js +172 -92
  198. package/dist/utility/logging/logRotator.js.map +1 -1
  199. package/dist/utility/npmUtilities.js +6 -4
  200. package/dist/utility/npmUtilities.js.map +1 -1
  201. package/dist/utility/watcherFallback.d.ts +0 -45
  202. package/dist/utility/watcherFallback.js +1 -125
  203. package/dist/utility/watcherFallback.js.map +1 -1
  204. package/dist/validation/configValidator.js +6 -3
  205. package/dist/validation/configValidator.js.map +1 -1
  206. package/index.ts +6 -0
  207. package/json/systemSchema.json +3 -0
  208. package/npm-shrinkwrap.json +131 -41
  209. package/package.json +10 -3
  210. package/resources/DESIGN.md +124 -19
  211. package/resources/DatabaseTransaction.ts +230 -17
  212. package/resources/LMDBTransaction.ts +21 -3
  213. package/resources/PrimaryRocksDatabase.ts +20 -7
  214. package/resources/RecordEncoder.ts +25 -5
  215. package/resources/Resource.ts +97 -13
  216. package/resources/ResourceInterface.ts +8 -0
  217. package/resources/RocksIndexStore.ts +2 -1
  218. package/resources/RocksTransactionLogStore.ts +111 -31
  219. package/resources/Table.ts +1224 -393
  220. package/resources/analytics/write.ts +10 -3
  221. package/resources/auditStore.ts +460 -11
  222. package/resources/dataLoader.ts +3 -4
  223. package/resources/databases.ts +610 -146
  224. package/resources/derivedIndexRegistry.ts +56 -0
  225. package/resources/derivedIndexRuntime.ts +2292 -0
  226. package/resources/graphql.ts +3 -2
  227. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +905 -46
  228. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  229. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  230. package/resources/nodeIdMapping.ts +50 -0
  231. package/resources/recordLock.ts +173 -7
  232. package/resources/recordLockCoordinator.ts +3043 -0
  233. package/resources/replayLogs.ts +5 -0
  234. package/resources/replicatedApplyFailure.ts +77 -0
  235. package/resources/scheduler/scheduler.ts +4 -4
  236. package/resources/search.ts +169 -49
  237. package/resources/tracked.ts +73 -22
  238. package/security/jsLoader.ts +6 -4
  239. package/server/DESIGN.md +11 -0
  240. package/server/REST.ts +36 -3
  241. package/server/http.ts +34 -2
  242. package/server/serverHelpers/Headers.ts +5 -1
  243. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  244. package/server/serverHelpers/Request.ts +33 -131
  245. package/server/serverHelpers/contentTypes.ts +188 -15
  246. package/server/serverHelpers/serverUtilities.ts +143 -24
  247. package/server/storageReclamation.ts +2 -2
  248. package/server/threads/isolatedApplications.ts +157 -0
  249. package/server/threads/logRotationTransport.ts +40 -0
  250. package/server/threads/manageThreads.js +254 -12
  251. package/server/threads/socketRouter.ts +217 -11
  252. package/server/threads/threadServer.js +30 -7
  253. package/studio/web/assets/{Chat-BnCBegQz.js → Chat-D3j-1yY1.js} +1 -1
  254. package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-BxJGYcfB.js} +3 -3
  255. package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-CT55oWOe.js} +1 -1
  256. package/studio/web/assets/{applications-DHxGi7JH.js → applications-D9Ct9_vm.js} +1 -1
  257. package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-DV8H7VwA.js} +1 -1
  258. package/studio/web/assets/{editor-DNcRHK54.js → editor-uatc0unt.js} +1 -1
  259. package/studio/web/assets/{html-Bdssedlg.js → html-Bm6D6paN.js} +1 -1
  260. package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CEn7tpLG.js} +1 -1
  261. package/studio/web/assets/{index-D6sxmFLR.js → index-BIXW6Pu4.js} +5 -5
  262. package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-UI7L-Vrk.js} +1 -1
  263. package/studio/web/assets/{javascript-B8meVSTH.js → javascript-CJ0G3AFZ.js} +1 -1
  264. package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-DQADAYEa.js} +1 -1
  265. package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-CAQJXWcI.js} +1 -1
  266. package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  267. package/studio/web/assets/{notifications-CQf18QKb.js → notifications-BbxTU6Aw.js} +1 -1
  268. package/studio/web/assets/{notifications-CvZivSbh.js → notifications-Cvb3P1lB.js} +1 -1
  269. package/studio/web/assets/{profile-DdOwtntb.js → profile-Yyb7gsvL.js} +1 -1
  270. package/studio/web/assets/{regions-n69fwagr.js → regions-OgjGHlU5.js} +1 -1
  271. package/studio/web/assets/{register-PfWTCXWB.js → register-6qwNEOY3.js} +2 -2
  272. package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BilDMtgB.js} +1 -1
  273. package/studio/web/assets/{setup-CUx_aUDl.js → setup-J6qJ7OIU.js} +2 -2
  274. package/studio/web/assets/{status-D7BVKqX9.js → status-0RWGcfyD.js} +1 -1
  275. package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-BIn-vErT.js} +1 -1
  276. package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-DgUXku4d.js} +1 -1
  277. package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-C9orXcsM.js} +1 -1
  278. package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  279. package/studio/web/assets/{workers-pR3jRY9D.js → workers-JVzSDmgx.js} +1 -1
  280. package/studio/web/assets/{xml-2iRnMhQO.js → xml-Cq-S8S4X.js} +1 -1
  281. package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-sfoRdh1M.js} +1 -1
  282. package/studio/web/index.html +1 -1
  283. package/utility/errors/hdbError.ts +54 -0
  284. package/utility/hdbTerms.ts +2 -0
  285. package/utility/logging/harper_logger.ts +209 -30
  286. package/utility/logging/logGenerationCoordinator.ts +196 -0
  287. package/utility/logging/logRotation.ts +367 -0
  288. package/utility/logging/logRotator.ts +196 -91
  289. package/utility/npmUtilities.ts +6 -4
  290. package/utility/watcherFallback.ts +0 -122
  291. package/validation/configValidator.ts +6 -3
@@ -73,6 +73,8 @@ const packageUtils_js_1 = require("../../utility/packageUtils.js");
73
73
  const globals_js_1 = require("../../globals.js");
74
74
  const console_1 = require("console");
75
75
  const node_util_1 = require("node:util");
76
+ const logRotation_ts_1 = require("./logRotation.js");
77
+ const logGenerationCoordinator_ts_1 = require("./logGenerationCoordinator.js");
76
78
  const { isNativeError } = node_util_1.types;
77
79
  // store the native write function so we can call it after we write to the log file (and store it on process.stdout
78
80
  // because unit tests will create multiple instances of this module)
@@ -101,6 +103,17 @@ exports.OUTPUTS = {
101
103
  // Location of default config YAML.
102
104
  const DEFAULT_CONFIG_FILE = join(packageUtils_js_1.PACKAGE_ROOT, 'static', hdbTerms.HDB_DEFAULT_CONFIG_FILE);
103
105
  const CLOSE_LOG_FD_TIMEOUT = 10000;
106
+ // Above the module-scope initLogSettings() call below, which reaches getFileLogger: a const
107
+ // declared after it is still in its temporal dead zone when the file sink first reads one.
108
+ const LOG_TIME_USAGE_THRESHOLD = 100;
109
+ // How long to write straight to stdio after the log file refuses an append.
110
+ const APPEND_RETRY_COOLDOWN = 5000;
111
+ // Ceiling on how often a failing rotation may report itself; the failure repeats every check point.
112
+ const ROTATION_REPORT_INTERVAL = 60000;
113
+ const SET_ROTATION_POLICY = Symbol('setRotationPolicy');
114
+ const GET_ROTATION_POLICY = Symbol('getRotationPolicy');
115
+ const FILE_ROTATION_SOURCE = Symbol('fileRotationSource');
116
+ const SERVICE_NAME = worker_threads_1.workerData?.name?.replace(/ /g, '-') || 'main';
104
117
  let logConsole;
105
118
  let log_to_file;
106
119
  let logToStdstreams;
@@ -159,7 +172,24 @@ function updateLogger(logger, logOptions, name, mainLoggerRef = mainLogger) {
159
172
  // rotation entirely (#1877). Excluded when `logger` IS mainLoggerRef: the fallback would just
160
173
  // reassign mainLogger.rotation to itself, making it impossible to ever clear main's rotation.
161
174
  const inheritedRotation = logOptions.rotation ?? (logger === mainLoggerRef ? undefined : mainLoggerRef?.rotation);
162
- if (inheritedRotation && inheritedRotation === mainLoggerRef?.rotation) {
175
+ const loggerPolicy = logger[GET_ROTATION_POLICY]?.();
176
+ const mainPolicy = mainLoggerRef?.[GET_ROTATION_POLICY]?.();
177
+ const inheritsRotation = logOptions.rotation == null && logger !== mainLoggerRef;
178
+ const rotationSource = inheritsRotation ? mainPolicy?.source : loggerPolicy?.ownSource;
179
+ let path = logOptions.path;
180
+ if (path) {
181
+ if (!logOptions.root)
182
+ logOptions.root = pathModule.dirname(path);
183
+ }
184
+ else if (logOptions.root) {
185
+ path = join(logOptions.root, logName);
186
+ }
187
+ else {
188
+ path = mainLoggerRef.path;
189
+ if (!logOptions.root)
190
+ logOptions.root = pathModule.dirname(path);
191
+ }
192
+ if (inheritedRotation && inheritedRotation === mainLoggerRef?.rotation && path !== mainLoggerRef?.path) {
163
193
  // This logger has no rotation block of its own and is inheriting main's wholesale — but
164
194
  // inheriting `rotation.path` too would point this logger's archives at wherever main
165
195
  // archives to. If this logger's file lives on a different filesystem/volume than that
@@ -170,25 +200,18 @@ function updateLogger(logger, logOptions, name, mainLoggerRef = mainLogger) {
170
200
  // goes through `logOptions.rotation` above, not this fallback. Clone rather than mutate:
171
201
  // `inheritedRotation` is the literal object shared by mainLogger.rotation and every other
172
202
  // component that inherited it.
203
+ //
204
+ // Only when the paths differ. A logger writing to main's own file shares main's rotation
205
+ // state — file loggers are cached per path — so stripping there discards the operator's
206
+ // configured rotation.path for the main log itself, which no EXDEV risk justifies.
173
207
  const { path: _inheritedPath, ...rotationWithoutPath } = inheritedRotation;
174
- logger.rotation = rotationWithoutPath;
208
+ setRotationPolicy(rotationWithoutPath);
175
209
  }
176
210
  else {
177
- logger.rotation = inheritedRotation;
178
- }
179
- let path = logOptions.path;
180
- if (path) {
181
- if (!logOptions.root)
182
- logOptions.root = pathModule.dirname(path);
183
- }
184
- else if (logOptions.root) {
185
- path = join(logOptions.root, logName);
186
- }
187
- else {
188
- path = mainLoggerRef.path;
189
- if (!logOptions.root)
190
- logOptions.root = pathModule.dirname(path);
211
+ setRotationPolicy(inheritedRotation);
191
212
  }
213
+ // After the rotation above: assigning `path` goes through the setter that rebuilds this path's
214
+ // file logger, and it reads `logger.rotation` as it stands at that moment.
192
215
  if (path)
193
216
  logger.path = path;
194
217
  else
@@ -199,6 +222,12 @@ function updateLogger(logger, logOptions, name, mainLoggerRef = mainLogger) {
199
222
  // if there is a configured tag or if a component is logging to default/main log path, use the component name as the tag
200
223
  // to differentiate it
201
224
  logger.tag = logOptions.tag ?? ((mainLoggerRef.path === logger.path || exports.externalLogger.path === logger.path) && name);
225
+ function setRotationPolicy(rotation) {
226
+ if (logger[SET_ROTATION_POLICY])
227
+ logger[SET_ROTATION_POLICY](rotation, rotationSource, inheritsRotation);
228
+ else
229
+ logger.rotation = rotation;
230
+ }
202
231
  }
203
232
  // creates a logger where the methods are only defined if they are within the log level.
204
233
  // Using this conditional logger means that every method call must be optional like log.trace?.('message),
@@ -233,7 +262,19 @@ async function updateLogSettings() {
233
262
  // TODO: Any way to differentiate changes that we can and can't handle?
234
263
  rootConfig.on('change', updateLogSettings);
235
264
  }
236
- let rootConfigObject = rootConfig.config;
265
+ applyLogSettings(rootConfig.config);
266
+ }
267
+ // Separate from the watcher wiring above because the watcher it builds reads a process-wide path,
268
+ // which leaves no way to exercise these settings against a given config.
269
+ function applyLogSettings(rootConfigObject) {
270
+ // `ready` settles on every terminal read outcome, including ones that carry no config at all —
271
+ // see DESIGN.md, "Every terminal read outcome settles the barrier". Applying that as settings
272
+ // would not be "the defaults": `updateLogger` reads an absent `rotation` as rotation off and an
273
+ // absent `console` as console off, so an unreadable config would silently disable logging.
274
+ // What `initLogSettings()` established at startup stands until a real config arrives, which
275
+ // is why the watcher settles those outcomes with no config rather than an empty one.
276
+ if (!rootConfigObject || typeof rootConfigObject !== 'object')
277
+ return;
237
278
  const logOptions = rootConfigObject.logging ?? {};
238
279
  // Resolve relative paths against rootPath from the same config
239
280
  const rootPath = rootConfigObject.rootPath;
@@ -256,7 +297,9 @@ async function updateLogSettings() {
256
297
  for (const name in rootConfigObject) {
257
298
  // we now scan each component to see if it has logging individual configured
258
299
  const component = rootConfigObject[name];
259
- if (component.logging) {
300
+ // `myComponent:` with nothing under it parses to null, and an async listener's TypeError
301
+ // escapes the watcher's emit guard as an unhandled rejection.
302
+ if (component?.logging) {
260
303
  updateLogger(mainLogger.forComponent(name), component.logging, name);
261
304
  }
262
305
  else if (mainLogger.hasComponent(name)) {
@@ -456,6 +499,8 @@ module.exports = {
456
499
  // we can start using the RootConfigWatcher
457
500
  start: updateLogSettings,
458
501
  startOnMainThread: updateLogSettings,
502
+ // Test-only: applies a config without the process-wide watcher `updateLogSettings` builds.
503
+ _applyLogSettingsForTests: applyLogSettings,
459
504
  errorToString,
460
505
  errorForLog,
461
506
  inspectForLog,
@@ -516,6 +561,14 @@ function logsAtLevel(level) {
516
561
  function initLogSettings(forceInit = false) {
517
562
  try {
518
563
  if (hdbProperties === undefined || forceInit) {
564
+ if (forceInit && mainLogger?.[SET_ROTATION_POLICY]) {
565
+ const currentPath = mainLogger.path;
566
+ if (currentPath) {
567
+ const { ownSource } = mainLogger[GET_ROTATION_POLICY]();
568
+ mainLogger[SET_ROTATION_POLICY](undefined, ownSource, false);
569
+ mainLogger.path = currentPath;
570
+ }
571
+ }
519
572
  closeLogFile();
520
573
  const bootPropsFilePath = getPropsFilePath();
521
574
  let properties = (0, assignCmdEnvVariables_ts_1.default)(['ROOTPATH']);
@@ -692,14 +745,18 @@ function suppressLogging(callback) {
692
745
  loggingEnabled = true;
693
746
  }
694
747
  }
695
- const SERVICE_NAME = worker_threads_1.workerData?.name?.replace(/ /g, '-') || 'main';
696
748
  // these are used to store information about the current service and tag so we can prepend them to the log during
697
749
  // the writes, without having to pass the information through the Console instance
698
750
  let currentLevel = 'info'; // default is info
699
751
  let currentServiceName;
700
752
  let currentTag;
701
753
  function createLogger(options = {}) {
702
- let { path: logFilePath, level: logLevel, stdStreams: logToStdstreams, rotation, isExternalInstance, writeToLog, component, } = options;
754
+ let { path: logFilePath, level: logLevel, stdStreams: logToStdstreams, rotation, isExternalInstance, writeToLog, component, rotationSource, rotationInherited = false, } = options;
755
+ const ownRotationSource = Symbol('rotationPolicySource');
756
+ let currentRotationSource = rotationSource ?? ownRotationSource;
757
+ let previousRotationSource;
758
+ let currentRotationInherited = rotationInherited;
759
+ let rotationChangeIsAuthoritative = rotation !== undefined;
703
760
  if (!logLevel)
704
761
  logLevel = 'info';
705
762
  let level = typeof logLevel === 'number' ? logLevel : LOG_LEVEL_HIERARCHY[logLevel];
@@ -750,7 +807,8 @@ function createLogger(options = {}) {
750
807
  else if (logToStdstreams)
751
808
  process.stderr.write(log);
752
809
  }
753
- let logToFile = logFilePath && getFileLogger(logFilePath, rotation, isExternalInstance);
810
+ let logToFile = logFilePath && getFileLogger(logFilePath, rotation, isExternalInstance, rotationPolicy());
811
+ rotationPolicyApplied();
754
812
  function logPrepend(write) {
755
813
  return {
756
814
  write(log) {
@@ -772,6 +830,14 @@ function createLogger(options = {}) {
772
830
  stderr: logPrepend(writeToLog ?? logStdErr),
773
831
  colorMode: (logToStdstreams && colorMode) || false,
774
832
  }, level);
833
+ logger[SET_ROTATION_POLICY] = function (newRotation, source = ownRotationSource, inherited = false) {
834
+ previousRotationSource = currentRotationSource;
835
+ currentRotationSource = source ?? ownRotationSource;
836
+ currentRotationInherited = inherited;
837
+ rotationChangeIsAuthoritative = true;
838
+ logger.rotation = newRotation;
839
+ };
840
+ logger[GET_ROTATION_POLICY] = () => ({ ownSource: ownRotationSource, source: currentRotationSource });
775
841
  updateConditional(logger);
776
842
  logger.path = logFilePath;
777
843
  Object.defineProperty(logger, 'path', {
@@ -780,7 +846,10 @@ function createLogger(options = {}) {
780
846
  },
781
847
  set(path) {
782
848
  logFilePath = path;
783
- logToFile = getFileLogger(logFilePath, logger.rotation, isExternalInstance);
849
+ if (!logFilePath)
850
+ return;
851
+ logToFile = getFileLogger(logFilePath, logger.rotation, isExternalInstance, rotationPolicy());
852
+ rotationPolicyApplied();
784
853
  if (isExternalInstance)
785
854
  writeToLogFile = logToFile;
786
855
  },
@@ -794,12 +863,15 @@ function createLogger(options = {}) {
794
863
  let componentLogger = components.get(name);
795
864
  if (!componentLogger) {
796
865
  const protoLogger = isExternal ? exports.externalLogger : logger;
866
+ const protoRotationPolicy = protoLogger[GET_ROTATION_POLICY]?.();
797
867
  componentLogger = createLogger({
798
868
  path: protoLogger.path,
799
869
  level: protoLogger.level,
800
870
  stdStreams: protoLogger.logToStdstreams,
801
871
  isExternalInstance: isExternal || name === 'external',
802
872
  rotation: protoLogger.rotation,
873
+ rotationSource: protoRotationPolicy?.source,
874
+ rotationInherited: true,
803
875
  writeToLog,
804
876
  component: true,
805
877
  });
@@ -816,35 +888,80 @@ function createLogger(options = {}) {
816
888
  logger.components = components;
817
889
  }
818
890
  return logger;
891
+ function rotationPolicy() {
892
+ return {
893
+ source: currentRotationSource,
894
+ previousSource: previousRotationSource,
895
+ inherited: currentRotationInherited,
896
+ authoritative: rotationChangeIsAuthoritative,
897
+ };
898
+ }
899
+ function rotationPolicyApplied() {
900
+ previousRotationSource = currentRotationSource;
901
+ rotationChangeIsAuthoritative = false;
902
+ }
819
903
  }
820
- const LOG_TIME_USAGE_THRESHOLD = 100;
821
- // How long to write straight to stdio after the log file refuses an append.
822
- const APPEND_RETRY_COOLDOWN = 5000;
823
904
  /**
824
905
  * Get the file logger for the given path. If it doesn't exist, create it.
825
906
  * @param path
826
907
  * @param isExternalInstance
827
908
  * @return {any}
828
909
  */
829
- function getFileLogger(path, rotation, isExternalInstance) {
910
+ function getFileLogger(path, rotation, isExternalInstance, rotationPolicy) {
830
911
  let logger = fileLoggers.get(path);
831
912
  let logFD, loggedFDError, loggedAppendError, logTimer, retryAppendAfter;
832
913
  let logBuffer;
914
+ let rotationProblemNotice;
833
915
  let logTimeUsage = 0;
916
+ let rotationGuard, logFDIdentity, nextRotationReport = 0;
834
917
  if (!logger) {
835
918
  logger = logToFile;
836
919
  logger.closeLogFile = closeLogFile;
920
+ // The guard has to be installed through the live closure: later calls for this path get the
921
+ // cached logger back, and their own logFD/logBuffer are dead.
922
+ logger.installRotationGuard = installRotationGuard;
837
923
  logger.path = path;
838
924
  fileLoggers.set(path, logger);
839
- }
840
- if (worker_threads_1.isMainThread && JSON.stringify(rotation) !== JSON.stringify(logger.rotation)) {
925
+ // Registered by the sink, not by the size guard: a thread that writes this file but has no
926
+ // guard (no maxSize, or rotation driven only by interval) still holds a descriptor, and an
927
+ // answer from it has to mean "released" rather than only "handler ran".
928
+ (0, logGenerationCoordinator_ts_1.registerLogSink)(path, { identity: () => logFDIdentity, close: closeLogFile });
929
+ }
930
+ const currentSource = logger[FILE_ROTATION_SOURCE];
931
+ const sameSource = currentSource === rotationPolicy.source;
932
+ const replacesPreviousSource = currentSource !== undefined && currentSource === rotationPolicy.previousSource;
933
+ const canInstall = rotation !== undefined &&
934
+ (!rotationPolicy.inherited || currentSource === undefined || sameSource || replacesPreviousSource);
935
+ const canClear = rotation === undefined && rotationPolicy.authoritative && (sameSource || replacesPreviousSource);
936
+ let reconfigured = false;
937
+ if (canInstall) {
938
+ logger[FILE_ROTATION_SOURCE] = rotationPolicy.source;
939
+ reconfigured = JSON.stringify(rotation) !== JSON.stringify(logger.rotation);
940
+ }
941
+ if (reconfigured) {
841
942
  logger.rotation = rotation;
943
+ // Every writing thread, and synchronously: request logging is produced by the HTTP workers, and
944
+ // at the rates that make maxSize matter a deferred guard misses megabytes before it exists.
945
+ logger.installRotationGuard(rotation);
946
+ }
947
+ else if (canClear) {
948
+ logger.rotation = undefined;
949
+ logger[FILE_ROTATION_SOURCE] = undefined;
950
+ logger.installRotationGuard(undefined);
951
+ reconfigured = true;
952
+ }
953
+ if (worker_threads_1.isMainThread && reconfigured) {
842
954
  setTimeout(() => {
843
- logger.rotator?.end();
844
- if (!rotation)
845
- return;
846
- const { logRotator } = require('./logRotator');
955
+ // Everything inside the try: a throw from a timer callback is unhandled, and neither
956
+ // require('./logRotator') (which reaches environmentManager's synchronous init) nor a
957
+ // rotator teardown may take the process down over log rotation (#847).
847
958
  try {
959
+ const previousRotator = logger.rotator;
960
+ logger.rotator = undefined;
961
+ previousRotator?.end();
962
+ if (!rotation)
963
+ return;
964
+ const { logRotator } = require('./logRotator');
848
965
  logger.rotator = logRotator({
849
966
  logger,
850
967
  ...rotation,
@@ -856,6 +973,50 @@ function getFileLogger(path, rotation, isExternalInstance) {
856
973
  }, 100);
857
974
  }
858
975
  return logger;
976
+ function installRotationGuard(newRotation) {
977
+ rotationGuard = undefined;
978
+ if (!newRotation || newRotation.enabled === false)
979
+ return;
980
+ const maxBytes = (0, logRotation_ts_1.parseMaxSize)(newRotation.maxSize);
981
+ if (!maxBytes) {
982
+ if (newRotation.maxSize != null)
983
+ reportRotationProblem(`logging.rotation.maxSize: ${logRotation_ts_1.INVALID_MAX_SIZE_MSG}`);
984
+ return;
985
+ }
986
+ try {
987
+ rotationGuard = (0, logRotation_ts_1.createRotationGuard)({
988
+ logPath: path,
989
+ maxBytes,
990
+ rotatedLogDir: (0, logRotation_ts_1.resolveRotatedLogDir)(path, newRotation.path),
991
+ // From the rotation block rather than environmentManager (which logRotator still reads):
992
+ // environmentManager imports this module, so it cannot be reached from the write path.
993
+ compress: newRotation.compress,
994
+ getLogIdentity: () => logFDIdentity,
995
+ closeLogFile,
996
+ report: reportRotationProblem,
997
+ onRotated(archivePath) {
998
+ // Same shape as every other line in the file: written straight to the sink, this
999
+ // bypasses the prefix createLogger's logPrepend adds, and readLog parses by level.
1000
+ logToFile(`[${SERVICE_NAME}/${worker_threads_1.threadId}] [notify]: hdb.log rotated, old log moved to ${archivePath}`);
1001
+ },
1002
+ });
1003
+ }
1004
+ catch (error) {
1005
+ reportRotationProblem(`log rotation is disabled for ${path}: ${error}`);
1006
+ }
1007
+ }
1008
+ function reportRotationProblem(text) {
1009
+ // Straight to stdio, never back through this sink: the sink is what rotation is failing on.
1010
+ // Rate-limited rather than once-only, so a later, different failure is still reported.
1011
+ if (nextRotationReport > performance.now())
1012
+ return;
1013
+ nextRotationReport = performance.now() + ROTATION_REPORT_INTERVAL;
1014
+ const message = `Harper log rotation problem — ${text}`;
1015
+ // Scripted services discard stdio. Preserve the warning in the active file on the next
1016
+ // successful append as well, without recursing through the rotation guard.
1017
+ rotationProblemNotice = `${new Date().toISOString()} [${SERVICE_NAME}/${worker_threads_1.threadId}] [error]: ${message}\n`;
1018
+ writeToStdioDirectly(process.stderr, `${message}\n`);
1019
+ }
859
1020
  function logToFile(log) {
860
1021
  let entry = `${new Date().toISOString()} ${log}${log.endsWith('\n') ? '' : '\n'}`;
861
1022
  if (logBuffer) {
@@ -885,18 +1046,29 @@ function getFileLogger(path, rotation, isExternalInstance) {
885
1046
  }
886
1047
  // this is called on a timer, and will write the log buffer to the file
887
1048
  function logQueuedData(entry) {
888
- openLogFile(undefined);
889
1049
  const payload = logBuffer ? logBuffer.join('') : entry;
1050
+ // Released before anything can re-enter: the rotation notice is written from inside the append
1051
+ // below, and a re-entrant flush still holding this batch would write it twice.
1052
+ logBuffer = null;
1053
+ if (payload === undefined)
1054
+ return;
890
1055
  // A file that just refused a write will refuse the next one too, and every attempt costs a
891
1056
  // failed syscall plus a thrown error on whatever path is logging - which, on a full volume,
892
1057
  // is the request path at full rate. Go straight to stdio until the cooldown expires.
893
- if (logFD && !(retryAppendAfter > performance.now())) {
1058
+ // Opened after the guard: a guard recovering by rotation closes this descriptor first.
1059
+ const mayAppend = !(retryAppendAfter > performance.now()) && (rotationGuard?.beforeAppend() ?? true);
1060
+ openLogFile(undefined);
1061
+ if (logFD && mayAppend) {
894
1062
  let startTime = performance.now();
895
1063
  try {
896
- fs.appendFileSync(logFD, payload);
1064
+ const appendPayload = rotationProblemNotice ? rotationProblemNotice + payload : payload;
1065
+ const appendBuffer = Buffer.from(appendPayload);
1066
+ fs.appendFileSync(logFD, appendBuffer);
1067
+ rotationProblemNotice = undefined;
897
1068
  // Both cleared, so a volume that fills again months later reports itself again
898
1069
  retryAppendAfter = undefined;
899
1070
  loggedAppendError = false;
1071
+ rotationGuard?.recordWrite(appendBuffer.length);
900
1072
  }
901
1073
  catch (error) {
902
1074
  retryAppendAfter = performance.now() + APPEND_RETRY_COOLDOWN;
@@ -910,7 +1082,6 @@ function getFileLogger(path, rotation, isExternalInstance) {
910
1082
  writeToStdioDirectly(process.stderr, `Harper cannot write to its log file: ${error}\n`);
911
1083
  }
912
1084
  writeToStdioDirectly(process.stdout, payload);
913
- logBuffer = null;
914
1085
  return;
915
1086
  }
916
1087
  let endTime = performance.now();
@@ -920,8 +1091,6 @@ function getFileLogger(path, rotation, isExternalInstance) {
920
1091
  }
921
1092
  else
922
1093
  writeToStdioDirectly(process.stdout, payload);
923
- if (logBuffer)
924
- logBuffer = null;
925
1094
  }
926
1095
  function closeLogFile(_unused) {
927
1096
  try {
@@ -929,6 +1098,7 @@ function getFileLogger(path, rotation, isExternalInstance) {
929
1098
  }
930
1099
  catch { }
931
1100
  logFD = null;
1101
+ logFDIdentity = null;
932
1102
  if (isExternalInstance)
933
1103
  mainLogFd = null;
934
1104
  }
@@ -936,6 +1106,15 @@ function getFileLogger(path, rotation, isExternalInstance) {
936
1106
  if (!logFD) {
937
1107
  try {
938
1108
  logFD = fs.openSync(path, 'a');
1109
+ // Which generation this descriptor belongs to, recorded once here so the size guard's
1110
+ // checkpoint needs a single pathname stat to tell whether the file has moved under it.
1111
+ try {
1112
+ const opened = fs.fstatSync(logFD);
1113
+ logFDIdentity = { ino: opened.ino, dev: opened.dev };
1114
+ }
1115
+ catch {
1116
+ logFDIdentity = null;
1117
+ }
939
1118
  if (isExternalInstance)
940
1119
  mainLogFd = logFD;
941
1120
  }