@adep/cli 0.1.1 → 0.1.2
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.js +2405 -164
- package/dist/vite.js +1378 -21
- package/dist/worker-entry.js +33 -2
- package/package.json +5 -4
package/dist/vite.js
CHANGED
|
@@ -5,7 +5,7 @@ import { adepPlugin } from "@adep/vite-plugin";
|
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { watch } from "node:fs";
|
|
7
7
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
8
|
-
import { basename, join as
|
|
8
|
+
import { basename, join as join8, resolve as resolve2 } from "node:path";
|
|
9
9
|
|
|
10
10
|
// packages/runtime/src/shared/capability-keys.ts
|
|
11
11
|
var RPC_CAPABILITY_KEY = "__adepRpc";
|
|
@@ -13,6 +13,8 @@ var CHAIN_CAPABILITY_KEY = "__adepChain";
|
|
|
13
13
|
var DB_RPC = {
|
|
14
14
|
/** 直通方法(如 query):`(method=query, args=[sql, params])`。 */
|
|
15
15
|
query: "query",
|
|
16
|
+
/** 写路径(CF-013):`(method=writeQuery, args=[sql, params])` → `{ changes }`。 */
|
|
17
|
+
writeQuery: "writeQuery",
|
|
16
18
|
/** 读取 owned 表变更流(DB-006):`(method=changes, args=[table, query])`。 */
|
|
17
19
|
changes: "changes",
|
|
18
20
|
/** 开启事务:`begin → txId`。 */
|
|
@@ -59,6 +61,25 @@ function normalizeHttpStatus(status) {
|
|
|
59
61
|
return isHttpStatus(status) ? status : 500;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
// packages/runtime/src/shared/http-envelope.ts
|
|
65
|
+
function isAdepHttpEnvelope(value) {
|
|
66
|
+
if (typeof value !== "object" || value === null) return false;
|
|
67
|
+
const envelope = value.__adepHttp;
|
|
68
|
+
if (typeof envelope !== "object" || envelope === null) return false;
|
|
69
|
+
const status = envelope.status;
|
|
70
|
+
return typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599;
|
|
71
|
+
}
|
|
72
|
+
function writeEnvelopeResponse(res, envelope) {
|
|
73
|
+
const { status, headers, body } = envelope.__adepHttp;
|
|
74
|
+
const noBody = status >= 100 && status < 200 || status === 204 || status === 205;
|
|
75
|
+
res.writeHead(status, { "content-type": "application/json", ...headers });
|
|
76
|
+
if (noBody) {
|
|
77
|
+
res.end();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
res.end(JSON.stringify(body === void 0 ? null : body));
|
|
81
|
+
}
|
|
82
|
+
|
|
62
83
|
// packages/runtime/src/functions/runtime/worker-executor.ts
|
|
63
84
|
import { readFileSync } from "node:fs";
|
|
64
85
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -161,6 +182,13 @@ function classifyDependencies(dependencies, builtin) {
|
|
|
161
182
|
}
|
|
162
183
|
return { builtinDeps, customDeps };
|
|
163
184
|
}
|
|
185
|
+
function splitBareSpecifier(request) {
|
|
186
|
+
if (request.startsWith("@")) {
|
|
187
|
+
const segments = request.split("/");
|
|
188
|
+
return segments.slice(0, 2).join("/");
|
|
189
|
+
}
|
|
190
|
+
return request.split("/")[0];
|
|
191
|
+
}
|
|
164
192
|
function depsKeyOf(dependencies) {
|
|
165
193
|
const canonical = Object.entries(dependencies).map(([name, version]) => `${name}@${version}`).toSorted().join("\n");
|
|
166
194
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
@@ -378,21 +406,35 @@ function unsafeOperation(message) {
|
|
|
378
406
|
function stripLiterals(sql) {
|
|
379
407
|
return sql.replace(/'[^']*(?:''[^']*)*'/g, "").replace(/"[^"]*(?:""[^"]*)*"/g, "").replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
380
408
|
}
|
|
381
|
-
function
|
|
409
|
+
function sqlHead(sql) {
|
|
410
|
+
return sql.trim().replace(/^\(+/, "").trim().toUpperCase();
|
|
411
|
+
}
|
|
412
|
+
function assertSafeStatement(sql) {
|
|
382
413
|
const stripped = stripLiterals(sql);
|
|
383
414
|
if (stripped.includes(";")) {
|
|
384
415
|
throw unsafeOperation("\u4EC5\u5141\u8BB8\u5355\u6761\u8BED\u53E5\uFF0C\u7981\u6B62\u591A\u8BED\u53E5\u5806\u53E0");
|
|
385
416
|
}
|
|
386
|
-
const head = stripped.trim().replace(/^\(+/, "").trim().toUpperCase();
|
|
387
|
-
if (!head.startsWith("SELECT") && !head.startsWith("WITH")) {
|
|
388
|
-
throw unsafeOperation("cloud.db.query \u4EC5\u5141\u8BB8\u53EA\u8BFB\u67E5\u8BE2\uFF08SELECT / WITH\uFF09");
|
|
389
|
-
}
|
|
390
417
|
if (/ATTACH|DETACH/.test(stripped.toUpperCase())) {
|
|
391
418
|
throw unsafeOperation("\u7981\u6B62\u8DE8\u5E93\u64CD\u4F5C\uFF08ATTACH / DETACH\uFF09");
|
|
392
419
|
}
|
|
393
420
|
if (/SQLITE_\w+/i.test(stripped)) {
|
|
394
421
|
throw unsafeOperation("\u7981\u6B62\u8BBF\u95EE\u7CFB\u7EDF\u8868\uFF08sqlite_*\uFF09");
|
|
395
422
|
}
|
|
423
|
+
return sqlHead(stripped);
|
|
424
|
+
}
|
|
425
|
+
function assertReadOnlyQuery(sql) {
|
|
426
|
+
const head = assertSafeStatement(sql);
|
|
427
|
+
if (!head.startsWith("SELECT") && !head.startsWith("WITH")) {
|
|
428
|
+
throw unsafeOperation("cloud.db.query \u4EC5\u5141\u8BB8\u53EA\u8BFB\u67E5\u8BE2\uFF08SELECT / WITH\uFF09");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function assertWriteQuery(sql) {
|
|
432
|
+
const head = assertSafeStatement(sql);
|
|
433
|
+
if (!head.startsWith("INSERT") && !head.startsWith("UPDATE") && !head.startsWith("DELETE")) {
|
|
434
|
+
throw unsafeOperation(
|
|
435
|
+
"cloud.db.writeQuery \u4EC5\u5141\u8BB8 DML\uFF08INSERT / UPDATE / DELETE\uFF09\uFF0CDDL \u8BF7\u8D70\u63A7\u5236\u53F0"
|
|
436
|
+
);
|
|
437
|
+
}
|
|
396
438
|
}
|
|
397
439
|
|
|
398
440
|
// packages/runtime/src/database/builder/owned.ts
|
|
@@ -938,6 +980,14 @@ function createCloudDb(driver, options = {}) {
|
|
|
938
980
|
assertReadOnlyQuery(sql);
|
|
939
981
|
return driver.all(sql, params);
|
|
940
982
|
},
|
|
983
|
+
async writeQuery(sql, params) {
|
|
984
|
+
if (!Array.isArray(params)) {
|
|
985
|
+
throw unsafeOperation("writeQuery \u7684 params \u5FC5\u987B\u63D0\u4F9B\uFF08\u65E0\u53C2\u4F20 []\uFF09");
|
|
986
|
+
}
|
|
987
|
+
guardWrite();
|
|
988
|
+
assertWriteQuery(sql);
|
|
989
|
+
return driver.run(sql, params);
|
|
990
|
+
},
|
|
941
991
|
async changes(table, query = {}) {
|
|
942
992
|
return readChanges(driver, table, query);
|
|
943
993
|
}
|
|
@@ -953,7 +1003,8 @@ var CLOUD_DB_SPEC = {
|
|
|
953
1003
|
rootMethod: "table",
|
|
954
1004
|
stepMethods: ["select", "where", "orderBy", "limit", "offset"],
|
|
955
1005
|
terminalMethods: ["get", "first", "count", "insert", "insertMany", "update", "delete"],
|
|
956
|
-
|
|
1006
|
+
// CF-013:writeQuery 是 D1 shim 的写路径(INSERT/UPDATE/DELETE),与 query 并列直通方法。
|
|
1007
|
+
directMethods: ["query", "writeQuery", "changes"],
|
|
957
1008
|
transactionMethod: "transaction"
|
|
958
1009
|
};
|
|
959
1010
|
var WRITE_TERMINALS = /* @__PURE__ */ new Set(["insert", "insertMany", "update", "delete"]);
|
|
@@ -1008,6 +1059,10 @@ function createDbCapability(driver, options = {}) {
|
|
|
1008
1059
|
const [sql, params] = args;
|
|
1009
1060
|
return await db.query(sql, params);
|
|
1010
1061
|
}
|
|
1062
|
+
case DB_RPC.writeQuery: {
|
|
1063
|
+
const [sql, params] = args;
|
|
1064
|
+
return await db.writeQuery(sql, params);
|
|
1065
|
+
}
|
|
1011
1066
|
case DB_RPC.changes: {
|
|
1012
1067
|
const [table, query] = args;
|
|
1013
1068
|
return await db.changes(table, query);
|
|
@@ -1489,6 +1544,106 @@ function createSimDbCapability(options = {}) {
|
|
|
1489
1544
|
return { bundle, engine, driver };
|
|
1490
1545
|
}
|
|
1491
1546
|
|
|
1547
|
+
// packages/runtime/src/database/sdk/kv.ts
|
|
1548
|
+
var KV_TABLE = "_adep_kv";
|
|
1549
|
+
var KV_RPC = {
|
|
1550
|
+
get: "get",
|
|
1551
|
+
put: "put",
|
|
1552
|
+
delete: "delete",
|
|
1553
|
+
list: "list"
|
|
1554
|
+
};
|
|
1555
|
+
var KV_CAPABILITY_CODES = {
|
|
1556
|
+
invalidMethod: "KV_INVALID_METHOD"
|
|
1557
|
+
};
|
|
1558
|
+
var KV_LIST_DEFAULT_LIMIT = 100;
|
|
1559
|
+
function nowSeconds() {
|
|
1560
|
+
return Math.floor(Date.now() / 1e3);
|
|
1561
|
+
}
|
|
1562
|
+
function errorWithCode2(error) {
|
|
1563
|
+
const code = typeof error === "object" && error !== null && typeof error.code === "string" ? error.code : void 0;
|
|
1564
|
+
if (code !== void 0 && error instanceof Error) {
|
|
1565
|
+
return new Error(`[${code}] ${error.message}`);
|
|
1566
|
+
}
|
|
1567
|
+
return error;
|
|
1568
|
+
}
|
|
1569
|
+
function createKvCapability(driver) {
|
|
1570
|
+
let ensurePromise = null;
|
|
1571
|
+
const ensureTable = () => {
|
|
1572
|
+
if (ensurePromise === null) {
|
|
1573
|
+
ensurePromise = driver.run(
|
|
1574
|
+
`CREATE TABLE IF NOT EXISTS ${KV_TABLE} (key TEXT PRIMARY KEY, value TEXT, expires_at INTEGER)`
|
|
1575
|
+
).then(() => void 0);
|
|
1576
|
+
}
|
|
1577
|
+
return ensurePromise;
|
|
1578
|
+
};
|
|
1579
|
+
const handler = async (method, args) => {
|
|
1580
|
+
try {
|
|
1581
|
+
await ensureTable();
|
|
1582
|
+
switch (method) {
|
|
1583
|
+
case KV_RPC.get: {
|
|
1584
|
+
const [key] = args;
|
|
1585
|
+
const row = await driver.get(`SELECT value, expires_at FROM ${KV_TABLE} WHERE key = ?`, [
|
|
1586
|
+
key
|
|
1587
|
+
]);
|
|
1588
|
+
if (row === null || row === void 0) return null;
|
|
1589
|
+
const expiresAt = row.expires_at;
|
|
1590
|
+
if (expiresAt !== null && expiresAt !== void 0 && Number(expiresAt) <= nowSeconds()) {
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
return row.value;
|
|
1594
|
+
}
|
|
1595
|
+
case KV_RPC.put: {
|
|
1596
|
+
const [key, value, options] = args;
|
|
1597
|
+
const ttl = options?.expirationTtlSeconds;
|
|
1598
|
+
const expiresAt = typeof ttl === "number" && ttl > 0 ? nowSeconds() + Math.floor(ttl) : null;
|
|
1599
|
+
await driver.run(`DELETE FROM ${KV_TABLE} WHERE key = ?`, [key]);
|
|
1600
|
+
await driver.run(`INSERT INTO ${KV_TABLE} (key, value, expires_at) VALUES (?, ?, ?)`, [
|
|
1601
|
+
key,
|
|
1602
|
+
value,
|
|
1603
|
+
expiresAt
|
|
1604
|
+
]);
|
|
1605
|
+
return void 0;
|
|
1606
|
+
}
|
|
1607
|
+
case KV_RPC.delete: {
|
|
1608
|
+
const [key] = args;
|
|
1609
|
+
await driver.run(`DELETE FROM ${KV_TABLE} WHERE key = ?`, [key]);
|
|
1610
|
+
return void 0;
|
|
1611
|
+
}
|
|
1612
|
+
case KV_RPC.list: {
|
|
1613
|
+
const [options] = args;
|
|
1614
|
+
const prefix = options?.prefix ?? "";
|
|
1615
|
+
const limit = options?.limit ?? KV_LIST_DEFAULT_LIMIT;
|
|
1616
|
+
const rows = prefix === "" ? await driver.all(`SELECT key, expires_at FROM ${KV_TABLE}`) : await driver.all(`SELECT key, expires_at FROM ${KV_TABLE} WHERE key LIKE ?`, [
|
|
1617
|
+
`${prefix}%`
|
|
1618
|
+
]);
|
|
1619
|
+
const now = nowSeconds();
|
|
1620
|
+
const entries = [];
|
|
1621
|
+
for (const row of rows) {
|
|
1622
|
+
const expiresAt = row.expires_at;
|
|
1623
|
+
if (expiresAt !== null && expiresAt !== void 0 && Number(expiresAt) <= now) continue;
|
|
1624
|
+
entries.push({
|
|
1625
|
+
name: String(row.key),
|
|
1626
|
+
expiration: expiresAt === null || expiresAt === void 0 ? null : Number(expiresAt)
|
|
1627
|
+
});
|
|
1628
|
+
if (entries.length >= limit) break;
|
|
1629
|
+
}
|
|
1630
|
+
return entries;
|
|
1631
|
+
}
|
|
1632
|
+
default:
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`\u672A\u77E5\u7684 cloud.kv \u65B9\u6CD5 "${String(method)}"\uFF08${KV_CAPABILITY_CODES.invalidMethod}\uFF09`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
throw errorWithCode2(error);
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
return {
|
|
1642
|
+
capabilities: [{ name: "kv", value: { [RPC_CAPABILITY_KEY]: true } }],
|
|
1643
|
+
rpcHandlers: { kv: handler }
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1492
1647
|
// packages/cli/src/sim/storage.ts
|
|
1493
1648
|
import { join as join3 } from "node:path";
|
|
1494
1649
|
|
|
@@ -1732,9 +1887,12 @@ function createStorageCapability(driver, signer, projectId) {
|
|
|
1732
1887
|
const handler = async (method, args) => {
|
|
1733
1888
|
switch (method) {
|
|
1734
1889
|
case "upload": {
|
|
1735
|
-
const [path, data] = args;
|
|
1890
|
+
const [path, data, options] = args;
|
|
1736
1891
|
return toStoredFile(
|
|
1737
|
-
await driver.put(projectId, path, asBytes(data), {
|
|
1892
|
+
await driver.put(projectId, path, asBytes(data), {
|
|
1893
|
+
visibility: DEFAULT_VISIBILITY,
|
|
1894
|
+
...options?.contentType === void 0 ? {} : { contentType: options.contentType }
|
|
1895
|
+
})
|
|
1738
1896
|
);
|
|
1739
1897
|
}
|
|
1740
1898
|
case "get": {
|
|
@@ -2160,7 +2318,8 @@ async function createSimRuntime(options) {
|
|
|
2160
2318
|
projectId: options.projectId ?? "local"
|
|
2161
2319
|
});
|
|
2162
2320
|
const simRealtime = createSimRealtimeCapability();
|
|
2163
|
-
const
|
|
2321
|
+
const kvBundle = createKvCapability(db.driver);
|
|
2322
|
+
const bundle = mergeBundles([db.bundle, storage.bundle, kvBundle, simRealtime.bundle]);
|
|
2164
2323
|
await db.engine.load();
|
|
2165
2324
|
return {
|
|
2166
2325
|
bundle,
|
|
@@ -2246,6 +2405,13 @@ function createSimInvokeHandler(options) {
|
|
|
2246
2405
|
};
|
|
2247
2406
|
}
|
|
2248
2407
|
|
|
2408
|
+
// packages/cli/src/builtin-deps.ts
|
|
2409
|
+
var LOCAL_STATE_OBJECTS_SPECIFIER = "@adep/runtime/state-objects";
|
|
2410
|
+
var LOCAL_BUILTIN_DEPS = [
|
|
2411
|
+
"@adep/cf-compat",
|
|
2412
|
+
LOCAL_STATE_OBJECTS_SPECIFIER
|
|
2413
|
+
];
|
|
2414
|
+
|
|
2249
2415
|
// packages/cli/src/sim/boundary.ts
|
|
2250
2416
|
var SIM_BOUNDARIES = [
|
|
2251
2417
|
{
|
|
@@ -2301,6 +2467,51 @@ function asStringRecord(value) {
|
|
|
2301
2467
|
}
|
|
2302
2468
|
return rec;
|
|
2303
2469
|
}
|
|
2470
|
+
function asBindingArray(value, label, errors, optionalKey) {
|
|
2471
|
+
if (!Array.isArray(value)) {
|
|
2472
|
+
errors.push(`${label} \u5FC5\u987B\u662F\u6570\u7EC4\uFF0C\u6536\u5230 ${typeNameOf(value)}`);
|
|
2473
|
+
return null;
|
|
2474
|
+
}
|
|
2475
|
+
const out = [];
|
|
2476
|
+
value.forEach((item, i) => {
|
|
2477
|
+
const idx = `${label}[${i}]`;
|
|
2478
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
2479
|
+
errors.push(`${idx} \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u6536\u5230 ${typeNameOf(item)}`);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
const rec = item;
|
|
2483
|
+
if (typeof rec.name !== "string" || rec.name.length === 0) {
|
|
2484
|
+
errors.push(`${idx}.name \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF0C\u6536\u5230 ${typeNameOf(rec.name)}`);
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const entry = { name: rec.name };
|
|
2488
|
+
const optional = rec[optionalKey];
|
|
2489
|
+
if (optional !== void 0) {
|
|
2490
|
+
if (typeof optional !== "string") {
|
|
2491
|
+
errors.push(`${idx}.${optionalKey} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF0C\u6536\u5230 ${typeNameOf(optional)}`);
|
|
2492
|
+
} else {
|
|
2493
|
+
entry[optionalKey] = optional;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
out.push(entry);
|
|
2497
|
+
});
|
|
2498
|
+
return out;
|
|
2499
|
+
}
|
|
2500
|
+
function asCfBindingsConfig(value, errors) {
|
|
2501
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2502
|
+
errors.push(`cfBindings \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u6536\u5230 ${typeNameOf(value)}`);
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
const rec = value;
|
|
2506
|
+
const bindings = {};
|
|
2507
|
+
const kv = rec.kv === void 0 ? void 0 : asBindingArray(rec.kv, "cfBindings.kv", errors, "namespace");
|
|
2508
|
+
if (kv !== null && kv !== void 0) bindings.kv = kv;
|
|
2509
|
+
const d1 = rec.d1 === void 0 ? void 0 : asBindingArray(rec.d1, "cfBindings.d1", errors, "database");
|
|
2510
|
+
if (d1 !== null && d1 !== void 0) bindings.d1 = d1;
|
|
2511
|
+
const r2 = rec.r2 === void 0 ? void 0 : asBindingArray(rec.r2, "cfBindings.r2", errors, "bucket");
|
|
2512
|
+
if (r2 !== null && r2 !== void 0) bindings.r2 = r2;
|
|
2513
|
+
return bindings;
|
|
2514
|
+
}
|
|
2304
2515
|
function parseAdepConfig(raw) {
|
|
2305
2516
|
if (raw === null || raw === void 0) {
|
|
2306
2517
|
return { ok: true, data: { ...DEFAULT_ADEP_CONFIG } };
|
|
@@ -2361,6 +2572,10 @@ function parseAdepConfig(raw) {
|
|
|
2361
2572
|
data.runtime = rec;
|
|
2362
2573
|
}
|
|
2363
2574
|
}
|
|
2575
|
+
if (input.cfBindings !== void 0) {
|
|
2576
|
+
const bindings = asCfBindingsConfig(input.cfBindings, errors);
|
|
2577
|
+
if (bindings !== null) data.cfBindings = bindings;
|
|
2578
|
+
}
|
|
2364
2579
|
if (errors.length > 0) return { ok: false, errors };
|
|
2365
2580
|
return { ok: true, data };
|
|
2366
2581
|
}
|
|
@@ -2409,6 +2624,1027 @@ ${result.errors.map((e) => ` - ${e}`).join("\n")}`;
|
|
|
2409
2624
|
return result.data;
|
|
2410
2625
|
}
|
|
2411
2626
|
|
|
2627
|
+
// packages/runtime/src/state-objects/runtime.ts
|
|
2628
|
+
var StateObject = class {
|
|
2629
|
+
id;
|
|
2630
|
+
/** 对象内存状态(单例驻留);load 时从后端恢复。 */
|
|
2631
|
+
state;
|
|
2632
|
+
alarmAt = null;
|
|
2633
|
+
/** 运行时注入的持久化句柄(内部;protected,子类 flush() 使用)。 */
|
|
2634
|
+
runtime;
|
|
2635
|
+
constructor(id) {
|
|
2636
|
+
this.id = id;
|
|
2637
|
+
this.state = this.initialState();
|
|
2638
|
+
}
|
|
2639
|
+
/** 供运行时读取/序列化。 */
|
|
2640
|
+
getState() {
|
|
2641
|
+
return { ...this.state };
|
|
2642
|
+
}
|
|
2643
|
+
/** 通用快照方法(任何对象可经 invoke(type, id, 'snapshot') 读取)。 */
|
|
2644
|
+
async snapshot() {
|
|
2645
|
+
return this.getState();
|
|
2646
|
+
}
|
|
2647
|
+
/** 设置未来唤醒(alarm):到期时运行时调 onAlarm(调度 CF-037 落地)。 */
|
|
2648
|
+
setAlarm(at) {
|
|
2649
|
+
this.alarmAt = at;
|
|
2650
|
+
}
|
|
2651
|
+
clearAlarm() {
|
|
2652
|
+
this.alarmAt = null;
|
|
2653
|
+
}
|
|
2654
|
+
/** alarm 到期回调(子类覆写)。 */
|
|
2655
|
+
async onAlarm() {
|
|
2656
|
+
}
|
|
2657
|
+
/** 显式持久化当前状态(方法内任何位置可调)。 */
|
|
2658
|
+
async flush() {
|
|
2659
|
+
await this.runtime.flush(this);
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
var StateObjectError = class extends Error {
|
|
2663
|
+
code;
|
|
2664
|
+
constructor(code, message) {
|
|
2665
|
+
super(message);
|
|
2666
|
+
this.name = "StateObjectError";
|
|
2667
|
+
this.code = code;
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
var StateObjectRuntime = class {
|
|
2671
|
+
registry = /* @__PURE__ */ new Map();
|
|
2672
|
+
instances = /* @__PURE__ */ new Map();
|
|
2673
|
+
backend;
|
|
2674
|
+
constructor(backend) {
|
|
2675
|
+
this.backend = backend;
|
|
2676
|
+
}
|
|
2677
|
+
define(cls) {
|
|
2678
|
+
this.registry.set(cls.typeName, cls);
|
|
2679
|
+
}
|
|
2680
|
+
/** 已注册类型清单(控制台/审计用)。 */
|
|
2681
|
+
types() {
|
|
2682
|
+
return [...this.registry.keys()];
|
|
2683
|
+
}
|
|
2684
|
+
/** 内存驻留实例数(诊断用)。 */
|
|
2685
|
+
residentCount() {
|
|
2686
|
+
return this.instances.size;
|
|
2687
|
+
}
|
|
2688
|
+
key(type, id) {
|
|
2689
|
+
return `${type}:${id}`;
|
|
2690
|
+
}
|
|
2691
|
+
/** 取对象实例(单例驻留):内存有 → 复用;无 → 后端恢复或 new 初始态。 */
|
|
2692
|
+
async get(type, id) {
|
|
2693
|
+
const cls = this.registry.get(type);
|
|
2694
|
+
if (cls === void 0) {
|
|
2695
|
+
throw new StateObjectError(
|
|
2696
|
+
"STATE_TYPE_NOT_REGISTERED",
|
|
2697
|
+
`\u72B6\u6001\u5BF9\u8C61\u7C7B\u578B "${type}" \u672A\u6CE8\u518C\uFF08\u5DF2\u6CE8\u518C\uFF1A${this.types().join(", ") || "\u65E0"}\uFF09`
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
const k = this.key(type, id);
|
|
2701
|
+
const hit = this.instances.get(k);
|
|
2702
|
+
if (hit !== void 0) return hit;
|
|
2703
|
+
const snapshot = await this.backend.load(type, id);
|
|
2704
|
+
const raw = new cls(id);
|
|
2705
|
+
const created = raw;
|
|
2706
|
+
created.state = snapshot !== null ? snapshot.state : created.state;
|
|
2707
|
+
created.alarmAt = snapshot !== null ? snapshot.alarmAt : null;
|
|
2708
|
+
created.runtime = this;
|
|
2709
|
+
this.instances.set(k, created);
|
|
2710
|
+
return created;
|
|
2711
|
+
}
|
|
2712
|
+
/** 方法路由:单例对象上串行执行方法。 */
|
|
2713
|
+
async invoke(type, id, method, args = []) {
|
|
2714
|
+
const inst = await this.get(type, id);
|
|
2715
|
+
const fn = inst[method];
|
|
2716
|
+
if (typeof fn !== "function") {
|
|
2717
|
+
throw new StateObjectError(
|
|
2718
|
+
"STATE_METHOD_NOT_FOUND",
|
|
2719
|
+
`\u72B6\u6001\u5BF9\u8C61 "${type}:${id}" \u65E0\u65B9\u6CD5 "${method}"`
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
try {
|
|
2723
|
+
return await fn.call(inst, ...args);
|
|
2724
|
+
} catch (error) {
|
|
2725
|
+
if (error instanceof StateObjectError) throw error;
|
|
2726
|
+
throw new StateObjectError(
|
|
2727
|
+
"STATE_INVOKE_ERROR",
|
|
2728
|
+
`\u72B6\u6001\u5BF9\u8C61 "${type}:${id}" \u65B9\u6CD5 "${method}" \u6267\u884C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
/** 持久化快照(对象方法内 flush() 触发,或运行时统一写)。 */
|
|
2733
|
+
async flush(inst) {
|
|
2734
|
+
const raw = inst;
|
|
2735
|
+
const type = inst.constructor.typeName;
|
|
2736
|
+
if (type === void 0) {
|
|
2737
|
+
throw new StateObjectError("STATE_TYPE_NOT_REGISTERED", "\u72B6\u6001\u5BF9\u8C61\u7C7B\u5FC5\u987B\u58F0\u660E static typeName");
|
|
2738
|
+
}
|
|
2739
|
+
await this.backend.save(type, inst.id, inst.getState(), raw.alarmAt);
|
|
2740
|
+
}
|
|
2741
|
+
/**
|
|
2742
|
+
* alarm 轮询:把到期对象唤醒并调 onAlarm(CF-037)。
|
|
2743
|
+
* - onAlarm 后统一持久化(flush):onAlarm 内改的 state 与 setAlarm 重调度 / clearAlarm
|
|
2744
|
+
* 的新 alarmAt 都落库;
|
|
2745
|
+
* - 单对象 onAlarm 抛错**不阻塞**同 tick 其余对象:失败对象不落库、alarmAt 保留
|
|
2746
|
+
* → 下轮 dueAlarms 仍命中重试;错误折叠进 failed(不泄漏内部栈)。
|
|
2747
|
+
*/
|
|
2748
|
+
async pump(now) {
|
|
2749
|
+
const due = await this.backend.dueAlarms(now);
|
|
2750
|
+
const fired = [];
|
|
2751
|
+
const failed = [];
|
|
2752
|
+
for (const d of due) {
|
|
2753
|
+
try {
|
|
2754
|
+
const inst = await this.get(d.type, d.id);
|
|
2755
|
+
await inst.onAlarm();
|
|
2756
|
+
await this.flush(inst);
|
|
2757
|
+
fired.push({ type: d.type, id: d.id });
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
failed.push({
|
|
2760
|
+
type: d.type,
|
|
2761
|
+
id: d.id,
|
|
2762
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
return { fired, failed };
|
|
2767
|
+
}
|
|
2768
|
+
/** 释放内存驻留(进程重启 / 测试隔离用);不删后端数据。 */
|
|
2769
|
+
evictAll() {
|
|
2770
|
+
this.instances.clear();
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
|
|
2774
|
+
// packages/runtime/src/state-objects/backend.ts
|
|
2775
|
+
var STATE_OBJECTS_TABLE = "_adep_state_objects";
|
|
2776
|
+
function createProjectDriverStateBackend(driver) {
|
|
2777
|
+
let ensured = false;
|
|
2778
|
+
const ensureTable = async () => {
|
|
2779
|
+
if (ensured) return;
|
|
2780
|
+
await driver.run(
|
|
2781
|
+
`CREATE TABLE IF NOT EXISTS ${STATE_OBJECTS_TABLE} ("type" TEXT NOT NULL, "id" TEXT NOT NULL, "state" TEXT NOT NULL, "alarm_at" INTEGER, "updated_at" INTEGER NOT NULL, PRIMARY KEY ("type", "id"))`
|
|
2782
|
+
);
|
|
2783
|
+
ensured = true;
|
|
2784
|
+
};
|
|
2785
|
+
return {
|
|
2786
|
+
async load(type, id) {
|
|
2787
|
+
await ensureTable();
|
|
2788
|
+
const rows = await driver.all(
|
|
2789
|
+
`SELECT "state", "alarm_at" FROM ${STATE_OBJECTS_TABLE} WHERE "type" = ? AND "id" = ?`,
|
|
2790
|
+
[type, id]
|
|
2791
|
+
);
|
|
2792
|
+
if (rows.length === 0) return null;
|
|
2793
|
+
return {
|
|
2794
|
+
state: JSON.parse(String(rows[0].state)),
|
|
2795
|
+
alarmAt: rows[0].alarm_at
|
|
2796
|
+
};
|
|
2797
|
+
},
|
|
2798
|
+
async save(type, id, state, alarmAt) {
|
|
2799
|
+
await ensureTable();
|
|
2800
|
+
const existing = await driver.all(
|
|
2801
|
+
`SELECT 1 FROM ${STATE_OBJECTS_TABLE} WHERE "type" = ? AND "id" = ?`,
|
|
2802
|
+
[type, id]
|
|
2803
|
+
);
|
|
2804
|
+
const stateJson = JSON.stringify(state);
|
|
2805
|
+
if (existing.length === 0) {
|
|
2806
|
+
await driver.run(
|
|
2807
|
+
`INSERT INTO ${STATE_OBJECTS_TABLE} ("type", "id", "state", "alarm_at", "updated_at") VALUES (?, ?, ?, ?, ?)`,
|
|
2808
|
+
[type, id, stateJson, alarmAt, Date.now()]
|
|
2809
|
+
);
|
|
2810
|
+
} else {
|
|
2811
|
+
await driver.run(
|
|
2812
|
+
`UPDATE ${STATE_OBJECTS_TABLE} SET "state" = ?, "alarm_at" = ?, "updated_at" = ? WHERE "type" = ? AND "id" = ?`,
|
|
2813
|
+
[stateJson, alarmAt, Date.now(), type, id]
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
},
|
|
2817
|
+
async dueAlarms(now) {
|
|
2818
|
+
await ensureTable();
|
|
2819
|
+
const rows = await driver.all(
|
|
2820
|
+
`SELECT "type", "id", "alarm_at" FROM ${STATE_OBJECTS_TABLE} WHERE "alarm_at" IS NOT NULL AND "alarm_at" <= ?`,
|
|
2821
|
+
[now]
|
|
2822
|
+
);
|
|
2823
|
+
return rows.map((r) => ({
|
|
2824
|
+
type: r.type,
|
|
2825
|
+
id: r.id,
|
|
2826
|
+
alarmAt: r.alarm_at
|
|
2827
|
+
}));
|
|
2828
|
+
},
|
|
2829
|
+
async remove(type, id) {
|
|
2830
|
+
await ensureTable();
|
|
2831
|
+
await driver.run(`DELETE FROM ${STATE_OBJECTS_TABLE} WHERE "type" = ? AND "id" = ?`, [
|
|
2832
|
+
type,
|
|
2833
|
+
id
|
|
2834
|
+
]);
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// packages/runtime/src/state-objects/http.ts
|
|
2840
|
+
var STATE_PATH = "state";
|
|
2841
|
+
function parseStatePath(rest) {
|
|
2842
|
+
const segments = rest.split("/").filter((s) => s.length > 0);
|
|
2843
|
+
if (segments[0] !== STATE_PATH || segments.length < 4) return null;
|
|
2844
|
+
return {
|
|
2845
|
+
type: decodeURIComponent(segments[1]),
|
|
2846
|
+
id: decodeURIComponent(segments[2]),
|
|
2847
|
+
method: decodeURIComponent(segments.slice(3).join("/"))
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
function parseStateBody(body) {
|
|
2851
|
+
if (body === null || body.length === 0) return { args: [] };
|
|
2852
|
+
try {
|
|
2853
|
+
const parsed = JSON.parse(body);
|
|
2854
|
+
if (!Array.isArray(parsed)) return { error: "body \u5FC5\u987B\u662F\u53C2\u6570\u6570\u7EC4\uFF08JSON\uFF09" };
|
|
2855
|
+
return { args: parsed };
|
|
2856
|
+
} catch {
|
|
2857
|
+
return { error: "body \u5FC5\u987B\u662F\u5408\u6CD5 JSON" };
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// packages/runtime/src/state-objects/demo.ts
|
|
2862
|
+
var DemoCounter = class extends StateObject {
|
|
2863
|
+
static typeName = "DemoCounter";
|
|
2864
|
+
initialState() {
|
|
2865
|
+
return { count: 0 };
|
|
2866
|
+
}
|
|
2867
|
+
async increment(by = 1) {
|
|
2868
|
+
this.state.count += by;
|
|
2869
|
+
await this.flush();
|
|
2870
|
+
return { count: this.state.count };
|
|
2871
|
+
}
|
|
2872
|
+
async get() {
|
|
2873
|
+
return { count: this.state.count };
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
var DemoChatRoom = class extends StateObject {
|
|
2877
|
+
static typeName = "DemoChatRoom";
|
|
2878
|
+
initialState() {
|
|
2879
|
+
return { members: [], messages: [], cursor: 0 };
|
|
2880
|
+
}
|
|
2881
|
+
async join(user) {
|
|
2882
|
+
if (!this.state.members.includes(user)) this.state.members.push(user);
|
|
2883
|
+
this.state.cursor += 1;
|
|
2884
|
+
await this.flush();
|
|
2885
|
+
return { members: this.state.members, cursor: this.state.cursor };
|
|
2886
|
+
}
|
|
2887
|
+
async send(user, text) {
|
|
2888
|
+
const seq = this.state.messages.length + 1;
|
|
2889
|
+
this.state.messages.push({ user, text, at: Date.now() });
|
|
2890
|
+
await this.flush();
|
|
2891
|
+
return { seq };
|
|
2892
|
+
}
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
// packages/runtime/src/functions/runtime/sandbox.ts
|
|
2896
|
+
import vm from "node:vm";
|
|
2897
|
+
import * as nodeModule from "node:module";
|
|
2898
|
+
|
|
2899
|
+
// packages/runtime/src/functions/runtime/exports.ts
|
|
2900
|
+
function transformModule(code) {
|
|
2901
|
+
let out = code;
|
|
2902
|
+
const named = [];
|
|
2903
|
+
out = out.replace(/(^|\n)(\s*)export\s+default\s+/g, "$1$2module.default = ");
|
|
2904
|
+
out = out.replace(
|
|
2905
|
+
/import\s*\{([^}]*)\}\s*from\s*(['"][^'"]*['"])[ \t]*;?/g,
|
|
2906
|
+
(_match, names, source) => `const {${names}} = require(${source})`
|
|
2907
|
+
);
|
|
2908
|
+
out = out.replace(
|
|
2909
|
+
/import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*from\s*(['"][^'"]*['"])[ \t]*;?/g,
|
|
2910
|
+
(_match, name, source) => `const ${name} = require(${source})`
|
|
2911
|
+
);
|
|
2912
|
+
out = out.replace(
|
|
2913
|
+
/import\s+([A-Za-z_$][\w$]*)\s+from\s*(['"][^'"]*['"])[ \t]*;?/g,
|
|
2914
|
+
(_match, name, source) => `const ${name} = require(${source})`
|
|
2915
|
+
);
|
|
2916
|
+
out = out.replace(
|
|
2917
|
+
/export\s+(const|let|var)\s+([A-Za-z_$][\w$]*)/g,
|
|
2918
|
+
(_match, kw, name) => {
|
|
2919
|
+
named.push(name);
|
|
2920
|
+
return `${kw} ${name}`;
|
|
2921
|
+
}
|
|
2922
|
+
);
|
|
2923
|
+
out = out.replace(
|
|
2924
|
+
/export\s+(async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/g,
|
|
2925
|
+
(_match, asyncKw, name) => {
|
|
2926
|
+
named.push(name);
|
|
2927
|
+
return `${asyncKw ?? ""}function ${name}`;
|
|
2928
|
+
}
|
|
2929
|
+
);
|
|
2930
|
+
out = out.replace(/export\s+class\s+([A-Za-z_$][\w$]*)/g, (_match, name) => {
|
|
2931
|
+
named.push(name);
|
|
2932
|
+
return `class ${name}`;
|
|
2933
|
+
});
|
|
2934
|
+
if (named.length > 0) {
|
|
2935
|
+
out += `
|
|
2936
|
+
;Object.assign(exports, { ${named.join(", ")} })`;
|
|
2937
|
+
}
|
|
2938
|
+
return out;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// packages/runtime/src/functions/runtime/sandbox.ts
|
|
2942
|
+
var bunTranspiler;
|
|
2943
|
+
function transpile(code) {
|
|
2944
|
+
const bun = globalThis["Bun"];
|
|
2945
|
+
if (bun?.Transpiler !== void 0) {
|
|
2946
|
+
bunTranspiler ??= new bun.Transpiler({ loader: "ts" });
|
|
2947
|
+
return bunTranspiler.transformSync(code);
|
|
2948
|
+
}
|
|
2949
|
+
const stripTypeScriptTypes2 = nodeModule.stripTypeScriptTypes;
|
|
2950
|
+
if (typeof stripTypeScriptTypes2 === "function") {
|
|
2951
|
+
return stripTypeScriptTypes2(code, { mode: "strip" });
|
|
2952
|
+
}
|
|
2953
|
+
return code;
|
|
2954
|
+
}
|
|
2955
|
+
function transformExports(code) {
|
|
2956
|
+
return transformModule(code);
|
|
2957
|
+
}
|
|
2958
|
+
var SANDBOX_BUILTIN_REJECTED = "FN_SANDBOX_BUILTIN_BLOCKED";
|
|
2959
|
+
function resolveModulePath(fromDir, request, files) {
|
|
2960
|
+
if (!request.startsWith("./") && !request.startsWith("../")) return null;
|
|
2961
|
+
const base = joinPosix(fromDir, request);
|
|
2962
|
+
const candidates = [base, `${base}.ts`, `${base}.js`, `${base}/index.ts`, `${base}/index.js`];
|
|
2963
|
+
for (const candidate of candidates) {
|
|
2964
|
+
const normalized = normalizePosix(candidate);
|
|
2965
|
+
if (files[normalized] !== void 0) return normalized;
|
|
2966
|
+
}
|
|
2967
|
+
return null;
|
|
2968
|
+
}
|
|
2969
|
+
function joinPosix(dir, request) {
|
|
2970
|
+
const parts = [...dir.split("/"), ...request.split("/")];
|
|
2971
|
+
const stack = [];
|
|
2972
|
+
for (const part of parts) {
|
|
2973
|
+
if (part === "" || part === ".") continue;
|
|
2974
|
+
if (part === "..") stack.pop();
|
|
2975
|
+
else stack.push(part);
|
|
2976
|
+
}
|
|
2977
|
+
return stack.join("/");
|
|
2978
|
+
}
|
|
2979
|
+
function normalizePosix(p) {
|
|
2980
|
+
return joinPosix("", p);
|
|
2981
|
+
}
|
|
2982
|
+
function loadModule(path, files, sandboxGlobals, cache, resolveBare, bareCache) {
|
|
2983
|
+
const cached = cache.get(path);
|
|
2984
|
+
if (cached !== void 0) return cached;
|
|
2985
|
+
const source = files[path];
|
|
2986
|
+
if (source === void 0) {
|
|
2987
|
+
throw new Error(`\u6A21\u5757\u4E0D\u5B58\u5728\uFF1A${path}`);
|
|
2988
|
+
}
|
|
2989
|
+
const exports = {};
|
|
2990
|
+
cache.set(path, exports);
|
|
2991
|
+
const dir = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
2992
|
+
const module = { exports, default: void 0 };
|
|
2993
|
+
const require2 = (request) => {
|
|
2994
|
+
const resolved = resolveModulePath(dir, request, files);
|
|
2995
|
+
if (resolved !== null) {
|
|
2996
|
+
return loadModule(resolved, files, sandboxGlobals, cache, resolveBare, bareCache);
|
|
2997
|
+
}
|
|
2998
|
+
if (resolveBare !== void 0 && !request.startsWith("./") && !request.startsWith("../")) {
|
|
2999
|
+
const cachedModule = bareCache?.get(request);
|
|
3000
|
+
if (cachedModule !== void 0) return cachedModule;
|
|
3001
|
+
const loaded = resolveBare(request);
|
|
3002
|
+
bareCache?.set(request, loaded);
|
|
3003
|
+
return loaded;
|
|
3004
|
+
}
|
|
3005
|
+
const error = new Error(
|
|
3006
|
+
`\u6C99\u7BB1\u62D2\u7EDD\u8BE5\u5F15\u7528\uFF1A"${request}"\u3002\u51FD\u6570\u5185\u53EA\u80FD require \u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84\u6587\u4EF6\uFF08FN_SANDBOX_BUILTIN_BLOCKED\uFF09`
|
|
3007
|
+
);
|
|
3008
|
+
error.code = SANDBOX_BUILTIN_REJECTED;
|
|
3009
|
+
throw error;
|
|
3010
|
+
};
|
|
3011
|
+
let transformed;
|
|
3012
|
+
try {
|
|
3013
|
+
transformed = transformExports(transpile(source));
|
|
3014
|
+
} catch (error) {
|
|
3015
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
3016
|
+
const stack = error.stack;
|
|
3017
|
+
const line = typeof stack === "string" ? /(?:^|\n):(\d+)(?:\n|$)/.exec(stack)?.[1] : void 0;
|
|
3018
|
+
const location = line === void 0 ? path : `${path}:${line}`;
|
|
3019
|
+
const wrapped = new Error(`\u6E90\u7801\u8BED\u6CD5\u9519\u8BEF\uFF08${location}\uFF09\uFF1A${rawMessage}`, { cause: error });
|
|
3020
|
+
const code = error.code;
|
|
3021
|
+
if (typeof code === "string") wrapped.code = code;
|
|
3022
|
+
throw wrapped;
|
|
3023
|
+
}
|
|
3024
|
+
const wrapper = vm.runInNewContext(
|
|
3025
|
+
`(function (module, exports, require) { ${transformed}
|
|
3026
|
+
})`,
|
|
3027
|
+
vm.createContext({ ...sandboxGlobals }),
|
|
3028
|
+
{ filename: path }
|
|
3029
|
+
);
|
|
3030
|
+
wrapper(module, exports, require2);
|
|
3031
|
+
if (module.default !== void 0 && exports["default"] === void 0) {
|
|
3032
|
+
exports["default"] = module.default;
|
|
3033
|
+
}
|
|
3034
|
+
return exports;
|
|
3035
|
+
}
|
|
3036
|
+
function loadFunctionModule(files, entry, sandboxGlobals, resolveBare) {
|
|
3037
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3038
|
+
const bareCache = /* @__PURE__ */ new Map();
|
|
3039
|
+
const entryExports = loadModule(entry, files, sandboxGlobals, cache, resolveBare, bareCache);
|
|
3040
|
+
const handler = entryExports["default"];
|
|
3041
|
+
if (typeof handler !== "function") {
|
|
3042
|
+
throw new Error(`\u5165\u53E3\u6587\u4EF6 ${entry} \u7F3A\u5C11 default \u5BFC\u51FA\u7684\u51FD\u6570`);
|
|
3043
|
+
}
|
|
3044
|
+
return { handler };
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// packages/runtime/src/state-objects/loader.ts
|
|
3048
|
+
var STATE_OBJECTS_ENTRY = "state-objects.ts";
|
|
3049
|
+
var STATE_OBJECTS_SPECIFIER = "@adep/runtime/state-objects";
|
|
3050
|
+
function resolveStateObjectSpecifier(request) {
|
|
3051
|
+
if (request === STATE_OBJECTS_SPECIFIER) return { StateObject };
|
|
3052
|
+
return void 0;
|
|
3053
|
+
}
|
|
3054
|
+
function defaultStateSandboxGlobals() {
|
|
3055
|
+
return {
|
|
3056
|
+
console,
|
|
3057
|
+
URL,
|
|
3058
|
+
URLSearchParams,
|
|
3059
|
+
Request,
|
|
3060
|
+
Response,
|
|
3061
|
+
Headers,
|
|
3062
|
+
TextEncoder,
|
|
3063
|
+
TextDecoder,
|
|
3064
|
+
AbortController,
|
|
3065
|
+
AbortSignal,
|
|
3066
|
+
ReadableStream,
|
|
3067
|
+
WritableStream,
|
|
3068
|
+
TransformStream,
|
|
3069
|
+
Blob,
|
|
3070
|
+
FormData,
|
|
3071
|
+
crypto,
|
|
3072
|
+
performance,
|
|
3073
|
+
atob,
|
|
3074
|
+
btoa,
|
|
3075
|
+
structuredClone
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
function collectStateObjectClasses(exports_) {
|
|
3079
|
+
const classes = [];
|
|
3080
|
+
for (const value of Object.values(exports_)) {
|
|
3081
|
+
if (typeof value !== "function") continue;
|
|
3082
|
+
const proto = value.prototype;
|
|
3083
|
+
if (typeof proto !== "object" || proto === null) continue;
|
|
3084
|
+
if (!(proto instanceof StateObject)) continue;
|
|
3085
|
+
if (!Object.hasOwn(value, "typeName")) {
|
|
3086
|
+
throw new Error(
|
|
3087
|
+
`\u72B6\u6001\u5BF9\u8C61\u7C7B\u5FC5\u987B\u58F0\u660E static typeName\uFF08${value.name ?? "anonymous"}\uFF09`
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
const typeName = value.typeName;
|
|
3091
|
+
if (typeof typeName !== "string" || typeName.length === 0) {
|
|
3092
|
+
throw new Error(
|
|
3093
|
+
`\u72B6\u6001\u5BF9\u8C61\u7C7B\u5FC5\u987B\u58F0\u660E static typeName\uFF08${value.name ?? "anonymous"}\uFF09`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
classes.push(value);
|
|
3097
|
+
}
|
|
3098
|
+
return classes;
|
|
3099
|
+
}
|
|
3100
|
+
function loadStateObjectClasses(files, entry = STATE_OBJECTS_ENTRY, options = {}) {
|
|
3101
|
+
if (files[entry] === void 0) {
|
|
3102
|
+
throw new Error(`\u72B6\u6001\u5BF9\u8C61\u58F0\u660E\u6587\u4EF6\u7F3A\u5931\uFF1A${entry}\uFF08\u7EA6\u5B9A ${STATE_OBJECTS_ENTRY}\uFF09`);
|
|
3103
|
+
}
|
|
3104
|
+
const globals = options.sandboxGlobals ?? defaultStateSandboxGlobals();
|
|
3105
|
+
const exportsObject = loadModule(entry, files, globals, /* @__PURE__ */ new Map(), options.resolveBare, /* @__PURE__ */ new Map());
|
|
3106
|
+
return collectStateObjectClasses(exportsObject);
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// packages/runtime/src/functions/runtime/worker-entry.ts
|
|
3110
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3111
|
+
import { createRequire } from "node:module";
|
|
3112
|
+
import { join as join7 } from "node:path";
|
|
3113
|
+
|
|
3114
|
+
// packages/runtime/src/functions/runtime/cloud-container.ts
|
|
3115
|
+
var KNOWN_CAPABILITY_HINTS = {
|
|
3116
|
+
db: {
|
|
3117
|
+
code: "DB_NOT_PROVISIONED",
|
|
3118
|
+
message: "\u9879\u76EE\u6570\u636E\u5E93\u5C1A\u672A\u5F00\u542F\uFF1A\u8BF7\u5230\u9879\u76EE\u8BBE\u7F6E\u542F\u52A8\u6570\u636E\u5E93\uFF08DB_NOT_PROVISIONED\uFF09"
|
|
3119
|
+
},
|
|
3120
|
+
storage: {
|
|
3121
|
+
code: "STORAGE_NOT_AVAILABLE",
|
|
3122
|
+
message: "\u6587\u4EF6\u5B58\u50A8\u5C1A\u672A\u5F00\u542F\uFF1A\u8BF7\u5230\u9879\u76EE\u8BBE\u7F6E\u5F00\u542F\u6587\u4EF6\u5B58\u50A8\uFF08STORAGE_NOT_AVAILABLE\uFF09"
|
|
3123
|
+
},
|
|
3124
|
+
fetch: {
|
|
3125
|
+
code: "FN_FETCH_DISABLED",
|
|
3126
|
+
message: "\u6C99\u7BB1\u7F51\u7EDC\u672A\u5F00\u542F\uFF1A\u8BF7\u914D\u7F6E FUNCTIONS_FETCH_ALLOWLIST \u767D\u540D\u5355\u540E\u91CD\u542F\uFF08FN_FETCH_DISABLED\uFF09"
|
|
3127
|
+
},
|
|
3128
|
+
realtime: {
|
|
3129
|
+
code: "REALTIME_NOT_AVAILABLE",
|
|
3130
|
+
message: "\u5B9E\u65F6\u901A\u9053\u672A\u88C5\u914D\uFF1Arealtime \u57DF\u672A\u88C5\u8F7D\uFF08\u68C0\u67E5\u90E8\u7F72\u5F62\u6001\u4E0E Redis \u914D\u7F6E\uFF09\uFF08REALTIME_NOT_AVAILABLE\uFF09"
|
|
3131
|
+
},
|
|
3132
|
+
kv: {
|
|
3133
|
+
code: "KV_NOT_PROVISIONED",
|
|
3134
|
+
message: "\u9879\u76EE KV \u5B58\u50A8\u672A\u53EF\u7528\uFF1Akv \u968F\u9879\u76EE\u6570\u636E\u5E93\u63D0\u4F9B\uFF0C\u8BF7\u5148\u542F\u52A8\u6570\u636E\u5E93\uFF08KV_NOT_PROVISIONED\uFF09"
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
var CapabilityNotRegisteredError = class extends Error {
|
|
3138
|
+
code;
|
|
3139
|
+
constructor(code, message) {
|
|
3140
|
+
super(message);
|
|
3141
|
+
this.code = code;
|
|
3142
|
+
this.name = "CapabilityNotRegisteredError";
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
function createCloud(registrations) {
|
|
3146
|
+
const store = registrations;
|
|
3147
|
+
return new Proxy(
|
|
3148
|
+
{},
|
|
3149
|
+
{
|
|
3150
|
+
get(_target, prop) {
|
|
3151
|
+
if (typeof prop !== "string") return void 0;
|
|
3152
|
+
if (!store.has(prop)) {
|
|
3153
|
+
const hint = KNOWN_CAPABILITY_HINTS[prop];
|
|
3154
|
+
throw new CapabilityNotRegisteredError(
|
|
3155
|
+
hint?.code ?? "CAPABILITY_NOT_REGISTERED",
|
|
3156
|
+
hint?.message ?? `\u80FD\u529B "${prop}" \u672A\u6CE8\u518C\uFF1A\u8BF7\u5728\u51FD\u6570\u6240\u5728\u9879\u76EE\u5B89\u88C5\u5BF9\u5E94\u80FD\u529B`
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
return store.get(prop);
|
|
3160
|
+
},
|
|
3161
|
+
has: () => true
|
|
3162
|
+
// 支持 'db' in ctx.cloud 形态
|
|
3163
|
+
}
|
|
3164
|
+
);
|
|
3165
|
+
}
|
|
3166
|
+
function createCloudContainer() {
|
|
3167
|
+
const registry = /* @__PURE__ */ new Map();
|
|
3168
|
+
const names = /* @__PURE__ */ new Set();
|
|
3169
|
+
let cloud = null;
|
|
3170
|
+
const rebuild = () => {
|
|
3171
|
+
cloud = createCloud(registry);
|
|
3172
|
+
};
|
|
3173
|
+
rebuild();
|
|
3174
|
+
return {
|
|
3175
|
+
register(name, impl) {
|
|
3176
|
+
registry.set(name, impl);
|
|
3177
|
+
names.add(name);
|
|
3178
|
+
rebuild();
|
|
3179
|
+
},
|
|
3180
|
+
unregister(name) {
|
|
3181
|
+
registry.delete(name);
|
|
3182
|
+
names.delete(name);
|
|
3183
|
+
rebuild();
|
|
3184
|
+
},
|
|
3185
|
+
get names() {
|
|
3186
|
+
return [...names];
|
|
3187
|
+
},
|
|
3188
|
+
get cloud() {
|
|
3189
|
+
return cloud;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// packages/runtime/src/functions/runtime/sandbox-fetch.ts
|
|
3195
|
+
var SandboxFetchError = class extends Error {
|
|
3196
|
+
code;
|
|
3197
|
+
constructor(code, message) {
|
|
3198
|
+
super(message);
|
|
3199
|
+
this.name = "SandboxFetchError";
|
|
3200
|
+
this.code = code;
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
3203
|
+
var DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024 * 1024;
|
|
3204
|
+
var defaultFetch = (url, init) => (
|
|
3205
|
+
// 真实 Response 与内部 ResponseLike 结构性一致;getReader 的多态签名差异属纯类型问题,运行语义等价。
|
|
3206
|
+
globalThis.fetch(url, init)
|
|
3207
|
+
);
|
|
3208
|
+
function isHostAllowed(host, rules) {
|
|
3209
|
+
const h = host.toLocaleLowerCase();
|
|
3210
|
+
return rules.some((raw) => {
|
|
3211
|
+
const rule = raw.trim().toLocaleLowerCase();
|
|
3212
|
+
if (rule.startsWith("*.")) {
|
|
3213
|
+
const base = rule.slice(2);
|
|
3214
|
+
return h === base || h.endsWith(`.${base}`);
|
|
3215
|
+
}
|
|
3216
|
+
return h === rule;
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
var FORBIDDEN = (url) => new SandboxFetchError(
|
|
3220
|
+
"FN_FETCH_FORBIDDEN",
|
|
3221
|
+
`cloud.fetch \u62D2\u7EDD\u8BBF\u95EE "${url}"\uFF1A\u8BE5\u4E3B\u673A\u4E0D\u5728\u6C99\u7BB1\u7F51\u7EDC\u767D\u540D\u5355\u5185\uFF08FN_FETCH_FORBIDDEN\uFF09`
|
|
3222
|
+
);
|
|
3223
|
+
var TOO_LARGE = new SandboxFetchError(
|
|
3224
|
+
"FN_FETCH_TOO_LARGE",
|
|
3225
|
+
"cloud.fetch \u54CD\u5E94\u4F53\u8D85\u8FC7\u4E0A\u9650\uFF08FN_FETCH_TOO_LARGE\uFF09"
|
|
3226
|
+
);
|
|
3227
|
+
function headersToRecord(headers) {
|
|
3228
|
+
const out = {};
|
|
3229
|
+
if (headers === void 0 || headers === null) return out;
|
|
3230
|
+
if (typeof headers.forEach === "function") {
|
|
3231
|
+
;
|
|
3232
|
+
headers.forEach((value, key) => {
|
|
3233
|
+
out[String(key).toLocaleLowerCase()] = String(value);
|
|
3234
|
+
});
|
|
3235
|
+
return out;
|
|
3236
|
+
}
|
|
3237
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
3238
|
+
out[key.toLocaleLowerCase()] = String(value);
|
|
3239
|
+
}
|
|
3240
|
+
return out;
|
|
3241
|
+
}
|
|
3242
|
+
function contentLength(res) {
|
|
3243
|
+
const headers = res.headers;
|
|
3244
|
+
if (headers === void 0 || headers === null) return null;
|
|
3245
|
+
if (typeof headers.get === "function") {
|
|
3246
|
+
const raw2 = headers.get?.("content-length");
|
|
3247
|
+
if (raw2 === void 0 || raw2 === null || raw2.trim() === "") return null;
|
|
3248
|
+
const n2 = Number(raw2);
|
|
3249
|
+
return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
|
|
3250
|
+
}
|
|
3251
|
+
const record = headers;
|
|
3252
|
+
const raw = record["content-length"] ?? record["Content-Length"];
|
|
3253
|
+
if (raw === void 0 || raw === null) return null;
|
|
3254
|
+
const n = Number(raw);
|
|
3255
|
+
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
3256
|
+
}
|
|
3257
|
+
function concatChunks(chunks, total) {
|
|
3258
|
+
const merged = new Uint8Array(total);
|
|
3259
|
+
let offset = 0;
|
|
3260
|
+
for (const chunk of chunks) {
|
|
3261
|
+
merged.set(chunk, offset);
|
|
3262
|
+
offset += chunk.byteLength;
|
|
3263
|
+
}
|
|
3264
|
+
return merged;
|
|
3265
|
+
}
|
|
3266
|
+
async function readBodyBytes(res, maxBytes) {
|
|
3267
|
+
if (maxBytes > 0) {
|
|
3268
|
+
const cl = contentLength(res);
|
|
3269
|
+
if (cl !== null && cl > maxBytes) throw TOO_LARGE;
|
|
3270
|
+
}
|
|
3271
|
+
const body = res.body;
|
|
3272
|
+
if (body !== void 0 && body !== null) {
|
|
3273
|
+
const reader = body.getReader();
|
|
3274
|
+
const chunks = [];
|
|
3275
|
+
let total = 0;
|
|
3276
|
+
for (; ; ) {
|
|
3277
|
+
const { done, value } = await reader.read();
|
|
3278
|
+
if (done) break;
|
|
3279
|
+
if (value !== void 0) {
|
|
3280
|
+
total += value.byteLength;
|
|
3281
|
+
if (maxBytes > 0 && total > maxBytes) throw TOO_LARGE;
|
|
3282
|
+
chunks.push(value);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
return concatChunks(chunks, total);
|
|
3286
|
+
}
|
|
3287
|
+
if (typeof res.arrayBuffer === "function") {
|
|
3288
|
+
const buffer = await res.arrayBuffer();
|
|
3289
|
+
const bytes2 = new Uint8Array(buffer);
|
|
3290
|
+
if (maxBytes > 0 && bytes2.byteLength > maxBytes) throw TOO_LARGE;
|
|
3291
|
+
return bytes2;
|
|
3292
|
+
}
|
|
3293
|
+
const text = await res.text();
|
|
3294
|
+
const bytes = encoder.encode(text);
|
|
3295
|
+
if (maxBytes > 0 && bytes.byteLength > maxBytes) throw TOO_LARGE;
|
|
3296
|
+
return bytes;
|
|
3297
|
+
}
|
|
3298
|
+
var encoder = new TextEncoder();
|
|
3299
|
+
function errorMessageOf(error) {
|
|
3300
|
+
if (error instanceof Error) return error.message;
|
|
3301
|
+
return String(error);
|
|
3302
|
+
}
|
|
3303
|
+
function normalizeResponse(res, maxBytes) {
|
|
3304
|
+
let cached = null;
|
|
3305
|
+
let cachedPromise = null;
|
|
3306
|
+
const readCached = () => {
|
|
3307
|
+
if (cached !== null) return Promise.resolve(cached);
|
|
3308
|
+
cachedPromise ??= readBodyBytes(res, maxBytes).then((bytes) => {
|
|
3309
|
+
cached = bytes;
|
|
3310
|
+
return bytes;
|
|
3311
|
+
});
|
|
3312
|
+
return cachedPromise;
|
|
3313
|
+
};
|
|
3314
|
+
const read = () => readCached();
|
|
3315
|
+
return {
|
|
3316
|
+
ok: res.ok,
|
|
3317
|
+
status: res.status,
|
|
3318
|
+
statusText: res.statusText,
|
|
3319
|
+
headers: headersToRecord(res.headers),
|
|
3320
|
+
text: async () => decoder.decode(await read()),
|
|
3321
|
+
arrayBuffer: async () => bufferViewToArrayBuffer(await read()),
|
|
3322
|
+
json: async () => JSON.parse(decoder.decode(await read()))
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
var decoder = new TextDecoder();
|
|
3326
|
+
function bufferViewToArrayBuffer(bytes) {
|
|
3327
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3328
|
+
}
|
|
3329
|
+
function createSandboxFetch(options) {
|
|
3330
|
+
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
3331
|
+
const { allowlist, timeoutMs } = options;
|
|
3332
|
+
const maxBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
|
|
3333
|
+
return async (url, init) => {
|
|
3334
|
+
let parsed;
|
|
3335
|
+
try {
|
|
3336
|
+
parsed = new URL(url);
|
|
3337
|
+
} catch {
|
|
3338
|
+
throw new SandboxFetchError("FN_FETCH_INVALID_URL", `cloud.fetch \u6536\u5230\u975E\u6CD5 URL"${url}"`);
|
|
3339
|
+
}
|
|
3340
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw FORBIDDEN(url);
|
|
3341
|
+
if (!isHostAllowed(parsed.hostname, allowlist)) throw FORBIDDEN(url);
|
|
3342
|
+
const controller = new AbortController();
|
|
3343
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3344
|
+
const signals = [controller.signal];
|
|
3345
|
+
if (init?.signal !== void 0) signals.push(init.signal);
|
|
3346
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
3347
|
+
try {
|
|
3348
|
+
let res;
|
|
3349
|
+
try {
|
|
3350
|
+
res = await fetchImpl(url, { ...init, signal });
|
|
3351
|
+
} catch (error) {
|
|
3352
|
+
if (controller.signal.aborted) {
|
|
3353
|
+
throw new SandboxFetchError(
|
|
3354
|
+
"FN_FETCH_TIMEOUT",
|
|
3355
|
+
`cloud.fetch \u8BF7\u6C42\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeoutMs}ms\uFF09`
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
if (init?.signal?.aborted === true) {
|
|
3359
|
+
throw new SandboxFetchError("FN_FETCH_ABORTED", "cloud.fetch \u8BF7\u6C42\u88AB\u8C03\u7528\u65B9\u4E2D\u6B62");
|
|
3360
|
+
}
|
|
3361
|
+
throw new SandboxFetchError(
|
|
3362
|
+
"FN_FETCH_FAILED",
|
|
3363
|
+
`cloud.fetch \u8BF7\u6C42\u5931\u8D25\uFF1A${errorMessageOf(error)}`
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
return normalizeResponse(res, maxBytes);
|
|
3367
|
+
} finally {
|
|
3368
|
+
clearTimeout(timer);
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
// packages/runtime/src/functions/runtime/chain.ts
|
|
3374
|
+
function isChainCapability(value) {
|
|
3375
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3376
|
+
const spec = value[CHAIN_CAPABILITY_KEY];
|
|
3377
|
+
return typeof spec === "object" && spec !== null && Array.isArray(spec.stepMethods) && Array.isArray(spec.terminalMethods);
|
|
3378
|
+
}
|
|
3379
|
+
function createChainClient(capability, port, pending, nextId, spec) {
|
|
3380
|
+
const call = (method, args) => new Promise((resolve3, reject) => {
|
|
3381
|
+
const id = nextId();
|
|
3382
|
+
pending.set(id, { resolve: resolve3, reject });
|
|
3383
|
+
port.postMessage({ type: "rpc", id, capability, method, args });
|
|
3384
|
+
});
|
|
3385
|
+
const makeChainable = (rootArgs, steps, txId) => new Proxy(
|
|
3386
|
+
{},
|
|
3387
|
+
{
|
|
3388
|
+
get(_target, prop) {
|
|
3389
|
+
if (typeof prop !== "string") return void 0;
|
|
3390
|
+
if (spec.stepMethods.includes(prop)) {
|
|
3391
|
+
return (...args) => makeChainable(rootArgs, [...steps, { method: prop, args }], txId);
|
|
3392
|
+
}
|
|
3393
|
+
if (spec.terminalMethods.includes(prop)) {
|
|
3394
|
+
return (...args) => call(DB_RPC.chain, [
|
|
3395
|
+
{
|
|
3396
|
+
rootArgs,
|
|
3397
|
+
steps,
|
|
3398
|
+
terminal: prop,
|
|
3399
|
+
terminalArgs: args,
|
|
3400
|
+
...txId === void 0 ? {} : { txId }
|
|
3401
|
+
}
|
|
3402
|
+
]);
|
|
3403
|
+
}
|
|
3404
|
+
return void 0;
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
);
|
|
3408
|
+
const makeRoot = (txId) => new Proxy(
|
|
3409
|
+
{},
|
|
3410
|
+
{
|
|
3411
|
+
get(_target, prop) {
|
|
3412
|
+
if (typeof prop !== "string") return void 0;
|
|
3413
|
+
if (prop === spec.rootMethod) {
|
|
3414
|
+
return (...rootArgs) => makeChainable(rootArgs, [], txId);
|
|
3415
|
+
}
|
|
3416
|
+
if (spec.directMethods.includes(prop)) {
|
|
3417
|
+
return (...args) => call(prop, args);
|
|
3418
|
+
}
|
|
3419
|
+
if (prop === spec.transactionMethod) {
|
|
3420
|
+
return (fn) => orchestrateTransaction(fn, txId);
|
|
3421
|
+
}
|
|
3422
|
+
return void 0;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
);
|
|
3426
|
+
const orchestrateTransaction = async (fn, _outerTxId) => {
|
|
3427
|
+
const txId = await call(DB_RPC.begin, []);
|
|
3428
|
+
const tx = makeRoot(txId);
|
|
3429
|
+
const settle = async (commit) => {
|
|
3430
|
+
await call(commit ? DB_RPC.commit : DB_RPC.rollback, [txId]).catch(() => void 0);
|
|
3431
|
+
};
|
|
3432
|
+
try {
|
|
3433
|
+
const result = await fn(tx);
|
|
3434
|
+
await settle(true);
|
|
3435
|
+
return result;
|
|
3436
|
+
} catch (error) {
|
|
3437
|
+
await settle(false);
|
|
3438
|
+
throw error;
|
|
3439
|
+
}
|
|
3440
|
+
};
|
|
3441
|
+
return makeRoot(void 0);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// packages/runtime/src/functions/runtime/worker-entry.ts
|
|
3445
|
+
function isRpcCapability(value) {
|
|
3446
|
+
return typeof value === "object" && value !== null && value[RPC_CAPABILITY_KEY] === true;
|
|
3447
|
+
}
|
|
3448
|
+
function createRpcClient(capability, port, pending, nextId) {
|
|
3449
|
+
return new Proxy(
|
|
3450
|
+
{},
|
|
3451
|
+
{
|
|
3452
|
+
get(_target, method) {
|
|
3453
|
+
if (typeof method !== "string") return void 0;
|
|
3454
|
+
return (...args) => {
|
|
3455
|
+
const id = nextId();
|
|
3456
|
+
return new Promise((resolve3, reject) => {
|
|
3457
|
+
pending.set(id, { resolve: resolve3, reject });
|
|
3458
|
+
port.postMessage({ type: "rpc", id, capability, method, args });
|
|
3459
|
+
});
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
function errorMessageOf2(error) {
|
|
3466
|
+
if (error instanceof Error) return error.message;
|
|
3467
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
3468
|
+
const message = error.message;
|
|
3469
|
+
if (typeof message === "string") return message;
|
|
3470
|
+
}
|
|
3471
|
+
return String(error);
|
|
3472
|
+
}
|
|
3473
|
+
function escapeRegExp(text) {
|
|
3474
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3475
|
+
}
|
|
3476
|
+
function firstUserFrame(stack, files) {
|
|
3477
|
+
for (const key of Object.keys(files)) {
|
|
3478
|
+
const match = new RegExp(
|
|
3479
|
+
`(?<![\\w$/.])${escapeRegExp(key)}:(\\d+)(?::(\\d+))?(?::(\\d+))?`
|
|
3480
|
+
).exec(stack);
|
|
3481
|
+
if (match !== null) {
|
|
3482
|
+
return match[2] === void 0 ? `${key}:${match[1]}` : `${key}:${match[1]}:${match[2]}`;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
return void 0;
|
|
3486
|
+
}
|
|
3487
|
+
function errorMessageWithLocation(error, files) {
|
|
3488
|
+
const message = errorMessageOf2(error);
|
|
3489
|
+
const stack = error.stack;
|
|
3490
|
+
if (typeof stack !== "string") return message;
|
|
3491
|
+
const frame = firstUserFrame(stack, files);
|
|
3492
|
+
return frame === void 0 ? message : `${message}\uFF08${frame}\uFF09`;
|
|
3493
|
+
}
|
|
3494
|
+
function createBareResolver(deps) {
|
|
3495
|
+
const platformRequire = createRequire(import.meta.url);
|
|
3496
|
+
const depsRequire = deps.dir === null ? null : createRequire(join7(deps.dir, "__deps__.js"));
|
|
3497
|
+
return (request) => {
|
|
3498
|
+
if (deps.builtin.includes(request)) return platformRequire(request);
|
|
3499
|
+
const root = splitBareSpecifier(request);
|
|
3500
|
+
if (deps.builtin.includes(root)) return platformRequire(request);
|
|
3501
|
+
if (deps.custom.includes(root)) {
|
|
3502
|
+
if (depsRequire === null) {
|
|
3503
|
+
throw new Error(`\u4F9D\u8D56 "${root}" \u672A\u88C5\u8F7D\uFF1A\u9879\u76EE\u4F9D\u8D56\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u53D1\u5E03\u51FD\u6570\u89E6\u53D1\u4F9D\u8D56\u88C5\u8F7D`);
|
|
3504
|
+
}
|
|
3505
|
+
try {
|
|
3506
|
+
return depsRequire(request);
|
|
3507
|
+
} catch (error2) {
|
|
3508
|
+
const message = errorMessageOf2(error2);
|
|
3509
|
+
throw new Error(
|
|
3510
|
+
`\u4F9D\u8D56 "${request}" \u88C5\u8F7D\u5931\u8D25\uFF08\u76EE\u5F55 ${deps.dir}\uFF09\uFF1A\u8BF7\u91CD\u65B0\u53D1\u5E03\u51FD\u6570\u4EE5\u89E6\u53D1\u4F9D\u8D56\u5B89\u88C5\u3002\u539F\u56E0\uFF1A${message}`,
|
|
3511
|
+
{ cause: error2 }
|
|
3512
|
+
);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
const error = new Error(
|
|
3516
|
+
`\u6C99\u7BB1\u62D2\u7EDD\u8BE5\u5F15\u7528\uFF1A"${request}"\u3002\u4EC5\u5141\u8BB8\u76F8\u5BF9\u8DEF\u5F84\u6587\u4EF6\u4E0E\u5DF2\u58F0\u660E\u7684\u4F9D\u8D56\uFF08\u5185\u7F6E\u767D\u540D\u5355\u6216 package.json\uFF09\uFF0C\u88F8\u6A21\u5757 "${root}" \u672A\u58F0\u660E\uFF08FN_SANDBOX_BUILTIN_BLOCKED\uFF09`
|
|
3517
|
+
);
|
|
3518
|
+
error.code = SANDBOX_BUILTIN_REJECTED;
|
|
3519
|
+
throw error;
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
function runWorker(input, port) {
|
|
3523
|
+
const emit = (level, args) => {
|
|
3524
|
+
port.postMessage({ type: "log", level, message: args.map(String).join(" ") });
|
|
3525
|
+
};
|
|
3526
|
+
const forwardedConsole = {
|
|
3527
|
+
log: (...args) => emit("log", args),
|
|
3528
|
+
error: (...args) => emit("error", args),
|
|
3529
|
+
warn: (...args) => emit("warn", args)
|
|
3530
|
+
};
|
|
3531
|
+
const container = createCloudContainer();
|
|
3532
|
+
const pending = /* @__PURE__ */ new Map();
|
|
3533
|
+
let rpcId = 0;
|
|
3534
|
+
port.on?.((message) => {
|
|
3535
|
+
const reply = message;
|
|
3536
|
+
if (reply?.type !== "rpc-response" || typeof reply.id !== "number") return;
|
|
3537
|
+
const entry = pending.get(reply.id);
|
|
3538
|
+
if (entry === void 0) return;
|
|
3539
|
+
pending.delete(reply.id);
|
|
3540
|
+
if (reply.ok === true) entry.resolve(reply.result);
|
|
3541
|
+
else entry.reject(new Error(reply.error ?? "RPC \u80FD\u529B\u8C03\u7528\u5931\u8D25"));
|
|
3542
|
+
});
|
|
3543
|
+
for (const capability of input.capabilities ?? []) {
|
|
3544
|
+
container.register(
|
|
3545
|
+
capability.name,
|
|
3546
|
+
isChainCapability(capability.value) ? createChainClient(
|
|
3547
|
+
capability.name,
|
|
3548
|
+
port,
|
|
3549
|
+
pending,
|
|
3550
|
+
() => ++rpcId,
|
|
3551
|
+
capability.value[CHAIN_CAPABILITY_KEY]
|
|
3552
|
+
) : isRpcCapability(capability.value) ? createRpcClient(capability.name, port, pending, () => ++rpcId) : capability.value
|
|
3553
|
+
);
|
|
3554
|
+
}
|
|
3555
|
+
const sandboxGlobals = {
|
|
3556
|
+
console: forwardedConsole,
|
|
3557
|
+
// CF-015:Cloudflare Workers 代码重度依赖 Web 标准 API(URL / Request / Response /
|
|
3558
|
+
// Headers / 流 / 编码 / crypto 等)。Node 18+ 全局内置,直接注入 vm 上下文;
|
|
3559
|
+
// 与 @adep/cf-compat 的 Workers 适配(ctxToRequest / responseToEnvelopeBody)配套。
|
|
3560
|
+
URL,
|
|
3561
|
+
URLSearchParams,
|
|
3562
|
+
Request,
|
|
3563
|
+
Response,
|
|
3564
|
+
Headers,
|
|
3565
|
+
TextEncoder,
|
|
3566
|
+
TextDecoder,
|
|
3567
|
+
AbortController,
|
|
3568
|
+
AbortSignal,
|
|
3569
|
+
ReadableStream,
|
|
3570
|
+
WritableStream,
|
|
3571
|
+
TransformStream,
|
|
3572
|
+
Blob,
|
|
3573
|
+
FormData,
|
|
3574
|
+
crypto,
|
|
3575
|
+
performance,
|
|
3576
|
+
atob,
|
|
3577
|
+
btoa,
|
|
3578
|
+
structuredClone
|
|
3579
|
+
};
|
|
3580
|
+
if (input.env !== void 0) {
|
|
3581
|
+
sandboxGlobals["process"] = { env: input.env };
|
|
3582
|
+
}
|
|
3583
|
+
if (input.fetch !== void 0) {
|
|
3584
|
+
const sandboxFetch = createSandboxFetch({
|
|
3585
|
+
allowlist: input.fetch.allowlist,
|
|
3586
|
+
timeoutMs: input.fetch.timeoutMs,
|
|
3587
|
+
...input.fetch.maxResponseBytes === void 0 ? {} : { maxResponseBytes: input.fetch.maxResponseBytes }
|
|
3588
|
+
});
|
|
3589
|
+
sandboxGlobals["fetch"] = sandboxFetch;
|
|
3590
|
+
container.register("fetch", sandboxFetch);
|
|
3591
|
+
}
|
|
3592
|
+
const ctx = {
|
|
3593
|
+
method: input.request.method,
|
|
3594
|
+
path: input.request.path,
|
|
3595
|
+
query: input.request.query,
|
|
3596
|
+
headers: input.request.headers,
|
|
3597
|
+
...input.request.body === void 0 ? {} : { body: input.request.body },
|
|
3598
|
+
files: input.request.files ?? [],
|
|
3599
|
+
cloud: container.cloud,
|
|
3600
|
+
user: input.request.user ?? null,
|
|
3601
|
+
...input.env === void 0 ? {} : { env: input.env }
|
|
3602
|
+
};
|
|
3603
|
+
try {
|
|
3604
|
+
const { handler } = loadFunctionModule(
|
|
3605
|
+
input.files,
|
|
3606
|
+
input.entry,
|
|
3607
|
+
sandboxGlobals,
|
|
3608
|
+
input.deps === void 0 ? void 0 : createBareResolver(input.deps)
|
|
3609
|
+
);
|
|
3610
|
+
const result = Promise.resolve(handler(ctx));
|
|
3611
|
+
void result.then(
|
|
3612
|
+
(value) => {
|
|
3613
|
+
port.postMessage({ type: "result", body: value === void 0 ? null : value });
|
|
3614
|
+
return void 0;
|
|
3615
|
+
},
|
|
3616
|
+
(error) => {
|
|
3617
|
+
port.postMessage({
|
|
3618
|
+
type: "error",
|
|
3619
|
+
message: errorMessageWithLocation(error, input.files),
|
|
3620
|
+
code: error.code,
|
|
3621
|
+
// FN-011:函数抛错可携带 status(4xx/5xx),透传给执行器落 ExecutorError.status。
|
|
3622
|
+
status: error.status
|
|
3623
|
+
});
|
|
3624
|
+
return void 0;
|
|
3625
|
+
}
|
|
3626
|
+
);
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
port.postMessage({
|
|
3629
|
+
type: "error",
|
|
3630
|
+
message: errorMessageWithLocation(error, input.files),
|
|
3631
|
+
code: error.code,
|
|
3632
|
+
status: error.status
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
var workerPort = parentPort;
|
|
3637
|
+
if (workerPort !== null && workerData !== void 0) {
|
|
3638
|
+
const port = {
|
|
3639
|
+
// eslint-disable-next-line unicorn/require-post-message-target-origin -- node worker_threads 无 targetOrigin 语义
|
|
3640
|
+
postMessage: (message) => workerPort.postMessage(message),
|
|
3641
|
+
on: (listener) => {
|
|
3642
|
+
workerPort.on("message", (value) => listener(value));
|
|
3643
|
+
}
|
|
3644
|
+
};
|
|
3645
|
+
runWorker(workerData, port);
|
|
3646
|
+
}
|
|
3647
|
+
|
|
2412
3648
|
// packages/cli/src/dev.ts
|
|
2413
3649
|
async function loadConfig(cwd) {
|
|
2414
3650
|
const name = basename(resolve2(cwd));
|
|
@@ -2416,7 +3652,8 @@ async function loadConfig(cwd) {
|
|
|
2416
3652
|
return {
|
|
2417
3653
|
name: config?.name ?? name,
|
|
2418
3654
|
functionsDir: config?.functionsDir ?? "functions",
|
|
2419
|
-
functionsPrefix: config?.functions_prefix ?? ""
|
|
3655
|
+
functionsPrefix: config?.functions_prefix ?? "",
|
|
3656
|
+
...config?.environment === void 0 ? {} : { environment: config.environment }
|
|
2420
3657
|
};
|
|
2421
3658
|
}
|
|
2422
3659
|
function parseEnvFile(content) {
|
|
@@ -2443,7 +3680,7 @@ async function collectFunctions(dir) {
|
|
|
2443
3680
|
}
|
|
2444
3681
|
for (const entry of entries) {
|
|
2445
3682
|
const rel = prefix.length === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
2446
|
-
const full =
|
|
3683
|
+
const full = join8(sub, entry.name);
|
|
2447
3684
|
if (entry.isDirectory()) {
|
|
2448
3685
|
await walk(full, rel);
|
|
2449
3686
|
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
@@ -2510,7 +3747,7 @@ function printBoundaries(log) {
|
|
|
2510
3747
|
}
|
|
2511
3748
|
}
|
|
2512
3749
|
async function ensureGitignore(cwd) {
|
|
2513
|
-
const gitignorePath =
|
|
3750
|
+
const gitignorePath = join8(cwd, ".gitignore");
|
|
2514
3751
|
const existing = await readFile5(gitignorePath, "utf8").catch(() => "");
|
|
2515
3752
|
if (existing.split(/\r?\n/).includes(".adep/")) return;
|
|
2516
3753
|
await writeFile3(gitignorePath, `${existing.replace(/\n+$/, "")}
|
|
@@ -2525,9 +3762,11 @@ async function startDevServer(options) {
|
|
|
2525
3762
|
if (options.prefix !== void 0) {
|
|
2526
3763
|
config.functionsPrefix = options.prefix.replace(/^\/+|\/+$/g, "");
|
|
2527
3764
|
}
|
|
2528
|
-
const functionsDir =
|
|
3765
|
+
const functionsDir = join8(cwd, config.functionsDir);
|
|
2529
3766
|
const coldStartAt = Date.now();
|
|
2530
|
-
const executor = new WorkerFunctionExecutor(
|
|
3767
|
+
const executor = new WorkerFunctionExecutor({
|
|
3768
|
+
deps: { rootDir: cwd, builtin: LOCAL_BUILTIN_DEPS }
|
|
3769
|
+
});
|
|
2531
3770
|
await mkdir3(functionsDir, { recursive: true });
|
|
2532
3771
|
const runtime = await createSimRuntime({
|
|
2533
3772
|
cwd,
|
|
@@ -2537,19 +3776,91 @@ async function startDevServer(options) {
|
|
|
2537
3776
|
});
|
|
2538
3777
|
await ensureGitignore(cwd);
|
|
2539
3778
|
let files = await collectFunctions(functionsDir);
|
|
2540
|
-
let env =
|
|
3779
|
+
let env = {
|
|
3780
|
+
...config.environment,
|
|
3781
|
+
...await loadSimEnv(cwd).catch(() => ({}))
|
|
3782
|
+
};
|
|
2541
3783
|
const dbBundle = runtime.bundle;
|
|
2542
3784
|
const invokeHandler = createSimInvokeHandler({ executor, files });
|
|
3785
|
+
let stateRuntime = null;
|
|
3786
|
+
let stateFingerprint = "";
|
|
3787
|
+
const bareResolver = createBareResolver({
|
|
3788
|
+
dir: null,
|
|
3789
|
+
builtin: [...LOCAL_BUILTIN_DEPS],
|
|
3790
|
+
custom: []
|
|
3791
|
+
});
|
|
3792
|
+
const collectStateDeclarations = () => {
|
|
3793
|
+
const declared = [];
|
|
3794
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
3795
|
+
for (const path of Object.keys(files)) {
|
|
3796
|
+
if (path === STATE_OBJECTS_ENTRY || path.endsWith(`/${STATE_OBJECTS_ENTRY}`)) {
|
|
3797
|
+
dirs.add(path === STATE_OBJECTS_ENTRY ? "" : path.slice(0, -STATE_OBJECTS_ENTRY.length - 1));
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
for (const dir of dirs) {
|
|
3801
|
+
const prefix = dir.length === 0 ? "" : `${dir}/`;
|
|
3802
|
+
const fileMap = {};
|
|
3803
|
+
for (const [path, content] of Object.entries(files)) {
|
|
3804
|
+
if (path.startsWith(prefix)) fileMap[path] = content;
|
|
3805
|
+
}
|
|
3806
|
+
declared.push({ dir, files: fileMap });
|
|
3807
|
+
}
|
|
3808
|
+
return declared;
|
|
3809
|
+
};
|
|
3810
|
+
const stateObjectBareResolver = (request) => {
|
|
3811
|
+
const state = resolveStateObjectSpecifier(request);
|
|
3812
|
+
if (state !== void 0) return state;
|
|
3813
|
+
return bareResolver(request);
|
|
3814
|
+
};
|
|
3815
|
+
const refreshStateRuntime = () => {
|
|
3816
|
+
const declarations = collectStateDeclarations();
|
|
3817
|
+
const fingerprint = declarations.map(
|
|
3818
|
+
(d) => `${d.dir}\0${d.files[`${d.dir === "" ? "" : `${d.dir}/`}${STATE_OBJECTS_ENTRY}`] ?? ""}`
|
|
3819
|
+
).join("");
|
|
3820
|
+
if (stateRuntime !== null && stateFingerprint === fingerprint) return stateRuntime;
|
|
3821
|
+
stateRuntime = new StateObjectRuntime(createProjectDriverStateBackend(runtime.db.driver));
|
|
3822
|
+
stateRuntime.define(DemoCounter);
|
|
3823
|
+
stateRuntime.define(DemoChatRoom);
|
|
3824
|
+
for (const d of declarations) {
|
|
3825
|
+
const loaded = loadStateObjectClasses(
|
|
3826
|
+
d.files,
|
|
3827
|
+
`${d.dir === "" ? "" : `${d.dir}/`}${STATE_OBJECTS_ENTRY}`,
|
|
3828
|
+
{
|
|
3829
|
+
resolveBare: stateObjectBareResolver
|
|
3830
|
+
}
|
|
3831
|
+
);
|
|
3832
|
+
for (const cls of loaded) stateRuntime.define(cls);
|
|
3833
|
+
}
|
|
3834
|
+
stateFingerprint = fingerprint;
|
|
3835
|
+
return stateRuntime;
|
|
3836
|
+
};
|
|
3837
|
+
let stateAlarmTimer;
|
|
3838
|
+
const startStateAlarmScheduler = () => {
|
|
3839
|
+
if (stateAlarmTimer !== void 0) return;
|
|
3840
|
+
stateAlarmTimer = setInterval(() => {
|
|
3841
|
+
void refreshStateRuntime().pump(Date.now()).catch((error) => {
|
|
3842
|
+
log(
|
|
3843
|
+
`[adep] \u72B6\u6001\u5BF9\u8C61 alarm tick \u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`
|
|
3844
|
+
);
|
|
3845
|
+
});
|
|
3846
|
+
}, options.stateAlarmTickMs ?? 1e3);
|
|
3847
|
+
stateAlarmTimer.unref?.();
|
|
3848
|
+
};
|
|
3849
|
+
const stopStateAlarmScheduler = () => {
|
|
3850
|
+
if (stateAlarmTimer === void 0) return;
|
|
3851
|
+
clearInterval(stateAlarmTimer);
|
|
3852
|
+
stateAlarmTimer = void 0;
|
|
3853
|
+
};
|
|
2543
3854
|
const reload = async () => {
|
|
2544
3855
|
const [nextFiles, envText2] = await Promise.all([
|
|
2545
3856
|
collectFunctions(functionsDir),
|
|
2546
|
-
readFile5(
|
|
3857
|
+
readFile5(join8(cwd, ".env.local"), "utf8").catch(() => "")
|
|
2547
3858
|
]);
|
|
2548
3859
|
files = nextFiles;
|
|
2549
|
-
env = parseEnvFile(envText2);
|
|
3860
|
+
env = { ...config.environment, ...parseEnvFile(envText2) };
|
|
2550
3861
|
};
|
|
2551
|
-
const envText = await readFile5(
|
|
2552
|
-
env = parseEnvFile(envText);
|
|
3862
|
+
const envText = await readFile5(join8(cwd, ".env.local"), "utf8").catch(() => "");
|
|
3863
|
+
env = { ...config.environment, ...parseEnvFile(envText) };
|
|
2553
3864
|
let debounceTimer;
|
|
2554
3865
|
let watcher;
|
|
2555
3866
|
try {
|
|
@@ -2609,6 +3920,43 @@ async function startDevServer(options) {
|
|
|
2609
3920
|
});
|
|
2610
3921
|
return;
|
|
2611
3922
|
}
|
|
3923
|
+
if (fnName === "state") {
|
|
3924
|
+
const normalizedPrefix = config.functionsPrefix.replace(/^\/+|\/+$/g, "");
|
|
3925
|
+
const rest = normalizedPrefix.length === 0 ? url.pathname : url.pathname.replace(new RegExp(`^/${normalizedPrefix}`), "");
|
|
3926
|
+
const parsed = parseStatePath(rest);
|
|
3927
|
+
if (parsed === null) {
|
|
3928
|
+
writeJson(res, 400, {
|
|
3929
|
+
error: { code: "STATE_BAD_PATH", message: "\u72B6\u6001\u5BF9\u8C61\u8DEF\u5F84\uFF1A/api/state/:type/:id/:method" }
|
|
3930
|
+
});
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
const rawBody = await bodyOf(req);
|
|
3934
|
+
const parsedBody = parseStateBody(
|
|
3935
|
+
req.method === "GET" || req.method === "HEAD" ? null : rawBody === void 0 ? null : typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody)
|
|
3936
|
+
);
|
|
3937
|
+
if ("error" in parsedBody) {
|
|
3938
|
+
writeJson(res, 400, { error: { code: "STATE_BAD_ARGS", message: parsedBody.error } });
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
try {
|
|
3942
|
+
const result = await refreshStateRuntime().invoke(
|
|
3943
|
+
parsed.type,
|
|
3944
|
+
parsed.id,
|
|
3945
|
+
parsed.method,
|
|
3946
|
+
parsedBody.args
|
|
3947
|
+
);
|
|
3948
|
+
writeJson(res, 200, { ok: true, data: result });
|
|
3949
|
+
} catch (error) {
|
|
3950
|
+
if (error instanceof StateObjectError) {
|
|
3951
|
+
writeJson(res, error.code === "STATE_TYPE_NOT_REGISTERED" ? 404 : 400, {
|
|
3952
|
+
error: { code: error.code, message: error.message }
|
|
3953
|
+
});
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
writeJson(res, 500, { error: { code: "STATE_INVOKE_ERROR", message: String(error) } });
|
|
3957
|
+
}
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
2612
3960
|
const entry = resolveFunctionEntry(files, fnName);
|
|
2613
3961
|
if (entry === void 0) {
|
|
2614
3962
|
log(`[adep] ${req.method ?? "GET"} /${fnName} -> 404\uFF08\u51FD\u6570\u4E0D\u5B58\u5728\uFF09`);
|
|
@@ -2620,8 +3968,15 @@ async function startDevServer(options) {
|
|
|
2620
3968
|
try {
|
|
2621
3969
|
const result = await executor.execute(input);
|
|
2622
3970
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
2623
|
-
log(`[adep] ${req.method ?? "GET"} /${fnName} -> 200 ${durationMs}ms`);
|
|
2624
3971
|
for (const line of result.logs) log(`[adep] ${line}`);
|
|
3972
|
+
if (isAdepHttpEnvelope(result.body)) {
|
|
3973
|
+
log(
|
|
3974
|
+
`[adep] ${req.method ?? "GET"} /${fnName} -> ${result.body.__adepHttp.status} ${durationMs}ms`
|
|
3975
|
+
);
|
|
3976
|
+
writeEnvelopeResponse(res, result.body);
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
log(`[adep] ${req.method ?? "GET"} /${fnName} -> 200 ${durationMs}ms`);
|
|
2625
3980
|
writeJson(res, 200, result.body === void 0 ? null : result.body);
|
|
2626
3981
|
} catch (error) {
|
|
2627
3982
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
@@ -2679,10 +4034,12 @@ async function startDevServer(options) {
|
|
|
2679
4034
|
log(`[adep] curl \u793A\u4F8B\uFF1Acurl ${baseUrl}${curlPath}`);
|
|
2680
4035
|
printBoundaries(log);
|
|
2681
4036
|
log(`[adep] \u6A21\u62DF\u6570\u636E\u76EE\u5F55\uFF1A${simDir(cwd)}`);
|
|
4037
|
+
startStateAlarmScheduler();
|
|
2682
4038
|
return {
|
|
2683
4039
|
port: actualPort,
|
|
2684
4040
|
baseUrl,
|
|
2685
4041
|
close: async () => {
|
|
4042
|
+
stopStateAlarmScheduler();
|
|
2686
4043
|
if (debounceTimer !== void 0) clearTimeout(debounceTimer);
|
|
2687
4044
|
watcher?.close();
|
|
2688
4045
|
await new Promise((resolveClose) => server.close(() => resolveClose()));
|