@dangao/bun-server 1.12.1 → 2.0.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 (214) hide show
  1. package/README.md +32 -0
  2. package/dist/ai/ai-module.d.ts +24 -0
  3. package/dist/ai/ai-module.d.ts.map +1 -0
  4. package/dist/ai/decorators.d.ts +25 -0
  5. package/dist/ai/decorators.d.ts.map +1 -0
  6. package/dist/ai/errors.d.ts +39 -0
  7. package/dist/ai/errors.d.ts.map +1 -0
  8. package/dist/ai/index.d.ts +12 -0
  9. package/dist/ai/index.d.ts.map +1 -0
  10. package/dist/ai/providers/anthropic-provider.d.ts +23 -0
  11. package/dist/ai/providers/anthropic-provider.d.ts.map +1 -0
  12. package/dist/ai/providers/google-provider.d.ts +20 -0
  13. package/dist/ai/providers/google-provider.d.ts.map +1 -0
  14. package/dist/ai/providers/ollama-provider.d.ts +17 -0
  15. package/dist/ai/providers/ollama-provider.d.ts.map +1 -0
  16. package/dist/ai/providers/openai-provider.d.ts +28 -0
  17. package/dist/ai/providers/openai-provider.d.ts.map +1 -0
  18. package/dist/ai/service.d.ts +40 -0
  19. package/dist/ai/service.d.ts.map +1 -0
  20. package/dist/ai/tools/tool-executor.d.ts +15 -0
  21. package/dist/ai/tools/tool-executor.d.ts.map +1 -0
  22. package/dist/ai/tools/tool-registry.d.ts +39 -0
  23. package/dist/ai/tools/tool-registry.d.ts.map +1 -0
  24. package/dist/ai/types.d.ts +134 -0
  25. package/dist/ai/types.d.ts.map +1 -0
  26. package/dist/ai-guard/ai-guard-module.d.ts +18 -0
  27. package/dist/ai-guard/ai-guard-module.d.ts.map +1 -0
  28. package/dist/ai-guard/decorators.d.ts +16 -0
  29. package/dist/ai-guard/decorators.d.ts.map +1 -0
  30. package/dist/ai-guard/detectors/content-moderator.d.ts +26 -0
  31. package/dist/ai-guard/detectors/content-moderator.d.ts.map +1 -0
  32. package/dist/ai-guard/detectors/injection-detector.d.ts +13 -0
  33. package/dist/ai-guard/detectors/injection-detector.d.ts.map +1 -0
  34. package/dist/ai-guard/detectors/pii-detector.d.ts +11 -0
  35. package/dist/ai-guard/detectors/pii-detector.d.ts.map +1 -0
  36. package/dist/ai-guard/index.d.ts +8 -0
  37. package/dist/ai-guard/index.d.ts.map +1 -0
  38. package/dist/ai-guard/service.d.ts +21 -0
  39. package/dist/ai-guard/service.d.ts.map +1 -0
  40. package/dist/ai-guard/types.d.ts +59 -0
  41. package/dist/ai-guard/types.d.ts.map +1 -0
  42. package/dist/conversation/conversation-module.d.ts +25 -0
  43. package/dist/conversation/conversation-module.d.ts.map +1 -0
  44. package/dist/conversation/decorators.d.ts +28 -0
  45. package/dist/conversation/decorators.d.ts.map +1 -0
  46. package/dist/conversation/index.d.ts +8 -0
  47. package/dist/conversation/index.d.ts.map +1 -0
  48. package/dist/conversation/service.d.ts +43 -0
  49. package/dist/conversation/service.d.ts.map +1 -0
  50. package/dist/conversation/stores/database-store.d.ts +46 -0
  51. package/dist/conversation/stores/database-store.d.ts.map +1 -0
  52. package/dist/conversation/stores/memory-store.d.ts +17 -0
  53. package/dist/conversation/stores/memory-store.d.ts.map +1 -0
  54. package/dist/conversation/stores/redis-store.d.ts +39 -0
  55. package/dist/conversation/stores/redis-store.d.ts.map +1 -0
  56. package/dist/conversation/types.d.ts +64 -0
  57. package/dist/conversation/types.d.ts.map +1 -0
  58. package/dist/embedding/embedding-module.d.ts +20 -0
  59. package/dist/embedding/embedding-module.d.ts.map +1 -0
  60. package/dist/embedding/index.d.ts +6 -0
  61. package/dist/embedding/index.d.ts.map +1 -0
  62. package/dist/embedding/providers/ollama-embedding-provider.d.ts +18 -0
  63. package/dist/embedding/providers/ollama-embedding-provider.d.ts.map +1 -0
  64. package/dist/embedding/providers/openai-embedding-provider.d.ts +18 -0
  65. package/dist/embedding/providers/openai-embedding-provider.d.ts.map +1 -0
  66. package/dist/embedding/service.d.ts +27 -0
  67. package/dist/embedding/service.d.ts.map +1 -0
  68. package/dist/embedding/types.d.ts +25 -0
  69. package/dist/embedding/types.d.ts.map +1 -0
  70. package/dist/index.d.ts +8 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2638 -1
  73. package/dist/mcp/decorators.d.ts +42 -0
  74. package/dist/mcp/decorators.d.ts.map +1 -0
  75. package/dist/mcp/index.d.ts +6 -0
  76. package/dist/mcp/index.d.ts.map +1 -0
  77. package/dist/mcp/mcp-module.d.ts +22 -0
  78. package/dist/mcp/mcp-module.d.ts.map +1 -0
  79. package/dist/mcp/registry.d.ts +23 -0
  80. package/dist/mcp/registry.d.ts.map +1 -0
  81. package/dist/mcp/server.d.ts +29 -0
  82. package/dist/mcp/server.d.ts.map +1 -0
  83. package/dist/mcp/types.d.ts +60 -0
  84. package/dist/mcp/types.d.ts.map +1 -0
  85. package/dist/prompt/index.d.ts +6 -0
  86. package/dist/prompt/index.d.ts.map +1 -0
  87. package/dist/prompt/prompt-module.d.ts +23 -0
  88. package/dist/prompt/prompt-module.d.ts.map +1 -0
  89. package/dist/prompt/service.d.ts +47 -0
  90. package/dist/prompt/service.d.ts.map +1 -0
  91. package/dist/prompt/stores/file-store.d.ts +36 -0
  92. package/dist/prompt/stores/file-store.d.ts.map +1 -0
  93. package/dist/prompt/stores/memory-store.d.ts +17 -0
  94. package/dist/prompt/stores/memory-store.d.ts.map +1 -0
  95. package/dist/prompt/types.d.ts +68 -0
  96. package/dist/prompt/types.d.ts.map +1 -0
  97. package/dist/rag/chunkers/markdown-chunker.d.ts +11 -0
  98. package/dist/rag/chunkers/markdown-chunker.d.ts.map +1 -0
  99. package/dist/rag/chunkers/text-chunker.d.ts +11 -0
  100. package/dist/rag/chunkers/text-chunker.d.ts.map +1 -0
  101. package/dist/rag/decorators.d.ts +24 -0
  102. package/dist/rag/decorators.d.ts.map +1 -0
  103. package/dist/rag/index.d.ts +7 -0
  104. package/dist/rag/index.d.ts.map +1 -0
  105. package/dist/rag/rag-module.d.ts +23 -0
  106. package/dist/rag/rag-module.d.ts.map +1 -0
  107. package/dist/rag/service.d.ts +36 -0
  108. package/dist/rag/service.d.ts.map +1 -0
  109. package/dist/rag/types.d.ts +56 -0
  110. package/dist/rag/types.d.ts.map +1 -0
  111. package/dist/vector-store/index.d.ts +6 -0
  112. package/dist/vector-store/index.d.ts.map +1 -0
  113. package/dist/vector-store/stores/memory-store.d.ts +17 -0
  114. package/dist/vector-store/stores/memory-store.d.ts.map +1 -0
  115. package/dist/vector-store/stores/pinecone-store.d.ts +27 -0
  116. package/dist/vector-store/stores/pinecone-store.d.ts.map +1 -0
  117. package/dist/vector-store/stores/qdrant-store.d.ts +29 -0
  118. package/dist/vector-store/stores/qdrant-store.d.ts.map +1 -0
  119. package/dist/vector-store/types.d.ts +60 -0
  120. package/dist/vector-store/types.d.ts.map +1 -0
  121. package/dist/vector-store/vector-store-module.d.ts +20 -0
  122. package/dist/vector-store/vector-store-module.d.ts.map +1 -0
  123. package/docs/ai.md +500 -0
  124. package/docs/best-practices.md +83 -8
  125. package/docs/database.md +23 -0
  126. package/docs/guide.md +90 -27
  127. package/docs/migration.md +81 -7
  128. package/docs/security.md +23 -0
  129. package/docs/zh/ai.md +441 -0
  130. package/docs/zh/best-practices.md +43 -0
  131. package/docs/zh/database.md +23 -0
  132. package/docs/zh/guide.md +40 -1
  133. package/docs/zh/migration.md +39 -0
  134. package/docs/zh/security.md +23 -0
  135. package/package.json +2 -2
  136. package/src/ai/ai-module.ts +62 -0
  137. package/src/ai/decorators.ts +30 -0
  138. package/src/ai/errors.ts +71 -0
  139. package/src/ai/index.ts +11 -0
  140. package/src/ai/providers/anthropic-provider.ts +190 -0
  141. package/src/ai/providers/google-provider.ts +179 -0
  142. package/src/ai/providers/ollama-provider.ts +126 -0
  143. package/src/ai/providers/openai-provider.ts +242 -0
  144. package/src/ai/service.ts +155 -0
  145. package/src/ai/tools/tool-executor.ts +38 -0
  146. package/src/ai/tools/tool-registry.ts +91 -0
  147. package/src/ai/types.ts +145 -0
  148. package/src/ai-guard/ai-guard-module.ts +50 -0
  149. package/src/ai-guard/decorators.ts +21 -0
  150. package/src/ai-guard/detectors/content-moderator.ts +80 -0
  151. package/src/ai-guard/detectors/injection-detector.ts +48 -0
  152. package/src/ai-guard/detectors/pii-detector.ts +64 -0
  153. package/src/ai-guard/index.ts +7 -0
  154. package/src/ai-guard/service.ts +100 -0
  155. package/src/ai-guard/types.ts +61 -0
  156. package/src/conversation/conversation-module.ts +63 -0
  157. package/src/conversation/decorators.ts +47 -0
  158. package/src/conversation/index.ts +7 -0
  159. package/src/conversation/service.ts +133 -0
  160. package/src/conversation/stores/database-store.ts +125 -0
  161. package/src/conversation/stores/memory-store.ts +57 -0
  162. package/src/conversation/stores/redis-store.ts +101 -0
  163. package/src/conversation/types.ts +68 -0
  164. package/src/embedding/embedding-module.ts +52 -0
  165. package/src/embedding/index.ts +5 -0
  166. package/src/embedding/providers/ollama-embedding-provider.ts +39 -0
  167. package/src/embedding/providers/openai-embedding-provider.ts +47 -0
  168. package/src/embedding/service.ts +55 -0
  169. package/src/embedding/types.ts +27 -0
  170. package/src/index.ts +10 -0
  171. package/src/mcp/decorators.ts +60 -0
  172. package/src/mcp/index.ts +5 -0
  173. package/src/mcp/mcp-module.ts +58 -0
  174. package/src/mcp/registry.ts +72 -0
  175. package/src/mcp/server.ts +164 -0
  176. package/src/mcp/types.ts +63 -0
  177. package/src/prompt/index.ts +5 -0
  178. package/src/prompt/prompt-module.ts +61 -0
  179. package/src/prompt/service.ts +93 -0
  180. package/src/prompt/stores/file-store.ts +135 -0
  181. package/src/prompt/stores/memory-store.ts +82 -0
  182. package/src/prompt/types.ts +84 -0
  183. package/src/rag/chunkers/markdown-chunker.ts +40 -0
  184. package/src/rag/chunkers/text-chunker.ts +30 -0
  185. package/src/rag/decorators.ts +26 -0
  186. package/src/rag/index.ts +6 -0
  187. package/src/rag/rag-module.ts +78 -0
  188. package/src/rag/service.ts +134 -0
  189. package/src/rag/types.ts +47 -0
  190. package/src/vector-store/index.ts +5 -0
  191. package/src/vector-store/stores/memory-store.ts +69 -0
  192. package/src/vector-store/stores/pinecone-store.ts +123 -0
  193. package/src/vector-store/stores/qdrant-store.ts +147 -0
  194. package/src/vector-store/types.ts +77 -0
  195. package/src/vector-store/vector-store-module.ts +50 -0
  196. package/tests/ai/ai-module.test.ts +46 -0
  197. package/tests/ai/ai-service.test.ts +91 -0
  198. package/tests/ai/tool-registry.test.ts +57 -0
  199. package/tests/ai-guard/ai-guard-module.test.ts +23 -0
  200. package/tests/ai-guard/content-moderator.test.ts +65 -0
  201. package/tests/ai-guard/pii-detector.test.ts +41 -0
  202. package/tests/conversation/conversation-module.test.ts +26 -0
  203. package/tests/conversation/conversation-service.test.ts +64 -0
  204. package/tests/conversation/memory-store.test.ts +68 -0
  205. package/tests/embedding/embedding-service.test.ts +55 -0
  206. package/tests/mcp/mcp-server.test.ts +85 -0
  207. package/tests/prompt/prompt-module.test.ts +30 -0
  208. package/tests/prompt/prompt-service.test.ts +74 -0
  209. package/tests/rag/chunkers.test.ts +58 -0
  210. package/tests/rag/rag-service.test.ts +66 -0
  211. package/tests/vector-store/memory-vector-store.test.ts +84 -0
  212. package/tests/interceptor/perf/interceptor-performance.test.ts +0 -340
  213. package/tests/perf/optimization.test.ts +0 -182
  214. package/tests/perf/regression.test.ts +0 -120
@@ -0,0 +1,58 @@
1
+ import { Module, MODULE_METADATA_KEY } from '../di/module';
2
+ import type { ModuleProvider } from '../di/module';
3
+ import { McpServer } from './server';
4
+ import { McpRegistry } from './registry';
5
+ import { MCP_SERVER_TOKEN, MCP_OPTIONS_TOKEN, type McpModuleOptions } from './types';
6
+
7
+ @Module({ providers: [] })
8
+ export class McpModule {
9
+ /**
10
+ * Configure the MCP module.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * McpModule.forRoot({
15
+ * transport: 'sse',
16
+ * path: '/mcp',
17
+ * serverInfo: { name: 'my-api', version: '1.0.0' },
18
+ * });
19
+ *
20
+ * // Register tools by scanning instances:
21
+ * const registry = container.resolve<McpRegistry>(MCP_SERVER_TOKEN);
22
+ * registry.scan(myServiceInstance);
23
+ * ```
24
+ */
25
+ public static forRoot(options: McpModuleOptions): typeof McpModule {
26
+ const registry = new McpRegistry();
27
+ const server = new McpServer(registry, options.serverInfo);
28
+
29
+ const resolvedOptions: McpModuleOptions = {
30
+ transport: 'sse',
31
+ path: '/mcp',
32
+ ...options,
33
+ };
34
+
35
+ const providers: ModuleProvider[] = [
36
+ { provide: MCP_OPTIONS_TOKEN, useValue: resolvedOptions },
37
+ { provide: MCP_SERVER_TOKEN, useValue: server },
38
+ { provide: McpRegistry, useValue: registry },
39
+ ];
40
+
41
+ const existing = Reflect.getMetadata(MODULE_METADATA_KEY, McpModule) || {};
42
+ Reflect.defineMetadata(MODULE_METADATA_KEY, {
43
+ ...existing,
44
+ providers: [...(existing.providers || []), ...providers],
45
+ exports: [
46
+ ...(existing.exports || []),
47
+ MCP_SERVER_TOKEN,
48
+ McpRegistry,
49
+ ],
50
+ }, McpModule);
51
+
52
+ return McpModule;
53
+ }
54
+
55
+ public static reset(): void {
56
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, McpModule);
57
+ }
58
+ }
@@ -0,0 +1,72 @@
1
+ import type { McpToolDefinition, McpResourceDefinition } from './types';
2
+ import { MCP_TOOL_METADATA_KEY, MCP_RESOURCE_METADATA_KEY } from './types';
3
+
4
+ export interface RegisteredMcpTool extends McpToolDefinition {
5
+ execute(args: Record<string, unknown>): Promise<unknown>;
6
+ }
7
+
8
+ export interface RegisteredMcpResource extends McpResourceDefinition {
9
+ read(uriParams: Record<string, string>): Promise<unknown>;
10
+ }
11
+
12
+ /**
13
+ * Registry for MCP tools and resources discovered via decorators.
14
+ */
15
+ export class McpRegistry {
16
+ private readonly tools = new Map<string, RegisteredMcpTool>();
17
+ private readonly resources = new Map<string, RegisteredMcpResource>();
18
+
19
+ /**
20
+ * Scan an object instance for @McpTool() and @McpResource() decorated methods
21
+ */
22
+ public scan(instance: object): void {
23
+ const proto = Object.getPrototypeOf(instance);
24
+ const methodNames = Object.getOwnPropertyNames(proto).filter(
25
+ (key) => key !== 'constructor',
26
+ );
27
+
28
+ for (const methodName of methodNames) {
29
+ const toolDef: McpToolDefinition | undefined = Reflect.getMetadata(
30
+ MCP_TOOL_METADATA_KEY,
31
+ proto,
32
+ methodName,
33
+ );
34
+ if (toolDef) {
35
+ const method = (instance as Record<string, unknown>)[methodName] as (args: unknown) => Promise<unknown>;
36
+ this.tools.set(toolDef.name, {
37
+ ...toolDef,
38
+ execute: (args) => method.call(instance, args),
39
+ });
40
+ }
41
+
42
+ const resourceDef: McpResourceDefinition | undefined = Reflect.getMetadata(
43
+ MCP_RESOURCE_METADATA_KEY,
44
+ proto,
45
+ methodName,
46
+ );
47
+ if (resourceDef) {
48
+ const method = (instance as Record<string, unknown>)[methodName] as (params: unknown) => Promise<unknown>;
49
+ this.resources.set(resourceDef.uri, {
50
+ ...resourceDef,
51
+ read: (params) => method.call(instance, params),
52
+ });
53
+ }
54
+ }
55
+ }
56
+
57
+ public getTools(): RegisteredMcpTool[] {
58
+ return Array.from(this.tools.values());
59
+ }
60
+
61
+ public getResources(): RegisteredMcpResource[] {
62
+ return Array.from(this.resources.values());
63
+ }
64
+
65
+ public getTool(name: string): RegisteredMcpTool | undefined {
66
+ return this.tools.get(name);
67
+ }
68
+
69
+ public getResource(uri: string): RegisteredMcpResource | undefined {
70
+ return this.resources.get(uri);
71
+ }
72
+ }
@@ -0,0 +1,164 @@
1
+ import type { McpServerInfo, JsonRpcRequest, JsonRpcResponse } from './types';
2
+ import type { McpRegistry } from './registry';
3
+
4
+ /**
5
+ * MCP protocol server — handles JSON-RPC 2.0 requests from MCP clients.
6
+ * Supports SSE transport (HTTP) and stdio transport.
7
+ *
8
+ * Implements MCP specification 2024-11-05.
9
+ */
10
+ export class McpServer {
11
+ private readonly registry: McpRegistry;
12
+ private readonly serverInfo: McpServerInfo;
13
+
14
+ public constructor(registry: McpRegistry, serverInfo: McpServerInfo) {
15
+ this.registry = registry;
16
+ this.serverInfo = serverInfo;
17
+ }
18
+
19
+ /**
20
+ * Handle an incoming JSON-RPC request and return a response
21
+ */
22
+ public async handle(request: JsonRpcRequest): Promise<JsonRpcResponse> {
23
+ try {
24
+ const result = await this.dispatch(request.method, request.params);
25
+ return { jsonrpc: '2.0', id: request.id ?? null, result };
26
+ } catch (err) {
27
+ return {
28
+ jsonrpc: '2.0',
29
+ id: request.id ?? null,
30
+ error: {
31
+ code: err instanceof McpError ? err.code : -32603,
32
+ message: err instanceof Error ? err.message : 'Internal error',
33
+ },
34
+ };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Handle a raw HTTP request body (JSON-RPC over HTTP)
40
+ * Returns a Response suitable for Bun's serve()
41
+ */
42
+ public async handleHttp(req: Request): Promise<Response> {
43
+ const body = await req.json() as JsonRpcRequest;
44
+ const response = await this.handle(body);
45
+ return new Response(JSON.stringify(response), {
46
+ headers: { 'Content-Type': 'application/json' },
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Create an SSE response that keeps the connection open for streaming
52
+ * This is the SSE transport endpoint
53
+ */
54
+ public createSseResponse(): Response {
55
+ const registry = this.registry;
56
+ const serverInfo = this.serverInfo;
57
+ const encoder = new TextEncoder();
58
+
59
+ const stream = new ReadableStream({
60
+ start(controller) {
61
+ // Send initial server info event
62
+ const initEvent = `event: endpoint\ndata: ${JSON.stringify({
63
+ type: 'endpoint',
64
+ method: 'POST',
65
+ })}\n\n`;
66
+ controller.enqueue(encoder.encode(initEvent));
67
+
68
+ // Keep connection alive with periodic pings
69
+ const pingInterval = setInterval(() => {
70
+ try {
71
+ controller.enqueue(encoder.encode(': ping\n\n'));
72
+ } catch {
73
+ clearInterval(pingInterval);
74
+ }
75
+ }, 15000);
76
+ },
77
+ });
78
+
79
+ return new Response(stream, {
80
+ headers: {
81
+ 'Content-Type': 'text/event-stream',
82
+ 'Cache-Control': 'no-cache',
83
+ 'Connection': 'keep-alive',
84
+ 'X-MCP-Server': `${serverInfo.name}/${serverInfo.version}`,
85
+ },
86
+ });
87
+ }
88
+
89
+ private async dispatch(method: string, params: unknown): Promise<unknown> {
90
+ switch (method) {
91
+ case 'initialize':
92
+ return {
93
+ protocolVersion: '2024-11-05',
94
+ capabilities: { tools: {}, resources: {} },
95
+ serverInfo: this.serverInfo,
96
+ };
97
+
98
+ case 'tools/list':
99
+ return {
100
+ tools: this.registry.getTools().map((t) => ({
101
+ name: t.name,
102
+ description: t.description,
103
+ inputSchema: t.inputSchema,
104
+ })),
105
+ };
106
+
107
+ case 'tools/call': {
108
+ const { name, arguments: args = {} } = params as { name: string; arguments?: Record<string, unknown> };
109
+ const tool = this.registry.getTool(name);
110
+ if (!tool) throw new McpError(-32601, `Tool "${name}" not found`);
111
+ const result = await tool.execute(args);
112
+ return {
113
+ content: [
114
+ {
115
+ type: 'text',
116
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
117
+ },
118
+ ],
119
+ };
120
+ }
121
+
122
+ case 'resources/list':
123
+ return {
124
+ resources: this.registry.getResources().map((r) => ({
125
+ uri: r.uri,
126
+ name: r.name,
127
+ description: r.description,
128
+ mimeType: r.mimeType,
129
+ })),
130
+ };
131
+
132
+ case 'resources/read': {
133
+ const { uri } = params as { uri: string };
134
+ const resource = this.registry.getResource(uri);
135
+ if (!resource) throw new McpError(-32601, `Resource "${uri}" not found`);
136
+ const content = await resource.read({});
137
+ return {
138
+ contents: [
139
+ {
140
+ uri,
141
+ mimeType: resource.mimeType ?? 'application/json',
142
+ text: typeof content === 'string' ? content : JSON.stringify(content),
143
+ },
144
+ ],
145
+ };
146
+ }
147
+
148
+ case 'ping':
149
+ return {};
150
+
151
+ default:
152
+ throw new McpError(-32601, `Method "${method}" not found`);
153
+ }
154
+ }
155
+ }
156
+
157
+ class McpError extends Error {
158
+ public constructor(
159
+ public readonly code: number,
160
+ message: string,
161
+ ) {
162
+ super(message);
163
+ }
164
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * MCP tool definition
3
+ */
4
+ export interface McpToolDefinition {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: Record<string, unknown>;
8
+ }
9
+
10
+ /**
11
+ * MCP resource definition
12
+ */
13
+ export interface McpResourceDefinition {
14
+ uri: string;
15
+ name: string;
16
+ description?: string;
17
+ mimeType?: string;
18
+ }
19
+
20
+ /**
21
+ * MCP server info
22
+ */
23
+ export interface McpServerInfo {
24
+ name: string;
25
+ version: string;
26
+ description?: string;
27
+ }
28
+
29
+ /**
30
+ * MCP transport type
31
+ */
32
+ export type McpTransport = 'sse' | 'stdio';
33
+
34
+ export interface McpModuleOptions {
35
+ transport?: McpTransport;
36
+ /** Path for SSE endpoint (default: '/mcp') */
37
+ path?: string;
38
+ serverInfo: McpServerInfo;
39
+ }
40
+
41
+ export const MCP_SERVER_TOKEN = Symbol('@dangao/bun-server:mcp:server');
42
+ export const MCP_OPTIONS_TOKEN = Symbol('@dangao/bun-server:mcp:options');
43
+
44
+ /** Metadata key for @McpTool() */
45
+ export const MCP_TOOL_METADATA_KEY = '@dangao/bun-server:mcp:tool';
46
+ /** Metadata key for @McpResource() */
47
+ export const MCP_RESOURCE_METADATA_KEY = '@dangao/bun-server:mcp:resource';
48
+
49
+ /** JSON-RPC 2.0 Request */
50
+ export interface JsonRpcRequest {
51
+ jsonrpc: '2.0';
52
+ id: string | number | null;
53
+ method: string;
54
+ params?: unknown;
55
+ }
56
+
57
+ /** JSON-RPC 2.0 Response */
58
+ export interface JsonRpcResponse {
59
+ jsonrpc: '2.0';
60
+ id: string | number | null;
61
+ result?: unknown;
62
+ error?: { code: number; message: string; data?: unknown };
63
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types';
2
+ export * from './service';
3
+ export * from './prompt-module';
4
+ export * from './stores/memory-store';
5
+ export * from './stores/file-store';
@@ -0,0 +1,61 @@
1
+ import { Module, MODULE_METADATA_KEY } from '../di/module';
2
+ import type { ModuleProvider } from '../di/module';
3
+ import { PromptService } from './service';
4
+ import {
5
+ PROMPT_SERVICE_TOKEN,
6
+ PROMPT_OPTIONS_TOKEN,
7
+ type PromptModuleOptions,
8
+ } from './types';
9
+ import { InMemoryPromptStore } from './stores/memory-store';
10
+
11
+ @Module({ providers: [] })
12
+ export class PromptModule {
13
+ /**
14
+ * Configure the prompt module.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * // With in-memory store (default):
19
+ * PromptModule.forRoot({});
20
+ *
21
+ * // With file store:
22
+ * PromptModule.forRoot({
23
+ * store: new FilePromptStore({ promptsDir: './.prompts' }),
24
+ * });
25
+ * ```
26
+ */
27
+ public static forRoot(options: PromptModuleOptions = {}): typeof PromptModule {
28
+ const resolvedOptions: PromptModuleOptions = {
29
+ ...options,
30
+ store: options.store ?? new InMemoryPromptStore(),
31
+ };
32
+
33
+ const service = new PromptService(resolvedOptions as Required<PromptModuleOptions>);
34
+
35
+ const providers: ModuleProvider[] = [
36
+ { provide: PROMPT_OPTIONS_TOKEN, useValue: resolvedOptions },
37
+ { provide: PROMPT_SERVICE_TOKEN, useValue: service },
38
+ PromptService,
39
+ ];
40
+
41
+ const existing = Reflect.getMetadata(MODULE_METADATA_KEY, PromptModule) || {};
42
+ Reflect.defineMetadata(MODULE_METADATA_KEY, {
43
+ ...existing,
44
+ providers: [...(existing.providers || []), ...providers],
45
+ exports: [
46
+ ...(existing.exports || []),
47
+ PROMPT_SERVICE_TOKEN,
48
+ PromptService,
49
+ ],
50
+ }, PromptModule);
51
+
52
+ return PromptModule;
53
+ }
54
+
55
+ /**
56
+ * Reset module state (for testing)
57
+ */
58
+ public static reset(): void {
59
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, PromptModule);
60
+ }
61
+ }
@@ -0,0 +1,93 @@
1
+ import { Injectable } from '../di/decorators';
2
+ import { Inject } from '../di/decorators';
3
+ import type {
4
+ PromptStore,
5
+ PromptTemplate,
6
+ CreatePromptTemplateInput,
7
+ UpdatePromptTemplateInput,
8
+ PromptModuleOptions,
9
+ } from './types';
10
+ import { PROMPT_OPTIONS_TOKEN, renderTemplate } from './types';
11
+ import { HttpException } from '../error/http-exception';
12
+
13
+ /**
14
+ * Manages prompt templates: CRUD operations, versioning, and variable rendering.
15
+ */
16
+ @Injectable()
17
+ export class PromptService {
18
+ private readonly store: PromptStore;
19
+
20
+ public constructor(
21
+ @Inject(PROMPT_OPTIONS_TOKEN) options: PromptModuleOptions,
22
+ ) {
23
+ this.store = options.store!;
24
+ }
25
+
26
+ /**
27
+ * Get a template by ID
28
+ */
29
+ public async get(id: string): Promise<PromptTemplate> {
30
+ const template = await this.store.get(id);
31
+ if (!template) throw new HttpException(404, `Prompt template "${id}" not found`);
32
+ return template;
33
+ }
34
+
35
+ /**
36
+ * Get a specific version of a template
37
+ */
38
+ public async getVersion(id: string, version: number): Promise<PromptTemplate> {
39
+ const template = await this.store.getVersion(id, version);
40
+ if (!template) throw new HttpException(404, `Prompt template "${id}" version ${version} not found`);
41
+ return template;
42
+ }
43
+
44
+ /**
45
+ * List all templates
46
+ */
47
+ public async list(): Promise<PromptTemplate[]> {
48
+ return this.store.list();
49
+ }
50
+
51
+ /**
52
+ * Create a new template
53
+ */
54
+ public async create(input: CreatePromptTemplateInput): Promise<PromptTemplate> {
55
+ return this.store.create(input);
56
+ }
57
+
58
+ /**
59
+ * Update a template (creates a new version)
60
+ */
61
+ public async update(id: string, input: UpdatePromptTemplateInput): Promise<PromptTemplate> {
62
+ return this.store.update(id, input);
63
+ }
64
+
65
+ /**
66
+ * Delete a template
67
+ */
68
+ public async delete(id: string): Promise<boolean> {
69
+ return this.store.delete(id);
70
+ }
71
+
72
+ /**
73
+ * Render a template by replacing {{varName}} with provided values
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const rendered = await promptService.render('greet-user', { name: 'Alice', app: 'MyApp' });
78
+ * // "Hello, Alice! Welcome to MyApp."
79
+ * ```
80
+ */
81
+ public async render(id: string, vars: Record<string, string>): Promise<string> {
82
+ const template = await this.get(id);
83
+ return renderTemplate(template.content, vars);
84
+ }
85
+
86
+ /**
87
+ * Render a specific version of a template
88
+ */
89
+ public async renderVersion(id: string, version: number, vars: Record<string, string>): Promise<string> {
90
+ const template = await this.getVersion(id, version);
91
+ return renderTemplate(template.content, vars);
92
+ }
93
+ }
@@ -0,0 +1,135 @@
1
+ import type {
2
+ PromptStore,
3
+ PromptTemplate,
4
+ CreatePromptTemplateInput,
5
+ UpdatePromptTemplateInput,
6
+ } from '../types';
7
+ import { extractVariables } from '../types';
8
+ import { InMemoryPromptStore } from './memory-store';
9
+
10
+ export interface FilePromptStoreConfig {
11
+ /** Directory containing JSON prompt files (default: './.prompts') */
12
+ promptsDir?: string;
13
+ }
14
+
15
+ /**
16
+ * File-backed prompt store — loads templates from JSON files at startup
17
+ * and persists changes back to disk.
18
+ *
19
+ * Each template is stored as a separate JSON file: `{promptsDir}/{id}.json`
20
+ *
21
+ * File format:
22
+ * ```json
23
+ * {
24
+ * "id": "greet-user",
25
+ * "name": "Greet User",
26
+ * "content": "Hello, {{name}}! Welcome to {{app}}.",
27
+ * "description": "Greeting template"
28
+ * }
29
+ * ```
30
+ */
31
+ export class FilePromptStore implements PromptStore {
32
+ private readonly promptsDir: string;
33
+ private readonly memory: InMemoryPromptStore;
34
+ private loaded = false;
35
+
36
+ public constructor(config: FilePromptStoreConfig = {}) {
37
+ this.promptsDir = config.promptsDir ?? './.prompts';
38
+ this.memory = new InMemoryPromptStore();
39
+ }
40
+
41
+ public async get(id: string): Promise<PromptTemplate | null> {
42
+ await this.ensureLoaded();
43
+ return this.memory.get(id);
44
+ }
45
+
46
+ public async getVersion(id: string, version: number): Promise<PromptTemplate | null> {
47
+ await this.ensureLoaded();
48
+ return this.memory.getVersion(id, version);
49
+ }
50
+
51
+ public async list(): Promise<PromptTemplate[]> {
52
+ await this.ensureLoaded();
53
+ return this.memory.list();
54
+ }
55
+
56
+ public async create(input: CreatePromptTemplateInput): Promise<PromptTemplate> {
57
+ await this.ensureLoaded();
58
+ const template = await this.memory.create(input);
59
+ await this.writeFile(template);
60
+ return template;
61
+ }
62
+
63
+ public async update(id: string, input: UpdatePromptTemplateInput): Promise<PromptTemplate> {
64
+ await this.ensureLoaded();
65
+ const template = await this.memory.update(id, input);
66
+ await this.writeFile(template);
67
+ return template;
68
+ }
69
+
70
+ public async delete(id: string): Promise<boolean> {
71
+ await this.ensureLoaded();
72
+ const deleted = await this.memory.delete(id);
73
+ if (deleted) {
74
+ try {
75
+ const path = `${this.promptsDir}/${id}.json`;
76
+ await Bun.file(path).exists() && Bun.write(path, ''); // Soft delete (empty file)
77
+ } catch {
78
+ // ignore
79
+ }
80
+ }
81
+ return deleted;
82
+ }
83
+
84
+ private async ensureLoaded(): Promise<void> {
85
+ if (this.loaded) return;
86
+ this.loaded = true;
87
+
88
+ try {
89
+ const glob = new Bun.Glob('*.json');
90
+ const files = Array.from(glob.scanSync(this.promptsDir));
91
+
92
+ for (const file of files) {
93
+ try {
94
+ const content = await Bun.file(`${this.promptsDir}/${file}`).text();
95
+ if (!content.trim()) continue;
96
+
97
+ const data = JSON.parse(content) as {
98
+ id?: string;
99
+ name: string;
100
+ content: string;
101
+ description?: string;
102
+ };
103
+
104
+ const id = data.id ?? file.replace(/\.json$/, '');
105
+ await this.memory.create({ id, ...data }).catch(() => {
106
+ // Template already exists — skip
107
+ });
108
+ } catch {
109
+ // Skip malformed files
110
+ }
111
+ }
112
+ } catch {
113
+ // Directory doesn't exist — start empty
114
+ }
115
+ }
116
+
117
+ private async writeFile(template: PromptTemplate): Promise<void> {
118
+ try {
119
+ const content = JSON.stringify(
120
+ {
121
+ id: template.id,
122
+ name: template.name,
123
+ content: template.content,
124
+ description: template.description,
125
+ variables: extractVariables(template.content),
126
+ },
127
+ null,
128
+ 2,
129
+ );
130
+ await Bun.write(`${this.promptsDir}/${template.id}.json`, content);
131
+ } catch {
132
+ // Ignore write errors (e.g., read-only filesystem)
133
+ }
134
+ }
135
+ }