webloom-framework 0.3.0 → 0.4.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/docs/api.md CHANGED
@@ -1,27 +1,31 @@
1
- # WebLoom API 说明
1
+ # WebLoom v4 API 说明
2
2
 
3
- ## 浏览器 Runtime
3
+ WebLoom 0.4.1 只支持两个真实 JavaScript realm:`window-main` 和
4
+ `shared-worker`。`runtime` 是受限的 `RuntimeKind`,不是可自由填写的环境标签;不支持
5
+ 的 Runtime 在装配边界 fail closed。
4
6
 
5
- WebLoom 0.3.0 只支持两个真实 JavaScript realm:`window-main`
6
- `shared-worker`。`runtime` 是受限的 `RuntimeKind`,不是可自由填写的环境标签;
7
- 不支持的 runtime 在装配边界 fail closed。不实现 Server、Service Worker 或
8
- 其它服务端 PluginHost。
7
+ 一次 Runtime 启动生成不可复用的 `runtimeInstanceId`。每个插件运行单元启动生成不可
8
+ 复用的 `instanceId`。同一个 SharedWorker 接收多个 Window 连接时,Worker 单元仍只有
9
+ 一个实例;每条物理端口拥有独立的 peer、调用、订阅和 exposure。
9
10
 
10
- 一次 Runtime 启动会生成不可复用的 `runtimeInstanceId`。每个插件运行单元启动
11
- 会生成不可复用的运行单元 `instanceId`。同一 SharedWorker 接收多个 Window 连接
12
- 时,Worker 单元仍只有一个实例;端口本身隔离请求和取消空间,物理端口身份不进入
13
- 公共 Runtime 或 RemoteService wire。
11
+ ## capability 与普通插件
14
12
 
15
- ## 普通插件 API
13
+ Capability 对象是契约、类型和运行时校验的唯一入口。跨 realm 只发送不含函数的
14
+ `{ kind, id, version }` descriptor;parser、transfer extractor 和 handler 留在各自
15
+ realm。
16
16
 
17
17
  ```ts
18
- import { createWindowApp, definePlugin } from "webloom-framework";
18
+ import { createWindowApp, defineCapability, definePlugin } from "webloom-framework";
19
+
20
+ const Hello = defineCapability<{ value: string }>({
21
+ kind: "local", id: "hello.service", version: "1",
22
+ });
19
23
 
20
24
  const hello = definePlugin({
21
25
  id: "hello",
22
- provides: ["hello.service"],
26
+ provides: [Hello] as const,
23
27
  setup(ctx) {
24
- ctx.provide("hello.service", { value: "world" });
28
+ ctx.provide(Hello, { value: "world" });
25
29
  ctx.onDispose(() => {
26
30
  // 释放本插件登记的资源。
27
31
  });
@@ -29,144 +33,177 @@ const hello = definePlugin({
29
33
  });
30
34
 
31
35
  const app = await createWindowApp({ plugins: [hello] });
32
- const service = app.capability<{ value: string }>("hello.service");
36
+ const service = app.capability(Hello);
33
37
  ```
34
38
 
35
- `definePlugin()` 将静态 `manifest/descriptor` 与当前 realm `setup` 分开保存。
36
- 静态 descriptor 可用于验证和快照,不携带函数。`createWindowApp()` 自动固定
37
- `window-main`、生成实例身份、创建内部 Implementation Registry、批量注册并
38
- 等待初始启动;使用者不需要手工 `register()`。
39
+ local capability 不能跨 Runtime;RPC/stream capability 必须提供生产 `ValueParser`:
39
40
 
40
- `createWindowApp()` 的 Promise 只有在必需插件成功后才成功。失败会抛出包含
41
- `pluginId`、`unitId` `phase` 的 `RuntimeInitializationError`。非必需插件的
42
- 失败保留在 Runtime 快照中,不会被伪装为 running。
41
+ ```ts
42
+ const ReadProfile = defineCapability({
43
+ kind: "rpc",
44
+ id: "profile.read",
45
+ version: "1",
46
+ request: profileRequestParser,
47
+ response: profileResponseParser,
48
+ });
43
49
 
44
- ## SharedWorker
50
+ const Changes = defineCapability({
51
+ kind: "stream",
52
+ id: "profile.changes",
53
+ version: "1",
54
+ request: profileWatchParser,
55
+ item: profileChangeParser,
56
+ });
57
+
58
+ const profile = definePlugin({
59
+ id: "profile",
60
+ provides: [ReadProfile, Changes] as const,
61
+ setup(ctx) {
62
+ ctx.handle(ReadProfile, (request, call) => loadProfile(request.userId, call.signal));
63
+ ctx.handle(Changes, (request, call) => observeProfiles(request, call.signal));
64
+ },
65
+ });
66
+ ```
67
+
68
+ `ctx.provide(C, value)` 只注册声明的 local;`ctx.handle(C, handler)` 只注册声明的
69
+ RPC/stream。`ctx.capability(C)` 和 `ctx.optionalCapability(C)` 的参数、返回值由 C
70
+ 推导,调用者不再手写请求/结果泛型,也不能临时传入 transfer 数组。
71
+
72
+ ## SharedWorker 与双向 peer
45
73
 
46
74
  Worker 入口:
47
75
 
48
76
  ```ts
49
77
  import { definePlugin, startSharedWorkerApp } from "webloom-framework";
78
+ import { Health } from "./contracts";
50
79
 
51
- const storage = definePlugin({
52
- id: "storage",
53
- provides: ["storage.service"],
80
+ const coordinator = definePlugin({
81
+ id: "coordinator",
82
+ provides: [Health] as const,
54
83
  setup(ctx) {
55
- ctx.provide("storage.service", {
56
- handle(request: { key: string }) {
57
- return { key: request.key };
58
- },
59
- });
84
+ ctx.handle(Health, (request) => ({ type: request.type, instanceId: ctx.instanceId }));
60
85
  },
61
86
  });
62
87
 
63
- startSharedWorkerApp({ id: "coordinator", plugins: [storage] });
88
+ startSharedWorkerApp({ id: "coordinator", plugins: [coordinator], expose: [Health] });
64
89
  ```
65
90
 
66
- Window 入口:
91
+ Window 侧把 Bundler 生成的 JavaScript Worker URL 传给连接器:
67
92
 
68
93
  ```ts
69
- // Vite emits a hashed JavaScript SharedWorker asset from this importer.
70
- import coordinatorWorkerUrl from "./coordinator.worker.ts?sharedworker&url";
94
+ import workerUrl from "./coordinator.worker.ts?sharedworker&url";
71
95
  import { connectSharedWorker } from "webloom-framework";
96
+ import { Health } from "./contracts";
72
97
 
98
+ const runtime = connectSharedWorker({ id: "coordinator", url: workerUrl });
99
+ const result = await runtime.capability(Health).call({ type: "health" });
100
+ ```
101
+
102
+ `connectSharedWorker()` 同步返回 `RuntimeHandle`。远程 capability 的 proxy 构造不等待
103
+ Worker;第一次 call/subscribe 在一个有限 deadline 内等待精确的 Runtime、contract 和
104
+ service exposure。断线、协议不兼容、超时和撤销都返回结构化错误;框架不自动重连或重放。
105
+
106
+ 页面反向能力必须先存在于 WindowApp:
107
+
108
+ ```ts
109
+ const page = await createWindowApp({ plugins: [pageIoPlugin] });
73
110
  const runtime = connectSharedWorker({
74
111
  id: "coordinator",
75
- url: coordinatorWorkerUrl,
112
+ url: workerUrl,
113
+ client: { app: page, expose: [LocalStorageIo] },
76
114
  });
115
+ ```
116
+
117
+ Worker 的 handler 通过 `call.peer.capability(LocalStorageIo)` 得到当前端口对应页面的
118
+ typed client。`PeerController` 只在可信 `configurePeer` 回调中出现,不暴露原始 port、Worker 或发送函数;`peer.expose(C, options)`
119
+ 只允许暴露本 Host 已注册的 RPC/stream,并在 provider scope、peer scope 和领域 scope
120
+ 任一撤销时同步移除。授权更新必须 revoke 旧 exposure 后新建,旧 proxy 永不换绑。
121
+
122
+ 如果页面需要在已有 Host 上分阶段增加远程依赖,从 `/advanced` 使用:
77
123
 
78
- const storage = runtime.capability("storage.service");
79
- await storage.call({ key: "hello" });
124
+ ```ts
125
+ import { attachRemote, registerPlugins } from "webloom-framework/advanced";
126
+
127
+ const detach = await attachRemote(page, runtime);
128
+ await registerPlugins(page, [remoteConsumerPlugin]);
129
+ // 结束页面生命周期时:detach(); await page.dispose();
80
130
  ```
81
131
 
82
- 连接入口必须创建真实的 `new SharedWorker(url, { type: "module" })`,并同步返回
83
- 本地 `RuntimeHandle`。Worker 只发布完整 `RuntimeSnapshot`;`capability()` 总是
84
- 返回惰性代理,第一次 `call()` 在同一个有限 deadline 内等待目录和远程执行。断线、
85
- 协议不兼容、Worker 重启或 Provider 实例变化都会同步撤销旧代理。显式重建只建立
86
- 新句柄和新代理,不会重放可能产生外部副作用的调用,也不会静默替换旧代理的绑定。
132
+ 普通 `createWindowApp()` 不接受 Host 或测试 scope 注入,也不会根据 Worker manifest 创建
133
+ 第二套 Window 单元。
87
134
 
88
- 这里的 `url` 必须是 Bundler 产出的 JavaScript Worker URL。Vite 使用
89
- `?sharedworker&url` 或等价的独立 Rollup entry;不要把
90
- `new URL("./coordinator.worker.ts", import.meta.url)` 作为普通参数传入框架,
91
- 因为框架内部的 `new SharedWorker()` 不会让 Vite 重新发现调用方源码入口。
135
+ ## wire、快照与错误
92
136
 
93
- `RuntimeHandle` 提供:
137
+ 唯一 Runtime wire 是 `webloom.runtime.v1`,消息为 snapshot、runtime-error、call、result、
138
+ error、cancel、next、credit。快照只有 ready 状态发布当前可调用 services;每条 peer 有
139
+ 独立严格递增 revision。旧协议被明确拒绝,不尝试降级解析。
94
140
 
95
- | 成员 | 语义 |
96
- | --- | --- |
97
- | `runtimeId` | Worker 的逻辑标识 |
98
- | `runtimeInstanceId` | 当前 Worker 物理启动身份 |
99
- | `state()` | `starting / ready / disconnected / failed / stopping / disposed` 快照 |
100
- | `capability()` | 立即获取惰性代理;第一次 `call()` 精确绑定 Runtime/service instance |
101
- | `subscribe()` | 观察 Runtime/Unit/服务快照 |
102
- | `dispose()` | 同步撤销本句柄,异步关闭连接资源 |
141
+ 框架错误通过 `WebLoomError` 暴露,至少覆盖 `protocol_mismatch`、`invalid_snapshot`、
142
+ `capability_unavailable`、`contract_mismatch`、`request_validation_failed`、
143
+ `response_validation_failed`、`request_clone_failed`、`response_clone_failed`、
144
+ `transfer_invalid`、`handler_failed`、`call_timeout`、`request_cancelled`、
145
+ `service_revoked`、`service_stale`、`transport_unavailable`、
146
+ `runtime_initialization_failed` `stream_overflow`。错误只带脱敏 message/details,
147
+ 不回显 request、密钥、口令、凭据或授权 headers;发送请求后的超时不推断远端副作用一定
148
+ 未发生。
103
149
 
104
- ### Window 投影 Worker capability
150
+ ## transfer stream
105
151
 
106
- Window Host 不会根据 Worker manifest 创建假运行单元。需要使用 Worker capability
107
- 的页面插件应把已经 `ready` 的句柄传给 Window App:
152
+ RPC 契约可声明 `transfer.request(value)`、`transfer.response(value)`,stream 契约可声明
153
+ `transfer.item(value)`。顺序固定为 parser extractor/去重/可达性校验 → `postMessage`;
154
+ 接收端再次 parser。多个 TypedArray 视图共享同一个 backing buffer 时,转移其中一个会
155
+ 转移整个 buffer 的所有权。发送后取消不恢复所有权,也不会自动重传;迟到结果不交付给
156
+ 旧 proxy。
157
+
158
+ Stream handler 返回 `AsyncIterable`,订阅返回 `ready`、`closed` 和幂等 `cancel()`:
108
159
 
109
160
  ```ts
110
- const app = await createWindowApp({
111
- remoteRuntime: runtime,
112
- plugins: [definePlugin({
113
- id: "window-consumer",
114
- dependencies: [{
115
- capability: "coordinator.service",
116
- contractVersion: "coordinator.service.v1",
117
- sourceRuntime: "shared-worker",
118
- }],
119
- setup(ctx) {
120
- const coordinator = ctx.serviceBridge?.requireProxy({
121
- capabilityId: "coordinator.service",
122
- contractVersion: "coordinator.service.v1",
123
- runtime: "shared-worker",
124
- }, ctx.scope);
125
- if (!coordinator) throw new Error("Remote service bridge is unavailable");
126
- ctx.provide("window.coordinator", coordinator);
127
- },
128
- })],
129
- });
161
+ const sub = runtime.capability(Changes).subscribe(
162
+ { userId: "123" },
163
+ { onNext: renderChange, timeoutMs: 5_000 },
164
+ );
165
+ await sub.ready;
166
+ await sub.closed;
130
167
  ```
131
168
 
132
- `remoteRuntime` 只向 Host 投影当前完整快照中的服务和单元状态;setup 函数不会进入
133
- Worker。这里的 `coordinator` 是惰性代理,业务在调用边界执行
134
- `await coordinator.call({ type: "health" })`。断线时 Host 同步撤销页面插件的 Scope
135
- 和远程代理,显式重建后的新代理必须重新取得,不会静默重绑旧引用。
169
+ 默认 credit 16,窗口范围为 1–256;没有 credit 时 producer 不拉取 iterator,item
170
+ 交付完成后才补回 credit。sequence 从 1 连续递增;错序、重复、非法 credit、消费者
171
+ 回调失败和撤销都会终止当前订阅,不影响其它 peer。长流没有自动心跳或持久重放保证。
136
172
 
137
- ## 生命周期和 Scope
173
+ ## Scope、诊断与 React
138
174
 
139
- RuntimeUnit 实例存在的条件是:目标 Runtime 存活、插件启用意图为 true、硬依赖
140
- 已就绪并且当前 Runtime 已装配实现。每个实例仍由框架创建一个内部
141
- `ResourceScope`,用于 `AbortSignal`、capability ownership、task/subscription
142
- ownership 和 cleanup callbacks;这个 Scope 不再从用户声明的生命周期分类推导。
175
+ `scope.listen()`、`scope.interval()`、`scope.subscribe()` 都返回幂等 release,并在同步
176
+ revoke 时解除资源;它们复用同一个 Scope resource ledger。异步创建使用 `scope.acquire()`,
177
+ 晚到资源仍会在 Scope 已撤销后尽力释放。
143
178
 
144
- 停用顺序固定为:同步阻止新 capability 和调用、撤销旧引用、触发 Scope
145
- `AbortSignal`、执行 setup teardown `ctx.onDispose()`,最后发布停止/清理状态。
146
- `revoke()` 先形成安全边界,`dispose()` 再等待异步收尾;清理失败和超时通过
147
- `LifecycleDisposeResult` 暴露。
179
+ App/Handle 提供不可变 `state()`、`inspect()`、`subscribe()`。inspect 只报告 Runtime、
180
+ 插件/单元、scope、peer framework-owned pending/stream 计数;`explain()` 返回稳定的
181
+ provider、契约不匹配、依赖阻塞、scope 撤销和初始化失败原因,不包含业务载荷。
148
182
 
149
- 领域状态(例如 owner、session epoch、Vault lock/unlock、桶世代和最终 I/O
150
- fence)不属于 WebLoom Runtime 生命周期。应用自己的 Coordinator/服务控制器
151
- 负责推进领域状态,再通过新的服务快照让旧代理失效。
183
+ React `/react`:
152
184
 
153
- ## 依赖和契约
185
+ ```tsx
186
+ import { WebLoomProvider, useCapability, usePluginState } from "webloom-framework/react";
187
+
188
+ function Panel() {
189
+ const health = useCapability(Health);
190
+ const plugin = usePluginState("coordinator");
191
+ return <output>{plugin?.lifecycleState ?? health ? "ready" : "waiting"}</output>;
192
+ }
193
+
194
+ <WebLoomProvider app={runtime}><Panel /></WebLoomProvider>;
195
+ ```
154
196
 
155
- Runtime 依赖必须声明精确的 `contractVersion` `sourceRuntime`。本地
156
- capability 可以使用 `runtimeCapabilityContractVersion()` 或
157
- `defineRuntimeUnitProvidedContracts()` 生成默认 v1 版本;框架不会根据 capability
158
- 名称猜测远端服务。`providedContracts`、Provider 实例身份、Runtime 启动身份和
159
- 快照 revision 都参与代理绑定。
197
+ Provider 保存稳定 App 引用,hooks 使用外部 store 一致性机制;capability、plugin
198
+ selector 按相关状态订阅,不把每次全局版本变化广播给所有消费者。
160
199
 
161
- ## 服务桥和 wire codec
200
+ ## 入口边界
162
201
 
163
- 低层 `createServiceBridge()` 仍可用于复杂打包和协议扩展。它只接受同一连接、
164
- 权威身份、连续快照和精确契约版本;传输层每次调用生成独立 `callId`,业务
165
- `operationId` 可以复用但不参与响应关联。默认消息 codec 生成
166
- `webloom.remote-service.*`;迁移旧协议时可传入显式前缀。
202
+ - 主入口:capability、插件、三个 Runtime 构造函数、App/Handle、契约/错误/Scope;不加载 React 或底层 transport。
203
+ - `/advanced`:Host/graph/registry、peer exposure、strict v4 bridge/provider 和分阶段装配。
204
+ - `/react`:WebLoomProvider、typed capability/plugin/resource hooks。
205
+ - `/testing`:生产 parser 驱动的 fake transport、可控 Worker/Scope harness。
167
206
 
168
- 普通插件不需要接触 `MessagePort`、握手或 codec。测试中的 `MessageChannel`
169
- 证明 transport simulation;真实浏览器验收仍需确认 `Window`
170
- `SharedWorkerGlobalScope` realm marker、setup 次数和多页面连接行为。
171
- 仓库提供 `scripts/browser-runtime-fixture/` 与 `pnpm run test:browser`;缺少
172
- Playwright/Chromium 时脚本明确报告 unsupported,不回退为 Node 或同页面模拟。
207
+ 真实浏览器验收必须使用 Bundler 产出的 SharedWorker chunk 和 Chromium;Node
208
+ MessageChannel 单测只证明 transport simulation,不替代 Window/SharedWorker realm、多个
209
+ 页面、transfer ownership Worker 退出验收。