@adep/web-container 0.1.0 → 0.1.1
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/README.md +49 -13
- package/dist/completion.d.ts +78 -0
- package/dist/container.d.ts +19 -0
- package/dist/index.d.ts +21 -4
- package/dist/index.js +4824 -95
- package/dist/module-loader.d.ts +34 -0
- package/dist/node-resolve.d.ts +38 -0
- package/dist/npm-client.d.ts +33 -7
- package/dist/offline/index.d.ts +9 -0
- package/dist/offline/net.d.ts +51 -0
- package/dist/offline/queue.d.ts +81 -0
- package/dist/offline/workspace.d.ts +60 -0
- package/dist/persistence.d.ts +97 -0
- package/dist/shell.d.ts +9 -0
- package/dist/sim/ctx.d.ts +19 -0
- package/dist/sim/db.d.ts +33 -0
- package/dist/sim/index.d.ts +24 -0
- package/dist/sim/kv.d.ts +23 -0
- package/dist/sim/realtime.d.ts +61 -0
- package/dist/sim/runner.d.ts +66 -0
- package/dist/sim/runtime.d.ts +48 -0
- package/dist/sim/storage.d.ts +42 -0
- package/dist/strip-types.d.ts +32 -0
- package/dist/tar.d.ts +30 -0
- package/dist/test-framework.d.ts +110 -0
- package/dist/test-runner.d.ts +63 -0
- package/dist/vfs.d.ts +10 -0
- package/dist/web-container.esm.js +5628 -0
- package/dist/web-container.iife.js +5646 -0
- package/dist/worker-runtime.d.ts +16 -1
- package/package.json +16 -19
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VFS 上的 CommonJS 模块加载器 + 静态 ESM import 变换(FN-021)。
|
|
3
|
+
*
|
|
4
|
+
* 安装后的 `node_modules/` 只有按 Node 解析规则命中还不够,还要**跑得起来**:
|
|
5
|
+
* - `createRequire(vfs, file)`:同步从 VFS 读文件、以 CJS 包装(`module/exports/require`)
|
|
6
|
+
* 求值,带按文件缓存与 `.json` 支持;解析全部委托 `node-resolve.ts`(含 main/exports)。
|
|
7
|
+
* - `transformStaticImports(source)`:把**单行静态** `import ... from '...'` 改写为 require
|
|
8
|
+
* 调用(default / 具名 / 命名空间 / side-effect 与组合形态)。多行 import、动态
|
|
9
|
+
* `import()` 与 `export` 语句不在变换范围——沙箱执行器(worker/vm)历史上就不支持
|
|
10
|
+
* ESM 导出,这里只补「import 第三方包」这一条首页承诺,不支持的形态保持原生语法错误
|
|
11
|
+
* 而不会被静默吃掉。
|
|
12
|
+
*/
|
|
13
|
+
import type { VirtualFileSystem } from './vfs';
|
|
14
|
+
import { type ExportCondition } from './node-resolve';
|
|
15
|
+
/** 已加载模块缓存:绝对路径 → module.exports。 */
|
|
16
|
+
export type ModuleCache = Map<string, {
|
|
17
|
+
exports: unknown;
|
|
18
|
+
}>;
|
|
19
|
+
export interface RequireOptions {
|
|
20
|
+
/** exports 条件(`require()` 用 require,变换后的 ESM import 用 import)。 */
|
|
21
|
+
condition?: ExportCondition;
|
|
22
|
+
/** 共享模块缓存(同一轮执行内幂等,Node 的 require cache 语义)。 */
|
|
23
|
+
cache?: ModuleCache;
|
|
24
|
+
}
|
|
25
|
+
/** interop:`import d from 'x'` 的默认值取法(与 Node ESM-CJS 互操作一致)。 */
|
|
26
|
+
export declare function interopDefault(value: unknown): unknown;
|
|
27
|
+
/** 在给定 VFS 与导入方文件上构造同步 `require`。找不到 / 求值失败抛可读错误。 */
|
|
28
|
+
export declare function createRequire(vfs: VirtualFileSystem, fromFileAbs: string, options?: RequireOptions): (specifier: string) => unknown;
|
|
29
|
+
/** 是否使用了模块语法(require 调用或静态 import 行)——决定是否走模块加载路径。 */
|
|
30
|
+
export declare function usesModuleSyntax(source: string): boolean;
|
|
31
|
+
/** 把单行静态 import 语句改写为 require 调用(变换后源码交给 CJS 执行器)。 */
|
|
32
|
+
export declare function transformStaticImports(source: string): string;
|
|
33
|
+
/** 变换 + require 注入所需的沙箱全局(供 worker/vm 运行时组装)。 */
|
|
34
|
+
export declare function createSandboxModuleGlobals(vfs: VirtualFileSystem, entryFileAbs: string): Record<string, unknown>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 解析所需的最小文件系统视图(`VirtualFileSystem` 天然满足)。 */
|
|
2
|
+
export interface ResolveFs {
|
|
3
|
+
exists(path: string): boolean;
|
|
4
|
+
readFile(path: string): string;
|
|
5
|
+
}
|
|
6
|
+
/** package.json 中本模块消费的字段(其余字段忽略)。 */
|
|
7
|
+
export interface PackageMeta {
|
|
8
|
+
name?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
main?: unknown;
|
|
11
|
+
exports?: unknown;
|
|
12
|
+
dependencies?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
/** Node 条件名:`import`(ESM 入口)与 `require`(CJS 入口)。 */
|
|
15
|
+
export type ExportCondition = 'import' | 'require';
|
|
16
|
+
/** 按扩展名 / 目录索引补全的文件候选后缀(顺序即优先级,与 Node 一致)。 */
|
|
17
|
+
export declare const RESOLVE_EXTENSIONS: readonly [".js", ".mjs", ".cjs", ".json"];
|
|
18
|
+
/** 解析失败错误(消息面向终端用户可读)。 */
|
|
19
|
+
export declare class ResolveError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/** 安全解析 package.json 文本;非法 JSON / 非对象一律 ResolveError。 */
|
|
23
|
+
export declare function parsePackageMeta(content: string, context: string): PackageMeta;
|
|
24
|
+
/**
|
|
25
|
+
* 解析包的入口文件绝对路径(不校验文件存在——存在性由调用方经 `resolveFile` 兜底)。
|
|
26
|
+
* 规则见文件头注释;不支持的形状抛可读错误。
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolvePackageEntry(meta: PackageMeta, pkgDirAbs: string, condition: ExportCondition): string;
|
|
29
|
+
/** 拆分裸说明符为包名 + 子路径(scoped 包取两段)。 */
|
|
30
|
+
export declare function splitPackageSpecifier(spec: string): {
|
|
31
|
+
name: string;
|
|
32
|
+
subpath: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* 解析一个说明符到 VFS 文件绝对路径。`fromFileAbs` 是导入方文件(绝对路径)。
|
|
36
|
+
* 找不到抛 ResolveError(消息含导入方与说明符,可操作)。
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveImport(fs: ResolveFs, specifier: string, fromFileAbs: string, condition?: ExportCondition): string;
|
package/dist/npm-client.d.ts
CHANGED
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* npm 客户端(FN-012 对外 SDK
|
|
2
|
+
* npm 客户端(FN-012 对外 SDK / FN-021 安装闭环):经平台 `/npm-relay/*` 中继取真实
|
|
3
|
+
* registry 依赖,并把 tarball **解包进虚拟文件系统 `node_modules/`**。
|
|
3
4
|
*
|
|
4
|
-
* 浏览器虚拟系统无法直连 npm registry(跨域)→
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 浏览器虚拟系统无法直连 npm registry(跨域)→ 全部请求走平台中继。`install` 的完整链路:
|
|
6
|
+
* 1. `/package` 解析包名 + spec 到确定版本(服务端缓存元数据);
|
|
7
|
+
* 2. 本地 `node_modules/<name>` 已有同版本 → 直接命中返回(重复安装幂等、零下载);
|
|
8
|
+
* 3. 否则 `/tarball` 下载 `.tgz` 字节 → gzip 解压 + ustar 解包(`tar.ts`,零依赖手写);
|
|
9
|
+
* 4. 读取包内 package.json 的 `dependencies`,按同一链路**递归**安装传递依赖
|
|
10
|
+
* (深度上限 + 循环依赖硬错误);
|
|
11
|
+
* 5. **先暂存、后提交**:整棵依赖闭包全部抓取 + 解包成功后才一次性写入 VFS——
|
|
12
|
+
* 任何一步失败(非白名单包、中继不可达、tarball 损坏)都不落一个字节的
|
|
13
|
+
* `node_modules/`,杜绝「装了个半成品、import 时半可用」的状态。
|
|
14
|
+
*
|
|
15
|
+
* VFS 的文件内容是字符串(UTF-8),解包按文本解码落盘;纯二进制条目(如 `.node`)
|
|
16
|
+
* 为 lossy 形态——这类包本来就不该进浏览器沙箱,解析期会以可读错误暴露。
|
|
7
17
|
*/
|
|
8
18
|
import type { CloudFetch } from '@adep/types';
|
|
9
19
|
export interface NpmPersistence {
|
|
10
20
|
exists(path: string): boolean;
|
|
11
21
|
writeFile(path: string, contents: string): void;
|
|
12
22
|
}
|
|
23
|
+
/** `install` 需要读回已装包的 package.json 做幂等判定(VirtualFileSystem 满足)。 */
|
|
24
|
+
export interface NpmInstallStorage extends NpmPersistence {
|
|
25
|
+
readFile(path: string): string;
|
|
26
|
+
}
|
|
13
27
|
export interface NpmClientOptions {
|
|
14
28
|
/** 平台 npm 中继基础路径(如 `/api/v1/npm-relay`;相对绝对均可)。 */
|
|
15
29
|
baseUrl: string;
|
|
16
|
-
/**
|
|
17
|
-
storage:
|
|
30
|
+
/** 本地落盘(虚拟文件系统:tarball 缓存 + node_modules 解包)。 */
|
|
31
|
+
storage: NpmInstallStorage;
|
|
18
32
|
/** 网络实现(缺省 globalThis.fetch)。 */
|
|
19
33
|
fetchImpl?: CloudFetch;
|
|
34
|
+
/** 传递依赖递归深度上限(缺省 5,超限显式报错而非静默截断)。 */
|
|
35
|
+
maxDepth?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface NpmInstalledPackage {
|
|
38
|
+
name: string;
|
|
39
|
+
version: string;
|
|
20
40
|
}
|
|
21
41
|
export interface NpmInstallResult {
|
|
22
42
|
name: string;
|
|
@@ -24,9 +44,15 @@ export interface NpmInstallResult {
|
|
|
24
44
|
distTags: string[];
|
|
25
45
|
/** 本地缓存 tarball 的 VFS 路径。 */
|
|
26
46
|
tarballPath: string;
|
|
27
|
-
/** 本次是否发生了真实下载(false = 本地缓存命中)。 */
|
|
47
|
+
/** 本次是否发生了真实下载(false = 已安装 / 本地缓存命中)。 */
|
|
28
48
|
downloaded: boolean;
|
|
49
|
+
/** 本次闭包安装到的全部包(含传递依赖,拓扑序:根包在前)。 */
|
|
50
|
+
packages: NpmInstalledPackage[];
|
|
51
|
+
/** 根包在 VFS 中的安装目录(相对根)。 */
|
|
52
|
+
nodeModulesPath: string;
|
|
29
53
|
}
|
|
54
|
+
/** 传递依赖递归深度上限:真实浅树(lodash 零依赖、dayjs 零依赖)远低于它;超它即失控。 */
|
|
55
|
+
export declare const DEFAULT_MAX_INSTALL_DEPTH = 5;
|
|
30
56
|
/** 解析 `name` 或 `name@spec` 到 { name, spec? }(含 scoped 包)。 */
|
|
31
57
|
export declare function parseSpec(spec: string): {
|
|
32
58
|
name: string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 网络状态探测与在线/离线切换(FN-017,PRD §2.14.2)。
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:
|
|
5
|
+
* - 浏览器 `online` / `offline` 事件作为主信号,但不盲信(有时事件触发但实际仍不可达);
|
|
6
|
+
* - 主动探测仅打 `GET /api/v1/health`(轻量、无鉴权、无副作用),**禁止轮询风暴**:
|
|
7
|
+
* 离线态下最多每 30s 探测一次,在线态下不主动探测(由事件驱动);
|
|
8
|
+
* - 状态切换是单向的:offline → online 必须经过一次成功的 health 探测才确认(避免假在线);
|
|
9
|
+
* online → offline 由 `offline` 事件或 fetch 失败立即触发。
|
|
10
|
+
*
|
|
11
|
+
* 本文件零 DOM 依赖之外的运行时 API(仅用 `window.addEventListener` / `fetch`),
|
|
12
|
+
* 可在浏览器与 Service Worker 中复用;Node 环境下退化为「永远在线」。
|
|
13
|
+
*/
|
|
14
|
+
export type NetworkStatus = 'online' | 'offline' | 'checking';
|
|
15
|
+
export interface NetworkState {
|
|
16
|
+
status: NetworkStatus;
|
|
17
|
+
/** 最近一次状态切换的时间戳(ms)。 */
|
|
18
|
+
lastChangedAt: number;
|
|
19
|
+
/** 最近一次成功 health 探测的时间戳(ms),离线时为 null。 */
|
|
20
|
+
lastHealthOkAt: number | null;
|
|
21
|
+
}
|
|
22
|
+
type StatusListener = (state: NetworkState) => void;
|
|
23
|
+
declare class NetworkMonitor {
|
|
24
|
+
private state;
|
|
25
|
+
private listeners;
|
|
26
|
+
private healthTimer;
|
|
27
|
+
private started;
|
|
28
|
+
/** 启动监听(幂等)。在浏览器环境下绑定 online/offline 事件。 */
|
|
29
|
+
start(): void;
|
|
30
|
+
/** 停止监听并清理定时器。 */
|
|
31
|
+
stop(): void;
|
|
32
|
+
/** 获取当前网络状态快照。 */
|
|
33
|
+
getState(): NetworkState;
|
|
34
|
+
/** 订阅状态变化;返回取消订阅函数。 */
|
|
35
|
+
subscribe(listener: StatusListener): () => void;
|
|
36
|
+
/** 主动触发一次 health 探测(用于用户手动点击「重试连接」等场景)。 */
|
|
37
|
+
checkNow(): Promise<boolean>;
|
|
38
|
+
private handleOnlineEvent;
|
|
39
|
+
private handleOfflineEvent;
|
|
40
|
+
private probeHealth;
|
|
41
|
+
private startHealthTimer;
|
|
42
|
+
private stopHealthTimer;
|
|
43
|
+
private setState;
|
|
44
|
+
}
|
|
45
|
+
/** 单例:全局共享一个网络监视器(避免多个实例各自起定时器导致探测风暴)。 */
|
|
46
|
+
export declare const networkMonitor: NetworkMonitor;
|
|
47
|
+
/** 便捷函数:当前是否在线。 */
|
|
48
|
+
export declare function isOnline(): boolean;
|
|
49
|
+
/** 便捷函数:订阅在线状态变化。 */
|
|
50
|
+
export declare function onNetworkChange(listener: StatusListener): () => void;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 待发布队列的客户端存储(FN-017,PRD §2.14.3)。
|
|
3
|
+
*
|
|
4
|
+
* 离线时的发布意图不落服务端版本(版本号只能由服务端生成),而是进本地队列,
|
|
5
|
+
* 回网后由用户确认重放(服务端冲突判定与幂等重放在 FN-018,UI 在 FE-016)。
|
|
6
|
+
*
|
|
7
|
+
* 本单只交付 headless 存储与状态机:
|
|
8
|
+
* - 入队:生成幂等键 queueId(projectId + intentType + baseVersion + 变更文件摘要的 hash),
|
|
9
|
+
* 同键重复入队不产生第二条(幂等);
|
|
10
|
+
* - 出队:按创建时间 FIFO,标记为「重放中」;
|
|
11
|
+
* - 状态机:pending → replaying → done / failed;
|
|
12
|
+
* - 持久化:localStorage(简单、同步、零依赖;后续可换 IndexedDB 但本单不做)。
|
|
13
|
+
*
|
|
14
|
+
* 队列项不含任何 secret(变更文件摘要只含路径 + hash,不含内容)。
|
|
15
|
+
*/
|
|
16
|
+
export type PublishIntentType = 'function_publish' | 'function_delete' | 'env_update' | 'deployment';
|
|
17
|
+
export type QueueItemStatus = 'pending' | 'replaying' | 'done' | 'failed';
|
|
18
|
+
export interface QueueItem {
|
|
19
|
+
/** 幂等键:projectId + intentType + baseVersion + fileSummaryHash 的 SHA-256 前 16 位。 */
|
|
20
|
+
queueId: string;
|
|
21
|
+
projectId: string;
|
|
22
|
+
intentType: PublishIntentType;
|
|
23
|
+
/** 变更文件摘要:[{ path, contentHash }],只含路径与内容 hash,不含文件内容本身。 */
|
|
24
|
+
fileSummary: Array<{
|
|
25
|
+
path: string;
|
|
26
|
+
contentHash: string;
|
|
27
|
+
}>;
|
|
28
|
+
/** 发布时的基线版本(用于回网后服务端冲突判定)。 */
|
|
29
|
+
baseVersion: string;
|
|
30
|
+
status: QueueItemStatus;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
/** 失败时的错误信息(status=failed 时存在)。 */
|
|
34
|
+
error?: string;
|
|
35
|
+
/** 重放后的服务端版本号(status=done 时存在)。 */
|
|
36
|
+
serverVersion?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface EnqueueOptions {
|
|
39
|
+
projectId: string;
|
|
40
|
+
intentType: PublishIntentType;
|
|
41
|
+
fileSummary: Array<{
|
|
42
|
+
path: string;
|
|
43
|
+
contentHash: string;
|
|
44
|
+
}>;
|
|
45
|
+
baseVersion: string;
|
|
46
|
+
}
|
|
47
|
+
declare class PublishQueue {
|
|
48
|
+
private items;
|
|
49
|
+
private loaded;
|
|
50
|
+
/** 从 localStorage 加载队列(懒加载,首次调用时执行)。 */
|
|
51
|
+
private ensureLoaded;
|
|
52
|
+
/** 持久化到 localStorage。 */
|
|
53
|
+
private persist;
|
|
54
|
+
/** 入队:幂等,同 queueId 不重复入队。返回队列项(新入队或已存在)。 */
|
|
55
|
+
enqueue(options: EnqueueOptions): QueueItem;
|
|
56
|
+
/** 出队:按创建时间 FIFO,取第一个 pending 项并标记为 replaying。 */
|
|
57
|
+
dequeue(): QueueItem | null;
|
|
58
|
+
/** 标记重放成功。 */
|
|
59
|
+
markDone(queueId: string, serverVersion: string): void;
|
|
60
|
+
/** 标记重放失败。 */
|
|
61
|
+
markFailed(queueId: string, error: string): void;
|
|
62
|
+
/** 重试失败项:把 failed 项重置为 pending。 */
|
|
63
|
+
retry(queueId: string): void;
|
|
64
|
+
/** 移除队列项(用户取消或已完成后清理)。 */
|
|
65
|
+
remove(queueId: string): void;
|
|
66
|
+
/** 清空已完成的项(done 状态)。 */
|
|
67
|
+
clearDone(): void;
|
|
68
|
+
/** 获取所有队列项(按创建时间排序)。 */
|
|
69
|
+
list(): QueueItem[];
|
|
70
|
+
/** 按状态筛选。 */
|
|
71
|
+
listByStatus(status: QueueItemStatus): QueueItem[];
|
|
72
|
+
/** 按项目筛选。 */
|
|
73
|
+
listByProject(projectId: string): QueueItem[];
|
|
74
|
+
/** 待发布项数量(pending + replaying)。 */
|
|
75
|
+
pendingCount(): number;
|
|
76
|
+
/** 是否有未同步的改动(pending 或 failed)。 */
|
|
77
|
+
hasUnsyncedChanges(): boolean;
|
|
78
|
+
}
|
|
79
|
+
/** 单例:全局共享一个发布队列。 */
|
|
80
|
+
export declare const publishQueue: PublishQueue;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 离线工作区(FN-017,PRD §2.14.4)。
|
|
3
|
+
*
|
|
4
|
+
* 源码工作副本的浏览器端持久化:逐项目分区,文件路径 → 内容的键值存储。
|
|
5
|
+
* 后端为 IndexedDB(异步、大容量、支持 Blob);Node 环境下退化为内存 Map(仅用于测试)。
|
|
6
|
+
*
|
|
7
|
+
* 设计原则:
|
|
8
|
+
* - 逐项目分区:每个项目一个 object store(`workspace:{projectId}`),避免跨项目污染;
|
|
9
|
+
* - 文件级读写:get/put/delete/list,路径为键,内容为字符串(源码文本)或 Blob(二进制资源);
|
|
10
|
+
* - 快照与恢复:snapshot() 返回整个项目的文件树,restore() 从快照恢复(用于离线初始化);
|
|
11
|
+
* - 变更追踪:记录最后修改时间,用于「未同步」计数与冲突判定;
|
|
12
|
+
* - **secret 不进工作区**:环境变量中的 secret 项只存键名,不存值(值在内存中,刷新后需重新输入)。
|
|
13
|
+
*
|
|
14
|
+
* 本文件不依赖任何 UI 框架,可在 Service Worker 与主线程中复用。
|
|
15
|
+
*/
|
|
16
|
+
export interface WorkspaceFile {
|
|
17
|
+
path: string;
|
|
18
|
+
content: string;
|
|
19
|
+
/** 最后修改时间(ms)。 */
|
|
20
|
+
lastModifiedAt: number;
|
|
21
|
+
/** 是否为 secret 项(secret 项不持久化内容)。 */
|
|
22
|
+
isSecret?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkspaceSnapshot {
|
|
25
|
+
projectId: string;
|
|
26
|
+
files: Array<{
|
|
27
|
+
path: string;
|
|
28
|
+
content: string;
|
|
29
|
+
lastModifiedAt: number;
|
|
30
|
+
}>;
|
|
31
|
+
/** 快照生成时间(ms)。 */
|
|
32
|
+
createdAt: number;
|
|
33
|
+
}
|
|
34
|
+
declare class OfflineWorkspace {
|
|
35
|
+
private backend;
|
|
36
|
+
constructor();
|
|
37
|
+
/** 读取文件。 */
|
|
38
|
+
getFile(projectId: string, path: string): Promise<WorkspaceFile | null>;
|
|
39
|
+
/** 写入文件(自动更新 lastModifiedAt)。 */
|
|
40
|
+
putFile(projectId: string, path: string, content: string, isSecret?: boolean): Promise<void>;
|
|
41
|
+
/** 删除文件。 */
|
|
42
|
+
deleteFile(projectId: string, path: string): Promise<void>;
|
|
43
|
+
/** 列出项目所有文件。 */
|
|
44
|
+
listFiles(projectId: string): Promise<WorkspaceFile[]>;
|
|
45
|
+
/** 检查文件是否存在。 */
|
|
46
|
+
hasFile(projectId: string, path: string): Promise<boolean>;
|
|
47
|
+
/** 生成项目快照(用于离线初始化或导出)。 */
|
|
48
|
+
snapshot(projectId: string): Promise<WorkspaceSnapshot>;
|
|
49
|
+
/** 从快照恢复(覆盖现有文件)。 */
|
|
50
|
+
restore(projectId: string, snapshot: WorkspaceSnapshot): Promise<void>;
|
|
51
|
+
/** 清空项目工作区。 */
|
|
52
|
+
clear(projectId: string): Promise<void>;
|
|
53
|
+
/** 获取项目文件数。 */
|
|
54
|
+
fileCount(projectId: string): Promise<number>;
|
|
55
|
+
/** 获取最近修改的文件(用于「未同步」展示)。 */
|
|
56
|
+
recentlyModified(projectId: string, limit?: number): Promise<WorkspaceFile[]>;
|
|
57
|
+
}
|
|
58
|
+
/** 单例:全局共享一个离线工作区。 */
|
|
59
|
+
export declare const offlineWorkspace: OfflineWorkspace;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器终端文件系统的可插拔持久化(FE-022,PRD §2.14)。
|
|
3
|
+
*
|
|
4
|
+
* `vfs.ts` 自 FN-012 起就写着「持久化由上层负责」,但上层从未接上:`bootstrap()` 每次新建内存 VFS,
|
|
5
|
+
* 终端里 `mkdir` / `echo > file` 建出来的文件刷新即丢(本单修的就是这一段)。
|
|
6
|
+
*
|
|
7
|
+
* 设计(与 `offline/workspace.ts` 同一取向:浏览器 IndexedDB、Node 内存,零运行时依赖):
|
|
8
|
+
* - **整树快照**而非逐条目写:VFS 是全内存的小树(浏览器内工作区),一次 `save(namespace, entries)`
|
|
9
|
+
* 比逐文件 put/delete 少一类「漏删」的错(VFS 里删掉的文件不会在库里留孤儿);
|
|
10
|
+
* - **namespace 分区**:按项目 / 按用途隔离(IDE 终端用 `terminal-fs:<projectId>`),互不污染;
|
|
11
|
+
* - **去抖落盘**:订阅 `vfs.onMutate`,合并一次命令引发的多次写之后再写库;提供显式 `flush()`
|
|
12
|
+
* 给「刷新前必须落盘」与 e2e 用(不依赖定时器,判据确定)。
|
|
13
|
+
*
|
|
14
|
+
* 本文件零 node 依赖,浏览器与 Node 单测共用;`indexedDB` / `localStorage` 不可用时自动降级为内存。
|
|
15
|
+
*/
|
|
16
|
+
import type { WebContainerDirectoryTree } from '@adep/types';
|
|
17
|
+
import { VirtualFileSystem } from './vfs';
|
|
18
|
+
/** 快照中的一个条目(目录与文件分记,`contents` 只对文件有意义)。 */
|
|
19
|
+
export interface FsPersistEntry {
|
|
20
|
+
path: string;
|
|
21
|
+
kind: 'file' | 'dir';
|
|
22
|
+
contents?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface FsPersistenceBackend {
|
|
25
|
+
load(namespace: string): Promise<FsPersistEntry[]>;
|
|
26
|
+
save(namespace: string, entries: FsPersistEntry[]): Promise<void>;
|
|
27
|
+
clear(namespace: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/** 把当前 VFS 拍平成快照条目(目录与文件都含,根 `/` 不入库——它是常量)。 */
|
|
30
|
+
export declare function snapshotVfs(vfs: VirtualFileSystem): FsPersistEntry[];
|
|
31
|
+
/** 从快照恢复:先目录后文件(`writeFile` 本身会自动补父,分序只为语义清晰与空目录可恢复)。 */
|
|
32
|
+
export declare function restoreVfs(vfs: VirtualFileSystem, entries: readonly FsPersistEntry[]): void;
|
|
33
|
+
/**
|
|
34
|
+
* 快照 → 目录树(`WebContainerDirectoryTree` 形态):供只能经 `WebContainer.mount()` 消费的上层复用,
|
|
35
|
+
* 不必持有 `VirtualFileSystem` 实例。
|
|
36
|
+
*/
|
|
37
|
+
export declare function entriesToTree(entries: readonly FsPersistEntry[]): WebContainerDirectoryTree;
|
|
38
|
+
/** 内存后端(Node 单测 / SSR / 无 IndexedDB 环境)。每次调用产出独立实例,测试之间互不串味。 */
|
|
39
|
+
export declare function createMemoryFsBackend(): FsPersistenceBackend;
|
|
40
|
+
/** IndexedDB 后端:单库单仓(`namespace` 为主键),整棵树的快照一条记录。 */
|
|
41
|
+
export declare function createIndexedDbFsBackend(dbName?: string): FsPersistenceBackend;
|
|
42
|
+
/** 缺省后端:浏览器 IndexedDB,其余(Node 单测 / SSR)内存。 */
|
|
43
|
+
export declare function pickDefaultFsBackend(): FsPersistenceBackend;
|
|
44
|
+
export interface FsPersistenceOptions {
|
|
45
|
+
/** 要持久化的 VFS(与 `bootstrap({ vfs })` 同一个实例)。 */
|
|
46
|
+
vfs: VirtualFileSystem;
|
|
47
|
+
/** 分区键,如 `terminal-fs:<projectId>`。 */
|
|
48
|
+
namespace: string;
|
|
49
|
+
/** 后端(缺省按环境自动选)。 */
|
|
50
|
+
backend?: FsPersistenceBackend;
|
|
51
|
+
/** 去抖窗口(ms,默认 150)。 */
|
|
52
|
+
debounceMs?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface FsPersistence {
|
|
55
|
+
/** 启动时灌回历史(返回恢复的条目数);必须在挂载初始文件之前调用,让服务端草稿覆盖为权威。 */
|
|
56
|
+
hydrate(): Promise<number>;
|
|
57
|
+
/** 立即落盘(取消挂起的去抖)。 */
|
|
58
|
+
flush(): Promise<void>;
|
|
59
|
+
/** 清空该 namespace 的持久化内容(不动当前 VFS)。 */
|
|
60
|
+
clear(): Promise<void>;
|
|
61
|
+
/** 是否有尚未落盘的变更。 */
|
|
62
|
+
pending(): boolean;
|
|
63
|
+
/** 最后一次成功落盘时间(ms),未落过为 null。 */
|
|
64
|
+
lastSavedAt(): number | null;
|
|
65
|
+
/** 退订 VFS 变更(组件卸载时调用)。 */
|
|
66
|
+
dispose(): void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 把 VFS 接到持久化后端上:`hydrate()` 灌回 → 订阅变更去抖落盘 → `flush()` 强制落盘。
|
|
70
|
+
* 恢复期间自身写操作不再触发落盘(否则「读回来又写回去」会白跑一次并可能覆盖更新中的快照)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function createFsPersistence(options: FsPersistenceOptions): FsPersistence;
|
|
73
|
+
/** 键值存储端口(`localStorage` 形态;测试可注入替身)。 */
|
|
74
|
+
export interface KvStorage {
|
|
75
|
+
getItem(key: string): string | null;
|
|
76
|
+
setItem(key: string, value: string): void;
|
|
77
|
+
removeItem(key: string): void;
|
|
78
|
+
}
|
|
79
|
+
/** 内存 KV(Node / 隐私模式 localStorage 不可用时的降级)。 */
|
|
80
|
+
export declare function createMemoryKvStorage(): KvStorage;
|
|
81
|
+
export interface CommandHistory {
|
|
82
|
+
/** 全部历史(旧 → 新)。 */
|
|
83
|
+
list(): string[];
|
|
84
|
+
/** 追加一条(空串与上一条重复忽略),返回当前列表。 */
|
|
85
|
+
push(line: string): string[];
|
|
86
|
+
clear(): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 终端命令历史的持久化存储(FE-022):刷新 / 重开会话后 ↑ 仍可回溯。
|
|
90
|
+
* 与 fs 快照分放两处(KV vs IndexedDB)是刻意的:历史是小而频繁的字符串数组,`localStorage` 同步读写
|
|
91
|
+
* 最贴合(与 FN-017 的待发布队列同口径),不值得为它开一次异步事务。
|
|
92
|
+
*/
|
|
93
|
+
export declare function createCommandHistory(options: {
|
|
94
|
+
key: string;
|
|
95
|
+
limit?: number;
|
|
96
|
+
storage?: KvStorage;
|
|
97
|
+
}): CommandHistory;
|
package/dist/shell.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* 浏览器可跑(零 node 依赖),`runtime` 为可替换适配(QuickJS WASM / Node vm)。
|
|
9
9
|
*/
|
|
10
10
|
import type { VirtualFileSystem } from './vfs';
|
|
11
|
+
import { type CompletionResult } from './completion';
|
|
12
|
+
import type { TestCommandPort } from './test-runner';
|
|
11
13
|
/** JS 运行时适配接口:能加载并执行一段 JS 模块(QuickJS WASM / Web Worker 沙箱 / Node vm 等实现)。 */
|
|
12
14
|
export interface JsRuntime {
|
|
13
15
|
/** 执行脚本文件,返回标准输出文本;实现抛错时以 `stderr` 语义上报。 */
|
|
@@ -25,6 +27,8 @@ export interface ShellCtx {
|
|
|
25
27
|
getCwd(): string;
|
|
26
28
|
setCwd(path: string): void;
|
|
27
29
|
runtime?: JsRuntime;
|
|
30
|
+
/** `test` 命令的运行器(FN-022):由 `bootstrap` 装配;裸 Shell 未注入时如实报「未装配」。 */
|
|
31
|
+
test?: TestCommandPort;
|
|
28
32
|
}
|
|
29
33
|
export declare class Shell {
|
|
30
34
|
private readonly ctx;
|
|
@@ -33,6 +37,11 @@ export declare class Shell {
|
|
|
33
37
|
execute(line: string): Promise<ShellResult>;
|
|
34
38
|
private runSegment;
|
|
35
39
|
private ls;
|
|
40
|
+
/**
|
|
41
|
+
* 对当前输入行求 Tab 补全候选(FE-022):命令位补内建命令名,参数位补 VFS 路径。
|
|
42
|
+
* 无候选不抛错(Tab 不该让终端报错),由调用方决定「不响应」还是「列出可能项」。
|
|
43
|
+
*/
|
|
44
|
+
complete(line: string): CompletionResult;
|
|
36
45
|
/** 相对 cwd 解析为绝对路径(VFS key)。 */
|
|
37
46
|
private resolve;
|
|
38
47
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Cloud } from '@adep/types';
|
|
2
|
+
import type { CapabilityBundle, CapabilityRpcHandler } from '@adep/types';
|
|
3
|
+
/** worker 端口协议的最小投影(`WorkerPort` 的结构子集,进程内实现)。 */
|
|
4
|
+
export interface MinimalPort {
|
|
5
|
+
postMessage(message: unknown): void;
|
|
6
|
+
on(listener: (message: unknown) => void): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 把宿主能力 handler 包成进程内 port:上行 `{type:'rpc', id, capability, method, args}` →
|
|
10
|
+
* 直调 handler → 以 `{type:'rpc-response', id, ok, result|error}` 回执。
|
|
11
|
+
* 错误归一为 `Error(message)`——与 worker 跨线程路径的回执形状一致(码已由 handler 装进 message)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function createLocalPort(handler: CapabilityRpcHandler): MinimalPort;
|
|
14
|
+
/**
|
|
15
|
+
* 按 bundle 产出用户函数侧的 `ctx.cloud`:
|
|
16
|
+
* - 注册语义与 `worker-entry.runWorker` 相同(链式 / 命令通道 / 原值三分支);
|
|
17
|
+
* - 未注册能力访问时抛与线上同文案的可操作提示(createCloudContainer 守卫)。
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildCloudFromBundle(bundle: CapabilityBundle): Cloud;
|
package/dist/sim/db.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器模拟 `cloud.db`(FN-023):`SimSqlEngine`(@adep/runtime/sim/sql-engine,与 CLI sim
|
|
3
|
+
* 同一份语义)+ IndexedDB/内存持久层 → 线上 `createDbCapability` 装配。
|
|
4
|
+
*
|
|
5
|
+
* 一致性红线(与 CLI-004 同一条):**只读复用线上 Provider 的装配逻辑**(`createDbCapability`),
|
|
6
|
+
* 只把驱动换成本文件绑定的进程内引擎——CRUD / 事务 / owned 变更流 / 安全硬约束全走与线上
|
|
7
|
+
* 完全相同的那份代码,契约必然一致(`shared/sim-contract.ts` 契约表在本仓测试里钉死)。
|
|
8
|
+
*
|
|
9
|
+
* 持久化:整库表快照存 `db:<projectId>` 键(`SimKvBackend`),刷新后数据仍在
|
|
10
|
+
* (Gherkin 场景 1 的 IndexedDB 判据)。
|
|
11
|
+
*/
|
|
12
|
+
import type { CapabilityBundle } from '@adep/types';
|
|
13
|
+
import type { ProjectDriver } from '@adep/runtime/db/project-driver';
|
|
14
|
+
import { SimSqlEngine } from '@adep/runtime/sim/sql-engine';
|
|
15
|
+
import type { SimKvBackend } from './kv';
|
|
16
|
+
export interface CreateBrowserSimDbOptions {
|
|
17
|
+
/** 项目 id(持久化分区键;`db:<projectId>`)。 */
|
|
18
|
+
projectId: string;
|
|
19
|
+
/** 持久化后端;缺省按环境自动选(浏览器 IndexedDB / 其余内存)。 */
|
|
20
|
+
kv?: SimKvBackend;
|
|
21
|
+
}
|
|
22
|
+
/** db 持久化键(`runtime.ts` 与诊断面共用;导出便于测试直读)。 */
|
|
23
|
+
export declare function simDbKey(projectId: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* 构造浏览器侧 `cloud.db` 能力 bundle,并暴露绑定它的 `engine`(schema 水合 / 测试断言面)
|
|
26
|
+
* 与 `driver`(ProjectDriver 形状)。内部复用线上 `createDbCapability`,
|
|
27
|
+
* bundle 的 `capabilities[].value`(链式规格)与 `rpcHandlers.db` 与线上完全一致。
|
|
28
|
+
*/
|
|
29
|
+
export declare function createBrowserSimDb(options: CreateBrowserSimDbOptions): {
|
|
30
|
+
bundle: CapabilityBundle;
|
|
31
|
+
engine: SimSqlEngine;
|
|
32
|
+
driver: ProjectDriver;
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器模拟运行时(FN-023):断网在浏览器内执行 cloud.* 真实语义。
|
|
3
|
+
*
|
|
4
|
+
* 能力装配复用 `@adep/runtime` 的线上工厂(只换持久化与传输落点,不改 API 语义):
|
|
5
|
+
* - `cloud.db`:SimSqlEngine(与 CLI sim 同一份引擎语义)+ IndexedDB 持久化(按项目分区);
|
|
6
|
+
* - `cloud.storage`:IndexedDB/blob 驱动,接口与线上 `createStorageCapability` 一致;
|
|
7
|
+
* - `cloud.realtime`:离线内存广播(CLI sim 同源语义)+ 回网切真实 WS 的传输切换;
|
|
8
|
+
* - 离线函数运行器:stripTypes → transformModule → evaluateModuleSource,`ctx` 与线上同形。
|
|
9
|
+
*
|
|
10
|
+
* 与 CLI / 线上的一致性由 `@adep/runtime/shared/sim-contract.ts` 的同一张契约表钉死
|
|
11
|
+
* (本包测试对浏览器驱动逐条复跑该表)。
|
|
12
|
+
*/
|
|
13
|
+
export { pickDefaultSimKv, createMemorySimKv, createIdbSimKv } from './kv';
|
|
14
|
+
export type { SimKvBackend } from './kv';
|
|
15
|
+
export { createBrowserSimDb, simDbKey } from './db';
|
|
16
|
+
export { createBrowserSimStorage, createBrowserStorageDriver, simStorageKey } from './storage';
|
|
17
|
+
export { createBrowserRealtime } from './realtime';
|
|
18
|
+
export type { BrowserRealtimeOptions, MinimalWebSocket, TicketResult } from './realtime';
|
|
19
|
+
export { createLocalPort, buildCloudFromBundle } from './ctx';
|
|
20
|
+
export type { MinimalPort } from './ctx';
|
|
21
|
+
export { createOfflineFunctionRunner } from './runner';
|
|
22
|
+
export type { LocalLogEntry, OfflineRunInput, OfflineRunRequest, OfflineRunResult, OfflineRunnerOptions, } from './runner';
|
|
23
|
+
export { createBrowserSimRuntime } from './runtime';
|
|
24
|
+
export type { BrowserSimRuntime, BrowserSimRuntimeOptions } from './runtime';
|
package/dist/sim/kv.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器模拟运行时的持久化后端(FN-023):异步键值端口,IndexedDB / 内存双实现。
|
|
3
|
+
*
|
|
4
|
+
* 设计与 `persistence.ts`(终端 fs 快照)同一取向:浏览器 IndexedDB、Node 内存,零运行时依赖。
|
|
5
|
+
* 键空间约定(组合层 `runtime.ts` 分配,本文件不管语义):
|
|
6
|
+
* - `db:<projectId>` → SimSqlEngine 的整库表快照(JSON 可结构化克隆值);
|
|
7
|
+
* - `storage:<projectId>` → 项目存储桶 `{ [path]: StoredObject }`。
|
|
8
|
+
*
|
|
9
|
+
* 每次写都是「整值 put」:模拟运行时的写频度与数据量都是开发态规模,不值得做去抖/增量,
|
|
10
|
+
* 简单形状才没有「漏删 / 孤儿」这类错(同 `persistence.ts` 头注释的取舍)。
|
|
11
|
+
*/
|
|
12
|
+
/** 浏览器模拟运行时的持久化端口(值必须是可结构化克隆的;内存实现按引用存)。 */
|
|
13
|
+
export interface SimKvBackend {
|
|
14
|
+
get(key: string): Promise<unknown>;
|
|
15
|
+
set(key: string, value: unknown): Promise<void>;
|
|
16
|
+
remove(key: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** 内存后端(Node 单测 / SSR / 隐私模式降级)。每次调用产出独立实例,测试之间互不串味。 */
|
|
19
|
+
export declare function createMemorySimKv(): SimKvBackend;
|
|
20
|
+
/** IndexedDB 后端:单库单仓(`key` 为主键),一 KV 一记录。 */
|
|
21
|
+
export declare function createIdbSimKv(dbName?: string): SimKvBackend;
|
|
22
|
+
/** 缺省后端:浏览器 IndexedDB,其余(Node 单测 / SSR)内存。 */
|
|
23
|
+
export declare function pickDefaultSimKv(): SimKvBackend;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器模拟 `cloud.realtime`(FN-023):离线内存广播总线(@adep/runtime/sim/realtime 的
|
|
3
|
+
* `SimRealtime`,与 CLI sim 同一份语义)+ **回网切真实 WS** 的传输切换。
|
|
4
|
+
*
|
|
5
|
+
* 语义层(广播 / 拉取式缓冲 / 订阅上限 / 错误码)与 CLI sim / 线上提供方共用一份真相源:
|
|
6
|
+
* - 错误码取 `capability-keys` 的 `REALTIME_CAPABILITY_CODES`(跨 realm 常量的家);
|
|
7
|
+
* - 期望表(publish 回执 / 未知订阅 / except 不可用 / 未知方法)在本仓测试里对浏览器 handler
|
|
8
|
+
* 逐条复跑(同 CLI 侧 REALTIME_SIM_CASES 一族)。
|
|
9
|
+
*
|
|
10
|
+
* 回网切换(Gherkin 场景 3):`goOnline()` 为每个活跃订阅开真实 WS(换票 →
|
|
11
|
+
* `Sec-WebSocket-Protocol` 携带票据,与服务端握手契约对齐);WS 下行消息**只**经
|
|
12
|
+
* 「直接推入该订阅缓冲」这一条路径进入,本地总线的监听保持挂载——两条摄入源的
|
|
13
|
+
* 消息集合不相交(本地 publish 只进总线、服务端消息只进 WS 推送),且每条消息
|
|
14
|
+
* 恰好走一条路径一次,因此不会出现双份。`goOffline()` 关 WS 回到纯本地广播。
|
|
15
|
+
*
|
|
16
|
+
* 边界:在线态下浏览器草稿的 publish 仍只进本地总线(浏览器侧没有「以平台身份发布」的
|
|
17
|
+
* HTTP 面;函数发布语义属于服务端执行面)。WS 断开不自动重连——重试发生在下一次
|
|
18
|
+
* `goOnline()`(重连风暴防护与 networkMonitor 的低频探测同口径)。
|
|
19
|
+
*/
|
|
20
|
+
import { SimRealtime } from '@adep/runtime/sim/realtime';
|
|
21
|
+
import type { CapabilityBundle } from '@adep/types';
|
|
22
|
+
/** 换票回执(`POST /api/v1/realtime/ticket` 的响应形状)。 */
|
|
23
|
+
export interface TicketResult {
|
|
24
|
+
ticket: string;
|
|
25
|
+
}
|
|
26
|
+
/** 最小 WebSocket 面(鸭子类型:Node 测试注入假 WS 走同一协议)。 */
|
|
27
|
+
export interface MinimalWebSocket {
|
|
28
|
+
close(code?: number): void;
|
|
29
|
+
onmessage: ((event: {
|
|
30
|
+
data: unknown;
|
|
31
|
+
}) => void) | null;
|
|
32
|
+
onclose: ((event: {
|
|
33
|
+
code?: number;
|
|
34
|
+
}) => void) | null;
|
|
35
|
+
}
|
|
36
|
+
export interface BrowserRealtimeOptions {
|
|
37
|
+
/** 项目 id(换票入参)。 */
|
|
38
|
+
projectId: string;
|
|
39
|
+
/** WS 基址(缺省由 `location.origin` 推导为 ws(s)://);Node 测试注入。 */
|
|
40
|
+
wsBaseUrl?: string;
|
|
41
|
+
/** 换票提供者(缺省 POST /api/v1/realtime/ticket,credentials include)。 */
|
|
42
|
+
ticketProvider?: () => Promise<TicketResult>;
|
|
43
|
+
/** WebSocket 工厂(Node 测试注入假 WS)。 */
|
|
44
|
+
wsFactory?: (url: string, protocols: string[]) => MinimalWebSocket;
|
|
45
|
+
/** 一次会话内的订阅上限(与 CLI sim 同参 64)。 */
|
|
46
|
+
maxSubscriptions?: number;
|
|
47
|
+
/** 单订阅缓冲上限(与 CLI sim 同参 64,超出丢最旧)。 */
|
|
48
|
+
maxBufferedMessages?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 构造浏览器侧 `cloud.realtime` 能力 bundle + 回网切换器。
|
|
52
|
+
* 传输切换器暴露给组合层(IDE 侧随 networkMonitor 状态调用 `goOnline` / `goOffline`)。
|
|
53
|
+
*/
|
|
54
|
+
export declare function createBrowserRealtime(options: BrowserRealtimeOptions): {
|
|
55
|
+
bundle: CapabilityBundle;
|
|
56
|
+
realtime: SimRealtime;
|
|
57
|
+
goOnline(): Promise<void>;
|
|
58
|
+
goOffline(): void;
|
|
59
|
+
/** 当前处于 WS 摄入态的订阅数(诊断用)。 */
|
|
60
|
+
wsConnectedCount(): number;
|
|
61
|
+
};
|