@harperfast/harper 5.3.0 → 5.3.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 (231) hide show
  1. package/bin/copyDb.ts +5 -1
  2. package/bin/deployRenderer.ts +3 -0
  3. package/components/Application.ts +66 -22
  4. package/components/DESIGN.md +89 -36
  5. package/components/componentLoader.ts +2 -0
  6. package/components/deploymentRecorder.ts +18 -1
  7. package/components/installFingerprint.ts +123 -0
  8. package/components/mcp/DESIGN.md +4 -4
  9. package/components/operations.js +29 -0
  10. package/config/DESIGN.md +5 -5
  11. package/dataLayer/DESIGN.md +37 -22
  12. package/dataLayer/backupRepository.ts +248 -0
  13. package/dataLayer/restoreMarker.ts +30 -75
  14. package/dataLayer/rocksdbBackup.ts +253 -110
  15. package/dist/bin/copyDb.js +3 -0
  16. package/dist/bin/copyDb.js.map +1 -1
  17. package/dist/bin/deployRenderer.js +3 -0
  18. package/dist/bin/deployRenderer.js.map +1 -1
  19. package/dist/components/Application.d.ts +5 -0
  20. package/dist/components/Application.js +51 -19
  21. package/dist/components/Application.js.map +1 -1
  22. package/dist/components/componentLoader.js +2 -0
  23. package/dist/components/componentLoader.js.map +1 -1
  24. package/dist/components/deploymentRecorder.js +18 -1
  25. package/dist/components/deploymentRecorder.js.map +1 -1
  26. package/dist/components/installFingerprint.d.ts +39 -0
  27. package/dist/components/installFingerprint.js +113 -0
  28. package/dist/components/installFingerprint.js.map +1 -0
  29. package/dist/components/operations.d.ts +2 -0
  30. package/dist/components/operations.js +31 -0
  31. package/dist/components/operations.js.map +1 -1
  32. package/dist/dataLayer/backupRepository.d.ts +33 -0
  33. package/dist/dataLayer/backupRepository.js +232 -0
  34. package/dist/dataLayer/backupRepository.js.map +1 -0
  35. package/dist/dataLayer/restoreMarker.d.ts +4 -1
  36. package/dist/dataLayer/restoreMarker.js +29 -68
  37. package/dist/dataLayer/restoreMarker.js.map +1 -1
  38. package/dist/dataLayer/rocksdbBackup.js +221 -112
  39. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/json/systemSchema.json +14 -0
  43. package/dist/resources/DatabaseTransaction.d.ts +140 -11
  44. package/dist/resources/DatabaseTransaction.js +1055 -464
  45. package/dist/resources/DatabaseTransaction.js.map +1 -1
  46. package/dist/resources/IterableEventQueue.d.ts +2 -1
  47. package/dist/resources/IterableEventQueue.js +16 -8
  48. package/dist/resources/IterableEventQueue.js.map +1 -1
  49. package/dist/resources/LMDBTransaction.d.ts +13 -2
  50. package/dist/resources/LMDBTransaction.js +219 -93
  51. package/dist/resources/LMDBTransaction.js.map +1 -1
  52. package/dist/resources/RecordEncoder.js +2 -3
  53. package/dist/resources/RecordEncoder.js.map +1 -1
  54. package/dist/resources/Resource.js +5 -6
  55. package/dist/resources/Resource.js.map +1 -1
  56. package/dist/resources/ResourceInterface.d.ts +3 -1
  57. package/dist/resources/ResourceInterface.js.map +1 -1
  58. package/dist/resources/RocksIndexStore.d.ts +2 -1
  59. package/dist/resources/RocksIndexStore.js +3 -0
  60. package/dist/resources/RocksIndexStore.js.map +1 -1
  61. package/dist/resources/Table.js +116 -12
  62. package/dist/resources/Table.js.map +1 -1
  63. package/dist/resources/analytics/write.js +6 -2
  64. package/dist/resources/analytics/write.js.map +1 -1
  65. package/dist/resources/auditStore.d.ts +71 -17
  66. package/dist/resources/auditStore.js +248 -64
  67. package/dist/resources/auditStore.js.map +1 -1
  68. package/dist/resources/branchDatabase.js +2 -0
  69. package/dist/resources/branchDatabase.js.map +1 -1
  70. package/dist/resources/databases.js +8 -2
  71. package/dist/resources/databases.js.map +1 -1
  72. package/dist/resources/indexes/fullTextNativeBinding.d.ts +6 -2
  73. package/dist/resources/indexes/fullTextNativeBinding.js +12 -2
  74. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
  75. package/dist/resources/indexes/fullTextQueryIndex.d.ts +4 -0
  76. package/dist/resources/indexes/fullTextQueryIndex.js +207 -10
  77. package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -1
  78. package/dist/resources/models/Models.d.ts +11 -0
  79. package/dist/resources/models/Models.js +95 -4
  80. package/dist/resources/models/Models.js.map +1 -1
  81. package/dist/resources/models/backendRegistry.d.ts +8 -0
  82. package/dist/resources/models/backendRegistry.js +15 -1
  83. package/dist/resources/models/backendRegistry.js.map +1 -1
  84. package/dist/resources/models/bootstrap.d.ts +2 -0
  85. package/dist/resources/models/bootstrap.js +6 -0
  86. package/dist/resources/models/bootstrap.js.map +1 -1
  87. package/dist/resources/models/calibration.d.ts +88 -0
  88. package/dist/resources/models/calibration.js +244 -0
  89. package/dist/resources/models/calibration.js.map +1 -0
  90. package/dist/resources/models/calibrationStore.d.ts +163 -0
  91. package/dist/resources/models/calibrationStore.js +986 -0
  92. package/dist/resources/models/calibrationStore.js.map +1 -0
  93. package/dist/resources/models/decisionStore.d.ts +24 -0
  94. package/dist/resources/models/decisionStore.js +45 -0
  95. package/dist/resources/models/decisionStore.js.map +1 -1
  96. package/dist/resources/models/generativeDecision.js +44 -15
  97. package/dist/resources/models/generativeDecision.js.map +1 -1
  98. package/dist/resources/models/types.d.ts +7 -0
  99. package/dist/resources/recordLock.d.ts +8 -5
  100. package/dist/resources/recordLock.js +6 -4
  101. package/dist/resources/recordLock.js.map +1 -1
  102. package/dist/resources/replayLogs.js +8 -1
  103. package/dist/resources/replayLogs.js.map +1 -1
  104. package/dist/resources/scheduler/engine.d.ts +4 -0
  105. package/dist/resources/scheduler/engine.js +30 -2
  106. package/dist/resources/scheduler/engine.js.map +1 -1
  107. package/dist/resources/search.d.ts +16 -4
  108. package/dist/resources/search.js +117 -13
  109. package/dist/resources/search.js.map +1 -1
  110. package/dist/resources/transaction.js +85 -5
  111. package/dist/resources/transaction.js.map +1 -1
  112. package/dist/resources/transactionBroadcast.d.ts +8 -0
  113. package/dist/resources/transactionBroadcast.js +65 -0
  114. package/dist/resources/transactionBroadcast.js.map +1 -1
  115. package/dist/security/keys.d.ts +1 -1
  116. package/dist/security/keys.js +170 -120
  117. package/dist/security/keys.js.map +1 -1
  118. package/dist/server/http.js +10 -0
  119. package/dist/server/http.js.map +1 -1
  120. package/dist/server/serverHelpers/Request.js +11 -2
  121. package/dist/server/serverHelpers/Request.js.map +1 -1
  122. package/dist/server/serverHelpers/contentTypes.js +8 -3
  123. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  124. package/dist/server/serverHelpers/uwsServer.js +38 -7
  125. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  126. package/dist/server/threads/manageThreads.d.ts +3 -3
  127. package/dist/server/threads/manageThreads.js +15 -13
  128. package/dist/server/threads/manageThreads.js.map +1 -1
  129. package/dist/server/threads/threadServer.d.ts +3 -0
  130. package/dist/server/threads/threadServer.js +24 -6
  131. package/dist/server/threads/threadServer.js.map +1 -1
  132. package/dist/upgrade/directives/5-3-0.js +9 -3
  133. package/dist/upgrade/directives/5-3-0.js.map +1 -1
  134. package/dist/utility/durableFile.d.ts +36 -0
  135. package/dist/utility/durableFile.js +106 -0
  136. package/dist/utility/durableFile.js.map +1 -0
  137. package/dist/utility/errors/hdbError.d.ts +4 -0
  138. package/dist/utility/errors/hdbError.js +10 -1
  139. package/dist/utility/errors/hdbError.js.map +1 -1
  140. package/dist/utility/globalSchema.d.ts +9 -0
  141. package/dist/utility/hdbTerms.d.ts +1 -0
  142. package/dist/utility/hdbTerms.js +1 -0
  143. package/dist/utility/hdbTerms.js.map +1 -1
  144. package/dist/validation/configValidator.js +29 -0
  145. package/dist/validation/configValidator.js.map +1 -1
  146. package/index.ts +7 -0
  147. package/json/systemSchema.json +14 -0
  148. package/npm-shrinkwrap.json +22 -22
  149. package/package.json +2 -2
  150. package/resources/DESIGN.md +57 -272
  151. package/resources/DatabaseTransaction.ts +708 -87
  152. package/resources/FULL_TEXT.md +6 -2
  153. package/resources/IterableEventQueue.ts +14 -7
  154. package/resources/LMDBTransaction.ts +212 -84
  155. package/resources/RecordEncoder.ts +2 -3
  156. package/resources/Resource.ts +5 -6
  157. package/resources/ResourceInterface.ts +3 -1
  158. package/resources/RocksIndexStore.ts +5 -0
  159. package/resources/Table.ts +118 -15
  160. package/resources/analytics/write.ts +6 -2
  161. package/resources/audit-retention.md +203 -0
  162. package/resources/auditStore.ts +306 -84
  163. package/resources/branchDatabase.ts +2 -0
  164. package/resources/databases.ts +6 -2
  165. package/resources/indexes/DESIGN.md +120 -177
  166. package/resources/indexes/fullTextNativeBinding.ts +22 -3
  167. package/resources/indexes/fullTextQueryIndex.ts +274 -11
  168. package/resources/models/DESIGN.md +11 -3
  169. package/resources/models/Models.ts +127 -4
  170. package/resources/models/backendRegistry.ts +17 -0
  171. package/resources/models/bootstrap.ts +9 -1
  172. package/resources/models/calibration.ts +298 -0
  173. package/resources/models/calibrationStore.ts +1167 -0
  174. package/resources/models/decisionStore.ts +57 -0
  175. package/resources/models/generativeDecision.ts +54 -16
  176. package/resources/models/types.ts +6 -0
  177. package/resources/record-locks.md +155 -141
  178. package/resources/recordLock.ts +12 -6
  179. package/resources/replayLogs.ts +9 -2
  180. package/resources/scheduler/DESIGN.md +4 -3
  181. package/resources/scheduler/engine.ts +35 -2
  182. package/resources/search.ts +154 -16
  183. package/resources/transaction.ts +50 -5
  184. package/resources/transactionBroadcast.ts +69 -0
  185. package/security/DESIGN.md +9 -30
  186. package/security/keys.ts +175 -127
  187. package/server/DESIGN.md +70 -36
  188. package/server/http.ts +10 -0
  189. package/server/serverHelpers/Request.ts +10 -2
  190. package/server/serverHelpers/contentTypes.ts +5 -3
  191. package/server/serverHelpers/uwsServer.ts +34 -7
  192. package/server/threads/manageThreads.js +16 -14
  193. package/server/threads/threadServer.js +24 -6
  194. package/studio/web/assets/{Chat-CNEKMllJ.js → Chat-4LMxI0yB.js} +1 -1
  195. package/studio/web/assets/{FloatingChat-CEnWhHeR.js → FloatingChat-kWHApEtF.js} +3 -3
  196. package/studio/web/assets/{SearchDialog-CwhJdEdY.js → SearchDialog-B7ymna97.js} +1 -1
  197. package/studio/web/assets/{apiToken-DC5vsaaO.js → apiToken-CE3AAt9f.js} +1 -1
  198. package/studio/web/assets/{applications-DQZxDy0x.js → applications-DdN7CqsB.js} +1 -1
  199. package/studio/web/assets/{cssMode-Dz2DxF4e.js → cssMode-BpySZUSt.js} +1 -1
  200. package/studio/web/assets/{editor-B3mjd3tz.js → editor-myLCVv9C.js} +1 -1
  201. package/studio/web/assets/{html-BsRGM7tp.js → html-DGNYO55O.js} +1 -1
  202. package/studio/web/assets/{htmlMode-tUKJaA3o.js → htmlMode-BUgpQr41.js} +1 -1
  203. package/studio/web/assets/{index-BuqFjN5K.js → index-FN4KC9va.js} +39 -39
  204. package/studio/web/assets/index-QLZw6xrU.css +1 -0
  205. package/studio/web/assets/{index.lazy-CcpfQFY_.js → index.lazy-DZFuaumG.js} +1 -1
  206. package/studio/web/assets/{javascript-BZ_uRGHl.js → javascript-DiBzh4pj.js} +1 -1
  207. package/studio/web/assets/{jsonMode-DgB-aFMj.js → jsonMode-Bm312nmu.js} +1 -1
  208. package/studio/web/assets/{languageServices-BleuBw17.js → languageServices-Dz2K9rZN.js} +1 -1
  209. package/studio/web/assets/{lspLanguageFeatures-ByVYLSsQ.js → lspLanguageFeatures-CNu1L5xn.js} +1 -1
  210. package/studio/web/assets/{notifications-TuzN0IMi.js → notifications--nwFLGmM.js} +1 -1
  211. package/studio/web/assets/{notifications-D1Nf-Edn.js → notifications-BelKw5bR.js} +1 -1
  212. package/studio/web/assets/{profile-D7C7Lupz.js → profile-AU5FjOBx.js} +1 -1
  213. package/studio/web/assets/{regions-nGQCm-v8.js → regions-C0sTSEHH.js} +1 -1
  214. package/studio/web/assets/{register-C3x9Ze6P.js → register-DyNR2rGk.js} +2 -2
  215. package/studio/web/assets/{setComponentFile-DpjrRqeG.js → setComponentFile-BkWx8AN3.js} +1 -1
  216. package/studio/web/assets/{setup-CGiKLZ9e.js → setup-D6LZS78d.js} +2 -2
  217. package/studio/web/assets/{status-CGJwrNPH.js → status-BCWnn70o.js} +1 -1
  218. package/studio/web/assets/{toggleHighContrast-C-Ne6P7T.js → toggleHighContrast-pyeGJ6W1.js} +1 -1
  219. package/studio/web/assets/{tsMode-CSreYTys.js → tsMode-CGV3iaWC.js} +1 -1
  220. package/studio/web/assets/{typescript-MnxBk89W.js → typescript-Ci7Khvwo.js} +1 -1
  221. package/studio/web/assets/{useEntityRestURL-TNrh77-s.js → useEntityRestURL-DNiz3aZ0.js} +1 -1
  222. package/studio/web/assets/{workers-BfZlUNVI.js → workers-byzxjbqy.js} +1 -1
  223. package/studio/web/assets/{xml-BHd6qGH7.js → xml-DGnHQ3CX.js} +1 -1
  224. package/studio/web/assets/{yaml-C53R8OQq.js → yaml-BUoTnPWb.js} +1 -1
  225. package/studio/web/index.html +2 -2
  226. package/upgrade/directives/5-3-0.ts +10 -3
  227. package/utility/durableFile.ts +96 -0
  228. package/utility/errors/hdbError.ts +9 -0
  229. package/utility/hdbTerms.ts +1 -0
  230. package/validation/configValidator.ts +31 -0
  231. package/studio/web/assets/index-72cEH474.css +0 -1
package/server/DESIGN.md CHANGED
@@ -16,7 +16,7 @@ This layer accepts inbound traffic on every supported protocol (HTTP/1.1, HTTP/2
16
16
  | **Operations API** | `operationsServer.ts` | Fastify-based JSON operations API (`{operation: 'create_table', ...}`). Internal/admin surface — not on the hot path for application data. |
17
17
  | **Custom Functions (legacy)** | `fastifyRoutes.ts` | Legacy custom functions only. Wraps Fastify with autoload. Don't add new code here. |
18
18
 
19
- A request entering `http.ts` does **not** go through Fastify. The two `handleApplication(scope)` functions (one in each Fastify file) load independently from component config.
19
+ A request entering `http.ts` does **not** go through Fastify unless no Harper handler answers it (status `-1` cascades to a registered Fastify instance). Of the two Fastify files only `fastifyRoutes.ts` has a `handleApplication(scope)`; `operationsServer.ts` is the `operationsApi` component, started once on the main thread through `startOnMainThread`.
20
20
 
21
21
  ---
22
22
 
@@ -57,7 +57,7 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
57
57
  | `throttle.ts` | Event-loop backpressure: runs a caller's calls one per `setImmediate` cycle and hands a call to the caller's limit handler (HTTP request queues, cache-source resolution) once queue depth × average cycle time passes the limit. |
58
58
  | `storageReclamation.ts` | Disk-pressure signals to downstream consumers; `getStorageSpaceStats()` is the shared quota-aware (falls back to `statfs`) source of available/free/size storage numbers — used by `Table.getStorageStats()` (#1976). NOT used for blob storage path weighting (`resources/blob.ts`): quota-status.json is a single instance-wide figure, so it can't distinguish between multiple `STORAGE_BLOBPATHS` disks — that still needs raw per-path `statfs`. |
59
59
  | `serverRegistry.ts` | Trivial registry export. |
60
- | `status/` | Server status reporting (cluster status, per-port info). |
60
+ | `status/` | `get_status`/`set_status`/`clear_status` over `system.hdb_status`, aggregated with component status and (opt-in) the resolved middleware chains (#1573). |
61
61
 
62
62
  > **Subscription message identity contract.** `sharedMessageEncoding.ts` encodes a message once and
63
63
  > reuses the bytes for every subscriber of the topic, keyed on the message object's identity. That is
@@ -75,13 +75,13 @@ A request entering `http.ts` does **not** go through Fastify. The two `handleApp
75
75
 
76
76
  ### Threads
77
77
 
78
- | File | Purpose |
79
- | -------------------------- | -------------------------------------------------------- |
80
- | `threads/socketRouter.ts` | Routes accepted sockets to worker threads based on port. |
81
- | `threads/manageThreads.js` | Thread pool lifecycle. |
82
- | `threads/threadServer.js` | Worker entry point — receives sockets via IPC. |
83
- | `threads/itc.js` | Inter-thread comms primitives. |
84
- | `transactionLogCooling.ts` | Main-thread timer that cools transaction-log mmaps. |
78
+ | File | Purpose |
79
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
80
+ | `threads/socketRouter.ts` | Starts the HTTP worker pool (`startHTTPThreads`) and isolated applications' dedicated workers (`reconcileIsolatedWorkers`); workers bind their own ports, so no socket is routed. |
81
+ | `threads/manageThreads.js` | Thread pool lifecycle. |
82
+ | `threads/threadServer.js` | Worker entry point — loads components (`startServers`) and binds each registered server itself (`listenOnPorts`, `reusePort` where the OS has it). |
83
+ | `threads/itc.js` | Inter-thread comms primitives. |
84
+ | `transactionLogCooling.ts` | Main-thread timer that cools transaction-log mmaps. |
85
85
 
86
86
  Process-wide shutdown begins by calling `beginProcessShutdown()` in `threads/manageThreads.js`.
87
87
  Once set, this terminal state prevents every worker replacement path and makes new `startWorker()`
@@ -100,7 +100,7 @@ replacement without being awaited. Each wait is bounded by a per-worker startup
100
100
  resolution means "the restart finished", not "every worker is new". A caller that treats its own success as
101
101
  "the component is live" must await it (see `deployComponent` in `components/operations.js`).
102
102
 
103
- > Workers receive `workerData.noServerStart = true` — never start the server inside a worker.
103
+ > `index.ts` sets `workerData.noServerStart = true` when Harper is imported from a thread it did not spawn, so `threadServer.js` skips `startServers()` there; Harper's own HTTP workers start their servers.
104
104
  >
105
105
  > `threadServer.listenOnDomainSocket()` skips a listener only when its path exceeds the platform's
106
106
  > `sockaddr_un.sun_path` byte limit (some Node versions reject it; others silently truncate it).
@@ -111,7 +111,7 @@ resolution means "the restart finished", not "every worker is new". A caller tha
111
111
 
112
112
  Single-instance background tasks pick their thread by what state they touch:
113
113
 
114
- - **Last worker** (`getWorkerIndex() === getWorkerCount() - 1`) — for tasks that operate on **worker-resident JS state**: audit cleanup (`resources/auditStore.ts`) and disk reclamation (`storageReclamation.ts`) walk per-store objects that only exist in a worker.
114
+ - **Last worker** (`ownsStoreMaintenance(storePath)` in `threads/manageThreads.js`: the last pool worker for shared stores; a dedicated isolated-application worker maintains only its own branch stores) — for tasks that operate on **worker-resident JS state**: audit cleanup (`resources/auditStore.ts`), TTL scans (`resources/Table.ts`) and disk reclamation (`storageReclamation.ts`) walk per-store objects that only exist in a worker.
115
115
  - **Main thread** (`isMainThread`) — for tasks that drive a **process-global native singleton** and need no JS state. `transactionLogCooling.ts` is the example: rocksdb-js's transaction-log registry is one C++ static shared across all worker threads, so any thread cools every log. The main thread is chosen because it is the only thread that lives for the whole process — a worker-driven timer would stall whenever that worker is recycled.
116
116
 
117
117
  ---
@@ -125,8 +125,8 @@ Every entry is a top-level function or named const. Jump via go-to-symbol or `gr
125
125
  | `registerUdsCleanupPaths`, `recordUdsBindSuccess`, `cleanupUdsFiles`, `markUdsBindFailed`, `writeUdsMetadata`, `cleanupSocketsDirectory` | UDS socket / metadata file lifecycle. Ownership-aware: `recordUdsBindSuccess` captures the inode a worker's own bind confirmed; `cleanupUdsFiles`/`markUdsBindFailed` only unlink a path when the inode on disk still matches, so an overlapping restart's outgoing worker can never delete the replacement that already rebound the same path (see restartWorkers() in manageThreads.js). `cleanupSocketsDirectory` is the separate crash-path sweep, run once from `socketRouter.ts`'s `startHTTPThreads` on main-thread startup, before any worker can bind. |
126
126
  | `handleApplication(scope)` | Component entry point — captures `httpOptions` for the scope. |
127
127
  | `getHttpOptions()` | Returns the current scope's `HttpOptions`. |
128
- | `deliverSocket()` | IPC-delivered socket handoff from `socketRouter`. |
129
- | `proxyRequest()` | Cross-port request routing. |
128
+ | `deliverSocket()` | Exported but called nowhere in core or harper-pro (`proxyRequest` is its only caller); workers bind their own ports. |
129
+ | `proxyRequest()` | Exported but called nowhere in core or harper-pro. |
130
130
  | `registerServer()` | Records a server for a port in the `SERVERS` map. |
131
131
  | `getPorts()` | Resolves listener options → list of `{port, secure}`. |
132
132
  | `httpServer()` | Main listener registration entry point. |
@@ -185,7 +185,7 @@ Consequences worth knowing:
185
185
 
186
186
  ## Operations authorization boundary
187
187
 
188
- Operations request bodies are untrusted data. `serverHandlers.js → handlePostRequest()` rejects
188
+ Operations request bodies are untrusted data. `serverHelpers/serverHandlers.js → handlePostRequest()` rejects
189
189
  prototype-mutating property names and strips the legacy `bypass_auth` property before dispatch.
190
190
  `serverUtilities.ts → chooseOperation()` never reads authorization control from the body: trusted
191
191
  internal callers pass bypass state as a separate argument and expose it to operation handlers only
@@ -250,12 +250,13 @@ user resolution touches system-table searches (`listUsers()`) that raise a defau
250
250
  storage outage as an unknown credential and hand it to application authorization. The tag is set at
251
251
  exactly these points:
252
252
 
253
- | Tagged rejection | Where |
254
- | ------------------------------------------------------------------ | ------------------------------------------ |
255
- | unknown user, inactive user, bad password | `security/user.ts → findAndValidateUser()` |
256
- | JWT syntax, signature, expiry, not-before, subject/claim rejection | `tokenAuthentication.ts → validateToken()` |
257
- | refresh-token hash mismatch, malformed scoped-token claims | `tokenAuthentication.ts` |
258
- | an `Authorization` scheme Harper does not implement | `security/auth.ts` |
253
+ | Tagged rejection | Where |
254
+ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
255
+ | unknown user, inactive user, bad password | `security/user.ts → findAndValidateUser()` |
256
+ | JWT syntax, signature, expiry, not-before, subject/claim rejection | `security/tokenAuthentication.ts → validateToken()` |
257
+ | refresh-token hash mismatch, malformed scoped-token claims | `security/tokenAuthentication.ts` |
258
+ | an `Authorization` scheme Harper does not implement | `security/auth.ts` |
259
+ | a component's `server.getUser` override that tags its own error | `markCredentialRejection` / `credentialRejectionError`, exported from `harper` (harper#2703) |
259
260
 
260
261
  `validateToken()` separates the two in the same catch: `jsonwebtoken` reports unusable key material
261
262
  through the very same `JsonWebTokenError` type it uses for a forged token, so the public key is
@@ -271,14 +272,14 @@ internal detail never reaches an unauthenticated client.
271
272
 
272
273
  Any layer that establishes Harper owns the route then settles the deferred state before doing work:
273
274
 
274
- | Layer | Where |
275
- | --------------------------------------- | ------------------------------------------------------------------ |
276
- | `REST.ts → http()` | after `resources.getMatch` succeeds (and for the OpenAPI document) |
277
- | `REST.ts` WebSocket handler | after `resources.getMatch(url, 'ws')` succeeds |
278
- | `graphqlQuerying.ts` | after the `/graphql` prefix match, ahead of its error mapping |
279
- | `static.ts` | after a static file entry matches |
280
- | `mqtt.ts` WebSocket handler | once the pending HTTP chain settles, before the first packet |
281
- | `components/mcp/adapters/harperHttp.ts` | after the WebSocket hand-off, before the body is read |
275
+ | Layer | Where |
276
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
277
+ | `REST.ts → http()` | after `resources.getMatch` succeeds (and for the OpenAPI document) |
278
+ | `REST.ts` WebSocket handler | once the HTTP chain settles, _before_ `resources.getMatch(url, 'ws')` (`assertNoDeferredCredentialRejection`), so the close code cannot reveal whether the resource exists |
279
+ | `graphqlQuerying.ts` | after the `/graphql` prefix match, ahead of its error mapping |
280
+ | `static.ts` | after a static file entry matches |
281
+ | `mqtt.ts` WebSocket handler | once the pending HTTP chain settles, before the first packet |
282
+ | `components/mcp/adapters/harperHttp.ts` | after the WebSocket hand-off, before the body is read |
282
283
 
283
284
  **Every Harper-owned handler registered `after: 'authentication'` owes this settlement**, because
284
285
  declining to call `nextHandler` is precisely the moment ownership is settled. A handler that skips
@@ -363,7 +364,7 @@ The `@table(cacheControl:)` value is persisted on the primary-key attribute (lik
363
364
  | How are content types (de)serialized? | `serverHelpers/contentTypes.ts` |
364
365
  | Why doesn't every MQTT subscriber re-serialize the message it receives? | `serverHelpers/sharedMessageEncoding.ts` (memoized on the message object the fan-out shares); consumed by the outbound listener in `mqtt.ts` |
365
366
  | Where do durable subscriptions live? | `DurableSubscriptionsSession.ts` |
366
- | How are sockets dispatched to worker threads? | `threads/socketRouter.ts` |
367
+ | How do worker threads get their connections? | Each binds its ports itself after the main thread's first bind (`threads/threadServer.js → listenOnPorts`, `reusePort` where the OS has it); `threads/socketRouter.ts → startHTTPThreads` starts them |
367
368
  | Where is the Operations API wired into Fastify? | `operationsServer.ts → buildServer` |
368
369
 
369
370
  ---
@@ -371,7 +372,7 @@ The `@table(cacheControl:)` value is persisted on the primary-key attribute (lik
371
372
  ## Conventions
372
373
 
373
374
  - Don't add new code to `fastifyRoutes.ts` — it's the legacy custom-functions path.
374
- - New protocol plugins implement the `Server` interface (in `Server.ts`) and register via `onRequest`/`onUpgrade`/`onWebSocket`.
375
+ - New protocol plugins register through the `Server` interface (`Server.ts`): `server.http()`/`request()`/`ws()`/`upgrade()` (bound to `http.ts`'s `httpServer`/`onRequest`/`onWebSocket`/`onUpgrade`) and `server.socket()` (`threadServer.js → onSocket`).
375
376
  - Always pass `name` when registering a listener with `before`/`after` — anonymous entries can't be ordered against.
376
377
  - Tests live in `../unitTests/server/`.
377
378
 
@@ -384,13 +385,13 @@ invoked, because the scope is written in that namespace (`sql`, `export_local`,
384
385
  make that awkward:
385
386
 
386
387
  1. On the **direct-SQL** path, the object handed to `checkASTPermissions` _is_ the client's request
387
- body, and this check is the only gate there (`chooseOperation`'s `sql` branch is mutually
388
- exclusive with its `verifyPerms` call). Any field read off that object is therefore a way to
388
+ body, and this check is the only table and token-scope gate there (`chooseOperation`'s `sql` branch
389
+ is mutually exclusive with its `verifyPerms` call; only `verifyOperationsAllowlist` also runs). Any field read off that object is therefore a way to
389
390
  name whichever operation the caller's scope happens to allow and run arbitrary SQL under it.
390
391
  `jsonMessage.operation` is safe only because dispatch already routed on that same field, so it
391
392
  cannot disagree with the operation running. Never add another.
392
393
  2. A **job** re-parses its SQL from the nested `search_operation` in a _different_ async context —
393
- `executeJob` persists the request and hands off to the job runner, and `jobProcess.ts` re-enters
394
+ `executeJob` persists the request and hands off to the job runner, and `jobs/jobProcess.ts` re-enters
394
395
  from the `hdb_job` record. So a store established around the originating request cannot reach
395
396
  it, and the re-parse would be judged as `sql` rather than as the job's own operation.
396
397
 
@@ -485,7 +486,7 @@ on `scope.options.on('change', ...)`. Three invariants to preserve:
485
486
  response. CI first caught this on the uWS shard (the integration suite's only unauthenticated
486
487
  404 case landed there); the same bug existed unnoticed on Bun's parallel `status === -1`
487
488
  branches (`getBunHTTPServer`'s bare-404 return and `bunDelegateToNodeServer`'s two `Response`s)
488
- and is fixed alongside it in `harper-1568-fix2`.
489
+ and is fixed alongside it in #1568.
489
490
 
490
491
  **Why the operations API doesn't get these headers in normal mode**: ops requests _do_ flow
491
492
  through the Harper-native `requestHandler` (`httpServer()` calls `getServer()` for every
@@ -525,7 +526,7 @@ without asking to close at all, since 1.0 persistence needs both an explicit `ke
525
526
  to read to — so a 1.0 response that got no `Content-Length` is close-delimited, the same line Node
526
527
  draws (Node closes it at ~7ms; Bun never does). An explicit `close` token wins over `keep-alive` on
527
528
  both versions. A 1.0 `keep-alive` request whose response _did_ get a
528
- `Content-Length` (`body.size` on a blob, `server/http.ts:698-709`) is left open, which is again what
529
+ `Content-Length` (`body.size` on a blob — the `body instanceof Blob` branch of `getHTTPServer`'s request handler) is left open, which is again what
529
530
  Node does and what Bun then handles correctly.
530
531
 
531
532
  `pipeBodyToResponse()` therefore ends `request.socket` itself for those shapes
@@ -571,6 +572,35 @@ matching Node's upgrade-then-authorize order). No core component registers custo
571
572
  middleware; `onUpgrade()`/`installUwsWsHandler()` warn when one is registered for a uWS-served
572
573
  port so the gap is visible instead of silent.
573
574
 
575
+ ## A per-thread UDS mirror is bound at a temp name and renamed over its published path (`server/threads/threadServer.js`)
576
+
577
+ libuv unlinks a pipe server's bound path when the handle closes (`uv__pipe_close` → `unlink`), with
578
+ no check of who owns the path now. On Linux `restartWorkers()` pre-starts the replacement worker
579
+ while the outgoing one still runs (#1417), and both publish the same `<worker>-<port>.sock`. Bound
580
+ directly at that path, the replacement rebinds it and the outgoing worker's `closeServers()` then
581
+ deletes the replacement's socket — Harper's own `cleanupUdsFiles()` is inode-ownership-aware
582
+ (#2035) and skips it, but libuv's unlink ran after that and removed every mirror on every rolling
583
+ restart (#2961). So `listenOnDomainSocket()` binds a mirror (`server.isPerThreadSocket`) at
584
+ `.<threadId>.<seq>` in the sockets directory and `renameSync()`s it over the published path:
585
+ `rename()` keeps the listening inode, so the close-time unlink only ever targets a temp name that
586
+ no longer exists, and the published path is never absent between an unlink and a bind.
587
+
588
+ - `recordUdsBindSuccess(publishedPath)` runs after the rename, so the identity it records is the
589
+ inode that is listening; `cleanupUdsFiles()`'s ownership guard is unchanged and is now the only
590
+ thing that removes a mirror's published file. The regression and the shutdown sequence are
591
+ covered by `unitTests/server/threads/threadServerListenOnPorts.test.js`; the real rolling restart
592
+ by `integrationTests/server/uds-mirror-overlapping-restart.test.ts`.
593
+ - The temp name is unique per process (`threadId` never repeats; `seq` is per thread); both it and
594
+ the published path are checked with `isDomainSocketPathTooLong`, and an overlong one takes the
595
+ existing fail-soft branch rather than falling back to a direct bind; it never matches a proxy's
596
+ `*-<port>.yaml`/`.sock` discovery; a leftover from a crash is swept by `cleanupSocketsDirectory()`
597
+ before any worker binds.
598
+ - The operations API's primary domain socket keeps the direct bind on purpose: `bin/cliOperations.ts`
599
+ reads that file's presence as "Harper is running", so its unlink on close is load-bearing.
600
+ - The uWS mirror (`HARPER_UWS_UDS`) keeps its direct bind: measured on uWebSockets.js 20.68.0,
601
+ `app.close()` never unlinks a `listen_unix` path, so it is not exposed. Bun restarts are
602
+ non-overlapping, so the Bun mirror is not exposed either.
603
+
574
604
  ## A worker that misses an ITC ack gets its OS thread state logged (`server/threads/manageThreads.js`)
575
605
 
576
606
  `broadcastWithAcknowledgement` already times out (30 s) on a worker whose port stays open but never acks, and that shape is almost always a blocked event loop — a native lock, a runaway synchronous call — which nothing inside the worker can report (harper-pro#788: a restarted node's single http worker went byte-silent while main kept serving `cluster_status`, and the app log only said "not acknowledged by worker thread(s) 2"). So each worker posts its Linux thread id (`readlink /proc/thread-self`) to main once at startup, before anything else runs on it, and the timeout branch reads that thread's kernel state from `/proc/self/task/<tid>`: state, `wchan`, the syscall number (the first token only — the rest of that file is argument registers and stack/instruction pointers), CPU ticks, and context-switch counts, plus two cross-platform signals main already has, `worker.performance.eventLoopUtilization()` and the age of the last 1 s resource report. It samples again a second later and logs the deltas: no CPU ticks, no context switches and `event loop active +1000ms` is "parked on a lock"; ticks climbing with state `R` is "spinning". It is deliberately main-thread-only and best-effort: `workers` and the tid live on the main thread's `Worker` objects, every `/proc` field is reported individually (a hardened container may deny `wchan`/`syscall` while `stat` stays readable), a follow-up sample whose `starttime` differs from the first is discarded (the tid may have been recycled), one diagnostic runs per worker with a 30 s cooldown so concurrent timeouts on the same worker don't multiply reads, and nothing here runs when acks arrive on time. It does not name the lock owner; that still needs a native stack from the next occurrence.
@@ -599,7 +629,7 @@ The SQL and job paths are additive rather than exclusive: `verifyPermsAST` valid
599
629
 
600
630
  ## `withNodeAdapter()`'s response is the body `PassThrough` it resolves with (`server/serverHelpers/NodeAdapterResponse.ts`)
601
631
 
602
- `Request.withNodeAdapter(handler)` gives third-party Node middleware an `IncomingMessage`/`ServerResponse` pair and resolves `{ status, headers, body }` once headers are committed. The response is `NodeAdapterResponse extends PassThrough`, and that same stream is the resolved `body`: `write()`'s return value, `'drain'`, `'finish'`, `'close'`, `writableEnded`/`writableFinished` and destroy propagation are Node's own rather than events forwarded from a second stream, which is what `Readable.pipe`, `compression`'s buffered `res.on('drain')` and Next.js's response writer depend on past the high-water mark (#2527). Invariants that middleware exercises and the unit test `unitTests/server/serverHelpers/nodeAdapterMiddleware.test.js` pins against the real `compression` (1.8 and the 1.7.4 Next.js vendors), `send`, `on-finished` and `on-headers`:
632
+ `Request.withNodeAdapter(handler)` gives third-party Node middleware an `IncomingMessage`/`ServerResponse` pair and resolves `{ status, headers, body }` once headers are committed. The response is `NodeAdapterResponse extends PassThrough`, and that same stream is the resolved `body`: `write()`'s return value, `'drain'`, `'finish'`, `'close'`, `writableEnded`/`writableFinished` and destroy propagation are Node's own rather than events forwarded from a second stream, which is what `Readable.pipe`, `compression`'s buffered `res.on('drain')` and Next.js's response writer depend on past the high-water mark (#2527). Invariants that middleware exercises and the unit test `unitTests/server/serverHelpers/nodeAdapterMiddleware.test.js` pins against the real `compression` 1.8 (its `compression-1.7` arm, meant for the 1.7.4 that Next.js vendors, has resolved to 1.8.2 since #2877, so 1.7.4 is currently untested), `send`, `on-finished` and `on-headers`:
603
633
 
604
634
  - **Headers commit exactly once, through `this.writeHead`.** `write()`, `end()`, `flushHeaders()` and `_implicitHeader()` all reach `this.writeHead(this.statusCode)` by property lookup, so a `writeHead` that `on-headers` replaced on the instance runs its listeners (the ones that set `Content-Encoding` and remove `Content-Length`) before the promise resolves. After commit, `setHeader`/`appendHeader`/`removeHeader` and a second `writeHead` throw `ERR_HTTP_HEADERS_SENT` as Node's do; `_header` (which `compression` ≤ 1.7 tests instead of `headersSent`) and `finished` (which `on-finished` tests) derive from that state.
605
635
  - **The adapter owns the `'error'` listener.** A `destroy(err)` right after `writeHead()` emits before the awaiting caller can attach one; the error stays in the stream's `errored` state for `pipeline()`, `finished()` or async iteration. Client disconnect (`Request.signal`) destroys the response without an error after headers (a plain premature close, which `pipeBodyToResponse` treats as routine) and rejects the promise with the abort reason before them; a handler that throws or rejects before ending the response destroys it, and one that fails after `end()` is logged at warn.
@@ -680,3 +710,7 @@ A response stream that fails after its first byte has no status left to report t
680
710
  Node gets this from `stream.pipeline` in `pipeBodyToResponse`. uWS has no Writable to pipe into, so `streamResponse` wires the events itself and calls `res.close()`, which also runs `onAborted` and aborts the request signal. It also checks `source.destroyed`: the uWS handler resolves asynchronously, so a source can be destroyed, and its `'close'` already emitted, before the sink listens for it — the response would stay open forever. The `'error'` listener goes on before that check, because `destroy(error)` emits on a later tick and an `'error'` with no listener is thrown. Under Bun the main port is a `node:http` server and takes the `pipeBodyToResponse` path; the `Bun.serve` fetch handler buffers a stream body before it responds, so a failed source there is an ordinary error response.
681
711
 
682
712
  This is the transport's fallback, not the error contract. SSE and NDJSON GETs hold their first step so an immediate failure is still an HTTP status, and they report later failures in-band ([Streaming startup errors](#streaming-startup-errors)). Every other format commits when the handler returns the stream, so a failure before its first chunk aborts the connection too. Pinned by `unitTests/server/serverHelpers/uwsServer.test.js` ("a response stream that does not end cleanly") and, end to end on every runtime leg, by `integrationTests/server/response-compression.test.ts`.
713
+
714
+ ## A coarse uWS 413 drains a closing upload before ending the response
715
+
716
+ uWS force-closes a completed `Connection: close` response while an upload is still arriving, which can lose its status to EPIPE. `uwsServer.ts` flushes a non-empty coarse-limit 413 chunk before discarding bytes until body end or a one-second deadline; that absolute resource bound guarantees early status, not complete framing for a sender that ignores it. Destroying a paused Readable does not clear its queue, so teardown removes data listeners and reads out queued bytes in bounded chunks before retaining the response. Native abort disarms the finisher independently of handler cancellation. "uWS oversized TCP uploads" in `unitTests/server/serverHelpers/uwsServer.test.js` pins these paths and unchanged immediate keep-alive rejection. Other rejection sites, including downstream responses from `contentTypes.ts`'s default 10 MB limit, still end immediately and remain outside this coarse-cap correction.
package/server/http.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { currentThreadId } from '@harperfast/rocksdb-js';
7
7
  import { Scope } from '../components/Scope.ts';
8
8
  import { Socket } from 'node:net';
9
+ import { setMaxListeners } from 'node:events';
9
10
  import harperLogger from '../utility/logging/harper_logger.ts';
10
11
  import { parentPort } from 'node:worker_threads';
11
12
  import * as env from '../utility/environment/environmentManager.ts';
@@ -1769,12 +1770,21 @@ function onWebSocket(listener: (ws: WebSocket) => void, options: OnWebSocketOpti
1769
1770
  if (options.maxPayload != null) cfg.wsMaxPayload = options.maxPayload;
1770
1771
  cfg.wsHandler = (ws: any, upgrade: any) => {
1771
1772
  try {
1773
+ // UwsRequest.signal falls back to a controller nothing can fire. Wire a real one, the
1774
+ // same way uwsServer.ts's HTTP path wires res.onAborted.
1775
+ const ac = new AbortController();
1776
+ // One controller per connection, but one abort listener per in-flight message-handling
1777
+ // transaction (resources/transaction.ts) — a busy connection can hold more than
1778
+ // EventEmitter's default 10-listener warning threshold on perfectly normal traffic.
1779
+ setMaxListeners(0, ac.signal);
1780
+ ws.once('close', () => ac.abort());
1772
1781
  const request: any = new UwsRequest({
1773
1782
  method: 'GET',
1774
1783
  url: upgrade.url,
1775
1784
  headers: upgrade.headers,
1776
1785
  secure,
1777
1786
  ip: upgrade.ip,
1787
+ signal: ac.signal,
1778
1788
  });
1779
1789
  request.isWebSocket = true;
1780
1790
  const chainCompletion = httpChain[port](request);
@@ -1,6 +1,7 @@
1
1
  import type { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerResponse } from 'node:http';
2
2
  import type { Socket } from 'node:net';
3
3
  import { TLSSocket } from 'node:tls';
4
+ import { setMaxListeners } from 'node:events';
4
5
  import { Readable } from 'node:stream';
5
6
  import { Headers as ResponseHeaders } from './Headers.ts';
6
7
  import { NodeAdapterResponse, type AdaptedResponse } from './NodeAdapterResponse.ts';
@@ -72,6 +73,7 @@ export class Request {
72
73
  this.url = url;
73
74
  this.headers = new RequestHeaders(nodeRequest.headers);
74
75
  this.__harperRequestUpgraded = false;
76
+ setMaxListeners(0, this.#abortController.signal);
75
77
  // Abort the request's signal on premature client disconnect. nodeResponse 'close'
76
78
  // also fires on clean completion; the writableFinished guard restricts to disconnect.
77
79
  if (typeof nodeResponse?.on === 'function') {
@@ -399,6 +401,7 @@ export class UwsRequest {
399
401
  this.#ip = source.ip;
400
402
  this.#body = source.body;
401
403
  this.#signal = source.signal;
404
+ if (this.#signal) setMaxListeners(0, this.#signal);
402
405
  this.__harperRequestUpgraded = false;
403
406
  }
404
407
  get absoluteURL() {
@@ -466,10 +469,15 @@ export class UwsRequest {
466
469
  return this.#signal?.aborted ?? false;
467
470
  }
468
471
  get signal(): AbortSignal {
469
- return this.#signal ?? new AbortController().signal;
472
+ if (!this.#signal) {
473
+ this.#signal = new AbortController().signal;
474
+ setMaxListeners(0, this.#signal);
475
+ }
476
+ return this.#signal;
470
477
  }
471
478
  _abort(): void {
472
- // Abort is driven by the uWS res.onAborted handler wired into the provided signal.
479
+ // Abort is driven externally, wired into the provided signal (uWS res.onAborted for plain HTTP,
480
+ // the ws adapter's 'close' event for WebSocket upgrades — see server/http.ts's wsHandler).
473
481
  }
474
482
  get nodeRequest() {
475
483
  return null;
@@ -216,11 +216,11 @@ mediaTypes.set('application/x-www-form-urlencoded', {
216
216
  const stringData = Buffer.isBuffer(data) ? data.toString('utf8') : data;
217
217
  const object: Record<string, string | string[]> = {};
218
218
  for (const [key, value] of new URLSearchParams(stringData)) {
219
- if (object.hasOwnProperty(key)) {
219
+ if (Object.hasOwn(object, key)) {
220
220
  // in case there are multiple query params with the same name, convert them to an array
221
221
  const last = object[key];
222
222
  if (Array.isArray(last)) last.push(value);
223
- else object.key = [last, value];
223
+ else object[key] = [last, value];
224
224
  } else object[key] = value;
225
225
  }
226
226
  return object;
@@ -228,7 +228,9 @@ mediaTypes.set('application/x-www-form-urlencoded', {
228
228
  serialize(data) {
229
229
  const usp = new URLSearchParams();
230
230
  for (const key in data) {
231
- usp.set(key, data);
231
+ const value = data[key];
232
+ if (Array.isArray(value)) for (const item of value) usp.append(key, item);
233
+ else usp.set(key, value);
232
234
  }
233
235
  return usp.toString();
234
236
  },
@@ -138,10 +138,15 @@ export async function createUwsServer(options: UwsServerOptions): Promise<{ app:
138
138
  const ip = port != null ? normalizeAddress(Buffer.from(res.getRemoteAddressAsText()).toString()) : undefined;
139
139
 
140
140
  const ac = new AbortController();
141
- res.onAborted(() => ac.abort());
142
- // Once a response has been committed (handler result, error, or a 413), writing again to the
143
- // same uWS response is invalid and aborts the process — so every write site guards on this in
144
- // addition to ac.signal.aborted (which only covers a client-side teardown, not our own writes).
141
+ let bodyDrainTimeout: ReturnType<typeof setTimeout> | undefined;
142
+ let finishRejectedResponse: (() => void) | undefined;
143
+ res.onAborted(() => {
144
+ clearTimeout(bodyDrainTimeout);
145
+ finishRejectedResponse = undefined;
146
+ ac.abort();
147
+ });
148
+ // A second handler response is invalid in uWS. The deferred 413 end instead uses its
149
+ // own finisher, which native onAborted invalidates independently of handler cancellation.
145
150
  let responseCompleted = false;
146
151
 
147
152
  const dispatch = (body?: UwsRequestBody) => {
@@ -183,19 +188,41 @@ export async function createUwsServer(options: UwsServerOptions): Promise<{ app:
183
188
  if (ended || body.destroyed) return;
184
189
  ended = true;
185
190
  body.destroy(error && body.listenerCount('error') > 0 ? error : undefined);
191
+ body.removeAllListeners('data');
192
+ while (body.readableLength) body.read(Math.min(body.readableLength, body.readableHighWaterMark));
186
193
  };
187
194
  ac.signal.addEventListener('abort', () => tearDown(new Error('request aborted')), { once: true });
188
195
  res.onData((chunk: ArrayBuffer, isLast: boolean) => {
189
- if (ended || body.destroyed) return;
196
+ if (ended || body.destroyed) {
197
+ if (isLast) finishRejectedResponse?.();
198
+ return;
199
+ }
190
200
  total += chunk.byteLength;
191
201
  if (total > maxBodyBytes) {
202
+ const error = new ClientError(`Request body exceeds ${maxBodyBytes} bytes`, 413);
192
203
  // Only send 413 if nothing has been written yet: the handler may have already responded
193
204
  // (or streamed) without consuming the body, and writing to that completed response aborts.
194
205
  if (!ac.signal.aborted && !responseCompleted) {
195
206
  responseCompleted = true;
196
- res.cork(() => res.writeStatus('413 Payload Too Large').end());
207
+ const connection = Array.isArray(headers.connection) ? headers.connection[0] : headers.connection;
208
+ res.cork(() => {
209
+ res.writeStatus('413 Payload Too Large');
210
+ if (!isLast && typeof connection === 'string' && connection.toLowerCase() === 'close') {
211
+ res.writeHeader('Connection', 'close');
212
+ res.writeHeader('content-type', 'text/plain');
213
+ res.write(errorToString(error));
214
+ finishRejectedResponse = () => {
215
+ if (!finishRejectedResponse) return;
216
+ finishRejectedResponse = undefined;
217
+ clearTimeout(bodyDrainTimeout);
218
+ res.cork(() => res.end());
219
+ };
220
+ bodyDrainTimeout = setTimeout(finishRejectedResponse, 1_000);
221
+ bodyDrainTimeout.unref();
222
+ } else res.end();
223
+ });
197
224
  }
198
- tearDown(new ClientError(`Request body exceeds ${maxBodyBytes} bytes`, 413));
225
+ tearDown(error);
199
226
  ac.abort();
200
227
  return;
201
228
  }
@@ -158,7 +158,16 @@ function notifyJobCleanupComplete() {
158
158
 
159
159
  const listenersByType = new Map();
160
160
  const messagesQueuedByType = new Map();
161
-
161
+ const { promise: whenThreadsStarted, resolve: threadsHaveStarted } = Promise.withResolvers();
162
+ const initialRestartNumber = workerData?.restartNumber || 1;
163
+ // Identifies this process incarnation, where the PID cannot: a container reuses PID 1. Minted once
164
+ // on the main thread and carried to workers, so live siblings agree on it — one derived per thread
165
+ // would not. `undefined` on a worker started without it; consumers must fall back, not treat that
166
+ // as a mismatch.
167
+ const processIncarnation = workerData ? workerData.processIncarnation : randomBytes(8).toString('hex');
168
+
169
+ // Every value in this literal is a bare identifier: Node's CommonJS export scan, which supplies the
170
+ // named bindings an ES module import can use, stops reading the literal at the first value that is not.
162
171
  module.exports = {
163
172
  startWorker,
164
173
  restartWorkers,
@@ -206,17 +215,13 @@ module.exports = {
206
215
  terminateProcessGroupsForThread,
207
216
  isProcessGroupAlive,
208
217
  isThreadRunning,
209
- restartNumber: workerData?.restartNumber || 1,
210
- // Identifies this process incarnation, where the PID cannot: a container reuses PID 1. Minted once
211
- // on the main thread and carried to workers, so live siblings agree on it — one derived per thread
212
- // would not. `undefined` on a worker started without it; consumers must fall back, not treat that
213
- // as a mismatch.
214
- processIncarnation: workerData ? workerData.processIncarnation : randomBytes(8).toString('hex'),
215
- // Assigned further down once defined. Listed here because TypeScript 7 only treats keys of this
216
- // literal as exports of the module, not later `module.exports.x =` assignments.
218
+ restartNumber: initialRestartNumber,
219
+ processIncarnation,
220
+ whenThreadsStarted,
221
+ threadsHaveStarted,
222
+ // Assigned further down once defined. TypeScript 7 only treats keys of this literal as exports and
223
+ // types them from it, so only a value that cannot be built before the literal belongs here.
217
224
  sendToThread: undefined,
218
- whenThreadsStarted: undefined,
219
- threadsHaveStarted: undefined,
220
225
  getThreadInfo: undefined,
221
226
  getRunningIsolatedApplications: undefined,
222
227
  watchDir: undefined,
@@ -241,9 +246,6 @@ connectedPorts.sendToThread = function (threadId, message) {
241
246
  // Direct thread-to-thread send, so a worker can reach a sibling (e.g. the record lock owner worker)
242
247
  // without a hop through main. Returns false when no port for the thread is connected.
243
248
  module.exports.sendToThread = connectedPorts.sendToThread;
244
- module.exports.whenThreadsStarted = new Promise((resolve) => {
245
- module.exports.threadsHaveStarted = resolve;
246
- });
247
249
 
248
250
  // make sure this is set on all threads, including the main thread (this is no-op
249
251
  // if it was already with the execArgv below)
@@ -2,8 +2,8 @@
2
2
 
3
3
  const { isMainThread, parentPort, threadId, workerData } = require('node:worker_threads');
4
4
  const { createServer: createSocketServer } = require('node:net');
5
- const { unlinkSync, existsSync, mkdirSync } = require('fs');
6
- const { join } = require('path');
5
+ const { unlinkSync, existsSync, mkdirSync, renameSync } = require('node:fs');
6
+ const { join, dirname } = require('node:path');
7
7
  let componentsLoadedResolve;
8
8
  exports.whenComponentsLoaded = new Promise((resolve) => {
9
9
  componentsLoadedResolve = resolve;
@@ -304,22 +304,35 @@ function startServers() {
304
304
  * An overlong path is the only condition that can skip a domain-socket listener. Check before
305
305
  * listen() because supported Node versions differ: some reject the path while others truncate and
306
306
  * bind it somewhere clients cannot reach using the configured path.
307
+ *
308
+ * A per-thread mirror binds at a temp name and is renamed over its published path: libuv unlinks a
309
+ * pipe server's bound path on close whoever owns it by then (server/DESIGN.md).
307
310
  */
308
311
  function listenOnDomainSocket(port, server) {
309
- if (isDomainSocketPathTooLong(port)) {
312
+ const bindPath = server.isPerThreadSocket && !isWindows ? mirrorBindPath(port) : port;
313
+ const overlong = [port, bindPath].find((path) => isDomainSocketPathTooLong(path));
314
+ if (overlong) {
310
315
  httpComponent.markUdsBindFailed(port);
311
316
  harperLogger.error(
312
- `Not binding domain socket listener${server.name ? ` for '${server.name}'` : ''} at ${port}: the ${Buffer.byteLength(port)}-byte path exceeds the platform limit of ${getDomainSocketPathMaxBytes()} bytes. Continuing without this domain socket.`
317
+ `Not binding domain socket listener${server.name ? ` for '${server.name}'` : ''} at ${port}: the ${Buffer.byteLength(overlong)}-byte path ${overlong} exceeds the platform limit of ${getDomainSocketPathMaxBytes()} bytes. Continuing without this domain socket.`
313
318
  );
314
319
  return Promise.resolve({ port, failed: true });
315
320
  }
316
- if (existsSync(port)) unlinkSync(port);
321
+ if (bindPath === port && existsSync(port)) unlinkSync(port);
317
322
  return new Promise((resolve, reject) => {
318
323
  function onError(error) {
319
324
  reject(error);
320
325
  }
321
326
  function onListening() {
322
327
  server.removeListener('error', onError);
328
+ if (bindPath !== port) {
329
+ try {
330
+ renameSync(bindPath, port);
331
+ } catch (error) {
332
+ server.close(); // libuv unlinks the temp name, which still exists
333
+ return reject(error);
334
+ }
335
+ }
323
336
  // Record ownership of the inode we just bound, so cleanupUdsFiles()/markUdsBindFailed()
324
337
  // (see http.ts) can tell this worker's own file apart from a replacement's later rebind
325
338
  // at the same path (see registerUdsCleanupPaths). A no-op for domain sockets that aren't
@@ -330,7 +343,7 @@ function listenOnDomainSocket(port, server) {
330
343
  }
331
344
  try {
332
345
  server.once('error', onError);
333
- server.listen({ path: port }, onListening);
346
+ server.listen({ path: bindPath }, onListening);
334
347
  } catch (error) {
335
348
  server.removeListener('error', onError);
336
349
  reject(error);
@@ -338,6 +351,11 @@ function listenOnDomainSocket(port, server) {
338
351
  });
339
352
  }
340
353
 
354
+ let mirrorBindSequence = 0;
355
+ function mirrorBindPath(socketPath) {
356
+ return join(dirname(socketPath), `.${threadId}.${++mirrorBindSequence}`);
357
+ }
358
+
341
359
  let listening;
342
360
  function listenOnPorts() {
343
361
  if (isBun) return listenOnPortsBun();
@@ -1,4 +1,4 @@
1
- import{i as e,t}from"./rolldown-runtime-Dd_uD5pT.js";import{C as n,S as r,_ as i,a,b as o,c as s,f as c,g as l,h as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,v as y,x as b,y as x}from"./vendor-core-6LhfJlRC.js";import{n as S}from"./cn-6T8VranJ.js";import{a as ee}from"./vendor-datadog-ozKf-pts.js";import{r as te}from"./vendor-react-BOliS0BL.js";import{B as ne,R as re,Y as ie,k as ae}from"./vendor-tanstack-Bntfka-M.js";import{n as oe}from"./setSessionStorage-B0bf71m4.js";import{t as se}from"./button-DWwfgGWI.js";import{Rt as ce}from"./vendor-ui-9EYYDgoN.js";import{t as C}from"./createLucideIcon-fO5dL7D5.js";import{n as le,r as ue,t as de}from"./chevron-up-CFxbMGv2.js";import{c as fe,f as pe,h as me,i as he,l as ge,m as _e,p as ve,r as ye,t as be,u as xe}from"./setComponentFile-DpjrRqeG.js";import{t as Se}from"./localStorageKeys-etzFY_xV.js";import{Ar as Ce,At as we,Bn as Te,Br as Ee,Er as De,Fr as Oe,H as ke,Hr as Ae,Ln as je,Nt as Me,Pr as Ne,Tn as Pe,Ur as Fe,V as Ie,Z as Le,c as Re,d as ze,f as Be,ft as Ve,gt as He,ir as Ue,jt as We,kt as Ge,m as Ke,nn as qe,qr as Je,ut as Ye,vr as Xe,wr as Ze,xr as Qe}from"./index-BuqFjN5K.js";import{t as $e}from"./useEntityRestURL-TNrh77-s.js";import{n as et,t as tt}from"./FloatingChat-CEnWhHeR.js";import{n as nt}from"./getAnalytics-C0FHjtdQ.js";var rt={name:`between-horizontal-start`,size:24,node:[[`rect`,{width:`13`,height:`7`,x:`8`,y:`3`,rx:`1`,key:`pkso9a`}],[`path`,{d:`m2 9 3 3-3 3`,key:`1agib5`}],[`rect`,{width:`13`,height:`7`,x:`8`,y:`14`,rx:`1`,key:`1q5fc1`}]],aliases:[`between-horizonal-start`]};rt.node;var it=C(rt),at={name:`book`,size:24,node:[[`path`,{d:`M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20`,key:`k3hazp`}]]};at.node;var ot=C(at),st={name:`chart-area`,size:24,node:[[`path`,{d:`M3 3v16a2 2 0 0 0 2 2h16`,key:`c24i48`}],[`path`,{d:`M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z`,key:`q0gr47`}]],aliases:[`area-chart`]};st.node;var ct=C(st),lt={name:`circle-x`,size:24,node:[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]],aliases:[`x-circle`]};lt.node;var ut=C(lt),dt={name:`file-pen`,size:24,node:[[`path`,{d:`M12.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v9.34`,key:`o6klzx`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M10.378 12.622a1 1 0 0 1 3 3.003L8.36 20.637a2 2 0 0 1-.854.506l-2.867.837a.5.5 0 0 1-.62-.62l.836-2.869a2 2 0 0 1 .506-.853z`,key:`zhnas1`}]],aliases:[`file-edit`]};dt.node;var ft=C(dt),pt={name:`logs`,size:24,node:[[`path`,{d:`M3 5h1`,key:`1mv5vm`}],[`path`,{d:`M3 12h1`,key:`lp3yf2`}],[`path`,{d:`M3 19h1`,key:`w6f3n9`}],[`path`,{d:`M8 5h1`,key:`1nxr5w`}],[`path`,{d:`M8 12h1`,key:`1con00`}],[`path`,{d:`M8 19h1`,key:`k7p10e`}],[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}]]};pt.node;var mt=C(pt),ht={name:`message-square-heart`,size:24,node:[[`path`,{d:`M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z`,key:`18887p`}],[`path`,{d:`M7.5 9.5c0 .687.265 1.383.697 1.844l3.009 3.264a1.14 1.14 0 0 0 .407.314 1 1 0 0 0 .783-.004 1.14 1.14 0 0 0 .398-.31l3.008-3.264A2.77 2.77 0 0 0 16.5 9.5 2.5 2.5 0 0 0 12 8a2.5 2.5 0 0 0-4.5 1.5`,key:`1faxuh`}]]};ht.node;var gt=C(ht),_t={name:`send`,size:24,node:[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]};_t.node;var vt=C(_t),yt={name:`user`,size:24,node:[[`path`,{d:`M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2`,key:`975kel`}],[`circle`,{cx:`12`,cy:`7`,r:`4`,key:`17ys0d`}]]};yt.node;var bt=C(yt),xt={name:`wrench`,size:24,node:[[`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z`,key:`1ngwbx`}]]};xt.node;var St=C(xt);async function Ct(){await S.delete(`/Chat/Messages/`)}var w=e(ee(),1),T=te();function wt({setMessages:e}){let[t,n]=(0,w.useState)(!1),r=(0,w.useCallback)(async()=>{if(!t){n(!0);try{await Ct(),e([])}catch(e){console.error(`Failed to clear chat:`,e)}finally{n(!1)}}},[t,e]);return(0,T.jsxs)(`button`,{type:`button`,className:`clear-chat-button gap-1`,onClick:r,disabled:t,title:`Clear chat`,children:[t?(0,T.jsx)(Ne,{className:`animate-spin`,size:18}):(0,T.jsx)(Ze,{size:18}),`Clear`]})}async function Tt(){let{data:e}=await S.get(`/Chat/Messages/`);return e}var Et=`vercel.ai.error`,Dt=Symbol.for(Et),Ot,kt,E=class e extends (kt=Error,Ot=Dt,kt){constructor({name:e,message:t,cause:n}){super(t),this[Ot]=!0,this.name=e,this.cause=n}static isInstance(t){return e.hasMarker(t,Et)}static hasMarker(e,t){let n=Symbol.for(t);return typeof e==`object`&&!!e&&n in e&&typeof e[n]==`boolean`&&e[n]===!0}},At=`AI_APICallError`,jt=`vercel.ai.error.${At}`,Mt=Symbol.for(jt),Nt,Pt,Ft=class extends (Pt=E,Nt=Mt,Pt){constructor({message:e,url:t,requestBodyValues:n,statusCode:r,responseHeaders:i,responseBody:a,cause:o,isRetryable:s=r!=null&&(r===408||r===409||r===429||r>=500),data:c}){super({name:At,message:e,cause:o}),this[Nt]=!0,this.url=t,this.requestBodyValues=n,this.statusCode=r,this.responseHeaders=i,this.responseBody=a,this.isRetryable=s,this.data=c}static isInstance(e){return E.hasMarker(e,jt)}},It=`AI_EmptyResponseBodyError`,Lt=`vercel.ai.error.${It}`,Rt=Symbol.for(Lt),zt,Bt,Vt=class extends (Bt=E,zt=Rt,Bt){constructor({message:e=`Empty response body`}={}){super({name:It,message:e}),this[zt]=!0}static isInstance(e){return E.hasMarker(e,Lt)}};function Ht(e){return e==null?`unknown error`:typeof e==`string`?e:e instanceof Error?e.toString():JSON.stringify(e)}var Ut=`AI_InvalidArgumentError`,Wt=`vercel.ai.error.${Ut}`,Gt=Symbol.for(Wt),Kt,qt,Jt=class extends (qt=E,Kt=Gt,qt){constructor({message:e,cause:t,argument:n}){super({name:Ut,message:e,cause:t}),this[Kt]=!0,this.argument=n}static isInstance(e){return E.hasMarker(e,Wt)}},Yt=`AI_JSONParseError`,Xt=`vercel.ai.error.${Yt}`,Zt=Symbol.for(Xt),Qt,$t,en=class extends ($t=E,Qt=Zt,$t){constructor({text:e,cause:t}){super({name:Yt,message:`JSON parsing failed: Text: ${e}.
1
+ import{i as e,t}from"./rolldown-runtime-Dd_uD5pT.js";import{C as n,S as r,_ as i,a,b as o,c as s,f as c,g as l,h as u,l as d,m as f,n as p,o as m,p as h,r as g,s as _,t as v,v as y,x as b,y as x}from"./vendor-core-6LhfJlRC.js";import{n as S}from"./cn-6T8VranJ.js";import{a as ee}from"./vendor-datadog-ozKf-pts.js";import{r as te}from"./vendor-react-BOliS0BL.js";import{B as ne,R as re,Y as ie,k as ae}from"./vendor-tanstack-Bntfka-M.js";import{n as oe}from"./setSessionStorage-B0bf71m4.js";import{t as se}from"./button-DWwfgGWI.js";import{Rt as ce}from"./vendor-ui-9EYYDgoN.js";import{t as C}from"./createLucideIcon-fO5dL7D5.js";import{n as le,r as ue,t as de}from"./chevron-up-CFxbMGv2.js";import{c as fe,f as pe,h as me,i as he,l as ge,m as _e,p as ve,r as ye,t as be,u as xe}from"./setComponentFile-BkWx8AN3.js";import{t as Se}from"./localStorageKeys-etzFY_xV.js";import{Ar as Ce,At as we,Bn as Te,Br as Ee,Er as De,Fr as Oe,H as ke,Hr as Ae,Ln as je,Nt as Me,Pr as Ne,Tn as Pe,Ur as Fe,V as Ie,Z as Le,c as Re,d as ze,f as Be,ft as Ve,gt as He,ir as Ue,jt as We,kt as Ge,m as Ke,nn as qe,qr as Je,ut as Ye,vr as Xe,wr as Ze,xr as Qe}from"./index-FN4KC9va.js";import{t as $e}from"./useEntityRestURL-DNiz3aZ0.js";import{n as et,t as tt}from"./FloatingChat-kWHApEtF.js";import{n as nt}from"./getAnalytics-C0FHjtdQ.js";var rt={name:`between-horizontal-start`,size:24,node:[[`rect`,{width:`13`,height:`7`,x:`8`,y:`3`,rx:`1`,key:`pkso9a`}],[`path`,{d:`m2 9 3 3-3 3`,key:`1agib5`}],[`rect`,{width:`13`,height:`7`,x:`8`,y:`14`,rx:`1`,key:`1q5fc1`}]],aliases:[`between-horizonal-start`]};rt.node;var it=C(rt),at={name:`book`,size:24,node:[[`path`,{d:`M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20`,key:`k3hazp`}]]};at.node;var ot=C(at),st={name:`chart-area`,size:24,node:[[`path`,{d:`M3 3v16a2 2 0 0 0 2 2h16`,key:`c24i48`}],[`path`,{d:`M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z`,key:`q0gr47`}]],aliases:[`area-chart`]};st.node;var ct=C(st),lt={name:`circle-x`,size:24,node:[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]],aliases:[`x-circle`]};lt.node;var ut=C(lt),dt={name:`file-pen`,size:24,node:[[`path`,{d:`M12.659 22H18a2 2 0 0 0 2-2V8a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 14 2H6a2 2 0 0 0-2 2v9.34`,key:`o6klzx`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M10.378 12.622a1 1 0 0 1 3 3.003L8.36 20.637a2 2 0 0 1-.854.506l-2.867.837a.5.5 0 0 1-.62-.62l.836-2.869a2 2 0 0 1 .506-.853z`,key:`zhnas1`}]],aliases:[`file-edit`]};dt.node;var ft=C(dt),pt={name:`logs`,size:24,node:[[`path`,{d:`M3 5h1`,key:`1mv5vm`}],[`path`,{d:`M3 12h1`,key:`lp3yf2`}],[`path`,{d:`M3 19h1`,key:`w6f3n9`}],[`path`,{d:`M8 5h1`,key:`1nxr5w`}],[`path`,{d:`M8 12h1`,key:`1con00`}],[`path`,{d:`M8 19h1`,key:`k7p10e`}],[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}]]};pt.node;var mt=C(pt),ht={name:`message-square-heart`,size:24,node:[[`path`,{d:`M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z`,key:`18887p`}],[`path`,{d:`M7.5 9.5c0 .687.265 1.383.697 1.844l3.009 3.264a1.14 1.14 0 0 0 .407.314 1 1 0 0 0 .783-.004 1.14 1.14 0 0 0 .398-.31l3.008-3.264A2.77 2.77 0 0 0 16.5 9.5 2.5 2.5 0 0 0 12 8a2.5 2.5 0 0 0-4.5 1.5`,key:`1faxuh`}]]};ht.node;var gt=C(ht),_t={name:`send`,size:24,node:[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]};_t.node;var vt=C(_t),yt={name:`user`,size:24,node:[[`path`,{d:`M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2`,key:`975kel`}],[`circle`,{cx:`12`,cy:`7`,r:`4`,key:`17ys0d`}]]};yt.node;var bt=C(yt),xt={name:`wrench`,size:24,node:[[`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z`,key:`1ngwbx`}]]};xt.node;var St=C(xt);async function Ct(){await S.delete(`/Chat/Messages/`)}var w=e(ee(),1),T=te();function wt({setMessages:e}){let[t,n]=(0,w.useState)(!1),r=(0,w.useCallback)(async()=>{if(!t){n(!0);try{await Ct(),e([])}catch(e){console.error(`Failed to clear chat:`,e)}finally{n(!1)}}},[t,e]);return(0,T.jsxs)(`button`,{type:`button`,className:`clear-chat-button gap-1`,onClick:r,disabled:t,title:`Clear chat`,children:[t?(0,T.jsx)(Ne,{className:`animate-spin`,size:18}):(0,T.jsx)(Ze,{size:18}),`Clear`]})}async function Tt(){let{data:e}=await S.get(`/Chat/Messages/`);return e}var Et=`vercel.ai.error`,Dt=Symbol.for(Et),Ot,kt,E=class e extends (kt=Error,Ot=Dt,kt){constructor({name:e,message:t,cause:n}){super(t),this[Ot]=!0,this.name=e,this.cause=n}static isInstance(t){return e.hasMarker(t,Et)}static hasMarker(e,t){let n=Symbol.for(t);return typeof e==`object`&&!!e&&n in e&&typeof e[n]==`boolean`&&e[n]===!0}},At=`AI_APICallError`,jt=`vercel.ai.error.${At}`,Mt=Symbol.for(jt),Nt,Pt,Ft=class extends (Pt=E,Nt=Mt,Pt){constructor({message:e,url:t,requestBodyValues:n,statusCode:r,responseHeaders:i,responseBody:a,cause:o,isRetryable:s=r!=null&&(r===408||r===409||r===429||r>=500),data:c}){super({name:At,message:e,cause:o}),this[Nt]=!0,this.url=t,this.requestBodyValues=n,this.statusCode=r,this.responseHeaders=i,this.responseBody=a,this.isRetryable=s,this.data=c}static isInstance(e){return E.hasMarker(e,jt)}},It=`AI_EmptyResponseBodyError`,Lt=`vercel.ai.error.${It}`,Rt=Symbol.for(Lt),zt,Bt,Vt=class extends (Bt=E,zt=Rt,Bt){constructor({message:e=`Empty response body`}={}){super({name:It,message:e}),this[zt]=!0}static isInstance(e){return E.hasMarker(e,Lt)}};function Ht(e){return e==null?`unknown error`:typeof e==`string`?e:e instanceof Error?e.toString():JSON.stringify(e)}var Ut=`AI_InvalidArgumentError`,Wt=`vercel.ai.error.${Ut}`,Gt=Symbol.for(Wt),Kt,qt,Jt=class extends (qt=E,Kt=Gt,qt){constructor({message:e,cause:t,argument:n}){super({name:Ut,message:e,cause:t}),this[Kt]=!0,this.argument=n}static isInstance(e){return E.hasMarker(e,Wt)}},Yt=`AI_JSONParseError`,Xt=`vercel.ai.error.${Yt}`,Zt=Symbol.for(Xt),Qt,$t,en=class extends ($t=E,Qt=Zt,$t){constructor({text:e,cause:t}){super({name:Yt,message:`JSON parsing failed: Text: ${e}.
2
2
  Error message: ${Ht(t)}`,cause:t}),this[Qt]=!0,this.text=e}static isInstance(e){return E.hasMarker(e,Xt)}},tn=`AI_TypeValidationError`,nn=`vercel.ai.error.${tn}`,rn=Symbol.for(nn),an,on,D=class e extends (on=E,an=rn,on){constructor({value:e,cause:t,context:n}){let r=`Type validation failed`;if(n?.field&&(r+=` for ${n.field}`),n?.entityName||n?.entityId){r+=` (`;let e=[];n.entityName&&e.push(n.entityName),n.entityId&&e.push(`id: "${n.entityId}"`),r+=e.join(`, `),r+=`)`}super({name:tn,message:`${r}: Value: ${JSON.stringify(e)}.
3
3
  Error message: ${Ht(t)}`,cause:t}),this[an]=!0,this.value=e,this.context=n}static isInstance(e){return E.hasMarker(e,nn)}static wrap({value:t,cause:n,context:r}){return e.isInstance(n)&&n.value===t&&n.context?.field===r?.field&&n.context?.entityName===r?.entityName&&n.context?.entityId===r?.entityId?n:new e({value:t,cause:n,context:r})}},sn=`AI_UnsupportedFunctionalityError`,cn=`vercel.ai.error.${sn}`,ln=Symbol.for(cn),un,dn,fn=class extends (dn=E,un=ln,dn){constructor({functionality:e,message:t=`'${e}' functionality not supported.`}){super({name:sn,message:t}),this[un]=!0,this.functionality=e}static isInstance(e){return E.hasMarker(e,cn)}},pn=class extends Error{constructor(e,t){super(e),this.name=`ParseError`,this.type=t.type,this.field=t.field,this.value=t.value,this.line=t.line}},mn=10,hn=13,O=32;function gn(e){}function _n(e){if(typeof e==`function`)throw TypeError("`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?");let{onEvent:t=gn,onError:n=gn,onRetry:r=gn,onComment:i,maxBufferSize:a}=e,o=[],s=0,c=!0,l,u=``,d=0,f,p=!1;function m(e){if(p)throw Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");if(c&&(c=!1,e.charCodeAt(0)===239&&e.charCodeAt(1)===187&&e.charCodeAt(2)===191&&(e=e.slice(3))),o.length===0){let t=g(e);t!==``&&(o.push(t),s=t.length),h();return}if(e.indexOf(`
4
4
  `)===-1&&e.indexOf(`\r`)===-1){o.push(e),s+=e.length,h();return}o.push(e);let t=o.join(``);o.length=0,s=0;let n=g(t);n!==``&&(o.push(n),s=n.length),h()}function h(){a!==void 0&&(s+u.length<=a||(p=!0,o.length=0,s=0,l=void 0,u=``,d=0,f=void 0,n(new pn(`Buffered data exceeded max buffer size of ${a} characters`,{type:`max-buffer-size-exceeded`}))))}function g(e){let n=0;if(e.indexOf(`\r`)===-1){let r=e.indexOf(`