@harperfast/harper 5.2.0 → 5.2.2

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 (308) hide show
  1. package/bin/copyDb.ts +40 -11
  2. package/bin/harper.ts +20 -51
  3. package/bin/help.ts +216 -0
  4. package/components/Application.ts +236 -46
  5. package/components/ApplicationScope.ts +26 -0
  6. package/components/EntryHandler.ts +410 -105
  7. package/components/RuntimeModuleTracker.ts +189 -0
  8. package/components/Scope.ts +68 -24
  9. package/components/componentLoader.ts +70 -16
  10. package/components/deployLifecycle.ts +119 -33
  11. package/components/deploymentRecorder.ts +102 -11
  12. package/components/mcp/tools/operations.ts +6 -0
  13. package/components/operations.js +8 -6
  14. package/config/configUtils.ts +78 -20
  15. package/config/harperConfigEnvVars.ts +114 -17
  16. package/config-root.schema.json +10 -0
  17. package/dataLayer/harperBridge/ResourceBridge.ts +26 -5
  18. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
  19. package/dataLayer/hdbInfoController.ts +8 -0
  20. package/dataLayer/schemaDescribe.ts +2 -1
  21. package/dist/bin/copyDb.js +33 -9
  22. package/dist/bin/copyDb.js.map +1 -1
  23. package/dist/bin/harper.d.ts +6 -0
  24. package/dist/bin/harper.js +18 -50
  25. package/dist/bin/harper.js.map +1 -1
  26. package/dist/bin/help.d.ts +8 -0
  27. package/dist/bin/help.js +192 -0
  28. package/dist/bin/help.js.map +1 -0
  29. package/dist/components/Application.d.ts +16 -1
  30. package/dist/components/Application.js +210 -38
  31. package/dist/components/Application.js.map +1 -1
  32. package/dist/components/ApplicationScope.d.ts +7 -0
  33. package/dist/components/ApplicationScope.js +22 -0
  34. package/dist/components/ApplicationScope.js.map +1 -1
  35. package/dist/components/EntryHandler.d.ts +4 -4
  36. package/dist/components/EntryHandler.js +386 -95
  37. package/dist/components/EntryHandler.js.map +1 -1
  38. package/dist/components/RuntimeModuleTracker.d.ts +11 -0
  39. package/dist/components/RuntimeModuleTracker.js +189 -0
  40. package/dist/components/RuntimeModuleTracker.js.map +1 -0
  41. package/dist/components/Scope.d.ts +1 -0
  42. package/dist/components/Scope.js +69 -22
  43. package/dist/components/Scope.js.map +1 -1
  44. package/dist/components/componentLoader.js +69 -10
  45. package/dist/components/componentLoader.js.map +1 -1
  46. package/dist/components/deployLifecycle.d.ts +6 -2
  47. package/dist/components/deployLifecycle.js +109 -31
  48. package/dist/components/deployLifecycle.js.map +1 -1
  49. package/dist/components/deploymentRecorder.d.ts +6 -0
  50. package/dist/components/deploymentRecorder.js +94 -12
  51. package/dist/components/deploymentRecorder.js.map +1 -1
  52. package/dist/components/mcp/tools/operations.js +6 -0
  53. package/dist/components/mcp/tools/operations.js.map +1 -1
  54. package/dist/components/operations.js +8 -6
  55. package/dist/components/operations.js.map +1 -1
  56. package/dist/config/configUtils.d.ts +6 -0
  57. package/dist/config/configUtils.js +78 -18
  58. package/dist/config/configUtils.js.map +1 -1
  59. package/dist/config/harperConfigEnvVars.js +108 -16
  60. package/dist/config/harperConfigEnvVars.js.map +1 -1
  61. package/dist/dataLayer/harperBridge/ResourceBridge.js +12 -5
  62. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  63. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -4
  64. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  65. package/dist/dataLayer/hdbInfoController.js +4 -0
  66. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  67. package/dist/dataLayer/schemaDescribe.js +2 -1
  68. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  69. package/dist/resources/DatabaseTransaction.d.ts +57 -0
  70. package/dist/resources/DatabaseTransaction.js +284 -104
  71. package/dist/resources/DatabaseTransaction.js.map +1 -1
  72. package/dist/resources/ResourceInterface.d.ts +8 -2
  73. package/dist/resources/ResourceInterface.js.map +1 -1
  74. package/dist/resources/Resources.js +22 -4
  75. package/dist/resources/Resources.js.map +1 -1
  76. package/dist/resources/Table.d.ts +8 -6
  77. package/dist/resources/Table.js +158 -31
  78. package/dist/resources/Table.js.map +1 -1
  79. package/dist/resources/analytics/write.js +6 -6
  80. package/dist/resources/analytics/write.js.map +1 -1
  81. package/dist/resources/auditStore.js +27 -1
  82. package/dist/resources/auditStore.js.map +1 -1
  83. package/dist/resources/blob.d.ts +0 -1
  84. package/dist/resources/blob.js +15 -7
  85. package/dist/resources/blob.js.map +1 -1
  86. package/dist/resources/databases.d.ts +22 -5
  87. package/dist/resources/databases.js +118 -9
  88. package/dist/resources/databases.js.map +1 -1
  89. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +15 -2
  90. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +128 -39
  91. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  92. package/dist/resources/jsResource.d.ts +4 -26
  93. package/dist/resources/jsResource.js +5 -59
  94. package/dist/resources/jsResource.js.map +1 -1
  95. package/dist/resources/models/Models.d.ts +11 -1
  96. package/dist/resources/models/Models.js +10 -1
  97. package/dist/resources/models/Models.js.map +1 -1
  98. package/dist/resources/models/backendRegistry.d.ts +9 -0
  99. package/dist/resources/models/backendRegistry.js +10 -0
  100. package/dist/resources/models/backendRegistry.js.map +1 -1
  101. package/dist/resources/models/openaiStream.d.ts +16 -1
  102. package/dist/resources/models/openaiStream.js +113 -21
  103. package/dist/resources/models/openaiStream.js.map +1 -1
  104. package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
  105. package/dist/resources/models/v1/chatCompletions.js +115 -0
  106. package/dist/resources/models/v1/chatCompletions.js.map +1 -0
  107. package/dist/resources/models/v1/embeddings.d.ts +11 -0
  108. package/dist/resources/models/v1/embeddings.js +71 -0
  109. package/dist/resources/models/v1/embeddings.js.map +1 -0
  110. package/dist/resources/models/v1/errors.d.ts +54 -0
  111. package/dist/resources/models/v1/errors.js +130 -0
  112. package/dist/resources/models/v1/errors.js.map +1 -0
  113. package/dist/resources/models/v1/index.d.ts +36 -0
  114. package/dist/resources/models/v1/index.js +75 -0
  115. package/dist/resources/models/v1/index.js.map +1 -0
  116. package/dist/resources/models/v1/models.d.ts +26 -0
  117. package/dist/resources/models/v1/models.js +44 -0
  118. package/dist/resources/models/v1/models.js.map +1 -0
  119. package/dist/resources/models/v1/translation.d.ts +133 -0
  120. package/dist/resources/models/v1/translation.js +298 -0
  121. package/dist/resources/models/v1/translation.js.map +1 -0
  122. package/dist/resources/roles.d.ts +1 -1
  123. package/dist/resources/roles.js +54 -7
  124. package/dist/resources/roles.js.map +1 -1
  125. package/dist/resources/search.d.ts +1 -1
  126. package/dist/resources/search.js +7 -3
  127. package/dist/resources/search.js.map +1 -1
  128. package/dist/security/jsLoader.js +84 -33
  129. package/dist/security/jsLoader.js.map +1 -1
  130. package/dist/security/role.js +4 -0
  131. package/dist/security/role.js.map +1 -1
  132. package/dist/security/superUserGuard.d.ts +7 -0
  133. package/dist/security/superUserGuard.js +23 -0
  134. package/dist/security/superUserGuard.js.map +1 -0
  135. package/dist/security/tokenAuthentication.d.ts +0 -1
  136. package/dist/security/tokenAuthentication.js +6 -2
  137. package/dist/security/tokenAuthentication.js.map +1 -1
  138. package/dist/security/user.d.ts +6 -1
  139. package/dist/security/user.js +23 -1
  140. package/dist/security/user.js.map +1 -1
  141. package/dist/server/http.d.ts +23 -2
  142. package/dist/server/http.js +99 -12
  143. package/dist/server/http.js.map +1 -1
  144. package/dist/server/operationsServer.d.ts +0 -2
  145. package/dist/server/operationsServer.js.map +1 -1
  146. package/dist/server/serverHelpers/multipartParser.js +9 -0
  147. package/dist/server/serverHelpers/multipartParser.js.map +1 -1
  148. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
  149. package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
  150. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
  151. package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
  152. package/dist/server/serverHelpers/registeredOperations.js +14 -13
  153. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  154. package/dist/server/serverHelpers/serverHandlers.js +19 -3
  155. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  156. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
  157. package/dist/server/serverHelpers/serverUtilities.js +10 -7
  158. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  159. package/dist/server/static.js +57 -18
  160. package/dist/server/static.js.map +1 -1
  161. package/dist/server/storageReclamation.d.ts +17 -0
  162. package/dist/server/storageReclamation.js +90 -6
  163. package/dist/server/storageReclamation.js.map +1 -1
  164. package/dist/server/threads/manageThreads.js +8 -0
  165. package/dist/server/threads/manageThreads.js.map +1 -1
  166. package/dist/server/threads/socketRouter.js +20 -0
  167. package/dist/server/threads/socketRouter.js.map +1 -1
  168. package/dist/server/threads/threadServer.js +12 -0
  169. package/dist/server/threads/threadServer.js.map +1 -1
  170. package/dist/sqlEngine/diff/differential.js +7 -3
  171. package/dist/sqlEngine/diff/differential.js.map +1 -1
  172. package/dist/sqlEngine/parser/normalizer.js +37 -9
  173. package/dist/sqlEngine/parser/normalizer.js.map +1 -1
  174. package/dist/sqlTranslator/index.js +6 -1
  175. package/dist/sqlTranslator/index.js.map +1 -1
  176. package/dist/upgrade/upgradePrompt.d.ts +2 -2
  177. package/dist/upgrade/upgradePrompt.js +22 -3
  178. package/dist/upgrade/upgradePrompt.js.map +1 -1
  179. package/dist/utility/environment/environmentManager.d.ts +11 -0
  180. package/dist/utility/environment/environmentManager.js +154 -0
  181. package/dist/utility/environment/environmentManager.js.map +1 -1
  182. package/dist/utility/errors/commonErrors.d.ts +1 -0
  183. package/dist/utility/errors/commonErrors.js +1 -0
  184. package/dist/utility/errors/commonErrors.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/install/installer.js +32 -0
  189. package/dist/utility/install/installer.js.map +1 -1
  190. package/dist/utility/lmdb/environmentUtility.js +2 -1
  191. package/dist/utility/lmdb/environmentUtility.js.map +1 -1
  192. package/dist/utility/logging/harper_logger.d.ts +7 -0
  193. package/dist/utility/logging/harper_logger.js +57 -24
  194. package/dist/utility/logging/harper_logger.js.map +1 -1
  195. package/npm-shrinkwrap.json +208 -198
  196. package/package.json +10 -8
  197. package/resources/DESIGN.md +1 -0
  198. package/resources/DatabaseTransaction.ts +313 -102
  199. package/resources/ResourceInterface.ts +8 -2
  200. package/resources/Resources.ts +22 -4
  201. package/resources/Table.ts +398 -259
  202. package/resources/analytics/write.ts +22 -20
  203. package/resources/auditStore.ts +32 -1
  204. package/resources/blob.ts +15 -8
  205. package/resources/databases.ts +123 -12
  206. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +119 -45
  207. package/resources/jsResource.ts +5 -62
  208. package/resources/models/Models.ts +14 -1
  209. package/resources/models/backendRegistry.ts +10 -0
  210. package/resources/models/openaiStream.ts +131 -19
  211. package/resources/models/v1/chatCompletions.ts +128 -0
  212. package/resources/models/v1/embeddings.ts +70 -0
  213. package/resources/models/v1/errors.ts +141 -0
  214. package/resources/models/v1/index.ts +72 -0
  215. package/resources/models/v1/models.ts +53 -0
  216. package/resources/models/v1/translation.ts +362 -0
  217. package/resources/roles.ts +67 -7
  218. package/resources/search.ts +8 -3
  219. package/security/jsLoader.ts +84 -30
  220. package/security/role.ts +7 -0
  221. package/security/superUserGuard.ts +20 -0
  222. package/security/tokenAuthentication.ts +6 -3
  223. package/security/user.ts +26 -1
  224. package/server/DESIGN.md +45 -34
  225. package/server/http.ts +100 -13
  226. package/server/operationsServer.ts +0 -2
  227. package/server/serverHelpers/multipartParser.ts +9 -0
  228. package/server/serverHelpers/operationAuthorizationState.ts +11 -0
  229. package/server/serverHelpers/registeredOperations.ts +19 -15
  230. package/server/serverHelpers/serverHandlers.js +20 -3
  231. package/server/serverHelpers/serverUtilities.ts +10 -7
  232. package/server/static.ts +75 -20
  233. package/server/storageReclamation.ts +104 -8
  234. package/server/threads/manageThreads.js +8 -0
  235. package/server/threads/socketRouter.ts +20 -0
  236. package/server/threads/threadServer.js +11 -0
  237. package/sqlTranslator/index.ts +6 -1
  238. package/static/defaultConfig.yaml +2 -0
  239. package/studio/web/assets/{Chat-DoVWScmq.js → Chat-BMUjPklt.js} +5 -5
  240. package/studio/web/assets/{Chat-DoVWScmq.js.map → Chat-BMUjPklt.js.map} +1 -1
  241. package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js → FloatingChat-Cbghuxe5.js} +5 -5
  242. package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js.map → FloatingChat-Cbghuxe5.js.map} +1 -1
  243. package/studio/web/assets/{apiToken-BUI_04o7.js → apiToken-CkgTQAqc.js} +2 -2
  244. package/studio/web/assets/{apiToken-BUI_04o7.js.map → apiToken-CkgTQAqc.js.map} +1 -1
  245. package/studio/web/assets/{applications-D03NA7wW.js → applications-WvF4NOTN.js} +13 -13
  246. package/studio/web/assets/applications-WvF4NOTN.js.map +1 -0
  247. package/studio/web/assets/chevron-right-anDJCGlp.js +2 -0
  248. package/studio/web/assets/chevron-right-anDJCGlp.js.map +1 -0
  249. package/studio/web/assets/index-CDeqVx71.css +1 -0
  250. package/studio/web/assets/index-Sevt3MVx.js +819 -0
  251. package/studio/web/assets/index-Sevt3MVx.js.map +1 -0
  252. package/studio/web/assets/index.lazy-Dg5YYPGg.js +14 -0
  253. package/studio/web/assets/{index.lazy-Dx3MpyDC.js.map → index.lazy-Dg5YYPGg.js.map} +1 -1
  254. package/studio/web/assets/{jsonMode-Cjj7dtlr.js → jsonMode-CBSEleE7.js} +2 -2
  255. package/studio/web/assets/{jsonMode-Cjj7dtlr.js.map → jsonMode-CBSEleE7.js.map} +1 -1
  256. package/studio/web/assets/languageServices-gxedeeTo.js +2 -0
  257. package/studio/web/assets/languageServices-gxedeeTo.js.map +1 -0
  258. package/studio/web/assets/{notifications-0edoFTsb.js → notifications-Ap84YyQp.js} +2 -2
  259. package/studio/web/assets/{notifications-0edoFTsb.js.map → notifications-Ap84YyQp.js.map} +1 -1
  260. package/studio/web/assets/notifications-vehqyOLM.js +2 -0
  261. package/studio/web/assets/{notifications-CwKhipK7.js.map → notifications-vehqyOLM.js.map} +1 -1
  262. package/studio/web/assets/{pollUnlessForbidden-HHdQZW1N.js → pollUnlessForbidden-BhzefVRn.js} +2 -2
  263. package/studio/web/assets/pollUnlessForbidden-BhzefVRn.js.map +1 -0
  264. package/studio/web/assets/{profile-DUfEPQtx.js → profile-DzDpRLHY.js} +2 -2
  265. package/studio/web/assets/{profile-DUfEPQtx.js.map → profile-DzDpRLHY.js.map} +1 -1
  266. package/studio/web/assets/queryClient-CgtkoiCY.js +2 -0
  267. package/studio/web/assets/queryClient-CgtkoiCY.js.map +1 -0
  268. package/studio/web/assets/regions-DpJ8s7iN.js +2 -0
  269. package/studio/web/assets/regions-DpJ8s7iN.js.map +1 -0
  270. package/studio/web/assets/register-BDy01-q3.js +3 -0
  271. package/studio/web/assets/register-BDy01-q3.js.map +1 -0
  272. package/studio/web/assets/register-CQHRwNP4.js +3 -0
  273. package/studio/web/assets/register-CQHRwNP4.js.map +1 -0
  274. package/studio/web/assets/{setComponentFile-DMPo4UjC.js → setComponentFile-D0aPcI3W.js} +2 -2
  275. package/studio/web/assets/{setComponentFile-DMPo4UjC.js.map → setComponentFile-D0aPcI3W.js.map} +1 -1
  276. package/studio/web/assets/setup-I15FAF9H.js +3 -0
  277. package/studio/web/assets/{setup-B56Oz1_u.js.map → setup-I15FAF9H.js.map} +1 -1
  278. package/studio/web/assets/status-BF1eJshJ.js +62 -0
  279. package/studio/web/assets/status-BF1eJshJ.js.map +1 -0
  280. package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js → swagger-ui-react-DwSPXXhl.js} +2 -2
  281. package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js.map → swagger-ui-react-DwSPXXhl.js.map} +1 -1
  282. package/studio/web/assets/{textarea-DhLMZ3PA.js → table-DpOjNQHt.js} +2 -10
  283. package/studio/web/assets/table-DpOjNQHt.js.map +1 -0
  284. package/studio/web/assets/{tsMode-CrHCRjTK.js → tsMode-Bu51A-VG.js} +2 -2
  285. package/studio/web/assets/{tsMode-CrHCRjTK.js.map → tsMode-Bu51A-VG.js.map} +1 -1
  286. package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js → useEntityRestURL-DrV1zMrN.js} +2 -2
  287. package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js.map → useEntityRestURL-DrV1zMrN.js.map} +1 -1
  288. package/studio/web/index.html +6 -7
  289. package/upgrade/upgradePrompt.ts +22 -3
  290. package/utility/environment/environmentManager.ts +152 -0
  291. package/utility/errors/commonErrors.ts +2 -0
  292. package/utility/hdbTerms.ts +2 -0
  293. package/utility/install/installer.ts +37 -0
  294. package/utility/lmdb/environmentUtility.ts +2 -1
  295. package/utility/logging/harper_logger.ts +57 -26
  296. package/studio/web/assets/applications-D03NA7wW.js.map +0 -1
  297. package/studio/web/assets/index-Bh_CNAHr.js +0 -809
  298. package/studio/web/assets/index-Bh_CNAHr.js.map +0 -1
  299. package/studio/web/assets/index-C8Mv9kHV.css +0 -1
  300. package/studio/web/assets/index.lazy-Dx3MpyDC.js +0 -14
  301. package/studio/web/assets/notifications-CwKhipK7.js +0 -2
  302. package/studio/web/assets/pollUnlessForbidden-HHdQZW1N.js.map +0 -1
  303. package/studio/web/assets/queryClient-CbA8wM7J.js +0 -2
  304. package/studio/web/assets/queryClient-CbA8wM7J.js.map +0 -1
  305. package/studio/web/assets/setup-B56Oz1_u.js +0 -3
  306. package/studio/web/assets/status-BAod7p3o.js +0 -62
  307. package/studio/web/assets/status-BAod7p3o.js.map +0 -1
  308. package/studio/web/assets/textarea-DhLMZ3PA.js.map +0 -1
package/bin/copyDb.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { getDatabases, getDefaultCompression, resetDatabases } from '../resources/databases.ts';
1
+ import {
2
+ getDatabases,
3
+ getDefaultCompression,
4
+ resetDatabases,
5
+ getRocksCompression,
6
+ toRocksCompression,
7
+ } from '../resources/databases.ts';
2
8
  import { open, asBinary } from 'lmdb';
3
9
  import { join } from 'path';
4
10
  import { move, remove } from 'fs-extra';
@@ -329,6 +335,10 @@ export function shapeForStructure(value: any): any {
329
335
 
330
336
  function openRocksDb(path: string, options: RocksDatabaseOptions & { dupSort?: boolean } = {}) {
331
337
  options.disableWAL ??= false;
338
+ // Migration creates a complete replacement database, so use the deployment codec for the files
339
+ // it writes; runtime opens additionally reconcile pre-existing sibling column families.
340
+ const legacyOptions = options as { compression?: unknown };
341
+ legacyOptions.compression = getRocksCompression() ?? toRocksCompression(legacyOptions.compression);
332
342
  if (!existsSync(path)) {
333
343
  mkdirSync(path, { recursive: true });
334
344
  }
@@ -444,15 +454,22 @@ function countRecords(rawDbi): { records: number; unversioned: number } {
444
454
  * the RocksDB lock — run in-process (inspector) on a live instance, or offline.
445
455
  */
446
456
  export function verifyMigratedDatabase(databasePath: string): Record<string, { records: number; unversioned: number }> {
447
- const rootStore = RocksDatabase.open(databasePath, {});
448
- const dbisDb = RocksDatabase.open(databasePath, {
449
- name: INTERNAL_DBIS_NAME,
450
- sharedStructuresKey: Symbol.for('structures'),
451
- });
457
+ // Every open handle, so a failure at any point (e.g. the second open throwing on lock
458
+ // contention) cannot leak an earlier handle that would hold the RocksDB lock on the very
459
+ // diagnostic path operators use after a broken migration.
460
+ const handles: RocksDatabase[] = [];
452
461
  const report: Record<string, { records: number; unversioned: number }> = {};
453
462
  try {
463
+ handles.push(RocksDatabase.open(databasePath, {}));
464
+ const dbisDb = RocksDatabase.open(databasePath, {
465
+ name: INTERNAL_DBIS_NAME,
466
+ sharedStructuresKey: Symbol.for('structures'),
467
+ });
468
+ handles.push(dbisDb);
454
469
  for (const { key, value: attribute } of dbisDb.getRange({})) {
455
470
  if (typeof key === 'symbol' || !attribute?.isPrimaryKey) continue;
471
+ // per-table handles close per-iteration so a many-table sweep does not hold every CF
472
+ // handle open at once; only the two pre-loop opens need the leak-safety array
456
473
  const rawDbi = RocksDatabase.open(databasePath, { name: key, encoding: false });
457
474
  try {
458
475
  report[key] = countRecords(rawDbi);
@@ -461,8 +478,13 @@ export function verifyMigratedDatabase(databasePath: string): Record<string, { r
461
478
  }
462
479
  }
463
480
  } finally {
464
- dbisDb.close();
465
- rootStore.close();
481
+ for (const handle of handles.reverse()) {
482
+ try {
483
+ handle.close();
484
+ } catch (error) {
485
+ console.error('Error closing verification store', error);
486
+ }
487
+ }
466
488
  }
467
489
  return report;
468
490
  }
@@ -510,7 +532,14 @@ export async function migrateDatabaseToRocks(sourceRootStore, databaseName: stri
510
532
  export async function copyDbToRocks(sourceRootStore, sourceDatabase: string, targetPath: string) {
511
533
  console.log(`Migrating database ${sourceDatabase} to RocksDB at ${targetPath}`);
512
534
  const sourceDbisDb = sourceRootStore.dbisDb;
513
-
535
+ // Runtime Harper stores disable RocksDB's native WAL for data/index column families and recover
536
+ // them from rocksdb-js transaction logs. This copier does not write those transaction logs, so
537
+ // only use the same fast write path for migrateOnStart's disposable staging directory: after an
538
+ // interruption it is deleted and recopied from LMDB. Direct copyDbToRocks callers retain WAL.
539
+ const disableDataWAL = targetPath.endsWith(MIGRATING_DIR_SUFFIX);
540
+
541
+ // Keep native WAL for the root/log-owner and __dbis__ handles, matching Harper's runtime policy.
542
+ // Their migration writes are metadata-sized; the 600 GB bulk is in the data/index handles below.
514
543
  const targetRootStore = openRocksDb(targetPath, { disableWAL: false });
515
544
  // Every handle opened on targetPath. All must be closed before returning so the caller can
516
545
  // atomically rename a staging directory into place — rocksdb-js registers descriptors by
@@ -587,10 +616,10 @@ export async function copyDbToRocks(sourceRootStore, sourceDatabase: string, tar
587
616
  let observerEncoder: any;
588
617
  let canonicalStructures: any;
589
618
  if (!isPrimary) {
590
- targetDbi = openRocksDb(targetPath, { dupSort: true, name: key });
619
+ targetDbi = openRocksDb(targetPath, { disableWAL: disableDataWAL, dupSort: true, name: key });
591
620
  targetHandles.push(targetDbi);
592
621
  } else {
593
- targetDbi = openRocksDb(targetPath, { name: key });
622
+ targetDbi = openRocksDb(targetPath, { disableWAL: disableDataWAL, name: key });
594
623
  targetHandles.push(targetDbi);
595
624
  // Patch the existing encoder (encoder is a getter-only property on RocksDatabase, cannot be replaced)
596
625
  // to install RecordEncoder's encode method so metadata headers (timestamps, HAS_BLOBS flag) are written
package/bin/harper.ts CHANGED
@@ -5,6 +5,7 @@ import * as fs from 'node:fs';
5
5
  import * as path from 'node:path';
6
6
  import logger from '../utility/logging/harper_logger.ts';
7
7
  import * as cliOperations from './cliOperations.ts';
8
+ import { help } from './help.ts';
8
9
  import { packageJson } from '../utility/packageUtils.js';
9
10
  import checkNode from '../launchServiceScripts/utility/checkNodeVersion.js';
10
11
  import * as hdbTerms from '../utility/hdbTerms.ts';
@@ -13,55 +14,6 @@ if (typeof process.setSourceMapsEnabled === 'function') {
13
14
  process.setSourceMapsEnabled(true); // this is necessary for source maps to work, at least on the main thread.
14
15
  }
15
16
 
16
- const HELP = `
17
- Usage: harperdb [command]
18
-
19
- With no command, harper will simply run Harper (in the foreground)
20
-
21
- Documentation: https://docs.harperdb.io/
22
-
23
- By default, the CLI also supports certain Operation APIs. Specify the operation name and any required parameters, and omit the 'operation' command.
24
-
25
- Commands:
26
- agent [message] - Chat with the built-in agent (interactive, or one-shot with a message; alias: chat)
27
- copy-db <source> <target> - Copies a database from source path to target path
28
- dev <path> - Run the application in dev mode with debugging, foreground logging, no auth
29
- install - Install harperdb
30
- <api-operation> <param>=<value> - Run an API operation and return result to the CLI, not all operations are supported
31
- To authenticate as a different user than the one being operated on
32
- (e.g. add_user/alter_user), set HARPER_CLI_USERNAME/HARPER_CLI_PASSWORD
33
- or run 'harper login'. The equivalent auth_username=<value>
34
- auth_password=<value> args also work, but a password passed as an
35
- argument is exposed in shell history, process listings and CI logs.
36
- A saved login token always outranks username=/password=, so a
37
- stale token that fails to refresh will 401 rather than falling
38
- back to them — run 'harper logout' or pass auth_username=/
39
- auth_password= to override it.
40
- login [target] [username] - Login to a remote or local Harper instance
41
- --for-ci prints the CI/CD credentials (target + long-lived
42
- refresh token) to stdout in dotenv format, and everything else
43
- to stderr, so it pipes without the token hitting your screen:
44
- harper login --for-ci | gh secret set --env-file -
45
- Log in as a user dedicated to that one CI consumer: Harper
46
- stores a single refresh token per user, so this revokes any
47
- refresh token that user already holds — another runner, another
48
- machine, or an earlier 'harper login' will 401 on its next
49
- refresh. Two consumers cannot share a user.
50
- logout [target] - Logout from Harper and clear saved JWT
51
- mcp [subcommand] - MCP stdio bridge / print-config / doctor (see 'harper mcp help')
52
- register - Register harperdb
53
- renew-certs - Generate a new set of self-signed certificates
54
- restart - Restart the harperdb background process
55
- run <path> - Run the application in the specified path
56
- start - Starts a separate background process for harperdb and CLI will exit
57
- status - Print the status of Harper
58
- stop - Stop the harperdb background process
59
- help - Display this output
60
- upgrade - Upgrade harperdb
61
- version - Print the version
62
- deploy - Deploy the application locally or remotely with target=<remote url>
63
- `;
64
-
65
17
  /**
66
18
  * Format a CLI error for the terminal. Expected, user-facing errors (a `ClientError` from an
67
19
  * operation — bad args, not found, a locked backup repo — which carry a numeric `statusCode`) get
@@ -74,6 +26,19 @@ export function formatCliError(error: any): string {
74
26
  return message;
75
27
  }
76
28
 
29
+ /**
30
+ * Whether a `-h`/`--help` anywhere in `argv` should print the top-level help. Returns false for the
31
+ * subcommands that own their own `--help` — mcp and agent/chat parse `process.argv.slice(3)`
32
+ * themselves downstream — so `harper mcp --help` reaches the mcp handler instead of this help.
33
+ */
34
+ export function wantsTopLevelHelp(argv: readonly string[], service: string | undefined): boolean {
35
+ const delegatesHelp =
36
+ service === SERVICE_ACTIONS_ENUM.MCP ||
37
+ service === SERVICE_ACTIONS_ENUM.AGENT ||
38
+ service === SERVICE_ACTIONS_ENUM.CHAT;
39
+ return !delegatesHelp && (argv.includes('-h') || argv.includes('--help'));
40
+ }
41
+
77
42
  async function harper() {
78
43
  let nodeResults = checkNode();
79
44
 
@@ -90,13 +55,17 @@ async function harper() {
90
55
 
91
56
  let service;
92
57
 
93
- if (process.argv && process.argv[2] && !process.argv[2].startsWith('-')) {
58
+ if (process.argv?.[2] && !process.argv[2].startsWith('-')) {
94
59
  service = process.argv[2].toLowerCase();
95
60
  }
96
61
 
62
+ if (wantsTopLevelHelp(process.argv, service)) {
63
+ return help();
64
+ }
65
+
97
66
  switch (service) {
98
67
  case SERVICE_ACTIONS_ENUM.HELP:
99
- return HELP;
68
+ return help();
100
69
  case SERVICE_ACTIONS_ENUM.START:
101
70
  return require('./run').launch();
102
71
  case SERVICE_ACTIONS_ENUM.INSTALL:
package/bin/help.ts ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * The `harper help` / `harper -h` output, kept as structured data so {@link help} can render it to
3
+ * the current terminal width (capped at 120 columns) at call time. Content lives separately from
4
+ * layout: editing the text can't break the wrapping, command names stay column-aligned across
5
+ * sections, and the CI/CD sample stays a verbatim, copy-pasteable line at any width.
6
+ */
7
+
8
+ const MAX_WIDTH = 120;
9
+ const INDENT = 2; // leading spaces for command names and detail blocks
10
+ const DASH = ' - '; // separator between a command name and its description
11
+ // Minimum description width for the two-column command layout. On terminals too narrow for it
12
+ // (width < descCol + this), command rows stack the name above an indented description instead of
13
+ // overflowing. Set to the widest word in any command description so two-column never overflows.
14
+ const MIN_DESC_WIDTH = 20;
15
+
16
+ /** A paragraph of prose, reflowed to the target width at `indent` spaces. */
17
+ interface TextBlock {
18
+ text: string;
19
+ indent?: number;
20
+ }
21
+
22
+ /** A verbatim line (a code sample); never joined or wrapped so it stays copy-pasteable. */
23
+ interface PreBlock {
24
+ pre: string;
25
+ indent?: number;
26
+ }
27
+
28
+ /** A two-column list of `[name, description]` rows; descriptions wrap with a hanging indent. */
29
+ interface CommandsBlock {
30
+ commands: [name: string, description: string][];
31
+ }
32
+
33
+ type Block = TextBlock | PreBlock | CommandsBlock;
34
+
35
+ interface Section {
36
+ heading?: string;
37
+ blocks: Block[];
38
+ }
39
+
40
+ const SECTIONS: Section[] = [
41
+ { blocks: [{ text: 'Usage: harperdb [command]' }] },
42
+ { blocks: [{ text: 'With no command, harper will simply run Harper (in the foreground)' }] },
43
+ { blocks: [{ text: 'Documentation: https://docs.harperdb.io/' }] },
44
+ {
45
+ blocks: [
46
+ {
47
+ text: [
48
+ 'By default, the CLI also supports certain Operation APIs. Specify the operation name and any required',
49
+ "parameters, and omit the 'operation' command.",
50
+ ].join(' '),
51
+ },
52
+ ],
53
+ },
54
+ { blocks: [{ text: 'Commands:' }] },
55
+ {
56
+ heading: 'Server',
57
+ blocks: [
58
+ {
59
+ commands: [
60
+ ['start', 'Starts a separate background process for harperdb and CLI will exit'],
61
+ ['stop', 'Stop the harperdb background process'],
62
+ ['restart', 'Restart the harperdb background process'],
63
+ ['status', 'Print the status of Harper'],
64
+ ],
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ heading: 'Applications',
70
+ blocks: [
71
+ {
72
+ commands: [
73
+ ['run <path>', 'Run the application in the specified path'],
74
+ ['dev <path>', 'Run the application in dev mode with debugging, foreground logging, no auth'],
75
+ ['deploy', 'Deploy the application locally or remotely with target=<remote url>'],
76
+ ],
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ heading: 'Install & maintenance',
82
+ blocks: [
83
+ {
84
+ commands: [
85
+ ['install', 'Install harperdb'],
86
+ ['upgrade', 'Upgrade harperdb'],
87
+ ['register', 'Register harperdb'],
88
+ ['renew-certs', 'Generate a new set of self-signed certificates'],
89
+ ['copy-db <source> <target>', 'Copies a database from source path to target path'],
90
+ ['version', 'Print the version'],
91
+ ['help', 'Display this output'],
92
+ ],
93
+ },
94
+ ],
95
+ },
96
+ {
97
+ heading: 'Accounts',
98
+ blocks: [
99
+ { pre: 'login [target] [username]', indent: INDENT },
100
+ {
101
+ text: [
102
+ 'Login to a remote or local Harper instance. --for-ci prints the CI/CD credentials (target +',
103
+ 'long-lived refresh token) to stdout in dotenv format, and everything else to stderr, so it pipes',
104
+ 'without the token hitting your screen:',
105
+ ].join(' '),
106
+ indent: 4,
107
+ },
108
+ { pre: 'harper login --for-ci | gh secret set --env-file -', indent: 6 },
109
+ {
110
+ text: [
111
+ 'Log in as a user dedicated to that one CI consumer: Harper stores a single refresh token per user, so',
112
+ 'this revokes any refresh token that user already holds — another runner, another machine, or an earlier',
113
+ "'harper login' will 401 on its next refresh. Two consumers cannot share a user.",
114
+ ].join(' '),
115
+ indent: 4,
116
+ },
117
+ { commands: [['logout [target]', 'Logout from Harper and clear saved JWT']] },
118
+ ],
119
+ },
120
+ {
121
+ heading: 'Assistants',
122
+ blocks: [
123
+ {
124
+ commands: [
125
+ ['agent [message]', 'Chat with the built-in agent (interactive, or one-shot with a message; alias: chat)'],
126
+ ['mcp [subcommand]', "MCP stdio bridge / print-config / doctor (see 'harper mcp help')"],
127
+ ],
128
+ },
129
+ ],
130
+ },
131
+ {
132
+ heading: 'Operations API',
133
+ blocks: [
134
+ { pre: '<api-operation> <param>=<value>', indent: INDENT },
135
+ {
136
+ text: [
137
+ 'Run an API operation and return the result to the CLI (not all operations are supported). See the full',
138
+ 'list of operations at: https://docs.harperdb.io/reference/v5/operations-api/operations',
139
+ ].join(' '),
140
+ indent: 4,
141
+ },
142
+ {
143
+ text: [
144
+ 'To authenticate as a different user than the one being operated on (e.g. add_user/alter_user), set',
145
+ "HARPER_CLI_USERNAME/HARPER_CLI_PASSWORD or run 'harper login'. The equivalent auth_username=<value>",
146
+ 'auth_password=<value> args also work, but a password passed as an argument is exposed in shell',
147
+ 'history, process listings and CI logs. A saved login token always outranks username=/password=, so a',
148
+ "stale token that fails to refresh will 401 rather than falling back to them — run 'harper logout' or",
149
+ 'pass auth_username=/auth_password= to override it.',
150
+ ].join(' '),
151
+ indent: 4,
152
+ },
153
+ ],
154
+ },
155
+ ];
156
+
157
+ const pad = (count: number): string => ' '.repeat(count);
158
+
159
+ /** Greedy word wrap. Never splits a token, so URLs and flags survive intact (they overflow). */
160
+ function wrap(text: string, width: number): string[] {
161
+ const words = text.split(/\s+/).filter(Boolean);
162
+ if (words.length === 0) return [];
163
+ const lines: string[] = [];
164
+ let line = words[0];
165
+ for (let i = 1; i < words.length; i++) {
166
+ if (line.length + 1 + words[i].length > width) {
167
+ lines.push(line);
168
+ line = words[i];
169
+ } else {
170
+ line += ' ' + words[i];
171
+ }
172
+ }
173
+ lines.push(line);
174
+ return lines;
175
+ }
176
+
177
+ function renderBlock(block: Block, width: number, nameWidth: number, descCol: number): string[] {
178
+ if ('commands' in block) {
179
+ // Two columns when the terminal leaves a readable description column; otherwise stack the name
180
+ // above an indented description so narrow terminals don't overflow the command rows.
181
+ const twoColumn = width - descCol >= MIN_DESC_WIDTH;
182
+ return block.commands.flatMap(([name, description]) => {
183
+ if (twoColumn) {
184
+ const wrapped = wrap(description, width - descCol);
185
+ const head = pad(INDENT) + name.padEnd(nameWidth) + DASH;
186
+ return [head + (wrapped[0] ?? ''), ...wrapped.slice(1).map((line) => pad(descCol) + line)];
187
+ }
188
+ const detail = INDENT * 2;
189
+ return [pad(INDENT) + name, ...wrap(description, Math.max(1, width - detail)).map((line) => pad(detail) + line)];
190
+ });
191
+ }
192
+ if ('pre' in block) return [pad(block.indent ?? 0) + block.pre];
193
+ const indent = block.indent ?? 0;
194
+ return wrap(block.text, Math.max(1, width - indent)).map((line) => pad(indent) + line);
195
+ }
196
+
197
+ /** Render the CLI help, wrapped to the terminal width (capped at 120, and 120 when not a TTY). */
198
+ export function help(): string {
199
+ const width = Math.min(process.stdout.columns || MAX_WIDTH, MAX_WIDTH);
200
+
201
+ // One shared description column across every command list so names line up between sections,
202
+ // driven by the longest command name.
203
+ const names = SECTIONS.flatMap((section) =>
204
+ section.blocks.flatMap((block) => ('commands' in block ? block.commands.map(([name]) => name) : []))
205
+ );
206
+ const nameWidth = Math.max(...names.map((name) => name.length));
207
+ const descCol = INDENT + nameWidth + DASH.length;
208
+
209
+ const lines: string[] = [];
210
+ for (const section of SECTIONS) {
211
+ if (lines.length) lines.push(''); // blank line between sections
212
+ if (section.heading) lines.push(section.heading);
213
+ for (const block of section.blocks) lines.push(...renderBlock(block, width, nameWidth, descCol));
214
+ }
215
+ return '\n' + lines.join('\n') + '\n';
216
+ }