@mandujs/core 0.41.2 → 0.43.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.
Files changed (88) hide show
  1. package/package.json +21 -4
  2. package/src/auth/__tests__/login.test.ts +420 -419
  3. package/src/auth/__tests__/reset.test.ts +296 -296
  4. package/src/brain/adapters/anthropic-oauth.ts +421 -420
  5. package/src/brain/adapters/index.ts +2 -1
  6. package/src/brain/adapters/ollama.ts +1 -1
  7. package/src/brain/adapters/openai-oauth.ts +534 -533
  8. package/src/brain/brain.ts +2 -1
  9. package/src/brain/redactor.ts +196 -196
  10. package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
  11. package/src/bundler/__tests__/cold-start.test.ts +504 -504
  12. package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
  13. package/src/bundler/__tests__/hdr.test.ts +1 -1
  14. package/src/bundler/analyzer.ts +958 -958
  15. package/src/bundler/build.ts +104 -14
  16. package/src/bundler/dev.ts +125 -0
  17. package/src/bundler/hmr-types.ts +1 -0
  18. package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
  19. package/src/bundler/plugins/index.ts +14 -0
  20. package/src/bundler/plugins/react-compiler-lint.ts +253 -0
  21. package/src/bundler/plugins/react-compiler.ts +162 -0
  22. package/src/bundler/types.ts +12 -0
  23. package/src/change/integrity.ts +2 -1
  24. package/src/client/index.ts +10 -0
  25. package/src/client/island.ts +38 -11
  26. package/src/client/router.ts +6 -1
  27. package/src/config/mandu.ts +57 -0
  28. package/src/config/validate.ts +42 -0
  29. package/src/content/collection.ts +844 -809
  30. package/src/content/content-layer.ts +316 -314
  31. package/src/content/content.test.ts +433 -433
  32. package/src/content/digest.ts +133 -133
  33. package/src/content/generate-types.ts +168 -168
  34. package/src/content/index.ts +6 -1
  35. package/src/content/llms-txt.ts +277 -277
  36. package/src/contract/define.ts +474 -474
  37. package/src/contract/route-helpers.ts +2 -1
  38. package/src/contract/zod-utils.ts +158 -155
  39. package/src/db/index.ts +513 -513
  40. package/src/desktop/__tests__/smoke.test.ts +100 -100
  41. package/src/desktop/webview-fallback.ts +583 -583
  42. package/src/desktop/window.ts +3 -1
  43. package/src/dev-error-overlay/overlay-client.ts +300 -300
  44. package/src/devtools/ai/mcp-connector.ts +499 -498
  45. package/src/devtools/client/components/kitchen-root.tsx +7 -2
  46. package/src/email/resend.ts +163 -163
  47. package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
  48. package/src/guard/ast-analyzer.ts +806 -806
  49. package/src/guard/graph.ts +898 -898
  50. package/src/guard/index.ts +16 -0
  51. package/src/guard/statistics.ts +578 -578
  52. package/src/guard/tsgolint-bridge.ts +512 -0
  53. package/src/i18n/locale-resolver.ts +214 -214
  54. package/src/id/__tests__/id.test.ts +120 -120
  55. package/src/intent/index.ts +321 -321
  56. package/src/island/index.ts +39 -23
  57. package/src/kitchen/api/contract-api.ts +15 -8
  58. package/src/kitchen/kitchen-ui.ts +2137 -2137
  59. package/src/lockfile/index.ts +3 -2
  60. package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
  61. package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
  62. package/src/middleware/secure/index.ts +417 -417
  63. package/src/observability/event-bus.ts +2 -2
  64. package/src/observability/metrics.ts +334 -334
  65. package/src/observability/tracing.ts +694 -694
  66. package/src/openapi/generator.ts +1 -1
  67. package/src/perf/user-marks.ts +553 -553
  68. package/src/plugins/registry.ts +387 -387
  69. package/src/resource/ddl/diff.ts +392 -392
  70. package/src/resource/ddl/snapshot.ts +448 -447
  71. package/src/resource/generator-schema.ts +477 -476
  72. package/src/resource/parser.ts +4 -2
  73. package/src/resource/schema.ts +1 -1
  74. package/src/router/fs-patterns.ts +422 -422
  75. package/src/runtime/fast-refresh-types.ts +126 -128
  76. package/src/runtime/image-handler.ts +206 -195
  77. package/src/runtime/router.test.ts +476 -476
  78. package/src/runtime/security.ts +155 -155
  79. package/src/runtime/server.ts +36 -19
  80. package/src/runtime/session-key.ts +328 -328
  81. package/src/scheduler/__tests__/scheduler.test.ts +514 -514
  82. package/src/seo/resolve/index.ts +353 -353
  83. package/src/spec/load.ts +1 -1
  84. package/src/testing/reporter.ts +676 -676
  85. package/src/testing/server.ts +196 -196
  86. package/src/testing/snapshot.ts +444 -444
  87. package/src/utils/__tests__/lru-cache.test.ts +186 -186
  88. package/src/utils/bun.ts +8 -8
@@ -1,387 +1,387 @@
1
- /**
2
- * DNA-001: Plugin Registry
3
- *
4
- * 플러그인 등록 및 관리
5
- * - 중복 등록 방지
6
- * - 의존성 해결
7
- * - 라이프사이클 관리
8
- */
9
-
10
- import type {
11
- Plugin,
12
- PluginApi,
13
- PluginCategory,
14
- PluginMeta,
15
- GuardPresetPlugin,
16
- BuildPlugin,
17
- LoggerTransportPlugin,
18
- McpToolPlugin,
19
- MiddlewarePlugin,
20
- } from "./types";
21
-
22
- /**
23
- * 플러그인 등록 상태
24
- */
25
- type PluginState = "pending" | "loaded" | "error" | "unloaded";
26
-
27
- /**
28
- * 등록된 플러그인 정보 (discriminated union by state)
29
- *
30
- * state에 따라 접근 가능한 필드가 타입 레벨에서 결정됨:
31
- * - pending: plugin, config만 존재
32
- * - loaded: plugin, config, loadedAt 존재
33
- * - error: plugin, config, error 존재
34
- * - unloaded: plugin만 존재
35
- */
36
- type RegisteredPlugin<TConfig = unknown> =
37
- | { state: "pending"; plugin: Plugin<TConfig>; config?: TConfig }
38
- | { state: "loaded"; plugin: Plugin<TConfig>; config?: TConfig; loadedAt: Date }
39
- | { state: "error"; plugin: Plugin<TConfig>; config?: TConfig; error: Error }
40
- | { state: "unloaded"; plugin: Plugin<TConfig> };
41
-
42
- /**
43
- * 소유자 정보 포함 리소스
44
- */
45
- interface OwnedResource<T> {
46
- owner: string;
47
- value: T;
48
- }
49
-
50
- /**
51
- * 플러그인 레지스트리
52
- */
53
- export class PluginRegistry {
54
- private plugins = new Map<string, RegisteredPlugin>();
55
- private guardPresets = new Map<string, OwnedResource<GuardPresetPlugin>>();
56
- private buildPlugins = new Map<string, OwnedResource<BuildPlugin>>();
57
- private loggerTransports = new Map<string, OwnedResource<LoggerTransportPlugin>>();
58
- private mcpTools = new Map<string, OwnedResource<McpToolPlugin>>();
59
- private middlewares = new Map<string, OwnedResource<MiddlewarePlugin>>();
60
-
61
- private logger = {
62
- debug: (msg: string, data?: unknown) =>
63
- console.debug(`[Plugin] ${msg}`, data ?? ""),
64
- info: (msg: string, data?: unknown) =>
65
- console.info(`[Plugin] ${msg}`, data ?? ""),
66
- warn: (msg: string, data?: unknown) =>
67
- console.warn(`[Plugin] ${msg}`, data ?? ""),
68
- error: (msg: string, data?: unknown) =>
69
- console.error(`[Plugin] ${msg}`, data ?? ""),
70
- };
71
-
72
- private configStore = new Map<string, unknown>();
73
-
74
- /**
75
- * 플러그인 등록
76
- */
77
- async register<TConfig>(
78
- plugin: Plugin<TConfig>,
79
- config?: TConfig
80
- ): Promise<void> {
81
- const { id } = plugin.meta;
82
-
83
- // 중복 체크
84
- if (this.plugins.has(id)) {
85
- throw new Error(`Plugin "${id}" is already registered`);
86
- }
87
-
88
- // 설정 검증
89
- let validatedConfig = config;
90
- if (plugin.configSchema) {
91
- const result = plugin.configSchema.safeParse(config);
92
- if (!result.success) {
93
- const error = new Error(
94
- `Invalid config for plugin "${id}": ${result.error.message}`
95
- );
96
- this.plugins.set(id, { plugin: plugin as Plugin<unknown>, state: "error", config: validatedConfig, error });
97
- throw error;
98
- }
99
- validatedConfig = result.data;
100
- }
101
-
102
- // 등록
103
- this.plugins.set(id, {
104
- plugin: plugin as Plugin<unknown>,
105
- state: "pending",
106
- config: validatedConfig,
107
- });
108
-
109
- // 로드
110
- try {
111
- const api = this.createPluginApi(id);
112
- await plugin.register(api, validatedConfig as TConfig);
113
-
114
- // onLoad 훅
115
- if (plugin.onLoad) {
116
- await plugin.onLoad();
117
- }
118
-
119
- this.plugins.set(id, {
120
- state: "loaded",
121
- plugin: plugin as Plugin<unknown>,
122
- config: validatedConfig,
123
- loadedAt: new Date(),
124
- });
125
-
126
- this.logger.info(`Plugin loaded: ${id} (v${plugin.meta.version})`);
127
- } catch (error) {
128
- const pluginError = error instanceof Error ? error : new Error(String(error));
129
- this.plugins.set(id, {
130
- state: "error",
131
- plugin: plugin as Plugin<unknown>,
132
- config: validatedConfig,
133
- error: pluginError,
134
- });
135
- throw pluginError;
136
- }
137
- }
138
-
139
- /**
140
- * 플러그인 언로드
141
- */
142
- async unregister(id: string): Promise<void> {
143
- const entry = this.plugins.get(id);
144
- if (!entry) {
145
- throw new Error(`Plugin "${id}" is not registered`);
146
- }
147
-
148
- // onUnload 훅
149
- if (entry.plugin.onUnload) {
150
- await entry.plugin.onUnload();
151
- }
152
-
153
- // 등록된 리소스 정리
154
- this.removeOwnedResources(id);
155
-
156
- this.plugins.delete(id);
157
-
158
- this.logger.info(`Plugin unloaded: ${id}`);
159
- }
160
-
161
- /**
162
- * 플러그인 조회
163
- */
164
- get(id: string): RegisteredPlugin | undefined {
165
- return this.plugins.get(id);
166
- }
167
-
168
- /**
169
- * 모든 플러그인 목록
170
- */
171
- getAll(): RegisteredPlugin[] {
172
- return Array.from(this.plugins.values());
173
- }
174
-
175
- /**
176
- * 카테고리별 플러그인 조회
177
- */
178
- getByCategory(category: PluginCategory): RegisteredPlugin[] {
179
- return this.getAll().filter((p) => p.plugin.meta.category === category);
180
- }
181
-
182
- /**
183
- * Guard 프리셋 조회
184
- */
185
- getGuardPreset(id: string): GuardPresetPlugin | undefined {
186
- return this.guardPresets.get(id)?.value;
187
- }
188
-
189
- /**
190
- * 모든 Guard 프리셋 목록
191
- */
192
- getAllGuardPresets(): GuardPresetPlugin[] {
193
- return Array.from(this.guardPresets.values()).map((entry) => entry.value);
194
- }
195
-
196
- /**
197
- * 빌드 플러그인 조회
198
- */
199
- getBuildPlugin(id: string): BuildPlugin | undefined {
200
- return this.buildPlugins.get(id)?.value;
201
- }
202
-
203
- /**
204
- * 모든 빌드 플러그인 목록
205
- */
206
- getAllBuildPlugins(): BuildPlugin[] {
207
- return Array.from(this.buildPlugins.values()).map((entry) => entry.value);
208
- }
209
-
210
- /**
211
- * 로거 전송 조회
212
- */
213
- getLoggerTransport(id: string): LoggerTransportPlugin | undefined {
214
- return this.loggerTransports.get(id)?.value;
215
- }
216
-
217
- /**
218
- * 모든 로거 전송 목록
219
- */
220
- getAllLoggerTransports(): LoggerTransportPlugin[] {
221
- return Array.from(this.loggerTransports.values()).map((entry) => entry.value);
222
- }
223
-
224
- /**
225
- * MCP 도구 조회
226
- */
227
- getMcpTool(name: string): McpToolPlugin | undefined {
228
- return this.mcpTools.get(name)?.value;
229
- }
230
-
231
- /**
232
- * 모든 MCP 도구 목록
233
- */
234
- getAllMcpTools(): McpToolPlugin[] {
235
- return Array.from(this.mcpTools.values()).map((entry) => entry.value);
236
- }
237
-
238
- /**
239
- * 미들웨어 조회 (순서대로)
240
- */
241
- getAllMiddlewares(): MiddlewarePlugin[] {
242
- return Array.from(this.middlewares.values())
243
- .map((entry) => entry.value)
244
- .sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
245
- }
246
-
247
- /**
248
- * 설정 저장
249
- */
250
- setConfig<T>(key: string, value: T): void {
251
- this.configStore.set(key, value);
252
- }
253
-
254
- /**
255
- * 설정 조회
256
- */
257
- getConfig<T>(key: string): T | undefined {
258
- return this.configStore.get(key) as T | undefined;
259
- }
260
-
261
- /**
262
- * 서버 시작 훅 실행
263
- */
264
- async onServerStart(): Promise<void> {
265
- for (const [id, entry] of this.plugins) {
266
- if (entry.state === "loaded" && entry.plugin.onServerStart) {
267
- try {
268
- await entry.plugin.onServerStart();
269
- } catch (error) {
270
- this.logger.error(`onServerStart failed for plugin "${id}"`, error);
271
- }
272
- }
273
- }
274
- }
275
-
276
- /**
277
- * 서버 종료 훅 실행
278
- */
279
- async onServerStop(): Promise<void> {
280
- for (const [id, entry] of this.plugins) {
281
- if (entry.state === "loaded" && entry.plugin.onServerStop) {
282
- try {
283
- await entry.plugin.onServerStop();
284
- } catch (error) {
285
- this.logger.error(`onServerStop failed for plugin "${id}"`, error);
286
- }
287
- }
288
- }
289
- }
290
-
291
- /**
292
- * 플러그인 API 생성
293
- */
294
- private createPluginApi(ownerId: string): PluginApi {
295
- return {
296
- registerGuardPreset: (preset) => {
297
- if (this.guardPresets.has(preset.id)) {
298
- throw new Error(`Guard preset "${preset.id}" is already registered`);
299
- }
300
- this.guardPresets.set(preset.id, { owner: ownerId, value: preset });
301
- this.logger.debug(`Registered guard preset: ${preset.id}`);
302
- },
303
-
304
- registerBuildPlugin: (plugin) => {
305
- if (this.buildPlugins.has(plugin.id)) {
306
- throw new Error(`Build plugin "${plugin.id}" is already registered`);
307
- }
308
- this.buildPlugins.set(plugin.id, { owner: ownerId, value: plugin });
309
- this.logger.debug(`Registered build plugin: ${plugin.id}`);
310
- },
311
-
312
- registerLoggerTransport: (transport) => {
313
- if (this.loggerTransports.has(transport.id)) {
314
- throw new Error(
315
- `Logger transport "${transport.id}" is already registered`
316
- );
317
- }
318
- this.loggerTransports.set(transport.id, { owner: ownerId, value: transport });
319
- this.logger.debug(`Registered logger transport: ${transport.id}`);
320
- },
321
-
322
- registerMcpTool: (tool) => {
323
- if (this.mcpTools.has(tool.name)) {
324
- throw new Error(`MCP tool "${tool.name}" is already registered`);
325
- }
326
- this.mcpTools.set(tool.name, { owner: ownerId, value: tool });
327
- this.logger.debug(`Registered MCP tool: ${tool.name}`);
328
- },
329
-
330
- registerMiddleware: (middleware) => {
331
- if (this.middlewares.has(middleware.id)) {
332
- throw new Error(
333
- `Middleware "${middleware.id}" is already registered`
334
- );
335
- }
336
- this.middlewares.set(middleware.id, { owner: ownerId, value: middleware });
337
- this.logger.debug(`Registered middleware: ${middleware.id}`);
338
- },
339
-
340
- getConfig: <T>(key: string) => this.getConfig<T>(key),
341
-
342
- logger: this.logger,
343
- };
344
- }
345
-
346
- private removeOwnedResources(ownerId: string): void {
347
- const removeOwned = <T>(map: Map<string, OwnedResource<T>>): void => {
348
- for (const [id, entry] of map.entries()) {
349
- if (entry.owner === ownerId) {
350
- map.delete(id);
351
- }
352
- }
353
- };
354
-
355
- removeOwned(this.guardPresets);
356
- removeOwned(this.buildPlugins);
357
- removeOwned(this.loggerTransports);
358
- removeOwned(this.mcpTools);
359
- removeOwned(this.middlewares);
360
- }
361
-
362
- /**
363
- * 레지스트리 초기화
364
- */
365
- async reset(): Promise<void> {
366
- // 모든 플러그인 언로드
367
- for (const id of this.plugins.keys()) {
368
- await this.unregister(id);
369
- }
370
-
371
- this.configStore.clear();
372
- }
373
- }
374
-
375
- /**
376
- * 전역 플러그인 레지스트리
377
- */
378
- export const globalPluginRegistry = new PluginRegistry();
379
-
380
- /**
381
- * 플러그인 정의 헬퍼
382
- */
383
- export function definePlugin<TConfig = void>(
384
- plugin: Plugin<TConfig>
385
- ): Plugin<TConfig> {
386
- return plugin;
387
- }
1
+ /**
2
+ * DNA-001: Plugin Registry
3
+ *
4
+ * 플러그인 등록 및 관리
5
+ * - 중복 등록 방지
6
+ * - 의존성 해결
7
+ * - 라이프사이클 관리
8
+ */
9
+
10
+ import type {
11
+ Plugin,
12
+ PluginApi,
13
+ PluginCategory,
14
+ PluginMeta,
15
+ GuardPresetPlugin,
16
+ BuildPlugin,
17
+ LoggerTransportPlugin,
18
+ McpToolPlugin,
19
+ MiddlewarePlugin,
20
+ } from "./types";
21
+
22
+ /**
23
+ * 플러그인 등록 상태
24
+ */
25
+ type PluginState = "pending" | "loaded" | "error" | "unloaded";
26
+
27
+ /**
28
+ * 등록된 플러그인 정보 (discriminated union by state)
29
+ *
30
+ * state에 따라 접근 가능한 필드가 타입 레벨에서 결정됨:
31
+ * - pending: plugin, config만 존재
32
+ * - loaded: plugin, config, loadedAt 존재
33
+ * - error: plugin, config, error 존재
34
+ * - unloaded: plugin만 존재
35
+ */
36
+ type RegisteredPlugin<TConfig = unknown> =
37
+ | { state: "pending"; plugin: Plugin<TConfig>; config?: TConfig }
38
+ | { state: "loaded"; plugin: Plugin<TConfig>; config?: TConfig; loadedAt: Date }
39
+ | { state: "error"; plugin: Plugin<TConfig>; config?: TConfig; error: Error }
40
+ | { state: "unloaded"; plugin: Plugin<TConfig> };
41
+
42
+ /**
43
+ * 소유자 정보 포함 리소스
44
+ */
45
+ interface OwnedResource<T> {
46
+ owner: string;
47
+ value: T;
48
+ }
49
+
50
+ /**
51
+ * 플러그인 레지스트리
52
+ */
53
+ export class PluginRegistry {
54
+ private plugins = new Map<string, RegisteredPlugin>();
55
+ private guardPresets = new Map<string, OwnedResource<GuardPresetPlugin>>();
56
+ private buildPlugins = new Map<string, OwnedResource<BuildPlugin>>();
57
+ private loggerTransports = new Map<string, OwnedResource<LoggerTransportPlugin>>();
58
+ private mcpTools = new Map<string, OwnedResource<McpToolPlugin>>();
59
+ private middlewares = new Map<string, OwnedResource<MiddlewarePlugin>>();
60
+
61
+ private logger = {
62
+ debug: (msg: string, data?: unknown) =>
63
+ console.debug(`[Plugin] ${msg}`, data ?? ""),
64
+ info: (msg: string, data?: unknown) =>
65
+ console.info(`[Plugin] ${msg}`, data ?? ""),
66
+ warn: (msg: string, data?: unknown) =>
67
+ console.warn(`[Plugin] ${msg}`, data ?? ""),
68
+ error: (msg: string, data?: unknown) =>
69
+ console.error(`[Plugin] ${msg}`, data ?? ""),
70
+ };
71
+
72
+ private configStore = new Map<string, unknown>();
73
+
74
+ /**
75
+ * 플러그인 등록
76
+ */
77
+ async register<TConfig>(
78
+ plugin: Plugin<TConfig>,
79
+ config?: TConfig
80
+ ): Promise<void> {
81
+ const { id } = plugin.meta;
82
+
83
+ // 중복 체크
84
+ if (this.plugins.has(id)) {
85
+ throw new Error(`Plugin "${id}" is already registered`);
86
+ }
87
+
88
+ // 설정 검증
89
+ let validatedConfig = config;
90
+ if (plugin.configSchema) {
91
+ const result = plugin.configSchema.safeParse(config);
92
+ if (!result.success) {
93
+ const error = new Error(
94
+ `Invalid config for plugin "${id}": ${result.error.message}`
95
+ );
96
+ this.plugins.set(id, { plugin: plugin as Plugin<unknown>, state: "error", config: validatedConfig, error });
97
+ throw error;
98
+ }
99
+ validatedConfig = result.data;
100
+ }
101
+
102
+ // 등록
103
+ this.plugins.set(id, {
104
+ plugin: plugin as Plugin<unknown>,
105
+ state: "pending",
106
+ config: validatedConfig,
107
+ });
108
+
109
+ // 로드
110
+ try {
111
+ const api = this.createPluginApi(id);
112
+ await plugin.register(api, validatedConfig as TConfig);
113
+
114
+ // onLoad 훅
115
+ if (plugin.onLoad) {
116
+ await plugin.onLoad();
117
+ }
118
+
119
+ this.plugins.set(id, {
120
+ state: "loaded",
121
+ plugin: plugin as Plugin<unknown>,
122
+ config: validatedConfig,
123
+ loadedAt: new Date(),
124
+ });
125
+
126
+ this.logger.info(`Plugin loaded: ${id} (v${plugin.meta.version})`);
127
+ } catch (error) {
128
+ const pluginError = error instanceof Error ? error : new Error(String(error));
129
+ this.plugins.set(id, {
130
+ state: "error",
131
+ plugin: plugin as Plugin<unknown>,
132
+ config: validatedConfig,
133
+ error: pluginError,
134
+ });
135
+ throw pluginError;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 플러그인 언로드
141
+ */
142
+ async unregister(id: string): Promise<void> {
143
+ const entry = this.plugins.get(id);
144
+ if (!entry) {
145
+ throw new Error(`Plugin "${id}" is not registered`);
146
+ }
147
+
148
+ // onUnload 훅
149
+ if (entry.plugin.onUnload) {
150
+ await entry.plugin.onUnload();
151
+ }
152
+
153
+ // 등록된 리소스 정리
154
+ this.removeOwnedResources(id);
155
+
156
+ this.plugins.delete(id);
157
+
158
+ this.logger.info(`Plugin unloaded: ${id}`);
159
+ }
160
+
161
+ /**
162
+ * 플러그인 조회
163
+ */
164
+ get(id: string): RegisteredPlugin | undefined {
165
+ return this.plugins.get(id);
166
+ }
167
+
168
+ /**
169
+ * 모든 플러그인 목록
170
+ */
171
+ getAll(): RegisteredPlugin[] {
172
+ return Array.from(this.plugins.values());
173
+ }
174
+
175
+ /**
176
+ * 카테고리별 플러그인 조회
177
+ */
178
+ getByCategory(category: PluginCategory): RegisteredPlugin[] {
179
+ return this.getAll().filter((p) => p.plugin.meta.category === category);
180
+ }
181
+
182
+ /**
183
+ * Guard 프리셋 조회
184
+ */
185
+ getGuardPreset(id: string): GuardPresetPlugin | undefined {
186
+ return this.guardPresets.get(id)?.value;
187
+ }
188
+
189
+ /**
190
+ * 모든 Guard 프리셋 목록
191
+ */
192
+ getAllGuardPresets(): GuardPresetPlugin[] {
193
+ return Array.from(this.guardPresets.values()).map((entry) => entry.value);
194
+ }
195
+
196
+ /**
197
+ * 빌드 플러그인 조회
198
+ */
199
+ getBuildPlugin(id: string): BuildPlugin | undefined {
200
+ return this.buildPlugins.get(id)?.value;
201
+ }
202
+
203
+ /**
204
+ * 모든 빌드 플러그인 목록
205
+ */
206
+ getAllBuildPlugins(): BuildPlugin[] {
207
+ return Array.from(this.buildPlugins.values()).map((entry) => entry.value);
208
+ }
209
+
210
+ /**
211
+ * 로거 전송 조회
212
+ */
213
+ getLoggerTransport(id: string): LoggerTransportPlugin | undefined {
214
+ return this.loggerTransports.get(id)?.value;
215
+ }
216
+
217
+ /**
218
+ * 모든 로거 전송 목록
219
+ */
220
+ getAllLoggerTransports(): LoggerTransportPlugin[] {
221
+ return Array.from(this.loggerTransports.values()).map((entry) => entry.value);
222
+ }
223
+
224
+ /**
225
+ * MCP 도구 조회
226
+ */
227
+ getMcpTool(name: string): McpToolPlugin | undefined {
228
+ return this.mcpTools.get(name)?.value;
229
+ }
230
+
231
+ /**
232
+ * 모든 MCP 도구 목록
233
+ */
234
+ getAllMcpTools(): McpToolPlugin[] {
235
+ return Array.from(this.mcpTools.values()).map((entry) => entry.value);
236
+ }
237
+
238
+ /**
239
+ * 미들웨어 조회 (순서대로)
240
+ */
241
+ getAllMiddlewares(): MiddlewarePlugin[] {
242
+ return Array.from(this.middlewares.values())
243
+ .map((entry) => entry.value)
244
+ .sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
245
+ }
246
+
247
+ /**
248
+ * 설정 저장
249
+ */
250
+ setConfig<T>(key: string, value: T): void {
251
+ this.configStore.set(key, value);
252
+ }
253
+
254
+ /**
255
+ * 설정 조회
256
+ */
257
+ getConfig<T>(key: string): T | undefined {
258
+ return this.configStore.get(key) as T | undefined;
259
+ }
260
+
261
+ /**
262
+ * 서버 시작 훅 실행
263
+ */
264
+ async onServerStart(): Promise<void> {
265
+ for (const [id, entry] of this.plugins) {
266
+ if (entry.state === "loaded" && entry.plugin.onServerStart) {
267
+ try {
268
+ await entry.plugin.onServerStart();
269
+ } catch (error) {
270
+ this.logger.error(`onServerStart failed for plugin "${id}"`, error);
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ /**
277
+ * 서버 종료 훅 실행
278
+ */
279
+ async onServerStop(): Promise<void> {
280
+ for (const [id, entry] of this.plugins) {
281
+ if (entry.state === "loaded" && entry.plugin.onServerStop) {
282
+ try {
283
+ await entry.plugin.onServerStop();
284
+ } catch (error) {
285
+ this.logger.error(`onServerStop failed for plugin "${id}"`, error);
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ /**
292
+ * 플러그인 API 생성
293
+ */
294
+ private createPluginApi(ownerId: string): PluginApi {
295
+ return {
296
+ registerGuardPreset: (preset) => {
297
+ if (this.guardPresets.has(preset.id)) {
298
+ throw new Error(`Guard preset "${preset.id}" is already registered`);
299
+ }
300
+ this.guardPresets.set(preset.id, { owner: ownerId, value: preset });
301
+ this.logger.debug(`Registered guard preset: ${preset.id}`);
302
+ },
303
+
304
+ registerBuildPlugin: (plugin) => {
305
+ if (this.buildPlugins.has(plugin.id)) {
306
+ throw new Error(`Build plugin "${plugin.id}" is already registered`);
307
+ }
308
+ this.buildPlugins.set(plugin.id, { owner: ownerId, value: plugin });
309
+ this.logger.debug(`Registered build plugin: ${plugin.id}`);
310
+ },
311
+
312
+ registerLoggerTransport: (transport) => {
313
+ if (this.loggerTransports.has(transport.id)) {
314
+ throw new Error(
315
+ `Logger transport "${transport.id}" is already registered`
316
+ );
317
+ }
318
+ this.loggerTransports.set(transport.id, { owner: ownerId, value: transport });
319
+ this.logger.debug(`Registered logger transport: ${transport.id}`);
320
+ },
321
+
322
+ registerMcpTool: (tool) => {
323
+ if (this.mcpTools.has(tool.name)) {
324
+ throw new Error(`MCP tool "${tool.name}" is already registered`);
325
+ }
326
+ this.mcpTools.set(tool.name, { owner: ownerId, value: tool });
327
+ this.logger.debug(`Registered MCP tool: ${tool.name}`);
328
+ },
329
+
330
+ registerMiddleware: (middleware) => {
331
+ if (this.middlewares.has(middleware.id)) {
332
+ throw new Error(
333
+ `Middleware "${middleware.id}" is already registered`
334
+ );
335
+ }
336
+ this.middlewares.set(middleware.id, { owner: ownerId, value: middleware });
337
+ this.logger.debug(`Registered middleware: ${middleware.id}`);
338
+ },
339
+
340
+ getConfig: <T>(key: string) => this.getConfig<T>(key),
341
+
342
+ logger: this.logger,
343
+ };
344
+ }
345
+
346
+ private removeOwnedResources(ownerId: string): void {
347
+ const removeOwned = <T>(map: Map<string, OwnedResource<T>>): void => {
348
+ for (const [id, entry] of map.entries()) {
349
+ if (entry.owner === ownerId) {
350
+ map.delete(id);
351
+ }
352
+ }
353
+ };
354
+
355
+ removeOwned(this.guardPresets);
356
+ removeOwned(this.buildPlugins);
357
+ removeOwned(this.loggerTransports);
358
+ removeOwned(this.mcpTools);
359
+ removeOwned(this.middlewares);
360
+ }
361
+
362
+ /**
363
+ * 레지스트리 초기화
364
+ */
365
+ async reset(): Promise<void> {
366
+ // 모든 플러그인 언로드
367
+ for (const id of this.plugins.keys()) {
368
+ await this.unregister(id);
369
+ }
370
+
371
+ this.configStore.clear();
372
+ }
373
+ }
374
+
375
+ /**
376
+ * 전역 플러그인 레지스트리
377
+ */
378
+ export const globalPluginRegistry = new PluginRegistry();
379
+
380
+ /**
381
+ * 플러그인 정의 헬퍼
382
+ */
383
+ export function definePlugin<TConfig = void>(
384
+ plugin: Plugin<TConfig>
385
+ ): Plugin<TConfig> {
386
+ return plugin;
387
+ }