acrylblends 1.0.4 → 1.0.5
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/docs/framework/agents/index.md +6 -4
- package/lib/accept-CNNHu4Cf.js +193 -0
- package/lib/{accepts-XBuZPVRI.js → accepts-Btq83xPd.js} +1 -1
- package/lib/accepts.js +1 -1
- package/lib/acryl-base.js +1 -331
- package/lib/adapter-CaZ6FZUU.js +37 -0
- package/lib/adapter.js +1 -36
- package/lib/{app-BM0rTCp7.js → app-CQcGWjrk.js} +4 -4
- package/lib/app-base-CpRMZL4v.js +332 -0
- package/lib/{base-NSN_SNv-.js → base-D_6kfOr-.js} +1 -1
- package/lib/basic-auth-C0HTRpXh.js +20 -0
- package/lib/basic-auth-DFLflXNe.js +95 -0
- package/lib/basic-auth.js +1 -94
- package/lib/bearer-auth-Cwxt-vgS.js +94 -0
- package/lib/bearer-auth.js +1 -93
- package/lib/body-D7f4e7w_.js +94 -0
- package/lib/body-limit-WmIjWRLt.js +66 -0
- package/lib/body-limit.js +1 -65
- package/lib/buffer-CkEOJCcY.js +51 -0
- package/lib/bun.js +3 -3
- package/lib/cache-B0bHH7s5.js +169 -0
- package/lib/cache.js +1 -168
- package/lib/{client-DDLXwhBo.js → client-BrMFnMEd.js} +1 -1
- package/lib/{client-modules-CLvWOjbd.js → client-modules-drj4mjhj.js} +4 -4
- package/lib/client.js +1 -1
- package/lib/color-COa3DplO.js +37 -0
- package/lib/combine-TWuGdcbC.js +137 -0
- package/lib/combine.js +1 -136
- package/lib/{components-cEW3ovK0.js → components-B2ka9Ax1.js} +4 -4
- package/lib/{components-JXVm45kK.js → components-PVXwaOaC.js} +2 -2
- package/lib/compress-BzWqsHF1.js +79 -0
- package/lib/compress-C-9vK4B2.d.ts +11 -0
- package/lib/compress-DCyrrQ5D.js +11 -0
- package/lib/compress.d.ts +1 -1
- package/lib/compress.js +2 -78
- package/lib/concurrent-DrF3d2c-.js +30 -0
- package/lib/conninfo-BCROmdnf.d.ts +11 -0
- package/lib/conninfo-CWJwieh4.js +19 -0
- package/lib/constants-CBSEudrb.js +7 -0
- package/lib/{context-DDO0cfuP.js → context-CkOh7Ygv.js} +2 -2
- package/lib/context-storage-iuPoI_CR.js +52 -0
- package/lib/context-storage.js +1 -51
- package/lib/cookie-B1LlkzOe.js +125 -0
- package/lib/cookie-BHIduy2T.js +82 -0
- package/lib/cookie.js +1 -81
- package/lib/cordis/db-sqlite.d.ts +1 -1
- package/lib/cordis/door.d.ts +9 -1
- package/lib/cordis/door.js +39 -8
- package/lib/cordis/inspect.js +4 -4
- package/lib/cordis/kv-db.d.ts +1 -1
- package/lib/cordis/kv-db.js +1 -1
- package/lib/cordis/kv-json.d.ts +1 -1
- package/lib/cordis/kv-json.js +1 -1
- package/lib/cordis/node-server.d.ts +1 -1
- package/lib/cordis/node-server.js +3 -116
- package/lib/cordis/router-http.js +2 -2
- package/lib/cordis/router.js +4 -4
- package/lib/cordis/state.js +7 -7
- package/lib/{core-CaY8XDZ6.js → core-BOu1jYua.js} +1 -1
- package/lib/cors-CbAQ-8qU.js +106 -0
- package/lib/cors.js +1 -105
- package/lib/crypto-iVP-N-tS.js +34 -0
- package/lib/csrf-DqqOZtgy.js +101 -0
- package/lib/csrf.js +1 -100
- package/lib/css-C5CPVMvM.js +134 -0
- package/lib/css.js +2 -133
- package/lib/db/sqlite.d.ts +1 -25
- package/lib/dev-CLgu-JRe.js +50 -0
- package/lib/dev.js +1 -49
- package/lib/dom-YjND9Jt3.js +74 -0
- package/lib/door/host.js +1 -1
- package/lib/door/page.js +1 -1
- package/lib/door/protocol.js +1 -212
- package/lib/encode-BhbNLrjp.js +33 -0
- package/lib/factory-CLdSf-xS.js +30 -0
- package/lib/factory.js +1 -29
- package/lib/handler-C0wZ0KwO.js +12 -0
- package/lib/helpers-BEjcdbgw.js +5 -0
- package/lib/{hooks-w72iLeFR.js → hooks-CfC07ri0.js} +1 -1
- package/lib/html-CgNa89xw.js +31 -0
- package/lib/html-DGExBFNg.js +103 -0
- package/lib/html.d.ts +2 -5
- package/lib/html.js +2 -30
- package/lib/http-exception-MjWkT89V.js +54 -0
- package/lib/http-exception.js +1 -53
- package/lib/index-dAzQsJG9.d.ts +6 -0
- package/lib/index.js +3 -3
- package/lib/ip-restriction-jKtKCRM_.js +166 -0
- package/lib/ip-restriction.js +1 -165
- package/lib/ipaddr-DCoIo528.js +238 -0
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +2 -1
- package/lib/jsx/dom/server.js +3 -3
- package/lib/jsx/dom.js +5 -73
- package/lib/jsx/jsx-dev-runtime.d.ts +2 -5
- package/lib/jsx/jsx-dev-runtime.js +2 -13
- package/lib/jsx/jsx-runtime.d.ts +2 -2
- package/lib/jsx/jsx-runtime.js +4 -4
- package/lib/jsx/streaming.js +1 -1
- package/lib/jsx-CUQgttxU.js +47 -0
- package/lib/jsx-dev-runtime-B4WTyq8r.js +14 -0
- package/lib/{jsx-dev-runtime-CWLRA2F3.js → jsx-dev-runtime-BLfA-EhI.js} +2 -2
- package/lib/jsx-dev-runtime-CGt4kNYr.d.ts +6 -0
- package/lib/jsx-renderer-CfifM4hT.js +99 -0
- package/lib/jsx-renderer.js +1 -98
- package/lib/jsx-runtime-COCRWBJ9.js +2 -0
- package/lib/jsx.js +5 -46
- package/lib/{jwk-LkzeoaU8.js → jwk-CDPpYQpD.js} +3 -3
- package/lib/jwk.js +1 -1
- package/lib/{jwt-BGJBw5AF.js → jwt-BXoIA2fQ.js} +2 -2
- package/lib/{jwt-CskdhEW9.js → jwt-lyS4Q3mT.js} +3 -3
- package/lib/jwt.js +2 -2
- package/lib/kv/db.d.ts +1 -10
- package/lib/kv/db.js +1 -56
- package/lib/kv/json.d.ts +1 -9
- package/lib/kv/json.js +1 -223
- package/lib/{language-D9a4zqvn.js → language-DeGRtLJt.js} +2 -2
- package/lib/language.js +1 -1
- package/lib/{linear-router-C3FPib6h.js → linear-router-CmZn_JLV.js} +2 -2
- package/lib/logger-Ckxu8diz.js +54 -0
- package/lib/logger.js +1 -53
- package/lib/loopback-only-BbasoF7R.js +90 -0
- package/lib/loopback-only.js +1 -89
- package/lib/method-not-allowed-TO2_hnfl.js +82 -0
- package/lib/method-not-allowed.js +1 -81
- package/lib/method-override-DnZKfJbV.js +103 -0
- package/lib/method-override.js +1 -102
- package/lib/middleware-BfG_fp8P.js +12 -0
- package/lib/mime-BnScXkQn.js +77 -0
- package/lib/mount-QkK_3NtJ.js +67 -0
- package/lib/mount.js +1 -66
- package/lib/node-server/conninfo.d.ts +1 -10
- package/lib/node-server/conninfo.js +1 -18
- package/lib/node-server/register.js +25 -2
- package/lib/node-server/serve-static.d.ts +1 -17
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +2 -2
- package/lib/node-server.js +4 -4
- package/lib/open-BqK4D2Wn.d.ts +11 -0
- package/lib/open-But9E2rL.js +57 -0
- package/lib/open-CULVJgOc.d.ts +12 -0
- package/lib/open-Dp4sxspc.js +224 -0
- package/lib/open-T7kgbO2O.d.ts +27 -0
- package/lib/{pattern-router-HZxjDGEQ.js → pattern-router-CJBecJBd.js} +1 -1
- package/lib/powered-by-CXQNjNIX.js +24 -0
- package/lib/powered-by.js +1 -23
- package/lib/pretty-json-CeC1yvYt.js +39 -0
- package/lib/pretty-json.js +1 -38
- package/lib/protocol-Dt0AZBOz.js +213 -0
- package/lib/proxy-C2w_mH7k.js +121 -0
- package/lib/proxy.js +1 -120
- package/lib/quick.js +4 -4
- package/lib/{reg-exp-router-DC1R3qoY.js → reg-exp-router-C_WV84eV.js} +2 -2
- package/lib/request-DvMgGtlW.js +323 -0
- package/lib/request.js +1 -322
- package/lib/route-D8m-a2I1.js +126 -0
- package/lib/route.js +1 -125
- package/lib/router/linear-router.js +1 -1
- package/lib/router/pattern-router.js +1 -1
- package/lib/router/reg-exp-router.js +1 -1
- package/lib/router/smart-router.js +1 -1
- package/lib/router/trie-router.js +1 -1
- package/lib/router-Be_o6nFu.js +35 -0
- package/lib/router.js +1 -34
- package/lib/schema-validator-EK3cUUCt.js +22 -0
- package/lib/schema-validator.js +1 -21
- package/lib/{secure-headers-mP3j6Hm6.js → secure-headers-C5ZOcXqk.js} +1 -1
- package/lib/secure-headers.js +1 -1
- package/lib/serve-static-B7sqnftW.d.ts +18 -0
- package/lib/{serve-static-3uo_9rjF.js → serve-static-C1Nkj_xB.js} +2 -2
- package/lib/{serve-static-Cp5adzGo.js → serve-static-EiJf1zkO.js} +4 -4
- package/lib/serve-static.js +1 -1
- package/lib/{smart-router-C5CZ5XSM.js → smart-router-BjxkDQCE.js} +1 -1
- package/lib/{snapshot-text-BLz8nlSc.js → snapshot-text-Bbx43mN0.js} +9 -9
- package/lib/{src-dy3Fk9tA.js → src-BTBm4OPD.js} +6 -6
- package/lib/src-LX0E4ec4.js +117 -0
- package/lib/{ssg-BwjcA-Ge.js → ssg-BEl7MHqg.js} +5 -5
- package/lib/ssg.js +1 -1
- package/lib/stream-CzM_SHkq.js +88 -0
- package/lib/{streaming-DHST3Scg.js → streaming-BlDaph8U.js} +3 -3
- package/lib/streaming.js +1 -1
- package/lib/{test-websocket-D8WWqE5G.js → test-websocket-BLeEvybj.js} +2 -2
- package/lib/testing-BQx5T_UX.js +17 -0
- package/lib/testing.js +1 -16
- package/lib/timeout-FeqZ9A-0.js +42 -0
- package/lib/timeout.js +1 -41
- package/lib/tiny.js +2 -2
- package/lib/trailing-slash-DwTx-1sk.js +85 -0
- package/lib/trailing-slash.js +1 -84
- package/lib/{trie-router-CfFbapzL.js → trie-router-JydLKUa2.js} +2 -2
- package/lib/url-BPh4d6j-.js +197 -0
- package/lib/utils/accept.js +1 -192
- package/lib/utils/basic-auth.js +1 -19
- package/lib/utils/body.js +1 -93
- package/lib/utils/buffer.js +1 -50
- package/lib/utils/color.js +1 -36
- package/lib/utils/compress.d.ts +1 -10
- package/lib/utils/compress.js +1 -10
- package/lib/utils/concurrent.js +1 -29
- package/lib/utils/constants.js +1 -6
- package/lib/utils/cookie.js +1 -124
- package/lib/utils/crypto.js +1 -33
- package/lib/utils/encode.js +1 -32
- package/lib/utils/handler.js +1 -11
- package/lib/utils/html.js +1 -102
- package/lib/utils/ipaddr.js +1 -237
- package/lib/utils/jwt.js +1 -1
- package/lib/utils/mime.js +1 -76
- package/lib/utils/stream.js +1 -87
- package/lib/utils/url.js +1 -196
- package/lib/{validator-D84mq-dO.js → validator-Btiyt5VJ.js} +3 -3
- package/lib/validator.js +1 -1
- package/lib/websocket-DsyBIKVA.js +56 -0
- package/lib/ws.js +1 -55
- package/package.json +1 -1
- package/plugins/acryl-door/src/host/chat-tools.ts +2 -0
- package/plugins/acryl-door/src/host/door.ts +19 -2
- package/plugins/acryl-door/src/index.ts +15 -2
- package/plugins/acryl-node-server/src/register.ts +29 -2
- package/plugins/agent/model.ts +6 -1
- package/plugins/agent/tools.ts +12 -2
- package/plugins/agent-panel/client/describe.ts +44 -0
- package/plugins/agent-panel/client/main.ts +4 -1
- package/plugins/agent-panel/index.ts +22 -10
- package/plugins/desktop/attach.ts +72 -0
- package/plugins/desktop/main.ts +17 -4
- package/plugins/extensions/index.ts +3 -4
- package/plugins/extensions/stage.ts +33 -5
- package/plugins/openai/index.ts +1 -1
- package/templates/agent/AGENTS.md +2 -0
- package/templates/agent/blend.yaml +4 -1
- package/templates/agent/src/app.ts +2 -0
- package/templates/agent/src/routes.ts +1 -1
- package/templates/agent/template.json +1 -0
- package/templates/agent/test/builder.test.ts +26 -21
- package/lib/helpers-CvTauQT5.js +0 -5
- package/lib/middleware-CPQ8himN.js +0 -12
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { a as compareKeys, c as runChange, i as codePoints, o as decodeValue, r as checkKey, s as encodeValue } from "./src-DRFVEQQb.js";
|
|
2
|
+
//#region ../../plugins/acryl-kv-db/src/open.ts
|
|
3
|
+
const IDENTIFIER = /^[a-z_][a-z0-9_]*$/u;
|
|
4
|
+
/** The `kv` port over a `db`. It does not own the database: whoever opened it closes it, and calls after that reject. */
|
|
5
|
+
function kvOnDb(db, options = {}) {
|
|
6
|
+
const table = options.table ?? "kv";
|
|
7
|
+
if (!IDENTIFIER.test(table)) throw new TypeError(`acryl-kv-db: the table name must be a plain lower case SQL identifier, got ${JSON.stringify(table)}`);
|
|
8
|
+
let ready;
|
|
9
|
+
const ensure = () => {
|
|
10
|
+
ready ??= db.exec(`CREATE TABLE IF NOT EXISTS ${table} (key TEXT PRIMARY KEY, value TEXT NOT NULL)`).catch((error) => {
|
|
11
|
+
ready = void 0;
|
|
12
|
+
throw error;
|
|
13
|
+
});
|
|
14
|
+
return ready;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Run a statement after the table exists. The table's statement and this one are both handed to the database at once (its queue keeps
|
|
18
|
+
* their order), so a `close()` called right after a write cannot slip between them and leave the write on a closed database.
|
|
19
|
+
*/
|
|
20
|
+
const after = async (work) => {
|
|
21
|
+
const created = ensure();
|
|
22
|
+
const result = work();
|
|
23
|
+
const [, value] = await Promise.all([created, result]);
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
const upsert = `INSERT INTO ${table} (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value`;
|
|
27
|
+
return {
|
|
28
|
+
async get(key) {
|
|
29
|
+
checkKey(key);
|
|
30
|
+
const row = await after(() => db.get(`SELECT value FROM ${table} WHERE key = ?`, key));
|
|
31
|
+
return row === void 0 ? void 0 : decodeValue(row.value);
|
|
32
|
+
},
|
|
33
|
+
async set(key, value) {
|
|
34
|
+
checkKey(key);
|
|
35
|
+
const text = encodeValue(value);
|
|
36
|
+
await after(() => db.run(upsert, key, text));
|
|
37
|
+
},
|
|
38
|
+
async update(key, change) {
|
|
39
|
+
checkKey(key);
|
|
40
|
+
return after(() => db.transaction(async (tx) => {
|
|
41
|
+
const row = await tx.get(`SELECT value FROM ${table} WHERE key = ?`, key);
|
|
42
|
+
const text = encodeValue(runChange(change, row === void 0 ? void 0 : decodeValue(row.value)));
|
|
43
|
+
await tx.run(upsert, key, text);
|
|
44
|
+
return decodeValue(text);
|
|
45
|
+
}));
|
|
46
|
+
},
|
|
47
|
+
async delete(key) {
|
|
48
|
+
checkKey(key);
|
|
49
|
+
await after(() => db.run(`DELETE FROM ${table} WHERE key = ?`, key));
|
|
50
|
+
},
|
|
51
|
+
async list(prefix = "") {
|
|
52
|
+
return (await after(() => prefix === "" ? db.all(`SELECT key FROM ${table}`) : db.all(`SELECT key FROM ${table} WHERE substr(key, 1, ?) = ?`, codePoints(prefix), prefix))).map((row) => row.key).sort(compareKeys);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { kvOnDb as t };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as Db } from "./database-DBHdwefi.js";
|
|
2
|
+
import { n as Kv } from "./kv-9f-AlRKN.js";
|
|
3
|
+
|
|
4
|
+
//#region ../../plugins/acryl-kv-db/src/open.d.ts
|
|
5
|
+
interface KvOnDbOptions {
|
|
6
|
+
/** The table that holds the documents. Defaults to `kv`; a plain SQL identifier. */
|
|
7
|
+
readonly table?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The `kv` port over a `db`. It does not own the database: whoever opened it closes it, and calls after that reject. */
|
|
10
|
+
declare function kvOnDb(db: Db, options?: KvOnDbOptions): Kv;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { kvOnDb as n, KvOnDbOptions as t };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { a as compareKeys, c as runChange, o as decodeValue, r as checkKey, s as encodeValue } from "./src-DRFVEQQb.js";
|
|
2
|
+
import { mkdir, open, readFile, readdir, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
//#region ../../plugins/acryl-kv-json/src/open.ts
|
|
5
|
+
/**
|
|
6
|
+
* The plain way to open the JSON-file provider, for code without Cordis (an acrylblends app): `openKv({ path })` reads one JSON file into
|
|
7
|
+
* memory and resolves the `kv` port with the function that closes it. The Cordis plugin is a thin wrapper over this, so both run one code
|
|
8
|
+
* path. This entry imports nothing from Cordis.
|
|
9
|
+
*
|
|
10
|
+
* Every write persists the whole map: serialize to a temporary file in the same folder, flush it, rename it over the file, and flush the
|
|
11
|
+
* folder where the platform allows. The call resolves after the rename, so a crash leaves the old file or the new one, never half of one.
|
|
12
|
+
* Writes are grouped: the calls made in one turn of the event loop, and those that queue while a file write is in flight, are applied in
|
|
13
|
+
* call order and persisted together by one write, so a burst of calls costs one flush and one rename, not one each (on Windows each costs
|
|
14
|
+
* tens of milliseconds). One process owns the
|
|
15
|
+
* file: two writers would overwrite each other.
|
|
16
|
+
*/
|
|
17
|
+
/** The file's own format version, for a later change of layout. */
|
|
18
|
+
const FORMAT = 1;
|
|
19
|
+
/** Stray temporary files from a crash end in `.tmp` and start with the file's name. */
|
|
20
|
+
const tempPrefix = (file) => `${basename(file)}.`;
|
|
21
|
+
/** The file content for a map of key to JSON text, keys in code point order, so the same data is the same bytes. */
|
|
22
|
+
function serialize(entries) {
|
|
23
|
+
const body = [...entries.keys()].sort(compareKeys).map((key) => `${JSON.stringify(key)}:${entries.get(key)}`).join(",");
|
|
24
|
+
return `{"version":${FORMAT},"entries":{${body}}}\n`;
|
|
25
|
+
}
|
|
26
|
+
function unreadable(file, reason) {
|
|
27
|
+
return /* @__PURE__ */ new Error(`acryl-kv-json: ${file} is not a readable kv file (${reason}); it was not changed`);
|
|
28
|
+
}
|
|
29
|
+
/** The entries of a file, or none when it does not exist. A file that exists and does not parse is an error: it is never started over. */
|
|
30
|
+
async function load(file) {
|
|
31
|
+
let text;
|
|
32
|
+
try {
|
|
33
|
+
text = await readFile(file, "utf8");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(text);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw unreadable(file, error.message);
|
|
43
|
+
}
|
|
44
|
+
if (typeof parsed !== "object" || parsed === null || parsed.version !== FORMAT) throw unreadable(file, "not a version 1 kv file");
|
|
45
|
+
const entries = parsed.entries;
|
|
46
|
+
if (typeof entries !== "object" || entries === null || Array.isArray(entries)) throw unreadable(file, "no entries object");
|
|
47
|
+
return new Map(Object.entries(entries).map(([key, value]) => [key, JSON.stringify(value)]));
|
|
48
|
+
}
|
|
49
|
+
/** Remove the temporary files an earlier crash left beside the file. */
|
|
50
|
+
async function removeStrays(file) {
|
|
51
|
+
const prefix = tempPrefix(file);
|
|
52
|
+
for (const name of await readdir(dirname(file))) if (name.startsWith(prefix) && name.endsWith(".tmp")) await rm(join(dirname(file), name), { force: true });
|
|
53
|
+
}
|
|
54
|
+
const RENAME_RETRIES = 8;
|
|
55
|
+
/** Rename over the target. Windows refuses while a scanner or an indexer holds the target for a moment, so it is retried briefly. */
|
|
56
|
+
async function replace(from, to) {
|
|
57
|
+
for (let attempt = 0;; attempt += 1) try {
|
|
58
|
+
await rename(from, to);
|
|
59
|
+
return;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const code = error.code;
|
|
62
|
+
if (attempt >= RENAME_RETRIES || code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw error;
|
|
63
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 10 * (attempt + 1)));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Flush the folder entry of a renamed file (POSIX); Windows cannot open a folder, and a filesystem that refuses is no worse than before. */
|
|
67
|
+
async function flushFolder(folder) {
|
|
68
|
+
if (process.platform === "win32") return;
|
|
69
|
+
try {
|
|
70
|
+
const handle = await open(folder, "r");
|
|
71
|
+
try {
|
|
72
|
+
await handle.sync();
|
|
73
|
+
} finally {
|
|
74
|
+
await handle.close();
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
/** Open (and create, when missing) a JSON-file store. @throws when the file exists and is not a readable kv file. */
|
|
79
|
+
async function openKv(options) {
|
|
80
|
+
const file = resolve(options.path);
|
|
81
|
+
await mkdir(dirname(file), { recursive: true });
|
|
82
|
+
await removeStrays(file);
|
|
83
|
+
let state = await load(file);
|
|
84
|
+
let closed = false;
|
|
85
|
+
let sequence = 0;
|
|
86
|
+
/** Calls waiting for the next file write, in call order. */
|
|
87
|
+
let queue = [];
|
|
88
|
+
/** Whether the loop that writes the queue runs; cleared by the loop itself, so a call queued after it ends starts a new one. */
|
|
89
|
+
let draining = false;
|
|
90
|
+
/** The latest loop, for `close` to wait on. */
|
|
91
|
+
let drained = Promise.resolve();
|
|
92
|
+
const guard = () => {
|
|
93
|
+
if (closed) throw new Error(`acryl-kv-json: ${file} is closed`);
|
|
94
|
+
};
|
|
95
|
+
/** Persist a map: the file is the old one or this one, never between. */
|
|
96
|
+
const persist = async (next) => {
|
|
97
|
+
sequence += 1;
|
|
98
|
+
const temp = join(dirname(file), `${tempPrefix(file)}${process.pid}.${sequence}.tmp`);
|
|
99
|
+
try {
|
|
100
|
+
const handle = await open(temp, "w");
|
|
101
|
+
try {
|
|
102
|
+
await handle.writeFile(serialize(next), "utf8");
|
|
103
|
+
await handle.sync();
|
|
104
|
+
} finally {
|
|
105
|
+
await handle.close();
|
|
106
|
+
}
|
|
107
|
+
await replace(temp, file);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
await rm(temp, { force: true });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
await flushFolder(dirname(file));
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Write the queue until it is empty. Each round takes every call queued so far and applies them in order to one copy of the state. A call
|
|
116
|
+
* whose change throws rejects with its error and changes nothing, and the others in the round still apply. The copy becomes the state
|
|
117
|
+
* only once it is on disk. A failed file write rejects every call in its round and leaves the state as it was.
|
|
118
|
+
*/
|
|
119
|
+
const drain = async () => {
|
|
120
|
+
try {
|
|
121
|
+
await Promise.resolve();
|
|
122
|
+
await rounds();
|
|
123
|
+
} finally {
|
|
124
|
+
draining = false;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const rounds = async () => {
|
|
128
|
+
while (queue.length > 0) {
|
|
129
|
+
const round = queue;
|
|
130
|
+
queue = [];
|
|
131
|
+
const draft = new Map(state);
|
|
132
|
+
const applied = [];
|
|
133
|
+
for (const call of round) {
|
|
134
|
+
let change;
|
|
135
|
+
try {
|
|
136
|
+
change = call.change((key) => draft.get(key));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
call.reject(error);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (change.text === void 0) draft.delete(change.key);
|
|
142
|
+
else draft.set(change.key, change.text);
|
|
143
|
+
applied.push({
|
|
144
|
+
call,
|
|
145
|
+
result: change.result
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (applied.length === 0) continue;
|
|
149
|
+
try {
|
|
150
|
+
await persist(draft);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
for (const { call } of applied) call.reject(error);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
state = draft;
|
|
156
|
+
for (const { call, result } of applied) call.resolve(result);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
/** Queue one change behind every earlier call; it resolves once a file write that contains it is on disk. */
|
|
160
|
+
const write = (change) => {
|
|
161
|
+
guard();
|
|
162
|
+
const done = new Promise((resolveCall, rejectCall) => {
|
|
163
|
+
queue.push({
|
|
164
|
+
change,
|
|
165
|
+
resolve: resolveCall,
|
|
166
|
+
reject: rejectCall
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
if (!draining) {
|
|
170
|
+
draining = true;
|
|
171
|
+
drained = drain();
|
|
172
|
+
}
|
|
173
|
+
return done;
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
kv: {
|
|
177
|
+
async get(key) {
|
|
178
|
+
guard();
|
|
179
|
+
checkKey(key);
|
|
180
|
+
const text = state.get(key);
|
|
181
|
+
return text === void 0 ? void 0 : decodeValue(text);
|
|
182
|
+
},
|
|
183
|
+
async set(key, value) {
|
|
184
|
+
checkKey(key);
|
|
185
|
+
const text = encodeValue(value);
|
|
186
|
+
await write(() => ({
|
|
187
|
+
key,
|
|
188
|
+
text,
|
|
189
|
+
result: void 0
|
|
190
|
+
}));
|
|
191
|
+
},
|
|
192
|
+
async update(key, change) {
|
|
193
|
+
checkKey(key);
|
|
194
|
+
return write((read) => {
|
|
195
|
+
const text = read(key);
|
|
196
|
+
const encoded = encodeValue(runChange(change, text === void 0 ? void 0 : decodeValue(text)));
|
|
197
|
+
return {
|
|
198
|
+
key,
|
|
199
|
+
text: encoded,
|
|
200
|
+
result: decodeValue(encoded)
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
async delete(key) {
|
|
205
|
+
checkKey(key);
|
|
206
|
+
await write(() => ({
|
|
207
|
+
key,
|
|
208
|
+
text: void 0,
|
|
209
|
+
result: void 0
|
|
210
|
+
}));
|
|
211
|
+
},
|
|
212
|
+
async list(prefix = "") {
|
|
213
|
+
guard();
|
|
214
|
+
return [...state.keys()].filter((key) => key.startsWith(prefix)).sort(compareKeys);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
close: async () => {
|
|
218
|
+
closed = true;
|
|
219
|
+
await drained;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { openKv as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t as Db } from "./database-DBHdwefi.js";
|
|
2
|
+
|
|
3
|
+
//#region ../../plugins/acryl-db-sqlite/src/open.d.ts
|
|
4
|
+
interface OpenDbOptions {
|
|
5
|
+
/** The database file, its folder created if missing; `:memory:` for a database that lives until `close`. */
|
|
6
|
+
readonly path: string;
|
|
7
|
+
/** A folder of `NNN-name.sql` files numbered from 1, as a path or a file URL, applied in order before `openDb` resolves. */
|
|
8
|
+
readonly migrations?: string | URL;
|
|
9
|
+
/** How long SQLite waits for a competing file lock, in milliseconds. Defaults to 5000. */
|
|
10
|
+
readonly busyTimeoutMs?: number;
|
|
11
|
+
/** The WAL auto-checkpoint threshold in pages; 0 disables it. Defaults to 1000. */
|
|
12
|
+
readonly walAutoCheckpointPages?: number;
|
|
13
|
+
}
|
|
14
|
+
/** An open database: the port, and the one function that closes it. */
|
|
15
|
+
interface OpenDb {
|
|
16
|
+
readonly db: Db;
|
|
17
|
+
/** Waits for running operations and transactions, checkpoints the WAL and releases the file. Calling it again does nothing. */
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Open, configure and migrate a SQLite database.
|
|
22
|
+
* @throws when the migration files are misnamed or misnumbered, or a migration fails (the file is closed, and unchanged), or the
|
|
23
|
+
* database's schema is newer than the migrations.
|
|
24
|
+
*/
|
|
25
|
+
declare function openDb(options: OpenDbOptions): Promise<OpenDb>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { OpenDbOptions as n, openDb as r, OpenDb as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UnsupportedPathError } from "./router.js";
|
|
1
|
+
import { a as UnsupportedPathError } from "./router-Be_o6nFu.js";
|
|
2
2
|
//#region ../../plugins/acryl-router/src/core/router/pattern-router/router.ts
|
|
3
3
|
const emptyParams = Object.create(null);
|
|
4
4
|
var PatternRouter = class {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region ../../plugins/acryl-router/src/core/middleware/powered-by/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Powered By Middleware for Acryl.
|
|
4
|
+
*
|
|
5
|
+
* @param options - The options for the Powered By Middleware.
|
|
6
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { poweredBy } from 'acrylblends/powered-by'
|
|
11
|
+
*
|
|
12
|
+
* const app = new Acryl()
|
|
13
|
+
*
|
|
14
|
+
* app.use(poweredBy()) // With options: poweredBy({ serverName: "My Server" })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const poweredBy = (options) => {
|
|
18
|
+
return async function poweredBy(c, next) {
|
|
19
|
+
await next();
|
|
20
|
+
c.res.headers.set("X-Powered-By", options?.serverName ?? "Acryl");
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { poweredBy as t };
|
package/lib/powered-by.js
CHANGED
|
@@ -1,24 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Powered By Middleware for Acryl.
|
|
4
|
-
*
|
|
5
|
-
* @param options - The options for the Powered By Middleware.
|
|
6
|
-
* @returns {MiddlewareHandler} The middleware handler function.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* import { poweredBy } from 'acrylblends/powered-by'
|
|
11
|
-
*
|
|
12
|
-
* const app = new Acryl()
|
|
13
|
-
*
|
|
14
|
-
* app.use(poweredBy()) // With options: poweredBy({ serverName: "My Server" })
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
const poweredBy = (options) => {
|
|
18
|
-
return async function poweredBy(c, next) {
|
|
19
|
-
await next();
|
|
20
|
-
c.res.headers.set("X-Powered-By", options?.serverName ?? "Acryl");
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
//#endregion
|
|
1
|
+
import { t as poweredBy } from "./powered-by-CXQNjNIX.js";
|
|
24
2
|
export { poweredBy };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region ../../plugins/acryl-router/src/core/middleware/pretty-json/index.ts
|
|
2
|
+
const jsonContentTypeRegex = /^application\/(?:[a-z0-9._-]+\+)?json(?=$|[;\s])/i;
|
|
3
|
+
/**
|
|
4
|
+
* Pretty JSON Middleware for Acryl.
|
|
5
|
+
*
|
|
6
|
+
*
|
|
7
|
+
* @param options - The options for the pretty JSON middleware.
|
|
8
|
+
* @returns {MiddlewareHandler} The middleware handler function.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const app = new Acryl()
|
|
13
|
+
*
|
|
14
|
+
* app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
|
|
15
|
+
* app.get('/', (c) => {
|
|
16
|
+
* return c.json({ message: 'Acryl!' })
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
const prettyJSON = (options) => {
|
|
21
|
+
const targetQuery = options?.query ?? "pretty";
|
|
22
|
+
return async function prettyJSON(c, next) {
|
|
23
|
+
const pretty = options?.force || c.req.query(targetQuery) || c.req.query(targetQuery) === "";
|
|
24
|
+
await next();
|
|
25
|
+
const contentType = c.res.headers.get("Content-Type");
|
|
26
|
+
if (pretty && contentType && jsonContentTypeRegex.test(contentType)) {
|
|
27
|
+
let obj;
|
|
28
|
+
try {
|
|
29
|
+
obj = await c.res.clone().json();
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
c.res = new Response(JSON.stringify(obj, null, options?.space ?? 2), c.res);
|
|
34
|
+
c.res.headers.delete("Content-Length");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { prettyJSON as t };
|
package/lib/pretty-json.js
CHANGED
|
@@ -1,39 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
const jsonContentTypeRegex = /^application\/(?:[a-z0-9._-]+\+)?json(?=$|[;\s])/i;
|
|
3
|
-
/**
|
|
4
|
-
* Pretty JSON Middleware for Acryl.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param options - The options for the pretty JSON middleware.
|
|
8
|
-
* @returns {MiddlewareHandler} The middleware handler function.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* const app = new Acryl()
|
|
13
|
-
*
|
|
14
|
-
* app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })
|
|
15
|
-
* app.get('/', (c) => {
|
|
16
|
-
* return c.json({ message: 'Acryl!' })
|
|
17
|
-
* })
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
const prettyJSON = (options) => {
|
|
21
|
-
const targetQuery = options?.query ?? "pretty";
|
|
22
|
-
return async function prettyJSON(c, next) {
|
|
23
|
-
const pretty = options?.force || c.req.query(targetQuery) || c.req.query(targetQuery) === "";
|
|
24
|
-
await next();
|
|
25
|
-
const contentType = c.res.headers.get("Content-Type");
|
|
26
|
-
if (pretty && contentType && jsonContentTypeRegex.test(contentType)) {
|
|
27
|
-
let obj;
|
|
28
|
-
try {
|
|
29
|
-
obj = await c.res.clone().json();
|
|
30
|
-
} catch {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
c.res = new Response(JSON.stringify(obj, null, options?.space ?? 2), c.res);
|
|
34
|
-
c.res.headers.delete("Content-Length");
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
//#endregion
|
|
1
|
+
import { t as prettyJSON } from "./pretty-json-CeC1yvYt.js";
|
|
39
2
|
export { prettyJSON };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
//#region ../../plugins/acryl-door/src/page/protocol.ts
|
|
2
|
+
/**
|
|
3
|
+
* The one shape of a door call, shared by the Host and the in-page driver (moved from `acryl-agent-control`, spec 041).
|
|
4
|
+
*
|
|
5
|
+
* The Host turns a verb's arguments into a {@link UiRequest}, sends it to the page over the channel, and gets a {@link UiResult} back.
|
|
6
|
+
* Everything crossing the channel is validated here once, so neither side trusts the other's JSON. The page imports this file too, so it
|
|
7
|
+
* lives with the page code and has no Node imports.
|
|
8
|
+
*/
|
|
9
|
+
/** The operations the driver performs on the page. */
|
|
10
|
+
const UI_OPS = [
|
|
11
|
+
"snapshot",
|
|
12
|
+
"click",
|
|
13
|
+
"type",
|
|
14
|
+
"select",
|
|
15
|
+
"press",
|
|
16
|
+
"scroll",
|
|
17
|
+
"wait",
|
|
18
|
+
"reload",
|
|
19
|
+
"navigate"
|
|
20
|
+
];
|
|
21
|
+
/** Operations that change the page, so each call needs approval. `snapshot`, `scroll` and `wait` only look. */
|
|
22
|
+
const MUTATING_OPS = [
|
|
23
|
+
"click",
|
|
24
|
+
"type",
|
|
25
|
+
"select",
|
|
26
|
+
"press",
|
|
27
|
+
"reload",
|
|
28
|
+
"navigate"
|
|
29
|
+
];
|
|
30
|
+
/** A place in the app to go to: a path on the page's own origin (`/demo`, `/notes?q=a#top`), never another site and never a `//host` address. */
|
|
31
|
+
const APP_PATH = /^\/(?![/\\])[^\0\\]{0,2000}$/u;
|
|
32
|
+
const MAX_TEXT_LENGTH = 1e4;
|
|
33
|
+
const MAX_WAIT_MS = 1e4;
|
|
34
|
+
const MAX_SNAPSHOT_NODES = 1e3;
|
|
35
|
+
const DEFAULT_SNAPSHOT_NODES = 300;
|
|
36
|
+
var UiControlError = class extends Error {
|
|
37
|
+
code;
|
|
38
|
+
constructor(code, message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.name = "UiControlError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const KEYS = /^(?:[A-Za-z0-9]|Enter|Escape|Tab|Backspace|Delete|Space|Arrow(?:Up|Down|Left|Right)|Home|End|PageUp|PageDown)$/;
|
|
45
|
+
const REF = /^\d{1,9}\.\d{1,6}$/;
|
|
46
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
47
|
+
const invalid = (message) => {
|
|
48
|
+
throw new UiControlError("invalid", message);
|
|
49
|
+
};
|
|
50
|
+
function ref(value) {
|
|
51
|
+
if (typeof value !== "string" || !REF.test(value)) return invalid("ref must look like 3.17, from the latest snapshot");
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function text(value, max = MAX_TEXT_LENGTH) {
|
|
55
|
+
if (typeof value !== "string" || value.length > max || value.includes("\0")) return invalid(`text must be a string of at most ${String(max)} characters`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/** @param value - untrusted JSON. @throws UiControlError `invalid`. */
|
|
59
|
+
function parseUiRequest(value) {
|
|
60
|
+
if (!isRecord(value) || typeof value.op !== "string") return invalid("a call needs an op");
|
|
61
|
+
const op = value.op;
|
|
62
|
+
const only = (allowed) => {
|
|
63
|
+
const extra = Object.keys(value).find((key) => key !== "op" && !allowed.includes(key));
|
|
64
|
+
if (extra !== void 0) invalid(`unknown field ${extra}`);
|
|
65
|
+
};
|
|
66
|
+
switch (op) {
|
|
67
|
+
case "snapshot": {
|
|
68
|
+
only(["cursor", "maxNodes"]);
|
|
69
|
+
const cursor = value.cursor === void 0 ? void 0 : Number(value.cursor);
|
|
70
|
+
const maxNodes = value.maxNodes === void 0 ? void 0 : Number(value.maxNodes);
|
|
71
|
+
if (cursor !== void 0 && (!Number.isInteger(cursor) || cursor < 0) || maxNodes !== void 0 && (!Number.isInteger(maxNodes) || maxNodes < 1 || maxNodes > 1e3)) return invalid("cursor and maxNodes must be small positive integers");
|
|
72
|
+
return {
|
|
73
|
+
op,
|
|
74
|
+
...cursor === void 0 ? {} : { cursor },
|
|
75
|
+
...maxNodes === void 0 ? {} : { maxNodes }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
case "click":
|
|
79
|
+
only(["ref"]);
|
|
80
|
+
return {
|
|
81
|
+
op,
|
|
82
|
+
ref: ref(value.ref)
|
|
83
|
+
};
|
|
84
|
+
case "type":
|
|
85
|
+
only([
|
|
86
|
+
"ref",
|
|
87
|
+
"text",
|
|
88
|
+
"submit",
|
|
89
|
+
"clear"
|
|
90
|
+
]);
|
|
91
|
+
if (value.submit !== void 0 && typeof value.submit !== "boolean" || value.clear !== void 0 && typeof value.clear !== "boolean") return invalid("submit and clear must be true or false");
|
|
92
|
+
return {
|
|
93
|
+
op,
|
|
94
|
+
ref: ref(value.ref),
|
|
95
|
+
text: text(value.text),
|
|
96
|
+
...value.submit === void 0 ? {} : { submit: value.submit },
|
|
97
|
+
...value.clear === void 0 ? {} : { clear: value.clear }
|
|
98
|
+
};
|
|
99
|
+
case "select":
|
|
100
|
+
only(["ref", "option"]);
|
|
101
|
+
return {
|
|
102
|
+
op,
|
|
103
|
+
ref: ref(value.ref),
|
|
104
|
+
option: text(value.option, 500)
|
|
105
|
+
};
|
|
106
|
+
case "press":
|
|
107
|
+
only(["key", "ref"]);
|
|
108
|
+
if (typeof value.key !== "string" || !KEYS.test(value.key)) return invalid("key must be a single character or a name such as Enter, Escape, Tab or ArrowDown");
|
|
109
|
+
return {
|
|
110
|
+
op,
|
|
111
|
+
key: value.key,
|
|
112
|
+
...value.ref === void 0 ? {} : { ref: ref(value.ref) }
|
|
113
|
+
};
|
|
114
|
+
case "scroll": {
|
|
115
|
+
only([
|
|
116
|
+
"direction",
|
|
117
|
+
"ref",
|
|
118
|
+
"amount"
|
|
119
|
+
]);
|
|
120
|
+
if (value.direction !== "up" && value.direction !== "down" && value.direction !== "left" && value.direction !== "right") return invalid("direction must be up, down, left or right");
|
|
121
|
+
const amount = value.amount === void 0 ? void 0 : Number(value.amount);
|
|
122
|
+
if (amount !== void 0 && (!Number.isFinite(amount) || amount <= 0 || amount > 1e4)) return invalid("amount must be between 1 and 10000 pixels");
|
|
123
|
+
return {
|
|
124
|
+
op,
|
|
125
|
+
direction: value.direction,
|
|
126
|
+
...value.ref === void 0 ? {} : { ref: ref(value.ref) },
|
|
127
|
+
...amount === void 0 ? {} : { amount }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case "wait": {
|
|
131
|
+
only([
|
|
132
|
+
"text",
|
|
133
|
+
"role",
|
|
134
|
+
"name",
|
|
135
|
+
"gone",
|
|
136
|
+
"timeoutMs"
|
|
137
|
+
]);
|
|
138
|
+
const timeoutMs = value.timeoutMs === void 0 ? void 0 : Number(value.timeoutMs);
|
|
139
|
+
if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 1e4)) return invalid(`timeoutMs must be between 0 and ${String(MAX_WAIT_MS)}`);
|
|
140
|
+
if (value.gone !== void 0 && typeof value.gone !== "boolean") return invalid("gone must be true or false");
|
|
141
|
+
if (value.text === void 0 && value.role === void 0 && value.name === void 0) return invalid("wait needs text, or a role and name");
|
|
142
|
+
return {
|
|
143
|
+
op,
|
|
144
|
+
...value.text === void 0 ? {} : { text: text(value.text, 500) },
|
|
145
|
+
...value.role === void 0 ? {} : { role: text(value.role, 60) },
|
|
146
|
+
...value.name === void 0 ? {} : { name: text(value.name, 500) },
|
|
147
|
+
...value.gone === void 0 ? {} : { gone: value.gone },
|
|
148
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case "reload":
|
|
152
|
+
only([]);
|
|
153
|
+
return { op };
|
|
154
|
+
case "navigate":
|
|
155
|
+
only(["path"]);
|
|
156
|
+
if (typeof value.path !== "string" || !APP_PATH.test(value.path)) return invalid("path must be a path of this app, such as /demo (it starts with one /, and names no other site)");
|
|
157
|
+
return {
|
|
158
|
+
op,
|
|
159
|
+
path: value.path
|
|
160
|
+
};
|
|
161
|
+
default: return invalid(`unknown op ${String(op)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** @param text - one `call` event's data from the Host. @throws when it is not a well-formed call. */
|
|
165
|
+
function parseChannelToPage(text) {
|
|
166
|
+
const value = JSON.parse(text);
|
|
167
|
+
if (isRecord(value) && value.t === "call" && typeof value.id === "number" && Number.isSafeInteger(value.id)) return {
|
|
168
|
+
t: "call",
|
|
169
|
+
id: value.id,
|
|
170
|
+
request: parseUiRequest(value.request)
|
|
171
|
+
};
|
|
172
|
+
throw new UiControlError("invalid", "not a channel call");
|
|
173
|
+
}
|
|
174
|
+
/** Every code a {@link UiControlError} can carry, so a code read off the wire is validated, not cast. */
|
|
175
|
+
const UI_ERROR_CODES = [
|
|
176
|
+
"no-window",
|
|
177
|
+
"stale-ref",
|
|
178
|
+
"unknown-ref",
|
|
179
|
+
"sensitive",
|
|
180
|
+
"protected",
|
|
181
|
+
"not-actionable",
|
|
182
|
+
"not-found",
|
|
183
|
+
"killed",
|
|
184
|
+
"timeout",
|
|
185
|
+
"aborted",
|
|
186
|
+
"unloaded",
|
|
187
|
+
"disconnected",
|
|
188
|
+
"invalid"
|
|
189
|
+
];
|
|
190
|
+
/** A window id the page chose: short, printable, so it can travel in a query string and a log line. */
|
|
191
|
+
const WINDOW_ID = /^[A-Za-z0-9-]{1,80}$/u;
|
|
192
|
+
/** @param value - one parsed message from the page; null when it is not a well-formed message. */
|
|
193
|
+
function parseChannelToHost(value) {
|
|
194
|
+
if (!isRecord(value)) return null;
|
|
195
|
+
if (value.t === "focus" && typeof value.focused === "boolean") return {
|
|
196
|
+
t: "focus",
|
|
197
|
+
focused: value.focused
|
|
198
|
+
};
|
|
199
|
+
if (value.t === "result" && typeof value.id === "number" && isRecord(value.value)) return {
|
|
200
|
+
t: "result",
|
|
201
|
+
id: value.id,
|
|
202
|
+
value: value.value
|
|
203
|
+
};
|
|
204
|
+
if (value.t === "error" && typeof value.id === "number" && typeof value.message === "string" && UI_ERROR_CODES.includes(value.code)) return {
|
|
205
|
+
t: "error",
|
|
206
|
+
id: value.id,
|
|
207
|
+
code: value.code,
|
|
208
|
+
message: value.message.slice(0, 500)
|
|
209
|
+
};
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
export { MAX_WAIT_MS as a, UI_OPS as c, parseChannelToHost as d, parseChannelToPage as f, MAX_TEXT_LENGTH as i, UiControlError as l, DEFAULT_SNAPSHOT_NODES as n, MUTATING_OPS as o, parseUiRequest as p, MAX_SNAPSHOT_NODES as r, UI_ERROR_CODES as s, APP_PATH as t, WINDOW_ID as u };
|