@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.
- package/bin/copyDb.ts +5 -1
- package/bin/deployRenderer.ts +3 -0
- package/components/Application.ts +66 -22
- package/components/DESIGN.md +89 -36
- package/components/componentLoader.ts +2 -0
- package/components/deploymentRecorder.ts +18 -1
- package/components/installFingerprint.ts +123 -0
- package/components/mcp/DESIGN.md +4 -4
- package/components/operations.js +29 -0
- package/config/DESIGN.md +5 -5
- package/dataLayer/DESIGN.md +37 -22
- package/dataLayer/backupRepository.ts +248 -0
- package/dataLayer/restoreMarker.ts +30 -75
- package/dataLayer/rocksdbBackup.ts +253 -110
- package/dist/bin/copyDb.js +3 -0
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deployRenderer.js +3 -0
- package/dist/bin/deployRenderer.js.map +1 -1
- package/dist/components/Application.d.ts +5 -0
- package/dist/components/Application.js +51 -19
- package/dist/components/Application.js.map +1 -1
- package/dist/components/componentLoader.js +2 -0
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deploymentRecorder.js +18 -1
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/installFingerprint.d.ts +39 -0
- package/dist/components/installFingerprint.js +113 -0
- package/dist/components/installFingerprint.js.map +1 -0
- package/dist/components/operations.d.ts +2 -0
- package/dist/components/operations.js +31 -0
- package/dist/components/operations.js.map +1 -1
- package/dist/dataLayer/backupRepository.d.ts +33 -0
- package/dist/dataLayer/backupRepository.js +232 -0
- package/dist/dataLayer/backupRepository.js.map +1 -0
- package/dist/dataLayer/restoreMarker.d.ts +4 -1
- package/dist/dataLayer/restoreMarker.js +29 -68
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.js +221 -112
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +14 -0
- package/dist/resources/DatabaseTransaction.d.ts +140 -11
- package/dist/resources/DatabaseTransaction.js +1055 -464
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/IterableEventQueue.d.ts +2 -1
- package/dist/resources/IterableEventQueue.js +16 -8
- package/dist/resources/IterableEventQueue.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +13 -2
- package/dist/resources/LMDBTransaction.js +219 -93
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/RecordEncoder.js +2 -3
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +5 -6
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +3 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +2 -1
- package/dist/resources/RocksIndexStore.js +3 -0
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/Table.js +116 -12
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -2
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +71 -17
- package/dist/resources/auditStore.js +248 -64
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/branchDatabase.js +2 -0
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databases.js +8 -2
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +6 -2
- package/dist/resources/indexes/fullTextNativeBinding.js +12 -2
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -1
- package/dist/resources/indexes/fullTextQueryIndex.d.ts +4 -0
- package/dist/resources/indexes/fullTextQueryIndex.js +207 -10
- package/dist/resources/indexes/fullTextQueryIndex.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -0
- package/dist/resources/models/Models.js +95 -4
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +8 -0
- package/dist/resources/models/backendRegistry.js +15 -1
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +2 -0
- package/dist/resources/models/bootstrap.js +6 -0
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/calibration.d.ts +88 -0
- package/dist/resources/models/calibration.js +244 -0
- package/dist/resources/models/calibration.js.map +1 -0
- package/dist/resources/models/calibrationStore.d.ts +163 -0
- package/dist/resources/models/calibrationStore.js +986 -0
- package/dist/resources/models/calibrationStore.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +24 -0
- package/dist/resources/models/decisionStore.js +45 -0
- package/dist/resources/models/decisionStore.js.map +1 -1
- package/dist/resources/models/generativeDecision.js +44 -15
- package/dist/resources/models/generativeDecision.js.map +1 -1
- package/dist/resources/models/types.d.ts +7 -0
- package/dist/resources/recordLock.d.ts +8 -5
- package/dist/resources/recordLock.js +6 -4
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/replayLogs.js +8 -1
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/scheduler/engine.d.ts +4 -0
- package/dist/resources/scheduler/engine.js +30 -2
- package/dist/resources/scheduler/engine.js.map +1 -1
- package/dist/resources/search.d.ts +16 -4
- package/dist/resources/search.js +117 -13
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transaction.js +85 -5
- package/dist/resources/transaction.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +8 -0
- package/dist/resources/transactionBroadcast.js +65 -0
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/keys.d.ts +1 -1
- package/dist/security/keys.js +170 -120
- package/dist/security/keys.js.map +1 -1
- package/dist/server/http.js +10 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/serverHelpers/Request.js +11 -2
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +8 -3
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +38 -7
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +3 -3
- package/dist/server/threads/manageThreads.js +15 -13
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +3 -0
- package/dist/server/threads/threadServer.js +24 -6
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +9 -3
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/durableFile.d.ts +36 -0
- package/dist/utility/durableFile.js +106 -0
- package/dist/utility/durableFile.js.map +1 -0
- package/dist/utility/errors/hdbError.d.ts +4 -0
- package/dist/utility/errors/hdbError.js +10 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +1 -0
- package/dist/utility/hdbTerms.js +1 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/validation/configValidator.js +29 -0
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +7 -0
- package/json/systemSchema.json +14 -0
- package/npm-shrinkwrap.json +22 -22
- package/package.json +2 -2
- package/resources/DESIGN.md +57 -272
- package/resources/DatabaseTransaction.ts +708 -87
- package/resources/FULL_TEXT.md +6 -2
- package/resources/IterableEventQueue.ts +14 -7
- package/resources/LMDBTransaction.ts +212 -84
- package/resources/RecordEncoder.ts +2 -3
- package/resources/Resource.ts +5 -6
- package/resources/ResourceInterface.ts +3 -1
- package/resources/RocksIndexStore.ts +5 -0
- package/resources/Table.ts +118 -15
- package/resources/analytics/write.ts +6 -2
- package/resources/audit-retention.md +203 -0
- package/resources/auditStore.ts +306 -84
- package/resources/branchDatabase.ts +2 -0
- package/resources/databases.ts +6 -2
- package/resources/indexes/DESIGN.md +120 -177
- package/resources/indexes/fullTextNativeBinding.ts +22 -3
- package/resources/indexes/fullTextQueryIndex.ts +274 -11
- package/resources/models/DESIGN.md +11 -3
- package/resources/models/Models.ts +127 -4
- package/resources/models/backendRegistry.ts +17 -0
- package/resources/models/bootstrap.ts +9 -1
- package/resources/models/calibration.ts +298 -0
- package/resources/models/calibrationStore.ts +1167 -0
- package/resources/models/decisionStore.ts +57 -0
- package/resources/models/generativeDecision.ts +54 -16
- package/resources/models/types.ts +6 -0
- package/resources/record-locks.md +155 -141
- package/resources/recordLock.ts +12 -6
- package/resources/replayLogs.ts +9 -2
- package/resources/scheduler/DESIGN.md +4 -3
- package/resources/scheduler/engine.ts +35 -2
- package/resources/search.ts +154 -16
- package/resources/transaction.ts +50 -5
- package/resources/transactionBroadcast.ts +69 -0
- package/security/DESIGN.md +9 -30
- package/security/keys.ts +175 -127
- package/server/DESIGN.md +70 -36
- package/server/http.ts +10 -0
- package/server/serverHelpers/Request.ts +10 -2
- package/server/serverHelpers/contentTypes.ts +5 -3
- package/server/serverHelpers/uwsServer.ts +34 -7
- package/server/threads/manageThreads.js +16 -14
- package/server/threads/threadServer.js +24 -6
- package/studio/web/assets/{Chat-CNEKMllJ.js → Chat-4LMxI0yB.js} +1 -1
- package/studio/web/assets/{FloatingChat-CEnWhHeR.js → FloatingChat-kWHApEtF.js} +3 -3
- package/studio/web/assets/{SearchDialog-CwhJdEdY.js → SearchDialog-B7ymna97.js} +1 -1
- package/studio/web/assets/{apiToken-DC5vsaaO.js → apiToken-CE3AAt9f.js} +1 -1
- package/studio/web/assets/{applications-DQZxDy0x.js → applications-DdN7CqsB.js} +1 -1
- package/studio/web/assets/{cssMode-Dz2DxF4e.js → cssMode-BpySZUSt.js} +1 -1
- package/studio/web/assets/{editor-B3mjd3tz.js → editor-myLCVv9C.js} +1 -1
- package/studio/web/assets/{html-BsRGM7tp.js → html-DGNYO55O.js} +1 -1
- package/studio/web/assets/{htmlMode-tUKJaA3o.js → htmlMode-BUgpQr41.js} +1 -1
- package/studio/web/assets/{index-BuqFjN5K.js → index-FN4KC9va.js} +39 -39
- package/studio/web/assets/index-QLZw6xrU.css +1 -0
- package/studio/web/assets/{index.lazy-CcpfQFY_.js → index.lazy-DZFuaumG.js} +1 -1
- package/studio/web/assets/{javascript-BZ_uRGHl.js → javascript-DiBzh4pj.js} +1 -1
- package/studio/web/assets/{jsonMode-DgB-aFMj.js → jsonMode-Bm312nmu.js} +1 -1
- package/studio/web/assets/{languageServices-BleuBw17.js → languageServices-Dz2K9rZN.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-ByVYLSsQ.js → lspLanguageFeatures-CNu1L5xn.js} +1 -1
- package/studio/web/assets/{notifications-TuzN0IMi.js → notifications--nwFLGmM.js} +1 -1
- package/studio/web/assets/{notifications-D1Nf-Edn.js → notifications-BelKw5bR.js} +1 -1
- package/studio/web/assets/{profile-D7C7Lupz.js → profile-AU5FjOBx.js} +1 -1
- package/studio/web/assets/{regions-nGQCm-v8.js → regions-C0sTSEHH.js} +1 -1
- package/studio/web/assets/{register-C3x9Ze6P.js → register-DyNR2rGk.js} +2 -2
- package/studio/web/assets/{setComponentFile-DpjrRqeG.js → setComponentFile-BkWx8AN3.js} +1 -1
- package/studio/web/assets/{setup-CGiKLZ9e.js → setup-D6LZS78d.js} +2 -2
- package/studio/web/assets/{status-CGJwrNPH.js → status-BCWnn70o.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-C-Ne6P7T.js → toggleHighContrast-pyeGJ6W1.js} +1 -1
- package/studio/web/assets/{tsMode-CSreYTys.js → tsMode-CGV3iaWC.js} +1 -1
- package/studio/web/assets/{typescript-MnxBk89W.js → typescript-Ci7Khvwo.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-TNrh77-s.js → useEntityRestURL-DNiz3aZ0.js} +1 -1
- package/studio/web/assets/{workers-BfZlUNVI.js → workers-byzxjbqy.js} +1 -1
- package/studio/web/assets/{xml-BHd6qGH7.js → xml-DGnHQ3CX.js} +1 -1
- package/studio/web/assets/{yaml-C53R8OQq.js → yaml-BUoTnPWb.js} +1 -1
- package/studio/web/index.html +2 -2
- package/upgrade/directives/5-3-0.ts +10 -3
- package/utility/durableFile.ts +96 -0
- package/utility/errors/hdbError.ts +9 -0
- package/utility/hdbTerms.ts +1 -0
- package/validation/configValidator.ts +31 -0
- 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.
|
|
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/` |
|
|
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` |
|
|
81
|
-
| `threads/manageThreads.js` | Thread pool lifecycle.
|
|
82
|
-
| `threads/threadServer.js` | Worker entry point —
|
|
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
|
-
>
|
|
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** (`
|
|
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()` |
|
|
129
|
-
| `proxyRequest()` |
|
|
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 |
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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)
|
|
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
|
-
|
|
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(
|
|
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:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// as
|
|
214
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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-
|
|
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(`
|