@orkestrel/scaffold 0.0.94 → 0.0.96
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/dist/bin/main.js +9 -9
- package/dist/bin/main.js.map +1 -1
- package/dist/host/claude/agents/orkestrel.md +52 -52
- package/dist/host/guides/agent.md +244 -38
- package/dist/host/guides/browser.md +2371 -2510
- package/dist/host/guides/codec.md +2 -2
- package/dist/host/guides/mcp.md +182 -100
- package/dist/host/guides/msg.md +14 -22
- package/dist/host/guides/ollama.md +172 -11
- package/dist/host/guides/server.md +122 -37
- package/dist/host/guides/test.md +26 -20
- package/dist/host/guides/toolbox.md +47 -40
- package/dist/host/manifest.json +11 -47
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/src/core/index.cjs +9 -9
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.js +9 -9
- package/dist/src/core/index.js.map +1 -1
- package/package.json +9 -9
|
@@ -230,17 +230,17 @@ body before receiving that response.
|
|
|
230
230
|
registers a raw protocol-upgrade claimant; `start` binds the listener and
|
|
231
231
|
resolves the actually-bound port while accepting an optional caller
|
|
232
232
|
`AbortSignal`; `stop` gracefully drains then closes; `destroy` is the
|
|
233
|
-
terminal, idempotent teardown.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
| Method | Returns | Summary
|
|
238
|
-
| --------- | ----------------- |
|
|
239
|
-
| `use` | `void` | Appends one middleware, or an array of them in order, to the onion, outer-to-inner in call order.
|
|
240
|
-
| `upgrade` | `void` | Registers an `UpgradeHandler` claimant that runs in registration order; a claimed socket is tracked until it closes.
|
|
241
|
-
| `start` | `Promise<number>` | Binds the configured `host` and `port`, or an ephemeral port, under an optional caller `AbortSignal`, and resolves the actually-bound port.
|
|
242
|
-
| `stop` | `Promise<void>` | Stops gracefully
|
|
243
|
-
| `destroy` | `Promise<void>` | Tears down for good: force-closes the listener and every socket, then the emitter — terminal and idempotent from any state.
|
|
233
|
+
terminal, idempotent teardown. An upgraded socket a handler claimed never holds
|
|
234
|
+
`stop` or `destroy` forever: the `stop` method drains it up to the `drain`
|
|
235
|
+
deadline and then destroys it, and the `destroy` method destroys it at once.
|
|
236
|
+
|
|
237
|
+
| Method | Returns | Summary |
|
|
238
|
+
| --------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
239
|
+
| `use` | `void` | Appends one middleware, or an array of them in order, to the onion, outer-to-inner in call order. |
|
|
240
|
+
| `upgrade` | `void` | Registers an `UpgradeHandler` claimant that runs in registration order; a claimed socket is tracked until it closes. |
|
|
241
|
+
| `start` | `Promise<number>` | Binds the configured `host` and `port`, or an ephemeral port, under an optional caller `AbortSignal`, and resolves the actually-bound port. |
|
|
242
|
+
| `stop` | `Promise<void>` | Stops gracefully within the `drain` deadline: fires the stop signal, drains in-flight requests and claimed upgraded sockets, then closes the listener; after a clean drain it ends each connection when no request exchange is open on it, and when the deadline expires it destroys every socket still open. |
|
|
243
|
+
| `destroy` | `Promise<void>` | Tears down for good: force-closes the listener and every socket, then the emitter — terminal and idempotent from any state. |
|
|
244
244
|
|
|
245
245
|
## Contract
|
|
246
246
|
|
|
@@ -262,33 +262,93 @@ These invariants hold across `src/server` ↔ `server.md`.
|
|
|
262
262
|
server is never born aborted; `address` is the real bound `AddressInfo`
|
|
263
263
|
after a successful start and `undefined` before start and after stop or
|
|
264
264
|
destroy; `stop()`/`destroy()` are idempotent no-ops from a state with
|
|
265
|
-
nothing to tear down
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
nothing to tear down, so a `stop()` call from `idle`, `starting`, or
|
|
266
|
+
`stopped` while no stop runs resolves at once; a `stop()` call made while
|
|
267
|
+
a stop runs returns that stop's promise and resolves with it after the
|
|
268
|
+
close, including a call made from a `stop` event listener or from a
|
|
269
|
+
listener on the stop signal; `EADDRINUSE` rejects `start()` outright — no
|
|
270
|
+
silent ephemeral fallback (use `discoverPort` up front for a
|
|
271
|
+
guaranteed-free port). A `start()` call made after `destroy()` while an
|
|
272
|
+
earlier `stop()` call has not yet resolved can be reset to `stopped` when
|
|
273
|
+
that stop finishes.
|
|
268
274
|
4. **Startup is bounded and caller-cancellable.** `start(signal?)` observes
|
|
269
275
|
caller cancellation only while binding; `timeouts.start` independently
|
|
270
|
-
bounds the bind (`0` permits no startup window)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
`
|
|
275
|
-
signal
|
|
276
|
+
bounds the bind (`0` permits no startup window) and is an integer from 0
|
|
277
|
+
through 2_147_483_647, or construction throws a `TypeError`. Cancellation
|
|
278
|
+
or deadline expiry closes the partial listener, clears the startup
|
|
279
|
+
deadline, resets the entity to `idle`, and rejects; expiry rejects with a
|
|
280
|
+
`DOMException` named `TimeoutError`, while caller cancellation rejects
|
|
281
|
+
with that signal's `reason`. A later `start()` is therefore permitted.
|
|
282
|
+
Aborting the caller signal after a successful start does not stop a live
|
|
283
|
+
server.
|
|
276
284
|
5. **Graceful drain is event-driven, never a busy-loop.** `stop()` fires the
|
|
277
285
|
stop signal, arms a `@orkestrel/timeout` deadline, and parks on the
|
|
278
286
|
drainable count reaching zero or the deadline firing (a wake-park, not
|
|
279
|
-
polling); it then emits `drain` with the still-pending counts and closes
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
polling); it then emits `drain` with the still-pending counts and closes
|
|
288
|
+
the listener. The server counts the open request exchanges on each
|
|
289
|
+
connection, because a pipelining client can hold several: an exchange
|
|
290
|
+
opens when a request reaches the handler and stays open until its request
|
|
291
|
+
message has ended or closed and its response has finished or closed.
|
|
292
|
+
The `error` listeners that this tracking attaches to the message and the
|
|
293
|
+
response stay attached, so a later error on either raises no uncaught
|
|
294
|
+
exception.
|
|
295
|
+
After a clean drain the close destroys every connection with no open
|
|
296
|
+
exchange. That covers an idle keep-alive connection. It covers a
|
|
297
|
+
connection that never sent a request: Node doesn't count it as idle, and
|
|
298
|
+
the `closeIdleConnections()` call alone would leave it holding the
|
|
299
|
+
`stop()` call open until the peer drops it. It also covers a connection
|
|
300
|
+
partway through its first or a later header block, because a graceful
|
|
301
|
+
stop serves counted work and a request whose headers never completed is
|
|
302
|
+
not counted. A connection that still carries an open exchange stays open,
|
|
303
|
+
including one whose handler answered, for example with a `413` response,
|
|
304
|
+
while its body still uploads: destroying it can reset the connection and
|
|
305
|
+
lose that response. The server ends that connection when its last
|
|
306
|
+
exchange completes, and the `stop()` call resolves after that connection
|
|
307
|
+
closes. Each body byte restarts Node's keep-alive socket timer, so no Node
|
|
308
|
+
timeout bounds an upload that keeps sending. The deadline that bounds the
|
|
309
|
+
drain therefore stays armed through the clean close's wait: when it
|
|
310
|
+
expires before the server's `close` event, the server destroys every
|
|
311
|
+
socket still open, as an expired drain does, so no connection holds the
|
|
312
|
+
`stop()` call past the `drain` deadline. The `drain` event has already
|
|
313
|
+
reported `[0, 0]` for that stop, because every response had finished at
|
|
314
|
+
the clean drain. The cut ends the unread part of an upload its handler had
|
|
315
|
+
already answered, and destroying that socket can also reset the
|
|
316
|
+
connection and lose the answered response the peer has not yet read. No
|
|
317
|
+
event reports that cut or that loss. An upload ends before the deadline
|
|
318
|
+
only because the `buildRequest` function of the `@orkestrel/router`
|
|
319
|
+
package reads a request body eagerly, whether or not the handler reads
|
|
320
|
+
it. A request or an upgrade that a connection sends after the listener
|
|
321
|
+
closes, such as one pipelined behind the end of that upload, is refused:
|
|
322
|
+
the server destroys that connection before the request is counted or a
|
|
323
|
+
handler sees it, so the `stop()` call waits only on work counted before
|
|
324
|
+
the close. That refusal, and the clean close's destroy of a connection
|
|
325
|
+
with no open exchange, can reset the connection when the kernel still
|
|
326
|
+
holds bytes the server never read, as it does for a peer that pipelines a
|
|
327
|
+
request behind a POST request; see
|
|
328
|
+
[the pipelining section of RFC 9112](https://www.rfc-editor.org/rfc/rfc9112#section-9.3.2).
|
|
329
|
+
After an expired drain the close destroys every socket and cuts the work
|
|
330
|
+
still pending. No I/O callback runs between the drain's settle and the
|
|
331
|
+
close, so a request that reached the handler by the close did so before
|
|
332
|
+
the drain settled and was counted. A request Node answers itself never
|
|
333
|
+
reaches the handler and is not counted: Node answers an unknown `Expect`
|
|
334
|
+
value with a `417` response, so that connection carries no open exchange
|
|
335
|
+
and the clean close destroys it. A request that reaches the server after
|
|
336
|
+
the `stop()` call begins and before the listener closes, on a kept-alive
|
|
337
|
+
connection or on a connection the listener accepts, is in-flight work:
|
|
338
|
+
the drain waits for it, and its `request.signal` property is already
|
|
339
|
+
aborted.
|
|
282
340
|
Drainable work is every in-flight request plus every upgraded socket a
|
|
283
341
|
handler claimed, because a long-lived upgraded connection is work a
|
|
284
342
|
graceful stop lets finish rather than cuts mid-frame. `drain` carries
|
|
285
|
-
both counts,
|
|
286
|
-
|
|
343
|
+
both counts, which say whether a request or a claimed socket was cut; a
|
|
344
|
+
cut during the clean close's wait reports `[0, 0]`. A claimed socket
|
|
345
|
+
never holds `stop()` or `destroy()` open forever: Node detaches an
|
|
287
346
|
upgraded socket from its own connection set, so neither
|
|
288
347
|
`closeIdleConnections()` nor `closeAllConnections()` reaches it while
|
|
289
348
|
`server.close()` still waits on it, and the server therefore tracks each
|
|
290
|
-
claimed socket until it closes and destroys the survivors itself
|
|
291
|
-
|
|
349
|
+
claimed socket until it closes and destroys the survivors itself when the
|
|
350
|
+
server closes. The claimant still owns the socket; tracking only watches
|
|
351
|
+
it.
|
|
292
352
|
A handler that wants a protocol-clean goodbye — a WebSocket close frame —
|
|
293
353
|
sends it on the `stop` event, which fires before the drain begins, and the
|
|
294
354
|
drain then settles on that close instead of running the deadline out. A
|
|
@@ -326,8 +386,8 @@ These invariants hold across `src/server` ↔ `server.md`.
|
|
|
326
386
|
`Request` exists there, only a raw `IncomingMessage` — and never crashes
|
|
327
387
|
the process) and the fan-out continues; an upgrade nothing claims destroys
|
|
328
388
|
the socket so it never leaks a dangling connection. A claimed socket is
|
|
329
|
-
tracked until it closes, which is what item 5's drain and
|
|
330
|
-
|
|
389
|
+
tracked until it closes, which is what item 5's drain and close act on —
|
|
390
|
+
ownership stays with the claimant either way.
|
|
331
391
|
8. **Body read exactly once, capped, zip-bomb-safe, scrubbed.**
|
|
332
392
|
`MiddlewareContext.body()` is lazy and cached, so a body-parsing middleware
|
|
333
393
|
and the eventual handler both reading it consume the underlying stream
|
|
@@ -534,9 +594,18 @@ function streamHandler(): Response {
|
|
|
534
594
|
|
|
535
595
|
### Graceful shutdown
|
|
536
596
|
|
|
537
|
-
`stop()`
|
|
538
|
-
|
|
539
|
-
work is requests and claimed upgraded sockets
|
|
597
|
+
The `stop()` call gives in-flight work up to the `drain` deadline, then
|
|
598
|
+
closes the listener; the `destroy()` call is the final, idempotent
|
|
599
|
+
teardown. In-flight work is requests and claimed upgraded sockets. An idle
|
|
600
|
+
keep-alive connection or a connection that never sent a request costs the
|
|
601
|
+
`stop()` call no wait. A connection whose request exchange is still open,
|
|
602
|
+
such as an upload still sending its body after the response, keeps the
|
|
603
|
+
`stop()` call waiting until that exchange completes or the same `drain`
|
|
604
|
+
deadline expires and cuts it. A request that such a connection sends after
|
|
605
|
+
the listener closes is refused, and its connection is destroyed. A second
|
|
606
|
+
`stop()` call made during the stop returns the first call's promise. The
|
|
607
|
+
`drain` option is an integer from 0 through 2_147_483_647 milliseconds, and
|
|
608
|
+
construction throws a `TypeError` for any other value.
|
|
540
609
|
|
|
541
610
|
```ts
|
|
542
611
|
import { createServer } from '@orkestrel/server'
|
|
@@ -548,7 +617,7 @@ server.emitter.on('drain', (pending, upgraded) =>
|
|
|
548
617
|
console.log(`drained with ${pending} requests and ${upgraded} sockets still open`),
|
|
549
618
|
)
|
|
550
619
|
await server.start()
|
|
551
|
-
await server.stop() //
|
|
620
|
+
await server.stop() // drains, then closes; the 5 s deadline cuts whatever still holds it
|
|
552
621
|
await server.destroy() // idempotent final teardown
|
|
553
622
|
```
|
|
554
623
|
|
|
@@ -727,10 +796,26 @@ new TextDecoder().decode(body) // 'hi' — capped decompression, the zip-bomb de
|
|
|
727
796
|
the status matrix, restart-fresh-abort, caller-cancelled / timed-out / clean
|
|
728
797
|
bounded startup, `EADDRINUSE` honesty, host/port binds, ephemeral default,
|
|
729
798
|
connection / header / per-socket request caps, graceful-vs-forced drain,
|
|
730
|
-
the
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
connection
|
|
799
|
+
the clean stop that ends a never-used connection inside the deadline after
|
|
800
|
+
the request in flight beside it finishes, the clean stop that counts and
|
|
801
|
+
serves a kept-alive request sent during the drain with its signal aborted
|
|
802
|
+
before it closes that idle connection, the clean stop that serves a
|
|
803
|
+
kept-alive request sent during the drain and keeps that connection open
|
|
804
|
+
until the drain settles, the clean stop that destroys a connection whose
|
|
805
|
+
first or second header block never completed and one whose `417` answer
|
|
806
|
+
left a withheld body pending, the clean stop whose drain waits for a
|
|
807
|
+
response after its POST body ended and then ends that connection, the
|
|
808
|
+
clean stop that keeps a connection whose `413` answer left its body
|
|
809
|
+
uploading and ends it when the upload ends, the `drain` deadline that cuts
|
|
810
|
+
that upload when it sends one more byte and stalls or keeps sending one
|
|
811
|
+
byte at a time, the concurrent `stop()` call that returns the stop in
|
|
812
|
+
flight and resolves with it after the close, the `stop()` call from `idle`
|
|
813
|
+
or `starting` that resolves at once, the clean stop that
|
|
814
|
+
refuses a request or an upgrade pipelined behind the end of that body after
|
|
815
|
+
the listener closes, the held-upgraded-socket stop (drained to
|
|
816
|
+
the deadline then cut, settled early when the claimant closes it, reported
|
|
817
|
+
on `drain`, and force-closed by `destroy()`) against a no-socket control,
|
|
818
|
+
20-parallel-none-dropped, connection facts threaded into state,
|
|
734
819
|
`context.body()` caching, boundary mapping (`HTTPError`/other/`expose`), the
|
|
735
820
|
stop-signal-reaches-handlers case, and the real slow-TCP proof that an SSE
|
|
736
821
|
producer parks at local queue pressure, resumes on drain, and stays bounded
|
package/dist/host/guides/test.md
CHANGED
|
@@ -749,26 +749,32 @@ A `Shape` cell holds the constant's declared type.
|
|
|
749
749
|
|
|
750
750
|
#### Helpers
|
|
751
751
|
|
|
752
|
-
| API | Kind | Signature | Summary
|
|
753
|
-
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
754
|
-
| `readInventory` | function | `(root: URL \| string, targets: readonly string[], options?: InventoryOptions) => Readonly<Record<string, string>>` | Reads files from selected targets below a root directory.
|
|
755
|
-
| `resolveContained` | function | `(root: string, target: string) => string \| undefined` | Resolves a target that stays below a root directory.
|
|
756
|
-
| `requireContained` | function | `(root: string, target: string) => string` | Resolves a target that stays below a root directory, refusing an escape.
|
|
757
|
-
| `
|
|
758
|
-
| `
|
|
759
|
-
| `
|
|
760
|
-
| `
|
|
761
|
-
| `
|
|
762
|
-
| `
|
|
763
|
-
| `
|
|
764
|
-
| `
|
|
765
|
-
| `
|
|
766
|
-
| `
|
|
767
|
-
| `
|
|
768
|
-
| `
|
|
769
|
-
| `
|
|
770
|
-
| `
|
|
771
|
-
| `
|
|
752
|
+
| API | Kind | Signature | Summary |
|
|
753
|
+
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
754
|
+
| `readInventory` | function | `(root: URL \| string, targets: readonly string[], options?: InventoryOptions) => Readonly<Record<string, string>>` | Reads files from selected targets below a root directory. |
|
|
755
|
+
| `resolveContained` | function | `(root: string, target: string) => string \| undefined` | Resolves a target that stays below a root directory. |
|
|
756
|
+
| `requireContained` | function | `(root: string, target: string) => string` | Resolves a target that stays below a root directory, refusing an escape. |
|
|
757
|
+
| `hasScratchPath` | function | `(root: string, target: string) => boolean` | Reports whether a contained entry exists under a scratch directory without following its final link. |
|
|
758
|
+
| `isExcluded` | function | `(key: string, exclusions: readonly string[]) => boolean` | Reports whether a root-relative key matches an exclusion. |
|
|
759
|
+
| `readIdentity` | function | `(status: Stats) => ScratchIdentity` | Reads the identity of one allocated directory off a host status. |
|
|
760
|
+
| `matchesIdentity` | function | `(current: ScratchIdentity, allocation: ScratchIdentity) => boolean` | Reports whether two directory identities name the same allocation. |
|
|
761
|
+
| `readErrorCode` | function | `(error: unknown) => string \| undefined` | Reads the `code` an unknown thrown value carries. |
|
|
762
|
+
| `createLink` | function | `(path: string, source: string) => void` | Creates a symbolic link with a directory-junction fallback for hosts that refuse symbolic links. |
|
|
763
|
+
| `removeTree` | function | `(path: string) => void` | Removes a directory tree, retrying past a transient Windows handle-release race. |
|
|
764
|
+
| `isRunning` | function | `(pid: number) => boolean` | Reports whether a process id names a live process. |
|
|
765
|
+
| `waitForSocketClose` | function | `(socket: Socket, options?: WaitOptions) => Promise<void>` | Waits for a socket to close, accepting a peer reset as a forced close. |
|
|
766
|
+
| `destroyScratch` | function | `(scratch: ScratchInterface, options?: WaitOptions) => Promise<void>` | Destroys a scratch directory, retrying until the host releases it. |
|
|
767
|
+
| `requestUpgrade` | function | `(port: number, options?: UpgradeOptions) => Promise<UpgradeResult>` | Drives a real client upgrade request against a loopback port and reports what the server did. |
|
|
768
|
+
| `supportsDirectoryLinks` | function | `() => boolean` | Checks whether this host links a directory, by creating one link and reading through it. |
|
|
769
|
+
| `supportsFileLinks` | function | `() => boolean` | Checks whether this host links a file, by creating one link and reading the file through it. |
|
|
770
|
+
| `supportsMode` | function | `() => boolean` | Checks whether POSIX permission bits round-trip through this host's `chmod` and `stat`. |
|
|
771
|
+
| `supportsCase` | function | `() => boolean` | Checks whether this host treats two names differing only by case as distinct files. |
|
|
772
|
+
| `supportsBytes` | function | `() => boolean` | Checks whether this host accepts a filename carrying a raw byte no UTF-8 decoder resolves. |
|
|
773
|
+
|
|
774
|
+
`hasScratchPath` takes a scratch's absolute path and a relative or absolute target. It checks
|
|
775
|
+
containment before inspecting the root, returns `false` for a missing root or entry, and refuses a
|
|
776
|
+
root that is a symbolic link or not a directory. It follows intermediate links and counts a dangling final link
|
|
777
|
+
as present. It checks no allocation identity.
|
|
772
778
|
|
|
773
779
|
`resolveContained` is the one lexical containment check, and `readInventory` and `createScratch`
|
|
774
780
|
both call it. It resolves the target against the root — relative or absolute — and returns
|