yeow-api 0.5.0 → 0.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeow-api",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Yeow API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/core.ts CHANGED
@@ -80,8 +80,8 @@ export { get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove, keys
80
80
  export { add as addRecipe, remove as removeRecipe, getForItem as getRecipesForItem } from './recipe.js';
81
81
  export { Material, getMaterials, getBlocks, getItems } from './material.js';
82
82
  export type { MaterialInfo } from './material.js';
83
- export { registerService, registerNativeService, request as serviceRequest, subscribe as serviceSubscribe, publish as servicePublish } from './service.js';
84
- export type { ServiceResult, NativeServiceResult } from './service.js';
83
+ export { Service, PluginService, NativeService, ServiceReply, registerService, registerNativeService, getService, hasService } from './service.js';
84
+ export type { ServiceKind, ServiceRequestOptions, ServiceResponse, NativeTerminateInfo, NativePlatforms } from './service.js';
85
85
  export { log, Logger } from './log.js';
86
86
  export type { Message } from './message.js';
87
87
  export { getEnv } from './env.js';
package/src/global.d.ts CHANGED
@@ -50,13 +50,22 @@ declare global {
50
50
 
51
51
  interface TextDecoder {
52
52
  readonly encoding: string;
53
- decode(input?: Uint8Array): string;
53
+ decode(input?: ArrayBuffer | ArrayBufferView): string;
54
54
  }
55
55
  const TextDecoder: {
56
56
  new (encoding?: string): TextDecoder;
57
57
  prototype: TextDecoder;
58
58
  };
59
59
 
60
+ // ── performance(高精度单调时间)──
61
+ interface Performance {
62
+ /** 相对上下文起点的毫秒数(单调、亚毫秒精度)。 */
63
+ now(): number;
64
+ /** 上下文创建时的 epoch 毫秒。 */
65
+ readonly timeOrigin: number;
66
+ }
67
+ var performance: Performance;
68
+
60
69
  function setTimeout(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
61
70
  function clearTimeout(id: string): void;
62
71
  function setInterval(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
package/src/service.ts CHANGED
@@ -1,8 +1,32 @@
1
- import { post } from './task.js';
1
+ export type ServiceKind = 'plugin' | 'native';
2
2
 
3
- export interface ServiceResult {
4
- serviceId: string;
5
- token: string;
3
+ export interface ServiceRequestOptions {
4
+ /** 请求头(app 级键值对;`content-type` 为典型项)。 */
5
+ headers?: Record<string, string>;
6
+ /** 便捷别名:等价于 `headers['content-type']`。 */
7
+ contentType?: string;
8
+ /** 请求体:JSON 值,或 `Uint8Array`(原始二进制,运行时解码后原样转发)。 */
9
+ body?: any;
10
+ /** 超时(毫秒);缺省用运行时配置(默认 30000)。 */
11
+ timeout?: number;
12
+ }
13
+
14
+ /**
15
+ * 服务响应(fetch 风格)。响应体始终以字节承载:`json()` / `text()` 在 JS 侧解码,
16
+ * `bytes()` / `arrayBuffer()` 直接取原始字节。`body` 为未来的可读流预留。
17
+ */
18
+ export interface ServiceResponse {
19
+ readonly ok: boolean;
20
+ readonly status: number;
21
+ readonly contentType: string;
22
+ readonly headers: Record<string, string>;
23
+ /** 预留:未来的可读流(当前为 undefined)。 */
24
+ readonly body?: unknown;
25
+ base64(): Promise<string>;
26
+ bytes(): Promise<Uint8Array>;
27
+ arrayBuffer(): Promise<ArrayBuffer>;
28
+ text(): Promise<string>;
29
+ json(): Promise<any>;
6
30
  }
7
31
 
8
32
  export interface NativeTerminateInfo {
@@ -12,40 +36,87 @@ export interface NativeTerminateInfo {
12
36
  output?: string;
13
37
  }
14
38
 
15
- export interface NativeServiceResult {
16
- serviceId: string;
17
- ready: () => Promise<void>;
18
- onTerminate: (handler: (info: NativeTerminateInfo) => void) => void;
39
+ /** 服务回复:服务方 `onRequest` 返回它以携带响应头;返回普通值等价于 JSON body。 */
40
+ export class ServiceReply {
41
+ constructor(public readonly body: any, public readonly headers?: Record<string, string>) {}
19
42
  }
20
43
 
21
- // ── Register Plugin Service ──
44
+ type NativePlatform = string | { file: string };
45
+ export type NativePlatforms = Record<string, NativePlatform>;
46
+
47
+ // ── 内部实现 ──────────────────────────────────────────────────────
22
48
 
23
- export async function registerService(refName: string, onRequest: (path: string, body: any) => any, isPublic = true): Promise<ServiceResult> {
24
- const svcCbId = _registerCallback((payload: any) => {
25
- if (payload?._svc === 'request') {
26
- let body: any = null;
27
- try { body = JSON.parse(payload.body); } catch { body = {}; }
28
- const result = onRequest(payload.path, body);
29
- $send('service', { t: 'response', requestId: payload.requestId, body: result });
49
+ function _response(r: { headers?: Record<string, string>; contentType?: string; base64?: string; status?: number }): ServiceResponse {
50
+ const b64 = r.base64 ?? '';
51
+ const headers: Record<string, string> = {};
52
+ const raw = r.headers || {};
53
+ for (const k of Object.keys(raw)) headers[k.toLowerCase()] = raw[k];
54
+ const ct = r.contentType || headers['content-type'] || 'application/octet-stream';
55
+ headers['content-type'] = ct;
56
+ const decode = () => new TextDecoder().decode(Uint8Array.fromBase64(b64));
57
+ return {
58
+ ok: true,
59
+ status: r.status ?? 200,
60
+ contentType: ct,
61
+ headers,
62
+ base64: () => Promise.resolve(b64),
63
+ bytes: () => Promise.resolve(Uint8Array.fromBase64(b64)),
64
+ arrayBuffer: () => {
65
+ const u8 = Uint8Array.fromBase64(b64);
66
+ return Promise.resolve(u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength ? u8.buffer : u8.slice().buffer);
67
+ },
68
+ text: () => Promise.resolve(decode()),
69
+ json: () => Promise.resolve().then(() => { const t = decode(); return t === '' ? null : JSON.parse(t); }),
70
+ };
71
+ }
72
+
73
+ function _request(serviceId: string, path: string, options: ServiceRequestOptions): Promise<ServiceResponse> {
74
+ return new Promise((resolve, reject) => {
75
+ const cbId = _registerCallback((result: any) => {
76
+ if (result?.err) { reject(new Error(result.err)); return; }
77
+ resolve(_response(result));
78
+ });
79
+ const headers: Record<string, string> = { ...(options.headers || {}) };
80
+ if (options.contentType && !headers['content-type']) headers['content-type'] = options.contentType;
81
+ const body = options.body;
82
+ if (body instanceof Uint8Array) {
83
+ if (!headers['content-type']) headers['content-type'] = 'application/octet-stream';
84
+ } else if (body !== undefined && body !== null) {
85
+ if (!headers['content-type']) headers['content-type'] = 'application/json';
86
+ } else if (!headers['content-type']) {
87
+ headers['content-type'] = 'application/json';
30
88
  }
31
- }, { persistent: true });
32
-
33
- const r = $send('service', { t: 'register', refName, onRequest: svcCbId, public: isPublic }) as ServiceResult & { err?: string; serviceId?: string };
34
- if (r?.err) {
35
- _unregisterCallback(svcCbId);
36
- const e: any = new Error(r.err);
37
- if (r.serviceId) e.serviceId = r.serviceId;
38
- throw e;
39
- }
40
- return r;
89
+ const p: Record<string, unknown> = { t: 'request', serviceId, path, requestId: cbId, headers, contentType: headers['content-type'] };
90
+ if (options.timeout !== undefined && options.timeout !== null) p.timeout = options.timeout;
91
+ if (body instanceof Uint8Array) {
92
+ p.body = body.toBase64();
93
+ p.bodyEncoding = 'base64';
94
+ } else if (body !== undefined && body !== null) {
95
+ p.body = body;
96
+ }
97
+ $send('service', p);
98
+ });
41
99
  }
42
100
 
43
- // ── Register Native Service ──
101
+ function _subscribe(serviceId: string, eventPath: string, handler: (body: any, eventPath: string) => void): () => void {
102
+ const cbId = _registerCallback((payload: any) => { handler(payload.body, payload.eventPath); }, { persistent: true });
103
+ $send('service', { t: 'subscribe', serviceId, eventPath, cb: cbId });
104
+ return () => {
105
+ $send('service', { t: 'unsubscribe', serviceId, eventPath });
106
+ _unregisterCallback(cbId);
107
+ };
108
+ }
44
109
 
45
- type NativePlatform = string | { file: string } | { dir: string; entry: string };
46
- type NativePlatforms = Record<string, NativePlatform>;
110
+ function _unregister(serviceId: string, token?: string): Promise<void> {
111
+ return new Promise((resolve, reject) => {
112
+ const p: Record<string, unknown> = { t: 'unregister', serviceId };
113
+ if (token) p.token = token;
114
+ const r = $send('service', p) as any;
115
+ if (r?.err) reject(new Error(r.err)); else resolve();
116
+ });
117
+ }
47
118
 
48
- function _serviceReady(serviceId: string): Promise<void> {
119
+ function _awaitReady(serviceId: string): Promise<void> {
49
120
  return new Promise((resolve, reject) => {
50
121
  const cbId = _registerCallback((result: any) => {
51
122
  if (result?.err) {
@@ -64,51 +135,138 @@ function _serviceReady(serviceId: string): Promise<void> {
64
135
  });
65
136
  }
66
137
 
67
- export async function registerNativeService(refName: string, platforms: NativePlatforms, isPublic = true): Promise<NativeServiceResult> {
68
- const r = $send('service', { t: 'registerNative', refName, platforms, public: isPublic }) as { serviceId: string; err?: string };
69
- if (r.err) {
70
- const e: any = new Error(r.err);
71
- if ((r as any).serviceId) e.serviceId = (r as any).serviceId;
72
- throw e;
138
+ function _info(serviceId: string): Promise<{ exists: boolean; kind: ServiceKind | null }> {
139
+ return new Promise((resolve, reject) => {
140
+ const r = $send('service', { t: 'info', serviceId }) as any;
141
+ if (r?.err) { reject(new Error(r.err)); return; }
142
+ resolve({ exists: !!r.exists, kind: (r.kind ?? null) as ServiceKind | null });
143
+ });
144
+ }
145
+
146
+ // ── Service 对象(OOP)────────────────────────────────────────────
147
+
148
+ /** 服务句柄基类(插件服务 / 原生服务通用能力)。 */
149
+ export abstract class Service {
150
+ abstract readonly kind: ServiceKind;
151
+ constructor(readonly id: string) {}
152
+
153
+ /** 请求服务(fetch 风格)。失败 / 超时 / 服务卸载时 Promise reject。 */
154
+ request(path: string, options: ServiceRequestOptions = {}): Promise<ServiceResponse> {
155
+ return _request(this.id, path, options);
156
+ }
157
+
158
+ /** 订阅服务事件;返回取消函数。插件 unload / hot-reload 时运行时自动清理。 */
159
+ subscribe(eventPath: string, handler: (body: any, eventPath: string) => void): () => void {
160
+ return _subscribe(this.id, eventPath, handler);
161
+ }
162
+
163
+ /** 卸载服务。Plugin Service 需属主 token,Native Service 需调用方为属主。 */
164
+ unregister(): Promise<void> {
165
+ return _unregister(this.id);
73
166
  }
74
- let terminateCb: string | null = null;
75
- return {
76
- serviceId: r.serviceId,
77
- ready: () => _serviceReady(r.serviceId),
78
- onTerminate(handler) {
79
- if (terminateCb) _unregisterCallback(terminateCb);
80
- terminateCb = _registerCallback((info: unknown) => handler(info as NativeTerminateInfo), { persistent: true });
81
- $send('service', { t: 'registerNativeTerminate', serviceId: r.serviceId, cb: terminateCb });
82
- },
83
- };
84
167
  }
85
168
 
86
- // ── Request ──
169
+ /** 插件服务(JS 服务)。属主句柄(registerService)持有 token;消费者句柄(getService)无 token。 */
170
+ export class PluginService extends Service {
171
+ readonly kind = 'plugin' as const;
172
+ readonly token?: string;
173
+ constructor(id: string, token?: string) {
174
+ super(id);
175
+ this.token = token;
176
+ }
87
177
 
88
- export function request(serviceId: string, path: string, body?: any): Promise<any> {
178
+ /** 发布事件(仅属主:需 token)。 */
179
+ publish(eventPath: string, body?: any): void {
180
+ if (!this.token) throw new Error('publish requires the owner token of plugin service ' + this.id);
181
+ $send('service', { t: 'publish', token: this.token, eventPath, body: body ?? {} });
182
+ }
183
+
184
+ override unregister(): Promise<void> {
185
+ if (!this.token) return Promise.reject(new Error('unregister requires the owner token of plugin service ' + this.id));
186
+ return _unregister(this.id, this.token);
187
+ }
188
+ }
189
+
190
+ /** 原生服务(子进程)。 */
191
+ export class NativeService extends Service {
192
+ readonly kind = 'native' as const;
193
+ private _terminateCb: string | null = null;
194
+ constructor(id: string) { super(id); }
195
+
196
+ /** 等待原生服务就绪(进程 TCP 就绪消息已收到时 resolve)。 */
197
+ ready(): Promise<void> { return _awaitReady(this.id); }
198
+
199
+ /** 注册服务终止钩子(仅属主有效;重复调用替换旧回调)。 */
200
+ onTerminate(handler: (info: NativeTerminateInfo) => void): void {
201
+ if (this._terminateCb) _unregisterCallback(this._terminateCb);
202
+ this._terminateCb = _registerCallback((info: unknown) => handler(info as NativeTerminateInfo), { persistent: true });
203
+ $send('service', { t: 'registerNativeTerminate', serviceId: this.id, cb: this._terminateCb });
204
+ }
205
+ }
206
+
207
+ // ── 注册 / 获取 ───────────────────────────────────────────────────
208
+
209
+ /**
210
+ * 注册 Plugin Service。成功返回 {@link PluginService}(属主句柄,含 token);
211
+ * 失败(权限 / 已存在)抛错——已存在时 `error.serviceId` 指向既有服务,应以其降级接入。
212
+ */
213
+ export function registerService(refName: string, onRequest: (path: string, body: any) => any, isPublic = true): Promise<PluginService> {
89
214
  return new Promise((resolve, reject) => {
90
- const cbId = _registerCallback((result: any) => {
91
- if (result?.err) { reject(new Error(result.err)); } else resolve(result);
92
- });
93
- $send('service', { t: 'request', serviceId, path, body: body || {}, requestId: cbId });
215
+ const svcCbId = _registerCallback((payload: any) => {
216
+ if (payload?._svc === 'request') {
217
+ const result = onRequest(payload.path, payload.body ?? null);
218
+ if (result instanceof ServiceReply) {
219
+ $send('service', { t: 'response', requestId: payload.requestId, headers: result.headers, body: result.body });
220
+ } else {
221
+ $send('service', { t: 'response', requestId: payload.requestId, body: result });
222
+ }
223
+ }
224
+ }, { persistent: true });
225
+ const r = $send('service', { t: 'register', refName, onRequest: svcCbId, public: isPublic }) as any;
226
+ if (r?.err) {
227
+ _unregisterCallback(svcCbId);
228
+ const e: any = new Error(r.err);
229
+ if (r.serviceId) e.serviceId = r.serviceId;
230
+ reject(e);
231
+ return;
232
+ }
233
+ resolve(new PluginService(r.serviceId, r.token));
94
234
  });
95
235
  }
96
236
 
97
- // ── Subscribe ──
98
-
99
- export function subscribe(serviceId: string, eventPath: string, handler: (body: any, eventPath: string) => void): () => void {
100
- const cbId = _registerCallback((payload: any) => {
101
- handler(payload.body, payload.eventPath);
102
- }, { persistent: true });
103
- $send('service', { t: 'subscribe', serviceId, eventPath, cb: cbId });
104
- return () => {
105
- $send('service', { t: 'unsubscribe', serviceId, eventPath });
106
- _unregisterCallback(cbId);
107
- };
237
+ /** 注册 Native Service。成功返回 {@link NativeService};失败抛错。 */
238
+ export function registerNativeService(refName: string, platforms: NativePlatforms, isPublic = true): Promise<NativeService> {
239
+ return new Promise((resolve, reject) => {
240
+ const r = $send('service', { t: 'registerNative', refName, platforms, public: isPublic }) as any;
241
+ if (r?.err) {
242
+ const e: any = new Error(r.err);
243
+ if (r.serviceId) e.serviceId = r.serviceId;
244
+ reject(e);
245
+ return;
246
+ }
247
+ resolve(new NativeService(r.serviceId));
248
+ });
108
249
  }
109
250
 
110
- // ── Publish ──
251
+ /** id 获取服务句柄;不存在时抛错。返回的消费者句柄无 token(不能 publish / unregister 插件服务)。 */
252
+ export function getService(serviceId: string): Promise<Service> {
253
+ return _info(serviceId).then(i => {
254
+ if (!i.exists) {
255
+ const e: any = new Error('Service not found: ' + serviceId);
256
+ e.serviceId = serviceId;
257
+ throw e;
258
+ }
259
+ return i.kind === 'native' ? new NativeService(serviceId) : new PluginService(serviceId);
260
+ });
261
+ }
111
262
 
112
- export function publish(token: string, eventPath: string, body?: any): void {
113
- $send('service', { t: 'publish', token, eventPath, body: body || {} });
263
+ /**
264
+ * 检查服务是否存在。
265
+ *
266
+ * > **不要用它判断后再注册**:检查与注册**不是原子操作**,并发下会竞态——两个插件可能同时检查到
267
+ * > 不存在,随后一个注册成功、另一个被拒绝。注册必须用 `try-catch` 包裹,并在捕获到「已存在」时用
268
+ * > `error.serviceId` 降级接入既有服务。此方法仅用于展示 / 诊断。
269
+ */
270
+ export function hasService(serviceId: string): Promise<boolean> {
271
+ return _info(serviceId).then(i => i.exists);
114
272
  }
package/src/worker.ts CHANGED
@@ -5,6 +5,19 @@ export interface WorkerOptions {
5
5
  entry?: string;
6
6
  /** 代码字符串;与 `entry` 互斥。 */
7
7
  code?: string;
8
+ /**
9
+ * 权限覆盖(可选)。默认继承主插件全部权限。
10
+ * - `allow`:白名单——只有命中的节点允许(未声明时继承主插件权限)
11
+ * - `deny`:黑名单——优先级最高(先于 allow 与继承)
12
+ *
13
+ * 节点格式 `channel:op`(如 `fs:server.readFile`、`http:*`、`task:player.*`、`*`)。
14
+ * **不能提权**:主插件未声明的权限,`allow` 无效(仍按默认拒绝)。
15
+ * 例:`deny: ['*']` 只允许标准 ES 代码;`deny: ['fs:*', 'http:*']` 仅禁用这两类。
16
+ */
17
+ permissions?: {
18
+ allow?: string[];
19
+ deny?: string[];
20
+ };
8
21
  }
9
22
 
10
23
  let _seq = 0;
@@ -35,9 +48,10 @@ function _sendWorkerAsync(t: string, p: Record<string, unknown>): Promise<void>
35
48
  * Worker —— 虚拟插件(独立 QuickJS 上下文 + 线程)。
36
49
  *
37
50
  * - 事件/命令/服务以独立实体注册;调度器任务独立统计
38
- * - 共享主插件的**数据目录**与**权限**
51
+ * - 共享主插件的**数据目录**;权限默认继承主插件,可按需用 `allow`/`deny` 收紧(不可提权)
39
52
  * - 不能创建新的 Worker(嵌套被拒绝)
40
- * - **创建后无法销毁,只能卸载**(卸载物理销毁 JS 上下文,句柄保留——可重新 load
53
+ * - `unload()` 卸载(物理销毁 JS 上下文,句柄保留——可重新 load);
54
+ * `destroy()` **彻底销毁**(移除注册,句柄作废,同名可重建)
41
55
  * - 主插件卸载时连带卸载;/yeow 管理命令不覆盖 Worker;profiler 会统计(标记 created by 主插件)
42
56
  * - Worker 的 JS 错误与主插件同样回传(dev 模式经 source-map 定位)
43
57
  */
@@ -46,15 +60,18 @@ export class Worker {
46
60
  private readonly entry?: string;
47
61
  private readonly code?: string;
48
62
  private readonly key: string;
63
+ private readonly permissions?: { allow?: string[]; deny?: string[] };
49
64
  private _loaded = false;
65
+ private _destroyed = false;
50
66
  private _onMessage: ((msg: any) => void) | null = null;
51
67
  /** 内部 workerId(主插件 JS 侧分配;跨主插件可重复)。 */
52
68
  readonly id: string;
53
69
 
54
- constructor(name: string, entry: string | undefined, code: string | undefined) {
70
+ constructor(name: string, entry: string | undefined, code: string | undefined, permissions?: { allow?: string[]; deny?: string[] }) {
55
71
  this.name = name;
56
72
  this.entry = entry;
57
73
  this.code = code;
74
+ this.permissions = permissions;
58
75
  this.id = 'worker_' + (++_seq);
59
76
  this.key = (__plugin?.name || 'unknown') + ':' + name;
60
77
  // 主插件侧 onMessage 回调(worker → main 时 Java 投递到这里)
@@ -71,6 +88,7 @@ export class Worker {
71
88
 
72
89
  /** 启动 Worker:执行 init.js → worker-inject.js → Worker 代码 → INIT → LOAD(已加载为 no-op)。 */
73
90
  load(): Promise<void> {
91
+ if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
74
92
  if (this._loaded) return Promise.resolve();
75
93
  this._loaded = true;
76
94
  return _sendWorkerAsync('load', { name: this.name });
@@ -78,22 +96,40 @@ export class Worker {
78
96
 
79
97
  /** 卸载 Worker(物理销毁 JS 上下文并清理其事件/命令/服务/任务;句柄保留,可重新 load)。 */
80
98
  unload(): Promise<void> {
99
+ if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
81
100
  this._loaded = false;
82
101
  return _sendWorkerAsync('unload', { name: this.name });
83
102
  }
84
103
 
85
104
  /** 向 Worker 发送消息(其 onMessage 回调接收;未 load 时抛错)。 */
86
105
  postMessage(msg: Record<string, unknown>): Promise<void> {
106
+ if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
87
107
  return _sendWorkerAsync('post', { name: this.name, msg });
88
108
  }
89
109
 
90
110
  /** 重载 Worker 代码(需已 load;旧上下文销毁、新代码重新加载)。 */
91
111
  reload(): Promise<void> {
112
+ if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
92
113
  const p: Record<string, unknown> = { name: this.name };
93
114
  if (this.entry) p.entry = this.entry;
94
115
  else p.code = this.code;
95
116
  return _sendWorkerAsync('reload', p);
96
117
  }
118
+
119
+ /**
120
+ * **彻底销毁** Worker:卸载并移除注册(清理事件/命令/服务/任务),句柄作废——
121
+ * 之后 load/post/reload 均 reject;同名 Worker 可重新 `createWorker`。
122
+ */
123
+ destroy(): Promise<void> {
124
+ if (this._destroyed) return Promise.resolve();
125
+ return _sendWorkerAsync('destroy', { name: this.name }).then(() => {
126
+ this._destroyed = true;
127
+ this._loaded = false;
128
+ const cb = _msgCbs[this.id];
129
+ if (cb) { _unregisterCallback(cb); delete _msgCbs[this.id]; }
130
+ delete _created[this.key];
131
+ });
132
+ }
97
133
  }
98
134
 
99
135
  /**
@@ -122,11 +158,12 @@ export function createWorker(options: WorkerOptions): Worker {
122
158
  throw new Error('createWorker: duplicate worker name "' + name + '" in plugin ' + (__plugin?.name || 'unknown'));
123
159
  }
124
160
  _created[key] = true;
125
- const w = new Worker(name, options.entry, options.code);
161
+ const w = new Worker(name, options.entry, options.code, options.permissions);
126
162
  // 注册到运行时注册表(同步;重复/非法名抛错)
127
163
  const p: Record<string, unknown> = { name, msgCb: _msgCbs[w.id] };
128
164
  if (options.entry) p.entry = options.entry;
129
165
  else p.code = options.code;
166
+ if (options.permissions) p.permissions = options.permissions;
130
167
  _sendWorker({ t: 'create', p });
131
168
  return w;
132
169
  }