@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/bin/cliOperations.ts +12 -0
  2. package/bin/restart.ts +66 -6
  3. package/components/Application.ts +1134 -112
  4. package/components/OptionsWatcher.ts +368 -102
  5. package/components/Scope.ts +20 -1
  6. package/components/componentLoader.ts +43 -4
  7. package/components/deploymentOperations.ts +4 -1
  8. package/components/deploymentRecorder.ts +9 -2
  9. package/components/operations.js +284 -52
  10. package/components/operationsValidation.js +49 -2
  11. package/components/packageComponent.ts +25 -1
  12. package/components/requestRestart.ts +11 -0
  13. package/config/RootConfigWatcher.ts +191 -37
  14. package/config/configReadRetry.ts +62 -0
  15. package/config/configUtils.ts +78 -26
  16. package/config/parseConfigFile.ts +34 -0
  17. package/config/readConfigFileSync.ts +44 -0
  18. package/config/watcherArming.ts +59 -0
  19. package/config-root.schema.json +4 -0
  20. package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
  21. package/dist/bin/cliOperations.js +13 -0
  22. package/dist/bin/cliOperations.js.map +1 -1
  23. package/dist/bin/restart.js +42 -6
  24. package/dist/bin/restart.js.map +1 -1
  25. package/dist/components/Application.d.ts +104 -9
  26. package/dist/components/Application.js +954 -102
  27. package/dist/components/Application.js.map +1 -1
  28. package/dist/components/OptionsWatcher.d.ts +4 -1
  29. package/dist/components/OptionsWatcher.js +378 -104
  30. package/dist/components/OptionsWatcher.js.map +1 -1
  31. package/dist/components/Scope.js +15 -1
  32. package/dist/components/Scope.js.map +1 -1
  33. package/dist/components/componentLoader.js +35 -3
  34. package/dist/components/componentLoader.js.map +1 -1
  35. package/dist/components/deploymentOperations.js +4 -1
  36. package/dist/components/deploymentOperations.js.map +1 -1
  37. package/dist/components/deploymentRecorder.d.ts +4 -2
  38. package/dist/components/deploymentRecorder.js +1 -0
  39. package/dist/components/deploymentRecorder.js.map +1 -1
  40. package/dist/components/operations.d.ts +28 -0
  41. package/dist/components/operations.js +263 -54
  42. package/dist/components/operations.js.map +1 -1
  43. package/dist/components/operationsValidation.js +48 -2
  44. package/dist/components/operationsValidation.js.map +1 -1
  45. package/dist/components/packageComponent.js +24 -0
  46. package/dist/components/packageComponent.js.map +1 -1
  47. package/dist/components/requestRestart.d.ts +1 -0
  48. package/dist/components/requestRestart.js +7 -0
  49. package/dist/components/requestRestart.js.map +1 -1
  50. package/dist/config/RootConfigWatcher.d.ts +2 -0
  51. package/dist/config/RootConfigWatcher.js +189 -35
  52. package/dist/config/RootConfigWatcher.js.map +1 -1
  53. package/dist/config/configReadRetry.d.ts +8 -0
  54. package/dist/config/configReadRetry.js +62 -0
  55. package/dist/config/configReadRetry.js.map +1 -0
  56. package/dist/config/configUtils.d.ts +10 -9
  57. package/dist/config/configUtils.js +63 -27
  58. package/dist/config/configUtils.js.map +1 -1
  59. package/dist/config/parseConfigFile.d.ts +4 -0
  60. package/dist/config/parseConfigFile.js +35 -0
  61. package/dist/config/parseConfigFile.js.map +1 -0
  62. package/dist/config/readConfigFileSync.d.ts +1 -0
  63. package/dist/config/readConfigFileSync.js +47 -0
  64. package/dist/config/readConfigFileSync.js.map +1 -0
  65. package/dist/config/watcherArming.d.ts +15 -0
  66. package/dist/config/watcherArming.js +59 -0
  67. package/dist/config/watcherArming.js.map +1 -0
  68. package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
  69. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.js +4 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/json/systemSchema.json +3 -0
  74. package/dist/resources/DatabaseTransaction.d.ts +25 -0
  75. package/dist/resources/DatabaseTransaction.js +224 -18
  76. package/dist/resources/DatabaseTransaction.js.map +1 -1
  77. package/dist/resources/LMDBTransaction.d.ts +2 -1
  78. package/dist/resources/LMDBTransaction.js +22 -3
  79. package/dist/resources/LMDBTransaction.js.map +1 -1
  80. package/dist/resources/PrimaryRocksDatabase.js +22 -6
  81. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  82. package/dist/resources/RecordEncoder.d.ts +1 -1
  83. package/dist/resources/RecordEncoder.js +21 -5
  84. package/dist/resources/RecordEncoder.js.map +1 -1
  85. package/dist/resources/Resource.js +97 -13
  86. package/dist/resources/Resource.js.map +1 -1
  87. package/dist/resources/ResourceInterface.d.ts +8 -0
  88. package/dist/resources/RocksIndexStore.js +2 -1
  89. package/dist/resources/RocksIndexStore.js.map +1 -1
  90. package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
  91. package/dist/resources/RocksTransactionLogStore.js +104 -33
  92. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  93. package/dist/resources/Table.d.ts +58 -7
  94. package/dist/resources/Table.js +1095 -346
  95. package/dist/resources/Table.js.map +1 -1
  96. package/dist/resources/analytics/write.js +10 -3
  97. package/dist/resources/analytics/write.js.map +1 -1
  98. package/dist/resources/auditStore.d.ts +170 -0
  99. package/dist/resources/auditStore.js +457 -11
  100. package/dist/resources/auditStore.js.map +1 -1
  101. package/dist/resources/dataLoader.js +3 -4
  102. package/dist/resources/dataLoader.js.map +1 -1
  103. package/dist/resources/databases.d.ts +16 -13
  104. package/dist/resources/databases.js +624 -176
  105. package/dist/resources/databases.js.map +1 -1
  106. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  107. package/dist/resources/derivedIndexRegistry.js +68 -0
  108. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  109. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  110. package/dist/resources/derivedIndexRuntime.js +2027 -0
  111. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  112. package/dist/resources/graphql.js +3 -2
  113. package/dist/resources/graphql.js.map +1 -1
  114. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
  115. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +848 -39
  116. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  117. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  118. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  119. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  120. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  121. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  122. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  123. package/dist/resources/nodeIdMapping.d.ts +5 -0
  124. package/dist/resources/nodeIdMapping.js +49 -0
  125. package/dist/resources/nodeIdMapping.js.map +1 -1
  126. package/dist/resources/recordLock.d.ts +47 -4
  127. package/dist/resources/recordLock.js +138 -7
  128. package/dist/resources/recordLock.js.map +1 -1
  129. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  130. package/dist/resources/recordLockCoordinator.js +2565 -0
  131. package/dist/resources/recordLockCoordinator.js.map +1 -0
  132. package/dist/resources/replayLogs.js +5 -0
  133. package/dist/resources/replayLogs.js.map +1 -1
  134. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  135. package/dist/resources/replicatedApplyFailure.js +63 -0
  136. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  137. package/dist/resources/scheduler/scheduler.js +3 -3
  138. package/dist/resources/scheduler/scheduler.js.map +1 -1
  139. package/dist/resources/search.d.ts +10 -4
  140. package/dist/resources/search.js +160 -40
  141. package/dist/resources/search.js.map +1 -1
  142. package/dist/resources/tracked.d.ts +5 -1
  143. package/dist/resources/tracked.js +74 -23
  144. package/dist/resources/tracked.js.map +1 -1
  145. package/dist/security/jsLoader.js +6 -4
  146. package/dist/security/jsLoader.js.map +1 -1
  147. package/dist/server/REST.js +33 -2
  148. package/dist/server/REST.js.map +1 -1
  149. package/dist/server/http.d.ts +5 -1
  150. package/dist/server/http.js +34 -2
  151. package/dist/server/http.js.map +1 -1
  152. package/dist/server/serverHelpers/Headers.d.ts +2 -0
  153. package/dist/server/serverHelpers/Headers.js +6 -0
  154. package/dist/server/serverHelpers/Headers.js.map +1 -1
  155. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  156. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  157. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  158. package/dist/server/serverHelpers/Request.d.ts +5 -10
  159. package/dist/server/serverHelpers/Request.js +38 -136
  160. package/dist/server/serverHelpers/Request.js.map +1 -1
  161. package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
  162. package/dist/server/serverHelpers/contentTypes.js +189 -15
  163. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  164. package/dist/server/serverHelpers/serverUtilities.js +96 -17
  165. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  166. package/dist/server/storageReclamation.js +1 -1
  167. package/dist/server/storageReclamation.js.map +1 -1
  168. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  169. package/dist/server/threads/isolatedApplications.js +171 -0
  170. package/dist/server/threads/isolatedApplications.js.map +1 -0
  171. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  172. package/dist/server/threads/logRotationTransport.js +33 -0
  173. package/dist/server/threads/logRotationTransport.js.map +1 -0
  174. package/dist/server/threads/manageThreads.d.ts +64 -6
  175. package/dist/server/threads/manageThreads.js +261 -12
  176. package/dist/server/threads/manageThreads.js.map +1 -1
  177. package/dist/server/threads/socketRouter.d.ts +1 -0
  178. package/dist/server/threads/socketRouter.js +196 -13
  179. package/dist/server/threads/socketRouter.js.map +1 -1
  180. package/dist/server/threads/threadServer.js +30 -7
  181. package/dist/server/threads/threadServer.js.map +1 -1
  182. package/dist/utility/errors/hdbError.d.ts +24 -0
  183. package/dist/utility/errors/hdbError.js +58 -1
  184. package/dist/utility/errors/hdbError.js.map +1 -1
  185. package/dist/utility/hdbTerms.d.ts +2 -0
  186. package/dist/utility/hdbTerms.js +2 -0
  187. package/dist/utility/hdbTerms.js.map +1 -1
  188. package/dist/utility/logging/harper_logger.js +217 -38
  189. package/dist/utility/logging/harper_logger.js.map +1 -1
  190. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  191. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  192. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  193. package/dist/utility/logging/logRotation.d.ts +46 -0
  194. package/dist/utility/logging/logRotation.js +365 -0
  195. package/dist/utility/logging/logRotation.js.map +1 -0
  196. package/dist/utility/logging/logRotator.d.ts +1 -1
  197. package/dist/utility/logging/logRotator.js +172 -92
  198. package/dist/utility/logging/logRotator.js.map +1 -1
  199. package/dist/utility/npmUtilities.js +6 -4
  200. package/dist/utility/npmUtilities.js.map +1 -1
  201. package/dist/utility/watcherFallback.d.ts +0 -45
  202. package/dist/utility/watcherFallback.js +1 -125
  203. package/dist/utility/watcherFallback.js.map +1 -1
  204. package/dist/validation/configValidator.js +6 -3
  205. package/dist/validation/configValidator.js.map +1 -1
  206. package/index.ts +6 -0
  207. package/json/systemSchema.json +3 -0
  208. package/npm-shrinkwrap.json +131 -41
  209. package/package.json +10 -3
  210. package/resources/DESIGN.md +124 -19
  211. package/resources/DatabaseTransaction.ts +230 -17
  212. package/resources/LMDBTransaction.ts +21 -3
  213. package/resources/PrimaryRocksDatabase.ts +20 -7
  214. package/resources/RecordEncoder.ts +25 -5
  215. package/resources/Resource.ts +97 -13
  216. package/resources/ResourceInterface.ts +8 -0
  217. package/resources/RocksIndexStore.ts +2 -1
  218. package/resources/RocksTransactionLogStore.ts +111 -31
  219. package/resources/Table.ts +1224 -393
  220. package/resources/analytics/write.ts +10 -3
  221. package/resources/auditStore.ts +460 -11
  222. package/resources/dataLoader.ts +3 -4
  223. package/resources/databases.ts +610 -146
  224. package/resources/derivedIndexRegistry.ts +56 -0
  225. package/resources/derivedIndexRuntime.ts +2292 -0
  226. package/resources/graphql.ts +3 -2
  227. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +905 -46
  228. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  229. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  230. package/resources/nodeIdMapping.ts +50 -0
  231. package/resources/recordLock.ts +173 -7
  232. package/resources/recordLockCoordinator.ts +3043 -0
  233. package/resources/replayLogs.ts +5 -0
  234. package/resources/replicatedApplyFailure.ts +77 -0
  235. package/resources/scheduler/scheduler.ts +4 -4
  236. package/resources/search.ts +169 -49
  237. package/resources/tracked.ts +73 -22
  238. package/security/jsLoader.ts +6 -4
  239. package/server/DESIGN.md +11 -0
  240. package/server/REST.ts +36 -3
  241. package/server/http.ts +34 -2
  242. package/server/serverHelpers/Headers.ts +5 -1
  243. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  244. package/server/serverHelpers/Request.ts +33 -131
  245. package/server/serverHelpers/contentTypes.ts +188 -15
  246. package/server/serverHelpers/serverUtilities.ts +143 -24
  247. package/server/storageReclamation.ts +2 -2
  248. package/server/threads/isolatedApplications.ts +157 -0
  249. package/server/threads/logRotationTransport.ts +40 -0
  250. package/server/threads/manageThreads.js +254 -12
  251. package/server/threads/socketRouter.ts +217 -11
  252. package/server/threads/threadServer.js +30 -7
  253. package/studio/web/assets/{Chat-BnCBegQz.js → Chat-D3j-1yY1.js} +1 -1
  254. package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-BxJGYcfB.js} +3 -3
  255. package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-CT55oWOe.js} +1 -1
  256. package/studio/web/assets/{applications-DHxGi7JH.js → applications-D9Ct9_vm.js} +1 -1
  257. package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-DV8H7VwA.js} +1 -1
  258. package/studio/web/assets/{editor-DNcRHK54.js → editor-uatc0unt.js} +1 -1
  259. package/studio/web/assets/{html-Bdssedlg.js → html-Bm6D6paN.js} +1 -1
  260. package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CEn7tpLG.js} +1 -1
  261. package/studio/web/assets/{index-D6sxmFLR.js → index-BIXW6Pu4.js} +5 -5
  262. package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-UI7L-Vrk.js} +1 -1
  263. package/studio/web/assets/{javascript-B8meVSTH.js → javascript-CJ0G3AFZ.js} +1 -1
  264. package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-DQADAYEa.js} +1 -1
  265. package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-CAQJXWcI.js} +1 -1
  266. package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  267. package/studio/web/assets/{notifications-CQf18QKb.js → notifications-BbxTU6Aw.js} +1 -1
  268. package/studio/web/assets/{notifications-CvZivSbh.js → notifications-Cvb3P1lB.js} +1 -1
  269. package/studio/web/assets/{profile-DdOwtntb.js → profile-Yyb7gsvL.js} +1 -1
  270. package/studio/web/assets/{regions-n69fwagr.js → regions-OgjGHlU5.js} +1 -1
  271. package/studio/web/assets/{register-PfWTCXWB.js → register-6qwNEOY3.js} +2 -2
  272. package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BilDMtgB.js} +1 -1
  273. package/studio/web/assets/{setup-CUx_aUDl.js → setup-J6qJ7OIU.js} +2 -2
  274. package/studio/web/assets/{status-D7BVKqX9.js → status-0RWGcfyD.js} +1 -1
  275. package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-BIn-vErT.js} +1 -1
  276. package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-DgUXku4d.js} +1 -1
  277. package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-C9orXcsM.js} +1 -1
  278. package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  279. package/studio/web/assets/{workers-pR3jRY9D.js → workers-JVzSDmgx.js} +1 -1
  280. package/studio/web/assets/{xml-2iRnMhQO.js → xml-Cq-S8S4X.js} +1 -1
  281. package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-sfoRdh1M.js} +1 -1
  282. package/studio/web/index.html +1 -1
  283. package/utility/errors/hdbError.ts +54 -0
  284. package/utility/hdbTerms.ts +2 -0
  285. package/utility/logging/harper_logger.ts +209 -30
  286. package/utility/logging/logGenerationCoordinator.ts +196 -0
  287. package/utility/logging/logRotation.ts +367 -0
  288. package/utility/logging/logRotator.ts +196 -91
  289. package/utility/npmUtilities.ts +6 -4
  290. package/utility/watcherFallback.ts +0 -122
  291. package/validation/configValidator.ts +6 -3
@@ -48,6 +48,8 @@ declare class StartedTransaction extends Error {
48
48
  * transactions, while core keeps the consistency guarantee.
49
49
  */
50
50
  export declare function transactionOpenTooLongError(): ServerError;
51
+ export declare function getReadTransactionGuard(transaction: ReadTransaction): (() => void) | undefined;
52
+ export declare function trackReadRange(transaction: ReadTransaction, createRange: () => any): any;
51
53
  type MaybePromise<T> = T | Promise<T>;
52
54
  export type CommitOptions = {
53
55
  doneWriting?: boolean;
@@ -62,6 +64,10 @@ type ReadTransaction = (LMDBTransaction | RocksTransaction) & {
62
64
  isDone?: boolean;
63
65
  isCommitted?: boolean;
64
66
  };
67
+ export type WriteGeneration = {
68
+ closed: boolean;
69
+ internalWrites: number;
70
+ };
65
71
  export type TransactionWrite = {
66
72
  key: Id;
67
73
  store: any;
@@ -96,7 +102,12 @@ export type TransactionWrite = {
96
102
  trackRecordVersion?: boolean;
97
103
  recordVersionApplied?: boolean;
98
104
  innerCommit?: MaybePromise<CommitResolution>;
105
+ reloadCommitBase?: boolean;
106
+ writeGeneration?: WriteGeneration;
107
+ instanceClosed?: boolean;
99
108
  };
109
+ export declare function closeWriteInstance(operation: TransactionWrite | null | undefined): void;
110
+ export declare function validateWrite(operation: TransactionWrite, txnTime: number, transaction: DatabaseTransaction): any;
100
111
  export declare function getAppliedWriteVersion(recordVersion: number | undefined, txnLogKey: number): number;
101
112
  /**
102
113
  * The state a preceding write in this transaction left for `operation`'s key, or undefined if this
@@ -128,6 +139,7 @@ export declare class DatabaseTransaction implements Transaction {
128
139
  scopeOwned?: boolean;
129
140
  });
130
141
  writes: TransactionWrite[];
142
+ ownedWrites?: WeakSet<TransactionWrite>;
131
143
  writesByKey?: Map<any, Map<unknown, TransactionWrite>>;
132
144
  completions: Promise<void>[];
133
145
  db: RootDatabaseKind;
@@ -169,8 +181,21 @@ export declare class DatabaseTransaction implements Transaction {
169
181
  commitPhaseTicks: number;
170
182
  commitChainHead?: DatabaseTransaction;
171
183
  recordLocks?: Map<any, Map<unknown, RecordLockHandle>>;
184
+ /**
185
+ * Whether any staged write was made through a record lock handle. The commit-time lease fence
186
+ * below is skipped entirely when this is false, which is every transaction in a core-only
187
+ * deployment: without it a bulk transaction of 100,000 plain writes pays 100,000 property checks
188
+ * before submission, on a path that is supposed to be untouched when no lock is involved. It
189
+ * latches rather than tracking a count, because a write whose handle was released or expired after
190
+ * staging is exactly what the fence exists to catch.
191
+ */
192
+ hasLeaseProtectedWrite?: boolean;
172
193
  pendingLocks?: Map<any, Map<unknown, Promise<RecordLockHandle>>>;
173
194
  setCommitPhase(committing: boolean): void;
195
+ rangeReadActive: boolean;
196
+ renewReadTimeout(): void;
197
+ renewIdleTimeout(): void;
198
+ renewChainForNativeCommit(): void;
174
199
  getReadTxn(disableSnapshot?: boolean): ReadTransaction;
175
200
  private attachOwnedTransaction;
176
201
  /**
@@ -40,6 +40,10 @@ exports.setCommitLatencyRecorder = setCommitLatencyRecorder;
40
40
  exports.getTransactionQueueDepths = getTransactionQueueDepths;
41
41
  exports.replicationConfirmation = replicationConfirmation;
42
42
  exports.transactionOpenTooLongError = transactionOpenTooLongError;
43
+ exports.getReadTransactionGuard = getReadTransactionGuard;
44
+ exports.trackReadRange = trackReadRange;
45
+ exports.closeWriteInstance = closeWriteInstance;
46
+ exports.validateWrite = validateWrite;
43
47
  exports.getAppliedWriteVersion = getAppliedWriteVersion;
44
48
  exports.priorStagedWrite = priorStagedWrite;
45
49
  exports.writeKeyId = writeKeyId;
@@ -65,6 +69,7 @@ const RETRY_NOW_VALUE = rocksdb_js_1.constants.RETRY_NOW_VALUE;
65
69
  const ordered_binary_1 = require("ordered-binary");
66
70
  const longLivedTransactions_ts_1 = require("./longLivedTransactions.js");
67
71
  const trackedTxns = new Set();
72
+ const readTransactionOwners = new WeakMap();
68
73
  // Read options for a rotated generation's native transactions; shared because they never vary.
69
74
  const SNAPSHOT_FREE = Object.freeze({ disableSnapshot: true });
70
75
  // Logical transactions the monitor supervises for their WRITES, kept apart from trackedTxns because the
@@ -297,6 +302,104 @@ function transactionOpenTooLongError() {
297
302
  // 422 signals the request itself must change (split the work), which is the actionable response.
298
303
  return new hdbError_ts_1.ServerError('Transaction was aborted after exceeding the maximum open-transaction time; split long-running work into smaller transactions', 422);
299
304
  }
305
+ class ReadSnapshotExpiredError extends hdbError_ts_1.ServerError {
306
+ constructor() {
307
+ super('Read scan snapshot expired; retry the read without replaying previously committed writes', 503);
308
+ this.name = 'ReadSnapshotExpiredError';
309
+ }
310
+ }
311
+ function getReadTransactionGuard(transaction) {
312
+ const owner = readTransactionOwners.get(transaction);
313
+ if (!owner)
314
+ return;
315
+ return function checkActive() {
316
+ if (owner.timedOut)
317
+ throw transactionOpenTooLongError();
318
+ if (owner.transaction !== transaction) {
319
+ throw new ReadSnapshotExpiredError();
320
+ }
321
+ };
322
+ }
323
+ function trackReadRange(transaction, createRange) {
324
+ const owner = readTransactionOwners.get(transaction);
325
+ if (!owner)
326
+ return createRange();
327
+ const checkActive = getReadTransactionGuard(transaction);
328
+ checkActive();
329
+ const range = createRange();
330
+ const iterate = range.iterate;
331
+ range.iterate = function (options) {
332
+ const iterator = iterate.call(this, options);
333
+ let done = false;
334
+ // Closing the underlying iterator is the one step here that can throw for a reason the caller
335
+ // must not see: `next()` only reaches it once the snapshot is already gone, which is the
336
+ // likeliest moment for the native layer to object, and an error from cleanup would replace the
337
+ // named 503 with exactly the raw iterator error this wrapper exists to stop surfacing. The
338
+ // closure reference rather than `this` so a destructured `next` still cleans up.
339
+ const wrapper = {
340
+ [Symbol.iterator]() {
341
+ return this;
342
+ },
343
+ next() {
344
+ if (done)
345
+ return { done: true, value: undefined };
346
+ try {
347
+ checkActive();
348
+ owner.rangeReadActive = true;
349
+ const result = iterator.next();
350
+ done = result.done === true;
351
+ return result;
352
+ }
353
+ catch (error) {
354
+ closeQuietly();
355
+ throw error;
356
+ }
357
+ },
358
+ return(value) {
359
+ if (!done) {
360
+ done = true;
361
+ iterator.return?.(value);
362
+ }
363
+ return { done: true, value };
364
+ },
365
+ throw(error) {
366
+ // Not delegated to `iterator.throw`: it closes and rethrows the same error anyway, and
367
+ // delegating after the close below would run it against an iterator already closed.
368
+ closeQuietly();
369
+ throw error;
370
+ },
371
+ };
372
+ function closeQuietly() {
373
+ try {
374
+ wrapper.return();
375
+ }
376
+ catch {
377
+ // the error being propagated is the actionable one
378
+ }
379
+ }
380
+ return wrapper;
381
+ };
382
+ return range;
383
+ }
384
+ function closeWriteInstance(operation) {
385
+ if (operation && !operation.instanceClosed) {
386
+ operation.instanceClosed = true;
387
+ if (operation.writeGeneration)
388
+ operation.writeGeneration.closed = true;
389
+ }
390
+ }
391
+ function validateWrite(operation, txnTime, transaction) {
392
+ const generation = operation.writeGeneration;
393
+ if (generation)
394
+ generation.internalWrites++;
395
+ try {
396
+ return operation.validate?.(txnTime, transaction);
397
+ }
398
+ finally {
399
+ if (generation)
400
+ generation.internalWrites--;
401
+ }
402
+ }
300
403
  function getAppliedWriteVersion(recordVersion, txnLogKey) {
301
404
  return recordVersion == null ? txnLogKey : Math.min(recordVersion, txnLogKey);
302
405
  }
@@ -342,6 +445,7 @@ class DatabaseTransaction {
342
445
  this.#scopeOwned = options?.scopeOwned === true;
343
446
  }
344
447
  writes = []; // the set of writes to commit if the conditions are met
448
+ ownedWrites;
345
449
  completions = []; // the set of outstanding async operations to complete
346
450
  db;
347
451
  transaction;
@@ -388,8 +492,8 @@ class DatabaseTransaction {
388
492
  txn.commitPhaseTicks = 0;
389
493
  }
390
494
  }
391
- getReadTxn(disableSnapshot) {
392
- this.readTxnRefCount = (this.readTxnRefCount || 0) + 1;
495
+ rangeReadActive = false;
496
+ renewReadTimeout() {
393
497
  // The limit is an IDLE limit. Writes always re-arm it (see addWrite), but reads only do so
394
498
  // while no uncommitted writes are held: staged writes hold write intents that other writers'
395
499
  // coordinated-retry commits park on, so a handler that wrote once and then only reads — an
@@ -402,6 +506,23 @@ class DatabaseTransaction {
402
506
  if ((this.writes.length === 0 && !this.next) || this.open !== exports.TRANSACTION_STATE.OPEN || !this.hasPendingWrites()) {
403
507
  this.timeout = Math.max(txnExpiration, this.timeoutBudget);
404
508
  }
509
+ }
510
+ // Each engine keeps its own expiration; LMDBTransaction overrides this with its own.
511
+ renewIdleTimeout() {
512
+ this.timeout = Math.max(txnExpiration, this.timeoutBudget ?? 0);
513
+ }
514
+ // The links after this one wait for its native commit before their own commit() is entered; each
515
+ // hop starts with a full idle window and a stalled native commit is still bounded by one.
516
+ renewChainForNativeCommit() {
517
+ for (let txn = this; txn; txn = txn.next) {
518
+ if (txn.timedOut || txn.open === exports.TRANSACTION_STATE.CLOSED)
519
+ continue;
520
+ txn.renewIdleTimeout();
521
+ }
522
+ }
523
+ getReadTxn(disableSnapshot) {
524
+ this.readTxnRefCount = (this.readTxnRefCount || 0) + 1;
525
+ this.renewReadTimeout();
405
526
  if (this.transaction) {
406
527
  if (this.transaction.openTimer)
407
528
  this.transaction.openTimer = 0;
@@ -434,6 +555,8 @@ class DatabaseTransaction {
434
555
  // Monitor state is not ownership state: it stays with `trackedTxns.add` in getReadTxn().
435
556
  attachOwnedTransaction(transaction) {
436
557
  this.transaction = transaction;
558
+ readTransactionOwners.set(transaction, this);
559
+ this.rangeReadActive = false;
437
560
  this.readTxnsUsed = 1;
438
561
  this.baseReadRefConsumed = false;
439
562
  this.handleOpenedAt = performance.now();
@@ -475,6 +598,7 @@ class DatabaseTransaction {
475
598
  trackedTxns.delete(this);
476
599
  this.endWriteSupervision();
477
600
  this.transaction = null;
601
+ this.rangeReadActive = false;
478
602
  this.readTxnsUsed = 0;
479
603
  this.readTxnRefCount = 0;
480
604
  this.handleOpenedAt = 0;
@@ -576,6 +700,7 @@ class DatabaseTransaction {
576
700
  const index = this.writes.indexOf(operation);
577
701
  if (index > -1)
578
702
  this.writes[index] = null;
703
+ this.ownedWrites?.delete(operation);
579
704
  if (operation.key === undefined)
580
705
  return;
581
706
  const writesForStore = this.writesByKey?.get(operation.store);
@@ -585,7 +710,7 @@ class DatabaseTransaction {
585
710
  // Membership, not `stagedIn`, which every commit handler clears and so cannot tell a takeover from a
586
711
  // write done in place; a prior already taken over must not become this transaction's basis again.
587
712
  let prior = operation.priorWrite;
588
- while (prior && !this.writes.includes(prior))
713
+ while (prior && !this.ownedWrites?.has(prior))
589
714
  prior = prior.priorWrite;
590
715
  const tail = writesForStore.get(keyId);
591
716
  if (tail === operation) {
@@ -619,10 +744,10 @@ class DatabaseTransaction {
619
744
  const h = storeMap.get(keyId);
620
745
  if (!h)
621
746
  return undefined;
622
- if (!h.released)
747
+ if (!h.isExpired())
623
748
  return h;
624
- // Prune released handles so they don't accumulate; an expired handle checking re-entrancy
625
- // would otherwise be seen as the holder and incorrectly granted access.
749
+ // Prune released/expired handles so they don't accumulate; a handle past its deadline checking
750
+ // re-entrancy would otherwise be seen as the holder and incorrectly granted access.
626
751
  storeMap.delete(keyId);
627
752
  return undefined;
628
753
  }
@@ -684,11 +809,19 @@ class DatabaseTransaction {
684
809
  // fire after a commit or abort, and routing it back here would revive a write this transaction
685
810
  // already rolled back — whose blobs abort() has reclaimed. Cleared here, save() resolves the
686
811
  // context's current transaction as it did before `stagedIn` existed.
687
- for (const write of this.writes)
812
+ for (const write of this.writes) {
688
813
  if (write?.stagedIn === this)
689
814
  write.stagedIn = undefined;
815
+ if (write)
816
+ this.ownedWrites?.delete(write);
817
+ }
690
818
  this.writes = [];
691
819
  this.writesByKey = undefined;
820
+ // The commit-fence latch belongs to the discarded batch. A reused transaction that once staged
821
+ // a locked write would otherwise re-scan every write of every later unlocked batch. Assigned
822
+ // only when it was set, so an ordinary transaction never gains the property at all.
823
+ if (this.hasLeaseProtectedWrite)
824
+ this.hasLeaseProtectedWrite = false;
692
825
  }
693
826
  /**
694
827
  * Drop this transaction's back-reference from its context once completed (commit or abort),
@@ -811,6 +944,7 @@ class DatabaseTransaction {
811
944
  this.writeTick = monitorTick;
812
945
  this.linkWrite(operation);
813
946
  this.writes.push(operation);
947
+ (this.ownedWrites ??= new WeakSet()).add(operation);
814
948
  operation.stagedIn = this;
815
949
  // Hold this write back while any earlier same-key write has not run — out of staging order both
816
950
  // diff against the pre-transaction record (harper#2211, DESIGN.md). The whole chain, not just the
@@ -841,16 +975,26 @@ class DatabaseTransaction {
841
975
  const lockHandle = operation.lockHandle;
842
976
  // Guard: a write staged through an expired or released lock handle must not land.
843
977
  // The handle's lease timer already unlocked the native key; another holder may have taken it.
844
- if (lockHandle && (lockHandle.expired || lockHandle.released)) {
845
- // Remove the operation from the staged set so subsequent writes on this context do not
846
- // re-throw 409 due to a stale null-saved entry sitting in this.writes.
847
- const failedIdx = this.writes.indexOf(operation);
848
- if (failedIdx > -1)
849
- this.writes[failedIdx] = null;
978
+ // isExpired() re-evaluates the deadline here rather than trusting the timer to have run: a
979
+ // holder whose event loop stalled past its lease would otherwise commit in the window between
980
+ // the deadline and its own timer callback, after peers had already granted the key onward.
981
+ //
982
+ // A RE-save is judged by the lease alone. `released` belongs to the first stage — a caller that
983
+ // unlocked and then saved is writing through a handle it gave back — but an operation already
984
+ // staged was staged while the lock was held, and an `unlock()` inside the lease leaves it valid
985
+ // (the same rule the pre-submit fence applies below). Without the distinction,
986
+ // `lock(); save(); unlock(); commit()` succeeded normally and threw 409 only when a conflict
987
+ // retry or an open read iterator forced the replay to re-save it.
988
+ if (operation.saved ? lockHandle?.isLeaseExpired() : lockHandle?.isExpired()) {
989
+ // Through `detachWrite`, which also repairs the per-key chain: dropping it from `writes` alone
990
+ // leaves `writesByKey` pointing at a write that was refused, so a caller that catches this 409
991
+ // and stages the same key again would take the rejected operation as its merge basis.
992
+ this.detachWrite(operation);
850
993
  throw (0, recordLock_ts_1.lockNotHeldError)(lockHandle);
851
994
  }
852
995
  // Lock-write timestamp rules.
853
996
  if (lockHandle) {
997
+ this.hasLeaseProtectedWrite = true;
854
998
  if (this.open === exports.TRANSACTION_STATE.CLOSED || this.saveCommits) {
855
999
  // CLOSED path (second+ write per ImmediateTransaction cycle) OR the first write in
856
1000
  // an ImmediateTransaction (open=OPEN until commit sets it CLOSED, but saveCommits
@@ -910,21 +1054,44 @@ class DatabaseTransaction {
910
1054
  }
911
1055
  if (!txnTime)
912
1056
  txnTime = this.timestamp = transaction.getTimestamp();
1057
+ if (!operation.saved && operation.pendingPriorWrite) {
1058
+ const pendingWrites = [];
1059
+ for (let pending = operation.pendingPriorWrite; pending;) {
1060
+ if (!pending.saved && this.ownedWrites?.has(pending))
1061
+ pendingWrites.push(pending);
1062
+ pending = pending.pendingPriorWrite !== undefined ? pending.pendingPriorWrite : pending.priorWrite;
1063
+ }
1064
+ for (let index = pendingWrites.length - 1; index >= 0; index--)
1065
+ this.save(pendingWrites[index], transaction, false, options);
1066
+ operation.pendingPriorWrite = null;
1067
+ }
913
1068
  // `txnTime` is this transaction's timestamp — the key its entries take in the per-origin log.
914
1069
  // A write applied from elsewhere carries the origin's record version too, and that is what the
915
1070
  // record is stored at; the two coincide for every locally-originated write. Gated on the apply
916
1071
  // flags so an ordinary write never reads the property (harper#2412).
917
1072
  const writeVersion = this.sourceApply || this.isReplay ? getAppliedWriteVersion(operation.recordVersion, txnTime) : txnTime;
918
- if (reloadEntry || operation.entry === undefined) {
919
- operation.entry = operation.store.getEntry(operation.key, { transaction });
1073
+ // A base that feeds stored state must come from this transaction's snapshot, never the
1074
+ // cross-worker cache vouch (stale when a resequenced write reused a version). That closes the
1075
+ // lost-update window only when this transaction holds a snapshot to validate the later Put
1076
+ // against; a snapshot-free transaction (this.snapshotFree, after a mid-scope-commit rotation)
1077
+ // has no snapshot for rocksdb-js to validate the Put against, so it only narrows the window to
1078
+ // the read-to-put span rather than closing it (open follow-up, tracked in the PR description).
1079
+ // Replays keep their pre-read base — their convergence contract is the replay pass itself.
1080
+ const reloadsCommitBase = operation.reloadCommitBase && !operation.saved && !this.isReplay;
1081
+ if (reloadEntry || operation.entry === undefined || reloadsCommitBase) {
1082
+ const uncachedRead = (!!operation.reloadCommitBase && !this.isReplay) || reloadEntry;
1083
+ operation.entry = operation.store.getEntry(operation.key, { transaction, uncachedRead });
920
1084
  }
921
1085
  if (!operation.saved) {
922
- operation.saved = true;
923
1086
  // immediately execute in this transaction
924
- if (operation.validate?.(writeVersion, this) === false) {
1087
+ const validated = validateWrite(operation, writeVersion, this);
1088
+ if (validated === false) {
1089
+ operation.saved = true;
925
1090
  operation.commit = () => { }; // noop if we try again
1091
+ closeWriteInstance(operation);
926
1092
  return;
927
1093
  }
1094
+ operation.saved = true;
928
1095
  let result = operation.before?.();
929
1096
  if (result?.then)
930
1097
  this.stageCompletion(result);
@@ -936,7 +1103,13 @@ class DatabaseTransaction {
936
1103
  operation.trackRecordVersion = true;
937
1104
  if (operation.trackRecordVersion)
938
1105
  operation.recordVersionApplied = false;
939
- const completion = operation.commit(writeVersion, operation.entry, this.retries > 0, transaction);
1106
+ let completion;
1107
+ try {
1108
+ completion = operation.commit(writeVersion, operation.entry, this.retries > 0, transaction);
1109
+ }
1110
+ finally {
1111
+ closeWriteInstance(operation);
1112
+ }
940
1113
  if (operation.trackRecordVersion)
941
1114
  operation.appliedRecordVersion = operation.recordVersionApplied ? writeVersion : undefined;
942
1115
  if (typeof completion?.then === 'function')
@@ -1013,6 +1186,8 @@ class DatabaseTransaction {
1013
1186
  // and replay branches below deliberately commit a handle other than this.transaction.
1014
1187
  if (!transaction)
1015
1188
  transaction = this.transaction;
1189
+ if (!options.transaction && this.writes.some((write) => write))
1190
+ this.renewChainForNativeCommit();
1016
1191
  this.open = exports.TRANSACTION_STATE.CLOSED;
1017
1192
  // RocksTransaction.commit() resolves with RETRY_NOW_VALUE (a number) under
1018
1193
  // coordinatedRetry, or void on a normal commit/abort.
@@ -1096,6 +1271,33 @@ class DatabaseTransaction {
1096
1271
  if (transaction) {
1097
1272
  this.writes = this.writes.filter((write) => write); // filter out removed entries
1098
1273
  if (this.writes.length > 0) {
1274
+ // Commit retries can construct fresh ranges on this live handle after read ownership ends.
1275
+ readTransactionOwners.delete(transaction);
1276
+ // Re-fence before submitting: the loop above skips operations already marked saved, so
1277
+ // save()'s own check cannot see a holder that stalled between staging and commit. The
1278
+ // retry/replay path re-saves every operation and is fenced there instead. Lease expiry
1279
+ // only: an unlock() inside the lease leaves the staged write valid, an elapsed lease
1280
+ // does not, whether or not the caller also unlocked.
1281
+ for (let i = 0; this.hasLeaseProtectedWrite && i < this.writes.length; i++) {
1282
+ const lapsed = this.writes[i].lockHandle;
1283
+ if (!lapsed?.isLeaseExpired())
1284
+ continue;
1285
+ try {
1286
+ transaction.abort();
1287
+ }
1288
+ catch { }
1289
+ // Every other terminal exit from commit() runs the logical cleanup too. Throwing
1290
+ // straight out would strand the OTHER locks this transaction holds, its staged
1291
+ // blobs, and the context's back-reference to a CLOSED transaction — and
1292
+ // transaction()'s onComplete has no rejection path to run it later.
1293
+ try {
1294
+ this.abort();
1295
+ }
1296
+ catch (error) {
1297
+ harperLogger.debug?.('cleaning up a transaction whose record lock lapsed', error);
1298
+ }
1299
+ throw (0, recordLock_ts_1.lockNotHeldError)(lapsed);
1300
+ }
1099
1301
  // The transaction was created with coordinatedRetry:true (see
1100
1302
  // getReadTxn), so commit() can resolve to RETRY_NOW_VALUE. That
1101
1303
  // sentinel (a number) is why commitResolution is typed
@@ -1855,6 +2057,10 @@ function startMonitoringTxns() {
1855
2057
  (0, longLivedTransactions_ts_1.finishLongLivedHolderReports)(reportBudget);
1856
2058
  }, txnExpiration).unref();
1857
2059
  function monitorTransaction(txn, checkedCommitPhaseChains, reportThresholdMs, reportNow, reportBudget) {
2060
+ if (txn.rangeReadActive) {
2061
+ txn.rangeReadActive = false;
2062
+ txn.renewReadTimeout();
2063
+ }
1858
2064
  reportIfLongLived(txn, reportThresholdMs, reportNow, reportBudget);
1859
2065
  {
1860
2066
  const commitChainHead = txn.commitChainHead ?? txn;