@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/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 join7, resolve as resolve2 } from "node:path";
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 assertReadOnlyQuery(sql) {
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
- directMethods: ["query", "changes"],
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), { visibility: DEFAULT_VISIBILITY })
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 bundle = mergeBundles([db.bundle, storage.bundle, simRealtime.bundle]);
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 = join7(sub, entry.name);
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 = join7(cwd, ".gitignore");
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 = join7(cwd, config.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 = await loadSimEnv(cwd).catch(() => ({}));
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(join7(cwd, ".env.local"), "utf8").catch(() => "")
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(join7(cwd, ".env.local"), "utf8").catch(() => "");
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()));