yeow-api 0.2.113 → 0.2.115

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.2.113",
3
+ "version": "0.2.115",
4
4
  "description": "Yeow API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/src/command.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { call } from './task.js';
2
2
  import type { TaskOptions } from './task.js';
3
+ import { Player } from './player.js';
4
+ import type { Permission } from './permission.js';
5
+ import type { PermissionOptions } from './permission.js';
6
+ import { permissionPayload } from './permission.js';
3
7
 
4
- export interface CommandSender {
5
- readonly name: string;
6
- readonly uuid: string;
7
- readonly isPlayer: boolean;
8
- sendMessage(msg: string): void;
8
+ /** 命令发送者:玩家为真正的 `Player` 对象(异步 `sendMessage` 等全部方法);控制台为字符串 `'CONSOLE'`。 */
9
+ export type CommandSender = Player | 'CONSOLE';
10
+
11
+ /** 判断发送者是否为玩家(非 'CONSOLE' 即玩家)。 */
12
+ export function isPlayer(sender: CommandSender): sender is Player {
13
+ return sender !== 'CONSOLE';
9
14
  }
10
15
 
11
16
  export interface CommandPayload {
@@ -23,7 +28,12 @@ export interface ManualCompleter {
23
28
  export interface CommandOptions {
24
29
  description?: string;
25
30
  usage?: string;
26
- permission?: string;
31
+ /**
32
+ * 权限节点:字符串(兼容)或权限节点对象 `{ node, default }`(或 `registerPermission` 返回值)。
33
+ * 节点注册进 Bukkit 权限系统(权限插件/ permissions.yml 可管理);**执行时检查**——
34
+ * `permissionCheck` 事件结果优先,无处理时回退 Bukkit `hasPermission`。未声明则所有人可执行。
35
+ */
36
+ permission?: string | Permission | PermissionOptions;
27
37
  aliases?: string[];
28
38
  executor: (payload: CommandPayload) => void;
29
39
  completer?: CompleterFn | ManualCompleter;
@@ -34,21 +44,17 @@ export function registerCommand(name: string, options: CommandOptions, taskOptio
34
44
  const completer = options.completer;
35
45
  const pluginName = __plugin?.name || 'unknown';
36
46
 
37
- function decorateSender(sender: CommandSender): CommandSender {
38
- if (sender?.uuid && sender.isPlayer) {
39
- (sender as any).sendMessage = (msg: string) => {
40
- call('player.sendMessage', { uuid: sender.uuid, message: msg }, taskOptions);
41
- };
42
- } else {
43
- (sender as any).sendMessage = (msg: string) => {
44
- $send('log', { level: 'INFO', message: msg });
45
- };
47
+ /** 原始 sender({name, uuid, isPlayer})→ 真正的 Player 或 'CONSOLE'。 */
48
+ function adaptSender(raw: any): CommandSender {
49
+ if (raw?.isPlayer) {
50
+ const p = Player.getSync(raw.uuid);
51
+ if (p) return p;
46
52
  }
47
- return sender;
53
+ return 'CONSOLE';
48
54
  }
49
55
 
50
56
  const cbId = _registerCallback((payload: CommandPayload) => {
51
- (payload as any).sender = decorateSender(payload.sender);
57
+ (payload as any).sender = adaptSender(payload.sender);
52
58
  executor(payload);
53
59
  }, { persistent: true });
54
60
 
@@ -65,7 +71,7 @@ export function registerCommand(name: string, options: CommandOptions, taskOptio
65
71
 
66
72
  compCbId = _registerCallback((data: any) => {
67
73
  try {
68
- const sender = decorateSender(data.sender);
74
+ const sender = adaptSender(data.sender);
69
75
  if (manualRelease) {
70
76
  const complete = (result: string[]) => {
71
77
  $send('task', {
@@ -108,7 +114,7 @@ export function registerCommand(name: string, options: CommandOptions, taskOptio
108
114
  completerCbId: compCbId,
109
115
  description: options.description,
110
116
  usage: options.usage,
111
- permission: options.permission,
117
+ permission: options.permission ? permissionPayload(options.permission) : undefined,
112
118
  aliases: options.aliases,
113
119
  }, taskOptions);
114
120
  }
package/src/core.ts CHANGED
@@ -27,6 +27,7 @@ export type {
27
27
  BlockBreakEvent, BlockPlaceEvent, BlockFadeEvent, BlockGrowEvent, BlockSpreadEvent, BlockExplodeEvent,
28
28
  InventoryOpenEvent, InventoryCloseEvent, InventoryClickEvent,
29
29
  ServerPingEvent, ServerCommandEvent,
30
+ PermissionCheckEvent,
30
31
  PlayerResourcePackStatusEvent,
31
32
  } from './event.js';
32
33
  export { onInit, onLoad, onUnload } from './lifecycle.js';
@@ -92,3 +93,9 @@ export { registerService, registerNativeService, request as serviceRequest, subs
92
93
  export type { ServiceResult, NativeServiceResult } from './service.js';
93
94
  export { log, Logger } from './log.js';
94
95
  export type { Message } from './message.js';
96
+ export { getEnv } from './env.js';
97
+ export type { EnvInfo } from './env.js';
98
+ export { registerPermission } from './permission.js';
99
+ export type { Permission, PermissionOptions, PermissionDefault } from './permission.js';
100
+ export { createWorker, Worker, onMessage, postMessage } from './worker.js';
101
+ export type { WorkerOptions } from './worker.js';
package/src/env.ts ADDED
@@ -0,0 +1,20 @@
1
+ /** env 通道返回的运行时环境信息(同步)。 */
2
+ export interface EnvInfo {
3
+ /** CPU 逻辑核心数。 */
4
+ cpus: number;
5
+ /** JVM 总内存(字节)。 */
6
+ memory: number;
7
+ /** 系统架构(如 `windows-x64` / `linux-x64` / `linux-arm64`)。 */
8
+ arch: string;
9
+ /** Minecraft 版本(如 `1.21.4`)。 */
10
+ minecraftVersion: string;
11
+ /** 运行时信息。 */
12
+ yeow: { platform: string; version: string };
13
+ /** epoch 微秒时间戳。 */
14
+ now: number;
15
+ }
16
+
17
+ /** 获取运行时环境信息(同步;含微秒时间戳)。 */
18
+ export function getEnv(): EnvInfo {
19
+ return $send('env', {}) as EnvInfo;
20
+ }
package/src/event.ts CHANGED
@@ -254,6 +254,27 @@ export interface ServerCommandEvent {
254
254
  sender: string;
255
255
  cancelled?: boolean;
256
256
  }
257
+
258
+ /**
259
+ * Yeow 生态权限检查(仅 `player.hasPermission` 任务与 Yeow 插件注册命令的执行检查触发;
260
+ * 其他 Java 插件的 hasPermission / 命令不会触发)。
261
+ *
262
+ * handler 返回 `{ allowed: <boolean> }` 决定结果;**不返回视为未处理**(回退 Bukkit hasPermission)。
263
+ * 多个 handler 都返回且结果冲突时,以**最后一个返回的为准**(不保证执行顺序)。
264
+ *
265
+ * ⚠ 普通插件不建议监听(每次权限检查都触发,影响性能)——专为 Yeow 生态权限管理插件设计。
266
+ * ⚠ handler 中调用 `hasPermission` 会再次触发本检查——**可能导致无限循环**,请避免。
267
+ */
268
+ export interface PermissionCheckEvent {
269
+ /** 检查对象:玩家 UUID 或 "CONSOLE"。 */
270
+ target: string;
271
+ /** 权限节点(如 "myplugin.home")。 */
272
+ node: string;
273
+ /** 权限对象(含节点默认值,`registerPermission` 或命令声明的权限)。 */
274
+ permission: { node: string; default?: string };
275
+ /** handler 返回 `{ allowed }` 决定结果。 */
276
+ allowed?: boolean;
277
+ }
257
278
  export interface InventoryClickEvent {
258
279
  player: Player;
259
280
  slot: number;
@@ -316,6 +337,7 @@ type EventMap = {
316
337
  inventoryClose: InventoryCloseEvent;
317
338
  serverPing: ServerPingEvent;
318
339
  serverCommand: ServerCommandEvent;
340
+ permissionCheck: PermissionCheckEvent;
319
341
  playerTeleport: PlayerTeleportEvent;
320
342
  playerItemConsume: PlayerItemConsumeEvent;
321
343
  inventoryClick: InventoryClickEvent;
package/src/global.d.ts CHANGED
@@ -11,6 +11,7 @@ declare global {
11
11
  function _unregisterCallback(id: string): void;
12
12
  function _getCurrentCbStack(): { stack: string; outer: unknown } | null;
13
13
  function _attachCbStack(err: unknown): void;
14
+ function reportError(e: unknown): void;
14
15
 
15
16
  // ── init.js 注入的标准环境(lib 为 ESNext,不含 DOM——自行声明)──
16
17
  interface YeowConsole {
@@ -0,0 +1,46 @@
1
+ import { call } from './task.js';
2
+ import type { TaskOptions } from './task.js';
3
+
4
+ /** 权限节点默认值:'all' = 所有人默认拥有;'op' = 仅 op(默认);'none' = 需授权。 */
5
+ export type PermissionDefault = 'all' | 'op' | 'none';
6
+
7
+ export interface PermissionOptions {
8
+ /** 权限节点(如 `myplugin.home`)。 */
9
+ node: string;
10
+ /** 默认值(默认 'op')。 */
11
+ default?: PermissionDefault;
12
+ }
13
+
14
+ /** 权限节点对象(注册后返回)。 */
15
+ export interface Permission {
16
+ node: string;
17
+ default: PermissionDefault;
18
+ }
19
+
20
+ let _registered: Record<string, Permission> = {};
21
+
22
+ /**
23
+ * 注册权限节点(幂等):节点注册进 Bukkit 权限系统——Paper 平台可通过
24
+ * `permissions.yml` 静态声明或 LuckPerms 等权限管理插件管理。
25
+ *
26
+ * 粒度较粗(只声明默认值);Yeow 生态内可由权限管理插件通过 `permissionCheck`
27
+ * 事件实现任意逻辑的权限管理。
28
+ */
29
+ export function registerPermission(options: PermissionOptions, taskOptions?: TaskOptions): Permission {
30
+ const node = options?.node;
31
+ if (!node || typeof node !== 'string' || node.trim() === '') {
32
+ throw new Error('registerPermission: node is required');
33
+ }
34
+ const def: PermissionDefault = options.default || 'op';
35
+ if (!_registered[node]) {
36
+ call('permission.register', { node, default: def }, taskOptions);
37
+ _registered[node] = { node, default: def };
38
+ }
39
+ return _registered[node];
40
+ }
41
+
42
+ /** 权限节点对象 → 请求载荷({ node, default });字符串包装为对象(default 默认 'op')——Java 侧不做兼容。 */
43
+ export function permissionPayload(perm: string | Permission | PermissionOptions): { node: string; default: PermissionDefault } {
44
+ if (typeof perm === 'string') return { node: perm, default: 'op' };
45
+ return { node: perm.node, default: perm.default || 'op' };
46
+ }
package/src/player.ts CHANGED
@@ -3,6 +3,7 @@ import type { TaskOptions } from './task.js';
3
3
  import { Location, LocationData } from './location.js';
4
4
  import type { ItemStack } from './item.js';
5
5
  import type { Message } from './message.js';
6
+ import type { Permission } from './permission.js';
6
7
 
7
8
  interface PlayerData {
8
9
  uuid: string;
@@ -135,11 +136,19 @@ export class Player {
135
136
  }
136
137
  giveExp(amount: number, options?: TaskOptions): Promise<void> { return post('player.giveExp', { uuid: this.uuid, amount }, options); }
137
138
  giveExpSync(amount: number, options?: TaskOptions): void { call('player.giveExp', { uuid: this.uuid, amount }, options); }
138
- hasPermission(node: string, options?: TaskOptions): Promise<boolean> {
139
- return post<boolean>('player.hasPermission', { uuid: this.uuid, permission: node }, options);
139
+ /** 检查权限(经 Yeow 权限检查:`permissionCheck` 事件优先,无处理时回退 Bukkit)。node 可为权限节点对象。 */
140
+ hasPermission(node: string | Permission, options?: TaskOptions): Promise<boolean> {
141
+ return post<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? node : { node: node.node } }, options);
140
142
  }
141
- hasPermissionSync(node: string, options?: TaskOptions): boolean {
142
- return call<boolean>('player.hasPermission', { uuid: this.uuid, permission: node }, options);
143
+ hasPermissionSync(node: string | Permission, options?: TaskOptions): boolean {
144
+ return call<boolean>('player.hasPermission', { uuid: this.uuid, permission: typeof node === 'string' ? node : { node: node.node } }, options);
145
+ }
146
+ /** 以玩家身份执行命令(**不含 `/` 前缀**,如 `say hi`;前缀会自动剥离;与服务器 `dispatchCommand`(控制台)相对)。 */
147
+ performCommand(cmd: string, options?: TaskOptions): Promise<boolean> {
148
+ return post<boolean>('player.performCommand', { uuid: this.uuid, command: cmd.replace(/^\//, '') }, options);
149
+ }
150
+ performCommandSync(cmd: string, options?: TaskOptions): boolean {
151
+ return call<boolean>('player.performCommand', { uuid: this.uuid, command: cmd.replace(/^\//, '') }, options);
143
152
  }
144
153
  teleport(loc: Location, options?: TaskOptions): Promise<void> { return post('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
145
154
  teleportSync(loc: Location, options?: TaskOptions): void { call('player.teleport', { uuid: this.uuid, ...loc.toObject() }, options); }
package/src/worker.ts ADDED
@@ -0,0 +1,150 @@
1
+ export interface WorkerOptions {
2
+ /** Worker 名(必填;不允许 'main';同一主插件内不重复,全局可重复)。 */
3
+ name: string;
4
+ /** 资源路径(通过 `getAssetsPath` 获取);与 `code` 互斥(不可同时传递)。 */
5
+ entry?: string;
6
+ /** 代码字符串;与 `entry` 互斥。 */
7
+ code?: string;
8
+ }
9
+
10
+ let _seq = 0;
11
+ /** 本主插件已创建的 worker 名(同主插件内唯一校验;unload 后允许重建)。 */
12
+ const _created: Record<string, boolean> = {};
13
+ /** 主插件侧:每个 worker 的 onMessage 回调 id。 */
14
+ const _msgCbs: Record<string, string> = {};
15
+
16
+ function _sendWorker(payload: Record<string, unknown>): unknown {
17
+ const r = $send('worker', payload);
18
+ if (r == null) return undefined;
19
+ if ((r as any)?.err) throw new Error((r as any).err);
20
+ return r;
21
+ }
22
+
23
+ /** 内部:异步调用 worker 通道(cb 回调)。 */
24
+ function _sendWorkerAsync(t: string, p: Record<string, unknown>): Promise<void> {
25
+ return new Promise((resolve, reject) => {
26
+ const cbId = _registerCallback((result: unknown) => {
27
+ if ((result as any)?.err) reject(new Error((result as any).err));
28
+ else resolve();
29
+ });
30
+ _sendWorker({ t, p: { ...p, cb: cbId } });
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Worker —— 虚拟插件(独立 QuickJS 上下文 + 线程)。
36
+ *
37
+ * - 事件/命令/服务以独立实体注册;调度器任务独立统计
38
+ * - 共享主插件的**数据目录**与**权限**
39
+ * - 不能创建新的 Worker(嵌套被拒绝)
40
+ * - **创建后无法销毁,只能卸载**(卸载物理销毁 JS 上下文,句柄保留——可重新 load)
41
+ * - 主插件卸载时连带卸载;/yeow 管理命令不覆盖 Worker;profiler 会统计(标记 created by 主插件)
42
+ * - Worker 的 JS 错误与主插件同样回传(dev 模式经 source-map 定位)
43
+ */
44
+ export class Worker {
45
+ private readonly name: string;
46
+ private readonly entry?: string;
47
+ private readonly code?: string;
48
+ private readonly key: string;
49
+ private _loaded = false;
50
+ private _onMessage: ((msg: any) => void) | null = null;
51
+ /** 内部 workerId(主插件 JS 侧分配;跨主插件可重复)。 */
52
+ readonly id: string;
53
+
54
+ constructor(name: string, entry: string | undefined, code: string | undefined) {
55
+ this.name = name;
56
+ this.entry = entry;
57
+ this.code = code;
58
+ this.id = 'worker_' + (++_seq);
59
+ this.key = (__plugin?.name || 'unknown') + ':' + name;
60
+ // 主插件侧 onMessage 回调(worker → main 时 Java 投递到这里)
61
+ _msgCbs[this.id] = _registerCallback((msg: unknown) => {
62
+ const cb = this._onMessage;
63
+ if (cb) { try { cb(msg); } catch (e) { console.error('[worker] onMessage error', e); } }
64
+ }, { persistent: true });
65
+ }
66
+
67
+ /** 注册接收 Worker 发来的消息。 */
68
+ onMessage(cb: (msg: any) => void): void {
69
+ this._onMessage = cb;
70
+ }
71
+
72
+ /** 启动 Worker:执行 init.js → worker-inject.js → Worker 代码 → INIT → LOAD(已加载为 no-op)。 */
73
+ load(): Promise<void> {
74
+ if (this._loaded) return Promise.resolve();
75
+ this._loaded = true;
76
+ return _sendWorkerAsync('load', { name: this.name });
77
+ }
78
+
79
+ /** 卸载 Worker(物理销毁 JS 上下文并清理其事件/命令/服务/任务;句柄保留,可重新 load)。 */
80
+ unload(): Promise<void> {
81
+ this._loaded = false;
82
+ return _sendWorkerAsync('unload', { name: this.name });
83
+ }
84
+
85
+ /** 向 Worker 发送消息(其 onMessage 回调接收;未 load 时抛错)。 */
86
+ postMessage(msg: Record<string, unknown>): Promise<void> {
87
+ return _sendWorkerAsync('post', { name: this.name, msg });
88
+ }
89
+
90
+ /** 重载 Worker 代码(需已 load;旧上下文销毁、新代码重新加载)。 */
91
+ reload(): Promise<void> {
92
+ const p: Record<string, unknown> = { name: this.name };
93
+ if (this.entry) p.entry = this.entry;
94
+ else p.code = this.code;
95
+ return _sendWorkerAsync('reload', p);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 创建 Worker(虚拟插件):**仅注册到注册表并返回句柄**——`worker.load()` 才真正启动
101
+ * (执行 init.js → worker-inject.js → Worker 代码 → INIT → LOAD)。
102
+ *
103
+ * `entry`(资源路径,经 getAssetsPath)与 `code` 二选一,同时传递抛错;
104
+ * `name` 必填、非 'main'、同主插件内不重复。
105
+ */
106
+ export function createWorker(options: WorkerOptions): Worker {
107
+ const name = options?.name;
108
+ if (!name || typeof name !== 'string' || name.trim() === '') {
109
+ throw new Error('createWorker: name is required');
110
+ }
111
+ if (name === 'main') {
112
+ throw new Error('createWorker: name must not be "main"');
113
+ }
114
+ if (options.entry && options.code) {
115
+ throw new Error('createWorker: entry and code cannot be passed together');
116
+ }
117
+ if (!options.entry && !options.code) {
118
+ throw new Error('createWorker: either entry or code is required');
119
+ }
120
+ const key = (__plugin?.name || 'unknown') + ':' + name;
121
+ if (_created[key]) {
122
+ throw new Error('createWorker: duplicate worker name "' + name + '" in plugin ' + (__plugin?.name || 'unknown'));
123
+ }
124
+ _created[key] = true;
125
+ const w = new Worker(name, options.entry, options.code);
126
+ // 注册到运行时注册表(同步;重复/非法名抛错)
127
+ const p: Record<string, unknown> = { name, msgCb: _msgCbs[w.id] };
128
+ if (options.entry) p.entry = options.entry;
129
+ else p.code = options.code;
130
+ _sendWorker({ t: 'create', p });
131
+ return w;
132
+ }
133
+
134
+ // ── Worker 侧 API(仅 Worker 环境可用)──────────────────────────────
135
+
136
+ const _isWorker = typeof (globalThis as any).__workerId !== 'undefined';
137
+
138
+ /** Worker 侧:注册消息接收回调(主插件 `worker.postMessage` 触发)。主插件环境调用抛错。 */
139
+ export function onMessage(cb: (msg: any) => void): void {
140
+ if (!_isWorker) throw new Error('onMessage is only available inside a Worker');
141
+ (globalThis as any)._workerOnMessage = (msg: any) => {
142
+ try { cb(msg); } catch (e) { reportError(e); }
143
+ };
144
+ }
145
+
146
+ /** Worker 侧:向主插件发送消息(主插件侧 `worker.onMessage` 回调接收)。主插件环境调用抛错。 */
147
+ export function postMessage(msg: Record<string, unknown>): void {
148
+ if (!_isWorker) throw new Error('postMessage is only available inside a Worker');
149
+ _sendWorker({ t: 'postToMain', p: { msg } });
150
+ }