@buenojs/bueno 0.8.4 → 0.8.6

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 (234) hide show
  1. package/README.md +264 -17
  2. package/dist/cli/{index.js → bin.js} +413 -332
  3. package/dist/container/index.js +273 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/graphql/index.js +2156 -0
  8. package/dist/health/index.js +364 -0
  9. package/dist/i18n/index.js +345 -0
  10. package/dist/index.js +9694 -5047
  11. package/dist/jobs/index.js +819 -0
  12. package/dist/lock/index.js +367 -0
  13. package/dist/logger/index.js +281 -0
  14. package/dist/metrics/index.js +289 -0
  15. package/dist/middleware/index.js +77 -0
  16. package/dist/migrations/index.js +571 -0
  17. package/dist/modules/index.js +3411 -0
  18. package/dist/notification/index.js +484 -0
  19. package/dist/observability/index.js +331 -0
  20. package/dist/openapi/index.js +795 -0
  21. package/dist/orm/index.js +1356 -0
  22. package/dist/router/index.js +886 -0
  23. package/dist/rpc/index.js +691 -0
  24. package/dist/schema/index.js +400 -0
  25. package/dist/telemetry/index.js +595 -0
  26. package/dist/template/index.js +640 -0
  27. package/dist/templates/index.js +640 -0
  28. package/dist/testing/index.js +1111 -0
  29. package/dist/types/index.js +60 -0
  30. package/llms.txt +231 -0
  31. package/package.json +125 -27
  32. package/src/cache/index.ts +2 -1
  33. package/src/cli/ARCHITECTURE.md +3 -3
  34. package/src/cli/bin.ts +2 -2
  35. package/src/cli/commands/build.ts +183 -165
  36. package/src/cli/commands/dev.ts +96 -89
  37. package/src/cli/commands/generate.ts +142 -111
  38. package/src/cli/commands/help.ts +20 -16
  39. package/src/cli/commands/index.ts +3 -6
  40. package/src/cli/commands/migration.ts +124 -105
  41. package/src/cli/commands/new.ts +294 -232
  42. package/src/cli/commands/start.ts +81 -79
  43. package/src/cli/core/args.ts +68 -50
  44. package/src/cli/core/console.ts +89 -95
  45. package/src/cli/core/index.ts +4 -4
  46. package/src/cli/core/prompt.ts +65 -62
  47. package/src/cli/core/spinner.ts +23 -20
  48. package/src/cli/index.ts +46 -38
  49. package/src/cli/templates/database/index.ts +37 -18
  50. package/src/cli/templates/database/mysql.ts +3 -3
  51. package/src/cli/templates/database/none.ts +2 -2
  52. package/src/cli/templates/database/postgresql.ts +3 -3
  53. package/src/cli/templates/database/sqlite.ts +3 -3
  54. package/src/cli/templates/deploy.ts +29 -26
  55. package/src/cli/templates/docker.ts +41 -30
  56. package/src/cli/templates/frontend/index.ts +33 -15
  57. package/src/cli/templates/frontend/none.ts +2 -2
  58. package/src/cli/templates/frontend/react.ts +18 -18
  59. package/src/cli/templates/frontend/solid.ts +15 -15
  60. package/src/cli/templates/frontend/svelte.ts +17 -17
  61. package/src/cli/templates/frontend/vue.ts +15 -15
  62. package/src/cli/templates/generators/index.ts +29 -29
  63. package/src/cli/templates/generators/types.ts +21 -21
  64. package/src/cli/templates/index.ts +6 -6
  65. package/src/cli/templates/project/api.ts +37 -36
  66. package/src/cli/templates/project/default.ts +25 -25
  67. package/src/cli/templates/project/fullstack.ts +28 -26
  68. package/src/cli/templates/project/index.ts +55 -16
  69. package/src/cli/templates/project/minimal.ts +17 -12
  70. package/src/cli/templates/project/types.ts +10 -5
  71. package/src/cli/templates/project/website.ts +15 -15
  72. package/src/cli/utils/fs.ts +55 -41
  73. package/src/cli/utils/index.ts +3 -3
  74. package/src/cli/utils/strings.ts +47 -33
  75. package/src/cli/utils/version.ts +14 -8
  76. package/src/config/env-validation.ts +100 -0
  77. package/src/config/env.ts +169 -41
  78. package/src/config/index.ts +28 -20
  79. package/src/config/loader.ts +25 -16
  80. package/src/config/merge.ts +21 -10
  81. package/src/config/types.ts +566 -25
  82. package/src/config/validation.ts +215 -7
  83. package/src/container/forward-ref.ts +22 -22
  84. package/src/container/index.ts +34 -12
  85. package/src/context/index.ts +11 -1
  86. package/src/database/index.ts +7 -190
  87. package/src/database/orm/builder.ts +457 -0
  88. package/src/database/orm/casts/index.ts +130 -0
  89. package/src/database/orm/casts/types.ts +25 -0
  90. package/src/database/orm/compiler.ts +304 -0
  91. package/src/database/orm/hooks/index.ts +114 -0
  92. package/src/database/orm/index.ts +61 -0
  93. package/src/database/orm/model-registry.ts +59 -0
  94. package/src/database/orm/model.ts +821 -0
  95. package/src/database/orm/relationships/base.ts +146 -0
  96. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  97. package/src/database/orm/relationships/belongs-to.ts +56 -0
  98. package/src/database/orm/relationships/has-many.ts +45 -0
  99. package/src/database/orm/relationships/has-one.ts +41 -0
  100. package/src/database/orm/relationships/index.ts +11 -0
  101. package/src/database/orm/scopes/index.ts +55 -0
  102. package/src/events/__tests__/event-system.test.ts +235 -0
  103. package/src/events/config.ts +238 -0
  104. package/src/events/example-usage.ts +185 -0
  105. package/src/events/index.ts +278 -0
  106. package/src/events/manager.ts +385 -0
  107. package/src/events/registry.ts +182 -0
  108. package/src/events/types.ts +124 -0
  109. package/src/frontend/api-routes.ts +65 -23
  110. package/src/frontend/bundler.ts +76 -34
  111. package/src/frontend/console-client.ts +2 -2
  112. package/src/frontend/console-stream.ts +94 -38
  113. package/src/frontend/dev-server.ts +94 -46
  114. package/src/frontend/file-router.ts +61 -19
  115. package/src/frontend/frameworks/index.ts +37 -10
  116. package/src/frontend/frameworks/react.ts +10 -8
  117. package/src/frontend/frameworks/solid.ts +11 -9
  118. package/src/frontend/frameworks/svelte.ts +15 -9
  119. package/src/frontend/frameworks/vue.ts +13 -11
  120. package/src/frontend/hmr-client.ts +12 -10
  121. package/src/frontend/hmr.ts +146 -103
  122. package/src/frontend/index.ts +14 -5
  123. package/src/frontend/islands.ts +41 -22
  124. package/src/frontend/isr.ts +59 -37
  125. package/src/frontend/layout.ts +36 -21
  126. package/src/frontend/ssr/react.ts +74 -27
  127. package/src/frontend/ssr/solid.ts +54 -20
  128. package/src/frontend/ssr/svelte.ts +48 -14
  129. package/src/frontend/ssr/vue.ts +50 -18
  130. package/src/frontend/ssr.ts +83 -39
  131. package/src/frontend/types.ts +91 -56
  132. package/src/graphql/built-in-engine.ts +598 -0
  133. package/src/graphql/context-builder.ts +110 -0
  134. package/src/graphql/decorators.ts +358 -0
  135. package/src/graphql/execution-pipeline.ts +227 -0
  136. package/src/graphql/graphql-module.ts +563 -0
  137. package/src/graphql/index.ts +101 -0
  138. package/src/graphql/metadata.ts +237 -0
  139. package/src/graphql/schema-builder.ts +319 -0
  140. package/src/graphql/subscription-handler.ts +283 -0
  141. package/src/graphql/types.ts +324 -0
  142. package/src/health/index.ts +21 -9
  143. package/src/i18n/engine.ts +305 -0
  144. package/src/i18n/index.ts +38 -0
  145. package/src/i18n/loader.ts +218 -0
  146. package/src/i18n/middleware.ts +164 -0
  147. package/src/i18n/negotiator.ts +162 -0
  148. package/src/i18n/types.ts +158 -0
  149. package/src/index.ts +182 -27
  150. package/src/jobs/drivers/memory.ts +315 -0
  151. package/src/jobs/drivers/redis.ts +459 -0
  152. package/src/jobs/index.ts +30 -0
  153. package/src/jobs/queue.ts +281 -0
  154. package/src/jobs/types.ts +295 -0
  155. package/src/jobs/worker.ts +380 -0
  156. package/src/logger/index.ts +1 -3
  157. package/src/logger/transports/index.ts +62 -22
  158. package/src/metrics/index.ts +25 -16
  159. package/src/migrations/index.ts +9 -0
  160. package/src/modules/filters.ts +13 -17
  161. package/src/modules/guards.ts +49 -26
  162. package/src/modules/index.ts +457 -299
  163. package/src/modules/interceptors.ts +58 -20
  164. package/src/modules/lazy.ts +11 -19
  165. package/src/modules/lifecycle.ts +15 -7
  166. package/src/modules/metadata.ts +15 -5
  167. package/src/modules/pipes.ts +94 -72
  168. package/src/notification/channels/base.ts +68 -0
  169. package/src/notification/channels/email.ts +105 -0
  170. package/src/notification/channels/push.ts +104 -0
  171. package/src/notification/channels/sms.ts +105 -0
  172. package/src/notification/channels/whatsapp.ts +104 -0
  173. package/src/notification/index.ts +48 -0
  174. package/src/notification/service.ts +354 -0
  175. package/src/notification/types.ts +344 -0
  176. package/src/observability/__tests__/observability.test.ts +483 -0
  177. package/src/observability/breadcrumbs.ts +114 -0
  178. package/src/observability/index.ts +136 -0
  179. package/src/observability/interceptor.ts +85 -0
  180. package/src/observability/service.ts +303 -0
  181. package/src/observability/trace.ts +37 -0
  182. package/src/observability/types.ts +196 -0
  183. package/src/openapi/__tests__/decorators.test.ts +335 -0
  184. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  185. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  186. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  187. package/src/openapi/decorators.ts +328 -0
  188. package/src/openapi/document-builder.ts +274 -0
  189. package/src/openapi/index.ts +112 -0
  190. package/src/openapi/metadata.ts +112 -0
  191. package/src/openapi/route-scanner.ts +289 -0
  192. package/src/openapi/schema-generator.ts +256 -0
  193. package/src/openapi/swagger-module.ts +166 -0
  194. package/src/openapi/types.ts +398 -0
  195. package/src/orm/index.ts +10 -0
  196. package/src/rpc/index.ts +3 -1
  197. package/src/schema/index.ts +9 -0
  198. package/src/security/index.ts +15 -6
  199. package/src/ssg/index.ts +9 -8
  200. package/src/telemetry/index.ts +76 -22
  201. package/src/template/index.ts +7 -0
  202. package/src/templates/engine.ts +224 -0
  203. package/src/templates/index.ts +9 -0
  204. package/src/templates/loader.ts +331 -0
  205. package/src/templates/renderers/markdown.ts +212 -0
  206. package/src/templates/renderers/simple.ts +269 -0
  207. package/src/templates/types.ts +154 -0
  208. package/src/testing/index.ts +100 -27
  209. package/src/types/optional-deps.d.ts +347 -187
  210. package/src/validation/index.ts +92 -2
  211. package/src/validation/schemas.ts +536 -0
  212. package/tests/integration/cli.test.ts +19 -19
  213. package/tests/integration/fullstack.test.ts +4 -4
  214. package/tests/unit/cli.test.ts +1 -1
  215. package/tests/unit/database.test.ts +2 -72
  216. package/tests/unit/env-validation.test.ts +166 -0
  217. package/tests/unit/events.test.ts +910 -0
  218. package/tests/unit/graphql.test.ts +991 -0
  219. package/tests/unit/i18n.test.ts +455 -0
  220. package/tests/unit/jobs.test.ts +493 -0
  221. package/tests/unit/notification.test.ts +988 -0
  222. package/tests/unit/observability.test.ts +453 -0
  223. package/tests/unit/orm/builder.test.ts +323 -0
  224. package/tests/unit/orm/casts.test.ts +179 -0
  225. package/tests/unit/orm/compiler.test.ts +220 -0
  226. package/tests/unit/orm/eager-loading.test.ts +285 -0
  227. package/tests/unit/orm/hooks.test.ts +191 -0
  228. package/tests/unit/orm/model.test.ts +373 -0
  229. package/tests/unit/orm/relationships.test.ts +303 -0
  230. package/tests/unit/orm/scopes.test.ts +74 -0
  231. package/tests/unit/templates-simple.test.ts +53 -0
  232. package/tests/unit/templates.test.ts +454 -0
  233. package/tests/unit/validation.test.ts +18 -24
  234. package/tsconfig.json +11 -3
@@ -0,0 +1,283 @@
1
+ /**
2
+ * GraphQL Subscription Handler
3
+ *
4
+ * Implements the graphql-transport-ws protocol over Bun's native WebSocket.
5
+ * Registered via app.setWebSocketHandler() so subscriptions run on the same
6
+ * port as the HTTP server — no separate port needed.
7
+ *
8
+ * Protocol spec: https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
9
+ *
10
+ * ## Usage
11
+ * Automatically configured by GraphQLModule.setup() when subscriptions: true.
12
+ * Requires an engine with supportsSubscriptions = true (e.g. GraphQLJsAdapter).
13
+ */
14
+
15
+ import type { GraphQLEngine, GraphQLContext, ResolvedSchema } from "./types";
16
+ import type { Container } from "../container";
17
+ import type { Guard, Interceptor } from "./execution-pipeline";
18
+ import { buildGraphQLContext } from "./context-builder";
19
+
20
+ // ============= Protocol Message Types =============
21
+
22
+ interface ConnectionInitMessage {
23
+ type: "connection_init";
24
+ payload?: Record<string, unknown>;
25
+ }
26
+ interface ConnectionAckMessage {
27
+ type: "connection_ack";
28
+ }
29
+ interface SubscribeMessage {
30
+ type: "subscribe";
31
+ id: string;
32
+ payload: {
33
+ query: string;
34
+ variables?: Record<string, unknown>;
35
+ operationName?: string;
36
+ };
37
+ }
38
+ interface NextMessage {
39
+ type: "next";
40
+ id: string;
41
+ payload: { data?: unknown; errors?: unknown[] };
42
+ }
43
+ interface ErrorMessage {
44
+ type: "error";
45
+ id: string;
46
+ payload: Array<{ message: string }>;
47
+ }
48
+ interface CompleteMessage {
49
+ type: "complete";
50
+ id?: string;
51
+ }
52
+
53
+ type ServerMessage = ConnectionAckMessage | NextMessage | ErrorMessage | CompleteMessage;
54
+ type ClientMessage = ConnectionInitMessage | SubscribeMessage | CompleteMessage;
55
+
56
+ // ============= Connection State =============
57
+
58
+ interface SubscriptionState {
59
+ generator: AsyncGenerator<unknown>;
60
+ }
61
+
62
+ interface ConnectionState {
63
+ subscriptions: Map<string, SubscriptionState>;
64
+ initialized: boolean;
65
+ }
66
+
67
+ // ============= WebSocket Data =============
68
+
69
+ interface WsData {
70
+ connectionId: string;
71
+ }
72
+
73
+ // ============= Subscription Handler =============
74
+
75
+ export class SubscriptionHandler {
76
+ private connections = new Map<string, ConnectionState>();
77
+
78
+ constructor(
79
+ private engine: GraphQLEngine,
80
+ private engineSchema: unknown,
81
+ private graphqlPath: string,
82
+ private container: Container,
83
+ private globalGuards: Guard[],
84
+ private globalInterceptors: Interceptor[],
85
+ ) {}
86
+
87
+ /**
88
+ * Returns a Bun WebSocketHandler to register on the server via app.setWebSocketHandler().
89
+ * Attaches an __upgradeHandler so Application.listen() can delegate WebSocket upgrades.
90
+ */
91
+ getWebSocketConfig(): Bun.WebSocketHandler<WsData> & { __upgradeHandler: (req: Request, srv: Bun.Server) => Response | undefined } {
92
+ const upgradeHandler = this.handleUpgrade.bind(this);
93
+ return {
94
+ __upgradeHandler: upgradeHandler,
95
+ open: (ws) => {
96
+ this.connections.set(ws.data.connectionId, {
97
+ subscriptions: new Map(),
98
+ initialized: false,
99
+ });
100
+ },
101
+
102
+ message: async (ws, raw) => {
103
+ try {
104
+ const text =
105
+ typeof raw === "string" ? raw : new TextDecoder().decode(raw as ArrayBuffer);
106
+ const msg = JSON.parse(text) as ClientMessage;
107
+ await this.handleMessage(ws, msg);
108
+ } catch (err) {
109
+ this.send(ws, {
110
+ type: "error",
111
+ id: "",
112
+ payload: [{ message: `Protocol error: ${(err as Error).message}` }],
113
+ });
114
+ }
115
+ },
116
+
117
+ close: (ws) => {
118
+ const state = this.connections.get(ws.data.connectionId);
119
+ if (state) {
120
+ // Cancel all active subscriptions
121
+ for (const sub of state.subscriptions.values()) {
122
+ sub.generator.return?.(undefined);
123
+ }
124
+ this.connections.delete(ws.data.connectionId);
125
+ }
126
+ },
127
+
128
+ error: (ws, error) => {
129
+ console.error("[GraphQL WS] WebSocket error:", error);
130
+ },
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Middleware-like upgrade handler.
136
+ * Call this from the app's fetch function for WebSocket upgrade requests.
137
+ * Returns a Response if not a GraphQL WS path, undefined if upgraded.
138
+ */
139
+ handleUpgrade(req: Request, server: Bun.Server): Response | undefined {
140
+ const url = new URL(req.url);
141
+ if (url.pathname !== this.graphqlPath) {
142
+ return undefined; // Not our path — let normal routing handle it
143
+ }
144
+
145
+ const protocol = req.headers.get("sec-websocket-protocol") ?? "";
146
+ if (!protocol.includes("graphql-transport-ws")) {
147
+ return new Response("Unsupported WebSocket protocol", { status: 426 });
148
+ }
149
+
150
+ const connectionId = crypto.randomUUID();
151
+ const upgraded = server.upgrade<WsData>(req, {
152
+ headers: {
153
+ "Sec-WebSocket-Protocol": "graphql-transport-ws",
154
+ },
155
+ data: { connectionId },
156
+ });
157
+
158
+ return upgraded ? undefined : new Response("WebSocket upgrade failed", { status: 400 });
159
+ }
160
+
161
+ // ============= Message Handlers =============
162
+
163
+ private async handleMessage(
164
+ ws: Bun.ServerWebSocket<WsData>,
165
+ msg: ClientMessage,
166
+ ): Promise<void> {
167
+ const state = this.connections.get(ws.data.connectionId);
168
+ if (!state) return;
169
+
170
+ switch (msg.type) {
171
+ case "connection_init":
172
+ if (state.initialized) {
173
+ // Already initialized — send error and close
174
+ ws.close(4429, "Too many initialisation requests");
175
+ return;
176
+ }
177
+ state.initialized = true;
178
+ this.send(ws, { type: "connection_ack" });
179
+ break;
180
+
181
+ case "subscribe":
182
+ if (!state.initialized) {
183
+ ws.close(4401, "Unauthorized: connection not initialized");
184
+ return;
185
+ }
186
+ if (state.subscriptions.has(msg.id)) {
187
+ ws.close(4409, `Subscriber for ${msg.id} already exists`);
188
+ return;
189
+ }
190
+ await this.handleSubscribe(ws, state, msg);
191
+ break;
192
+
193
+ case "complete":
194
+ if (msg.id) {
195
+ const sub = state.subscriptions.get(msg.id);
196
+ if (sub) {
197
+ sub.generator.return?.(undefined);
198
+ state.subscriptions.delete(msg.id);
199
+ }
200
+ }
201
+ break;
202
+ }
203
+ }
204
+
205
+ private async handleSubscribe(
206
+ ws: Bun.ServerWebSocket<WsData>,
207
+ state: ConnectionState,
208
+ msg: SubscribeMessage,
209
+ ): Promise<void> {
210
+ if (!this.engine.subscribe) {
211
+ this.send(ws, {
212
+ type: "error",
213
+ id: msg.id,
214
+ payload: [
215
+ {
216
+ message:
217
+ "Subscriptions are not supported by the configured GraphQL engine. " +
218
+ "Use GraphQLJsAdapter for subscription support.",
219
+ },
220
+ ],
221
+ });
222
+ return;
223
+ }
224
+
225
+ const context: GraphQLContext = {
226
+ request: new Request(`ws://localhost${this.graphqlPath}`),
227
+ user: undefined,
228
+ httpContext: null,
229
+ };
230
+
231
+ let generator: AsyncGenerator<unknown>;
232
+ try {
233
+ generator = await this.engine.subscribe(
234
+ this.engineSchema,
235
+ msg.payload.query,
236
+ msg.payload.variables ?? {},
237
+ context,
238
+ msg.payload.operationName,
239
+ );
240
+ } catch (err) {
241
+ this.send(ws, {
242
+ type: "error",
243
+ id: msg.id,
244
+ payload: [{ message: (err as Error).message }],
245
+ });
246
+ return;
247
+ }
248
+
249
+ state.subscriptions.set(msg.id, { generator });
250
+
251
+ // Stream results
252
+ (async () => {
253
+ try {
254
+ for await (const result of generator) {
255
+ if (!state.subscriptions.has(msg.id)) break; // Cancelled
256
+ this.send(ws, {
257
+ type: "next",
258
+ id: msg.id,
259
+ payload: result as { data?: unknown; errors?: unknown[] },
260
+ });
261
+ }
262
+ // Subscription completed
263
+ this.send(ws, { type: "complete", id: msg.id });
264
+ state.subscriptions.delete(msg.id);
265
+ } catch (err) {
266
+ this.send(ws, {
267
+ type: "error",
268
+ id: msg.id,
269
+ payload: [{ message: (err as Error).message }],
270
+ });
271
+ state.subscriptions.delete(msg.id);
272
+ }
273
+ })();
274
+ }
275
+
276
+ private send(ws: Bun.ServerWebSocket<WsData>, msg: ServerMessage): void {
277
+ try {
278
+ ws.send(JSON.stringify(msg));
279
+ } catch {
280
+ // Connection may have closed — ignore
281
+ }
282
+ }
283
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * GraphQL Module Types
3
+ *
4
+ * Core type definitions for the Bueno GraphQL integration layer.
5
+ */
6
+
7
+ // ============= Constructor Type =============
8
+
9
+ export type Constructor<T = unknown> = new (...args: unknown[]) => T;
10
+
11
+ // ============= Scalar Types =============
12
+
13
+ /**
14
+ * Represents a GraphQL scalar type via its JS constructor.
15
+ * Usage: () => String, () => Number, () => Boolean, () => GraphQLID
16
+ */
17
+ export type GraphQLScalar =
18
+ | StringConstructor
19
+ | NumberConstructor
20
+ | BooleanConstructor
21
+ | typeof GraphQLID
22
+ | typeof GraphQLInt
23
+ | typeof GraphQLFloat;
24
+
25
+ /**
26
+ * A thunk that returns a type constructor or array of constructors.
27
+ * Used to avoid circular reference issues in type definitions.
28
+ *
29
+ * @example
30
+ * () => String // GraphQL String scalar
31
+ * () => Number // GraphQL Float scalar
32
+ * () => [User] // [User!]! list
33
+ * () => User // User object type
34
+ */
35
+ export type TypeFn = () => Constructor | Constructor[] | GraphQLScalar;
36
+
37
+ // ============= Scalar Sentinels =============
38
+
39
+ /**
40
+ * Sentinel class representing GraphQL ID scalar.
41
+ * Usage: @Field(() => GraphQLID)
42
+ */
43
+ export class GraphQLID {
44
+ static readonly __type = "ID";
45
+ }
46
+
47
+ /**
48
+ * Sentinel class representing GraphQL Int scalar.
49
+ * Usage: @Field(() => GraphQLInt)
50
+ */
51
+ export class GraphQLInt {
52
+ static readonly __type = "Int";
53
+ }
54
+
55
+ /**
56
+ * Sentinel class representing GraphQL Float scalar.
57
+ * Usage: @Field(() => GraphQLFloat)
58
+ */
59
+ export class GraphQLFloat {
60
+ static readonly __type = "Float";
61
+ }
62
+
63
+ // ============= Metadata Shapes =============
64
+
65
+ /** Options for @Field decorator */
66
+ export interface FieldDecoratorOptions {
67
+ /** Whether the field can be null (default: false → non-null) */
68
+ nullable?: boolean;
69
+ /** Human-readable description */
70
+ description?: string;
71
+ /** Deprecation reason */
72
+ deprecationReason?: string;
73
+ /** Default value for input types */
74
+ defaultValue?: unknown;
75
+ }
76
+
77
+ /** Options for @Query/@Mutation/@Subscription decorators */
78
+ export interface FieldOptions extends FieldDecoratorOptions {
79
+ /** GraphQL field name override (default: method name) */
80
+ name?: string;
81
+ }
82
+
83
+ /** Metadata stored per @Field-decorated property */
84
+ export interface FieldMetadata {
85
+ propertyKey: string;
86
+ typeFn: TypeFn;
87
+ nullable: boolean;
88
+ description?: string;
89
+ deprecationReason?: string;
90
+ defaultValue?: unknown;
91
+ }
92
+
93
+ /** Metadata for a single resolver method parameter */
94
+ export interface ParamMetadata {
95
+ index: number;
96
+ kind: "args" | "argsObject" | "context";
97
+ /** For @Args('name') — the specific argument name */
98
+ argName?: string;
99
+ /** For @Args('name', InputType) — the input object type */
100
+ inputTypeFn?: TypeFn;
101
+ }
102
+
103
+ /** Metadata stored per @Query/@Mutation/@Subscription method */
104
+ export interface ResolverFieldMetadata {
105
+ /** Actual method name on the class */
106
+ methodName: string;
107
+ /** GraphQL field name (defaults to methodName) */
108
+ fieldName: string;
109
+ typeFn: TypeFn;
110
+ kind: "query" | "mutation" | "subscription";
111
+ nullable: boolean;
112
+ description?: string;
113
+ deprecationReason?: string;
114
+ paramMetadata: ParamMetadata[];
115
+ }
116
+
117
+ /** Metadata stored per @Resolver-decorated class */
118
+ export interface ResolverClassMetadata {
119
+ /** GraphQL type name (default: class name) */
120
+ name: string;
121
+ }
122
+
123
+ /** Metadata stored per @ObjectType / @InputType class */
124
+ export interface TypeClassMetadata {
125
+ name: string;
126
+ kind: "object" | "input";
127
+ description?: string;
128
+ }
129
+
130
+ // ============= Schema Representation =============
131
+
132
+ /** A single resolvable field in the built schema */
133
+ export interface ResolvedField {
134
+ resolverInstance: unknown;
135
+ methodName: string;
136
+ paramMetadata: ParamMetadata[];
137
+ typeFn: TypeFn;
138
+ nullable: boolean;
139
+ }
140
+
141
+ /** Internal schema representation consumed by the built-in engine */
142
+ export interface ResolvedSchema {
143
+ queryFields: Map<string, ResolvedField>;
144
+ mutationFields: Map<string, ResolvedField>;
145
+ subscriptionFields: Map<string, ResolvedField>;
146
+ }
147
+
148
+ // ============= GraphQL Context =============
149
+
150
+ /** Context available inside resolver methods */
151
+ export interface GraphQLContext {
152
+ /** The original HTTP Request */
153
+ request: Request;
154
+ /** Authenticated user (set by auth guards via context.set('user', ...)) */
155
+ user?: unknown;
156
+ /** Reference to the HTTP Context for advanced use */
157
+ httpContext: unknown;
158
+ /** Allow arbitrary values */
159
+ [key: string]: unknown;
160
+ }
161
+
162
+ // ============= Execution Results =============
163
+
164
+ /** A GraphQL error entry in the response */
165
+ export interface GraphQLError {
166
+ message: string;
167
+ locations?: Array<{ line: number; column: number }>;
168
+ path?: Array<string | number>;
169
+ extensions?: Record<string, unknown>;
170
+ }
171
+
172
+ /** The GraphQL response envelope */
173
+ export interface GraphQLResult {
174
+ data?: Record<string, unknown> | null;
175
+ errors?: GraphQLError[];
176
+ }
177
+
178
+ // ============= Engine Interface =============
179
+
180
+ /**
181
+ * Pluggable GraphQL execution engine.
182
+ * Implement this interface to use graphql-js, GraphQL Yoga, Mercurius, etc.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * import * as graphqlJs from 'graphql';
187
+ * import { GraphQLJsAdapter } from '@buenojs/bueno/graphql';
188
+ *
189
+ * GraphQLModule.setup(app, {
190
+ * engine: new GraphQLJsAdapter(graphqlJs),
191
+ * resolvers: [UserResolver],
192
+ * });
193
+ * ```
194
+ */
195
+ export interface GraphQLEngine {
196
+ /**
197
+ * Build the internal schema representation from resolver and type metadata.
198
+ * Returns an opaque schema object that is passed back to execute().
199
+ */
200
+ buildSchema(
201
+ resolvers: ResolverFieldsByType,
202
+ types: Map<string, FieldMetadata[]>,
203
+ sdl: string,
204
+ ): unknown;
205
+
206
+ /**
207
+ * Execute a GraphQL query or mutation.
208
+ */
209
+ execute(
210
+ schema: unknown,
211
+ query: string,
212
+ variables: Record<string, unknown>,
213
+ context: GraphQLContext,
214
+ operationName?: string,
215
+ ): Promise<GraphQLResult>;
216
+
217
+ /**
218
+ * Execute a GraphQL subscription (optional).
219
+ * Returns an async generator that yields results.
220
+ */
221
+ subscribe?(
222
+ schema: unknown,
223
+ query: string,
224
+ variables: Record<string, unknown>,
225
+ context: GraphQLContext,
226
+ operationName?: string,
227
+ ): Promise<AsyncGenerator<GraphQLResult>>;
228
+
229
+ /**
230
+ * Whether this engine supports introspection queries.
231
+ * Used to determine if the GraphQL Playground should be enabled.
232
+ */
233
+ readonly supportsIntrospection: boolean;
234
+
235
+ /**
236
+ * Whether this engine supports subscriptions.
237
+ */
238
+ readonly supportsSubscriptions: boolean;
239
+ }
240
+
241
+ /** Resolver fields organized by root type */
242
+ export interface ResolverFieldsByType {
243
+ queries: Map<string, ResolvedField>;
244
+ mutations: Map<string, ResolvedField>;
245
+ subscriptions: Map<string, ResolvedField>;
246
+ }
247
+
248
+ // ============= Module Options =============
249
+
250
+ /**
251
+ * Options passed to GraphQLModule.setup()
252
+ */
253
+ export interface GraphQLModuleOptions {
254
+ /**
255
+ * Resolver classes to register.
256
+ * Dependencies are resolved from the DI container.
257
+ */
258
+ resolvers: Constructor[];
259
+
260
+ /**
261
+ * GraphQL engine adapter (default: built-in lightweight engine).
262
+ * Use GraphQLJsAdapter for full spec compliance.
263
+ */
264
+ engine?: GraphQLEngine;
265
+
266
+ /**
267
+ * HTTP path for the GraphQL endpoint (default: '/graphql').
268
+ */
269
+ path?: string;
270
+
271
+ /**
272
+ * Enable GraphiQL playground UI at GET <path>.
273
+ * Default: true when using an engine with supportsIntrospection,
274
+ * false when using the built-in engine.
275
+ * Set to true to force-enable even with the built-in engine (with warning).
276
+ */
277
+ playground?: boolean;
278
+
279
+ /**
280
+ * Serve the SDL at GET <path>/schema (default: true).
281
+ */
282
+ introspection?: boolean;
283
+
284
+ /**
285
+ * Enable WebSocket subscriptions (default: false).
286
+ * Requires an engine with supportsSubscriptions.
287
+ */
288
+ subscriptions?: boolean;
289
+
290
+ /**
291
+ * Sync @Field metadata to the OpenAPI property store (default: false).
292
+ * Enables unified types: one class works for both GraphQL and REST/OpenAPI.
293
+ * Requires @buenojs/bueno/openapi to be in scope.
294
+ */
295
+ syncOpenAPI?: boolean;
296
+
297
+ /** Maximum query complexity score (default: 1000) */
298
+ complexityLimit?: number;
299
+
300
+ /** Maximum query depth (default: 10) */
301
+ maxDepth?: number;
302
+ }
303
+
304
+ // ============= Config Interface =============
305
+
306
+ /**
307
+ * GraphQL configuration added to BuenoConfig.
308
+ */
309
+ export interface GraphQLConfig {
310
+ /** Enable GraphQL support (default: false) */
311
+ enabled?: boolean;
312
+ /** HTTP path (default: '/graphql') */
313
+ path?: string;
314
+ /** Enable playground (default: auto) */
315
+ playground?: boolean;
316
+ /** Enable introspection SDL endpoint (default: true) */
317
+ introspection?: boolean;
318
+ /** Maximum query complexity (default: 1000) */
319
+ complexityLimit?: number;
320
+ /** Maximum query depth (default: 10) */
321
+ maxDepth?: number;
322
+ /** Enable subscriptions (default: false) */
323
+ subscriptions?: boolean;
324
+ }
@@ -73,7 +73,10 @@ export interface HealthMiddlewareOptions {
73
73
  /** Whether to expose metrics in response (default: true) */
74
74
  exposeMetrics?: boolean;
75
75
  /** Initial health checks to register */
76
- checks?: Record<string, HealthCheckFn | { fn: HealthCheckFn; options?: CheckOptions }>;
76
+ checks?: Record<
77
+ string,
78
+ HealthCheckFn | { fn: HealthCheckFn; options?: CheckOptions }
79
+ >;
77
80
  /** Custom version string (default: from package.json) */
78
81
  version?: string;
79
82
  }
@@ -164,7 +167,10 @@ export class HealthCheckManager {
164
167
  check.fn(),
165
168
  new Promise<never>((_, reject) =>
166
169
  setTimeout(
167
- () => reject(new Error(`Check timed out after ${check.options.timeout}ms`)),
170
+ () =>
171
+ reject(
172
+ new Error(`Check timed out after ${check.options.timeout}ms`),
173
+ ),
168
174
  check.options.timeout,
169
175
  ),
170
176
  ),
@@ -286,9 +292,10 @@ export class HealthCheckManager {
286
292
  /**
287
293
  * Create health check middleware
288
294
  */
289
- export function createHealthMiddleware(
290
- options: HealthMiddlewareOptions = {},
291
- ): { middleware: Middleware; manager: HealthCheckManager } {
295
+ export function createHealthMiddleware(options: HealthMiddlewareOptions = {}): {
296
+ middleware: Middleware;
297
+ manager: HealthCheckManager;
298
+ } {
292
299
  const {
293
300
  healthPath = "/health",
294
301
  readyPath = "/ready",
@@ -367,7 +374,9 @@ export function createDatabaseCheck(
367
374
  return {
368
375
  status: isHealthy ? "healthy" : "unhealthy",
369
376
  latency: Date.now() - start,
370
- message: isHealthy ? "Database connection OK" : "Database health check failed",
377
+ message: isHealthy
378
+ ? "Database connection OK"
379
+ : "Database health check failed",
371
380
  };
372
381
  }
373
382
 
@@ -399,7 +408,8 @@ export function createDatabaseCheck(
399
408
  return {
400
409
  status: "unhealthy",
401
410
  latency: Date.now() - start,
402
- message: error instanceof Error ? error.message : "Database check failed",
411
+ message:
412
+ error instanceof Error ? error.message : "Database check failed",
403
413
  };
404
414
  }
405
415
  };
@@ -424,7 +434,9 @@ export function createCacheCheck(
424
434
  return {
425
435
  status: isHealthy ? "healthy" : "unhealthy",
426
436
  latency: Date.now() - start,
427
- message: isHealthy ? "Cache connection OK" : "Cache health check failed",
437
+ message: isHealthy
438
+ ? "Cache connection OK"
439
+ : "Cache health check failed",
428
440
  };
429
441
  }
430
442
 
@@ -601,4 +613,4 @@ export function createHTTPCheck(
601
613
  */
602
614
  export function createHealthManager(version?: string): HealthCheckManager {
603
615
  return new HealthCheckManager(version);
604
- }
616
+ }