@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
@@ -4,6 +4,14 @@ import { makeTable } from './Table.ts';
4
4
  import { RocksDatabase } from '@harperfast/rocksdb-js';
5
5
  export declare function isReadOnlyMode(): boolean;
6
6
  declare const DEFINED_TABLES: unique symbol;
7
+ export declare function getRocksCompression(): string | undefined;
8
+ /**
9
+ * Test-only: un-freezes the resolved codec. Production code never calls this — the freeze is the
10
+ * invariant (see getRocksCompression above) — but a test process runs many unrelated test files in
11
+ * one process, so whichever file happens to open a RocksDatabase first freezes this for everyone
12
+ * after it. Tests that need to exercise config changes call this to get back to the unresolved state.
13
+ */
14
+ export declare function resetRocksCompression(): void;
7
15
  export declare const NON_REPLICATING_SYSTEM_TABLES: string[];
8
16
  export type Table = ReturnType<typeof makeTable> & {
9
17
  indexingOperation?: any;
@@ -63,6 +71,23 @@ export type DatabaseWatcherEventMap = {
63
71
  export declare const databaseEventsEmitter: EventEmitter<DatabaseWatcherEventMap>;
64
72
  export declare const tables: Tables;
65
73
  export declare const databases: Databases;
74
+ /**
75
+ * Map a persisted (LMDB-era) compression value to what rocksdb-js accepts. Table metadata carries
76
+ * values where a defined falsy value (false, '') means compression was explicitly disabled, and
77
+ * `true` / `{ threshold, ... }` mean enabled with defaults — `storage.compression` defaults to
78
+ * `true` (defaultConfig.yaml), so essentially every pre-existing table asked for compression.
79
+ *
80
+ * "Enabled" resolves to an explicit codec rather than to unset. Unset is not equivalent: RocksDB
81
+ * persists the codec per column family and a reopen that requests nothing inherits what the family
82
+ * already has, applying the build default only when the family does not yet exist. Leaving these
83
+ * unset therefore silently ignores the operator's request on every database created before the
84
+ * native build carried codecs — it keeps writing uncompressed forever, while a brand-new database
85
+ * gets lz4. Naming the codec makes the setting mean the same thing in both cases.
86
+ *
87
+ * This governs newly written files; existing SSTs keep their codec until write traffic rewrites
88
+ * them (`db.compact()` will not — see getRocksCompression above).
89
+ */
90
+ export declare function toRocksCompression(compression: unknown): unknown;
66
91
  /**
67
92
  * This gets the set of tables from the default database ("data").
68
93
  */
@@ -107,6 +132,17 @@ interface TableDefinition {
107
132
  hidden?: boolean;
108
133
  cacheControl?: string | null;
109
134
  }
135
+ /**
136
+ * Resolve the directory that holds (or would hold) a database's storage, from the databases
137
+ * config, storage path config/env, or the hdb root — without opening anything. This is the
138
+ * parent directory selection used by `database()`; a RocksDB database lives at
139
+ * `join(resolveDatabaseStorageRoot(...), databaseName)`.
140
+ */
141
+ export declare function resolveDatabaseStorageRoot(databaseName: string, tableName?: string): string;
142
+ /**
143
+ * Resolve the directory path of a RocksDB database (whether or not it exists or is loaded).
144
+ */
145
+ export declare function resolveDatabasePath(databaseName: string): string;
110
146
  /**
111
147
  * Get root store for a database
112
148
  * @param options
@@ -121,6 +157,28 @@ export declare function database({ database: databaseName, table: tableName }: {
121
157
  * @param databaseName
122
158
  */
123
159
  export declare function dropDatabase(databaseName: any): Promise<void>;
160
+ /**
161
+ * Close a RocksDB database's store handles on the current thread and unregister it, without
162
+ * touching its files. Used by the restore_backup flow: every thread must release its handles so
163
+ * `backups.restore()` can purge and rewrite the (fully closed) database directory. A subsequent
164
+ * `resetDatabases()`/`getDatabases()` rescan reloads it (or skips it while a restore is in
165
+ * progress, per the restore marker checks in the scan).
166
+ */
167
+ export declare function closeDatabase(databaseName: string): boolean;
168
+ /**
169
+ * Close every RocksDB (user) database this thread has open, releasing its native handles.
170
+ *
171
+ * rocksdb-js's registry is process-global across worker threads, and a thread that exits WITHOUT
172
+ * closing leaks its handles (the process-global refCount never drops), while the only alternative,
173
+ * `shutdown()`, tears down rocksdb for the entire process. So a worker thread that opens databases
174
+ * and then exits — notably a job worker (jobProcess), which opens the whole database graph via
175
+ * `getDatabases()` and exits when the job finishes — must close its handles explicitly, or those
176
+ * handles linger process-wide (and, e.g., block an online `restore_backup` from confirming the
177
+ * database is closed). The `system` database is intentionally left open: it is non-enumerable here
178
+ * (skipped by the loop), is never restored online, and the exiting worker may still touch the job
179
+ * table during teardown. Best-effort: closing failures are swallowed inside `closeDatabase`.
180
+ */
181
+ export declare function closeLoadedDatabases(): void;
124
182
  /**
125
183
  * This can be called to ensure that the specified table exists and if it does not exist, it should be created.
126
184
  * @param tableName
@@ -162,7 +220,7 @@ export declare function onRemovedTable(listener: (tableName: string, databaseNam
162
220
  export declare function onRemovedDB(listener: (databaseName: string) => void): {
163
221
  remove(): void;
164
222
  };
165
- export declare function getDefaultCompression(): {
223
+ export declare function getDefaultCompression(): false | {
166
224
  startingOffset: number;
167
225
  };
168
226
  /**
@@ -38,12 +38,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.databases = exports.tables = exports.databaseEventsEmitter = exports.NON_REPLICATING_SYSTEM_TABLES = void 0;
40
40
  exports.isReadOnlyMode = isReadOnlyMode;
41
+ exports.getRocksCompression = getRocksCompression;
42
+ exports.resetRocksCompression = resetRocksCompression;
43
+ exports.toRocksCompression = toRocksCompression;
41
44
  exports.getTables = getTables;
42
45
  exports.getDatabases = getDatabases;
43
46
  exports.readMetaDb = readMetaDb;
44
47
  exports.resetDatabases = resetDatabases;
48
+ exports.resolveDatabaseStorageRoot = resolveDatabaseStorageRoot;
49
+ exports.resolveDatabasePath = resolveDatabasePath;
45
50
  exports.database = database;
46
51
  exports.dropDatabase = dropDatabase;
52
+ exports.closeDatabase = closeDatabase;
53
+ exports.closeLoadedDatabases = closeLoadedDatabases;
47
54
  exports.table = table;
48
55
  exports.canonicalizeIndexOptions = canonicalizeIndexOptions;
49
56
  exports.dropTableMeta = dropTableMeta;
@@ -86,6 +93,7 @@ const RocksIndexStore_ts_1 = require("./RocksIndexStore.js");
86
93
  const when_ts_1 = require("../utility/when.js");
87
94
  const rocksMemoryConfig_ts_1 = require("../utility/rocksMemoryConfig.js");
88
95
  const processManagement_js_1 = require("../utility/processManagement/processManagement.js");
96
+ const restoreMarker_ts_1 = require("../dataLayer/restoreMarker.js");
89
97
  /**
90
98
  * Check if Harper is running in read-only mode.
91
99
  * Read-only mode can be enabled via:
@@ -133,6 +141,76 @@ const DEFAULT_DATABASE_NAME = 'data';
133
141
  const DEFINED_TABLES = Symbol('defined-tables');
134
142
  const DEFAULT_COMPRESSION_THRESHOLD = ((0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_PAGESIZE) || 4096) - 60; // larger than this requires multiple pages
135
143
  (0, environmentManager_ts_1.initSync)();
144
+ /**
145
+ * The RocksDB block/blob codec for every column family this process opens (`storage.rocks.compression`),
146
+ * or `undefined` to leave rocksdb-js on its own default (lz4 wherever the native build has it).
147
+ *
148
+ * Resolved on the first open and then frozen, deliberately. RocksDB fixes a column family's codec
149
+ * for as long as it is open and rejects a reopen that disagrees, and Harper's worker threads share
150
+ * one process-wide column-family registry — so every open, in every thread, has to resolve the same
151
+ * value. Re-reading config per open does not guarantee that: on a fresh install the system families
152
+ * are created by `mountHdb()` before the config file exists, so the main thread would resolve
153
+ * nothing and the workers would resolve the configured codec, after which `__dbis__` cannot be
154
+ * reopened and Harper fails with "The system database failed to load". The installer stages this
155
+ * value before `mountHdb()` (see utility/install/installer.ts) so that first open already sees it.
156
+ *
157
+ * Unset is NOT "use the build default" for a family that already exists — see toRocksCompression.
158
+ */
159
+ let resolvedRocksCompression;
160
+ let rocksCompressionResolved = false;
161
+ function getRocksCompression() {
162
+ if (!rocksCompressionResolved) {
163
+ resolvedRocksCompression = readDatabaseCodec();
164
+ rocksCompressionResolved = true;
165
+ }
166
+ return resolvedRocksCompression;
167
+ }
168
+ /**
169
+ * Test-only: un-freezes the resolved codec. Production code never calls this — the freeze is the
170
+ * invariant (see getRocksCompression above) — but a test process runs many unrelated test files in
171
+ * one process, so whichever file happens to open a RocksDatabase first freezes this for everyone
172
+ * after it. Tests that need to exercise config changes call this to get back to the unresolved state.
173
+ */
174
+ function resetRocksCompression() {
175
+ resolvedRocksCompression = undefined;
176
+ rocksCompressionResolved = false;
177
+ }
178
+ /**
179
+ * The codec every column family in this process opens under.
180
+ *
181
+ * Compression is a deployment setting, not a per-table one. RocksDB opens all of a database's
182
+ * column families in one call, so the codec has to be decided before the first open — which is
183
+ * before Harper has read any table's metadata (that catalog is itself one of the families being
184
+ * opened). Resolving one codec from configuration and applying it to every family is what makes
185
+ * that possible; it is passed with `compressionForAllColumnFamilies` so families this process
186
+ * never names individually adopt it too, which is what lets a database created before the codec
187
+ * existed start compressing.
188
+ *
189
+ * `storage.rocks.compression` names a codec outright. Otherwise `storage.compression` (default
190
+ * true) decides enabled-or-not and the build default fills in the algorithm. Per-table metadata
191
+ * still records the LMDB-era boolean, but no longer selects: a table persisted as disabled inside
192
+ * a deployment that enables compression would need its own codec, and it cannot have one.
193
+ */
194
+ function readDatabaseCodec() {
195
+ const explicit = readRocksCompressionConfig();
196
+ if (explicit)
197
+ return explicit;
198
+ return toRocksCompression(getDefaultCompression());
199
+ }
200
+ function readRocksCompressionConfig() {
201
+ const configured = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ROCKS_COMPRESSION);
202
+ if (configured === undefined || configured === null || configured === '')
203
+ return undefined;
204
+ const requested = String(configured).trim().toLowerCase();
205
+ if (!requested)
206
+ return undefined;
207
+ // Rejected here rather than at the open: an unsupported name throws inside RocksDatabase.open,
208
+ // which surfaces as the system database failing to load partway through startup.
209
+ if (!rocksdb_js_1.supportedCompression.includes(requested)) {
210
+ throw new Error(`storage.rocks.compression="${requested}" is not available in this build of @harperfast/rocksdb-js. Supported: ${rocksdb_js_1.supportedCompression.join(', ')}`);
211
+ }
212
+ return requested;
213
+ }
136
214
  // I don't know if this is the best place for this, but somewhere we need to specify which tables
137
215
  // replicate by default:
138
216
  exports.NON_REPLICATING_SYSTEM_TABLES = [
@@ -148,8 +226,53 @@ exports.NON_REPLICATING_SYSTEM_TABLES = [
148
226
  exports.databaseEventsEmitter = new node_events_1.EventEmitter();
149
227
  exports.tables = Object.create(null);
150
228
  exports.databases = Object.create(null);
229
+ /**
230
+ * Codec used to honor an "enabled, unspecified" compression setting, or `undefined` where the
231
+ * native build cannot provide it (in which case the request degrades to the build default rather
232
+ * than throwing).
233
+ */
234
+ const DEFAULT_ENABLED_CODEC = rocksdb_js_1.supportedCompression.includes('lz4') ? 'lz4' : undefined;
235
+ /**
236
+ * Map a persisted (LMDB-era) compression value to what rocksdb-js accepts. Table metadata carries
237
+ * values where a defined falsy value (false, '') means compression was explicitly disabled, and
238
+ * `true` / `{ threshold, ... }` mean enabled with defaults — `storage.compression` defaults to
239
+ * `true` (defaultConfig.yaml), so essentially every pre-existing table asked for compression.
240
+ *
241
+ * "Enabled" resolves to an explicit codec rather than to unset. Unset is not equivalent: RocksDB
242
+ * persists the codec per column family and a reopen that requests nothing inherits what the family
243
+ * already has, applying the build default only when the family does not yet exist. Leaving these
244
+ * unset therefore silently ignores the operator's request on every database created before the
245
+ * native build carried codecs — it keeps writing uncompressed forever, while a brand-new database
246
+ * gets lz4. Naming the codec makes the setting mean the same thing in both cases.
247
+ *
248
+ * This governs newly written files; existing SSTs keep their codec until write traffic rewrites
249
+ * them (`db.compact()` will not — see getRocksCompression above).
250
+ */
251
+ function toRocksCompression(compression) {
252
+ if (compression === undefined)
253
+ return undefined;
254
+ if (!compression)
255
+ return 'none';
256
+ // An object carrying an explicit `algorithm` is already a rocksdb-js request; anything else
257
+ // (`true`, or an LMDB descriptor like { startingOffset, threshold }) is "enabled, unspecified".
258
+ if (compression === true || (typeof compression === 'object' && !compression.algorithm))
259
+ return DEFAULT_ENABLED_CODEC;
260
+ return compression;
261
+ }
151
262
  function openRocksDatabase(path, options) {
152
263
  options.disableWAL ??= true;
264
+ const legacyOptions = options;
265
+ // A configured codec applies to every column family, overriding whatever per-table metadata
266
+ // carries — that metadata records the LMDB-era boolean, so without this there is no way to
267
+ // select a RocksDB codec for a deployment.
268
+ // One codec for every column family, and applied to every family this open touches — not just
269
+ // the one being named. RocksDB opens them all at once and a family's codec cannot change while
270
+ // it is open, so a family this process never names individually would otherwise stay on
271
+ // whatever it was created with, forever.
272
+ const databaseCodec = getRocksCompression();
273
+ legacyOptions.compression = databaseCodec;
274
+ if (databaseCodec)
275
+ options.compressionForAllColumnFamilies = true;
153
276
  // Apply read-only mode if enabled
154
277
  if (isReadOnlyMode()) {
155
278
  options.readOnly = true;
@@ -284,12 +407,21 @@ function getDatabases() {
284
407
  if (databasePath && (0, node_fs_1.existsSync)(databasePath)) {
285
408
  // First load all the databases from our main database folder
286
409
  // TODO: Load any databases defined with explicit storage paths from the config
287
- for (const databaseEntry of (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true })) {
410
+ const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
411
+ const blockedByRestore = databasesBlockedByRestore(databasePath);
412
+ for (const databaseEntry of entries) {
288
413
  // in-progress migration staging dirs are not databases until atomically renamed into place
289
414
  if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
290
415
  continue;
416
+ // the restore-metadata directory is reserved: never load it as a database, even if a
417
+ // (out-of-band) RocksDB directory happens to occupy that reserved name — the API can't
418
+ // create it (schemaRegex forbids the backtick), but the scan opens any CURRENT+MANIFEST dir
419
+ if (databaseEntry.name === restoreMarker_ts_1.RESTORE_META_DIR)
420
+ continue;
291
421
  const dbName = (0, path_1.basename)(databaseEntry.name, '.mdb');
292
422
  const dbPath = (0, path_1.join)(databasePath, databaseEntry.name);
423
+ if (blockedByRestore.has(dbName))
424
+ continue;
293
425
  if (databaseEntry.isFile() &&
294
426
  (0, path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb' &&
295
427
  !schemaConfigs[dbName]?.path) {
@@ -334,8 +466,14 @@ function getDatabases() {
334
466
  const schemaConfig = schemaConfigs[dbName];
335
467
  const databasePath = schemaConfig.path;
336
468
  if ((0, node_fs_1.existsSync)(databasePath)) {
337
- for (const databaseEntry of (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true })) {
469
+ const entries = (0, node_fs_1.readdirSync)(databasePath, { withFileTypes: true });
470
+ const blockedByRestore = databasesBlockedByRestore(databasePath);
471
+ for (const databaseEntry of entries) {
338
472
  if (databaseEntry.name.endsWith(hdbTerms_ts_1.MIGRATING_DIR_SUFFIX))
473
+ continue; // migration staging dir
474
+ if (databaseEntry.name === restoreMarker_ts_1.RESTORE_META_DIR)
475
+ continue; // reserved restore-metadata dir
476
+ if (blockedByRestore.has((0, path_1.basename)(databaseEntry.name, '.mdb')))
339
477
  continue;
340
478
  if (databaseEntry.isFile() && (0, path_1.extname)(databaseEntry.name).toLowerCase() === '.mdb') {
341
479
  readMetaDb((0, path_1.join)(databasePath, databaseEntry.name), (0, path_1.basename)(databaseEntry.name, '.mdb'), dbName);
@@ -413,6 +551,27 @@ function getDatabases() {
413
551
  }
414
552
  return exports.databases;
415
553
  }
554
+ /**
555
+ * Scan a databases directory's entries for restore lock/marker files and return the names of
556
+ * databases that must not be loaded: a held restore lock means a restore is in progress in some
557
+ * process; an unheld lock with a surviving `.restoring` marker means a restore was interrupted
558
+ * mid-purge (the directory may be partial garbage) and must be rerun. The files live *next to*
559
+ * the database directory, so this also covers a database whose directory is missing or empty.
560
+ */
561
+ function databasesBlockedByRestore(databasePath) {
562
+ const blocked = new Set();
563
+ for (const [dbName, state] of (0, restoreMarker_ts_1.scanBlockedRestores)(databasePath)) {
564
+ if (state === 'in-progress') {
565
+ logger.warn(`A restore of database '${dbName}' is in progress; not loading it`);
566
+ blocked.add(dbName);
567
+ }
568
+ else if (state === 'incomplete') {
569
+ logger.error(`Incomplete restore of database '${dbName}' detected (a restore started but did not finish); not loading it — rerun the restore to recover`);
570
+ blocked.add(dbName);
571
+ }
572
+ }
573
+ return blocked;
574
+ }
416
575
  /**
417
576
  * This is responsible for reading the internal dbi of a single database file to get a list of all the tables and
418
577
  * their indexed or registered attributes
@@ -823,19 +982,12 @@ function setTable(tables, tableName, Table) {
823
982
  return Table;
824
983
  }
825
984
  /**
826
- * Get root store for a database
827
- * @param options
828
- * @returns
985
+ * Resolve the directory that holds (or would hold) a database's storage, from the databases
986
+ * config, storage path config/env, or the hdb root — without opening anything. This is the
987
+ * parent directory selection used by `database()`; a RocksDB database lives at
988
+ * `join(resolveDatabaseStorageRoot(...), databaseName)`.
829
989
  */
830
- function database({ database: databaseName, table: tableName }) {
831
- if (!databaseName)
832
- databaseName = DEFAULT_DATABASE_NAME;
833
- getDatabases();
834
- ensureDB(databaseName);
835
- const definedDatabase = definedDatabases.get(databaseName);
836
- if (definedDatabase?.rootStore) {
837
- return definedDatabase.rootStore;
838
- }
990
+ function resolveDatabaseStorageRoot(databaseName, tableName) {
839
991
  const databaseConfig = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
840
992
  if (process.env.SCHEMAS_DATA_PATH) {
841
993
  databaseConfig.data = { path: process.env.SCHEMAS_DATA_PATH };
@@ -854,12 +1006,44 @@ function database({ database: databaseName, table: tableName }) {
854
1006
  if (!databasePath) {
855
1007
  throw new Error(`Unable to determine database storage path. Ensure STORAGE_PATH, HDB_ROOT, or a valid config path is set.`);
856
1008
  }
1009
+ return databasePath;
1010
+ }
1011
+ /**
1012
+ * Resolve the directory path of a RocksDB database (whether or not it exists or is loaded).
1013
+ */
1014
+ function resolveDatabasePath(databaseName) {
1015
+ return (0, path_1.join)(resolveDatabaseStorageRoot(databaseName), databaseName);
1016
+ }
1017
+ /**
1018
+ * Get root store for a database
1019
+ * @param options
1020
+ * @returns
1021
+ */
1022
+ function database({ database: databaseName, table: tableName }) {
1023
+ if (!databaseName)
1024
+ databaseName = DEFAULT_DATABASE_NAME;
1025
+ getDatabases();
1026
+ ensureDB(databaseName);
1027
+ const definedDatabase = definedDatabases.get(databaseName);
1028
+ if (definedDatabase?.rootStore) {
1029
+ return definedDatabase.rootStore;
1030
+ }
1031
+ const databaseConfig = (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.DATABASES) || {};
1032
+ if (process.env.SCHEMAS_DATA_PATH) {
1033
+ databaseConfig.data = { path: process.env.SCHEMAS_DATA_PATH };
1034
+ }
1035
+ const tablePath = tableName && databaseConfig[databaseName]?.tables?.[tableName]?.path;
1036
+ const databasePath = resolveDatabaseStorageRoot(databaseName, tableName);
857
1037
  let rootStore;
858
1038
  const useRocksdb = (process.env.HARPER_STORAGE_ENGINE || (0, environmentManager_ts_1.get)(hdbTerms_ts_1.CONFIG_PARAMS.STORAGE_ENGINE)) !== 'lmdb';
859
1039
  if (useRocksdb) {
860
1040
  const path = (0, path_1.join)(databasePath, tablePath ? tableName : databaseName);
861
1041
  rootStore = rocksdbDatabaseEnvs.get(path);
862
1042
  if (!rootStore || rootStore.status === 'closed') {
1043
+ // this on-demand open (create_table/create_database and friends) must not resurrect a
1044
+ // database that a restore is rewriting (or left half-purged) — the scan-time restore
1045
+ // checks don't cover this path
1046
+ throwIfBlockedByRestore(path, databaseName);
863
1047
  rootStore = openRocksDatabase(path, {
864
1048
  disableWAL: false,
865
1049
  enableStats: true,
@@ -884,6 +1068,49 @@ function database({ database: databaseName, table: tableName }) {
884
1068
  definedDatabase.rootStore = rootStore;
885
1069
  return rootStore;
886
1070
  }
1071
+ function throwIfBlockedByRestore(dbPath, databaseName) {
1072
+ const restoreState = (0, restoreMarker_ts_1.checkRestoreState)(dbPath);
1073
+ if (restoreState !== 'clear') {
1074
+ const error = new Error(restoreState === 'in-progress'
1075
+ ? `Database '${databaseName}' is being restored; retry when the restore completes`
1076
+ : `Database '${databaseName}' has an incomplete restore; rerun restore_backup to recover it`);
1077
+ error.statusCode = 409;
1078
+ throw error;
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Take the per-database restore lock for a drop, refusing (409) if a restore holds it (in-progress)
1083
+ * or a crashed restore left a marker (incomplete). Pushes the acquired lock onto `held` so the
1084
+ * caller releases it after the drop. On refusal, releases anything already held and throws.
1085
+ *
1086
+ * The lock is not reentrant within a process, so a path already in `held` must be skipped — every
1087
+ * table in a RocksDB database shares one root store (and one lock path), and re-acquiring it in the
1088
+ * same drop would spuriously 409 on the second table.
1089
+ */
1090
+ function lockDatabaseForDrop(dbPath, databaseName, held) {
1091
+ if (held.some((h) => h.dbPath === dbPath))
1092
+ return;
1093
+ let lock;
1094
+ try {
1095
+ lock = (0, restoreMarker_ts_1.acquireRestoreLock)(dbPath);
1096
+ }
1097
+ catch (error) {
1098
+ for (const h of held)
1099
+ (0, restoreMarker_ts_1.releaseRestoreLock)(h);
1100
+ throw error; // 409: a restore is in progress and holds the lock
1101
+ }
1102
+ // We now hold the lock, so no restore is active. A surviving marker is therefore debris from a
1103
+ // crashed restore (incomplete) — refuse rather than delete a directory that still needs recovery.
1104
+ if ((0, restoreMarker_ts_1.restoreMarkerPresent)(dbPath)) {
1105
+ (0, restoreMarker_ts_1.releaseRestoreLock)(lock);
1106
+ for (const h of held)
1107
+ (0, restoreMarker_ts_1.releaseRestoreLock)(h);
1108
+ const error = new Error(`Database '${databaseName}' has an incomplete restore; rerun restore_backup to recover it`);
1109
+ error.statusCode = 409;
1110
+ throw error;
1111
+ }
1112
+ held.push(lock);
1113
+ }
887
1114
  /**
888
1115
  * Delete the database
889
1116
  * @param databaseName
@@ -893,15 +1120,112 @@ async function dropDatabase(databaseName) {
893
1120
  throw new Error('Database does not exist');
894
1121
  const dbTables = exports.databases[databaseName];
895
1122
  let rootStore;
1123
+ // Hold the per-database restore lock across the entire drop so its file deletion can never
1124
+ // interleave with a restore's purge-and-copy on the same directory — a destroy landing after a
1125
+ // restore's copy would gut a "successful" restore, and vice versa. Restore takes the same lock
1126
+ // (before writing its marker), so both operations serialize on this one primitive rather than on
1127
+ // a check-then-act marker probe. Released in the finally below.
1128
+ const restoreLocks = [];
1129
+ try {
1130
+ for (const tableName in dbTables) {
1131
+ const table = dbTables[tableName];
1132
+ rootStore = table.primaryStore.rootStore;
1133
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase)
1134
+ lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
1135
+ lmdbDatabaseEnvs.delete(rootStore.path);
1136
+ rocksdbDatabaseEnvs.delete(rootStore.path);
1137
+ }
1138
+ for (const tableName in dbTables) {
1139
+ exports.databaseEventsEmitter.emit('dropTable', tableName, databaseName);
1140
+ }
1141
+ if (databaseName === 'data') {
1142
+ for (const tableName in exports.tables) {
1143
+ delete exports.tables[tableName];
1144
+ }
1145
+ delete exports.tables[DEFINED_TABLES];
1146
+ }
1147
+ delete exports.databases[databaseName];
1148
+ exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
1149
+ if (rootStore) {
1150
+ if (rootStore.status === 'open') {
1151
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1152
+ rootStore.close();
1153
+ rootStore.destroy();
1154
+ }
1155
+ else {
1156
+ await rootStore.close();
1157
+ await (0, promises_1.unlink)(rootStore.path);
1158
+ }
1159
+ }
1160
+ }
1161
+ else {
1162
+ rootStore = database({ database: databaseName, table: null });
1163
+ // a tableless database resolves its root store here rather than in the loop above, so take
1164
+ // the drop lock now (still before any destructive step)
1165
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase)
1166
+ lockDatabaseForDrop(rootStore.path, databaseName, restoreLocks);
1167
+ if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1168
+ rootStore.close();
1169
+ rootStore.destroy();
1170
+ }
1171
+ else if (rootStore.status === 'open') {
1172
+ await rootStore.close();
1173
+ await (0, promises_1.unlink)(rootStore.path);
1174
+ }
1175
+ }
1176
+ await (0, blob_ts_1.deleteRootBlobPathsForDB)(rootStore);
1177
+ }
1178
+ finally {
1179
+ for (const lock of restoreLocks)
1180
+ (0, restoreMarker_ts_1.releaseRestoreLock)(lock);
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Close a RocksDB database's store handles on the current thread and unregister it, without
1185
+ * touching its files. Used by the restore_backup flow: every thread must release its handles so
1186
+ * `backups.restore()` can purge and rewrite the (fully closed) database directory. A subsequent
1187
+ * `resetDatabases()`/`getDatabases()` rescan reloads it (or skips it while a restore is in
1188
+ * progress, per the restore marker checks in the scan).
1189
+ */
1190
+ function closeDatabase(databaseName) {
1191
+ const dbTables = exports.databases[databaseName];
1192
+ if (!dbTables)
1193
+ return false;
1194
+ const rootStores = new Set();
1195
+ const closeStore = (store, description) => {
1196
+ try {
1197
+ store?.close?.();
1198
+ }
1199
+ catch (error) {
1200
+ logger.warn(`Error closing ${description} while closing database ${databaseName}:`, error);
1201
+ }
1202
+ };
896
1203
  for (const tableName in dbTables) {
897
1204
  const table = dbTables[tableName];
898
- rootStore = table.primaryStore.rootStore;
1205
+ if (!table?.primaryStore)
1206
+ continue;
1207
+ if (table.primaryStore.rootStore)
1208
+ rootStores.add(table.primaryStore.rootStore);
1209
+ for (const indexName in table.indices || {}) {
1210
+ closeStore(table.indices[indexName], `index ${tableName}.${indexName}`);
1211
+ }
1212
+ closeStore(table.primaryStore, `table ${tableName}`);
1213
+ }
1214
+ // a database with no tables (an empty schema, or one whose tables were all dropped) still holds
1215
+ // an open root store, tracked only on the defined-database entry rather than any table — include
1216
+ // it so its handles are released too (the Set dedupes it against the per-table root stores above)
1217
+ const definedRoot = definedDatabases?.get(databaseName)?.rootStore;
1218
+ if (definedRoot)
1219
+ rootStores.add(definedRoot);
1220
+ for (const rootStore of rootStores) {
1221
+ closeStore(rootStore.dbisDb, 'attributes store');
1222
+ closeStore(rootStore, 'root store');
899
1223
  lmdbDatabaseEnvs.delete(rootStore.path);
900
1224
  rocksdbDatabaseEnvs.delete(rootStore.path);
901
1225
  }
902
- for (const tableName in dbTables) {
903
- exports.databaseEventsEmitter.emit('dropTable', tableName, databaseName);
904
- }
1226
+ const definedDatabase = definedDatabases?.get(databaseName);
1227
+ if (definedDatabase)
1228
+ definedDatabase.rootStore = undefined;
905
1229
  if (databaseName === 'data') {
906
1230
  for (const tableName in exports.tables) {
907
1231
  delete exports.tables[tableName];
@@ -909,31 +1233,42 @@ async function dropDatabase(databaseName) {
909
1233
  delete exports.tables[DEFINED_TABLES];
910
1234
  }
911
1235
  delete exports.databases[databaseName];
912
- exports.databaseEventsEmitter.emit('dropDatabase', databaseName);
913
- if (rootStore) {
914
- if (rootStore.status === 'open') {
915
- if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
916
- rootStore.close();
917
- rootStore.destroy();
918
- }
919
- else {
920
- await rootStore.close();
921
- await (0, promises_1.unlink)(rootStore.path);
1236
+ return true;
1237
+ }
1238
+ /**
1239
+ * Close every RocksDB (user) database this thread has open, releasing its native handles.
1240
+ *
1241
+ * rocksdb-js's registry is process-global across worker threads, and a thread that exits WITHOUT
1242
+ * closing leaks its handles (the process-global refCount never drops), while the only alternative,
1243
+ * `shutdown()`, tears down rocksdb for the entire process. So a worker thread that opens databases
1244
+ * and then exits — notably a job worker (jobProcess), which opens the whole database graph via
1245
+ * `getDatabases()` and exits when the job finishes — must close its handles explicitly, or those
1246
+ * handles linger process-wide (and, e.g., block an online `restore_backup` from confirming the
1247
+ * database is closed). The `system` database is intentionally left open: it is non-enumerable here
1248
+ * (skipped by the loop), is never restored online, and the exiting worker may still touch the job
1249
+ * table during teardown. Best-effort: closing failures are swallowed inside `closeDatabase`.
1250
+ */
1251
+ function closeLoadedDatabases() {
1252
+ // snapshot the names first: closeDatabase() deletes from `databases` as it goes
1253
+ for (const databaseName of Object.keys(exports.databases)) {
1254
+ const dbTables = exports.databases[databaseName];
1255
+ if (!dbTables)
1256
+ continue;
1257
+ let isRocks = false;
1258
+ for (const tableName in dbTables) {
1259
+ if (dbTables[tableName]?.primaryStore?.rootStore instanceof rocksdb_js_1.RocksDatabase) {
1260
+ isRocks = true;
1261
+ break;
922
1262
  }
923
1263
  }
924
- }
925
- else {
926
- rootStore = database({ database: databaseName, table: null });
927
- if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
928
- rootStore.close();
929
- rootStore.destroy();
930
- }
931
- else if (rootStore.status === 'open') {
932
- await rootStore.close();
933
- await (0, promises_1.unlink)(rootStore.path);
1264
+ // a tableless database exposes no table root store, so also check the defined-database
1265
+ // entry — otherwise its open root store would leak on worker exit
1266
+ if (!isRocks && definedDatabases?.get(databaseName)?.rootStore instanceof rocksdb_js_1.RocksDatabase) {
1267
+ isRocks = true;
934
1268
  }
1269
+ if (isRocks)
1270
+ closeDatabase(databaseName);
935
1271
  }
936
- await (0, blob_ts_1.deleteRootBlobPathsForDB)(rootStore);
937
1272
  }
938
1273
  // HNSW_NO_AUTOVERSION kill-switch: when set, a NEW index initializes as legacy rather than
939
1274
  // versioned. process.env values are strings, so a bare truthiness check would treat "0"/"false"
@@ -1010,7 +1345,11 @@ function openIndex(dbiKey, rootStore, attribute) {
1010
1345
  // Enable cache (WeakLRUCache + VT) for all custom-object index stores so the VT is
1011
1346
  // available before resolveIndexFormat decides the format. Versioned stores need the VT
1012
1347
  // for cached traversal; legacy stores pay a small per-write cache.delete() overhead only.
1013
- dbi = openRocksDatabase(rootStore.path, { ...dbiInit, name: dbiKey, cache: isCustomObjectIndex });
1348
+ dbi = openRocksDatabase(rootStore.path, {
1349
+ ...dbiInit,
1350
+ name: dbiKey,
1351
+ cache: isCustomObjectIndex,
1352
+ });
1014
1353
  dbi.rootStore = rootStore;
1015
1354
  // Custom-index object stores (e.g. HNSW) write graph nodes via plain put() with no staged
1016
1355
  // transaction timestamp, so their values carry no version and the PrimaryRocksDatabase
@@ -1230,6 +1569,10 @@ function table(tableDefinition) {
1230
1569
  clearInterruptedDropEntries(rootStore.path, tableName);
1231
1570
  }
1232
1571
  if (rootStore instanceof rocksdb_js_1.RocksDatabase) {
1572
+ // Usually a genuinely new column family (existingTableMeta above found no catalog
1573
+ // entry), but an interrupted drop just completed above can leave the physical CF
1574
+ // behind under its old codec even though the catalog entry is gone — same fallback
1575
+ // as the reconcile paths covers that remnant case too.
1233
1576
  primaryStore = openRocksDatabase(rootStore.path, { ...dbiInit, name: dbiName, cache: true });
1234
1577
  }
1235
1578
  else {
@@ -1950,7 +2293,9 @@ function getDefaultCompression() {
1950
2293
  LMDB_COMPRESSION_OPTS['dictionary'] = (0, node_fs_1.readFileSync)(STORAGE_COMPRESSION_DICTIONARY);
1951
2294
  if (STORAGE_COMPRESSION_THRESHOLD)
1952
2295
  LMDB_COMPRESSION_OPTS['threshold'] = STORAGE_COMPRESSION_THRESHOLD;
1953
- return LMDB_COMPRESSION && LMDB_COMPRESSION_OPTS;
2296
+ // normalize disabled to false so a falsy config value ('' or null) is never persisted
2297
+ // into table metadata as-is (openRocksDatabase maps defined-falsy to 'none')
2298
+ return LMDB_COMPRESSION ? LMDB_COMPRESSION_OPTS : false;
1954
2299
  }
1955
2300
  /**
1956
2301
  * Force all RocksDB databases to flush to disk.