@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
@@ -27,6 +27,7 @@ import { errorToString } from '../../utility/logging/harper_logger.ts';
27
27
  import { RocksDatabase } from '@harperfast/rocksdb-js';
28
28
  import { BridgeMethods } from './BridgeMethods.ts';
29
29
  import lmdbGetBackup from './lmdbBridge/lmdbMethods/lmdbGetBackup.js';
30
+ import { createBackupStream, resolveSingleRootStore } from '../rocksdbBackup.ts';
30
31
  import { DeleteTransactionLogsBeforeResults } from './DeleteTransactionLogsBeforeResults.ts';
31
32
  import type { Readable } from 'node:stream';
32
33
 
@@ -491,12 +492,25 @@ export class ResourceBridge extends BridgeMethods {
491
492
  : typeof deleteObj.timestamp === 'string'
492
493
  ? Number.parseInt(deleteObj.timestamp)
493
494
  : deleteObj.timestamp;
495
+ const databaseName = deleteObj.database || deleteObj.schema || DEFAULT_DATABASE;
494
496
  const table = getTable(deleteObj);
497
+ // A nonexistent table must not fall through to the no-table branch below — on RocksDB that
498
+ // widens a table-scoped request (e.g. a typo) into a whole-database log purge (#2049).
499
+ // Presence check, not truthiness: a table named "0" addressed numerically is still table-scoped.
500
+ if (deleteObj.table != null && !table)
501
+ throw handleHDBError(
502
+ new Error(),
503
+ HDB_ERROR_MSGS.TABLE_NOT_FOUND(databaseName, deleteObj.table),
504
+ 404,
505
+ undefined,
506
+ undefined,
507
+ true
508
+ );
495
509
  if (!table) {
496
510
  // no table, check if any of the tables are RocksDB
497
511
  // since all tables share the same transaction log store, we break after the first
498
512
  // RocksDB table is found
499
- const tables = getDatabases()[deleteObj.database];
513
+ const tables = getDatabases()[databaseName];
500
514
  if (tables) {
501
515
  for (const table of Object.values(tables)) {
502
516
  if (table.primaryStore instanceof RocksDatabase) {
@@ -508,9 +522,16 @@ export class ResourceBridge extends BridgeMethods {
508
522
  }
509
523
  }
510
524
  } else if (table.primaryStore instanceof RocksDatabase) {
511
- const deleted = table.primaryStore.purgeLogs({ before, includeEntryCounts: true });
512
- totalResults.log_files_deleted += deleted.length;
513
- totalResults.entries_deleted += deleted.reduce((acc, file) => acc + file.entries, 0);
525
+ // All tables in a RocksDB database share one transaction log with no per-table purge
526
+ // granularity; honoring `table` here would silently purge every sibling table's log (#2049).
527
+ throw handleHDBError(
528
+ new Error(),
529
+ `Table-level transaction log deletion is not supported for RocksDB tables because all tables in a database share one transaction log; to delete the transaction logs for the entire '${databaseName}' database, use delete_transaction_logs_before with only 'database' and 'timestamp'`,
530
+ 400,
531
+ undefined,
532
+ undefined,
533
+ true
534
+ );
514
535
  } else {
515
536
  totalResults.entries_deleted += await table.deleteHistory(before, deleteObj.cleanup_deleted_records);
516
537
  }
@@ -563,7 +584,49 @@ export class ResourceBridge extends BridgeMethods {
563
584
  schema?: string;
564
585
  table?: string;
565
586
  tables?: string[];
587
+ include_audit?: boolean;
588
+ gzip?: boolean;
589
+ exclude_blobs?: boolean;
566
590
  }): Promise<Readable> {
591
+ const databaseName = getBackupObj.database || getBackupObj.schema || 'data';
592
+ const database = getDatabases()[databaseName];
593
+ if (!database) {
594
+ throw new ClientError(`Database '${databaseName}' does not exist`, 404);
595
+ }
596
+ const firstTable = database[Object.keys(database)[0]];
597
+ if (!firstTable) {
598
+ throw new ClientError(`Database '${databaseName}' has no tables to back up`);
599
+ }
600
+ if (firstTable.primaryStore.rootStore instanceof RocksDatabase) {
601
+ // RocksDB: stream a fresh full-snapshot tar of the database's current state — no
602
+ // scratch disk, nothing to clean up. Per-engine params are validated descriptively.
603
+ if (getBackupObj.tables || getBackupObj.table) {
604
+ throw new ClientError(`'tables'/'table' are LMDB-only options; RocksDB backups are always whole-database`);
605
+ }
606
+ if (getBackupObj.include_audit !== undefined) {
607
+ throw new ClientError(
608
+ `'include_audit' is an LMDB-only option; RocksDB backups always include the transaction log`
609
+ );
610
+ }
611
+ if (getBackupObj.gzip !== undefined && typeof getBackupObj.gzip !== 'boolean') {
612
+ throw new ClientError(`'gzip' must be a boolean`);
613
+ }
614
+ if (getBackupObj.exclude_blobs !== undefined && typeof getBackupObj.exclude_blobs !== 'boolean') {
615
+ throw new ClientError(`'exclude_blobs' must be a boolean`);
616
+ }
617
+ const rootStore = resolveSingleRootStore(databaseName);
618
+ // gzip defaults on (it compresses the snapshot substantially); gzip=false opts out.
619
+ // blobs are included by default; exclude_blobs=true streams an engine-only tar.
620
+ return createBackupStream(
621
+ rootStore,
622
+ databaseName,
623
+ getBackupObj.gzip !== false,
624
+ getBackupObj.exclude_blobs === true
625
+ );
626
+ }
627
+ if (getBackupObj.gzip !== undefined) {
628
+ throw new ClientError(`'gzip' is a RocksDB-only option; LMDB backups are gzipped per the accept-encoding header`);
629
+ }
567
630
  return lmdbGetBackup(getBackupObj);
568
631
  }
569
632
  }
@@ -655,7 +718,8 @@ function getTable(operationObject: { database?: string; schema?: string; table?:
655
718
  const databaseName = operationObject.database || operationObject.schema || DEFAULT_DATABASE;
656
719
  const tables = getDatabases()[databaseName];
657
720
  if (!tables) throw handleHDBError(new Error(), HDB_ERROR_MSGS.SCHEMA_NOT_FOUND(databaseName), 404);
658
- return operationObject.table ? tables[operationObject.table] : undefined;
721
+ // Presence check, not truthiness, so a table named "0" resolves when addressed numerically.
722
+ return operationObject.table != null ? tables[operationObject.table] : undefined;
659
723
  }
660
724
 
661
725
  /**
@@ -183,15 +183,23 @@ export async function getVersionUpdateInfo() {
183
183
  `You have installed a version lower than the version that your data was created on or was upgraded to. This may cause issues and is currently not supported.${os.EOL}${hdbTerms.SUPPORT_HELP_MSG}`
184
184
  )
185
185
  );
186
+ log.error(
187
+ `This instance's data was last run on version ${dataVersion}, which is newer than this installed version ${upgradeVersion}. Downgrading across major versions is not supported.`
188
+ );
186
189
  throw new Error('Trying to downgrade major HDB versions is not supported.');
187
190
  }
188
191
  if (!hdbUtils.isCompatibleDataVersion(dataVersion.toString(), upgradeVersion.toString(), true)) {
189
192
  console.log(chalk.yellow(`This instance's data was last run on version ${dataVersion}`));
193
+ log.warn(
194
+ `This instance's data was last run on version ${dataVersion}, which is newer than this installed version ${upgradeVersion}. Confirmation is required before running the older version against this data.`
195
+ );
190
196
 
191
197
  if (await forceDowngradePrompt(new UpgradeObject(dataVersion, upgradeVersion))) {
192
198
  await insertHdbUpgradeInfo(upgradeVersion.toString());
199
+ log.notify(`Downgrade confirmed; data version recorded as ${upgradeVersion}.`);
193
200
  } else {
194
201
  console.log('Cancelled downgrade, closing Harper');
202
+ log.notify('Cancelled downgrade, closing Harper');
195
203
  process.exit(0);
196
204
  }
197
205
  }
@@ -0,0 +1,276 @@
1
+ 'use strict';
2
+
3
+ import {
4
+ closeSync,
5
+ existsSync,
6
+ fsyncSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readdirSync,
10
+ readFileSync,
11
+ unlinkSync,
12
+ writeSync,
13
+ } from 'node:fs';
14
+ import { basename, dirname, join } from 'node:path';
15
+ import { createHash } from 'node:crypto';
16
+ import { tryFileLock, fileLockRelease } from '@harperfast/rocksdb-js';
17
+
18
+ /**
19
+ * Restore lock + marker protocol for RocksDB database restores (online operation and offline CLI),
20
+ * and the shared per-database exclusion used by `dropDatabase` so a drop and a restore can never
21
+ * mutate the same directory concurrently.
22
+ *
23
+ * Restore metadata lives in an isolated `` `restore` `` directory *beside* the database directory
24
+ * (never inside it, since a restore purges the destination). Each database's two files are keyed by
25
+ * a hash of the database directory name rather than being suffixed onto the name itself. That keeps
26
+ * them out of the database-name namespace — a legal database literally named `orders.restoring`
27
+ * would otherwise be mistaken for the restore marker of `orders`, and a 250-character name plus a
28
+ * `.restore.lock` suffix would exceed the 255-byte `NAME_MAX` on most filesystems. The directory
29
+ * name deliberately contains a backtick: `schemaRegex` (the database-name validator) forbids only
30
+ * `/` and `` ` `` among filesystem-legal characters, so no legal database can ever occupy this path
31
+ * — including a database literally named `.restore` (which *is* a legal name, so a plain `.restore`
32
+ * directory would collide with it and land the markers inside the live database). The directory is
33
+ * not itself a RocksDB/LMDB database (no `CURRENT`/`MANIFEST-`/`.mdb`), so the startup scan ignores
34
+ * it, and no user can create a database that resolves to it.
35
+ *
36
+ * - `<meta-dir>/<key>.lock` — an OS-level exclusive file lock (via rocksdb-js `tryFileLock`),
37
+ * effective across processes, containers, and worker threads, auto-released on process exit.
38
+ * Only *held-ness* is meaningful; the file itself persists after release (harmless). Held for the
39
+ * duration of a restore, and briefly by `dropDatabase` so the two serialize on the same primitive.
40
+ * Known limitation: the lock is owned by the process, so if the restore job's worker *thread*
41
+ * dies without the process exiting, the lock stays held (restores 409) until Harper restarts.
42
+ * - `<meta-dir>/<key>.restoring` — the completion marker. Written (and fsynced) after the lock is
43
+ * acquired and before the destructive restore begins; deleted only after the restore completes
44
+ * successfully, while still holding the lock. Its *existence* means "a restore started and has
45
+ * not finished successfully". Its first line records the database directory name so the startup
46
+ * scan can map a marker back to the database it blocks without decoding the hashed key.
47
+ */
48
+
49
+ // The backtick makes this an illegal database name (schemaRegex rejects `/` and backtick only), so
50
+ // it can never collide with a real database directory — see the module header.
51
+ export const RESTORE_META_DIR = '`restore`';
52
+ export const RESTORE_LOCK_SUFFIX = '.lock';
53
+ export const RESTORING_MARKER_SUFFIX = '.restoring';
54
+
55
+ /**
56
+ * Directory holding the restore metadata for a database — the reserved `` `restore` `` sibling of
57
+ * the database directory (see the module header for why the name contains a backtick). Shared by
58
+ * every database under the same parent, so a single readdir surfaces all pending restores during
59
+ * the startup scan.
60
+ */
61
+ export function restoreMetaDir(dbPath: string): string {
62
+ return join(dirname(dbPath), RESTORE_META_DIR);
63
+ }
64
+
65
+ /**
66
+ * Filesystem-safe, length-bounded key for a database's restore metadata files. Hashing the
67
+ * database directory name (not the full path, so it is stable regardless of where the databases
68
+ * root lives) keeps the metadata filenames short and collision-free while staying independent of
69
+ * the database-name namespace. Database directory names are unique within a databases root, so
70
+ * their hashes are too.
71
+ */
72
+ function restoreMetaKey(dbPath: string): string {
73
+ return createHash('sha256').update(basename(dbPath)).digest('hex').slice(0, 32);
74
+ }
75
+
76
+ export function restoreLockPath(dbPath: string): string {
77
+ return join(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + RESTORE_LOCK_SUFFIX);
78
+ }
79
+
80
+ export function restoringMarkerPath(dbPath: string): string {
81
+ return join(restoreMetaDir(dbPath), restoreMetaKey(dbPath) + RESTORING_MARKER_SUFFIX);
82
+ }
83
+
84
+ export type RestoreState = 'in-progress' | 'incomplete' | 'clear';
85
+
86
+ /**
87
+ * Whether a `.restoring` marker exists for a database. Cheaper than `checkRestoreState` and, unlike
88
+ * it, safe to call while *this* thread holds the restore lock: `checkRestoreState` would re-probe
89
+ * the lock (which reads as held from the same thread) and report 'in-progress' rather than telling
90
+ * a caller that a *leftover* marker is present. `dropDatabase` uses this after acquiring the lock to
91
+ * distinguish debris from a crashed restore.
92
+ */
93
+ export function restoreMarkerPresent(dbPath: string): boolean {
94
+ return existsSync(restoringMarkerPath(dbPath));
95
+ }
96
+
97
+ /** The lock and (optional) marker held by a begin/acquire call, threaded back to complete/abandon. */
98
+ export type RestoreLock = {
99
+ /** rocksdb-js file-lock token; non-zero. */
100
+ token: number;
101
+ /** The database directory this lock guards. */
102
+ dbPath: string;
103
+ /**
104
+ * True when a `.restoring` marker already existed at `beginRestore` time — i.e. this restore is a
105
+ * recovery attempt over a possibly half-purged directory. A pre-existing marker must never be
106
+ * cleared by a *failed* recovery attempt, or the directory could be reloaded as healthy while
107
+ * still partial. Only set on `beginRestore`; always false for a bare `acquireRestoreLock`.
108
+ */
109
+ preexisting: boolean;
110
+ };
111
+
112
+ /**
113
+ * Determine the restore state of a database directory. Used by startup database detection and
114
+ * the open-database guards:
115
+ * - 'in-progress': marker present and the restore lock is held (a restore is running in some
116
+ * process) — do not load.
117
+ * - 'incomplete': marker present but the lock is free (crashed mid-restore; the directory may
118
+ * be partial garbage) — do not load; rerun the restore.
119
+ * - 'clear': no marker — load normally (a stale, unheld lock file alone is fine).
120
+ *
121
+ * The marker is checked FIRST and the lock is only probed when the marker exists. Probing takes
122
+ * and releases the flock, and probes are mutually exclusive across threads — if every rescan on
123
+ * every thread probed the (persistent) lock file of a long-ago-restored database, concurrent
124
+ * rescans would collide and misclassify healthy databases as 'in-progress'. Marker-first is
125
+ * safe: `beginRestore` writes (and fsyncs) the marker immediately after taking the lock and
126
+ * before any destructive step, so a database without a marker has nothing to protect yet.
127
+ */
128
+ export function checkRestoreState(dbPath: string): RestoreState {
129
+ if (!existsSync(restoringMarkerPath(dbPath))) return 'clear';
130
+ const lockPath = restoreLockPath(dbPath);
131
+ if (existsSync(lockPath)) {
132
+ const token = tryFileLock(lockPath);
133
+ if (token === 0) return 'in-progress';
134
+ fileLockRelease(token);
135
+ }
136
+ return 'incomplete';
137
+ }
138
+
139
+ /**
140
+ * fsync a directory so a create/unlink of an entry within it is durable. Best-effort: Windows (and
141
+ * some filesystems) reject opening a directory for fsync with EPERM/EISDIR/ENOTSUP — the durability
142
+ * flush is a POSIX nicety, so treat those as a no-op rather than failing the restore.
143
+ */
144
+ function fsyncDir(dir: string): void {
145
+ let dirFd: number;
146
+ try {
147
+ dirFd = openSync(dir, 'r');
148
+ } catch (error: any) {
149
+ if (error.code === 'EPERM' || error.code === 'EISDIR' || error.code === 'ENOTSUP') return;
150
+ throw error;
151
+ }
152
+ try {
153
+ fsyncSync(dirFd);
154
+ } finally {
155
+ closeSync(dirFd);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Take the per-database restore lock without writing a marker. Used by `dropDatabase` so a drop and
161
+ * a restore serialize on the same primitive: whichever takes the lock first runs to completion; the
162
+ * other gets a 409. Throws (statusCode 409) if the lock is already held.
163
+ */
164
+ export function acquireRestoreLock(dbPath: string): RestoreLock {
165
+ mkdirSync(restoreMetaDir(dbPath), { recursive: true });
166
+ const token = tryFileLock(restoreLockPath(dbPath));
167
+ if (token === 0) {
168
+ const error: any = new Error(`Restore already in progress for database at ${dbPath}`);
169
+ error.statusCode = 409;
170
+ throw error;
171
+ }
172
+ return { token, dbPath, preexisting: false };
173
+ }
174
+
175
+ /**
176
+ * Release a lock taken by `acquireRestoreLock` (no marker to remove).
177
+ */
178
+ export function releaseRestoreLock(lock: RestoreLock): void {
179
+ fileLockRelease(lock.token);
180
+ }
181
+
182
+ /**
183
+ * Acquire the per-database restore lock and write the restoring marker. Call before any
184
+ * destructive step. Returns the lock (with `preexisting` set when a marker was already present, so
185
+ * a failed recovery attempt knows not to clear it). Throws (statusCode 409) if another restore
186
+ * already holds the lock.
187
+ */
188
+ export function beginRestore(dbPath: string): RestoreLock {
189
+ const markerPath = restoringMarkerPath(dbPath);
190
+ const preexisting = existsSync(markerPath);
191
+ const lock = acquireRestoreLock(dbPath);
192
+ try {
193
+ const fd = openSync(markerPath, 'w');
194
+ try {
195
+ // first line is the database directory name so the startup scan can map this marker back to
196
+ // the database it blocks without reversing the hashed key
197
+ writeSync(fd, `${basename(dbPath)}\nrestore started ${new Date().toISOString()}\n`);
198
+ fsyncSync(fd);
199
+ } finally {
200
+ closeSync(fd);
201
+ }
202
+ // fsync the metadata directory so the marker's directory entry is durable — without this a
203
+ // power loss can lose the entry, and a half-purged database would load as healthy
204
+ fsyncDir(restoreMetaDir(dbPath));
205
+ } catch (error) {
206
+ fileLockRelease(lock.token);
207
+ throw error;
208
+ }
209
+ return { ...lock, preexisting };
210
+ }
211
+
212
+ /**
213
+ * Mark the restore successful: delete the marker (while still holding the lock), then release
214
+ * the lock.
215
+ */
216
+ export function completeRestore(lock: RestoreLock): void {
217
+ try {
218
+ unlinkSync(restoringMarkerPath(lock.dbPath));
219
+ // fsync the metadata directory so the marker's *removal* is durable — symmetric with the
220
+ // creation fsync in beginRestore. Without it, a power loss could resurrect the marker's
221
+ // directory entry and misclassify a fully-restored database as incomplete.
222
+ fsyncDir(restoreMetaDir(lock.dbPath));
223
+ } finally {
224
+ fileLockRelease(lock.token);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Release the lock after a failed restore, leaving the marker in place so the database is
230
+ * detected as an incomplete restore (and not loaded) until a rerun succeeds.
231
+ */
232
+ export function abandonRestore(lock: RestoreLock): void {
233
+ fileLockRelease(lock.token);
234
+ }
235
+
236
+ /**
237
+ * Remove a database's restore marker if one is present, then release the lock. Used by
238
+ * `dropDatabase`: a dropped database that carried an incomplete-restore marker should not leave the
239
+ * marker behind to block a future database of the same name. No-op on the marker if none exists.
240
+ */
241
+ export function clearRestoreMarker(lock: RestoreLock): void {
242
+ try {
243
+ const markerPath = restoringMarkerPath(lock.dbPath);
244
+ if (existsSync(markerPath)) {
245
+ unlinkSync(markerPath);
246
+ fsyncDir(restoreMetaDir(lock.dbPath));
247
+ }
248
+ } finally {
249
+ fileLockRelease(lock.token);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Scan a databases root's reserved `` `restore` `` metadata directory and report every database currently blocked from
255
+ * loading, mapping each surviving marker back to its database name via the marker's first line.
256
+ * Returns `[dbName, state]` pairs for markers whose state is `in-progress` or `incomplete`
257
+ * (a `clear` result means the marker was removed concurrently and the database is loadable).
258
+ */
259
+ export function scanBlockedRestores(databasesRoot: string): Array<[string, RestoreState]> {
260
+ const metaDir = join(databasesRoot, RESTORE_META_DIR);
261
+ if (!existsSync(metaDir)) return [];
262
+ const blocked: Array<[string, RestoreState]> = [];
263
+ for (const entry of readdirSync(metaDir, { withFileTypes: true })) {
264
+ if (!entry.isFile() || !entry.name.endsWith(RESTORING_MARKER_SUFFIX)) continue;
265
+ let dbName: string;
266
+ try {
267
+ dbName = readFileSync(join(metaDir, entry.name), 'utf8').split('\n', 1)[0];
268
+ } catch {
269
+ continue; // marker removed concurrently
270
+ }
271
+ if (!dbName) continue;
272
+ const state = checkRestoreState(join(databasesRoot, dbName));
273
+ if (state !== 'clear') blocked.push([dbName, state]);
274
+ }
275
+ return blocked;
276
+ }