@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4

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 (311) hide show
  1. package/bin/cliOperations.ts +25 -3
  2. package/bin/run.ts +18 -0
  3. package/components/Application.ts +52 -78
  4. package/components/DESIGN.md +76 -3
  5. package/components/Scope.ts +10 -4
  6. package/components/anthropic/index.ts +10 -1
  7. package/components/bedrock/index.ts +10 -1
  8. package/components/componentLoader.ts +23 -14
  9. package/components/mcp/tools/operations.ts +3 -1
  10. package/components/ollama/index.ts +10 -1
  11. package/components/openai/index.ts +265 -18
  12. package/components/operations.js +40 -1
  13. package/config/configUtils.ts +3 -19
  14. package/dataLayer/DESIGN.md +29 -5
  15. package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
  16. package/dataLayer/restoreMarker.ts +250 -14
  17. package/dataLayer/schema.ts +2 -7
  18. package/dataLayer/schemaDescribe.ts +10 -0
  19. package/dist/bin/cliOperations.js +28 -3
  20. package/dist/bin/cliOperations.js.map +1 -1
  21. package/dist/bin/run.js +19 -0
  22. package/dist/bin/run.js.map +1 -1
  23. package/dist/components/Application.d.ts +8 -2
  24. package/dist/components/Application.js +43 -69
  25. package/dist/components/Application.js.map +1 -1
  26. package/dist/components/Scope.d.ts +1 -1
  27. package/dist/components/Scope.js +9 -4
  28. package/dist/components/Scope.js.map +1 -1
  29. package/dist/components/anthropic/index.d.ts +0 -10
  30. package/dist/components/anthropic/index.js +9 -1
  31. package/dist/components/anthropic/index.js.map +1 -1
  32. package/dist/components/bedrock/index.d.ts +0 -12
  33. package/dist/components/bedrock/index.js +9 -1
  34. package/dist/components/bedrock/index.js.map +1 -1
  35. package/dist/components/componentLoader.js +23 -14
  36. package/dist/components/componentLoader.js.map +1 -1
  37. package/dist/components/mcp/tools/operations.js +1 -1
  38. package/dist/components/mcp/tools/operations.js.map +1 -1
  39. package/dist/components/ollama/index.d.ts +0 -13
  40. package/dist/components/ollama/index.js +9 -1
  41. package/dist/components/ollama/index.js.map +1 -1
  42. package/dist/components/openai/index.d.ts +11 -15
  43. package/dist/components/openai/index.js +224 -12
  44. package/dist/components/openai/index.js.map +1 -1
  45. package/dist/components/operations.d.ts +9 -0
  46. package/dist/components/operations.js +32 -2
  47. package/dist/components/operations.js.map +1 -1
  48. package/dist/config/configUtils.d.ts +1 -2
  49. package/dist/config/configUtils.js +7 -22
  50. package/dist/config/configUtils.js.map +1 -1
  51. package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
  52. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  53. package/dist/dataLayer/restoreMarker.d.ts +25 -3
  54. package/dist/dataLayer/restoreMarker.js +213 -13
  55. package/dist/dataLayer/restoreMarker.js.map +1 -1
  56. package/dist/dataLayer/schema.js +1 -4
  57. package/dist/dataLayer/schema.js.map +1 -1
  58. package/dist/dataLayer/schemaDescribe.js +10 -0
  59. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/json/systemSchema.json +28 -0
  63. package/dist/resources/DatabaseTransaction.d.ts +21 -1
  64. package/dist/resources/DatabaseTransaction.js +147 -8
  65. package/dist/resources/DatabaseTransaction.js.map +1 -1
  66. package/dist/resources/PrimaryRocksDatabase.js +1 -1
  67. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  68. package/dist/resources/Table.d.ts +31 -3
  69. package/dist/resources/Table.js +397 -79
  70. package/dist/resources/Table.js.map +1 -1
  71. package/dist/resources/auditStore.js +16 -10
  72. package/dist/resources/auditStore.js.map +1 -1
  73. package/dist/resources/blob.d.ts +1 -0
  74. package/dist/resources/blob.js +14 -5
  75. package/dist/resources/blob.js.map +1 -1
  76. package/dist/resources/branchDatabase.js +3 -3
  77. package/dist/resources/branchDatabase.js.map +1 -1
  78. package/dist/resources/databaseDropPreparation.d.ts +20 -0
  79. package/dist/resources/databaseDropPreparation.js +106 -0
  80. package/dist/resources/databaseDropPreparation.js.map +1 -0
  81. package/dist/resources/databases.d.ts +26 -21
  82. package/dist/resources/databases.js +1040 -204
  83. package/dist/resources/databases.js.map +1 -1
  84. package/dist/resources/derivedIndexRegistry.d.ts +23 -0
  85. package/dist/resources/derivedIndexRegistry.js +92 -0
  86. package/dist/resources/derivedIndexRegistry.js.map +1 -1
  87. package/dist/resources/derivedIndexRuntime.d.ts +10 -0
  88. package/dist/resources/derivedIndexRuntime.js +136 -25
  89. package/dist/resources/derivedIndexRuntime.js.map +1 -1
  90. package/dist/resources/derivedIndexes.d.ts +39 -0
  91. package/dist/resources/derivedIndexes.js +733 -0
  92. package/dist/resources/derivedIndexes.js.map +1 -0
  93. package/dist/resources/fullTextSchema.d.ts +8 -0
  94. package/dist/resources/fullTextSchema.js +42 -0
  95. package/dist/resources/fullTextSchema.js.map +1 -1
  96. package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
  97. package/dist/resources/fullTextSchemaLifecycle.js +0 -9
  98. package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
  99. package/dist/resources/graphql.js +101 -9
  100. package/dist/resources/graphql.js.map +1 -1
  101. package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
  102. package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
  103. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
  104. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
  105. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
  106. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
  107. package/dist/resources/models/Models.d.ts +26 -3
  108. package/dist/resources/models/Models.js +256 -13
  109. package/dist/resources/models/Models.js.map +1 -1
  110. package/dist/resources/models/TestBackend.d.ts +3 -1
  111. package/dist/resources/models/TestBackend.js +35 -1
  112. package/dist/resources/models/TestBackend.js.map +1 -1
  113. package/dist/resources/models/analyticsTable.d.ts +2 -2
  114. package/dist/resources/models/analyticsTable.js +4 -2
  115. package/dist/resources/models/analyticsTable.js.map +1 -1
  116. package/dist/resources/models/backendHelpers.d.ts +24 -0
  117. package/dist/resources/models/backendHelpers.js +18 -1
  118. package/dist/resources/models/backendHelpers.js.map +1 -1
  119. package/dist/resources/models/backendRegistry.d.ts +10 -21
  120. package/dist/resources/models/backendRegistry.js +74 -18
  121. package/dist/resources/models/backendRegistry.js.map +1 -1
  122. package/dist/resources/models/bootstrap.d.ts +7 -0
  123. package/dist/resources/models/bootstrap.js +36 -4
  124. package/dist/resources/models/bootstrap.js.map +1 -1
  125. package/dist/resources/models/decideHook.d.ts +33 -0
  126. package/dist/resources/models/decideHook.js +105 -0
  127. package/dist/resources/models/decideHook.js.map +1 -0
  128. package/dist/resources/models/decision.d.ts +64 -0
  129. package/dist/resources/models/decision.js +507 -0
  130. package/dist/resources/models/decision.js.map +1 -0
  131. package/dist/resources/models/decisionStore.d.ts +134 -0
  132. package/dist/resources/models/decisionStore.js +350 -0
  133. package/dist/resources/models/decisionStore.js.map +1 -0
  134. package/dist/resources/models/embedHook.d.ts +66 -8
  135. package/dist/resources/models/embedHook.js +228 -61
  136. package/dist/resources/models/embedHook.js.map +1 -1
  137. package/dist/resources/models/generativeDecision.d.ts +58 -0
  138. package/dist/resources/models/generativeDecision.js +315 -0
  139. package/dist/resources/models/generativeDecision.js.map +1 -0
  140. package/dist/resources/models/types.d.ts +232 -4
  141. package/dist/security/authn/oidc/tokenExchange.js +3 -33
  142. package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
  143. package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
  144. package/dist/security/authn/oidc/tokenUseTable.js +28 -0
  145. package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
  146. package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
  147. package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
  148. package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
  149. package/dist/security/certificateVerification/crlVerification.js +77 -165
  150. package/dist/security/certificateVerification/crlVerification.js.map +1 -1
  151. package/dist/security/certificateVerification/types.d.ts +0 -3
  152. package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
  153. package/dist/security/certificateVerification/verificationTables.js +125 -0
  154. package/dist/security/certificateVerification/verificationTables.js.map +1 -0
  155. package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
  156. package/dist/security/certificateVerification/verificationUtils.js +6 -31
  157. package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
  158. package/dist/security/jsLoader.d.ts +5 -0
  159. package/dist/security/jsLoader.js +8 -0
  160. package/dist/security/jsLoader.js.map +1 -1
  161. package/dist/server/Server.d.ts +1 -0
  162. package/dist/server/Server.js.map +1 -1
  163. package/dist/server/http.js +2 -2
  164. package/dist/server/http.js.map +1 -1
  165. package/dist/server/itc/serverHandlers.js +21 -2
  166. package/dist/server/itc/serverHandlers.js.map +1 -1
  167. package/dist/server/jobs/jobOwnership.d.ts +25 -0
  168. package/dist/server/jobs/jobOwnership.js +127 -0
  169. package/dist/server/jobs/jobOwnership.js.map +1 -0
  170. package/dist/server/jobs/jobProcess.js +9 -2
  171. package/dist/server/jobs/jobProcess.js.map +1 -1
  172. package/dist/server/jobs/jobs.js +18 -5
  173. package/dist/server/jobs/jobs.js.map +1 -1
  174. package/dist/server/loadRootComponents.js +10 -0
  175. package/dist/server/loadRootComponents.js.map +1 -1
  176. package/dist/server/serverHelpers/contentTypes.js +11 -1
  177. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  178. package/dist/server/serverHelpers/serverHandlers.js +12 -2
  179. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  180. package/dist/server/threads/itc.d.ts +4 -1
  181. package/dist/server/threads/itc.js +29 -6
  182. package/dist/server/threads/itc.js.map +1 -1
  183. package/dist/server/threads/manageThreads.d.ts +12 -4
  184. package/dist/server/threads/manageThreads.js +241 -119
  185. package/dist/server/threads/manageThreads.js.map +1 -1
  186. package/dist/server/threads/threadServer.d.ts +1 -0
  187. package/dist/server/threads/threadServer.js +15 -1
  188. package/dist/server/threads/threadServer.js.map +1 -1
  189. package/dist/server/threads/windowsProcessTree.d.ts +91 -0
  190. package/dist/server/threads/windowsProcessTree.js +361 -0
  191. package/dist/server/threads/windowsProcessTree.js.map +1 -0
  192. package/dist/server/throttle.d.ts +1 -1
  193. package/dist/server/throttle.js +8 -1
  194. package/dist/server/throttle.js.map +1 -1
  195. package/dist/upgrade/directives/5-3-0.js +53 -46
  196. package/dist/upgrade/directives/5-3-0.js.map +1 -1
  197. package/dist/utility/errors/hdbError.d.ts +10 -1
  198. package/dist/utility/errors/hdbError.js +55 -11
  199. package/dist/utility/errors/hdbError.js.map +1 -1
  200. package/dist/utility/fsync.d.ts +2 -0
  201. package/dist/utility/fsync.js +20 -0
  202. package/dist/utility/fsync.js.map +1 -0
  203. package/dist/utility/globalSchema.d.ts +18 -0
  204. package/dist/utility/hdbTerms.d.ts +3 -0
  205. package/dist/utility/hdbTerms.js +3 -0
  206. package/dist/utility/hdbTerms.js.map +1 -1
  207. package/dist/utility/signalling.d.ts +7 -1
  208. package/dist/utility/signalling.js +27 -3
  209. package/dist/utility/signalling.js.map +1 -1
  210. package/dist/utility/when.d.ts +1 -0
  211. package/dist/utility/when.js +21 -0
  212. package/dist/utility/when.js.map +1 -1
  213. package/dist/validation/configValidator.js +30 -3
  214. package/dist/validation/configValidator.js.map +1 -1
  215. package/index.ts +13 -0
  216. package/json/systemSchema.json +28 -0
  217. package/npm-shrinkwrap.json +151 -55
  218. package/package.json +14 -13
  219. package/resources/DESIGN.md +80 -100
  220. package/resources/DatabaseTransaction.ts +157 -10
  221. package/resources/PrimaryRocksDatabase.ts +2 -2
  222. package/resources/Table.ts +494 -101
  223. package/resources/auditStore.ts +18 -10
  224. package/resources/blob.ts +13 -6
  225. package/resources/branchDatabase.ts +3 -3
  226. package/resources/databaseDropPreparation.ts +120 -0
  227. package/resources/databases.ts +1155 -205
  228. package/resources/derivedIndexRegistry.ts +122 -0
  229. package/resources/derivedIndexRuntime.ts +157 -30
  230. package/resources/derivedIndexes.ts +856 -0
  231. package/resources/fullTextSchema.ts +55 -0
  232. package/resources/fullTextSchemaLifecycle.ts +0 -15
  233. package/resources/graphql.ts +107 -12
  234. package/resources/indexes/DESIGN.md +7 -0
  235. package/resources/indexes/hnswDerivedIndex.ts +0 -260
  236. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
  237. package/resources/models/DESIGN.md +39 -0
  238. package/resources/models/Models.ts +361 -18
  239. package/resources/models/TestBackend.ts +56 -1
  240. package/resources/models/analyticsTable.ts +6 -4
  241. package/resources/models/backendHelpers.ts +17 -0
  242. package/resources/models/backendRegistry.ts +76 -21
  243. package/resources/models/bootstrap.ts +57 -7
  244. package/resources/models/decideHook.ts +135 -0
  245. package/resources/models/decision.ts +505 -0
  246. package/resources/models/decisionStore.ts +419 -0
  247. package/resources/models/embedHook.ts +272 -55
  248. package/resources/models/generativeDecision.ts +459 -0
  249. package/resources/models/types.ts +231 -4
  250. package/security/DESIGN.md +22 -2
  251. package/security/authn/oidc/tokenExchange.ts +6 -34
  252. package/security/authn/oidc/tokenUseTable.ts +27 -0
  253. package/security/certificateVerification/certificateVerificationSource.ts +2 -3
  254. package/security/certificateVerification/crlVerification.ts +96 -174
  255. package/security/certificateVerification/types.ts +0 -3
  256. package/security/certificateVerification/verificationTables.ts +124 -0
  257. package/security/certificateVerification/verificationUtils.ts +8 -32
  258. package/security/jsLoader.ts +9 -1
  259. package/server/DESIGN.md +27 -1
  260. package/server/Server.ts +2 -1
  261. package/server/http.ts +4 -2
  262. package/server/itc/serverHandlers.js +29 -1
  263. package/server/jobs/jobOwnership.ts +89 -0
  264. package/server/jobs/jobProcess.ts +8 -2
  265. package/server/jobs/jobs.ts +15 -4
  266. package/server/loadRootComponents.js +11 -0
  267. package/server/serverHelpers/contentTypes.ts +13 -1
  268. package/server/serverHelpers/serverHandlers.js +11 -2
  269. package/server/threads/itc.js +32 -6
  270. package/server/threads/manageThreads.js +268 -122
  271. package/server/threads/threadServer.js +13 -1
  272. package/server/threads/windowsProcessTree.ts +409 -0
  273. package/server/throttle.ts +11 -1
  274. package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
  275. package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
  276. package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
  277. package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
  278. package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
  279. package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
  280. package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
  281. package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
  282. package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
  283. package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
  284. package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
  285. package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
  286. package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
  287. package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
  288. package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
  289. package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
  290. package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
  291. package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
  292. package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
  293. package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
  294. package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
  295. package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
  296. package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
  297. package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
  298. package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
  299. package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
  300. package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
  301. package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
  302. package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
  303. package/studio/web/index.html +1 -1
  304. package/upgrade/directives/5-3-0.ts +61 -54
  305. package/utility/DESIGN.md +5 -1
  306. package/utility/errors/hdbError.ts +56 -8
  307. package/utility/fsync.ts +16 -0
  308. package/utility/hdbTerms.ts +3 -0
  309. package/utility/signalling.ts +33 -4
  310. package/utility/when.ts +22 -0
  311. package/validation/configValidator.ts +36 -7
@@ -1,5 +1,127 @@
1
+ import { DerivedIndexLagError } from '../utility/errors/hdbError.ts';
2
+
1
3
  const registrations = new WeakMap<object, Map<number, number>>();
2
4
  const admissions = new WeakMap<object, Map<number, Array<() => string | undefined>>>();
5
+ type LockStore = { status?: string; tryLock(key: string): boolean; unlock(key: string): void };
6
+ type FenceWaitOptions = { shouldContinue?: () => boolean; timeoutMilliseconds?: number };
7
+ const DEFAULT_FENCE_WAIT_MILLISECONDS = 70_000;
8
+
9
+ function fullTextClearLockKey(tableId: number): string {
10
+ return `derived-index:fulltext:${tableId}:table-clear`;
11
+ }
12
+
13
+ function fullTextRetirementLockKey(tableName: string): string {
14
+ return `derived-index:fulltext:${tableName}:retirement`;
15
+ }
16
+
17
+ /** Hold this node-wide fence for the full duration of an asynchronous table clear. */
18
+ export function acquireFullTextClearFence(rootStore: LockStore, tableId: number): (() => void) | undefined {
19
+ const key = fullTextClearLockKey(tableId);
20
+ if (!rootStore.tryLock(key)) return;
21
+ let held = true;
22
+ return () => {
23
+ if (!held) return;
24
+ held = false;
25
+ rootStore.unlock(key);
26
+ };
27
+ }
28
+
29
+ /** Probe without waiting; callers hold the schema lock so a clear cannot start between this and persistence. */
30
+ export function fullTextClearInProgress(rootStore: LockStore, tableId: number): boolean {
31
+ const release = acquireFullTextClearFence(rootStore, tableId);
32
+ if (!release) return true;
33
+ release();
34
+ return false;
35
+ }
36
+
37
+ /** Wait until the current clear releases its fence; callers retry admission afterwards. */
38
+ export function waitForFullTextClear(
39
+ rootStore: LockStore,
40
+ tableId: number,
41
+ options?: FenceWaitOptions
42
+ ): Promise<boolean> {
43
+ return waitForFence(rootStore, () => acquireFullTextClearFence(rootStore, tableId), 'table clear', options);
44
+ }
45
+
46
+ /** Fence same-name recreation while a dropped table's native directories are being retired. */
47
+ export function acquireFullTextRetirementFence(rootStore: LockStore, tableName: string): (() => void) | undefined {
48
+ const key = fullTextRetirementLockKey(tableName);
49
+ if (!rootStore.tryLock(key)) return;
50
+ let held = true;
51
+ return () => {
52
+ if (!held) return;
53
+ held = false;
54
+ rootStore.unlock(key);
55
+ };
56
+ }
57
+
58
+ /** Probe under the schema lock so recreation cannot pass between the probe and catalog persistence. */
59
+ export function fullTextRetirementInProgress(rootStore: LockStore, tableName: string): boolean {
60
+ const release = acquireFullTextRetirementFence(rootStore, tableName);
61
+ if (!release) return true;
62
+ release();
63
+ return false;
64
+ }
65
+
66
+ /** Wait until the current retirement releases its fence; the caller must recheck its generation afterwards. */
67
+ export function waitForFullTextRetirement(
68
+ rootStore: LockStore,
69
+ tableName: string,
70
+ options?: FenceWaitOptions
71
+ ): Promise<boolean> {
72
+ return waitForFence(
73
+ rootStore,
74
+ () => acquireFullTextRetirementFence(rootStore, tableName),
75
+ `retirement of full-text storage for '${tableName}'`,
76
+ options
77
+ );
78
+ }
79
+
80
+ export function waitForFullTextRetirementLease(
81
+ rootStore: LockStore,
82
+ tableName: string,
83
+ options?: FenceWaitOptions
84
+ ): Promise<(() => void) | undefined> {
85
+ return waitForFenceLease(
86
+ rootStore,
87
+ () => acquireFullTextRetirementFence(rootStore, tableName),
88
+ `retirement of full-text storage for '${tableName}'`,
89
+ options
90
+ );
91
+ }
92
+
93
+ async function waitForFence(
94
+ rootStore: LockStore,
95
+ acquire: () => (() => void) | undefined,
96
+ description: string,
97
+ options: FenceWaitOptions = {}
98
+ ): Promise<boolean> {
99
+ const release = await waitForFenceLease(rootStore, acquire, description, options);
100
+ if (!release) return false;
101
+ release();
102
+ return true;
103
+ }
104
+
105
+ async function waitForFenceLease(
106
+ rootStore: LockStore,
107
+ acquire: () => (() => void) | undefined,
108
+ description: string,
109
+ options: FenceWaitOptions = {}
110
+ ): Promise<(() => void) | undefined> {
111
+ const deadline = Date.now() + (options.timeoutMilliseconds ?? DEFAULT_FENCE_WAIT_MILLISECONDS);
112
+ let retryDelayMilliseconds = 1;
113
+ for (;;) {
114
+ if (options.shouldContinue && !options.shouldContinue()) return;
115
+ if (rootStore.status !== undefined && rootStore.status !== 'open')
116
+ throw new Error(`Cannot wait for ${description} on a ${rootStore.status} store`);
117
+ const release = acquire();
118
+ if (release) return release;
119
+ if (Date.now() >= deadline)
120
+ throw new DerivedIndexLagError(`Timed out waiting for ${description}; retry the operation`);
121
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMilliseconds));
122
+ retryDelayMilliseconds = Math.min(retryDelayMilliseconds * 2, 50);
123
+ }
124
+ }
3
125
 
4
126
  /** `admission` returns a reason while writes to these tables must be rejected. */
5
127
  export function registerDerivedIndexTables(
@@ -188,6 +188,10 @@ export type DerivedIndexRegistration = {
188
188
  backend: DerivedIndexBackend;
189
189
  projections: ReadonlyMap<number, (record: unknown) => unknown>;
190
190
  options?: DerivedIndexRunnerOptions;
191
+ /** Shared readiness identity when it must be narrower than the stable backend/lock identity. */
192
+ readinessId?: string;
193
+ /** False once this table generation has been superseded. Must be synchronous and allocation-free. */
194
+ isCurrent?: () => boolean;
191
195
  };
192
196
 
193
197
  /**
@@ -246,6 +250,8 @@ const READINESS_STATES: DerivedIndexReadinessState[] = [
246
250
  'needs-rebuild',
247
251
  'unavailable',
248
252
  ];
253
+ const READINESS_BACKEND_FAILED = READINESS_STATES.length;
254
+ const READINESS_RETRYING = READINESS_BACKEND_FAILED + 1;
249
255
  const READINESS_REASONS: DerivedIndexReadinessReason[] = [
250
256
  'none',
251
257
  'cursor-missing',
@@ -333,6 +339,7 @@ export class DerivedIndexRuntime {
333
339
  this.#listening = true;
334
340
  }
335
341
  runner.wake(true);
342
+ let releaseStarted = false;
336
343
  return () => {
337
344
  if (this.#runners.get(registration.backend.id) === runner) {
338
345
  this.#runners.delete(registration.backend.id);
@@ -343,6 +350,8 @@ export class DerivedIndexRuntime {
343
350
  }
344
351
  this.#stopListeningIfIdle();
345
352
  }
353
+ if (releaseStarted) return this.#retryRunnerRelease(registration.backend.id, runner);
354
+ releaseStarted = true;
346
355
  return this.#track(runner, runner.stop());
347
356
  };
348
357
  }
@@ -417,7 +426,7 @@ export class DerivedIndexRuntime {
417
426
 
418
427
  #pollCoverage(backendId: string, group: CoverageWaitGroup) {
419
428
  try {
420
- const views = getReadinessViews(this.#logStore, backendId);
429
+ const views = getReadinessViews(this.#logStore, this.#runners.get(backendId)?.readinessId ?? backendId);
421
430
  const before = readReadiness(views);
422
431
  const time = Atomics.load(views.coverage, 0);
423
432
  const after = readReadiness(views);
@@ -443,23 +452,31 @@ export class DerivedIndexRuntime {
443
452
  /** Force a rebuild (or retry one that became `unavailable`). Returns false when the backend cannot be rebuilt by the runtime. */
444
453
  requestRebuild(backendId: string): boolean {
445
454
  const held = this.#heldRunners.get(backendId);
446
- if (held) {
447
- const released = held.runner.retryRelease();
448
- this.#pendingStops.add(released);
449
- released.then(
450
- () => {
451
- this.#pendingStops.delete(released);
452
- this.#pendingStops.delete(held.stopped);
453
- if (this.#heldRunners.get(backendId) === held) this.#heldRunners.delete(backendId);
454
- },
455
- () => {}
456
- );
457
- }
455
+ if (held) void this.#retryRunnerRelease(backendId, held.runner);
458
456
  const runner = this.#runners.get(backendId);
459
457
  if (runner) return runner.requestRebuild();
460
458
  return held !== undefined;
461
459
  }
462
460
 
461
+ #retryRunnerRelease(backendId: string, runner: DerivedIndexRunner): Promise<void> {
462
+ const held = this.#heldRunners.get(backendId);
463
+ const released = runner.retryRelease();
464
+ this.#pendingStops.add(released);
465
+ released.then(
466
+ () => {
467
+ this.#pendingStops.delete(released);
468
+ if (held?.runner === runner) this.#pendingStops.delete(held.stopped);
469
+ if (this.#heldRunners.get(backendId)?.runner === runner) this.#heldRunners.delete(backendId);
470
+ },
471
+ () => {
472
+ if (held?.runner === runner && held.stopped !== released) this.#pendingStops.delete(held.stopped);
473
+ const current = this.#heldRunners.get(backendId);
474
+ if (!current || current.runner === runner) this.#heldRunners.set(backendId, { runner, stopped: released });
475
+ }
476
+ );
477
+ return released;
478
+ }
479
+
463
480
  /** Resolves once every runner has released ownership and its backend shutdown has settled. */
464
481
  stop(): Promise<void> {
465
482
  if (this.#stopping) return this.#stopping;
@@ -485,8 +502,15 @@ export class DerivedIndexRuntime {
485
502
 
486
503
  #stopListening() {
487
504
  if (!this.#listening) return;
488
- this.#logStore.rootStore.off?.('committed', this.#onCommit);
489
- this.#listening = false;
505
+ try {
506
+ this.#logStore.rootStore.off?.('committed', this.#onCommit);
507
+ } catch (error) {
508
+ // An already-closed store cannot emit again and some native stores reject listener access
509
+ // after close. Preserve errors from a live store, where losing the listener would be unsafe.
510
+ if (this.#logStore.rootStore.status === 'open') throw error;
511
+ } finally {
512
+ this.#listening = false;
513
+ }
490
514
  }
491
515
  }
492
516
 
@@ -624,6 +648,7 @@ class DerivedIndexRunner {
624
648
  #unregisterTables: () => void;
625
649
  #ownerEpoch?: bigint;
626
650
  #readinessBuffer: SharedReadinessBuffer;
651
+ #wakeBuffer: SharedReadinessBuffer;
627
652
  #sharedViews: SharedViews;
628
653
  #resetting?: Promise<void>;
629
654
  status: DerivedIndexRunnerStatus = { state: 'idle' };
@@ -632,6 +657,10 @@ class DerivedIndexRunner {
632
657
  return this.#registration.backend.id;
633
658
  }
634
659
 
660
+ get readinessId() {
661
+ return this.#registration.readinessId ?? this.id;
662
+ }
663
+
635
664
  retryRelease(): Promise<void> {
636
665
  if (!this.#heldLock) return this.#stopResult ?? Promise.resolve();
637
666
  this.#heldLock = false;
@@ -666,7 +695,13 @@ class DerivedIndexRunner {
666
695
  typeof root?.getSync === 'function' &&
667
696
  typeof root?.removeSync === 'function';
668
697
  this.#lagBudget = effectiveLagBudget(options);
669
- this.#readinessBuffer = readinessBuffer(logStore, registration.backend.id, () => this.#notified());
698
+ const notified = () => this.#notified();
699
+ this.#readinessBuffer = readinessBuffer(
700
+ logStore,
701
+ this.readinessId,
702
+ this.readinessId === this.id ? notified : undefined
703
+ );
704
+ this.#wakeBuffer = this.readinessId === this.id ? this.#readinessBuffer : wakeBuffer(logStore, this.id, notified);
670
705
  this.#sharedViews = sharedViewsOf(this.#readinessBuffer);
671
706
  try {
672
707
  registration.backend.attach({
@@ -678,6 +713,7 @@ class DerivedIndexRunner {
678
713
  );
679
714
  } catch (error) {
680
715
  this.#readinessBuffer.cancel?.();
716
+ if (this.#wakeBuffer !== this.#readinessBuffer) this.#wakeBuffer.cancel?.();
681
717
  throw error;
682
718
  }
683
719
  this.#unregisterTables = registerDerivedIndexTables(
@@ -700,11 +736,7 @@ class DerivedIndexRunner {
700
736
  if (this.#stopped || this.#rebuilding) return;
701
737
  if (!fromBackend && !forCoverage && this.#lagBudget > 0) this.#unreadSince ??= this.#options.now();
702
738
  if (this.status.state === 'unavailable') {
703
- if (
704
- this.#heldLock ||
705
- Atomics.load(this.#shared().words, READINESS_STATE) === READINESS_STATES.indexOf('unavailable')
706
- )
707
- return;
739
+ if (this.#heldLock || isUnavailableReadinessState(Atomics.load(this.#shared().words, READINESS_STATE))) return;
708
740
  this.status = { state: 'idle' };
709
741
  }
710
742
  // A shared rebuild request must reach an owner parked on backpressure or backoff at its next wake.
@@ -742,6 +774,7 @@ class DerivedIndexRunner {
742
774
  try {
743
775
  this.#unsubscribeBackend?.();
744
776
  this.#readinessBuffer.cancel?.();
777
+ if (this.#wakeBuffer !== this.#readinessBuffer) this.#wakeBuffer.cancel?.();
745
778
  } catch (error) {
746
779
  logger.warn?.(`Derived index '${this.id}' cleanup hook threw`, error);
747
780
  }
@@ -883,9 +916,9 @@ class DerivedIndexRunner {
883
916
  return true;
884
917
  }
885
918
  // The owner may be another worker that never idles: leave the request where every runner looks,
886
- // and notify whoever holds the buffer's callback.
919
+ // and notify every generation sharing the stable runner lock.
887
920
  Atomics.store(this.#shared().words, READINESS_REBUILD_REQUEST, 1);
888
- this.#readinessBuffer.notify?.();
921
+ this.#wakeBuffer.notify?.();
889
922
  this.wake(true);
890
923
  return true;
891
924
  }
@@ -927,12 +960,14 @@ class DerivedIndexRunner {
927
960
  * The lock is taken without an unlock callback. rocksdb-js queues such a callback as a
928
961
  * thread-safe function of the caller's env, and on Node 22 a callback left behind by a worker that
929
962
  * was terminated aborts the process when another thread unlocks (HarperFast/rocksdb-js, pending).
930
- * A successor is woken by the releasing owner's `notify()` on the readiness buffer — which
931
- * tolerates a dead listener's env — and by the retry timer below.
963
+ * A successor is woken by the releasing owner's `notify()` on the stable-id wake buffer — which
964
+ * tolerates a dead listener's env — and by the retry timer below. Registrations without a
965
+ * generation-specific readiness id keep using the readiness buffer itself.
932
966
  */
933
967
  #acquire() {
934
968
  // Commit wakes are frequent; neither a parked nor a backing-off runner may re-probe on each one.
935
969
  if (this.#waitingForLock || this.#lockBackoff) return;
970
+ if (this.#registration.isCurrent && !this.#registration.isCurrent()) return;
936
971
  if (this.#releasing) {
937
972
  this.#releasing.then(() => this.wake(true));
938
973
  return;
@@ -982,6 +1017,15 @@ class DerivedIndexRunner {
982
1017
  this.#reachedEndOfLog = false;
983
1018
  try {
984
1019
  if (!reviving) this.#ownerEpoch = this.#mintEpoch();
1020
+ // A worker that could not construct this generation may have published a setup-only
1021
+ // failure before this healthy backend acquired the stable runner lock. Ownership is
1022
+ // stronger evidence: clear only that sentinel, never a runner-published unavailable state.
1023
+ Atomics.compareExchange(
1024
+ this.#shared().words,
1025
+ READINESS_STATE,
1026
+ READINESS_BACKEND_FAILED,
1027
+ READINESS_STATES.indexOf('unknown')
1028
+ );
985
1029
  this.status = { state: 'running', ownerEpoch: this.#ownerEpoch };
986
1030
  const condemned = this.#readCondemnation();
987
1031
  const shared = this.getReadiness();
@@ -1101,6 +1145,10 @@ class DerivedIndexRunner {
1101
1145
 
1102
1146
  #drain() {
1103
1147
  if (!this.#owned || this.#stopped || this.#rebuilding) return;
1148
+ if (this.#registration.isCurrent && !this.#registration.isCurrent()) {
1149
+ this.#release();
1150
+ return;
1151
+ }
1104
1152
  if (this.#canRebuild() && this.#takeSharedRebuildRequest()) {
1105
1153
  if (this.#rebuildTimer) {
1106
1154
  clearTimeout(this.#rebuildTimer);
@@ -2083,10 +2131,10 @@ class DerivedIndexRunner {
2083
2131
  logger.error(`Failed to release derived index runner '${backend.id}'`, error);
2084
2132
  }
2085
2133
  // A skip left standing by an absent or throwing `notify` swallows a peer's release instead.
2086
- if (this.#readinessBuffer.notify) {
2134
+ if (this.#wakeBuffer.notify) {
2087
2135
  this.#skipNextNotify = !this.#stopped;
2088
2136
  try {
2089
- this.#readinessBuffer.notify();
2137
+ this.#wakeBuffer.notify();
2090
2138
  } catch (error) {
2091
2139
  this.#skipNextNotify = false;
2092
2140
  logger.warn?.(`Derived index '${backend.id}' could not notify peers of its release`, error);
@@ -2134,6 +2182,16 @@ function readinessBuffer(
2134
2182
  ) as SharedReadinessBuffer;
2135
2183
  }
2136
2184
 
2185
+ function wakeBuffer(
2186
+ logStore: RocksTransactionLogStore,
2187
+ backendId: string,
2188
+ callback: () => void
2189
+ ): SharedReadinessBuffer {
2190
+ return logStore.getUserSharedBuffer(`derived-index:${backendId}:wake`, new ArrayBuffer(1), {
2191
+ callback,
2192
+ }) as SharedReadinessBuffer;
2193
+ }
2194
+
2137
2195
  type SharedViews = {
2138
2196
  words: Int32Array;
2139
2197
  epoch: BigInt64Array;
@@ -2151,12 +2209,19 @@ function sharedViewsOf(buffer: ArrayBufferLike): SharedViews {
2151
2209
  const readinessViews = new WeakMap<object, Map<string, SharedViews>>();
2152
2210
 
2153
2211
  function readReadiness({ words, epoch }: SharedViews): DerivedIndexReadiness {
2154
- const state = READINESS_STATES[Atomics.load(words, READINESS_STATE)] ?? 'unknown';
2155
- const reason = READINESS_REASONS[Atomics.load(words, READINESS_REASON)] ?? 'none';
2212
+ const stateIndex = Atomics.load(words, READINESS_STATE);
2213
+ const setupFailed = stateIndex === READINESS_BACKEND_FAILED;
2214
+ const retrying = stateIndex === READINESS_RETRYING;
2215
+ const state = setupFailed ? 'unavailable' : retrying ? 'unknown' : (READINESS_STATES[stateIndex] ?? 'unknown');
2216
+ const reason = setupFailed
2217
+ ? 'backend-failed'
2218
+ : retrying
2219
+ ? 'none'
2220
+ : (READINESS_REASONS[Atomics.load(words, READINESS_REASON)] ?? 'none');
2156
2221
  const readiness: DerivedIndexReadiness = {
2157
2222
  state,
2158
2223
  ownerEpoch: Atomics.load(epoch, 0),
2159
- rebuildAttempts: Atomics.load(words, READINESS_ATTEMPTS),
2224
+ rebuildAttempts: retrying || setupFailed ? 0 : Atomics.load(words, READINESS_ATTEMPTS),
2160
2225
  };
2161
2226
  if (reason !== 'none') readiness.reason = reason;
2162
2227
  return readiness;
@@ -2170,6 +2235,68 @@ export function readDerivedIndexReadiness(
2170
2235
  return readReadiness(getReadinessViews(logStore, backendId));
2171
2236
  }
2172
2237
 
2238
+ /** Publish setup-time state before a backend runner exists. */
2239
+ export function publishDerivedIndexReadiness(
2240
+ logStore: RocksTransactionLogStore,
2241
+ backendId: string,
2242
+ state: DerivedIndexReadinessState,
2243
+ reason: DerivedIndexReadinessReason = 'none'
2244
+ ): void {
2245
+ const views = getReadinessViews(logStore, backendId);
2246
+ if (state === 'unknown') Atomics.store(views.words, READINESS_ATTEMPTS, 0);
2247
+ Atomics.store(views.words, READINESS_REASON, READINESS_REASONS.indexOf(reason));
2248
+ Atomics.store(views.words, READINESS_STATE, READINESS_STATES.indexOf(state));
2249
+ }
2250
+
2251
+ /** Publish a setup failure only while no elected runner has published a stronger state. */
2252
+ export function publishDerivedIndexUnavailableIfUnknown(
2253
+ logStore: RocksTransactionLogStore,
2254
+ backendId: string
2255
+ ): boolean {
2256
+ const { words, epoch } = getReadinessViews(logStore, backendId);
2257
+ // RETRYING is an explicit setup retry. Epochs are monotonic, so a prior
2258
+ // owner must not prevent that retry from returning to an observable failure.
2259
+ const retryEpoch = Atomics.load(epoch, 0);
2260
+ if (
2261
+ Atomics.compareExchange(words, READINESS_STATE, READINESS_RETRYING, READINESS_BACKEND_FAILED) === READINESS_RETRYING
2262
+ ) {
2263
+ // Close the same owner-election race as the unknown-state path below. A
2264
+ // runner that acquired this epoch owns readiness, even if its worker has
2265
+ // not published ready yet.
2266
+ if (Atomics.load(epoch, 0) !== retryEpoch) {
2267
+ Atomics.compareExchange(words, READINESS_STATE, READINESS_BACKEND_FAILED, READINESS_STATES.indexOf('unknown'));
2268
+ return false;
2269
+ }
2270
+ return true;
2271
+ }
2272
+ if (Atomics.load(epoch, 0) !== 0n) return false;
2273
+ const published =
2274
+ Atomics.compareExchange(words, READINESS_STATE, READINESS_STATES.indexOf('unknown'), READINESS_BACKEND_FAILED) ===
2275
+ READINESS_STATES.indexOf('unknown');
2276
+ if (!published) return false;
2277
+ // Close the race with an owner acquiring between the epoch probe and the state CAS.
2278
+ if (Atomics.load(epoch, 0) !== 0n) {
2279
+ Atomics.compareExchange(words, READINESS_STATE, READINESS_BACKEND_FAILED, READINESS_STATES.indexOf('unknown'));
2280
+ return false;
2281
+ }
2282
+ return true;
2283
+ }
2284
+
2285
+ /** Rearm setup only while no peer has replaced the unavailable state. */
2286
+ export function retryDerivedIndexUnavailable(logStore: RocksTransactionLogStore, backendId: string): boolean {
2287
+ const { words } = getReadinessViews(logStore, backendId);
2288
+ const unavailable = READINESS_STATES.indexOf('unavailable');
2289
+ if (Atomics.compareExchange(words, READINESS_STATE, unavailable, READINESS_RETRYING) === unavailable) return true;
2290
+ return (
2291
+ Atomics.compareExchange(words, READINESS_STATE, READINESS_BACKEND_FAILED, READINESS_RETRYING) ===
2292
+ READINESS_BACKEND_FAILED
2293
+ );
2294
+ }
2295
+
2296
+ function isUnavailableReadinessState(stateIndex: number): boolean {
2297
+ return stateIndex === READINESS_STATES.indexOf('unavailable') || stateIndex === READINESS_BACKEND_FAILED;
2298
+ }
2299
+
2173
2300
  function getReadinessViews(logStore: RocksTransactionLogStore, backendId: string): SharedViews {
2174
2301
  let byBackend = readinessViews.get(logStore);
2175
2302
  if (!byBackend) readinessViews.set(logStore, (byBackend = new Map()));