@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
@@ -147,20 +147,55 @@ export class Resource<Record extends object = any> implements ResourceInterface<
147
147
  static put = transactional(
148
148
  function (resource: any, query: RequestTarget, request: Context, data: any) {
149
149
  if (Array.isArray(data) && resource.#isCollection && resource.constructor.loadAsInstance !== false) {
150
+ const resourceClass = resource.constructor;
151
+ const primaryKey = resourceClass.primaryKey;
152
+ // Before dispatching anything: a malformed body must not race a sibling's write, and a bare
153
+ // dereference below would reach the client as a 500 carrying V8's wording. A primitive is
154
+ // malformed for the same reason — only a Table's own key validation rejects one later, so a
155
+ // plain Resource's override would otherwise be handed it.
156
+ for (let index = 0; index < data.length; index++) {
157
+ const element = data[index];
158
+ if (element == null || typeof element !== 'object')
159
+ throw new ClientError(
160
+ `Array element at index ${index} is ${element === null ? 'null' : typeof element}, expected a record`
161
+ );
162
+ }
163
+ const elementTarget = elementTargetFactory(query);
150
164
  const results = [];
151
- for (const element of data) {
152
- const resourceClass = resource.constructor;
153
- const id = element[resourceClass.primaryKey];
154
- let target = new RequestTarget();
155
- target.id = id;
156
- const elementResource = resourceClass.getResource(target, request, {
157
- async: true,
165
+ try {
166
+ for (let index = 0; index < data.length; index++) {
167
+ const element = data[index];
168
+ const target = elementTarget(element[primaryKey]);
169
+ const elementResource = resourceClass.getResource(target, request, {
170
+ async: true,
171
+ });
172
+ // `target`, not `request`: this is the target slot, and Table's back-compat shift only
173
+ // recognizes a RequestTarget there — a context is taken for the record.
174
+ // `missingMethod` rather than an unguarded call, so a resource without `put` answers 405
175
+ // here exactly as it does on the single-record path below.
176
+ if (typeof elementResource.then === 'function')
177
+ results.push(
178
+ elementResource.then((resource) =>
179
+ resource.put ? resource.put(element, target) : missingMethod(resource, 'put')
180
+ )
181
+ );
182
+ else if (elementResource.put) results.push(elementResource.put(element, target));
183
+ else missingMethod(elementResource, 'put');
184
+ }
185
+ } catch (error) {
186
+ // Settle the already-dispatched elements or a rejection among them goes unhandled. Then
187
+ // report the earliest-index failure, the same rule `settleElements` applies: every sibling
188
+ // already dispatched precedes the element that threw, so the batch reports the same failure
189
+ // whether that element's dispatch resolved synchronously or asynchronously.
190
+ return Promise.allSettled(results).then((settled) => {
191
+ const failed = settled.find((sibling) => sibling.status === 'rejected');
192
+ if (!failed) throw error;
193
+ throw (failed as PromiseRejectedResult).reason;
158
194
  });
159
- if (typeof elementResource.then === 'function')
160
- results.push(elementResource.then((resource) => resource.put(element, request)));
161
- else results.push(elementResource.put(element, query));
162
195
  }
163
- return Promise.all(results);
196
+ // Not `Promise.all`: settling the batch while a slower element is still resolving lets that
197
+ // element commit outside the failed batch — a partially applied array PUT.
198
+ return settleElements(results);
164
199
  }
165
200
  return resource.put
166
201
  ? resource.constructor.loadAsInstance === false
@@ -571,6 +606,53 @@ export function snakeCase(camelCase: string) {
571
606
  );
572
607
  }
573
608
 
609
+ /**
610
+ * Mint per-element targets for a fanned-out batch write. Each element needs its own object — its id
611
+ * must not be visible to a sibling whose dispatch resolves later — but it also needs the request's
612
+ * query and route metadata, which a `put()` override can read. The request's contribution is
613
+ * therefore resolved once here rather than deep-cloned per element, and nested metadata is shared
614
+ * rather than copied. `checkPermission` never carries: the collection receiver's single verdict is
615
+ * authoritative and per-element dispatch must not re-arm it.
616
+ */
617
+ function elementTargetFactory(query: any): (id: any) => RequestTarget {
618
+ const base = cloneRequestTarget(query);
619
+ const search = base.toString();
620
+ // Only what the element target would not otherwise have. `RequestTarget` declares `pathname`,
621
+ // `search` and `sort` without `declare`, so they are own properties on every instance and would
622
+ // otherwise always be "carried" — overwriting what this element's own construction just derived,
623
+ // and making the skip below dead. A nullish own value carries no metadata either.
624
+ const carried: Record<string, any> = {};
625
+ for (const key of Object.keys(base)) {
626
+ if (key === 'id' || key === 'isCollection' || key === 'pathname' || key === 'search') continue;
627
+ const value = (base as any)[key];
628
+ if (value != null) carried[key] = value;
629
+ }
630
+ const hasCarried = Object.keys(carried).length > 0;
631
+ return (id) => {
632
+ const target = search ? new RequestTarget(search) : new RequestTarget();
633
+ if (hasCarried) Object.assign(target, carried);
634
+ target.id = id;
635
+ target.isCollection = false;
636
+ return target;
637
+ };
638
+ }
639
+
640
+ /**
641
+ * Report a fanned-out batch write's outcome only once every element has staged or failed its own
642
+ * write, so the enclosing transaction cannot unwind under one. Rejects with the first failure.
643
+ */
644
+ function settleElements(results: any[]): Promise<any[]> {
645
+ return Promise.allSettled(results).then((settled) => {
646
+ const values = new Array(settled.length);
647
+ for (let index = 0; index < settled.length; index++) {
648
+ const element = settled[index];
649
+ if (element.status === 'rejected') throw element.reason;
650
+ values[index] = element.value;
651
+ }
652
+ return values;
653
+ });
654
+ }
655
+
574
656
  /**
575
657
  * This is responsible for arranging arguments in the main static methods and creating the appropriate context and default transaction wrapping
576
658
  * @param action
@@ -733,8 +815,10 @@ function transactional(
733
815
  if (!query) {
734
816
  query = new RequestTarget();
735
817
  query.id = id;
736
- if (isCollection && options.method === 'put' && Array.isArray(data) && this.loadAsInstance === false)
737
- query.isCollection = true;
818
+ // The collection inferred from the batch's null id has to survive onto the synthesized target,
819
+ // or the array is dispatched as one record update against a null primary key. Array-only:
820
+ // a null id on non-array data means auto-generate, not collection.
821
+ if (isCollection && options.method === 'put' && Array.isArray(data)) query.isCollection = true;
738
822
  }
739
823
  if (options.method === 'query' && data && typeof data === 'object') {
740
824
  // QUERY executes the independently parsed body target. Make its requested projection part of
@@ -201,6 +201,10 @@ interface TypedDirectCondition<Record extends object, Property extends keyof Rec
201
201
  search_type?: Comparator;
202
202
  value?: Record[Property] | Record[Property][];
203
203
  search_value?: Record[Property] | Record[Property][];
204
+ /** Native HNSW coverage tolerance in milliseconds; defaults to 3000, with 0 requiring current coverage. */
205
+ maxIndexLagMilliseconds?: number;
206
+ /** Wait for coverage of prior committed writes, up to 30,000 ms; 0 (default) does not wait. */
207
+ waitForIndexMilliseconds?: number;
204
208
  /**
205
209
  * If true, the condition is negated. Phase 1: filter-only — forces a
206
210
  * full scan unless paired with another indexed condition.
@@ -223,6 +227,10 @@ export type Conditions<Record extends object = any> = Condition<Record>[];
223
227
 
224
228
  export interface Sort<Record extends object = any> {
225
229
  attribute: keyof Record;
230
+ /** Native HNSW coverage tolerance in milliseconds; defaults to 3000, with 0 requiring current coverage. */
231
+ maxIndexLagMilliseconds?: number;
232
+ /** Wait for coverage of prior committed writes, up to 30,000 ms; 0 (default) does not wait. */
233
+ waitForIndexMilliseconds?: number;
226
234
  descending?: boolean;
227
235
  next?: Sort<Record>;
228
236
  }
@@ -1,3 +1,4 @@
1
+ import { trackReadRange } from './DatabaseTransaction.ts';
1
2
  import {
2
3
  type CountEstimate,
3
4
  type CountEstimateOptions,
@@ -48,7 +49,7 @@ export class RocksIndexStore extends RocksDatabase {
48
49
  * @param options
49
50
  */
50
51
  getRange(options: StoreIteratorOptions): Iterable<any> {
51
- return super.getRange(translateIndexBounds(options)).map(({ key }) => {
52
+ return trackReadRange(options.transaction, () => super.getRange(translateIndexBounds(options))).map(({ key }) => {
52
53
  return { key: key[0], value: key.length > 2 ? key.slice(1) : key[1] };
53
54
  });
54
55
  }
@@ -29,10 +29,16 @@ type TransactionLogIterator = Iterator<TransactionEntry | number> & {
29
29
  };
30
30
 
31
31
  type TrackedIterator = IterableIterator<TransactionEntry> & { lastVersion?: number; lastEndTxn?: boolean };
32
+ type NamedTransactionEntry = TransactionEntry & { logName?: string };
33
+ export type ExactStartFailure = 'missing' | 'incomplete' | 'duplicate';
32
34
 
33
35
  export type TransactionLogIterable = Iterable<AuditRecord> & {
34
36
  /** Corrupt frames that ended a log's iteration during this range. */
35
37
  corruptFrameStop: CorruptFrameStop;
38
+ /** Physical logs whose iterators ended with an unexpected, non-corruption error. */
39
+ failedLogs: Set<string>;
40
+ /** Physical logs whose requested exact transaction could not form one unambiguous resume boundary. */
41
+ exactStartFailures: Map<string, ExactStartFailure>;
36
42
  };
37
43
 
38
44
  const reportCorruptFrame = createCorruptFrameReporter(harperLogger);
@@ -242,6 +248,7 @@ export class RocksTransactionLogStore extends EventEmitter {
242
248
  getRange(options: {
243
249
  start?: number;
244
250
  exactStart?: boolean;
251
+ exclusiveStart?: boolean;
245
252
  end?: number;
246
253
  log?: string | number;
247
254
  excludeLogs?: string[];
@@ -249,6 +256,10 @@ export class RocksTransactionLogStore extends EventEmitter {
249
256
  startByLog?: Map<string, number>;
250
257
  startFromLastFlushed?: boolean;
251
258
  readUncommitted?: boolean;
259
+ /** Include the source transaction-log name on each returned audit record. */
260
+ includeLogName?: boolean;
261
+ /** Validate and consume one complete exact-start transaction per entry in `startByLog`. */
262
+ resumeAfterExactStart?: boolean;
252
263
  /**
253
264
  * Track which version a break truncated, in `corruptFrameStop.truncatedVersions`. Costs two
254
265
  * property stores per yielded entry (see below), so it defaults off: only boot replay reads
@@ -262,6 +273,46 @@ export class RocksTransactionLogStore extends EventEmitter {
262
273
  let singleLogIterator: TrackedIterator;
263
274
  const attributeCorruption = options.trackCorruptTransactions === true;
264
275
  const corruptFrameStop: CorruptFrameStop = { breaks: 0, truncatedVersions: new Set(), midLogBreak: false };
276
+ const failedLogs = new Set<string>();
277
+ const exactStartFailures = new Map<string, ExactStartFailure>();
278
+ const failedIterators = new WeakSet<IterableIterator<TransactionEntry>>();
279
+ const safeNext = (iterator: IterableIterator<TransactionEntry>, log: TransactionLog) => {
280
+ if (failedIterators.has(iterator)) return { value: undefined, done: true } as IteratorResult<TransactionEntry>;
281
+ try {
282
+ return iterator.next();
283
+ } catch (error) {
284
+ failedIterators.add(iterator);
285
+ failedLogs.add(log.name);
286
+ harperLogger.error('Transaction log iterator failed; terminating this log', error, {
287
+ log: log.name,
288
+ });
289
+ return { value: undefined, done: true } as IteratorResult<TransactionEntry>;
290
+ }
291
+ };
292
+ const resumePastExactStart = (
293
+ result: IteratorResult<TransactionEntry>,
294
+ iterator: TrackedIterator,
295
+ log: TransactionLog,
296
+ expected: number
297
+ ): IteratorResult<TransactionEntry> => {
298
+ if (result.done || result.value.timestamp !== expected) {
299
+ exactStartFailures.set(log.name, 'missing');
300
+ return { value: undefined, done: true };
301
+ }
302
+ while (!result.value.endTxn) {
303
+ result = safeNext(iterator, log);
304
+ if (result.done || result.value.timestamp !== expected) {
305
+ exactStartFailures.set(log.name, 'incomplete');
306
+ return { value: undefined, done: true };
307
+ }
308
+ }
309
+ result = safeNext(iterator, log);
310
+ if (!result.done && result.value.timestamp === expected) {
311
+ exactStartFailures.set(log.name, 'duplicate');
312
+ return { value: undefined, done: true };
313
+ }
314
+ return result;
315
+ };
265
316
  // Each log's iterator carries the version and endTxn of the last entry it yielded, so a break
266
317
  // can be attributed to the source transaction whose remaining entries it swallowed — unless
267
318
  // that entry's own endTxn already closed the transaction, in which case the break fell after
@@ -299,9 +350,34 @@ export class RocksTransactionLogStore extends EventEmitter {
299
350
  log = this.rootStore.useLog(options.log);
300
351
  }
301
352
  }
302
- const queryIterator = trackCorruptFrames(log, options);
353
+ const resumeAfterExactStart =
354
+ options.resumeAfterExactStart === true && options.exactStart === true && options.start !== undefined;
355
+ const queryOptions = resumeAfterExactStart ? { ...options, exclusiveStart: false } : options;
356
+ const queryIterator = trackCorruptFrames(log, queryOptions);
303
357
  singleLogIterator = queryIterator;
304
- iterable.iterate = () => queryIterator;
358
+ let exactStartObserved = !resumeAfterExactStart;
359
+ iterable.iterate =
360
+ options.includeLogName || resumeAfterExactStart
361
+ ? () => ({
362
+ next: () => {
363
+ let result = safeNext(queryIterator, log);
364
+ if (!exactStartObserved) {
365
+ exactStartObserved = true;
366
+ if (resumeAfterExactStart) result = resumePastExactStart(result, queryIterator, log, options.start);
367
+ }
368
+ if (!result.done && options.includeLogName) (result.value as NamedTransactionEntry).logName = log.name;
369
+ return result;
370
+ },
371
+ [Symbol.iterator]() {
372
+ return this;
373
+ },
374
+ return: (value?: any) => queryIterator.return?.(value) ?? { value, done: true },
375
+ throw: (error?: any) => {
376
+ if (queryIterator.throw) return queryIterator.throw(error);
377
+ throw error;
378
+ },
379
+ })
380
+ : () => queryIterator;
305
381
  } else {
306
382
  const onlyKeys = options.onlyKeys;
307
383
  let logs: TransactionLog[] = [];
@@ -309,30 +385,8 @@ export class RocksTransactionLogStore extends EventEmitter {
309
385
  let nextEntries: any[];
310
386
  let latestUpdates: number;
311
387
  const iterators: TrackedIterator[] = [];
312
- // Iterators that have permanently failed (corrupt entry stuck at the same
313
- // position). Tracked by identity so the retry-poll path in next() and
314
- // updateIterators() never calls .next() on them again — otherwise every
315
- // subsequent drain cycle would re-throw the same RangeError, spamming logs
316
- // and burning CPU.
317
- const failedIterators = new WeakSet<IterableIterator<TransactionEntry>>();
318
- // Per-log advance that converts a thrown corrupt-entry error from rocksdb-js
319
- // into a clean `done: true` for that iterator. The reader's RangeError leaves
320
- // `position` at the bad entry; re-calling next() would re-throw indefinitely.
321
- // Terminating just this log lets the aggregate keep draining the other peers'
322
- // logs and prevents the throw from escaping into setImmediate-scheduled
323
- // consumers (notifyFromTransactionData) where it becomes an uncaughtException.
324
- const safeNext = (iterator: IterableIterator<TransactionEntry>, log?: TransactionLog) => {
325
- if (failedIterators.has(iterator)) return { value: undefined, done: true };
326
- try {
327
- return iterator.next();
328
- } catch (error) {
329
- failedIterators.add(iterator);
330
- harperLogger.error('Transaction log iterator failed; terminating this log', error, {
331
- log: log?.name,
332
- });
333
- return { value: undefined, done: true };
334
- }
335
- };
388
+ const expectedExactStarts: Array<number | undefined> = [];
389
+ const observedExactStarts = new Set<string>();
336
390
  const updateIterators = () => {
337
391
  if (latestUpdates !== this.updates) {
338
392
  const latestLogs = (this.nodeLogs || this.loadLogs()).filter(
@@ -342,9 +396,18 @@ export class RocksTransactionLogStore extends EventEmitter {
342
396
  if (!logs.includes(log)) {
343
397
  logs.push(log);
344
398
  let queryOptions = options;
399
+ let expectedExactStart: number | undefined;
345
400
  if (options.startByLog) {
346
- // if the startByLog is provided, we use that
347
- queryOptions = { ...options, start: options.startByLog.get(log.name) ?? 0 };
401
+ if (options.startByLog.has(log.name)) {
402
+ expectedExactStart = options.startByLog.get(log.name)!;
403
+ queryOptions = {
404
+ ...options,
405
+ start: expectedExactStart,
406
+ exclusiveStart: options.resumeAfterExactStart ? false : options.exclusiveStart,
407
+ };
408
+ } else {
409
+ queryOptions = { ...options, start: 0, exactStart: false, exclusiveStart: false };
410
+ }
348
411
  } else if (latestUpdates >= 0) {
349
412
  // if this is not the first update, that means that this is a brand new log and if start wasn't specified
350
413
  // that means we are taking all future requests, so we need to start at zero so we don't introduce a race
@@ -352,6 +415,7 @@ export class RocksTransactionLogStore extends EventEmitter {
352
415
  queryOptions = { ...options, start: options.start ?? 0 };
353
416
  }
354
417
  iterators.push(trackCorruptFrames(log, queryOptions));
418
+ expectedExactStarts.push(expectedExactStart);
355
419
  }
356
420
  }
357
421
  latestUpdates = this.updates;
@@ -360,12 +424,22 @@ export class RocksTransactionLogStore extends EventEmitter {
360
424
  let log = logs[i];
361
425
  if (!latestLogs.includes(log)) {
362
426
  logs.splice(i, 1);
363
- iterators.splice(i--, 1);
427
+ iterators.splice(i, 1);
428
+ expectedExactStarts.splice(i--, 1);
364
429
  }
365
430
  }
366
431
  }
367
432
  }
368
- nextEntries = iterators.map((iterator, i) => safeNext(iterator, logs[i]));
433
+ nextEntries = iterators.map((iterator, i) => {
434
+ const result = safeNext(iterator, logs[i]);
435
+ const expected = expectedExactStarts[i];
436
+ if (expected !== undefined && !observedExactStarts.has(logs[i].name)) {
437
+ observedExactStarts.add(logs[i].name);
438
+ if (options.resumeAfterExactStart) return resumePastExactStart(result, iterator, logs[i], expected);
439
+ if (result.done || result.value.timestamp !== expected) exactStartFailures.set(logs[i].name, 'missing');
440
+ }
441
+ return result;
442
+ });
369
443
  };
370
444
  updateIterators();
371
445
 
@@ -408,6 +482,7 @@ export class RocksTransactionLogStore extends EventEmitter {
408
482
  }
409
483
  }
410
484
  if (earliestIndex >= 0) {
485
+ if (options.includeLogName) (earliest as NamedTransactionEntry).logName = logs[earliestIndex].name;
411
486
  if (attributeCorruption) {
412
487
  // before the refill, which is where a break surfaces and needs this entry's version
413
488
  iterators[earliestIndex].lastVersion = earliest.timestamp;
@@ -438,6 +513,7 @@ export class RocksTransactionLogStore extends EventEmitter {
438
513
  if (index >= 0) {
439
514
  logs.splice(index, 1);
440
515
  iterators.splice(index, 1);
516
+ expectedExactStarts.splice(index, 1);
441
517
  nextEntries.splice(index, 1);
442
518
  options.excludeLogs.push(logName);
443
519
  }
@@ -445,7 +521,7 @@ export class RocksTransactionLogStore extends EventEmitter {
445
521
  };
446
522
  iterable.iterate = () => aggregateIterator;
447
523
  }
448
- const mappedAggregateIterable = iterable.map(({ timestamp, data, endTxn }: TransactionEntry) => {
524
+ const mappedAggregateIterable = iterable.map(({ timestamp, data, endTxn, logName }: NamedTransactionEntry) => {
449
525
  // A break surfaces on the pull after this entry, so recording it here is in time to attribute
450
526
  // that break to this entry's transaction. The aggregate branch records its own, per source
451
527
  // log, because there this callback cannot tell which log an entry came from.
@@ -478,6 +554,7 @@ export class RocksTransactionLogStore extends EventEmitter {
478
554
  position += 8;
479
555
  }
480
556
  const auditRecord = readAuditEntry(data, position, undefined);
557
+ if (options.includeLogName) auditRecord.logName = logName;
481
558
  auditRecord.txnLogKey = timestamp;
482
559
  auditRecord.endTxn = endTxn;
483
560
  auditRecord.previousResidencyId = previousResidencyId;
@@ -493,6 +570,7 @@ export class RocksTransactionLogStore extends EventEmitter {
493
570
  // the log key is all this entry still yields; its record version is undecodable
494
571
  version: timestamp,
495
572
  txnLogKey: timestamp,
573
+ logName: options.includeLogName ? logName : undefined,
496
574
  endTxn,
497
575
  type: undefined,
498
576
  tableId: undefined,
@@ -509,6 +587,8 @@ export class RocksTransactionLogStore extends EventEmitter {
509
587
  mappedAggregateIterable.removeLog = aggregateIterator.removeLog;
510
588
  }
511
589
  mappedAggregateIterable.corruptFrameStop = corruptFrameStop;
590
+ mappedAggregateIterable.failedLogs = failedLogs;
591
+ mappedAggregateIterable.exactStartFailures = exactStartFailures;
512
592
  return mappedAggregateIterable as TransactionLogIterable;
513
593
  }
514
594
  getKeys(_options?: any) {