acrylblends 0.8.1 → 0.9.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +83 -7
- package/dist/index.js.map +1 -1
- package/lib/audit-BXnYsLge.d.ts +58 -0
- package/lib/cell-ChU41DZD.d.ts +32 -0
- package/lib/client-modules-CLvWOjbd.js +267 -0
- package/lib/connection-ZLEso7XL.d.ts +25 -0
- package/lib/conninfo.d.ts +1 -23
- package/lib/cordis/db-sqlite.d.ts +56 -0
- package/lib/cordis/db-sqlite.js +58 -0
- package/lib/cordis/db.d.ts +8 -0
- package/lib/cordis/db.js +1 -0
- package/lib/cordis/door.d.ts +159 -0
- package/lib/cordis/door.js +757 -0
- package/lib/cordis/inspect.d.ts +77 -0
- package/lib/cordis/inspect.js +149 -0
- package/lib/cordis/kv-db.d.ts +16 -0
- package/lib/cordis/kv-db.js +18 -0
- package/lib/cordis/kv-json.d.ts +16 -0
- package/lib/cordis/kv-json.js +44 -0
- package/lib/cordis/kv.d.ts +8 -0
- package/lib/cordis/kv.js +1 -0
- package/lib/cordis/node-server.d.ts +45 -0
- package/lib/cordis/node-server.js +93 -0
- package/lib/cordis/router-http.d.ts +56 -0
- package/lib/cordis/router-http.js +117 -0
- package/lib/cordis/router.d.ts +8 -0
- package/lib/cordis/router.js +7 -0
- package/lib/cordis/state/react.d.ts +6 -0
- package/lib/cordis/state/react.js +11 -0
- package/lib/cordis/state.d.ts +33 -0
- package/lib/cordis/state.js +163 -0
- package/lib/core-CaY8XDZ6.js +19 -0
- package/lib/database-DBHdwefi.d.ts +44 -0
- package/lib/db/conformance.d.ts +11 -0
- package/lib/db/conformance.js +267 -0
- package/lib/db/sqlite.d.ts +1 -1
- package/lib/db/sqlite.js +1 -196
- package/lib/db.d.ts +2 -1
- package/lib/{src-0hI7M7cN.js → deprecated-BP4fPt38.js} +0 -17
- package/lib/door/host.d.ts +24 -0
- package/lib/door/host.js +2 -0
- package/lib/door/page.d.ts +105 -0
- package/lib/door/page.js +1030 -0
- package/lib/door/protocol.d.ts +2 -0
- package/lib/door/protocol.js +197 -0
- package/lib/error-t4N2js2I.d.ts +8 -0
- package/lib/etag-DaKMOjty.js +135 -0
- package/lib/etag.js +1 -134
- package/lib/helpers-CvTauQT5.js +5 -0
- package/lib/http-exception-kpGJkmpi.d.ts +57 -0
- package/lib/http-exception.d.ts +1 -56
- package/lib/{index-BLTYsyc1.d.ts → index-BnGgRKTv.d.ts} +3 -39
- package/lib/index-BqVQkTnu.d.ts +98 -0
- package/lib/index-Dmst2QCQ.d.ts +44 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -1
- package/lib/jwk-LkzeoaU8.js +117 -0
- package/lib/jwk.js +1 -116
- package/lib/jwt-CskdhEW9.js +115 -0
- package/lib/jwt.js +2 -114
- package/lib/kv/conformance.d.ts +13 -0
- package/lib/kv/conformance.js +301 -0
- package/lib/kv/db.d.ts +2 -2
- package/lib/kv/json.d.ts +1 -1
- package/lib/kv-9f-AlRKN.d.ts +39 -0
- package/lib/kv.d.ts +2 -1
- package/lib/language-D9a4zqvn.js +198 -0
- package/lib/language.js +1 -197
- package/lib/middleware-CPQ8himN.js +12 -0
- package/lib/node-server/serve-static.d.ts +0 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -7
- package/lib/node-server.js +3 -1586
- package/lib/open-CMhnJODn.js +197 -0
- package/lib/protocol-BizVt4J6.d.ts +144 -0
- package/lib/request-id-CKVvHOO1.js +34 -0
- package/lib/request-id.js +1 -33
- package/lib/secure-headers-mP3j6Hm6.js +163 -0
- package/lib/secure-headers.js +1 -162
- package/lib/{serve-static-B849HlX2.js → serve-static-3uo_9rjF.js} +1 -4
- package/lib/server-DvgMGtzo.js +1323 -0
- package/lib/snapshot-text-D3NspDyT.js +338 -0
- package/lib/src-CwABFr6r.js +328 -0
- package/lib/timeout.d.ts +1 -1
- package/lib/timing-Cky7C1Jf.js +183 -0
- package/lib/timing.js +1 -182
- package/package.json +89 -1
- package/plugins/acryl-db/plugin.json +6 -1
- package/plugins/acryl-db-sqlite/plugin.json +4 -0
- package/plugins/acryl-db-sqlite/src/facade/node.ts +1 -1
- package/plugins/acryl-db-sqlite/src/index.ts +2 -2
- package/plugins/acryl-db-sqlite/src/open.ts +2 -2
- package/plugins/acryl-door/plugin.json +4 -1
- package/plugins/acryl-door/src/host/http.ts +2 -2
- package/plugins/acryl-door/src/host/mcp-routes.ts +1 -1
- package/plugins/acryl-door/src/host/operator-routes.ts +1 -1
- package/plugins/acryl-door/src/host/page-routes.ts +3 -3
- package/plugins/acryl-door/src/index.ts +2 -2
- package/plugins/acryl-inspect/plugin.json +4 -1
- package/plugins/acryl-inspect/src/service.ts +2 -2
- package/plugins/acryl-kv/plugin.json +6 -1
- package/plugins/acryl-kv-db/plugin.json +4 -0
- package/plugins/acryl-kv-db/src/index.ts +1 -1
- package/plugins/acryl-kv-db/src/open.ts +3 -3
- package/plugins/acryl-kv-json/plugin.json +4 -0
- package/plugins/acryl-kv-json/src/index.ts +1 -1
- package/plugins/acryl-kv-json/src/open.ts +2 -2
- package/plugins/acryl-node-server/plugin.json +5 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +1 -1
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +1 -1
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +2 -2
- package/plugins/acryl-node-server/src/adapter/websocket.ts +2 -2
- package/plugins/acryl-node-server/src/client-modules.ts +2 -2
- package/plugins/acryl-node-server/src/index.ts +1 -1
- package/plugins/acryl-router/plugin.json +11 -1
- package/plugins/acryl-router/src/connection.d.ts +25 -0
- package/plugins/acryl-router/src/core/app-base.d.ts +215 -0
- package/plugins/acryl-router/src/core/app.d.ts +19 -0
- package/plugins/acryl-router/src/core/client/client.d.ts +4 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.d.ts +35 -0
- package/plugins/acryl-router/src/core/client/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/client/types.d.ts +229 -0
- package/plugins/acryl-router/src/core/client/utils.d.ts +18 -0
- package/plugins/acryl-router/src/core/compose.d.ts +14 -0
- package/plugins/acryl-router/src/core/context.d.ts +443 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.d.ts +30 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.d.ts +36 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.d.ts +25 -0
- package/plugins/acryl-router/src/core/helper/css/common.d.ts +59 -0
- package/plugins/acryl-router/src/core/helper/css/index.d.ts +80 -0
- package/plugins/acryl-router/src/core/helper/dev/index.d.ts +20 -0
- package/plugins/acryl-router/src/core/helper/factory/index.d.ts +82 -0
- package/plugins/acryl-router/src/core/helper/html/index.d.ts +8 -0
- package/plugins/acryl-router/src/core/helper/proxy/index.d.ts +73 -0
- package/plugins/acryl-router/src/core/helper/route/index.d.ts +75 -0
- package/plugins/acryl-router/src/core/helper/ssg/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.d.ts +44 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.d.ts +27 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.d.ts +91 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.d.ts +16 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.d.ts +8 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.d.ts +13 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.d.ts +3 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.d.ts +3 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/helper/testing/index.d.ts +13 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.d.ts +70 -0
- package/plugins/acryl-router/src/core/http-exception.d.ts +58 -0
- package/plugins/acryl-router/src/core/index.d.ts +37 -0
- package/plugins/acryl-router/src/core/jsx/base.d.ts +61 -0
- package/plugins/acryl-router/src/core/jsx/children.d.ts +9 -0
- package/plugins/acryl-router/src/core/jsx/components.d.ts +15 -0
- package/plugins/acryl-router/src/core/jsx/constants.d.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/context.d.ts +51 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.d.ts +10 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.d.ts +3 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.d.ts +83 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.d.ts +39 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.d.ts +16 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.d.ts +8 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.d.ts +7 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.d.ts +73 -0
- package/plugins/acryl-router/src/core/jsx/dom/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/jsx/hooks/index.d.ts +47 -0
- package/plugins/acryl-router/src/core/jsx/index.d.ts +76 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.d.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.d.ts +13 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.d.ts +735 -0
- package/plugins/acryl-router/src/core/jsx/streaming.d.ts +36 -0
- package/plugins/acryl-router/src/core/jsx/types.d.ts +36 -0
- package/plugins/acryl-router/src/core/jsx/utils.d.ts +4 -0
- package/plugins/acryl-router/src/core/request/constants.d.ts +1 -0
- package/plugins/acryl-router/src/core/request.d.ts +306 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.d.ts +6 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.d.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.d.ts +13 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.d.ts +17 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.d.ts +11 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.d.ts +8 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.d.ts +11 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.d.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.d.ts +7 -0
- package/plugins/acryl-router/src/core/router/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/router.d.ts +97 -0
- package/plugins/acryl-router/src/core/types.d.ts +573 -0
- package/plugins/acryl-router/src/core/utils/accept.d.ts +6 -0
- package/plugins/acryl-router/src/core/utils/body.d.ts +79 -0
- package/plugins/acryl-router/src/core/utils/buffer.d.ts +17 -0
- package/plugins/acryl-router/src/core/utils/color.d.ts +21 -0
- package/plugins/acryl-router/src/core/utils/concurrent.d.ts +11 -0
- package/plugins/acryl-router/src/core/utils/constants.d.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.d.ts +41 -0
- package/plugins/acryl-router/src/core/utils/crypto.d.ts +15 -0
- package/plugins/acryl-router/src/core/utils/handler.d.ts +6 -0
- package/plugins/acryl-router/src/core/utils/headers.d.ts +8 -0
- package/plugins/acryl-router/src/core/utils/html.d.ts +44 -0
- package/plugins/acryl-router/src/core/utils/http-status.d.ts +32 -0
- package/plugins/acryl-router/src/core/utils/mime.d.ts +70 -0
- package/plugins/acryl-router/src/core/utils/stream.d.ts +31 -0
- package/plugins/acryl-router/src/core/utils/types.d.ts +128 -0
- package/plugins/acryl-router/src/core/utils/url.d.ts +44 -0
- package/plugins/acryl-router/src/core/validator/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/validator/utils.d.ts +29 -0
- package/plugins/acryl-router/src/core/validator/validator.d.ts +16 -0
- package/plugins/acryl-router/src/helpers/index.d.ts +17 -0
- package/plugins/acryl-router/src/helpers/schema-validator.d.ts +54 -0
- package/plugins/acryl-router-http/plugin.json +4 -1
- package/plugins/acryl-router-http/src/index.ts +1 -1
- package/plugins/acryl-state/plugin.json +5 -1
- package/plugins/acryl-state/src/service.ts +3 -3
- package/plugins/tsconfig.json +32 -20
- package/docs/framework/UPSTREAM.md +0 -3
- package/docs/framework/upstream.json +0 -9
- package/lib/index-D4FAsBZu.d.ts +0 -85
- package/plugins/acryl-db/upstream.json +0 -18
- package/plugins/acryl-db-sqlite/upstream.json +0 -21
- package/plugins/acryl-door/upstream.json +0 -28
- package/plugins/acryl-inspect/upstream.json +0 -18
- package/plugins/acryl-kv/upstream.json +0 -18
- package/plugins/acryl-kv-db/upstream.json +0 -21
- package/plugins/acryl-kv-json/upstream.json +0 -22
- package/plugins/acryl-node-server/upstream.json +0 -21
- package/plugins/acryl-router/upstream.json +0 -20
- package/plugins/acryl-router-http/upstream.json +0 -22
- package/plugins/acryl-state/NOTICE +0 -30
- package/plugins/acryl-state/upstream.json +0 -22
- package/plugins/desktop/upstream.json +0 -12
- package/plugins/desktop-web/upstream.json +0 -12
- package/templates/todo/upstream.json +0 -12
- /package/lib/{index-CTy4N7ng.d.ts → deprecated-CTy4N7ng.d.ts} +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { t as Acryl } from "./app-BM0rTCp7.js";
|
|
2
|
+
import { bodyLimit } from "./body-limit.js";
|
|
3
|
+
import { methodNotAllowed } from "./method-not-allowed.js";
|
|
4
|
+
import { r as streamSSE } from "./streaming-DHST3Scg.js";
|
|
5
|
+
import { loopbackAuthority, loopbackOnly } from "./loopback-only.js";
|
|
6
|
+
import "./helpers-CvTauQT5.js";
|
|
7
|
+
import { UiControlError, WINDOW_ID, parseChannelToHost } from "./door/protocol.js";
|
|
8
|
+
import "./src-CwABFr6r.js";
|
|
9
|
+
import "./middleware-CPQ8himN.js";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { timingSafeEqual } from "node:crypto";
|
|
12
|
+
import { closeSync, mkdirSync, openSync, readFileSync, renameSync, statSync, writeSync } from "node:fs";
|
|
13
|
+
//#region ../../plugins/acryl-door/src/host/channel.ts
|
|
14
|
+
/**
|
|
15
|
+
* The Host's side of the page channel: the connected pages of the app, and calls that go to the current one (moved from
|
|
16
|
+
* `acryl-agent-control`, spec 041).
|
|
17
|
+
*
|
|
18
|
+
* The Host cannot reach into a page, so each page opens one event stream to the Host and answers calls by POST. With several pages open,
|
|
19
|
+
* the one the user focused last is the one the agent drives. A page that reconnects with the same window id replaces its old stream.
|
|
20
|
+
*/
|
|
21
|
+
/** How long a call may take before it is given up (a wait may take up to 10 seconds itself). */
|
|
22
|
+
const DEFAULT_CALL_TIMEOUT_MS = 3e4;
|
|
23
|
+
var UiChannel = class {
|
|
24
|
+
connections = /* @__PURE__ */ new Map();
|
|
25
|
+
pending = /* @__PURE__ */ new Map();
|
|
26
|
+
nextId = 1;
|
|
27
|
+
clock = 0;
|
|
28
|
+
closed = false;
|
|
29
|
+
/**
|
|
30
|
+
* Register a page's stream. A stream already open for the same window is closed first, and its calls settle as `disconnected`.
|
|
31
|
+
* @returns the function to call when this stream ends.
|
|
32
|
+
*/
|
|
33
|
+
attach(windowId, focused, stream) {
|
|
34
|
+
const previous = this.connections.get(windowId);
|
|
35
|
+
if (previous !== void 0) this.drop(previous, true);
|
|
36
|
+
const connection = {
|
|
37
|
+
stream,
|
|
38
|
+
windowId,
|
|
39
|
+
focused,
|
|
40
|
+
lastActive: ++this.clock
|
|
41
|
+
};
|
|
42
|
+
this.connections.set(windowId, connection);
|
|
43
|
+
return () => {
|
|
44
|
+
if (this.connections.get(windowId) === connection) this.drop(connection, false);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** A message a page sent by POST. Only the window that was asked may answer a call, so another page cannot forge a result. */
|
|
48
|
+
receive(windowId, message) {
|
|
49
|
+
const connection = this.connections.get(windowId);
|
|
50
|
+
if (connection === void 0) return;
|
|
51
|
+
if (message.t === "focus") {
|
|
52
|
+
connection.focused = message.focused;
|
|
53
|
+
if (message.focused) connection.lastActive = ++this.clock;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const entry = this.pending.get(message.id);
|
|
57
|
+
if (entry === void 0 || entry.connection !== connection) return;
|
|
58
|
+
if (message.t === "result") this.settle(message.id, message.value);
|
|
59
|
+
else this.settle(message.id, void 0, new UiControlError(message.code, message.message));
|
|
60
|
+
}
|
|
61
|
+
/** True when at least one page is connected. */
|
|
62
|
+
get connected() {
|
|
63
|
+
return this.connections.size > 0;
|
|
64
|
+
}
|
|
65
|
+
/** The windows currently connected, most recently active first (for diagnostics and tests). */
|
|
66
|
+
windows() {
|
|
67
|
+
return this.byRecency().map(({ windowId, focused }) => ({
|
|
68
|
+
windowId,
|
|
69
|
+
focused
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Ask the current page to do something.
|
|
74
|
+
* @throws UiControlError `no-window`, `timeout`, `aborted`, `unloaded`, `disconnected`, or whatever the page refused with.
|
|
75
|
+
*/
|
|
76
|
+
call(request, options = {}) {
|
|
77
|
+
if (this.closed) return Promise.reject(new UiControlError("unloaded", "the door was unloaded"));
|
|
78
|
+
const target = this.byRecency()[0];
|
|
79
|
+
if (target === void 0) return Promise.reject(new UiControlError("no-window", "no page of the app is open to control"));
|
|
80
|
+
const signal = options.signal;
|
|
81
|
+
if (signal?.aborted === true) return Promise.reject(new UiControlError("aborted", "the call was cancelled"));
|
|
82
|
+
const id = this.nextId;
|
|
83
|
+
this.nextId += 1;
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
this.settle(id, void 0, new UiControlError("timeout", "the page did not answer in time"));
|
|
87
|
+
}, options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS);
|
|
88
|
+
const onAbort = () => {
|
|
89
|
+
this.settle(id, void 0, new UiControlError("aborted", "the call was cancelled"));
|
|
90
|
+
};
|
|
91
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
92
|
+
this.pending.set(id, {
|
|
93
|
+
connection: target,
|
|
94
|
+
resolve,
|
|
95
|
+
reject,
|
|
96
|
+
timer,
|
|
97
|
+
cleanup: () => {
|
|
98
|
+
signal?.removeEventListener("abort", onAbort);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const frame = {
|
|
102
|
+
t: "call",
|
|
103
|
+
id,
|
|
104
|
+
request
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
target.stream.send(JSON.stringify(frame));
|
|
108
|
+
} catch {
|
|
109
|
+
this.settle(id, void 0, new UiControlError("disconnected", "the page could not be reached"));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Close every page's stream and settle every pending call as `unloaded`. */
|
|
114
|
+
close() {
|
|
115
|
+
this.closed = true;
|
|
116
|
+
for (const id of [...this.pending.keys()]) this.settle(id, void 0, new UiControlError("unloaded", "the door was unloaded"));
|
|
117
|
+
for (const connection of [...this.connections.values()]) connection.stream.close();
|
|
118
|
+
this.connections.clear();
|
|
119
|
+
}
|
|
120
|
+
byRecency() {
|
|
121
|
+
return [...this.connections.values()].sort((a, b) => b.lastActive - a.lastActive);
|
|
122
|
+
}
|
|
123
|
+
/** Forget a connection; its calls in flight settle as `disconnected` at once rather than waiting for their timeout. */
|
|
124
|
+
drop(connection, closeStream) {
|
|
125
|
+
this.connections.delete(connection.windowId);
|
|
126
|
+
for (const [id, entry] of [...this.pending]) if (entry.connection === connection) this.settle(id, void 0, new UiControlError("disconnected", "the page went away before it answered"));
|
|
127
|
+
if (closeStream) connection.stream.close();
|
|
128
|
+
}
|
|
129
|
+
settle(id, value, failure) {
|
|
130
|
+
const entry = this.pending.get(id);
|
|
131
|
+
if (entry === void 0) return;
|
|
132
|
+
this.pending.delete(id);
|
|
133
|
+
clearTimeout(entry.timer);
|
|
134
|
+
entry.cleanup();
|
|
135
|
+
if (failure !== void 0) entry.reject(failure);
|
|
136
|
+
else if (value !== void 0) entry.resolve(value);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region ../../plugins/acryl-door/src/host/audit.ts
|
|
141
|
+
/**
|
|
142
|
+
* The audit log: one JSON line per door call, append-only, in the app's own `.acryl/audit/` (moved from `acryl-agent-control`, spec 041).
|
|
143
|
+
*
|
|
144
|
+
* It is the record of what the agent did to the app. It never holds text the agent typed or values it read,
|
|
145
|
+
* only which control was touched and how the call ended.
|
|
146
|
+
*/
|
|
147
|
+
const MAX_LOG_BYTES = 5 * 1024 * 1024;
|
|
148
|
+
/** The audit log below a folder the caller owns (the app's `.acryl/` here; ACRYL's app home for the DSH face). */
|
|
149
|
+
function auditPath(folder) {
|
|
150
|
+
return join(folder, "audit", "ui-control.jsonl");
|
|
151
|
+
}
|
|
152
|
+
var AuditLog = class {
|
|
153
|
+
path;
|
|
154
|
+
constructor(path) {
|
|
155
|
+
this.path = path;
|
|
156
|
+
}
|
|
157
|
+
/** Append one line; a failing disk never breaks the tool call. */
|
|
158
|
+
record(entry) {
|
|
159
|
+
try {
|
|
160
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
161
|
+
this.rotateIfLarge();
|
|
162
|
+
const fd = openSync(this.path, "a", 384);
|
|
163
|
+
try {
|
|
164
|
+
writeSync(fd, `${JSON.stringify(entry)}\n`);
|
|
165
|
+
} finally {
|
|
166
|
+
closeSync(fd);
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
/** The most recent entries, oldest first. */
|
|
171
|
+
recent(limit = 100) {
|
|
172
|
+
try {
|
|
173
|
+
return readFileSync(this.path, "utf8").split("\n").filter((line) => line !== "").slice(-limit).flatMap((line) => {
|
|
174
|
+
try {
|
|
175
|
+
return [JSON.parse(line)];
|
|
176
|
+
} catch {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
} catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
rotateIfLarge() {
|
|
185
|
+
try {
|
|
186
|
+
if (statSync(this.path).size >= MAX_LOG_BYTES) renameSync(this.path, `${this.path}.1`);
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region ../../plugins/acryl-door/src/host/http.ts
|
|
192
|
+
/**
|
|
193
|
+
* What every door route shares (moved from `acryl-agent-control`, spec 041). Two kinds of caller reach it, each with its own guard: the
|
|
194
|
+
* app's own page (same origin, on a loopback address), and an operator on this machine (`.acryl/bin/acryl`, an agent's shell) presenting
|
|
195
|
+
* this run's secret.
|
|
196
|
+
*/
|
|
197
|
+
/** Every door route lives below this path. */
|
|
198
|
+
const DOOR_PREFIX = "/api/acryl-door";
|
|
199
|
+
/** The cap on a JSON request body. */
|
|
200
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
201
|
+
/** The JSON body of a refusal. */
|
|
202
|
+
function error(message) {
|
|
203
|
+
return { error: message };
|
|
204
|
+
}
|
|
205
|
+
/** The app's own page, on a loopback address, and nothing else: another origin, another port and a script on the machine are refused. */
|
|
206
|
+
const ownPage = loopbackOnly({ callers: "own-page" });
|
|
207
|
+
/** A constant-time comparison of a `Bearer` header with the secret. */
|
|
208
|
+
function tokenMatches(header, secret) {
|
|
209
|
+
const presented = header?.startsWith("Bearer ") === true ? header.slice(7) : "";
|
|
210
|
+
const a = Buffer.from(presented);
|
|
211
|
+
const b = Buffer.from(secret);
|
|
212
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Only an operator on this machine: a loopback peer addressing this server's own port (`loopbackAuthority`), presenting this run's
|
|
216
|
+
* secret. Anything else is 403 `forbidden`, which the client reads as "refused: the app restarted and the secret rotated?".
|
|
217
|
+
*/
|
|
218
|
+
function operatorOnly(secret) {
|
|
219
|
+
return async (c, next) => {
|
|
220
|
+
if (loopbackAuthority(c) === void 0 || !tokenMatches(c.req.header("authorization"), secret)) return c.json(error("forbidden"), 403);
|
|
221
|
+
await next();
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** The headers every private answer carries, set after the handler so a refusal carries them too. */
|
|
225
|
+
const privateHeaders = async (c, next) => {
|
|
226
|
+
await next();
|
|
227
|
+
c.header("cache-control", "no-store");
|
|
228
|
+
c.header("x-content-type-options", "nosniff");
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* The door's routes as one app: each part's routes, every answer with the private headers, and a known path asked with another method
|
|
232
|
+
* answered 405 with its `Allow` before any guard runs. The middleware is scoped to the prefix, so it never wraps another plugin's routes.
|
|
233
|
+
*/
|
|
234
|
+
function privateApi(prefix, ...routes) {
|
|
235
|
+
const app = new Acryl();
|
|
236
|
+
app.use(`${prefix}/*`, privateHeaders, methodNotAllowed({
|
|
237
|
+
app,
|
|
238
|
+
onMethodNotAllowed: (c, allowed) => c.json(error("method not allowed"), 405, { allow: allowed.join(", ") })
|
|
239
|
+
}));
|
|
240
|
+
for (const part of routes) app.route("/", part);
|
|
241
|
+
return app;
|
|
242
|
+
}
|
|
243
|
+
/** Refuse a body over {@link MAX_BODY_BYTES} with 413 before the handler reads it. */
|
|
244
|
+
const jsonBody = bodyLimit({
|
|
245
|
+
maxSize: MAX_BODY_BYTES,
|
|
246
|
+
onError: (c) => c.json(error("request body is too large"), 413)
|
|
247
|
+
});
|
|
248
|
+
/** Returned by {@link readJson} for a body that is not JSON. */
|
|
249
|
+
const INVALID_JSON = Symbol("invalid JSON");
|
|
250
|
+
/** The parsed body, or {@link INVALID_JSON}. */
|
|
251
|
+
async function readJson(c) {
|
|
252
|
+
try {
|
|
253
|
+
return await c.req.json();
|
|
254
|
+
} catch {
|
|
255
|
+
return INVALID_JSON;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region ../../plugins/acryl-door/src/host/page-routes.ts
|
|
260
|
+
/**
|
|
261
|
+
* The page channel on the router: an event stream down to the page and POST up, so it needs no WebSocket upgrade on any adapter.
|
|
262
|
+
*
|
|
263
|
+
* `GET <prefix>/channel?window=<id>&focused=0|1` is the page's stream. The Host writes each call as `event: call` and a `ping` every
|
|
264
|
+
* heartbeat, so a page that hears nothing for a while knows the stream died and reconnects. `POST <prefix>/channel/answer` carries
|
|
265
|
+
* `{ window, message }` (a result, an error or a focus change). Both are for the app's own page only (same origin, loopback).
|
|
266
|
+
*/
|
|
267
|
+
/** How often the Host writes a `ping`; the page gives up on a stream that is silent for three of these. */
|
|
268
|
+
const HEARTBEAT_MS = 15e3;
|
|
269
|
+
function pageChannelRoutes(channel, options) {
|
|
270
|
+
const heartbeatMs = options.heartbeatMs ?? 15e3;
|
|
271
|
+
const page = options.hostnames === void 0 ? ownPage : loopbackOnly({
|
|
272
|
+
callers: "own-page",
|
|
273
|
+
hostnames: options.hostnames
|
|
274
|
+
});
|
|
275
|
+
return new Acryl().get(`${options.prefix}/channel`, page, (c) => {
|
|
276
|
+
const windowId = c.req.query("window");
|
|
277
|
+
if (windowId === void 0 || !WINDOW_ID.test(windowId)) return c.json(error("a window id is required"), 400);
|
|
278
|
+
const focused = c.req.query("focused") === "1";
|
|
279
|
+
const signal = c.req.raw.signal;
|
|
280
|
+
return streamSSE(c, async (stream) => {
|
|
281
|
+
let end = () => {};
|
|
282
|
+
const ended = new Promise((resolve) => {
|
|
283
|
+
end = resolve;
|
|
284
|
+
});
|
|
285
|
+
stream.onAbort(end);
|
|
286
|
+
signal.addEventListener("abort", end, { once: true });
|
|
287
|
+
const write = (event, data) => {
|
|
288
|
+
stream.writeSSE({
|
|
289
|
+
event,
|
|
290
|
+
data
|
|
291
|
+
}).catch(end);
|
|
292
|
+
};
|
|
293
|
+
const detach = channel.attach(windowId, focused, {
|
|
294
|
+
send: (data) => {
|
|
295
|
+
write("call", data);
|
|
296
|
+
},
|
|
297
|
+
close: end
|
|
298
|
+
});
|
|
299
|
+
const beat = setInterval(() => {
|
|
300
|
+
write("ping", "");
|
|
301
|
+
}, heartbeatMs);
|
|
302
|
+
try {
|
|
303
|
+
write("ready", "");
|
|
304
|
+
await ended;
|
|
305
|
+
} finally {
|
|
306
|
+
clearInterval(beat);
|
|
307
|
+
signal.removeEventListener("abort", end);
|
|
308
|
+
detach();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}).post(`${options.prefix}/channel/answer`, page, jsonBody, async (c) => {
|
|
312
|
+
const body = await readJson(c);
|
|
313
|
+
if (body === INVALID_JSON || typeof body !== "object" || body === null) return c.json(error("invalid request body"), 400);
|
|
314
|
+
const { window: windowId, message } = body;
|
|
315
|
+
const parsed = parseChannelToHost(message);
|
|
316
|
+
if (typeof windowId !== "string" || !WINDOW_ID.test(windowId) || parsed === null) return c.json(error("invalid message"), 400);
|
|
317
|
+
channel.receive(windowId, parsed);
|
|
318
|
+
return c.body(null, 204);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region ../../plugins/acryl-door/src/page/snapshot-text.ts
|
|
323
|
+
/**
|
|
324
|
+
* The snapshot as compact text for an agent: one line per element, indented by depth.
|
|
325
|
+
* @param more - how to ask for the rest when the page was cut, given the cursor to continue from.
|
|
326
|
+
*/
|
|
327
|
+
function renderSnapshot(snapshot, more = (cursor) => `call ui_snapshot with cursor ${String(cursor)}`) {
|
|
328
|
+
const lines = snapshot.nodes.map((node) => {
|
|
329
|
+
const level = node.level === void 0 ? "" : ` level=${String(node.level)}`;
|
|
330
|
+
const states = node.states.length === 0 ? "" : ` [${node.states.join(", ")}]`;
|
|
331
|
+
const value = node.value === void 0 ? "" : ` value=${JSON.stringify(node.value)}`;
|
|
332
|
+
return `${" ".repeat(Math.min(node.depth, 8))}- ${node.role}${node.name === "" ? "" : ` ${JSON.stringify(node.name)}`}${level}${states}${value} [ref=${node.ref}]`;
|
|
333
|
+
});
|
|
334
|
+
const rest = snapshot.nextCursor === void 0 ? "" : `\n(${String(snapshot.total - snapshot.nextCursor)} more elements; ${more(snapshot.nextCursor)})`;
|
|
335
|
+
return `Page: ${snapshot.title || "(untitled)"}\nSnapshot ${String(snapshot.generation)}, ${String(snapshot.nodes.length)} of ${String(snapshot.total)} elements:\n${lines.join("\n")}${rest}`;
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
export { INVALID_JSON as a, operatorOnly as c, AuditLog as d, auditPath as f, DOOR_PREFIX as i, privateApi as l, HEARTBEAT_MS as n, error as o, UiChannel as p, pageChannelRoutes as r, jsonBody as s, renderSnapshot as t, readJson as u };
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { HTTPException } from "./http-exception.js";
|
|
2
|
+
import { mergePath } from "./utils/url.js";
|
|
3
|
+
import { COMPOSED_HANDLER } from "./utils/constants.js";
|
|
4
|
+
import { t as TrieRouter } from "./trie-router-CfFbapzL.js";
|
|
5
|
+
import { t as Acryl } from "./app-BM0rTCp7.js";
|
|
6
|
+
import "./core-CaY8XDZ6.js";
|
|
7
|
+
import "./deprecated-BP4fPt38.js";
|
|
8
|
+
import "./connection-C7xgP1hP.js";
|
|
9
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
10
|
+
import z from "@deepseek-ai/schemastery";
|
|
11
|
+
//#region ../../plugins/acryl-router/src/service.ts
|
|
12
|
+
/**
|
|
13
|
+
* The ACRYL router service (`ctx.router`): plugin-owned `Acryl` mounts on Web-standard Request and Response.
|
|
14
|
+
*
|
|
15
|
+
* A plugin builds an ordinary `Acryl` and mounts it with `ctx.router.route(path, app)` from its own context, so the mount disappears when that
|
|
16
|
+
* plugin unloads. `fetch(request)` runs a request in process: Web and Desktop serve it behind HTTP through an adapter, the terminal UI calls
|
|
17
|
+
* it directly, so one handler serves every surface without opening a socket. Disposal refuses new requests, aborts in-flight ones, ends any
|
|
18
|
+
* response body still streaming and waits for all of it to settle.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_DISPOSE_GRACE_MS = 2e3;
|
|
21
|
+
/** Every field has a default, so a caller passes only what it changes: `ctx.plugin(plugin, { ... })` takes the partial shape. */
|
|
22
|
+
const Config = z.object({
|
|
23
|
+
trailingSlash: z.union(["strict", "ignore"]).default("strict").description("Whether `/a` and `/a/` are different paths."),
|
|
24
|
+
disposeGraceMs: z.number().min(0).default(DEFAULT_DISPOSE_GRACE_MS).description("How long disposal waits for aborted requests, their response bodies and their waitUntil tasks before it gives up on them.")
|
|
25
|
+
});
|
|
26
|
+
/** Stable Cordis plugin name, equal to the package name and the Loader row id. */
|
|
27
|
+
const name = "acryl-router";
|
|
28
|
+
/** The core's `app.route` with a structural app: at run time it reads only the routes and the app's own error handler. */
|
|
29
|
+
const copyRoutes = (app) => new Acryl().route("/", app);
|
|
30
|
+
/** Wait for `tasks`, including any added while waiting (a stream may register more after the handler returned). */
|
|
31
|
+
async function drain(tasks) {
|
|
32
|
+
let seen = 0;
|
|
33
|
+
while (seen < tasks.length) {
|
|
34
|
+
const batch = tasks.slice(seen);
|
|
35
|
+
seen = tasks.length;
|
|
36
|
+
await Promise.allSettled(batch);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var Router = class extends Service {
|
|
40
|
+
/** Everything that changes after construction, kept in one object so the service's live state is in one place. */
|
|
41
|
+
state = {
|
|
42
|
+
mounts: /* @__PURE__ */ new Set(),
|
|
43
|
+
errorHandler: void 0,
|
|
44
|
+
notFoundHandler: void 0,
|
|
45
|
+
composed: void 0,
|
|
46
|
+
closing: false
|
|
47
|
+
};
|
|
48
|
+
flights = /* @__PURE__ */ new Set();
|
|
49
|
+
/** The flight of each request the router dispatched, keyed by the request its handlers see as `c.req.raw`. */
|
|
50
|
+
flightOf = /* @__PURE__ */ new WeakMap();
|
|
51
|
+
strict;
|
|
52
|
+
graceMs;
|
|
53
|
+
logError;
|
|
54
|
+
constructor(ctx, config = {}) {
|
|
55
|
+
super(ctx, "router");
|
|
56
|
+
const { trailingSlash = "strict", disposeGraceMs = DEFAULT_DISPOSE_GRACE_MS } = config;
|
|
57
|
+
this.strict = trailingSlash === "strict";
|
|
58
|
+
this.graceMs = disposeGraceMs;
|
|
59
|
+
this.logError = (error, c) => {
|
|
60
|
+
if (error instanceof HTTPException) return error.getResponse();
|
|
61
|
+
if (c.req.raw.signal.aborted) return new Response("Client Closed Request", { status: 499 });
|
|
62
|
+
ctx.logger.error(`acryl-router: handler failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
return c.text("Internal Server Error", 500);
|
|
64
|
+
};
|
|
65
|
+
ctx.effect(() => async () => {
|
|
66
|
+
this.state.closing = true;
|
|
67
|
+
await this.end([...this.flights], "disposal");
|
|
68
|
+
}, "acryl-router: abort and settle in-flight requests");
|
|
69
|
+
}
|
|
70
|
+
/** Registrations currently held (mounts, and the router-wide error and not-found handlers); a leak check for reload tests. */
|
|
71
|
+
get size() {
|
|
72
|
+
return this.state.mounts.size + (this.state.errorHandler === void 0 ? 0 : 1) + (this.state.notFoundHandler === void 0 ? 0 : 1);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Serve `app` under `path` (`'/'` for its own paths as they are). The mount belongs to the calling plugin and goes away when it unloads;
|
|
76
|
+
* the returned function removes it sooner. Mounts are composed in the order they were made. Like the core's own `app.route`, the routes
|
|
77
|
+
* are copied now: one the app registers after this call is not served (mount again, or finish the app first).
|
|
78
|
+
*/
|
|
79
|
+
route(path, app) {
|
|
80
|
+
const mount = this.snapshot(path, app);
|
|
81
|
+
this.state.mounts.add(mount);
|
|
82
|
+
this.state.composed = void 0;
|
|
83
|
+
const remove = () => {
|
|
84
|
+
if (!this.state.mounts.delete(mount)) return Promise.resolve();
|
|
85
|
+
this.state.composed = void 0;
|
|
86
|
+
return this.end([...this.flights].filter((flight) => (flight.running.get(mount) ?? 0) > 0 || flight.producer === mount), `removing ${path}`);
|
|
87
|
+
};
|
|
88
|
+
const served = new Set(app.routes.map((route) => `${route.method} ${mergePath(path, route.path)}`));
|
|
89
|
+
const owner = this.ctx;
|
|
90
|
+
owner.effect(function* () {
|
|
91
|
+
for (const route of served) yield owner.effect(() => () => {}, `acryl-router: serves ${route}`);
|
|
92
|
+
yield remove;
|
|
93
|
+
}, `acryl-router: route ${path}`);
|
|
94
|
+
return () => {
|
|
95
|
+
remove();
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The mount's own copy of `app`'s routes as they are now (like the core's `app.route`), each handler wrapped so the router knows which
|
|
100
|
+
* mount's code a request is in. The core's `app.route` runs first so a sub-app's own `onError` still applies to its routes.
|
|
101
|
+
*/
|
|
102
|
+
snapshot(path, app) {
|
|
103
|
+
const copy = copyRoutes(app);
|
|
104
|
+
const tagged = new Acryl();
|
|
105
|
+
const mount = {
|
|
106
|
+
path,
|
|
107
|
+
app: tagged
|
|
108
|
+
};
|
|
109
|
+
for (const route of copy.routes) {
|
|
110
|
+
const inner = route.handler;
|
|
111
|
+
const handler = async (c, next) => {
|
|
112
|
+
const flight = this.flightOf.get(c.req.raw);
|
|
113
|
+
if (flight === void 0) return inner(c, next);
|
|
114
|
+
let calledNext = false;
|
|
115
|
+
flight.running.set(mount, (flight.running.get(mount) ?? 0) + 1);
|
|
116
|
+
try {
|
|
117
|
+
const result = await inner(c, () => {
|
|
118
|
+
calledNext = true;
|
|
119
|
+
return next();
|
|
120
|
+
});
|
|
121
|
+
if (!calledNext && result instanceof Response) flight.producer = mount;
|
|
122
|
+
return result;
|
|
123
|
+
} finally {
|
|
124
|
+
flight.running.set(mount, (flight.running.get(mount) ?? 1) - 1);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
handler[COMPOSED_HANDLER] = inner;
|
|
128
|
+
tagged.on(route.method, route.path, handler);
|
|
129
|
+
const registered = tagged.routes.at(-1);
|
|
130
|
+
if (registered !== void 0) registered.basePath = route.basePath;
|
|
131
|
+
}
|
|
132
|
+
return mount;
|
|
133
|
+
}
|
|
134
|
+
/** Abort `flights`, cut their bodies short and wait for them to settle, at most `disposeGraceMs`. */
|
|
135
|
+
async end(flights, why) {
|
|
136
|
+
if (flights.length === 0) return;
|
|
137
|
+
for (const flight of flights) {
|
|
138
|
+
flight.controller.abort();
|
|
139
|
+
flight.endBody();
|
|
140
|
+
}
|
|
141
|
+
let timer;
|
|
142
|
+
const gaveUp = new Promise((resolve) => {
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
resolve("gave-up");
|
|
145
|
+
}, this.graceMs);
|
|
146
|
+
});
|
|
147
|
+
const outcome = await Promise.race([Promise.allSettled(flights.map((flight) => flight.settled)).then(() => "settled"), gaveUp]);
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
if (outcome === "gave-up") this.ctx.logger.warn(`acryl-router: ${String(flights.length)} request(s) did not finish ${String(this.graceMs)} ms after abort (${why}); continued without them`);
|
|
150
|
+
}
|
|
151
|
+
/** Turn errors no mounted app handled into responses. One handler per router; the calling plugin owns it. */
|
|
152
|
+
onError(handler) {
|
|
153
|
+
if (this.state.errorHandler !== void 0) throw new Error("router: an error handler is already registered");
|
|
154
|
+
this.state.errorHandler = handler;
|
|
155
|
+
this.state.composed = void 0;
|
|
156
|
+
const remove = () => {
|
|
157
|
+
if (this.state.errorHandler !== handler) return;
|
|
158
|
+
this.state.errorHandler = void 0;
|
|
159
|
+
this.state.composed = void 0;
|
|
160
|
+
};
|
|
161
|
+
this.ctx.effect(() => remove, "acryl-router: onError");
|
|
162
|
+
return remove;
|
|
163
|
+
}
|
|
164
|
+
/** Answer requests no route claimed. One handler per router; the calling plugin owns it. */
|
|
165
|
+
notFound(handler) {
|
|
166
|
+
if (this.state.notFoundHandler !== void 0) throw new Error("router: a not-found handler is already registered");
|
|
167
|
+
this.state.notFoundHandler = handler;
|
|
168
|
+
this.state.composed = void 0;
|
|
169
|
+
const remove = () => {
|
|
170
|
+
if (this.state.notFoundHandler !== handler) return;
|
|
171
|
+
this.state.notFoundHandler = void 0;
|
|
172
|
+
this.state.composed = void 0;
|
|
173
|
+
};
|
|
174
|
+
this.ctx.effect(() => remove, "acryl-router: notFound");
|
|
175
|
+
return remove;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Call the router the way a test calls an app: a path or URL string, a `URL` or a `Request`, with the usual init.
|
|
179
|
+
* A relative path is resolved against `http://localhost`.
|
|
180
|
+
*/
|
|
181
|
+
request(input, init, connection) {
|
|
182
|
+
const request = input instanceof Request ? input : new Request(new URL(String(input), "http://localhost"), init);
|
|
183
|
+
return this.fetch(request, connection);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Run a request in process. Never throws: failures become a 500, a disposed router answers 503. `connection` describes the transport
|
|
187
|
+
* (an HTTP adapter passes the socket's peer address and local port); an in-process caller omits it. Handlers read it with
|
|
188
|
+
* `connectionOf(c)`, abort with `c.req.raw.signal` and keep the request in flight with `c.executionCtx.waitUntil(task)`.
|
|
189
|
+
*/
|
|
190
|
+
fetch(request, connection = {}) {
|
|
191
|
+
if (this.state.closing) return Promise.resolve(new Response("Service Unavailable", { status: 503 }));
|
|
192
|
+
const controller = new AbortController();
|
|
193
|
+
const forward = () => controller.abort();
|
|
194
|
+
if (request.signal.aborted) controller.abort();
|
|
195
|
+
else request.signal.addEventListener("abort", forward, { once: true });
|
|
196
|
+
const tasks = [];
|
|
197
|
+
const executionCtx = {
|
|
198
|
+
waitUntil: (task) => {
|
|
199
|
+
tasks.push(task);
|
|
200
|
+
},
|
|
201
|
+
passThroughOnException: () => {},
|
|
202
|
+
props: {}
|
|
203
|
+
};
|
|
204
|
+
const env = { connection };
|
|
205
|
+
let bodyDone = Promise.resolve();
|
|
206
|
+
let endBody = () => {};
|
|
207
|
+
let resolveSettled;
|
|
208
|
+
const flight = {
|
|
209
|
+
controller,
|
|
210
|
+
running: /* @__PURE__ */ new Map(),
|
|
211
|
+
producer: void 0,
|
|
212
|
+
endBody: () => {
|
|
213
|
+
endBody();
|
|
214
|
+
},
|
|
215
|
+
settled: new Promise((resolve) => {
|
|
216
|
+
resolveSettled = resolve;
|
|
217
|
+
})
|
|
218
|
+
};
|
|
219
|
+
const inner = new Request(request, { signal: controller.signal });
|
|
220
|
+
this.flightOf.set(inner, flight);
|
|
221
|
+
const response = this.dispatch(inner, env, executionCtx).then((raw) => {
|
|
222
|
+
const tracked = this.track(raw);
|
|
223
|
+
bodyDone = tracked.done;
|
|
224
|
+
endBody = tracked.end;
|
|
225
|
+
if (controller.signal.aborted) tracked.end();
|
|
226
|
+
return tracked.response;
|
|
227
|
+
});
|
|
228
|
+
const settled = response.then(async () => {
|
|
229
|
+
await bodyDone;
|
|
230
|
+
await drain(tasks);
|
|
231
|
+
});
|
|
232
|
+
resolveSettled(settled);
|
|
233
|
+
this.flights.add(flight);
|
|
234
|
+
settled.finally(() => {
|
|
235
|
+
request.signal.removeEventListener("abort", forward);
|
|
236
|
+
this.flights.delete(flight);
|
|
237
|
+
});
|
|
238
|
+
return response;
|
|
239
|
+
}
|
|
240
|
+
async dispatch(request, env, executionCtx) {
|
|
241
|
+
try {
|
|
242
|
+
return await this.app().fetch(request, env, executionCtx);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.ctx.logger.error(`acryl-router: request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
245
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
app() {
|
|
249
|
+
if (this.state.composed !== void 0) return this.state.composed;
|
|
250
|
+
const root = new Acryl({
|
|
251
|
+
strict: this.strict,
|
|
252
|
+
router: new TrieRouter()
|
|
253
|
+
});
|
|
254
|
+
for (const mount of this.state.mounts) root.route(mount.path, mount.app);
|
|
255
|
+
root.onError(this.state.errorHandler ?? this.logError);
|
|
256
|
+
if (this.state.notFoundHandler !== void 0) root.notFound(this.state.notFoundHandler);
|
|
257
|
+
this.state.composed = root;
|
|
258
|
+
return root;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Hand the caller a body the router can end. Reading it passes the handler's chunks through; disposal cancels the handler's stream (which runs
|
|
262
|
+
* the stream helpers' own abort cleanup) and closes the caller's side, so a stream never outlives the plugin that served it.
|
|
263
|
+
*/
|
|
264
|
+
track(response) {
|
|
265
|
+
const source = response.body;
|
|
266
|
+
if (source === null) return {
|
|
267
|
+
response,
|
|
268
|
+
done: Promise.resolve(),
|
|
269
|
+
end: () => {}
|
|
270
|
+
};
|
|
271
|
+
const reader = source.getReader();
|
|
272
|
+
let finish;
|
|
273
|
+
const done = new Promise((resolve) => {
|
|
274
|
+
finish = resolve;
|
|
275
|
+
});
|
|
276
|
+
let ended = false;
|
|
277
|
+
let sink;
|
|
278
|
+
const end = () => {
|
|
279
|
+
if (ended) return;
|
|
280
|
+
ended = true;
|
|
281
|
+
finish();
|
|
282
|
+
};
|
|
283
|
+
const endEarly = () => {
|
|
284
|
+
if (ended) return;
|
|
285
|
+
reader.cancel(/* @__PURE__ */ new Error("acryl-router: disposed")).catch(() => {});
|
|
286
|
+
try {
|
|
287
|
+
sink?.close();
|
|
288
|
+
} catch {}
|
|
289
|
+
end();
|
|
290
|
+
};
|
|
291
|
+
const body = new ReadableStream({
|
|
292
|
+
start: (controller) => {
|
|
293
|
+
sink = controller;
|
|
294
|
+
},
|
|
295
|
+
pull: async (controller) => {
|
|
296
|
+
try {
|
|
297
|
+
const chunk = await reader.read();
|
|
298
|
+
if (ended) return;
|
|
299
|
+
if (chunk.done) {
|
|
300
|
+
controller.close();
|
|
301
|
+
end();
|
|
302
|
+
} else controller.enqueue(chunk.value);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (!ended) controller.error(error);
|
|
305
|
+
end();
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
cancel: async (reason) => {
|
|
309
|
+
end();
|
|
310
|
+
await reader.cancel(reason);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
response: new Response(body, {
|
|
315
|
+
status: response.status,
|
|
316
|
+
statusText: response.statusText,
|
|
317
|
+
headers: response.headers
|
|
318
|
+
}),
|
|
319
|
+
done,
|
|
320
|
+
end: endEarly
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
function apply(ctx, config = {}) {
|
|
325
|
+
new Router(ctx, config);
|
|
326
|
+
}
|
|
327
|
+
//#endregion
|
|
328
|
+
export { name as i, Router as n, apply as r, Config as t };
|
package/lib/timeout.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { L as MiddlewareHandler, t as Context } from "./context-DS3fYyG-.js";
|
|
2
|
-
import { HTTPException } from "./http-exception.js";
|
|
2
|
+
import { t as HTTPException } from "./http-exception-kpGJkmpi.js";
|
|
3
3
|
|
|
4
4
|
//#region ../../plugins/acryl-router/src/core/middleware/timeout/index.d.ts
|
|
5
5
|
type HTTPExceptionFunction = (context: Context) => HTTPException;
|