@harperfast/harper 5.2.13 → 5.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (655) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +58 -9
  4. package/bin/copyDb.ts +282 -59
  5. package/bin/deploySetup.ts +16 -5
  6. package/bin/harper.ts +1 -1
  7. package/bin/help.ts +4 -1
  8. package/bin/lite.ts +4 -1
  9. package/bin/restart.ts +120 -4
  10. package/bin/run.ts +6 -11
  11. package/bin/upgrade.js +7 -3
  12. package/bin/workloadIdentity.ts +119 -0
  13. package/components/Application.ts +3436 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +440 -98
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +56 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +381 -32
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/deploymentOperations.ts +4 -1
  23. package/components/deploymentRecorder.ts +9 -2
  24. package/components/mcp/adapters/harperHttp.ts +4 -0
  25. package/components/mcp/listChanged.ts +4 -0
  26. package/components/mcp/toolRegistry.ts +2 -0
  27. package/components/mcp/tools/operations.ts +9 -0
  28. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  29. package/components/operations.js +537 -113
  30. package/components/operationsValidation.js +98 -3
  31. package/components/packageComponent.ts +25 -1
  32. package/components/requestRestart.ts +11 -0
  33. package/components/status/ComponentStatusRegistry.ts +59 -0
  34. package/config/RootConfigWatcher.ts +240 -40
  35. package/config/configReadRetry.ts +62 -0
  36. package/config/configUtils.ts +357 -48
  37. package/config/harperConfigEnvVars.ts +170 -27
  38. package/config/parseConfigFile.ts +34 -0
  39. package/config/readConfigFileSync.ts +44 -0
  40. package/config/watcherArming.ts +59 -0
  41. package/config-root.schema.json +33 -0
  42. package/dataLayer/blobBackup.ts +160 -50
  43. package/dataLayer/delete.ts +6 -1
  44. package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
  45. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  46. package/dataLayer/hdbInfoController.ts +34 -1
  47. package/dataLayer/insert.ts +44 -1
  48. package/dataLayer/rocksdbBackup.ts +53 -10
  49. package/dataLayer/schema.ts +11 -1
  50. package/dataLayer/schemaDescribe.ts +8 -1
  51. package/dist/agent/mcpTools.js +1 -1
  52. package/dist/agent/mcpTools.js.map +1 -1
  53. package/dist/agent/session.d.ts +22 -0
  54. package/dist/agent/session.js +26 -15
  55. package/dist/agent/session.js.map +1 -1
  56. package/dist/bin/cliOperations.js +61 -9
  57. package/dist/bin/cliOperations.js.map +1 -1
  58. package/dist/bin/copyDb.d.ts +12 -1
  59. package/dist/bin/copyDb.js +248 -60
  60. package/dist/bin/copyDb.js.map +1 -1
  61. package/dist/bin/deploySetup.d.ts +2 -0
  62. package/dist/bin/deploySetup.js +11 -3
  63. package/dist/bin/deploySetup.js.map +1 -1
  64. package/dist/bin/harper.js +1 -1
  65. package/dist/bin/harper.js.map +1 -1
  66. package/dist/bin/help.js +4 -1
  67. package/dist/bin/help.js.map +1 -1
  68. package/dist/bin/lite.js +4 -1
  69. package/dist/bin/lite.js.map +1 -1
  70. package/dist/bin/restart.js +91 -6
  71. package/dist/bin/restart.js.map +1 -1
  72. package/dist/bin/run.js +4 -10
  73. package/dist/bin/run.js.map +1 -1
  74. package/dist/bin/upgrade.js +4 -3
  75. package/dist/bin/upgrade.js.map +1 -1
  76. package/dist/bin/workloadIdentity.d.ts +18 -0
  77. package/dist/bin/workloadIdentity.js +100 -0
  78. package/dist/bin/workloadIdentity.js.map +1 -0
  79. package/dist/components/Application.d.ts +235 -17
  80. package/dist/components/Application.js +3038 -238
  81. package/dist/components/Application.js.map +1 -1
  82. package/dist/components/ApplicationScope.d.ts +8 -0
  83. package/dist/components/ApplicationScope.js +7 -0
  84. package/dist/components/ApplicationScope.js.map +1 -1
  85. package/dist/components/EntryHandler.js +26 -10
  86. package/dist/components/EntryHandler.js.map +1 -1
  87. package/dist/components/OptionsWatcher.d.ts +4 -0
  88. package/dist/components/OptionsWatcher.js +440 -99
  89. package/dist/components/OptionsWatcher.js.map +1 -1
  90. package/dist/components/RuntimeModuleTracker.js +40 -6
  91. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  92. package/dist/components/Scope.js +52 -13
  93. package/dist/components/Scope.js.map +1 -1
  94. package/dist/components/awaitRestart.d.ts +33 -0
  95. package/dist/components/awaitRestart.js +61 -0
  96. package/dist/components/awaitRestart.js.map +1 -0
  97. package/dist/components/componentLoader.d.ts +38 -1
  98. package/dist/components/componentLoader.js +313 -24
  99. package/dist/components/componentLoader.js.map +1 -1
  100. package/dist/components/componentPreparationLock.d.ts +5 -0
  101. package/dist/components/componentPreparationLock.js +14 -6
  102. package/dist/components/componentPreparationLock.js.map +1 -1
  103. package/dist/components/deploymentOperations.js +4 -1
  104. package/dist/components/deploymentOperations.js.map +1 -1
  105. package/dist/components/deploymentRecorder.d.ts +4 -2
  106. package/dist/components/deploymentRecorder.js +1 -0
  107. package/dist/components/deploymentRecorder.js.map +1 -1
  108. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  109. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  110. package/dist/components/mcp/listChanged.js +5 -0
  111. package/dist/components/mcp/listChanged.js.map +1 -1
  112. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  113. package/dist/components/mcp/toolRegistry.js.map +1 -1
  114. package/dist/components/mcp/tools/operations.d.ts +5 -0
  115. package/dist/components/mcp/tools/operations.js +9 -0
  116. package/dist/components/mcp/tools/operations.js.map +1 -1
  117. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  118. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  119. package/dist/components/operations.d.ts +28 -0
  120. package/dist/components/operations.js +476 -113
  121. package/dist/components/operations.js.map +1 -1
  122. package/dist/components/operationsValidation.js +97 -3
  123. package/dist/components/operationsValidation.js.map +1 -1
  124. package/dist/components/packageComponent.js +24 -0
  125. package/dist/components/packageComponent.js.map +1 -1
  126. package/dist/components/requestRestart.d.ts +1 -0
  127. package/dist/components/requestRestart.js +7 -0
  128. package/dist/components/requestRestart.js.map +1 -1
  129. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  130. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  131. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  132. package/dist/config/RootConfigWatcher.d.ts +9 -1
  133. package/dist/config/RootConfigWatcher.js +228 -37
  134. package/dist/config/RootConfigWatcher.js.map +1 -1
  135. package/dist/config/configReadRetry.d.ts +8 -0
  136. package/dist/config/configReadRetry.js +62 -0
  137. package/dist/config/configReadRetry.js.map +1 -0
  138. package/dist/config/configUtils.d.ts +11 -2
  139. package/dist/config/configUtils.js +304 -47
  140. package/dist/config/configUtils.js.map +1 -1
  141. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  142. package/dist/config/harperConfigEnvVars.js +162 -25
  143. package/dist/config/harperConfigEnvVars.js.map +1 -1
  144. package/dist/config/parseConfigFile.d.ts +4 -0
  145. package/dist/config/parseConfigFile.js +35 -0
  146. package/dist/config/parseConfigFile.js.map +1 -0
  147. package/dist/config/readConfigFileSync.d.ts +1 -0
  148. package/dist/config/readConfigFileSync.js +47 -0
  149. package/dist/config/readConfigFileSync.js.map +1 -0
  150. package/dist/config/watcherArming.d.ts +15 -0
  151. package/dist/config/watcherArming.js +59 -0
  152. package/dist/config/watcherArming.js.map +1 -0
  153. package/dist/dataLayer/blobBackup.d.ts +49 -20
  154. package/dist/dataLayer/blobBackup.js +139 -50
  155. package/dist/dataLayer/blobBackup.js.map +1 -1
  156. package/dist/dataLayer/delete.js +1 -1
  157. package/dist/dataLayer/delete.js.map +1 -1
  158. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  159. package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
  160. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  161. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  162. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  163. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  164. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  165. package/dist/dataLayer/hdbInfoController.js +30 -1
  166. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  167. package/dist/dataLayer/insert.d.ts +9 -1
  168. package/dist/dataLayer/insert.js +30 -0
  169. package/dist/dataLayer/insert.js.map +1 -1
  170. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  171. package/dist/dataLayer/rocksdbBackup.js +45 -8
  172. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  173. package/dist/dataLayer/schema.js +8 -0
  174. package/dist/dataLayer/schema.js.map +1 -1
  175. package/dist/dataLayer/schemaDescribe.js +8 -1
  176. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  177. package/dist/index.d.ts +2 -1
  178. package/dist/index.js +4 -1
  179. package/dist/index.js.map +1 -1
  180. package/dist/json/systemSchema.json +55 -0
  181. package/dist/resources/DatabaseTransaction.d.ts +77 -0
  182. package/dist/resources/DatabaseTransaction.js +586 -54
  183. package/dist/resources/DatabaseTransaction.js.map +1 -1
  184. package/dist/resources/LMDBTransaction.d.ts +2 -1
  185. package/dist/resources/LMDBTransaction.js +43 -6
  186. package/dist/resources/LMDBTransaction.js.map +1 -1
  187. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  188. package/dist/resources/PrimaryRocksDatabase.js +49 -5
  189. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  190. package/dist/resources/RecordEncoder.d.ts +20 -0
  191. package/dist/resources/RecordEncoder.js +110 -9
  192. package/dist/resources/RecordEncoder.js.map +1 -1
  193. package/dist/resources/RequestTarget.d.ts +2 -0
  194. package/dist/resources/RequestTarget.js.map +1 -1
  195. package/dist/resources/Resource.js +117 -24
  196. package/dist/resources/Resource.js.map +1 -1
  197. package/dist/resources/ResourceInterface.d.ts +28 -1
  198. package/dist/resources/ResourceInterface.js.map +1 -1
  199. package/dist/resources/RocksIndexStore.d.ts +6 -1
  200. package/dist/resources/RocksIndexStore.js +24 -9
  201. package/dist/resources/RocksIndexStore.js.map +1 -1
  202. package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
  203. package/dist/resources/RocksTransactionLogStore.js +160 -49
  204. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  205. package/dist/resources/Table.d.ts +165 -10
  206. package/dist/resources/Table.js +1805 -303
  207. package/dist/resources/Table.js.map +1 -1
  208. package/dist/resources/analytics/write.js +10 -3
  209. package/dist/resources/analytics/write.js.map +1 -1
  210. package/dist/resources/auditStore.d.ts +181 -2
  211. package/dist/resources/auditStore.js +640 -29
  212. package/dist/resources/auditStore.js.map +1 -1
  213. package/dist/resources/blob.d.ts +129 -9
  214. package/dist/resources/blob.js +992 -114
  215. package/dist/resources/blob.js.map +1 -1
  216. package/dist/resources/branchDatabase.d.ts +48 -0
  217. package/dist/resources/branchDatabase.js +892 -0
  218. package/dist/resources/branchDatabase.js.map +1 -0
  219. package/dist/resources/crdt.js +50 -12
  220. package/dist/resources/crdt.js.map +1 -1
  221. package/dist/resources/dataLoader.js +3 -4
  222. package/dist/resources/dataLoader.js.map +1 -1
  223. package/dist/resources/databases.d.ts +157 -14
  224. package/dist/resources/databases.js +1462 -325
  225. package/dist/resources/databases.js.map +1 -1
  226. package/dist/resources/defineTable.d.ts +10 -2
  227. package/dist/resources/defineTable.js +9 -1
  228. package/dist/resources/defineTable.js.map +1 -1
  229. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  230. package/dist/resources/derivedIndexRegistry.js +68 -0
  231. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  232. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  233. package/dist/resources/derivedIndexRuntime.js +2027 -0
  234. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  235. package/dist/resources/graphql.d.ts +1 -1
  236. package/dist/resources/graphql.js +52 -16
  237. package/dist/resources/graphql.js.map +1 -1
  238. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
  239. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
  240. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  241. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  242. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  243. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  244. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  245. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  246. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  247. package/dist/resources/longLivedTransactions.d.ts +71 -0
  248. package/dist/resources/longLivedTransactions.js +358 -0
  249. package/dist/resources/longLivedTransactions.js.map +1 -0
  250. package/dist/resources/models/backendRegistry.d.ts +26 -0
  251. package/dist/resources/models/backendRegistry.js +60 -2
  252. package/dist/resources/models/backendRegistry.js.map +1 -1
  253. package/dist/resources/models/bootstrap.d.ts +33 -1
  254. package/dist/resources/models/bootstrap.js +416 -31
  255. package/dist/resources/models/bootstrap.js.map +1 -1
  256. package/dist/resources/nodeIdMapping.d.ts +5 -0
  257. package/dist/resources/nodeIdMapping.js +49 -0
  258. package/dist/resources/nodeIdMapping.js.map +1 -1
  259. package/dist/resources/recordLock.d.ts +123 -0
  260. package/dist/resources/recordLock.js +315 -0
  261. package/dist/resources/recordLock.js.map +1 -0
  262. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  263. package/dist/resources/recordLockCoordinator.js +2565 -0
  264. package/dist/resources/recordLockCoordinator.js.map +1 -0
  265. package/dist/resources/replayLogs.d.ts +14 -1
  266. package/dist/resources/replayLogs.js +157 -24
  267. package/dist/resources/replayLogs.js.map +1 -1
  268. package/dist/resources/replayLogsGuards.d.ts +94 -7
  269. package/dist/resources/replayLogsGuards.js +111 -7
  270. package/dist/resources/replayLogsGuards.js.map +1 -1
  271. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  272. package/dist/resources/replicatedApplyFailure.js +63 -0
  273. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  274. package/dist/resources/scheduler/scheduler.js +3 -3
  275. package/dist/resources/scheduler/scheduler.js.map +1 -1
  276. package/dist/resources/search.d.ts +10 -4
  277. package/dist/resources/search.js +283 -37
  278. package/dist/resources/search.js.map +1 -1
  279. package/dist/resources/tracked.d.ts +5 -1
  280. package/dist/resources/tracked.js +74 -23
  281. package/dist/resources/tracked.js.map +1 -1
  282. package/dist/resources/transactionBroadcast.d.ts +1 -1
  283. package/dist/resources/transactionBroadcast.js +2 -2
  284. package/dist/resources/transactionBroadcast.js.map +1 -1
  285. package/dist/security/auth.js +61 -30
  286. package/dist/security/auth.js.map +1 -1
  287. package/dist/security/authn/oidc/claims.d.ts +22 -0
  288. package/dist/security/authn/oidc/claims.js +71 -0
  289. package/dist/security/authn/oidc/claims.js.map +1 -0
  290. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  291. package/dist/security/authn/oidc/identityToken.js +111 -0
  292. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  293. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  294. package/dist/security/authn/oidc/jwks.js +261 -0
  295. package/dist/security/authn/oidc/jwks.js.map +1 -0
  296. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  297. package/dist/security/authn/oidc/providers/generic.js +34 -0
  298. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  299. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  300. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  301. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  302. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  303. package/dist/security/authn/oidc/providers/index.js +24 -0
  304. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  305. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  306. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  307. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  308. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  309. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  310. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  311. package/dist/security/authn/oidc/types.d.ts +38 -0
  312. package/dist/security/authn/oidc/types.js +6 -0
  313. package/dist/security/authn/oidc/types.js.map +1 -0
  314. package/dist/security/certificateVerification/index.js +40 -11
  315. package/dist/security/certificateVerification/index.js.map +1 -1
  316. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  317. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  318. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  319. package/dist/security/certificateVerification/types.d.ts +1 -0
  320. package/dist/security/credentialProvenance.d.ts +35 -0
  321. package/dist/security/credentialProvenance.js +51 -0
  322. package/dist/security/credentialProvenance.js.map +1 -0
  323. package/dist/security/credentialRejection.d.ts +4 -0
  324. package/dist/security/credentialRejection.js +24 -0
  325. package/dist/security/credentialRejection.js.map +1 -0
  326. package/dist/security/deferredAuthentication.d.ts +36 -0
  327. package/dist/security/deferredAuthentication.js +70 -0
  328. package/dist/security/deferredAuthentication.js.map +1 -0
  329. package/dist/security/impersonation.d.ts +21 -0
  330. package/dist/security/impersonation.js +108 -9
  331. package/dist/security/impersonation.js.map +1 -1
  332. package/dist/security/jsLoader.d.ts +6 -0
  333. package/dist/security/jsLoader.js +77 -15
  334. package/dist/security/jsLoader.js.map +1 -1
  335. package/dist/security/keys.js +301 -71
  336. package/dist/security/keys.js.map +1 -1
  337. package/dist/security/operationScope.d.ts +21 -0
  338. package/dist/security/operationScope.js +36 -0
  339. package/dist/security/operationScope.js.map +1 -0
  340. package/dist/security/permissionsTranslator.js +21 -0
  341. package/dist/security/permissionsTranslator.js.map +1 -1
  342. package/dist/security/tokenAuthentication.d.ts +19 -1
  343. package/dist/security/tokenAuthentication.js +191 -10
  344. package/dist/security/tokenAuthentication.js.map +1 -1
  345. package/dist/security/user.js +4 -3
  346. package/dist/security/user.js.map +1 -1
  347. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  348. package/dist/server/DurableSubscriptionsSession.js +67 -10
  349. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  350. package/dist/server/REST.js +106 -2
  351. package/dist/server/REST.js.map +1 -1
  352. package/dist/server/graphqlQuerying.js +4 -0
  353. package/dist/server/graphqlQuerying.js.map +1 -1
  354. package/dist/server/http.d.ts +16 -1
  355. package/dist/server/http.js +122 -17
  356. package/dist/server/http.js.map +1 -1
  357. package/dist/server/itc/serverHandlers.js +8 -1
  358. package/dist/server/itc/serverHandlers.js.map +1 -1
  359. package/dist/server/jobs/jobProcess.js +6 -2
  360. package/dist/server/jobs/jobProcess.js.map +1 -1
  361. package/dist/server/jobs/jobs.js +4 -1
  362. package/dist/server/jobs/jobs.js.map +1 -1
  363. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  364. package/dist/server/liveSubscriptionAuth.js +105 -39
  365. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  366. package/dist/server/loadRootComponents.js +49 -10
  367. package/dist/server/loadRootComponents.js.map +1 -1
  368. package/dist/server/mqtt.d.ts +2 -0
  369. package/dist/server/mqtt.js +165 -30
  370. package/dist/server/mqtt.js.map +1 -1
  371. package/dist/server/nodeName.d.ts +2 -0
  372. package/dist/server/nodeName.js +107 -23
  373. package/dist/server/nodeName.js.map +1 -1
  374. package/dist/server/serverHelpers/Headers.d.ts +27 -0
  375. package/dist/server/serverHelpers/Headers.js +145 -1
  376. package/dist/server/serverHelpers/Headers.js.map +1 -1
  377. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  378. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  379. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  380. package/dist/server/serverHelpers/Request.d.ts +5 -10
  381. package/dist/server/serverHelpers/Request.js +38 -136
  382. package/dist/server/serverHelpers/Request.js.map +1 -1
  383. package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
  384. package/dist/server/serverHelpers/contentTypes.js +221 -40
  385. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  386. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  387. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  388. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  389. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  390. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  391. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  392. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  393. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  394. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  395. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  396. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  397. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  398. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  399. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  400. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  401. package/dist/server/serverHelpers/serverUtilities.js +178 -29
  402. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  403. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  404. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  405. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  406. package/dist/server/serverHelpers/uwsServer.js +19 -1
  407. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  408. package/dist/server/static.js +24 -28
  409. package/dist/server/static.js.map +1 -1
  410. package/dist/server/storageReclamation.d.ts +5 -0
  411. package/dist/server/storageReclamation.js +17 -1
  412. package/dist/server/storageReclamation.js.map +1 -1
  413. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  414. package/dist/server/threads/isolatedApplications.js +171 -0
  415. package/dist/server/threads/isolatedApplications.js.map +1 -0
  416. package/dist/server/threads/itc.d.ts +7 -2
  417. package/dist/server/threads/itc.js +5 -1
  418. package/dist/server/threads/itc.js.map +1 -1
  419. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  420. package/dist/server/threads/logRotationTransport.js +33 -0
  421. package/dist/server/threads/logRotationTransport.js.map +1 -0
  422. package/dist/server/threads/manageThreads.d.ts +83 -3
  423. package/dist/server/threads/manageThreads.js +769 -75
  424. package/dist/server/threads/manageThreads.js.map +1 -1
  425. package/dist/server/threads/socketRouter.d.ts +1 -0
  426. package/dist/server/threads/socketRouter.js +277 -31
  427. package/dist/server/threads/socketRouter.js.map +1 -1
  428. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  429. package/dist/server/threads/threadHeapMemory.js +31 -0
  430. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  431. package/dist/server/threads/threadServer.js +76 -22
  432. package/dist/server/threads/threadServer.js.map +1 -1
  433. package/dist/sqlEngine/config.d.ts +1 -3
  434. package/dist/sqlEngine/config.js +19 -16
  435. package/dist/sqlEngine/config.js.map +1 -1
  436. package/dist/sqlTranslator/index.d.ts +1 -1
  437. package/dist/sqlTranslator/index.js +30 -7
  438. package/dist/sqlTranslator/index.js.map +1 -1
  439. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  440. package/dist/upgrade/directives/5-3-0.js +148 -0
  441. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  442. package/dist/upgrade/directives/directivesController.js +2 -1
  443. package/dist/upgrade/directives/directivesController.js.map +1 -1
  444. package/dist/utility/OperationFunctionCaller.js +2 -1
  445. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  446. package/dist/utility/common_utils.d.ts +16 -0
  447. package/dist/utility/common_utils.js +32 -6
  448. package/dist/utility/common_utils.js.map +1 -1
  449. package/dist/utility/componentNames.d.ts +8 -0
  450. package/dist/utility/componentNames.js +12 -1
  451. package/dist/utility/componentNames.js.map +1 -1
  452. package/dist/utility/environment/environmentManager.js +3 -6
  453. package/dist/utility/environment/environmentManager.js.map +1 -1
  454. package/dist/utility/environment/systemInformation.d.ts +1 -0
  455. package/dist/utility/environment/systemInformation.js +1 -0
  456. package/dist/utility/environment/systemInformation.js.map +1 -1
  457. package/dist/utility/errors/commonErrors.d.ts +2 -0
  458. package/dist/utility/errors/commonErrors.js +2 -0
  459. package/dist/utility/errors/commonErrors.js.map +1 -1
  460. package/dist/utility/errors/hdbError.d.ts +33 -0
  461. package/dist/utility/errors/hdbError.js +58 -1
  462. package/dist/utility/errors/hdbError.js.map +1 -1
  463. package/dist/utility/globalSchema.d.ts +18 -0
  464. package/dist/utility/hdbTerms.d.ts +18 -0
  465. package/dist/utility/hdbTerms.js +20 -2
  466. package/dist/utility/hdbTerms.js.map +1 -1
  467. package/dist/utility/logging/harper_logger.d.ts +2 -0
  468. package/dist/utility/logging/harper_logger.js +286 -30
  469. package/dist/utility/logging/harper_logger.js.map +1 -1
  470. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  471. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  472. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  473. package/dist/utility/logging/logRotation.d.ts +46 -0
  474. package/dist/utility/logging/logRotation.js +365 -0
  475. package/dist/utility/logging/logRotation.js.map +1 -0
  476. package/dist/utility/logging/logRotator.d.ts +1 -1
  477. package/dist/utility/logging/logRotator.js +192 -85
  478. package/dist/utility/logging/logRotator.js.map +1 -1
  479. package/dist/utility/nodeIdentity.d.ts +9 -0
  480. package/dist/utility/nodeIdentity.js +58 -0
  481. package/dist/utility/nodeIdentity.js.map +1 -0
  482. package/dist/utility/npmUtilities.js +11 -7
  483. package/dist/utility/npmUtilities.js.map +1 -1
  484. package/dist/utility/operationPermissions.d.ts +3 -1
  485. package/dist/utility/operationPermissions.js +16 -1
  486. package/dist/utility/operationPermissions.js.map +1 -1
  487. package/dist/utility/operation_authorization.d.ts +10 -7
  488. package/dist/utility/operation_authorization.js +212 -41
  489. package/dist/utility/operation_authorization.js.map +1 -1
  490. package/dist/utility/watchPath.d.ts +29 -0
  491. package/dist/utility/watchPath.js +68 -0
  492. package/dist/utility/watchPath.js.map +1 -0
  493. package/dist/utility/watcherFallback.d.ts +41 -0
  494. package/dist/utility/watcherFallback.js +153 -0
  495. package/dist/utility/watcherFallback.js.map +1 -1
  496. package/dist/validation/configValidator.d.ts +12 -0
  497. package/dist/validation/configValidator.js +205 -75
  498. package/dist/validation/configValidator.js.map +1 -1
  499. package/dist/validation/installValidator.js +12 -0
  500. package/dist/validation/installValidator.js.map +1 -1
  501. package/dist/validation/validationWrapper.d.ts +11 -0
  502. package/dist/validation/validationWrapper.js +16 -3
  503. package/dist/validation/validationWrapper.js.map +1 -1
  504. package/index.ts +8 -0
  505. package/json/systemSchema.json +55 -0
  506. package/npm-shrinkwrap.json +286 -194
  507. package/package.json +15 -7
  508. package/resources/DESIGN.md +219 -52
  509. package/resources/DatabaseTransaction.ts +657 -52
  510. package/resources/LMDBTransaction.ts +46 -6
  511. package/resources/PrimaryRocksDatabase.ts +46 -7
  512. package/resources/RecordEncoder.ts +124 -9
  513. package/resources/RequestTarget.ts +2 -0
  514. package/resources/Resource.ts +114 -22
  515. package/resources/ResourceInterface.ts +31 -0
  516. package/resources/RocksIndexStore.ts +30 -9
  517. package/resources/RocksTransactionLogStore.ts +188 -51
  518. package/resources/Table.ts +1960 -301
  519. package/resources/analytics/write.ts +10 -3
  520. package/resources/auditStore.ts +647 -32
  521. package/resources/blob.ts +1029 -110
  522. package/resources/branchDatabase.ts +941 -0
  523. package/resources/crdt.ts +76 -12
  524. package/resources/dataLoader.ts +3 -4
  525. package/resources/databases.ts +1584 -320
  526. package/resources/defineTable.ts +18 -2
  527. package/resources/derivedIndexRegistry.ts +56 -0
  528. package/resources/derivedIndexRuntime.ts +2292 -0
  529. package/resources/graphql.ts +72 -17
  530. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
  531. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  532. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  533. package/resources/longLivedTransactions.ts +360 -0
  534. package/resources/models/backendRegistry.ts +84 -2
  535. package/resources/models/bootstrap.ts +473 -28
  536. package/resources/nodeIdMapping.ts +50 -0
  537. package/resources/recordLock.ts +419 -0
  538. package/resources/recordLockCoordinator.ts +3043 -0
  539. package/resources/replayLogs.ts +152 -26
  540. package/resources/replayLogsGuards.ts +171 -8
  541. package/resources/replicatedApplyFailure.ts +77 -0
  542. package/resources/scheduler/scheduler.ts +4 -4
  543. package/resources/search.ts +288 -46
  544. package/resources/tracked.ts +73 -22
  545. package/resources/transactionBroadcast.ts +3 -3
  546. package/security/auth.ts +68 -29
  547. package/security/authn/oidc/claims.ts +72 -0
  548. package/security/authn/oidc/identityToken.ts +129 -0
  549. package/security/authn/oidc/jwks.ts +260 -0
  550. package/security/authn/oidc/providers/generic.ts +40 -0
  551. package/security/authn/oidc/providers/githubActions.ts +137 -0
  552. package/security/authn/oidc/providers/index.ts +52 -0
  553. package/security/authn/oidc/tokenExchange.ts +300 -0
  554. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  555. package/security/authn/oidc/types.ts +41 -0
  556. package/security/certificateVerification/index.ts +54 -13
  557. package/security/certificateVerification/trustedIssuers.ts +76 -0
  558. package/security/certificateVerification/types.ts +1 -0
  559. package/security/credentialProvenance.ts +47 -0
  560. package/security/credentialRejection.ts +22 -0
  561. package/security/deferredAuthentication.ts +71 -0
  562. package/security/impersonation.ts +117 -12
  563. package/security/jsLoader.ts +83 -18
  564. package/security/keys.ts +298 -72
  565. package/security/operationScope.ts +33 -0
  566. package/security/permissionsTranslator.js +23 -0
  567. package/security/tokenAuthentication.ts +233 -12
  568. package/security/user.ts +4 -3
  569. package/server/DESIGN.md +194 -16
  570. package/server/DurableSubscriptionsSession.ts +71 -11
  571. package/server/REST.ts +115 -4
  572. package/server/graphqlQuerying.ts +4 -0
  573. package/server/http.ts +133 -20
  574. package/server/itc/serverHandlers.js +8 -1
  575. package/server/jobs/jobProcess.ts +8 -2
  576. package/server/jobs/jobs.ts +4 -1
  577. package/server/liveSubscriptionAuth.ts +129 -46
  578. package/server/loadRootComponents.js +50 -8
  579. package/server/mqtt.ts +179 -38
  580. package/server/nodeName.ts +103 -21
  581. package/server/serverHelpers/Headers.ts +135 -0
  582. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  583. package/server/serverHelpers/Request.ts +33 -131
  584. package/server/serverHelpers/contentTypes.ts +217 -36
  585. package/server/serverHelpers/deployValidationState.ts +24 -13
  586. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  587. package/server/serverHelpers/registeredOperations.ts +79 -22
  588. package/server/serverHelpers/requestSanitization.ts +15 -0
  589. package/server/serverHelpers/serverHandlers.js +6 -3
  590. package/server/serverHelpers/serverUtilities.ts +232 -40
  591. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  592. package/server/serverHelpers/uwsServer.ts +17 -2
  593. package/server/static.ts +23 -29
  594. package/server/storageReclamation.ts +15 -2
  595. package/server/threads/isolatedApplications.ts +157 -0
  596. package/server/threads/itc.js +11 -1
  597. package/server/threads/logRotationTransport.ts +40 -0
  598. package/server/threads/manageThreads.js +771 -66
  599. package/server/threads/socketRouter.ts +291 -30
  600. package/server/threads/threadHeapMemory.ts +26 -0
  601. package/server/threads/threadServer.js +73 -22
  602. package/sqlTranslator/index.ts +31 -8
  603. package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
  604. package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
  605. package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
  606. package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
  607. package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
  608. package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
  609. package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
  610. package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
  611. package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
  612. package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
  613. package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
  614. package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
  615. package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
  616. package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  617. package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
  618. package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
  619. package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
  620. package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
  621. package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
  622. package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
  623. package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
  624. package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
  625. package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
  626. package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
  627. package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
  628. package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  629. package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
  630. package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
  631. package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
  632. package/studio/web/index.html +1 -1
  633. package/upgrade/directives/5-3-0.ts +132 -0
  634. package/upgrade/directives/directivesController.ts +2 -1
  635. package/utility/OperationFunctionCaller.ts +2 -1
  636. package/utility/common_utils.ts +30 -5
  637. package/utility/componentNames.ts +12 -0
  638. package/utility/environment/environmentManager.ts +3 -7
  639. package/utility/environment/systemInformation.ts +7 -0
  640. package/utility/errors/commonErrors.ts +4 -0
  641. package/utility/errors/hdbError.ts +57 -0
  642. package/utility/hdbTerms.ts +19 -0
  643. package/utility/logging/harper_logger.ts +278 -26
  644. package/utility/logging/logGenerationCoordinator.ts +196 -0
  645. package/utility/logging/logRotation.ts +367 -0
  646. package/utility/logging/logRotator.ts +213 -81
  647. package/utility/nodeIdentity.ts +45 -0
  648. package/utility/npmUtilities.ts +12 -8
  649. package/utility/operationPermissions.ts +18 -1
  650. package/utility/operation_authorization.ts +231 -42
  651. package/utility/watchPath.ts +63 -0
  652. package/utility/watcherFallback.ts +148 -0
  653. package/validation/configValidator.ts +215 -75
  654. package/validation/installValidator.ts +15 -0
  655. package/validation/validationWrapper.ts +18 -4
@@ -5,6 +5,12 @@ import { copyFile, link, mkdir, readdir, rename, rm, unlink, writeFile } from 'n
5
5
  import { dirname, join, relative } from 'node:path';
6
6
  import { ClientError } from '../utility/errors/hdbError.ts';
7
7
  import logger from '../utility/logging/harper_logger.ts';
8
+ import {
9
+ type BlobCaptureDisposition,
10
+ classifyBlobFileForCapture,
11
+ createCaptureMarker,
12
+ isSystemicIoError,
13
+ } from '../resources/blob.ts';
8
14
 
9
15
  /**
10
16
  * Managed-backup snapshotting of a database's file-backed blobs.
@@ -21,17 +27,17 @@ import logger from '../utility/logging/harper_logger.ts';
21
27
  * possible (cheap, no extra space on the same filesystem) and copied otherwise (never symlinked, so
22
28
  * a snapshot is a standalone set of files that survives independent of the live blob).
23
29
  *
24
- * Consistency is best-effort and point-in-time-ish, matching how the engine treats the transaction
25
- * log: the walk captures whatever files exist at snapshot time. A blob deleted mid-walk is skipped;
26
- * a blob being written mid-walk is captured as-is (a hard link shares the inode, so it reflects the
27
- * writer's final bytes; a cross-filesystem copy captures the bytes present at copy time). Harper
28
- * does not freeze blob writes for the duration of a backup.
30
+ * Harper does not freeze blob writes for a backup, and a blob is written in place at its final path,
31
+ * so the walk can meet one that is still growing. Hard-linking that inode would put bytes in the
32
+ * snapshot that keep changing after it was taken. Every entry returned by the walk is therefore
33
+ * classified (`captureBlobFile`): a complete blob is linked, and one that becomes unavailable or is
34
+ * not yet complete is replaced by a marker rather than dropped. This closes the classify-to-capture
35
+ * race and keeps that file id reserved; a file reclaimed before its parent directory is read remains
36
+ * outside the snapshot.
29
37
  *
30
- * Hard-linking is safe against later mutation because Harper blobs are content-addressed and
31
- * write-once: each write allocates a fresh monotonic file id (a new path), and an update or delete
32
- * unlinks the old path rather than rewriting it in place — so a snapshot's hard link keeps the exact
33
- * bytes alive even after the live blob is deleted, and no in-place overwrite can retroactively alter
34
- * a snapshot.
38
+ * Past that gate, hard-linking is safe against later mutation: each write takes a fresh monotonic
39
+ * file id, and an update, delete, or in-place repair replaces or unlinks the path rather than
40
+ * rewriting the inode.
35
41
  */
36
42
 
37
43
  /** Directory holding all blob snapshots for a backup repository. */
@@ -46,17 +52,17 @@ export function blobSnapshotDir(backupDir: string, backupId: number): string {
46
52
 
47
53
  /**
48
54
  * Hard-link `src` to `dest`, falling back to a copy when the two are on different filesystems (or
49
- * the filesystem does not support additional hard links). Never creates a symlink. A source that
50
- * vanished mid-walk (a concurrent blob delete) is skipped rather than failing the whole snapshot.
55
+ * the filesystem does not support additional hard links). Never creates a symlink. Returns false
56
+ * when the source vanishes so the caller can substitute a marker for it. `counts.copied` is what a
57
+ * caller on a latency budget needs: the fallback turns a constant-time clone into an O(bytes) one.
51
58
  */
52
- async function linkOrCopy(src: string, dest: string): Promise<void> {
59
+ async function linkOrCopy(src: string, dest: string, counts?: { copied: number }): Promise<boolean> {
53
60
  await mkdir(dirname(dest), { recursive: true });
54
61
  try {
55
62
  await link(src, dest);
56
63
  } catch (error: any) {
57
64
  if (error.code === 'ENOENT') {
58
- // src disappeared (concurrent delete) — nothing to snapshot
59
- if (!existsSync(src)) return;
65
+ if (!existsSync(src)) return false; // vanished since it was classified
60
66
  throw error;
61
67
  }
62
68
  if (
@@ -66,24 +72,86 @@ async function linkOrCopy(src: string, dest: string): Promise<void> {
66
72
  error.code === 'ENOTSUP' ||
67
73
  error.code === 'EOPNOTSUPP'
68
74
  ) {
69
- await copyFile(src, dest);
70
- return;
75
+ try {
76
+ await copyFile(src, dest);
77
+ } catch (copyError: any) {
78
+ if (copyError.code === 'ENOENT') return false;
79
+ throw copyError;
80
+ }
81
+ if (counts) counts.copied++;
82
+ return true;
71
83
  }
72
84
  if (error.code === 'EEXIST') {
73
85
  await unlink(dest);
74
- await linkOrCopy(src, dest);
75
- return;
86
+ return linkOrCopy(src, dest, counts);
76
87
  }
77
88
  throw error;
78
89
  }
90
+ return true;
91
+ }
92
+
93
+ /** What a substituted marker says happened, so a branch clone doesn't report itself as a backup. */
94
+ export interface CaptureMarkerReasons {
95
+ gone: string;
96
+ pending: string;
97
+ }
98
+
99
+ const BACKUP_MARKER_REASONS: CaptureMarkerReasons = {
100
+ gone: 'blob was deleted while this backup was being taken',
101
+ pending: 'blob was not yet complete when this backup was taken',
102
+ };
103
+
104
+ /** Put one blob file into the destination, returning how the entry was captured. */
105
+ async function captureBlobFile(
106
+ srcPath: string,
107
+ destPath: string,
108
+ reasons: CaptureMarkerReasons = BACKUP_MARKER_REASONS,
109
+ counts?: { copied: number }
110
+ ): Promise<BlobCaptureDisposition> {
111
+ let disposition: BlobCaptureDisposition;
112
+ try {
113
+ disposition = await classifyBlobFileForCapture(srcPath);
114
+ } catch (error) {
115
+ // This rethrow is unverified: only the membership of SYSTEMIC_IO_ERRORS is tested, because
116
+ // reaching it needs a real host-level fault no test here can produce.
117
+ if (isSystemicIoError(error)) throw error;
118
+ // Fall back to what this walk did before it classified anything. `link()` needs no read permission
119
+ // on the source, so a blob this failed to *read* may still be perfectly capturable; substituting
120
+ // here would turn an unreadable-but-valid root into a backup of nothing but stubs.
121
+ logger.warn(`Could not verify blob ${srcPath} for snapshot; capturing it unverified`, error);
122
+ disposition = 'capture';
123
+ }
124
+ if (disposition === 'skip') return disposition;
125
+ if (disposition === 'capture') {
126
+ if (await linkOrCopy(srcPath, destPath, counts)) return disposition;
127
+ disposition = 'gone';
128
+ }
129
+ await mkdir(dirname(destPath), { recursive: true });
130
+ await writeFile(destPath, createCaptureMarker(disposition, disposition === 'gone' ? reasons.gone : reasons.pending));
131
+ return disposition;
79
132
  }
80
133
 
81
134
  /**
82
135
  * Recursively copy every file under `srcRoot` into `destRoot` (hard-link-else-copy), preserving the
83
136
  * relative directory structure. Missing `srcRoot` is a no-op (a database with no blobs yet).
137
+ *
138
+ * `classify` substitutes a marker for a file that is mid-write or already gone, so the destination
139
+ * fails loudly on that blob rather than carrying a truncated one. It belongs to the capture direction
140
+ * only; restore must replace exactly what the snapshot holds.
141
+ *
142
+ * Also used by branch materialization (harper#644), which clones a base's blob roots into the
143
+ * branch's own so the OS inode refcount does the reference counting. Its walk runs inside a window
144
+ * other threads wait on, which is what `onProgress` is for.
84
145
  */
85
- async function copyTree(srcRoot: string, destRoot: string): Promise<void> {
86
- if (!existsSync(srcRoot)) return;
146
+ export async function copyTree(
147
+ srcRoot: string,
148
+ destRoot: string,
149
+ classify = false,
150
+ reasons?: CaptureMarkerReasons,
151
+ onProgress?: () => void
152
+ ): Promise<{ substituted: number; captured: number; copied: number }> {
153
+ const counts = { substituted: 0, captured: 0, copied: 0 };
154
+ if (!existsSync(srcRoot)) return counts;
87
155
  const stack: string[] = [srcRoot];
88
156
  while (stack.length > 0) {
89
157
  const dir = stack.pop() as string;
@@ -99,63 +167,103 @@ async function copyTree(srcRoot: string, destRoot: string): Promise<void> {
99
167
  if (entry.isDirectory()) {
100
168
  stack.push(srcPath);
101
169
  } else if (entry.isFile()) {
102
- await linkOrCopy(srcPath, join(destRoot, relative(srcRoot, srcPath)));
170
+ const destPath = join(destRoot, relative(srcRoot, srcPath));
171
+ if (classify) {
172
+ const disposition = await captureBlobFile(srcPath, destPath, reasons, counts);
173
+ if (disposition === 'pending' || disposition === 'gone') counts.substituted++;
174
+ else if (disposition === 'capture') counts.captured++;
175
+ } else {
176
+ await linkOrCopy(srcPath, destPath, counts);
177
+ }
178
+ onProgress?.();
103
179
  }
104
180
  // symlinks/other node types in a blob root are not expected and are intentionally skipped
105
181
  }
106
182
  }
183
+ return counts;
107
184
  }
108
185
 
109
186
  /**
110
- * Snapshot a database's blob roots into a backup's blob directory. Writes to a temporary sibling
111
- * and atomically renames into place so a create_backup that fails mid-copy never leaves a partial
112
- * `blobs/<backupId>/` that a later restore would trust. Overwrites any pre-existing snapshot for the
113
- * same id (create_backup always produces a fresh id, so this only matters on a retried offline run).
187
+ * Copy every blob root into `destDir` as `<rootIndex>/<relpath>`, hard-linking where possible.
188
+ * Writes to a temporary sibling and atomically renames into place so a run that fails mid-copy never
189
+ * leaves a partial directory a later restore would trust, and replaces any pre-existing `destDir`.
190
+ * Shared by managed-backup snapshots and `copy-db`'s standalone blob copy (harper#2048).
114
191
  */
115
- export async function snapshotBlobs(backupDir: string, backupId: number, blobRoots: string[]): Promise<void> {
116
- const finalDir = blobSnapshotDir(backupDir, backupId);
117
- const tempDir = join(blobsRootDir(backupDir), `.tmp-${backupId}`);
192
+ export async function copyBlobRootsByIndex(destDir: string, blobRoots: string[]): Promise<void> {
193
+ const tempDir = destDir + '.tmp';
118
194
  await rm(tempDir, { recursive: true, force: true });
119
195
  await mkdir(tempDir, { recursive: true });
120
196
  try {
197
+ let substituted = 0;
198
+ let captured = 0;
121
199
  for (let index = 0; index < blobRoots.length; index++) {
122
- await copyTree(blobRoots[index], join(tempDir, String(index)));
200
+ const counts = await copyTree(blobRoots[index], join(tempDir, String(index)), true);
201
+ substituted += counts.substituted;
202
+ captured += counts.captured;
123
203
  }
124
- await rm(finalDir, { recursive: true, force: true });
125
- await rename(tempDir, finalDir);
204
+ if (substituted > 0) {
205
+ logger.warn(
206
+ `Blob copy into ${destDir} substituted ${substituted} of ${substituted + captured} blob ` +
207
+ `file(s) with PENDING or ERROR markers because they were not capturable whole.`
208
+ );
209
+ }
210
+ await rm(destDir, { recursive: true, force: true });
211
+ await rename(tempDir, destDir);
126
212
  } catch (error) {
127
213
  await rm(tempDir, { recursive: true, force: true }).catch(() => {});
128
214
  throw error;
129
215
  }
216
+ }
217
+
218
+ /**
219
+ * Snapshot a database's blob roots into a backup's blob directory. Overwrites any pre-existing
220
+ * snapshot for the same id (create_backup always produces a fresh id, so this only matters on a
221
+ * retried offline run).
222
+ */
223
+ export async function snapshotBlobs(backupDir: string, backupId: number, blobRoots: string[]): Promise<void> {
224
+ await copyBlobRootsByIndex(blobSnapshotDir(backupDir, backupId), blobRoots);
130
225
  await writeBlobsReadme(backupDir, blobRoots);
131
226
  }
132
227
 
133
228
  /**
134
229
  * Build the `blobs/README.md` documenting the blob snapshot layout, so an operator inspecting or
135
- * hand-recovering a backup can decode the numeric directories. Two variants:
136
- * - managed (default): a create_backup repository, where snapshots are keyed by backup id
230
+ * hand-recovering a backup can decode the numeric directories. Three variants:
231
+ * - `managed` (default): a create_backup repository, where snapshots are keyed by backup id
137
232
  * (`<backupId>/<rootIndex>/…`) and restore is automatic via `restore_backup`.
138
- * - archive (`archive: true`): a downloaded `get_backup` tar, which holds a single snapshot with no
139
- * backup-id level (`<rootIndex>/…`) and is restored by extracting the files back into the roots.
233
+ * - `archive`: a downloaded `get_backup` tar, which holds a single snapshot with no backup-id level
234
+ * (`<rootIndex>/…`) and is restored by extracting the files back into the roots.
235
+ * - `copy`: the companion directory `copy-db` writes beside a database copy, restored by hand.
140
236
  */
141
- export function blobsReadmeContent(blobRoots: string[], { archive = false }: { archive?: boolean } = {}): string {
237
+ export function blobsReadmeContent(
238
+ blobRoots: string[],
239
+ { variant = 'managed' }: { variant?: 'managed' | 'archive' | 'copy' } = {}
240
+ ): string {
142
241
  const rootMapping =
143
242
  blobRoots.length > 0 ? blobRoots.map((root, index) => ` ${index} -> ${root}`).join('\n') : ' (none)';
144
- const layout = archive
145
- ? '<rootIndex>/<shard1>/<shard2>/<fileId>'
146
- : '<backupId>/<rootIndex>/<shard1>/<shard2>/<fileId>';
147
- const intro = archive
148
- ? `This directory holds this database's file-backed blobs within a downloaded \`get_backup\` archive.
243
+ const layout =
244
+ variant === 'managed'
245
+ ? '<backupId>/<rootIndex>/<shard1>/<shard2>/<fileId>'
246
+ : '<rootIndex>/<shard1>/<shard2>/<fileId>';
247
+ const intro =
248
+ variant === 'copy'
249
+ ? `This directory holds the file-backed blobs of a \`copy-db\` database copy; the database file itself
250
+ is the sibling \`.mdb\` this directory is named after. Blobs are addressed by database NAME and the
251
+ configured blob roots — never by the database file's path — so a copy is only restorable with these
252
+ files: put each \`<rootIndex>/\` tree into the matching blob root of whatever database name you
253
+ restore the copy as (mapping below).`
254
+ : variant === 'archive'
255
+ ? `This directory holds this database's file-backed blobs within a downloaded \`get_backup\` archive.
149
256
  To restore them, extract each \`<rootIndex>/\` tree back into the matching blob root (see the mapping
150
257
  below and ../README.md).`
151
- : `This directory holds point-in-time snapshots of this database's file-backed blobs, captured
258
+ : `This directory holds point-in-time snapshots of this database's file-backed blobs, captured
152
259
  alongside each RocksDB managed backup. You do not restore these by hand — \`restore_backup\` puts
153
260
  them back automatically (see ../README.md); this file just documents the layout.`;
154
- const backupIdBullet = archive
155
- ? ''
156
- : `- **<backupId>** matches the RocksDB backup id (\`harper list_backups\`). Each id is a full,
261
+ const backupIdBullet =
262
+ variant === 'managed'
263
+ ? `- **<backupId>** matches the RocksDB backup id (\`harper list_backups\`). Each id is a full,
157
264
  independent snapshot (not incremental).
158
- `;
265
+ `
266
+ : '';
159
267
  return `# Harper blob snapshots
160
268
 
161
269
  ${intro}
@@ -166,7 +274,7 @@ ${intro}
166
274
 
167
275
  ${backupIdBullet}- **<rootIndex>** is which of the database's blob roots the file came from — the index into
168
276
  \`storage.blobPaths[n]\`. When \`storage.blobPaths\` is not configured there is a single default root
169
- (\`<rootPath>/blobs/<db>\`) at index 0. Current mapping for this backup:
277
+ (\`<rootPath>/blobs/<db>\`) at index 0. Current mapping:
170
278
 
171
279
  ${rootMapping}
172
280
 
@@ -175,8 +283,10 @@ ${rootMapping}
175
283
  4096 entries per directory). E.g. a blob with id \`0x12345678\` lives at \`12/345/678\`; a short id
176
284
  like \`0xc1a\` lives at \`0/0/c1a\`.
177
285
 
178
- Files are hard links to the live blobs when the backup is on the same filesystem, and copies
179
- otherwise.
286
+ Complete blobs are hard links to the live blobs when the destination is on the same filesystem, and
287
+ copies otherwise. A blob that was not capturable whole is stored as a marker instead: header type
288
+ \`0xfe\` is retryable (PENDING), while \`0xff\` is terminal (ERROR). The marker preserves the file id
289
+ but does not contain the original blob bytes.
180
290
  `;
181
291
  }
182
292
 
@@ -110,7 +110,12 @@ export async function deleteAuditLogsBefore(deleteObj: any) {
110
110
  harperLogger.info(`Finished deleting audit logs before ${deleteObj.timestamp}`);
111
111
 
112
112
  // `deleteTransactionLogsBefore` returns `entries_deleted`; the legacy result exposes it as `transactions_deleted`.
113
- return new DeleteAuditLogsBeforeResults(results.start_timestamp, results.end_timestamp, results.entries_deleted);
113
+ return new DeleteAuditLogsBeforeResults(
114
+ results.start_timestamp,
115
+ results.end_timestamp,
116
+ results.entries_deleted,
117
+ results.log_files_deleted
118
+ );
114
119
  }
115
120
 
116
121
  /**
@@ -25,6 +25,7 @@ import type {
25
25
  import { collapseData } from '../../resources/tracked.ts';
26
26
  import { errorToString } from '../../utility/logging/harper_logger.ts';
27
27
  import { RocksDatabase } from '@harperfast/rocksdb-js';
28
+ import { boundedAuditPruneEnd, raiseAuditFloor } from '../../resources/auditStore.ts';
28
29
  import { BridgeMethods } from './BridgeMethods.ts';
29
30
  import lmdbGetBackup from './lmdbBridge/lmdbMethods/lmdbGetBackup.js';
30
31
  import { createBackupStream, resolveSingleRootStore } from '../rocksdbBackup.ts';
@@ -199,10 +200,35 @@ export class ResourceBridge extends BridgeMethods {
199
200
  return this.upsertRecords(updateObj);
200
201
  }
201
202
 
202
- async upsertRecords(upsertObj) {
203
+ /**
204
+ * Create-or-replace: the stored record becomes exactly the submitted one, so an attribute the
205
+ * request omits is REMOVED rather than kept. `update`/`upsert` merge (`Table.patch`), which is the
206
+ * v4-compatible behaviour every existing client depends on and is why this is a separate operation
207
+ * rather than a flag on those. Equivalent to REST `PUT /Table/id` — same `Table.put`, so the same
208
+ * audit type, replication shape, and retained `__createdtime__`.
209
+ */
210
+ async putRecords(putObj) {
211
+ return this.upsertRecords(putObj, true);
212
+ }
213
+
214
+ async upsertRecords(upsertObj, fullRecord = false) {
203
215
  const { attributes } = insertUpdateValidate(upsertObj);
204
216
 
205
217
  let new_attributes;
218
+ // `fullRecord` arrives as an ARGUMENT, never as a field on the request. Reading it off
219
+ // `upsertObj` would let a client send `full_record: true` with an `update` and get a replace —
220
+ // which makes the operation name stop describing the write, and bypasses the attribute-scoped
221
+ // `put` denial in `verifyPerms`, since that is keyed on the operation.
222
+ //
223
+ // Without it a write over an existing record merges (`Table.patch`), so an attribute the caller
224
+ // omitted keeps its stored value and `null` stores a null. That merge is the v4-compatible
225
+ // behaviour `update`/`upsert` must keep; HarperFast/studio#1643 is the removal case it cannot
226
+ // serve, and `put` is the operation that can.
227
+ //
228
+ // A put is also one operation where a client emulating removal needs two: the record is never
229
+ // absent between them, subscribers see a single write rather than a delete followed by an
230
+ // insert, and `__createdtime__` survives (`Table._writeUpdate` retains the stored created time
231
+ // on a full update and only stamps a new one for a new entry).
206
232
  const Table = getDatabases()[upsertObj.schema][upsertObj.table];
207
233
  const context: Context = {
208
234
  user: upsertObj.hdb_user,
@@ -259,9 +285,14 @@ export class ResourceBridge extends BridgeMethods {
259
285
  }
260
286
  }
261
287
  }
288
+ // Keyed on whether there IS a record to remove from, not on which operation asked. Guarding
289
+ // the insert flag alone left `upsert` with no primary key reaching the same silent partial
290
+ // write one branch over: `insertUpdateValidate` requires a hash attribute only for
291
+ // `update`, so such a record takes the `id == undefined → Table.create` path and stored an
292
+ // auto-keyed record with the named attributes stripped, returning 200.
262
293
  await (id == undefined
263
294
  ? Table.create(record, context)
264
- : existingRecord
295
+ : existingRecord && !fullRecord
265
296
  ? Table.patch(record, context)
266
297
  : Table.put(record, context));
267
298
  keys.push(record[Table.primaryKey]);
@@ -490,8 +521,26 @@ export class ResourceBridge extends BridgeMethods {
490
521
  deleteObj.timestamp instanceof Date
491
522
  ? deleteObj.timestamp.getTime()
492
523
  : typeof deleteObj.timestamp === 'string'
493
- ? Number.parseInt(deleteObj.timestamp)
524
+ ? // Number, not Number.parseInt: parseInt takes a numeric PREFIX ('9999999999999oops' is a
525
+ // year-2286 bound to it, '1e3' is 1) where Number rejects both as NaN. Empty/whitespace is
526
+ // explicit because Number('') and Number(' ') are 0.
527
+ deleteObj.timestamp.trim() === ''
528
+ ? Number.NaN
529
+ : Number(deleteObj.timestamp)
494
530
  : deleteObj.timestamp;
531
+ // Audit keys are raw float64: NaN and negatives sort above every real timestamp (a range ending
532
+ // there spans the whole log), and Infinity records the unknown sentinel raiseAuditFloor never lifts.
533
+ // Reject anything but a finite non-negative number here, as the 400 it is rather than a server
534
+ // fault. `Number.isFinite` never coerces, so it is the type check too.
535
+ if (!Number.isFinite(before) || before < 0 || Object.is(before, -0))
536
+ throw handleHDBError(
537
+ new Error(),
538
+ `'timestamp' must be a non-negative epoch time or Date, received: ${String(deleteObj.timestamp)}`,
539
+ 400,
540
+ undefined,
541
+ undefined,
542
+ true
543
+ );
495
544
  const databaseName = deleteObj.database || deleteObj.schema || DEFAULT_DATABASE;
496
545
  const table = getTable(deleteObj);
497
546
  // A nonexistent table must not fall through to the no-table branch below — on RocksDB that
@@ -514,7 +563,14 @@ export class ResourceBridge extends BridgeMethods {
514
563
  if (tables) {
515
564
  for (const table of Object.values(tables)) {
516
565
  if (table.primaryStore instanceof RocksDatabase) {
517
- const deleted = table.primaryStore.purgeLogs({ before, includeEntryCounts: true });
566
+ // Clamp before recording: an operator-supplied bound has no ceiling of its own, and a
567
+ // floor above everything reachable never comes down (`raiseAuditFloor` only raises and
568
+ // `establishAuditFloor` skips a store that has a record), so a far-future bound would
569
+ // retire the floor for every table in this database. The purge takes the same clamped
570
+ // bound, so it cannot remove an entry the floor does not cover.
571
+ const pruneEnd = boundedAuditPruneEnd(table.auditStore, before);
572
+ raiseAuditFloor(table.auditStore, pruneEnd);
573
+ const deleted = table.primaryStore.purgeLogs({ before: pruneEnd, includeEntryCounts: true });
518
574
  totalResults.log_files_deleted += deleted.length;
519
575
  totalResults.entries_deleted += deleted.reduce((acc, file) => acc + file.entries, 0);
520
576
  break;
@@ -546,11 +602,12 @@ export class ResourceBridge extends BridgeMethods {
546
602
  // get the history of each record
547
603
  for (const id of readAuditLogObj.search_values) {
548
604
  histories[id] = (await table.getHistoryOfRecord(id)).map((auditRecord) => {
549
- let operation = auditRecord.operation ?? auditRecord.type;
550
- if (operation === 'put') operation = 'upsert';
605
+ let operation = normalizeHistoryOperation(auditRecord.operation, auditRecord.type);
551
606
  return {
552
607
  operation,
553
- timestamp: auditRecord.version,
608
+ // the transaction's position in the log, which is what this field has always
609
+ // reported on RocksDB and what groups an atomic transaction (harper#2412)
610
+ timestamp: auditRecord.localTime,
554
611
  user_name: auditRecord.user,
555
612
  ids: [id],
556
613
  records: [auditRecord.value],
@@ -745,9 +802,8 @@ async function* groupRecordsInHistory(table, start?, end?, limit?) {
745
802
  let enqueued;
746
803
  let count = 0;
747
804
  for await (const entry of table.getHistory(start, end)) {
748
- let operation = entry.operation ?? entry.type;
749
- if (operation === 'put') operation = 'upsert';
750
- const { id, version: timestamp, value } = entry;
805
+ let operation = normalizeHistoryOperation(entry.operation, entry.type);
806
+ const { id, localTime: timestamp, value } = entry;
751
807
  if (enqueued?.timestamp === timestamp) {
752
808
  enqueued.ids.push(id);
753
809
  enqueued.records.push(value);
@@ -771,3 +827,17 @@ async function* groupRecordsInHistory(table, start?, end?, limit?) {
771
827
  }
772
828
  if (enqueued) yield enqueued;
773
829
  }
830
+
831
+ /**
832
+ * Which operation a history entry should report: the originating operation when one was recorded,
833
+ * otherwise the physical write type.
834
+ *
835
+ * A recorded `put` is reported as `put`, so replication catch-up replays it as a replace. Only a
836
+ * LEGACY physical put — one with no originating operation, written before `put` existed — is still
837
+ * normalized to `upsert`, which is what produced a physical put back then. Normalizing both would
838
+ * make catch-up patch the replica and retain attributes the source removed.
839
+ */
840
+ function normalizeHistoryOperation(originatingOperation, physicalType) {
841
+ if (originatingOperation !== undefined) return originatingOperation;
842
+ return physicalType === 'put' ? 'upsert' : physicalType;
843
+ }
@@ -8,11 +8,13 @@ class DeleteAuditLogsBeforeResults {
8
8
  * @param {number} startTimestamp
9
9
  * @param {number} endTimestamp
10
10
  * @param {number} transactionsDeleted
11
+ * @param {number} logFilesDeleted
11
12
  */
12
- constructor(startTimestamp = undefined, endTimestamp = undefined, transactionsDeleted = 0) {
13
+ constructor(startTimestamp = undefined, endTimestamp = undefined, transactionsDeleted = 0, logFilesDeleted = 0) {
13
14
  this.start_timestamp = startTimestamp;
14
15
  this.end_timestamp = endTimestamp;
15
16
  this.transactions_deleted = transactionsDeleted;
17
+ this.log_files_deleted = logFilesDeleted;
16
18
  this.deprecated = 'Please use delete_transaction_logs_before instead';
17
19
  }
18
20
  }
@@ -37,6 +37,37 @@ const DEFAULT_DATA_VERSION_NUM = '2.9.9';
37
37
  // This value should change as supported versions change.
38
38
  const MINIMUM_SUPPORTED_VERSION_NUM = '3.0.0';
39
39
 
40
+ export class VersionStampNotRecordedError extends Error {
41
+ statusCode: number;
42
+ constructor(message: string) {
43
+ super(message);
44
+ this.name = 'VersionStampNotRecordedError';
45
+ this.statusCode = 500;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * `insert.insert` reports a key that already exists as skipped, not as a failure, so a resolved
51
+ * insert is not evidence that the version was recorded. Ids compare as strings: the guard is
52
+ * against a write that did not happen, not against a hash returned in a different type.
53
+ */
54
+ export function assertVersionRecorded(insertResult: any, expectedId: number, newVersionString: string) {
55
+ const insertedHashes = insertResult?.inserted_hashes;
56
+ if (
57
+ Array.isArray(insertedHashes) &&
58
+ insertedHashes.length === 1 &&
59
+ String(insertedHashes[0]) === String(expectedId)
60
+ ) {
61
+ return;
62
+ }
63
+
64
+ throw new VersionStampNotRecordedError(
65
+ `Could not record data version ${newVersionString} in '${hdbTerms.SYSTEM_SCHEMA_NAME}.${hdbTerms.SYSTEM_TABLE_NAMES.INFO_TABLE_NAME}': ` +
66
+ `the insert of ${HDB_INFO_SEARCH_ATTRIBUTE} ${expectedId} reported inserted ${JSON.stringify(insertedHashes ?? null)} ` +
67
+ `and skipped ${JSON.stringify(insertResult?.skipped_hashes ?? null)}`
68
+ );
69
+ }
70
+
40
71
  /**
41
72
  * * Insert a row into hdbInfo with the initial version data at install.
42
73
  *
@@ -91,7 +122,9 @@ export async function insertHdbUpgradeInfo(newVersionString: string) {
91
122
  );
92
123
 
93
124
  await pSetSchemaDataToGlobal();
94
- return insert.insert(insertObject);
125
+ const insertResult = await insert.insert(insertObject);
126
+ assertVersionRecorded(insertResult, newId, newVersionString);
127
+ return insertResult;
95
128
  }
96
129
 
97
130
  /**
@@ -21,6 +21,7 @@ const pGlobalSchema = util.promisify(globalSchema.getTableSchema);
21
21
  const UPDATE_ACTION = 'updated';
22
22
  const INSERT_ACTION = 'inserted';
23
23
  const UPSERT_ACTION = 'upserted';
24
+ const PUT_ACTION = 'put';
24
25
 
25
26
  //IMPORTANT - This validation function is the async version of the code in harperBridge/bridgeUtility/insertUpdateValidate.js
26
27
  // make sure any changes below are also made there. This is to resolve a circular dependency.
@@ -218,6 +219,42 @@ async function upsertData(upsertObject: any) {
218
219
  );
219
220
  }
220
221
 
222
+ /**
223
+ * Create-or-replace the records in the putObject parameter: the stored record becomes exactly the
224
+ * submitted one, so an attribute the request omits is removed. `update`/`upsert` merge instead, and
225
+ * must keep doing so for v4 compatibility — hence a distinct operation rather than a flag on those.
226
+ * Equivalent to REST `PUT /Table/id`.
227
+ * @param putObject - Represents the data that will be written
228
+ */
229
+ async function putData(putObject: any) {
230
+ if (putObject.operation !== 'put') {
231
+ throw handleHDBError(new Error(), 'invalid operation, must be put', HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR);
232
+ }
233
+
234
+ let validator = insertValidator(putObject);
235
+ if (validator) {
236
+ throw handleHDBError(new Error(), validator.message, HTTP_STATUS_CODES.BAD_REQUEST);
237
+ }
238
+
239
+ hdbUtils.transformReq(putObject);
240
+
241
+ let invalidSchemaTableMsg = hdbUtils.checkSchemaTableExist(putObject.schema, putObject.table);
242
+ if (invalidSchemaTableMsg) {
243
+ throw handleHDBError(new Error(), invalidSchemaTableMsg, HTTP_STATUS_CODES.BAD_REQUEST);
244
+ }
245
+
246
+ let bridgePutResult = await harperBridge.putRecords(putObject);
247
+
248
+ return returnObject(
249
+ PUT_ACTION,
250
+ bridgePutResult.written_hashes,
251
+ putObject,
252
+ [],
253
+ bridgePutResult.new_attributes,
254
+ bridgePutResult.txn_time
255
+ );
256
+ }
257
+
221
258
  /**
222
259
  * Constructs return object for insert, update, and upsert.
223
260
  * @param action
@@ -254,6 +291,12 @@ function returnObject(
254
291
  return return_object;
255
292
  }
256
293
 
294
+ if (action === PUT_ACTION) {
295
+ // `put` never skips: every submitted record is written, created or replaced.
296
+ return_object.put_hashes = written_hashes;
297
+ return return_object;
298
+ }
299
+
257
300
  return_object.update_hashes = written_hashes;
258
301
  return_object.skipped_hashes = skipped;
259
302
  return return_object;
@@ -263,4 +306,4 @@ export function flush(object: any) {
263
306
  hdbUtils.transformReq(object);
264
307
  return harperBridge.flush(object.schema, object.table);
265
308
  }
266
- export { insertData as insert, updateData as update, upsertData as upsert };
309
+ export { insertData as insert, updateData as update, upsertData as upsert, putData as put };