@zhin.js/core 1.0.17 → 1.0.18

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/REFACTORING_COMPLETE.md +178 -0
  3. package/REFACTORING_STATUS.md +263 -0
  4. package/lib/adapter.d.ts +44 -19
  5. package/lib/adapter.d.ts.map +1 -1
  6. package/lib/adapter.js +81 -50
  7. package/lib/adapter.js.map +1 -1
  8. package/lib/bot.d.ts +7 -12
  9. package/lib/bot.d.ts.map +1 -1
  10. package/lib/built/adapter-process.d.ts +36 -0
  11. package/lib/built/adapter-process.d.ts.map +1 -0
  12. package/lib/built/adapter-process.js +77 -0
  13. package/lib/built/adapter-process.js.map +1 -0
  14. package/lib/built/command.d.ts +46 -0
  15. package/lib/built/command.d.ts.map +1 -0
  16. package/lib/built/command.js +54 -0
  17. package/lib/built/command.js.map +1 -0
  18. package/lib/built/component.d.ts +42 -0
  19. package/lib/built/component.d.ts.map +1 -0
  20. package/lib/built/component.js +66 -0
  21. package/lib/built/component.js.map +1 -0
  22. package/lib/built/config.d.ts +31 -0
  23. package/lib/built/config.d.ts.map +1 -0
  24. package/lib/built/config.js +141 -0
  25. package/lib/built/config.js.map +1 -0
  26. package/lib/built/cron.d.ts +53 -0
  27. package/lib/built/cron.d.ts.map +1 -0
  28. package/lib/built/cron.js +79 -0
  29. package/lib/built/cron.js.map +1 -0
  30. package/lib/built/database.d.ts +17 -0
  31. package/lib/built/database.d.ts.map +1 -0
  32. package/lib/built/database.js +28 -0
  33. package/lib/built/database.js.map +1 -0
  34. package/lib/{permissions.d.ts → built/permission.d.ts} +5 -10
  35. package/lib/built/permission.d.ts.map +1 -0
  36. package/lib/{permissions.js → built/permission.js} +11 -10
  37. package/lib/built/permission.js.map +1 -0
  38. package/lib/command.d.ts +7 -7
  39. package/lib/command.d.ts.map +1 -1
  40. package/lib/command.js +5 -15
  41. package/lib/command.js.map +1 -1
  42. package/lib/component.d.ts.map +1 -1
  43. package/lib/component.js.map +1 -1
  44. package/lib/cron.d.ts +1 -0
  45. package/lib/cron.d.ts.map +1 -1
  46. package/lib/cron.js +2 -0
  47. package/lib/cron.js.map +1 -1
  48. package/lib/index.d.ts +11 -3
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +14 -4
  51. package/lib/index.js.map +1 -1
  52. package/lib/jsx-runtime.d.ts +2 -2
  53. package/lib/message.d.ts +2 -2
  54. package/lib/message.d.ts.map +1 -1
  55. package/lib/plugin.d.ts +164 -51
  56. package/lib/plugin.d.ts.map +1 -1
  57. package/lib/plugin.js +521 -150
  58. package/lib/plugin.js.map +1 -1
  59. package/lib/prompt.d.ts +1 -1
  60. package/lib/prompt.d.ts.map +1 -1
  61. package/lib/prompt.js +2 -1
  62. package/lib/prompt.js.map +1 -1
  63. package/lib/types.d.ts +33 -33
  64. package/lib/types.d.ts.map +1 -1
  65. package/lib/utils.d.ts +16 -1
  66. package/lib/utils.d.ts.map +1 -1
  67. package/lib/utils.js +166 -66
  68. package/lib/utils.js.map +1 -1
  69. package/package.json +15 -9
  70. package/src/adapter.ts +131 -80
  71. package/src/bot.ts +8 -13
  72. package/src/built/adapter-process.ts +77 -0
  73. package/src/built/command.ts +102 -0
  74. package/src/built/component.ts +111 -0
  75. package/src/built/config.ts +126 -0
  76. package/src/built/cron.ts +140 -0
  77. package/src/built/database.ts +38 -0
  78. package/src/{permissions.ts → built/permission.ts} +9 -12
  79. package/src/command.ts +11 -20
  80. package/src/component.ts +0 -1
  81. package/src/cron.ts +2 -0
  82. package/src/index.ts +15 -5
  83. package/src/message.ts +2 -2
  84. package/src/plugin.ts +671 -202
  85. package/src/prompt.ts +4 -3
  86. package/src/types.ts +41 -35
  87. package/src/utils.ts +418 -296
  88. package/test/minimal-bot.ts +31 -0
  89. package/test/stress-test.ts +123 -0
  90. package/tests/command.test.ts +47 -44
  91. package/ASYNC-JSX-SUPPORT.md +0 -173
  92. package/lib/app.d.ts +0 -191
  93. package/lib/app.d.ts.map +0 -1
  94. package/lib/app.js +0 -604
  95. package/lib/app.js.map +0 -1
  96. package/lib/config.d.ts +0 -54
  97. package/lib/config.d.ts.map +0 -1
  98. package/lib/config.js +0 -308
  99. package/lib/config.js.map +0 -1
  100. package/lib/log-transport.d.ts +0 -37
  101. package/lib/log-transport.d.ts.map +0 -1
  102. package/lib/log-transport.js +0 -136
  103. package/lib/log-transport.js.map +0 -1
  104. package/lib/permissions.d.ts.map +0 -1
  105. package/lib/permissions.js.map +0 -1
  106. package/src/app.ts +0 -772
  107. package/src/config.ts +0 -397
  108. package/src/log-transport.ts +0 -163
  109. package/tests/app.test.ts +0 -265
  110. package/tests/permissions.test.ts +0 -358
  111. package/tests/plugin.test.ts +0 -234
  112. package/tests/prompt.test.ts +0 -223
package/src/adapter.ts CHANGED
@@ -1,92 +1,143 @@
1
- import {Bot} from "./bot.js";
2
- import {Plugin} from "./plugin.js";
1
+ import { Bot } from "./bot.js";
2
+ import { Plugin } from "./plugin.js";
3
+ import { EventEmitter } from "events";
4
+ import { Message } from "./message.js";
5
+ import { BeforeSendHandler, SendOptions } from "./types.js";
6
+ import { segment } from "./utils.js";
3
7
  /**
4
8
  * Adapter类:适配器抽象,管理多平台Bot实例。
5
9
  * 负责根据配置启动/关闭各平台机器人,统一异常处理。
6
10
  */
7
- export class Adapter<R extends Bot=Bot>{
8
- /** 当前适配器下所有Bot实例,key为bot名称 */
9
- public bots:Map<string,R>=new Map<string, R>()
10
- #botFactory:Adapter.BotFactory<R>
11
- /**
12
- * 构造函数
13
- * @param name 适配器名称(如 'process'、'qq' 等)
14
- * @param botFactory Bot工厂函数或构造器
15
- */
16
- constructor(public name:string,botFactory:Adapter.BotFactory<R>) {
17
- this.#botFactory=botFactory
18
- }
19
- createBot(config: Adapter.Config<R>) {
20
- const bot=Adapter.isBotConstructor(this.#botFactory)?
21
- new this.#botFactory(config)
22
- :this.#botFactory(config)
23
- return bot
24
- }
25
- /**
26
- * 启动适配器,自动根据配置创建并连接所有Bot
27
- * @param plugin 所属插件实例
28
- */
29
- async mounted(plugin:Plugin){
30
- const configs=plugin.app.config.bots?.filter(c=>c.context===this.name)
31
- if(!configs?.length) return plugin.logger.warn(`no bot config for adapter ${this.name} found`)
32
- try {
33
- for(const config of configs){
34
- let bot: R
35
- if (Adapter.isBotConstructor(this.#botFactory)) {
36
- bot = new this.#botFactory(config) as R
37
- } else {
38
- bot = this.#botFactory(config) as R
39
- }
40
- try {
41
- await bot.$connect()
42
- plugin.logger.info(`bot ${config.name} of adapter ${this.name} connected`)
43
- this.bots.set(config.name,bot)
44
- } catch (error) {
45
- // 如果连接失败,确保错误正确传播
46
- throw error
47
- }
48
- }
11
+ export abstract class Adapter<R extends Bot = Bot> extends EventEmitter<Adapter.Lifecycle> {
12
+ /** 当前适配器下所有Bot实例,key为bot名称 */
13
+ public bots: Map<string, R> = new Map<string, R>();
14
+ /**
15
+ * 构造函数
16
+ * @param name 适配器名称(如 'process'、'qq' 等)
17
+ * @param botFactory Bot工厂函数或构造器
18
+ */
19
+ constructor(
20
+ public plugin: Plugin,
21
+ public name: keyof Plugin.Contexts,
22
+ public config: Adapter.BotConfig<R>[]
23
+ ) {
24
+ super();
25
+ this.on('call.recallMessage', async(bot_id, id) => {
26
+ const bot = this.bots.get(bot_id);
27
+ if(!bot) throw new Error(`Bot ${bot_id} not found`);
28
+ this.logger.info(`${bot_id} recall ${id}`);
29
+ await bot.$recallMessage(id);
30
+ })
31
+ this.on('call.sendMessage', async (bot_id:string,options:SendOptions) => {
32
+ const fns=this.plugin.root.listeners('before.sendMessage') as BeforeSendHandler[];
33
+ for(const fn of fns){
34
+ const result=await fn(options);
35
+ if(result) options=result;
36
+ }
37
+ const bot = this.bots.get(bot_id);
38
+ if(!bot) throw new Error(`Bot ${bot_id} not found`);
39
+ this.logger.info(`${bot_id} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
40
+ return await bot.$sendMessage(options);
41
+ });
42
+ this.on('message.receive', (message) => {
43
+ this.logger.info(`${message.$bot} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`);
44
+ this.plugin?.middleware(message, async ()=>{});
45
+ });
46
+ }
47
+ abstract createBot(config: Adapter.BotConfig<R>): R;
48
+ get logger() {
49
+ if(!this.plugin) throw new Error("Adapter is not associated with any plugin");
50
+ return this.plugin.logger;
51
+ }
52
+ binding(plugin: Plugin) {
53
+ this.plugin = plugin;
54
+ }
55
+ async start() {
56
+ this.plugin.root.adapters.push(this.name);
57
+ if (!this.config?.length) return;
49
58
 
50
- plugin.logger.info(`adapter ${this.name} mounted`)
51
- } catch (error) {
52
- // 确保错误正确传播
53
- throw error
54
- }
59
+ for (const config of this.config) {
60
+ const bot = this.createBot(config);
61
+ await bot.$connect();
62
+ this.logger.debug(`bot ${bot.$id} of adapter ${this.name} connected`);
63
+ this.bots.set(bot.$id, bot);
55
64
  }
56
- /**
57
- * 停止适配器,断开并移除所有Bot实例
58
- * @param plugin 所属插件实例
59
- */
60
- async dispose(plugin:Plugin){
65
+ this.logger.debug(`adapter ${this.name} started`);
66
+ }
67
+ /**
68
+ * 停止适配器,断开并移除所有Bot实例
69
+ * @param plugin 所属插件实例
70
+ */
71
+ async stop() {
72
+ try {
73
+ for (const [id, bot] of this.bots) {
61
74
  try {
62
- for(const [name,bot] of this.bots){
63
- try {
64
- await bot.$disconnect()
65
- plugin.logger.info(`bot ${name} of adapter ${this.name} disconnected`)
66
- this.bots.delete(name)
67
- } catch (error) {
68
- // 如果断开连接失败,确保错误正确传播
69
- throw error
70
- }
71
- }
72
- plugin.logger.info(`adapter ${this.name} stopped`)
75
+ await bot.$disconnect();
76
+ this.logger.debug(`bot ${id} of adapter ${this.name} disconnected`);
73
77
  } catch (error) {
74
- // 确保错误正确传播
75
- throw error
78
+ // 如果断开连接失败,确保错误正确传播
79
+ throw error;
76
80
  }
81
+ }
82
+ // 清理 bots Map
83
+ this.bots.clear();
84
+
85
+ // 从 adapters 数组中移除
86
+ const idx = this.plugin.root.adapters.indexOf(this.name);
87
+ if (idx !== -1) {
88
+ this.plugin.root.adapters.splice(idx, 1);
89
+ }
90
+
91
+ // 移除所有事件监听器
92
+ this.removeAllListeners();
93
+
94
+ this.logger.info(`adapter ${this.name} stopped`);
95
+ } catch (error) {
96
+ // 确保错误正确传播
97
+ throw error;
77
98
  }
99
+ }
78
100
  }
101
+ export interface Adapters {}
79
102
  export namespace Adapter {
80
- export type BotBotConstructor<T extends Bot>=T extends Bot<infer F,infer S> ? {
81
- new(config:S):T
82
- }: {
83
- new(config:Bot.Config):T
84
- }
85
- export function isBotConstructor<T extends Bot>(fn: BotFactory<T>): fn is BotBotConstructor<T> {
86
- return fn.prototype &&
87
- fn.prototype.constructor === fn
88
- }
89
- export type BotCreator<T extends Bot>=T extends Bot<infer F,infer S> ? (config: S) => T : (config: Bot.Config) => T
90
- export type BotFactory<T extends Bot> = BotBotConstructor<T>|BotCreator<T>
91
- export type Config<T extends Bot>=T extends Bot<infer F,infer S>?S:Bot.Config
92
- }
103
+ export type Factory<R extends Adapter = Adapter> = {
104
+ new (
105
+ plugin: Plugin,
106
+ name: string,
107
+ config: Adapter.BotConfig<Adapter.InferBot<R>>[]
108
+ ):R
109
+ };
110
+ export interface Lifecycle {
111
+ 'message.receive': [Message];
112
+ 'message.private.receive': [Message];
113
+ 'message.group.receive': [Message];
114
+ 'message.channel.receive': [Message];
115
+ 'call.recallMessage': [string, string];
116
+ 'call.sendMessage': [string, SendOptions];
117
+ }
118
+ /**
119
+ * 适配器工厂注册表
120
+ * 灵感来源于 zhinjs/next 的 Adapter.Registry
121
+ */
122
+ export const Registry = new Map<string, Factory>();
123
+ export type InferBot<R extends Adapter=Adapter> = R extends Adapter<infer T>
124
+ ? T
125
+ : never;
126
+ export type BotConfig<T extends Bot> = T extends Bot<infer R> ? R : never;
127
+ export type BotMessage<T extends Bot> = T extends Bot<infer _L, infer R>
128
+ ? R
129
+ : never;
130
+ /**
131
+ * 注册适配器工厂
132
+ *
133
+ * @param name 适配器名称
134
+ * @param factory 适配器工厂函数
135
+ * @example
136
+ * ```typescript
137
+ * Adapter.register('icqq', IcqqAdapter);
138
+ * ```
139
+ */
140
+ export function register(name: string, factory: Factory) {
141
+ Registry.set(name, factory);
142
+ }
143
+ }
package/src/bot.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  import type { SendOptions } from "./types.js";
2
2
  import { Message } from "./message.js";
3
+ import { Adapter, Adapters } from "./adapter.js";
3
4
  /**
4
5
  * Bot接口:所有平台机器人需实现的统一接口。
5
6
  * 负责消息格式化、连接、断开、消息发送等。
6
7
  * @template M 消息类型
7
8
  * @template T 配置类型
8
9
  */
9
- export interface Bot<M extends object = {}, T extends Bot.Config = Bot.Config> {
10
+ export interface Bot<Config extends object= {},Event extends object = {}> {
11
+ $id:string
10
12
  /** 机器人配置 */
11
- $config: T;
13
+ $config: Config;
12
14
  /** 是否已连接 */
13
- $connected?: boolean;
15
+ $connected: boolean;
14
16
  /** 格式化平台消息为标准Message结构 */
15
- $formatMessage(message: M): Message<M>;
17
+ $formatMessage(event: Event): Message<Event>;
16
18
  /** 连接机器人 */
17
19
  $connect(): Promise<void>;
18
20
  /** 断开机器人 */
@@ -23,12 +25,5 @@ export interface Bot<M extends object = {}, T extends Bot.Config = Bot.Config> {
23
25
  $sendMessage(options: SendOptions): Promise<string>;
24
26
  }
25
27
  export namespace Bot {
26
- /**
27
- * Bot配置类型,所有平台机器人通用
28
- */
29
- export interface Config {
30
- context: string;
31
- name: string;
32
- [key: string]: any;
33
- }
34
- }
28
+ export type Config =Adapter.BotConfig<Adapters[keyof Adapters]>
29
+ }
@@ -0,0 +1,77 @@
1
+ import { Adapter, Plugin, Bot, SendContent, SendOptions, MessageBase, Message, segment } from "@zhin.js/core";
2
+ export class ProcessBot implements Bot<{},{content:string,ts:number}>{
3
+ $id=`${process.pid}`;
4
+ get logger() {
5
+ return this.adapter.logger;
6
+ }
7
+ $connected=false;
8
+ constructor(public adapter: ProcessAdapter, public $config={}) {
9
+ this.$listenInput=this.$listenInput.bind(this);
10
+ }
11
+ $listenInput:(data:Buffer<ArrayBufferLike>)=>void=function (this:ProcessBot,data){
12
+ const content = data.toString().trim();
13
+ if (content) {
14
+ this.adapter.emit('message.receive', this.$formatMessage({content,ts:Date.now()}));
15
+ }
16
+ }
17
+ async connect() {
18
+ }
19
+ async disconnect() {
20
+ }
21
+ $formatMessage(event: {content:string,ts:number}): Message<{content:string,ts:number}> {
22
+ const base:MessageBase={
23
+ $id: `${event.ts}`,
24
+ $adapter: 'process',
25
+ $bot: `${process.pid}`,
26
+ $sender: {
27
+ id: `${process.pid}`,
28
+ name: process.title,
29
+ },
30
+ $channel: {
31
+ id: `${process.pid}`,
32
+ type: 'private',
33
+ },
34
+ $content: [{type:'text',data:{text:event.content}}],
35
+ $raw: event.content,
36
+ $timestamp: event.ts,
37
+ $recall: async () => {
38
+ await this.$recallMessage(base.$id)
39
+ },
40
+ $reply: async (content: SendContent) => {
41
+ return await this.$sendMessage({
42
+ context: 'process',
43
+ bot: base.$bot,
44
+ content,
45
+ id: base.$id,
46
+ type: base.$channel.type,
47
+ });
48
+ },
49
+ }
50
+ return Message.from(event, base);
51
+ }
52
+ async $recallMessage(id: string) {
53
+ }
54
+ async $sendMessage(options: SendOptions) {
55
+ return `${Date.now()}`;
56
+ }
57
+ async $connect(): Promise<void> {
58
+ process.stdin.on('data', this.$listenInput);
59
+ this.$connected = true;
60
+ }
61
+ async $disconnect() {
62
+ process.stdin.removeListener('data', this.$listenInput);
63
+ }
64
+ }
65
+ export class ProcessAdapter extends Adapter<ProcessBot>{
66
+ constructor(plugin: Plugin) {
67
+ super(plugin, 'process', [{}]);
68
+ }
69
+ createBot(): ProcessBot {
70
+ return new ProcessBot(this);
71
+ }
72
+ }
73
+ declare module '@zhin.js/core'{
74
+ interface RegisteredAdapters{
75
+ process:ProcessAdapter
76
+ }
77
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Command Context
3
+ * 管理所有插件注册的命令
4
+ */
5
+ import { MessageCommand } from "../command.js";
6
+ import { Message } from "../message.js";
7
+ import { Context, Plugin, getPlugin } from "../plugin.js";
8
+ import type { RegisteredAdapter, AdapterMessage } from "../types.js";
9
+
10
+ /**
11
+ * CommandContext 扩展方法类型
12
+ */
13
+ export interface CommandContextExtensions {
14
+ /** 添加命令 */
15
+ addCommand<T extends RegisteredAdapter>(command: MessageCommand<T>): () => void;
16
+ }
17
+
18
+ // 扩展 Plugin 接口
19
+ declare module "../plugin.js" {
20
+ namespace Plugin {
21
+ interface Extensions extends CommandContextExtensions {}
22
+ interface Contexts {
23
+ command: CommandService;
24
+ }
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 命令服务数据
30
+ */
31
+ export interface CommandService {
32
+ /** 命令列表(保持顺序) */
33
+ readonly items: MessageCommand<RegisteredAdapter>[];
34
+ /** 按 pattern 索引 */
35
+ readonly byName: Map<string, MessageCommand<RegisteredAdapter>>;
36
+ /** 添加命令 */
37
+ add(command: MessageCommand<RegisteredAdapter>, pluginName: string): () => void;
38
+ /** 移除命令 */
39
+ remove(command: MessageCommand<RegisteredAdapter>): boolean;
40
+ /** 按名称获取 */
41
+ get(pattern: string): MessageCommand<RegisteredAdapter> | undefined;
42
+ /** 处理消息 */
43
+ handle(message: Message<AdapterMessage<RegisteredAdapter>>, plugin: Plugin): Promise<any>;
44
+ }
45
+
46
+ /**
47
+ * 创建命令 Context
48
+ */
49
+ export function createCommandService(): Context<'command', CommandContextExtensions> {
50
+ const items: MessageCommand<RegisteredAdapter>[] = [];
51
+ const byName = new Map<string, MessageCommand<RegisteredAdapter>>();
52
+ const pluginMap = new Map<MessageCommand<RegisteredAdapter>, string>();
53
+
54
+ const value: CommandService = {
55
+ items,
56
+ byName,
57
+
58
+ add(command, pluginName) {
59
+ items.push(command);
60
+ byName.set(command.pattern, command);
61
+ pluginMap.set(command, pluginName);
62
+ return () => value.remove(command);
63
+ },
64
+
65
+ remove(command) {
66
+ const index = items.indexOf(command);
67
+ if (index !== -1) {
68
+ items.splice(index, 1);
69
+ byName.delete(command.pattern);
70
+ pluginMap.delete(command);
71
+ return true;
72
+ }
73
+ return false;
74
+ },
75
+
76
+ get(pattern) {
77
+ return byName.get(pattern);
78
+ },
79
+
80
+ async handle(message, plugin) {
81
+ for (const command of items) {
82
+ const result = await command.handle(message, plugin);
83
+ if (result) return result;
84
+ }
85
+ return null;
86
+ }
87
+ };
88
+
89
+ return {
90
+ name: 'command',
91
+ description: '命令服务',
92
+ value,
93
+ extensions: {
94
+ addCommand<T extends RegisteredAdapter>(command: MessageCommand<T>) {
95
+ const plugin = getPlugin();
96
+ const dispose = value.add(command as MessageCommand<RegisteredAdapter>, plugin.name);
97
+ plugin.onDispose(dispose);
98
+ return dispose;
99
+ }
100
+ }
101
+ };
102
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Component Context
3
+ * 管理所有插件注册的组件
4
+ */
5
+ import { Component, renderComponents } from "../component.js";
6
+ import { SendOptions, MaybePromise } from "../types.js";
7
+ import { Context, Plugin, getPlugin } from "../plugin.js";
8
+
9
+ type Listener = (options: SendOptions) => MaybePromise<SendOptions>;
10
+
11
+ /**
12
+ * ComponentContext 扩展方法类型
13
+ */
14
+ export interface ComponentContextExtensions {
15
+ /** 添加组件 */
16
+ addComponent<T extends Component<any>>(component: T): () => void;
17
+ }
18
+
19
+ // 扩展 Plugin 接口
20
+ declare module "../plugin.js" {
21
+ namespace Plugin {
22
+ interface Extensions extends ComponentContextExtensions {}
23
+ interface Contexts {
24
+ component: ComponentService;
25
+ }
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 组件服务数据
31
+ */
32
+ export interface ComponentService {
33
+ /** 按名称索引 */
34
+ readonly byName: Map<string, Component<any>>;
35
+ /** 添加组件 */
36
+ add(component: Component<any>, pluginName: string): () => void;
37
+ /** 获取所有组件名称 */
38
+ getAllNames(): string[];
39
+ /** 移除组件 */
40
+ remove(component: Component<any>): boolean;
41
+ /** 按名称获取 */
42
+ get(name: string): Component<any> | undefined;
43
+ }
44
+
45
+ /**
46
+ * 创建组件 Context
47
+ */
48
+ export function createComponentService(): Context<'component', ComponentContextExtensions> {
49
+ const byName = new Map<string, Component<any>>();
50
+ const pluginMap = new Map<Component<any>, string>();
51
+ let listener: Listener | undefined;
52
+ let rootPlugin: Plugin | undefined;
53
+
54
+ const value: ComponentService = {
55
+ byName,
56
+
57
+ add(component, pluginName) {
58
+ byName.set(component.name, component);
59
+ pluginMap.set(component, pluginName);
60
+ return () => value.remove(component);
61
+ },
62
+ getAllNames() {
63
+ return Array.from(byName.keys());
64
+ },
65
+ remove(component) {
66
+ if (byName.has(component.name)) {
67
+ byName.delete(component.name);
68
+ pluginMap.delete(component);
69
+ return true;
70
+ }
71
+ return false;
72
+ },
73
+
74
+ get(name) {
75
+ return byName.get(name);
76
+ }
77
+ };
78
+
79
+ return {
80
+ name: 'component',
81
+ description: '组件服务',
82
+ value,
83
+
84
+ mounted(plugin: Plugin) {
85
+ rootPlugin = plugin;
86
+ // 创建消息渲染监听器
87
+ listener = (options: SendOptions) => {
88
+ return renderComponents(byName, options);
89
+ };
90
+ plugin.root.on('before.sendMessage', listener);
91
+ return value;
92
+ },
93
+
94
+ dispose() {
95
+ if (listener && rootPlugin) {
96
+ rootPlugin.root.off('before.sendMessage', listener);
97
+ listener = undefined;
98
+ }
99
+ },
100
+
101
+ extensions: {
102
+ addComponent<T extends Component<any>>(component: T) {
103
+ const plugin = getPlugin();
104
+ const dispose = value.add(component, plugin.name);
105
+ plugin.onDispose(dispose);
106
+ return dispose;
107
+ }
108
+ }
109
+ };
110
+ }
111
+
@@ -0,0 +1,126 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { stringify as stringifyYaml,parse as parseYaml } from "yaml";
4
+ import { Schema } from "@zhin.js/schema";
5
+ export class ConfigLoader<T extends object>{
6
+ #data: T;
7
+ get data(): T {
8
+ return this.#proxy(this.#data,this);
9
+ }
10
+ get raw(): T {
11
+ return this.#data;
12
+ }
13
+ get extension() {
14
+ return path.extname(this.filename).toLowerCase();
15
+ }
16
+ constructor(public filename: string, public initial:T, public schema?: Schema<T>) {
17
+ this.#data = this.initial;
18
+ }
19
+ #proxy<R extends object>(data: R, loader: ConfigLoader<T>) {
20
+ return new Proxy(data, {
21
+ get(target, prop, receiver) {
22
+ const result= Reflect.get(target, prop, receiver);
23
+ if(result instanceof Object && result!==null) return loader.#proxy(result,loader);
24
+ if(typeof result==='string') {
25
+ if(result.startsWith('\\${') && result.endsWith('}')) return result.slice(1);
26
+ if(/^\$\{(.*)\}$/.test(result)){
27
+ const content = result.slice(2, -1);
28
+ const [key, ...rest] = content.split(':');
29
+ const defaultValue = rest.length > 0 ? rest.join(':') : undefined;
30
+ return process.env[key] ?? defaultValue ?? (loader.initial as any)[key] ?? result;
31
+ }
32
+ }
33
+ return result;
34
+ },
35
+ set(target, prop, value, receiver) {
36
+ const result= Reflect.set(target, prop, value, receiver);
37
+ loader.save(loader.filename);
38
+ return result;
39
+ },
40
+ deleteProperty(target, prop) {
41
+ const result= Reflect.deleteProperty(target, prop);
42
+ loader.save(loader.filename);
43
+ return result;
44
+ }
45
+ });
46
+ }
47
+ load() {
48
+ const fullPath=path.resolve(process.cwd(), this.filename);
49
+ if (!fs.existsSync(fullPath)) {
50
+ this.save(fullPath);
51
+ }
52
+ const content = fs.readFileSync(fullPath, "utf-8");
53
+ let rawConfig: any;
54
+ switch (this.extension) {
55
+ case ".json":
56
+ rawConfig = JSON.parse(content);
57
+ break;
58
+ case ".yaml":
59
+ case ".yml":
60
+ rawConfig = parseYaml(content);
61
+ break;
62
+ }
63
+ if(this.schema){
64
+ this.#data = this.schema(rawConfig || this.initial) as T;
65
+ }else{
66
+ this.#data = rawConfig as T;
67
+ }
68
+ }
69
+ save(fullPath: string) {
70
+ switch (this.extension) {
71
+ case ".json":
72
+ fs.writeFileSync(fullPath, JSON.stringify(this.#data, null, 2));
73
+ break;
74
+ case ".yaml":
75
+ case ".yml":
76
+ fs.writeFileSync(fullPath, stringifyYaml(this.#data));
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ export namespace ConfigLoader{
82
+ export const supportedExtensions = [".json", ".yaml", ".yml"];
83
+ export function load<T extends object>(filename: string, initial?:T, schema?: Schema<T>) {
84
+ const result = new ConfigLoader<T>(filename, initial??{} as T, schema);
85
+ result.load();
86
+ return result;
87
+ }
88
+ }
89
+ export class ConfigService{
90
+ configs: Map<string, ConfigLoader<any>> = new Map();
91
+ constructor() {
92
+ }
93
+ load<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>) {
94
+ const ext = path.extname(filename).toLowerCase();
95
+ if (!ConfigLoader.supportedExtensions.includes(ext)) {
96
+ throw new Error(`不支持的配置文件格式: ${ext}`);
97
+ }
98
+ const config = ConfigLoader.load(filename, initial as T, schema);
99
+ this.configs.set(filename, config);
100
+ return config;
101
+ }
102
+ get<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>): T {
103
+ if(!this.configs.has(filename)) this.load(filename, initial, schema);
104
+ const config = this.configs.get(filename);
105
+ if(!config) throw new Error(`配置文件 ${filename} 未加载`);
106
+ return config.data as T;
107
+ }
108
+ getRaw<T extends object>(filename: string, initial?:Partial<T>, schema?: Schema<T>): T {
109
+ if(!this.configs.has(filename)) this.load(filename, initial, schema);
110
+ const config = this.configs.get(filename);
111
+ if(!config) throw new Error(`配置文件 ${filename} 未加载`);
112
+ return config.raw as T;
113
+ }
114
+ /**
115
+ * 更新配置文件内容
116
+ * @param filename 配置文件名
117
+ * @param data 新的配置数据
118
+ */
119
+ set<T extends object>(filename: string, data: T): void {
120
+ const config = this.configs.get(filename);
121
+ if(!config) throw new Error(`配置文件 ${filename} 未加载`);
122
+ // 直接更新内部数据(会触发 Proxy 的 set 拦截器并自动保存)
123
+ Object.assign(config.raw, data);
124
+ config.save(path.resolve(process.cwd(), filename));
125
+ }
126
+ }