acrylblends 0.9.2 → 0.11.0

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 (60) hide show
  1. package/dist/index.js.map +1 -1
  2. package/docs/framework/cordis/node-server.md +25 -3
  3. package/lib/bun.d.ts +1 -1
  4. package/lib/cloudflare-workers.d.ts +1 -1
  5. package/lib/cordis/door.d.ts +2 -1
  6. package/lib/cordis/door.js +7 -4
  7. package/lib/cordis/inspect.js +1 -1
  8. package/lib/cordis/node-server.d.ts +14 -2
  9. package/lib/cordis/node-server.js +27 -3
  10. package/lib/cordis/router-http.d.ts +25 -2
  11. package/lib/cordis/router-http.js +54 -6
  12. package/lib/cordis/router.d.ts +1 -1
  13. package/lib/cordis/router.js +1 -1
  14. package/lib/cordis/state.js +1 -1
  15. package/lib/door/host.js +1 -1
  16. package/lib/{index-BvFh1Gfx.d.ts → index-C-SklY6u.d.ts} +3 -0
  17. package/lib/{index-BqVQkTnu.d.ts → index-D4nVR3wu.d.ts} +18 -3
  18. package/lib/node-server.d.ts +2 -38
  19. package/lib/node-server.js +2 -2
  20. package/lib/{snapshot-text-D3NspDyT.js → snapshot-text-yaWlezbd.js} +1 -1
  21. package/lib/{src-CwABFr6r.js → src-DNzw0Qsb.js} +3 -2
  22. package/lib/test-websocket-DyeS50Tr.d.ts +136 -0
  23. package/lib/{server-DvgMGtzo.js → test-websocket-ZXHNHQSU.js} +225 -3
  24. package/lib/ws.d.ts +1 -1
  25. package/lib/ws.js +3 -0
  26. package/package.json +6 -2
  27. package/plugins/acryl-door/src/host/door.ts +5 -3
  28. package/plugins/acryl-door/src/index.ts +1 -1
  29. package/plugins/acryl-node-server/plugin.json +1 -1
  30. package/plugins/acryl-node-server/src/adapter/websocket-types.ts +5 -1
  31. package/plugins/acryl-node-server/src/adapter/websocket.ts +33 -5
  32. package/plugins/acryl-node-server/src/index.ts +35 -1
  33. package/plugins/acryl-node-server/src/test-websocket.ts +110 -0
  34. package/plugins/acryl-node-server/src/upgrade-hub.ts +152 -0
  35. package/plugins/acryl-router/src/core/helper/websocket/index.ts +5 -0
  36. package/plugins/acryl-router/src/service.ts +18 -4
  37. package/plugins/acryl-router-http/plugin.json +2 -2
  38. package/plugins/acryl-router-http/src/index.ts +70 -6
  39. package/plugins/agent-panel/client/main.ts +70 -2
  40. package/plugins/agent-panel/drivers.ts +83 -0
  41. package/plugins/agent-panel/headless.ts +154 -0
  42. package/plugins/agent-panel/index.ts +42 -125
  43. package/plugins/agent-panel/plugin.json +1 -1
  44. package/plugins/driver-pty/client/terminal.ts +188 -0
  45. package/plugins/driver-pty/client/vendor/LICENSE +21 -0
  46. package/plugins/driver-pty/client/vendor/LICENSE-addon-fit +19 -0
  47. package/plugins/driver-pty/client/vendor/VERSION +4 -0
  48. package/plugins/driver-pty/client/vendor/addon-fit.d.mts +6 -0
  49. package/plugins/driver-pty/client/vendor/addon-fit.mjs +18 -0
  50. package/plugins/driver-pty/client/vendor/xterm-css.d.mts +2 -0
  51. package/plugins/driver-pty/client/vendor/xterm-css.mjs +2 -0
  52. package/plugins/driver-pty/client/vendor/xterm.d.mts +17 -0
  53. package/plugins/driver-pty/client/vendor/xterm.mjs +53 -0
  54. package/plugins/driver-pty/index.ts +249 -0
  55. package/plugins/driver-pty/plugin.json +13 -0
  56. package/plugins/driver-pty/spawn.ts +36 -0
  57. package/plugins/driver-pty/terminal.ts +84 -0
  58. package/shared/AGENTS.core.md +1 -1
  59. package/shared/DEFAULTS.md +1 -1
  60. package/lib/error-t4N2js2I.d.ts +0 -8
@@ -1,4 +1,5 @@
1
- import { t as RequestError } from "../error-t4N2js2I.js";
1
+ import { c as UpgradeHubOptions, i as testWebSocket, l as UpgradeRouter, n as TestWebSocketOptions, o as UpgradeEmitter, p as WebSocketLike, r as TestWebSocketResult, s as UpgradeHub, t as TestSocket, u as RequestError } from "../test-websocket-DyeS50Tr.js";
2
+ import { n as UpgradeWebSocket } from "../index-C-SklY6u.js";
2
3
  import { ServeStaticOptions, serveStatic } from "../node-server/serve-static.js";
3
4
  import { Context, Service } from "@deepseek-ai/cordis";
4
5
  import z from "@deepseek-ai/schemastery";
@@ -11,6 +12,8 @@ interface Config {
11
12
  hostname: string;
12
13
  port: number;
13
14
  scan: number;
15
+ /** Limits of WebSocket upgrades (`upgrades()`); the defaults suit most apps. */
16
+ websocket: UpgradeHubOptions;
14
17
  }
15
18
  /** Every field has a default, so a caller passes only what it changes: `ctx.plugin(plugin, { ... })` takes the partial shape. */
16
19
  declare const Config: z<Partial<Config>, Config>;
@@ -23,8 +26,17 @@ declare class NodeServer extends Service {
23
26
  readonly hostname: string;
24
27
  readonly config: Config;
25
28
  private readonly server;
29
+ private hub;
26
30
  private bound;
27
31
  constructor(ctx: Context, config: Config);
32
+ /**
33
+ * `upgradeWebSocket` for the plugin that asks (call it once, in `apply`): `app.get('/ws', upgrade((c) => ({ onMessage(event, ws) { ... } })))`. Checks go in ordinary middleware on the
34
+ * route, and a handler that returns a `Response` refuses the upgrade with that status before any handshake. Every socket it accepts belongs to the calling plugin: when the plugin
35
+ * unloads they are closed with 1001 within `websocket.grace` and the handler sees `close` once. Pass `{ textOnly: true }` to drop binary frames. Needs the `ws` package.
36
+ */
37
+ upgrades(): UpgradeWebSocket<WebSocketLike>;
38
+ /** How many WebSocket connections are open now. */
39
+ get sockets(): number;
28
40
  /** The port in use, 0 before the server listens. */
29
41
  get port(): number;
30
42
  /** The address clients use. */
@@ -42,4 +54,4 @@ declare function isPortUnavailable(error: unknown, platform?: NodeJS.Platform):
42
54
  /** Loads once the server listens, so a plugin that injects `nodeServer` can rely on `url`. A failure to bind fails the plugin. */
43
55
  declare function apply(ctx: Context, config?: Partial<Config>): Promise<void>;
44
56
  //#endregion
45
- export { Config, NodeServer, RequestError, type ServeStaticOptions, apply, inject, isPortUnavailable, name, serveStatic };
57
+ export { Config, NodeServer, RequestError, type ServeStaticOptions, type TestSocket, type TestWebSocketOptions, type TestWebSocketResult, UpgradeEmitter, UpgradeHub, type UpgradeHubOptions, type UpgradeRouter, apply, inject, isPortUnavailable, name, serveStatic, testWebSocket };
@@ -1,4 +1,4 @@
1
- import { a as Request, o as RequestError, t as createAdaptorServer } from "../server-DvgMGtzo.js";
1
+ import { c as Request, i as createAdaptorServer, l as RequestError, n as UpgradeEmitter, r as UpgradeHub, t as testWebSocket } from "../test-websocket-ZXHNHQSU.js";
2
2
  import { t as serveStatic } from "../serve-static-3uo_9rjF.js";
3
3
  import { Service } from "@deepseek-ai/cordis";
4
4
  import z from "@deepseek-ai/schemastery";
@@ -10,12 +10,18 @@ const inject = ["router"];
10
10
  const Config = z.object({
11
11
  hostname: z.string().default("127.0.0.1").description("Address to bind. Loopback by default; binding elsewhere exposes the app to the network."),
12
12
  port: z.number().min(0).max(65535).default(0).description("Port to listen on; 0 lets the system choose. A taken port is followed by the next free one."),
13
- scan: z.number().min(0).default(20).description("How many further ports to try when the configured one is taken.")
13
+ scan: z.number().min(0).default(20).description("How many further ports to try when the configured one is taken."),
14
+ websocket: z.object({
15
+ maxPayload: z.number().min(1).default(1024 * 1024).description("The largest WebSocket frame accepted, in bytes; a bigger one closes the socket with 1009."),
16
+ heartbeat: z.number().min(0).default(3e4).description("Milliseconds between pings; a peer that did not answer the last one is terminated. 0 turns it off."),
17
+ grace: z.number().min(0).default(2e3).description("Milliseconds a closing socket gets before it is terminated when its plugin unloads.")
18
+ }).default({})
14
19
  });
15
20
  var NodeServer = class extends Service {
16
21
  hostname;
17
22
  config;
18
23
  server;
24
+ hub;
19
25
  bound = 0;
20
26
  constructor(ctx, config) {
21
27
  super(ctx, "nodeServer");
@@ -31,6 +37,7 @@ var NodeServer = class extends Service {
31
37
  overrideGlobalObjects: false
32
38
  });
33
39
  ctx.effect(() => async () => {
40
+ await this.hub?.close();
34
41
  this.server.closeAllConnections();
35
42
  if (this.server.listening) await new Promise((resolve) => {
36
43
  this.server.close(() => {
@@ -39,6 +46,22 @@ var NodeServer = class extends Service {
39
46
  });
40
47
  }, "acryl-node-server: close listener and open connections");
41
48
  }
49
+ /**
50
+ * `upgradeWebSocket` for the plugin that asks (call it once, in `apply`): `app.get('/ws', upgrade((c) => ({ onMessage(event, ws) { ... } })))`. Checks go in ordinary middleware on the
51
+ * route, and a handler that returns a `Response` refuses the upgrade with that status before any handshake. Every socket it accepts belongs to the calling plugin: when the plugin
52
+ * unloads they are closed with 1001 within `websocket.grace` and the handler sees `close` once. Pass `{ textOnly: true }` to drop binary frames. Needs the `ws` package.
53
+ */
54
+ upgrades() {
55
+ if (!this.hub) {
56
+ this.hub = new UpgradeHub(this.config.websocket);
57
+ this.hub.attach(this.server, this.ctx.router);
58
+ }
59
+ return this.hub.upgradeWebSocketFor(this.ctx);
60
+ }
61
+ /** How many WebSocket connections are open now. */
62
+ get sockets() {
63
+ return this.hub?.size ?? 0;
64
+ }
42
65
  /** The port in use, 0 before the server listens. */
43
66
  get port() {
44
67
  return this.bound;
@@ -86,8 +109,9 @@ async function apply(ctx, config = {}) {
86
109
  hostname: "127.0.0.1",
87
110
  port: 0,
88
111
  scan: 20,
112
+ websocket: {},
89
113
  ...config
90
114
  }).listen();
91
115
  }
92
116
  //#endregion
93
- export { Config, NodeServer, RequestError, apply, inject, isPortUnavailable, name, serveStatic };
117
+ export { Config, NodeServer, RequestError, UpgradeEmitter, UpgradeHub, apply, inject, isPortUnavailable, name, serveStatic, testWebSocket };
@@ -1,5 +1,7 @@
1
- import { i as RouterHttpService } from "../index-BqVQkTnu.js";
1
+ import { n as UpgradeWebSocket } from "../index-C-SklY6u.js";
2
+ import { i as RouterHttpService } from "../index-D4nVR3wu.js";
2
3
  import { IncomingMessage, ServerResponse } from "node:http";
4
+ import { Duplex } from "node:stream";
3
5
  import { Context, Service } from "@deepseek-ai/cordis";
4
6
 
5
7
  //#region ../../plugins/acryl-router-http/src/serve.d.ts
@@ -40,16 +42,37 @@ interface WebServerPort {
40
42
  readonly path: string;
41
43
  readonly handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
42
44
  }): () => void;
45
+ /** One exact-path upgrade claim (`WebUpgradeRoute` of the web server); optional because an older web server has none. */
46
+ registerUpgrade?(route: {
47
+ readonly path: string;
48
+ readonly handler: (req: IncomingMessage, socket: Duplex, head: Buffer) => void | Promise<void>;
49
+ }): () => void;
43
50
  }
44
51
  declare class RouterHttp extends Service implements RouterHttpService {
45
52
  private readonly router;
46
53
  private readonly web;
54
+ private hub;
55
+ /** The adapter's own context: the hub lives as long as the adapter, whichever plugin asked for it first (`this.ctx` is the caller's). */
56
+ private readonly home;
57
+ private readonly upgradesIn;
47
58
  constructor(ctx: Context);
48
59
  /**
49
60
  * Serve `prefix` and everything below it through the router. Returns a remover; the mount also goes away when the
50
61
  * calling plugin unloads. A prefix another route already claims throws, as the webServer treats it as misconfiguration.
62
+ * `options.upgrade` names the exact paths that may be upgraded to a WebSocket: each must be inside the prefix, and each is one
63
+ * exact-path upgrade route of the web server, removed with the mount. No other path of the mount accepts an upgrade.
64
+ */
65
+ mount(prefix: string, options?: {
66
+ readonly upgrade?: readonly string[];
67
+ }): () => void;
68
+ /**
69
+ * `upgradeWebSocket` for the calling plugin: the sockets its routes accept are closed with 1001 when it unloads. Needs the `ws` package.
70
+ * The route must be on a path the plugin mounted with `upgrade: [path]`.
51
71
  */
52
- mount(prefix: string): () => void;
72
+ upgrades(): UpgradeWebSocket;
73
+ /** How many WebSocket connections are open now. */
74
+ get sockets(): number;
75
+ private ensureHub;
53
76
  }
54
77
  declare function apply(ctx: Context): void;
55
78
  //#endregion
@@ -1,3 +1,5 @@
1
+ import { n as UpgradeEmitter, r as UpgradeHub } from "../test-websocket-ZXHNHQSU.js";
2
+ import "./node-server.js";
1
3
  import { pipeline } from "node:stream/promises";
2
4
  import { Readable } from "node:stream";
3
5
  import { Service } from "@deepseek-ai/cordis";
@@ -88,27 +90,73 @@ const PREFIX_PATTERN = /^\/[A-Za-z0-9._~\-/]*[A-Za-z0-9._~-]$/;
88
90
  var RouterHttp = class extends Service {
89
91
  router;
90
92
  web;
93
+ hub;
94
+ /** The adapter's own context: the hub lives as long as the adapter, whichever plugin asked for it first (`this.ctx` is the caller's). */
95
+ home;
96
+ upgradesIn = new UpgradeEmitter();
91
97
  constructor(ctx) {
92
98
  super(ctx, "routerHttp");
99
+ this.home = ctx;
93
100
  this.router = ctx.router;
94
101
  this.web = ctx.get("webServer");
95
102
  }
96
103
  /**
97
104
  * Serve `prefix` and everything below it through the router. Returns a remover; the mount also goes away when the
98
105
  * calling plugin unloads. A prefix another route already claims throws, as the webServer treats it as misconfiguration.
106
+ * `options.upgrade` names the exact paths that may be upgraded to a WebSocket: each must be inside the prefix, and each is one
107
+ * exact-path upgrade route of the web server, removed with the mount. No other path of the mount accepts an upgrade.
99
108
  */
100
- mount(prefix) {
109
+ mount(prefix, options = {}) {
101
110
  if (!PREFIX_PATTERN.test(prefix)) throw new TypeError(`acryl-router-http: "${prefix}" is not a mountable path prefix`);
102
- const remove = this.web.register({
103
- kind: "prefix",
104
- path: prefix,
105
- handler: (req, res) => serve(this.router, req, res)
106
- });
111
+ const upgrade = options.upgrade ?? [];
112
+ for (const path of upgrade) if (!PREFIX_PATTERN.test(path) || !(path === prefix || path.startsWith(`${prefix}/`))) throw new TypeError(`acryl-router-http: the upgrade path "${path}" is not inside the prefix "${prefix}"`);
113
+ if (upgrade.length > 0 && this.web.registerUpgrade === void 0) throw new TypeError("acryl-router-http: this web server cannot take WebSocket upgrades (it has no registerUpgrade)");
114
+ const removers = [];
115
+ try {
116
+ if (upgrade.length > 0) this.ensureHub();
117
+ for (const path of upgrade) removers.push(this.web.registerUpgrade({
118
+ path,
119
+ handler: (req, socket, head) => {
120
+ this.upgradesIn.emit("upgrade", req, socket, head);
121
+ }
122
+ }));
123
+ removers.push(this.web.register({
124
+ kind: "prefix",
125
+ path: prefix,
126
+ handler: (req, res) => serve(this.router, req, res)
127
+ }));
128
+ } catch (error) {
129
+ for (const remove of removers) remove();
130
+ throw error;
131
+ }
132
+ const remove = () => {
133
+ for (const undo of removers.splice(0)) undo();
134
+ };
107
135
  this.ctx.effect(() => () => {
108
136
  remove();
109
137
  }, `acryl-router-http: mount ${prefix}`);
110
138
  return remove;
111
139
  }
140
+ /**
141
+ * `upgradeWebSocket` for the calling plugin: the sockets its routes accept are closed with 1001 when it unloads. Needs the `ws` package.
142
+ * The route must be on a path the plugin mounted with `upgrade: [path]`.
143
+ */
144
+ upgrades() {
145
+ return this.ensureHub().upgradeWebSocketFor(this.ctx);
146
+ }
147
+ /** How many WebSocket connections are open now. */
148
+ get sockets() {
149
+ return this.hub?.size ?? 0;
150
+ }
151
+ ensureHub() {
152
+ if (!this.hub) {
153
+ const hub = new UpgradeHub();
154
+ hub.attach(this.upgradesIn, this.router);
155
+ this.hub = hub;
156
+ this.home.effect(() => () => hub.close(), "acryl-router-http: WebSocket hub");
157
+ }
158
+ return this.hub;
159
+ }
112
160
  };
113
161
  function apply(ctx) {
114
162
  new RouterHttp(ctx);
@@ -3,6 +3,6 @@ import { c as StatusCode, n as ContentfulStatusCode } from "../http-status-DaAwG
3
3
  import { a as getConnInfo, i as connectionOf, n as RouterBindings, r as RouterEnv, t as ConnectionInfo } from "../connection-ZLEso7XL.js";
4
4
  import { t as Acryl } from "../app-D8riatvv.js";
5
5
  import { n as AppRequest, t as App } from "../deprecated-CTy4N7ng.js";
6
- import { a as TrailingSlash, i as RouterHttpService, n as MountableApp, o as apply, r as Router, s as name, t as Config } from "../index-BqVQkTnu.js";
6
+ import { a as TrailingSlash, i as RouterHttpService, n as MountableApp, o as apply, r as Router, s as name, t as Config } from "../index-D4nVR3wu.js";
7
7
  import { t as HTTPException } from "../http-exception-kpGJkmpi.js";
8
8
  export { Acryl, type AcrylRequest, App, type AppRequest, Config, type ConnectionInfo, type ContentfulStatusCode, Context, type ContextRenderer, type ContextVariableMap, type Env, type ErrorHandler, type ExecutionContext, HTTPException, type Handler, type Input, type MiddlewareHandler, type MountableApp, type Next, type NotFoundHandler, type NotFoundResponse, Router, type RouterBindings, type RouterEnv, type RouterHttpService, type Schema, type StatusCode, type ToSchema, type TrailingSlash, type TypedResponse, type ValidationTargets, apply, connectionOf, getConnInfo, name };
@@ -3,5 +3,5 @@ import { t as Context } from "../context-DDO0cfuP.js";
3
3
  import { t as Acryl } from "../app-BM0rTCp7.js";
4
4
  import { t as App } from "../deprecated-BP4fPt38.js";
5
5
  import { n as getConnInfo, t as connectionOf } from "../connection-C7xgP1hP.js";
6
- import { i as name, n as Router, r as apply, t as Config } from "../src-CwABFr6r.js";
6
+ import { i as name, n as Router, r as apply, t as Config } from "../src-DNzw0Qsb.js";
7
7
  export { Acryl, App, Config, Context, HTTPException, Router, apply, connectionOf, getConnInfo, name };
@@ -3,7 +3,7 @@ import { methodNotAllowed } from "../method-not-allowed.js";
3
3
  import { r as streamSSE } from "../streaming-DHST3Scg.js";
4
4
  import { loopbackOnly } from "../loopback-only.js";
5
5
  import "../helpers-CvTauQT5.js";
6
- import "../src-CwABFr6r.js";
6
+ import "../src-DNzw0Qsb.js";
7
7
  import "../middleware-CPQ8himN.js";
8
8
  import { Service } from "@deepseek-ai/cordis";
9
9
  //#region ../../plugins/acryl-state/src/cell.ts
package/lib/door/host.js CHANGED
@@ -1,2 +1,2 @@
1
- import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-D3NspDyT.js";
1
+ import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-yaWlezbd.js";
2
2
  export { AuditLog, HEARTBEAT_MS, UiChannel, auditPath, pageChannelRoutes, renderSnapshot };
@@ -33,6 +33,8 @@ interface WSContextInit<T = unknown> {
33
33
  close(code?: number, reason?: string): void;
34
34
  raw?: T;
35
35
  readyState: WSReadyState;
36
+ /** Bytes queued to send and not yet written to the socket; a sender that cares about back-pressure reads it. */
37
+ bufferedAmount?: number;
36
38
  url?: string | URL | null;
37
39
  protocol?: string | null;
38
40
  }
@@ -52,6 +54,7 @@ declare class WSContext<T = unknown> {
52
54
  raw?: T;
53
55
  binaryType: BinaryType;
54
56
  get readyState(): WSReadyState;
57
+ get bufferedAmount(): number;
55
58
  url: URL | null;
56
59
  protocol: string | null;
57
60
  close(code?: number, reason?: string): void;
@@ -1,5 +1,6 @@
1
1
  import { B as NotFoundHandler, y as ErrorHandler } from "./context-DS3fYyG-.js";
2
2
  import { t as ConnectionInfo } from "./connection-ZLEso7XL.js";
3
+ import { n as UpgradeWebSocket } from "./index-C-SklY6u.js";
3
4
  import { Context, Service } from "@deepseek-ai/cordis";
4
5
  import z from "@deepseek-ai/schemastery";
5
6
 
@@ -18,8 +19,21 @@ declare const Config: z<Partial<Config>, Config>;
18
19
  * served over HTTP without depending on the adapter package.
19
20
  */
20
21
  interface RouterHttpService {
21
- /** Serve `prefix` and everything below it through the router; returns a remover. The mount also goes away when the calling plugin unloads. */
22
- mount(prefix: string): () => void;
22
+ /**
23
+ * Serve `prefix` and everything below it through the router; returns a remover. The mount also goes away when the calling plugin unloads.
24
+ * `options.upgrade` lists the exact paths below the prefix that may be upgraded to a WebSocket (each must be inside the prefix); no other path of the mount accepts an upgrade.
25
+ */
26
+ mount(prefix: string, options?: {
27
+ readonly upgrade?: readonly string[];
28
+ }): () => void;
29
+ /**
30
+ * `upgradeWebSocket` for the calling plugin: `app.get('/terminal', upgrade((c) => ({ onMessage(event, ws) { ... } })))` on a route the plugin mounted with `upgrade: ['/terminal']`.
31
+ * Checks go in ordinary middleware on the route; a handler that returns a `Response` refuses with that status before any handshake. The sockets it accepts belong to the calling
32
+ * plugin: when it unloads they are closed with 1001 and the handler sees `close` once. Needs the `ws` package.
33
+ */
34
+ upgrades(): UpgradeWebSocket;
35
+ /** How many WebSocket connections are open now. */
36
+ readonly sockets: number;
23
37
  }
24
38
  declare module '@deepseek-ai/cordis' {
25
39
  interface Context {
@@ -83,8 +97,9 @@ declare class Router extends Service {
83
97
  * Run a request in process. Never throws: failures become a 500, a disposed router answers 503. `connection` describes the transport
84
98
  * (an HTTP adapter passes the socket's peer address and local port); an in-process caller omits it. Handlers read it with
85
99
  * `connectionOf(c)`, abort with `c.req.raw.signal` and keep the request in flight with `c.executionCtx.waitUntil(task)`.
100
+ * `bindings` is an object an adapter hands to the handler as `c.env` (an upgrade request's socket hooks, for `upgradeWebSocket`); `connection` is added to it.
86
101
  */
87
- fetch(request: Request, connection?: ConnectionInfo): Promise<Response>;
102
+ fetch(request: Request, connection?: ConnectionInfo, bindings?: object): Promise<Response>;
88
103
  private dispatch;
89
104
  private app;
90
105
  /**
@@ -1,42 +1,12 @@
1
1
  import { t as Acryl } from "./app-D8riatvv.js";
2
- import { n as UpgradeWebSocket } from "./index-BvFh1Gfx.js";
3
- import { t as RequestError } from "./error-t4N2js2I.js";
2
+ import { a as Owner, c as UpgradeHubOptions, d as upgradeWebSocket, f as WebSocketData, i as testWebSocket, l as UpgradeRouter, m as WebSocketServerLike, n as TestWebSocketOptions, o as UpgradeEmitter, p as WebSocketLike, r as TestWebSocketResult, s as UpgradeHub, t as TestSocket, u as RequestError } from "./test-websocket-DyeS50Tr.js";
4
3
  import { ServeStaticOptions, serveStatic } from "./node-server/serve-static.js";
5
4
  import { getConnInfo } from "./node-server/conninfo.js";
6
5
  import { AddressInfo } from "node:net";
7
6
  import { IncomingMessage, Server, ServerOptions, ServerResponse, createServer } from "node:http";
8
7
  import { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, SecureServerOptions, ServerOptions as ServerOptions$1, createSecureServer, createServer as createServer$1 } from "node:http2";
9
- import { Duplex } from "node:stream";
10
8
  import { Server as Server$1, ServerOptions as ServerOptions$2, createServer as createServer$2 } from "node:https";
11
9
 
12
- //#region ../../plugins/acryl-node-server/src/adapter/websocket-types.d.ts
13
- type WSReadyState = 0 | 1 | 2 | 3;
14
- type WebSocketData = string | ArrayBuffer | Uint8Array | readonly Uint8Array[];
15
- type WebSocketSendOptions = {
16
- compress?: boolean;
17
- };
18
- interface WebSocketLike {
19
- protocol: string;
20
- readyState: WSReadyState;
21
- close(code?: number, reason?: string): void;
22
- send(data: string | ArrayBuffer | ArrayBufferView, options?: WebSocketSendOptions): void;
23
- on(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this;
24
- on(event: 'close', listener: (code: number, reason: Uint8Array) => void): this;
25
- on(event: 'error', listener: (error: unknown) => void): this;
26
- off(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this;
27
- }
28
- interface WebSocketServerLike {
29
- options: {
30
- noServer?: boolean;
31
- };
32
- on(event: 'connection', listener: (ws: WebSocketLike, request: IncomingMessage) => void): this;
33
- on(event: 'headers', listener: (headers: string[]) => void): this;
34
- off(event: 'headers', listener: (headers: string[]) => void): this;
35
- emit(event: 'connection', ws: WebSocketLike, request: IncomingMessage): boolean;
36
- handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, callback: (ws: WebSocketLike) => void): void;
37
- close(): void;
38
- }
39
- //#endregion
40
10
  //#region ../../plugins/acryl-node-server/src/adapter/types.d.ts
41
11
  type HttpBindings = {
42
12
  incoming: IncomingMessage;
@@ -107,12 +77,6 @@ declare function serve(options: HttpsOptions, listeningListener?: (info: Address
107
77
  declare function serve(options: HttpOptions, listeningListener?: (info: AddressInfo) => void): Server;
108
78
  declare function serve(options: Options, listeningListener?: (info: AddressInfo) => void): ServerType;
109
79
  //#endregion
110
- //#region ../../plugins/acryl-node-server/src/adapter/websocket.d.ts
111
- type UpgradeWebSocketOptions = {
112
- onError: (err: unknown) => void;
113
- };
114
- declare const upgradeWebSocket: UpgradeWebSocket<WebSocketLike, UpgradeWebSocketOptions>;
115
- //#endregion
116
80
  //#region ../../plugins/acryl-node-server/src/adapter/listener.d.ts
117
81
  declare const getRequestListener: (fetchCallback: FetchCallback, options?: {
118
82
  hostname?: string;
@@ -161,4 +125,4 @@ declare function clientModules(options: ClientModulesOptions): Acryl;
161
125
  */
162
126
  declare function buildClientModules(options: BuildClientModulesOptions): string[];
163
127
  //#endregion
164
- export { type BuildClientModulesOptions, type ClientModulesOptions, type Http2Bindings, type HttpBindings, RequestError, type ServeStaticOptions, type ServerType, type WebSocketData, type WebSocketLike, type WebSocketServerLike, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
128
+ export { type BuildClientModulesOptions, type ClientModulesOptions, type Http2Bindings, type HttpBindings, type Owner, RequestError, type ServeStaticOptions, type ServerType, type TestSocket, type TestWebSocketOptions, type TestWebSocketResult, UpgradeEmitter, UpgradeHub, type UpgradeHubOptions, type UpgradeRouter, type WebSocketData, type WebSocketLike, type WebSocketServerLike, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, testWebSocket, upgradeWebSocket };
@@ -1,5 +1,5 @@
1
- import { i as getRequestListener, n as serve, o as RequestError, r as upgradeWebSocket, t as createAdaptorServer } from "./server-DvgMGtzo.js";
1
+ import { a as serve, i as createAdaptorServer, l as RequestError, n as UpgradeEmitter, o as upgradeWebSocket, r as UpgradeHub, s as getRequestListener, t as testWebSocket } from "./test-websocket-ZXHNHQSU.js";
2
2
  import { t as serveStatic } from "./serve-static-3uo_9rjF.js";
3
3
  import { getConnInfo } from "./node-server/conninfo.js";
4
4
  import { n as clientModules, t as buildClientModules } from "./client-modules-CLvWOjbd.js";
5
- export { RequestError, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, upgradeWebSocket };
5
+ export { RequestError, UpgradeEmitter, UpgradeHub, buildClientModules, clientModules, createAdaptorServer, getConnInfo, getRequestListener, serve, serveStatic, testWebSocket, upgradeWebSocket };
@@ -5,7 +5,7 @@ import { r as streamSSE } from "./streaming-DHST3Scg.js";
5
5
  import { loopbackAuthority, loopbackOnly } from "./loopback-only.js";
6
6
  import "./helpers-CvTauQT5.js";
7
7
  import { UiControlError, WINDOW_ID, parseChannelToHost } from "./door/protocol.js";
8
- import "./src-CwABFr6r.js";
8
+ import "./src-DNzw0Qsb.js";
9
9
  import "./middleware-CPQ8himN.js";
10
10
  import { dirname, join } from "node:path";
11
11
  import { timingSafeEqual } from "node:crypto";
@@ -186,8 +186,9 @@ var Router = class extends Service {
186
186
  * Run a request in process. Never throws: failures become a 500, a disposed router answers 503. `connection` describes the transport
187
187
  * (an HTTP adapter passes the socket's peer address and local port); an in-process caller omits it. Handlers read it with
188
188
  * `connectionOf(c)`, abort with `c.req.raw.signal` and keep the request in flight with `c.executionCtx.waitUntil(task)`.
189
+ * `bindings` is an object an adapter hands to the handler as `c.env` (an upgrade request's socket hooks, for `upgradeWebSocket`); `connection` is added to it.
189
190
  */
190
- fetch(request, connection = {}) {
191
+ fetch(request, connection = {}, bindings) {
191
192
  if (this.state.closing) return Promise.resolve(new Response("Service Unavailable", { status: 503 }));
192
193
  const controller = new AbortController();
193
194
  const forward = () => controller.abort();
@@ -201,7 +202,7 @@ var Router = class extends Service {
201
202
  passThroughOnException: () => {},
202
203
  props: {}
203
204
  };
204
- const env = { connection };
205
+ const env = bindings === void 0 ? { connection } : Object.assign(bindings, { connection });
205
206
  let bodyDone = Promise.resolve();
206
207
  let endBody = () => {};
207
208
  let resolveSettled;
@@ -0,0 +1,136 @@
1
+ import { n as UpgradeWebSocket } from "./index-C-SklY6u.js";
2
+ import { IncomingMessage } from "node:http";
3
+ import { Duplex } from "node:stream";
4
+ import { EventEmitter } from "node:events";
5
+
6
+ //#region ../../plugins/acryl-node-server/src/adapter/websocket-types.d.ts
7
+ type WSReadyState = 0 | 1 | 2 | 3;
8
+ type WebSocketData = string | ArrayBuffer | Uint8Array | readonly Uint8Array[];
9
+ type WebSocketSendOptions = {
10
+ compress?: boolean;
11
+ };
12
+ interface WebSocketLike {
13
+ protocol: string;
14
+ readyState: WSReadyState;
15
+ bufferedAmount: number;
16
+ close(code?: number, reason?: string): void;
17
+ send(data: string | ArrayBuffer | ArrayBufferView, options?: WebSocketSendOptions): void;
18
+ on(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this;
19
+ on(event: 'close', listener: (code: number, reason: Uint8Array) => void): this;
20
+ on(event: 'error', listener: (error: unknown) => void): this;
21
+ on(event: 'pong', listener: () => void): this;
22
+ ping(): void;
23
+ terminate(): void;
24
+ off(event: 'message', listener: (data: WebSocketData, isBinary: boolean) => void): this;
25
+ }
26
+ interface WebSocketServerLike {
27
+ options: {
28
+ noServer?: boolean;
29
+ };
30
+ on(event: 'connection', listener: (ws: WebSocketLike, request: IncomingMessage) => void): this;
31
+ on(event: 'headers', listener: (headers: string[]) => void): this;
32
+ off(event: 'headers', listener: (headers: string[]) => void): this;
33
+ emit(event: 'connection', ws: WebSocketLike, request: IncomingMessage): boolean;
34
+ handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, callback: (ws: WebSocketLike) => void): void;
35
+ close(callback?: () => void): void;
36
+ }
37
+ //#endregion
38
+ //#region ../../plugins/acryl-node-server/src/adapter/websocket.d.ts
39
+ type UpgradeWebSocketOptions = {
40
+ onError: (err: unknown) => void; /** Hand the handler text frames only; binary frames are dropped before `onMessage`. */
41
+ textOnly?: boolean; /** Called with each accepted socket: the mounting plugin's scope keeps it to close it when the plugin unloads. */
42
+ track?: (ws: WebSocketLike) => void;
43
+ };
44
+ /** The part of a server `setupWebSocket` listens to: the Host's web server hands upgrades to a plain emitter with the same two methods. */
45
+ interface UpgradeSource {
46
+ on(event: 'upgrade', listener: (request: IncomingMessage, socket: Duplex, head: Buffer) => void): unknown;
47
+ on(event: 'close', listener: () => void): unknown;
48
+ listenerCount(event: 'upgrade'): number;
49
+ }
50
+ declare const upgradeWebSocket: UpgradeWebSocket<WebSocketLike, UpgradeWebSocketOptions>;
51
+ //#endregion
52
+ //#region ../../plugins/acryl-node-server/src/adapter/error.d.ts
53
+ declare class RequestError extends Error {
54
+ constructor(message: string, options?: {
55
+ cause?: unknown;
56
+ });
57
+ }
58
+ //#endregion
59
+ //#region ../../plugins/acryl-node-server/src/upgrade-hub.d.ts
60
+ interface UpgradeHubOptions {
61
+ /** The largest frame accepted, in bytes; a bigger one closes the socket with 1009. Default 1 MiB. */
62
+ maxPayload?: number;
63
+ /** Milliseconds between pings; a peer that did not answer the previous one is terminated. Default 30 000; 0 turns the heartbeat off. */
64
+ heartbeat?: number;
65
+ /** Milliseconds a closing socket gets to finish its close handshake before it is terminated. Default 2000. */
66
+ grace?: number;
67
+ }
68
+ /** The part of the router a hub calls: run a request with connection facts and the adapter's bindings. */
69
+ interface UpgradeRouter {
70
+ fetch(request: Request, connection: {
71
+ remoteAddress?: string | undefined;
72
+ localPort?: number | undefined;
73
+ }, bindings?: object): Promise<Response>;
74
+ }
75
+ /** What a hub needs of a Cordis context: tying a disposer to the plugin that asked. */
76
+ interface Owner {
77
+ effect(execute: () => () => unknown, label?: string): unknown;
78
+ }
79
+ declare class UpgradeHub {
80
+ readonly server: WebSocketServerLike;
81
+ private readonly all;
82
+ private readonly alive;
83
+ private readonly grace;
84
+ private timer;
85
+ private closed;
86
+ constructor(options?: UpgradeHubOptions);
87
+ /**
88
+ * Accept upgrades from `source` and answer them with `router`: the request runs through the router's middleware with the socket's peer address and local port as connection
89
+ * facts (so the same loopback, origin and host checks work), and the upgrade bindings travel in `c.env` for `upgradeWebSocket`.
90
+ */
91
+ attach(source: UpgradeSource, router: UpgradeRouter): void;
92
+ /** How many sockets are open. */
93
+ get size(): number;
94
+ /** `upgradeWebSocket` for the plugin `owner`: the sockets it accepts are closed with 1001 when that plugin unloads. */
95
+ upgradeWebSocketFor(owner: Owner): UpgradeWebSocket<WebSocketLike>;
96
+ /** Close `sockets` with `code`, wait for their close handshakes up to the grace, terminate what is left. */
97
+ closeAll(sockets: Iterable<WebSocketLike>, code: number, reason: string): Promise<void>;
98
+ /** End everything: every socket closes with 1001, the heartbeat stops, the `ws` server closes. */
99
+ close(): Promise<void>;
100
+ }
101
+ /** A stand-in for a server's `upgrade` events, for transports that dispatch upgrades themselves (the Host web server's exact-path routes). */
102
+ declare class UpgradeEmitter extends EventEmitter {}
103
+ //#endregion
104
+ //#region ../../plugins/acryl-node-server/src/test-websocket.d.ts
105
+ /** The client side of an accepted test connection. */
106
+ interface TestSocket {
107
+ /** Frames the handler sent, in order (text only). */
108
+ readonly received: string[];
109
+ /** The code and reason the handler closed with, when it did. */
110
+ readonly closed: {
111
+ readonly code: number | undefined;
112
+ readonly reason: string | undefined;
113
+ } | undefined;
114
+ /** Send a text frame to the handler. */
115
+ send(text: string): void;
116
+ /** Close from the client side. */
117
+ close(code?: number, reason?: string): void;
118
+ }
119
+ interface TestWebSocketOptions {
120
+ readonly headers?: Record<string, string>;
121
+ /** The socket's peer address and local port, as an adapter would pass them. */
122
+ readonly connection?: {
123
+ readonly remoteAddress?: string;
124
+ readonly localPort?: number;
125
+ };
126
+ /** The URL origin the request is made against; default `http://127.0.0.1`. */
127
+ readonly base?: string;
128
+ }
129
+ interface TestWebSocketResult {
130
+ /** The refusal's status, or 101 when the upgrade was accepted. */
131
+ readonly status: number;
132
+ readonly socket?: TestSocket;
133
+ }
134
+ declare function testWebSocket(fetch: (request: Request, env: object) => Promise<Response>, path: string, options?: TestWebSocketOptions): Promise<TestWebSocketResult>;
135
+ //#endregion
136
+ export { Owner as a, UpgradeHubOptions as c, upgradeWebSocket as d, WebSocketData as f, testWebSocket as i, UpgradeRouter as l, WebSocketServerLike as m, TestWebSocketOptions as n, UpgradeEmitter as o, WebSocketLike as p, TestWebSocketResult as r, UpgradeHub as s, TestSocket as t, RequestError as u };