@buenojs/bueno 0.8.5 → 0.8.7

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 (421) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/README.md +259 -15
  3. package/dist/cache/index.d.ts +187 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cli/bin.d.ts +8 -0
  6. package/dist/cli/bin.d.ts.map +1 -0
  7. package/dist/cli/bin.js +484 -156
  8. package/dist/cli/commands/add-frontend.d.ts +7 -0
  9. package/dist/cli/commands/add-frontend.d.ts.map +1 -0
  10. package/dist/cli/commands/build.d.ts +7 -0
  11. package/dist/cli/commands/build.d.ts.map +1 -0
  12. package/dist/cli/commands/dev.d.ts +7 -0
  13. package/dist/cli/commands/dev.d.ts.map +1 -0
  14. package/dist/cli/commands/generate.d.ts +7 -0
  15. package/dist/cli/commands/generate.d.ts.map +1 -0
  16. package/dist/cli/commands/help.d.ts +7 -0
  17. package/dist/cli/commands/help.d.ts.map +1 -0
  18. package/dist/cli/commands/index.d.ts +59 -0
  19. package/dist/cli/commands/index.d.ts.map +1 -0
  20. package/dist/cli/commands/migration.d.ts +7 -0
  21. package/dist/cli/commands/migration.d.ts.map +1 -0
  22. package/dist/cli/commands/new.d.ts +7 -0
  23. package/dist/cli/commands/new.d.ts.map +1 -0
  24. package/dist/cli/commands/start.d.ts +7 -0
  25. package/dist/cli/commands/start.d.ts.map +1 -0
  26. package/dist/cli/core/args.d.ts +61 -0
  27. package/dist/cli/core/args.d.ts.map +1 -0
  28. package/dist/cli/core/console.d.ts +135 -0
  29. package/dist/cli/core/console.d.ts.map +1 -0
  30. package/dist/cli/core/index.d.ts +10 -0
  31. package/dist/cli/core/index.d.ts.map +1 -0
  32. package/dist/cli/core/prompt.d.ts +63 -0
  33. package/dist/cli/core/prompt.d.ts.map +1 -0
  34. package/dist/cli/core/spinner.d.ts +111 -0
  35. package/dist/cli/core/spinner.d.ts.map +1 -0
  36. package/dist/cli/index.d.ts +47 -0
  37. package/dist/cli/index.d.ts.map +1 -0
  38. package/dist/cli/templates/database/index.d.ts +24 -0
  39. package/dist/cli/templates/database/index.d.ts.map +1 -0
  40. package/dist/cli/templates/database/mysql.d.ts +6 -0
  41. package/dist/cli/templates/database/mysql.d.ts.map +1 -0
  42. package/dist/cli/templates/database/none.d.ts +8 -0
  43. package/dist/cli/templates/database/none.d.ts.map +1 -0
  44. package/dist/cli/templates/database/postgresql.d.ts +6 -0
  45. package/dist/cli/templates/database/postgresql.d.ts.map +1 -0
  46. package/dist/cli/templates/database/sqlite.d.ts +6 -0
  47. package/dist/cli/templates/database/sqlite.d.ts.map +1 -0
  48. package/dist/cli/templates/deploy.d.ts +41 -0
  49. package/dist/cli/templates/deploy.d.ts.map +1 -0
  50. package/dist/cli/templates/docker.d.ts +30 -0
  51. package/dist/cli/templates/docker.d.ts.map +1 -0
  52. package/dist/cli/templates/frontend/index.d.ts +25 -0
  53. package/dist/cli/templates/frontend/index.d.ts.map +1 -0
  54. package/dist/cli/templates/frontend/none.d.ts +8 -0
  55. package/dist/cli/templates/frontend/none.d.ts.map +1 -0
  56. package/dist/cli/templates/frontend/react.d.ts +6 -0
  57. package/dist/cli/templates/frontend/react.d.ts.map +1 -0
  58. package/dist/cli/templates/frontend/solid.d.ts +6 -0
  59. package/dist/cli/templates/frontend/solid.d.ts.map +1 -0
  60. package/dist/cli/templates/frontend/svelte.d.ts +6 -0
  61. package/dist/cli/templates/frontend/svelte.d.ts.map +1 -0
  62. package/dist/cli/templates/frontend/vue.d.ts +6 -0
  63. package/dist/cli/templates/frontend/vue.d.ts.map +1 -0
  64. package/dist/cli/templates/generators/index.d.ts +29 -0
  65. package/dist/cli/templates/generators/index.d.ts.map +1 -0
  66. package/dist/cli/templates/generators/types.d.ts +32 -0
  67. package/dist/cli/templates/generators/types.d.ts.map +1 -0
  68. package/dist/cli/templates/index.d.ts +12 -0
  69. package/dist/cli/templates/index.d.ts.map +1 -0
  70. package/dist/cli/templates/project/api.d.ts +6 -0
  71. package/dist/cli/templates/project/api.d.ts.map +1 -0
  72. package/dist/cli/templates/project/default.d.ts +6 -0
  73. package/dist/cli/templates/project/default.d.ts.map +1 -0
  74. package/dist/cli/templates/project/fullstack.d.ts +14 -0
  75. package/dist/cli/templates/project/fullstack.d.ts.map +1 -0
  76. package/dist/cli/templates/project/index.d.ts +26 -0
  77. package/dist/cli/templates/project/index.d.ts.map +1 -0
  78. package/dist/cli/templates/project/minimal.d.ts +6 -0
  79. package/dist/cli/templates/project/minimal.d.ts.map +1 -0
  80. package/dist/cli/templates/project/types.d.ts +80 -0
  81. package/dist/cli/templates/project/types.d.ts.map +1 -0
  82. package/dist/cli/templates/project/website.d.ts +8 -0
  83. package/dist/cli/templates/project/website.d.ts.map +1 -0
  84. package/dist/cli/utils/fs.d.ts +137 -0
  85. package/dist/cli/utils/fs.d.ts.map +1 -0
  86. package/dist/cli/utils/index.d.ts +9 -0
  87. package/dist/cli/utils/index.d.ts.map +1 -0
  88. package/dist/cli/utils/strings.d.ts +86 -0
  89. package/dist/cli/utils/strings.d.ts.map +1 -0
  90. package/dist/cli/utils/version.d.ts +15 -0
  91. package/dist/cli/utils/version.d.ts.map +1 -0
  92. package/dist/config/env-validation.d.ts +49 -0
  93. package/dist/config/env-validation.d.ts.map +1 -0
  94. package/dist/config/env.d.ts +167 -0
  95. package/dist/config/env.d.ts.map +1 -0
  96. package/dist/config/index.d.ts +168 -0
  97. package/dist/config/index.d.ts.map +1 -0
  98. package/dist/config/loader.d.ts +81 -0
  99. package/dist/config/loader.d.ts.map +1 -0
  100. package/dist/config/merge.d.ts +66 -0
  101. package/dist/config/merge.d.ts.map +1 -0
  102. package/dist/config/types.d.ts +322 -0
  103. package/dist/config/types.d.ts.map +1 -0
  104. package/dist/config/validation.d.ts +100 -0
  105. package/dist/config/validation.d.ts.map +1 -0
  106. package/dist/container/forward-ref.d.ts +116 -0
  107. package/dist/container/forward-ref.d.ts.map +1 -0
  108. package/dist/container/index.d.ts +95 -0
  109. package/dist/container/index.d.ts.map +1 -0
  110. package/dist/container/index.js +26 -3
  111. package/dist/context/index.d.ts +143 -0
  112. package/dist/context/index.d.ts.map +1 -0
  113. package/dist/database/index.d.ts +219 -0
  114. package/dist/database/index.d.ts.map +1 -0
  115. package/dist/database/migrations/index.d.ts +146 -0
  116. package/dist/database/migrations/index.d.ts.map +1 -0
  117. package/dist/database/orm/builder.d.ts +122 -0
  118. package/dist/database/orm/builder.d.ts.map +1 -0
  119. package/dist/database/orm/casts/index.d.ts +16 -0
  120. package/dist/database/orm/casts/index.d.ts.map +1 -0
  121. package/dist/database/orm/casts/types.d.ts +16 -0
  122. package/dist/database/orm/casts/types.d.ts.map +1 -0
  123. package/dist/database/orm/compiler.d.ts +90 -0
  124. package/dist/database/orm/compiler.d.ts.map +1 -0
  125. package/dist/database/orm/hooks/index.d.ts +53 -0
  126. package/dist/database/orm/hooks/index.d.ts.map +1 -0
  127. package/dist/database/orm/index.d.ts +21 -0
  128. package/dist/database/orm/index.d.ts.map +1 -0
  129. package/dist/database/orm/model-registry.d.ts +33 -0
  130. package/dist/database/orm/model-registry.d.ts.map +1 -0
  131. package/dist/database/orm/model.d.ts +245 -0
  132. package/dist/database/orm/model.d.ts.map +1 -0
  133. package/dist/database/orm/relationships/base.d.ts +69 -0
  134. package/dist/database/orm/relationships/base.d.ts.map +1 -0
  135. package/dist/database/orm/relationships/belongs-to-many.d.ts +47 -0
  136. package/dist/database/orm/relationships/belongs-to-many.d.ts.map +1 -0
  137. package/dist/database/orm/relationships/belongs-to.d.ts +17 -0
  138. package/dist/database/orm/relationships/belongs-to.d.ts.map +1 -0
  139. package/dist/database/orm/relationships/has-many.d.ts +14 -0
  140. package/dist/database/orm/relationships/has-many.d.ts.map +1 -0
  141. package/dist/database/orm/relationships/has-one.d.ts +14 -0
  142. package/dist/database/orm/relationships/has-one.d.ts.map +1 -0
  143. package/dist/database/orm/relationships/index.d.ts +10 -0
  144. package/dist/database/orm/relationships/index.d.ts.map +1 -0
  145. package/dist/database/orm/scopes/index.d.ts +36 -0
  146. package/dist/database/orm/scopes/index.d.ts.map +1 -0
  147. package/dist/database/schema/index.d.ts +155 -0
  148. package/dist/database/schema/index.d.ts.map +1 -0
  149. package/dist/events/__tests__/event-system.test.d.ts +2 -0
  150. package/dist/events/__tests__/event-system.test.d.ts.map +1 -0
  151. package/dist/events/config.d.ts +16 -0
  152. package/dist/events/config.d.ts.map +1 -0
  153. package/dist/events/example-usage.d.ts +12 -0
  154. package/dist/events/example-usage.d.ts.map +1 -0
  155. package/dist/events/index.d.ts +27 -0
  156. package/dist/events/index.d.ts.map +1 -0
  157. package/dist/events/manager.d.ts +33 -0
  158. package/dist/events/manager.d.ts.map +1 -0
  159. package/dist/events/registry.d.ts +31 -0
  160. package/dist/events/registry.d.ts.map +1 -0
  161. package/dist/events/types.d.ts +105 -0
  162. package/dist/events/types.d.ts.map +1 -0
  163. package/dist/frontend/api-routes.d.ts +189 -0
  164. package/dist/frontend/api-routes.d.ts.map +1 -0
  165. package/dist/frontend/bundler.d.ts +99 -0
  166. package/dist/frontend/bundler.d.ts.map +1 -0
  167. package/dist/frontend/console-client.d.ts +11 -0
  168. package/dist/frontend/console-client.d.ts.map +1 -0
  169. package/dist/frontend/console-stream.d.ts +138 -0
  170. package/dist/frontend/console-stream.d.ts.map +1 -0
  171. package/dist/frontend/dev-server.d.ts +174 -0
  172. package/dist/frontend/dev-server.d.ts.map +1 -0
  173. package/dist/frontend/file-router.d.ts +170 -0
  174. package/dist/frontend/file-router.d.ts.map +1 -0
  175. package/dist/frontend/frameworks/index.d.ts +41 -0
  176. package/dist/frontend/frameworks/index.d.ts.map +1 -0
  177. package/dist/frontend/frameworks/react.d.ts +32 -0
  178. package/dist/frontend/frameworks/react.d.ts.map +1 -0
  179. package/dist/frontend/frameworks/solid.d.ts +42 -0
  180. package/dist/frontend/frameworks/solid.d.ts.map +1 -0
  181. package/dist/frontend/frameworks/svelte.d.ts +57 -0
  182. package/dist/frontend/frameworks/svelte.d.ts.map +1 -0
  183. package/dist/frontend/frameworks/vue.d.ts +36 -0
  184. package/dist/frontend/frameworks/vue.d.ts.map +1 -0
  185. package/dist/frontend/hmr-client.d.ts +22 -0
  186. package/dist/frontend/hmr-client.d.ts.map +1 -0
  187. package/dist/frontend/hmr.d.ts +185 -0
  188. package/dist/frontend/hmr.d.ts.map +1 -0
  189. package/dist/frontend/index.d.ts +34 -0
  190. package/dist/frontend/index.d.ts.map +1 -0
  191. package/dist/frontend/islands.d.ts +135 -0
  192. package/dist/frontend/islands.d.ts.map +1 -0
  193. package/dist/frontend/isr.d.ts +143 -0
  194. package/dist/frontend/isr.d.ts.map +1 -0
  195. package/dist/frontend/layout.d.ts +140 -0
  196. package/dist/frontend/layout.d.ts.map +1 -0
  197. package/dist/frontend/ssr/react.d.ts +118 -0
  198. package/dist/frontend/ssr/react.d.ts.map +1 -0
  199. package/dist/frontend/ssr/solid.d.ts +141 -0
  200. package/dist/frontend/ssr/solid.d.ts.map +1 -0
  201. package/dist/frontend/ssr/svelte.d.ts +158 -0
  202. package/dist/frontend/ssr/svelte.d.ts.map +1 -0
  203. package/dist/frontend/ssr/vue.d.ts +161 -0
  204. package/dist/frontend/ssr/vue.d.ts.map +1 -0
  205. package/dist/frontend/ssr.d.ts +147 -0
  206. package/dist/frontend/ssr.d.ts.map +1 -0
  207. package/dist/frontend/types.d.ts +1902 -0
  208. package/dist/frontend/types.d.ts.map +1 -0
  209. package/dist/graphql/built-in-engine.d.ts +36 -0
  210. package/dist/graphql/built-in-engine.d.ts.map +1 -0
  211. package/dist/graphql/context-builder.d.ts +44 -0
  212. package/dist/graphql/context-builder.d.ts.map +1 -0
  213. package/dist/graphql/decorators.d.ts +162 -0
  214. package/dist/graphql/decorators.d.ts.map +1 -0
  215. package/dist/graphql/execution-pipeline.d.ts +67 -0
  216. package/dist/graphql/execution-pipeline.d.ts.map +1 -0
  217. package/dist/graphql/graphql-module.d.ts +70 -0
  218. package/dist/graphql/graphql-module.d.ts.map +1 -0
  219. package/dist/graphql/index.d.ts +48 -0
  220. package/dist/graphql/index.d.ts.map +1 -0
  221. package/dist/graphql/index.js +2156 -0
  222. package/dist/graphql/metadata.d.ts +37 -0
  223. package/dist/graphql/metadata.d.ts.map +1 -0
  224. package/dist/graphql/schema-builder.d.ts +34 -0
  225. package/dist/graphql/schema-builder.d.ts.map +1 -0
  226. package/dist/graphql/subscription-handler.d.ts +47 -0
  227. package/dist/graphql/subscription-handler.d.ts.map +1 -0
  228. package/dist/graphql/types.d.ts +252 -0
  229. package/dist/graphql/types.d.ts.map +1 -0
  230. package/dist/health/index.d.ts +176 -0
  231. package/dist/health/index.d.ts.map +1 -0
  232. package/dist/i18n/engine.d.ts +105 -0
  233. package/dist/i18n/engine.d.ts.map +1 -0
  234. package/dist/i18n/index.d.ts +13 -0
  235. package/dist/i18n/index.d.ts.map +1 -0
  236. package/dist/i18n/loader.d.ts +79 -0
  237. package/dist/i18n/loader.d.ts.map +1 -0
  238. package/dist/i18n/middleware.d.ts +96 -0
  239. package/dist/i18n/middleware.d.ts.map +1 -0
  240. package/dist/i18n/negotiator.d.ts +84 -0
  241. package/dist/i18n/negotiator.d.ts.map +1 -0
  242. package/dist/i18n/types.d.ts +129 -0
  243. package/dist/i18n/types.d.ts.map +1 -0
  244. package/dist/index.d.ts +48 -0
  245. package/dist/index.d.ts.map +1 -0
  246. package/dist/index.js +520 -434
  247. package/dist/jobs/drivers/memory.d.ts +38 -0
  248. package/dist/jobs/drivers/memory.d.ts.map +1 -0
  249. package/dist/jobs/drivers/redis.d.ts +34 -0
  250. package/dist/jobs/drivers/redis.d.ts.map +1 -0
  251. package/dist/jobs/index.d.ts +12 -0
  252. package/dist/jobs/index.d.ts.map +1 -0
  253. package/dist/jobs/queue.d.ts +93 -0
  254. package/dist/jobs/queue.d.ts.map +1 -0
  255. package/dist/jobs/types.d.ts +193 -0
  256. package/dist/jobs/types.d.ts.map +1 -0
  257. package/dist/jobs/worker.d.ts +91 -0
  258. package/dist/jobs/worker.d.ts.map +1 -0
  259. package/dist/lock/index.d.ts +141 -0
  260. package/dist/lock/index.d.ts.map +1 -0
  261. package/dist/logger/index.d.ts +156 -0
  262. package/dist/logger/index.d.ts.map +1 -0
  263. package/dist/logger/transports/index.d.ts +371 -0
  264. package/dist/logger/transports/index.d.ts.map +1 -0
  265. package/dist/metrics/index.d.ts +163 -0
  266. package/dist/metrics/index.d.ts.map +1 -0
  267. package/dist/middleware/built-in.d.ts +50 -0
  268. package/dist/middleware/built-in.d.ts.map +1 -0
  269. package/dist/middleware/index.d.ts +40 -0
  270. package/dist/middleware/index.d.ts.map +1 -0
  271. package/dist/migrations/index.d.ts +10 -0
  272. package/dist/migrations/index.d.ts.map +1 -0
  273. package/dist/modules/filters.d.ts +150 -0
  274. package/dist/modules/filters.d.ts.map +1 -0
  275. package/dist/modules/guards.d.ts +188 -0
  276. package/dist/modules/guards.d.ts.map +1 -0
  277. package/dist/modules/index.d.ts +266 -0
  278. package/dist/modules/index.d.ts.map +1 -0
  279. package/dist/modules/index.js +514 -449
  280. package/dist/modules/interceptors.d.ts +242 -0
  281. package/dist/modules/interceptors.d.ts.map +1 -0
  282. package/dist/modules/lazy.d.ts +187 -0
  283. package/dist/modules/lazy.d.ts.map +1 -0
  284. package/dist/modules/lifecycle.d.ts +221 -0
  285. package/dist/modules/lifecycle.d.ts.map +1 -0
  286. package/dist/modules/metadata.d.ts +32 -0
  287. package/dist/modules/metadata.d.ts.map +1 -0
  288. package/dist/modules/pipes.d.ts +287 -0
  289. package/dist/modules/pipes.d.ts.map +1 -0
  290. package/dist/notification/channels/base.d.ts +32 -0
  291. package/dist/notification/channels/base.d.ts.map +1 -0
  292. package/dist/notification/channels/email.d.ts +37 -0
  293. package/dist/notification/channels/email.d.ts.map +1 -0
  294. package/dist/notification/channels/push.d.ts +37 -0
  295. package/dist/notification/channels/push.d.ts.map +1 -0
  296. package/dist/notification/channels/sms.d.ts +37 -0
  297. package/dist/notification/channels/sms.d.ts.map +1 -0
  298. package/dist/notification/channels/whatsapp.d.ts +37 -0
  299. package/dist/notification/channels/whatsapp.d.ts.map +1 -0
  300. package/dist/notification/index.d.ts +15 -0
  301. package/dist/notification/index.d.ts.map +1 -0
  302. package/dist/notification/service.d.ts +100 -0
  303. package/dist/notification/service.d.ts.map +1 -0
  304. package/dist/notification/types.d.ts +253 -0
  305. package/dist/notification/types.d.ts.map +1 -0
  306. package/dist/observability/__tests__/observability.test.d.ts +2 -0
  307. package/dist/observability/__tests__/observability.test.d.ts.map +1 -0
  308. package/dist/observability/breadcrumbs.d.ts +48 -0
  309. package/dist/observability/breadcrumbs.d.ts.map +1 -0
  310. package/dist/observability/index.d.ts +95 -0
  311. package/dist/observability/index.d.ts.map +1 -0
  312. package/dist/observability/interceptor.d.ts +19 -0
  313. package/dist/observability/interceptor.d.ts.map +1 -0
  314. package/dist/observability/service.d.ts +101 -0
  315. package/dist/observability/service.d.ts.map +1 -0
  316. package/dist/observability/trace.d.ts +21 -0
  317. package/dist/observability/trace.d.ts.map +1 -0
  318. package/dist/observability/types.d.ts +172 -0
  319. package/dist/observability/types.d.ts.map +1 -0
  320. package/dist/openapi/__tests__/decorators.test.d.ts +2 -0
  321. package/dist/openapi/__tests__/decorators.test.d.ts.map +1 -0
  322. package/dist/openapi/__tests__/document-builder.test.d.ts +2 -0
  323. package/dist/openapi/__tests__/document-builder.test.d.ts.map +1 -0
  324. package/dist/openapi/__tests__/route-scanner.test.d.ts +2 -0
  325. package/dist/openapi/__tests__/route-scanner.test.d.ts.map +1 -0
  326. package/dist/openapi/__tests__/schema-generator.test.d.ts +2 -0
  327. package/dist/openapi/__tests__/schema-generator.test.d.ts.map +1 -0
  328. package/dist/openapi/decorators.d.ts +173 -0
  329. package/dist/openapi/decorators.d.ts.map +1 -0
  330. package/dist/openapi/document-builder.d.ts +82 -0
  331. package/dist/openapi/document-builder.d.ts.map +1 -0
  332. package/dist/openapi/index.d.ts +48 -0
  333. package/dist/openapi/index.d.ts.map +1 -0
  334. package/dist/openapi/index.js +59 -40
  335. package/dist/openapi/metadata.d.ts +36 -0
  336. package/dist/openapi/metadata.d.ts.map +1 -0
  337. package/dist/openapi/route-scanner.d.ts +34 -0
  338. package/dist/openapi/route-scanner.d.ts.map +1 -0
  339. package/dist/openapi/schema-generator.d.ts +53 -0
  340. package/dist/openapi/schema-generator.d.ts.map +1 -0
  341. package/dist/openapi/swagger-module.d.ts +57 -0
  342. package/dist/openapi/swagger-module.d.ts.map +1 -0
  343. package/dist/openapi/types.d.ts +344 -0
  344. package/dist/openapi/types.d.ts.map +1 -0
  345. package/dist/orm/index.d.ts +10 -0
  346. package/dist/orm/index.d.ts.map +1 -0
  347. package/dist/router/index.d.ts +73 -0
  348. package/dist/router/index.d.ts.map +1 -0
  349. package/dist/router/linear.d.ts +54 -0
  350. package/dist/router/linear.d.ts.map +1 -0
  351. package/dist/router/regex.d.ts +49 -0
  352. package/dist/router/regex.d.ts.map +1 -0
  353. package/dist/router/tree.d.ts +112 -0
  354. package/dist/router/tree.d.ts.map +1 -0
  355. package/dist/rpc/index.d.ts +321 -0
  356. package/dist/rpc/index.d.ts.map +1 -0
  357. package/dist/schema/index.d.ts +10 -0
  358. package/dist/schema/index.d.ts.map +1 -0
  359. package/dist/security/index.d.ts +126 -0
  360. package/dist/security/index.d.ts.map +1 -0
  361. package/dist/ssg/index.d.ts +73 -0
  362. package/dist/ssg/index.d.ts.map +1 -0
  363. package/dist/storage/index.d.ts +99 -0
  364. package/dist/storage/index.d.ts.map +1 -0
  365. package/dist/telemetry/index.d.ts +376 -0
  366. package/dist/telemetry/index.d.ts.map +1 -0
  367. package/dist/template/index.d.ts +7 -0
  368. package/dist/template/index.d.ts.map +1 -0
  369. package/dist/templates/engine.d.ts +60 -0
  370. package/dist/templates/engine.d.ts.map +1 -0
  371. package/dist/templates/index.d.ts +9 -0
  372. package/dist/templates/index.d.ts.map +1 -0
  373. package/dist/templates/loader.d.ts +45 -0
  374. package/dist/templates/loader.d.ts.map +1 -0
  375. package/dist/templates/renderers/markdown.d.ts +46 -0
  376. package/dist/templates/renderers/markdown.d.ts.map +1 -0
  377. package/dist/templates/renderers/simple.d.ts +35 -0
  378. package/dist/templates/renderers/simple.d.ts.map +1 -0
  379. package/dist/templates/types.d.ts +138 -0
  380. package/dist/templates/types.d.ts.map +1 -0
  381. package/dist/testing/index.d.ts +539 -0
  382. package/dist/testing/index.d.ts.map +1 -0
  383. package/dist/types/index.d.ts +116 -0
  384. package/dist/types/index.d.ts.map +1 -0
  385. package/dist/validation/index.d.ts +89 -0
  386. package/dist/validation/index.d.ts.map +1 -0
  387. package/dist/validation/schemas.d.ts +243 -0
  388. package/dist/validation/schemas.d.ts.map +1 -0
  389. package/dist/websocket/index.d.ts +252 -0
  390. package/dist/websocket/index.d.ts.map +1 -0
  391. package/llms.txt +231 -0
  392. package/package.json +6 -2
  393. package/src/cli/ARCHITECTURE.md +3 -3
  394. package/src/cli/commands/add-frontend.ts +444 -0
  395. package/src/cli/commands/new.ts +23 -0
  396. package/src/cli/index.ts +1 -0
  397. package/src/cli/templates/frontend/react.ts +2 -1
  398. package/src/cli/templates/frontend/solid.ts +2 -1
  399. package/src/cli/templates/frontend/svelte.ts +2 -1
  400. package/src/cli/templates/frontend/vue.ts +2 -1
  401. package/src/cli/templates/project/api.ts +1 -1
  402. package/src/cli/templates/project/default.ts +1 -1
  403. package/src/cli/templates/project/fullstack.ts +14 -104
  404. package/src/cli/templates/project/website.ts +63 -12
  405. package/src/config/types.ts +21 -0
  406. package/src/graphql/built-in-engine.ts +598 -0
  407. package/src/graphql/context-builder.ts +110 -0
  408. package/src/graphql/decorators.ts +358 -0
  409. package/src/graphql/execution-pipeline.ts +227 -0
  410. package/src/graphql/graphql-module.ts +563 -0
  411. package/src/graphql/index.ts +101 -0
  412. package/src/graphql/metadata.ts +237 -0
  413. package/src/graphql/schema-builder.ts +319 -0
  414. package/src/graphql/subscription-handler.ts +283 -0
  415. package/src/graphql/types.ts +324 -0
  416. package/src/index.ts +3 -0
  417. package/src/modules/index.ts +48 -1
  418. package/tests/integration/cli.test.ts +19 -19
  419. package/tests/unit/cli.test.ts +1 -1
  420. package/tests/unit/graphql.test.ts +991 -0
  421. package/tsconfig.declaration.json +14 -0
@@ -0,0 +1,991 @@
1
+ /**
2
+ * GraphQL Module Unit Tests
3
+ *
4
+ * Tests for decorators, metadata, schema builder, built-in engine,
5
+ * execution pipeline, context builder, and GraphQL module setup.
6
+ */
7
+
8
+ import { describe, test, expect, beforeEach } from "bun:test";
9
+ import { Context } from "../../src/context";
10
+
11
+ // ============= Imports =============
12
+
13
+ import {
14
+ Resolver,
15
+ ObjectType,
16
+ InputType,
17
+ Field,
18
+ Query,
19
+ Mutation,
20
+ Subscription,
21
+ Args,
22
+ GqlContext,
23
+ } from "../../src/graphql/decorators";
24
+ import {
25
+ getResolverMetadata,
26
+ getTypeMetadata,
27
+ getQueryFields,
28
+ getMutationFields,
29
+ getSubscriptionFields,
30
+ getParamMetadata,
31
+ getAllGqlPropertyMetadata,
32
+ getGqlPropertyMetadata,
33
+ getGqlPropertyKeys,
34
+ } from "../../src/graphql/metadata";
35
+ import { GraphQLID, GraphQLInt, GraphQLFloat } from "../../src/graphql/types";
36
+ import {
37
+ SchemaBuilder,
38
+ typeFnToScalarName,
39
+ typeFnToSDL,
40
+ } from "../../src/graphql/schema-builder";
41
+ import { BuiltinGraphQLEngine } from "../../src/graphql/built-in-engine";
42
+ import {
43
+ buildGraphQLContext,
44
+ enrichContextForGraphQL,
45
+ parseGraphQLRequest,
46
+ } from "../../src/graphql/context-builder";
47
+ import {
48
+ callResolver,
49
+ GraphQLForbiddenError,
50
+ } from "../../src/graphql/execution-pipeline";
51
+ import type { GraphQLContext, ResolvedField } from "../../src/graphql/types";
52
+
53
+ // ============= Test Helpers =============
54
+
55
+ function makeHttpContext(
56
+ path = "/graphql",
57
+ method = "POST",
58
+ headers: Record<string, string> = {},
59
+ body?: string,
60
+ ): Context {
61
+ const req = new Request(`http://localhost${path}`, {
62
+ method,
63
+ headers: { "content-type": "application/json", ...headers },
64
+ body,
65
+ });
66
+ return new Context(req);
67
+ }
68
+
69
+ function makeGqlContext(httpCtx?: Context): GraphQLContext {
70
+ const ctx = httpCtx ?? makeHttpContext();
71
+ return buildGraphQLContext(ctx);
72
+ }
73
+
74
+ // ============= 1. Decorators =============
75
+
76
+ describe("GraphQL Decorators", () => {
77
+ test("@Resolver stores resolver metadata with default name", () => {
78
+ @Resolver()
79
+ class UserResolver {}
80
+
81
+ const meta = getResolverMetadata(UserResolver);
82
+ expect(meta).toBeDefined();
83
+ expect(meta!.name).toBe("UserResolver");
84
+ });
85
+
86
+ test("@Resolver stores resolver metadata with explicit name", () => {
87
+ @Resolver("User")
88
+ class UserFieldResolver {}
89
+
90
+ const meta = getResolverMetadata(UserFieldResolver);
91
+ expect(meta!.name).toBe("User");
92
+ });
93
+
94
+ test("@ObjectType stores type metadata", () => {
95
+ @ObjectType()
96
+ class Product {}
97
+
98
+ const meta = getTypeMetadata(Product);
99
+ expect(meta).toBeDefined();
100
+ expect(meta!.name).toBe("Product");
101
+ expect(meta!.kind).toBe("object");
102
+ });
103
+
104
+ test("@ObjectType accepts name and description", () => {
105
+ @ObjectType("ProductType", { description: "A product" })
106
+ class ProductV2 {}
107
+
108
+ const meta = getTypeMetadata(ProductV2);
109
+ expect(meta!.name).toBe("ProductType");
110
+ expect(meta!.description).toBe("A product");
111
+ });
112
+
113
+ test("@InputType stores input type metadata", () => {
114
+ @InputType()
115
+ class CreateInput {}
116
+
117
+ const meta = getTypeMetadata(CreateInput);
118
+ expect(meta!.kind).toBe("input");
119
+ });
120
+
121
+ test("@Field stores field metadata with defaults", () => {
122
+ class ItemType {
123
+ @Field(() => String)
124
+ declare title: string;
125
+ }
126
+
127
+ const meta = getGqlPropertyMetadata(ItemType.prototype, "title");
128
+ expect(meta).toBeDefined();
129
+ expect(meta!.nullable).toBe(false);
130
+ expect(meta!.propertyKey).toBe("title");
131
+ });
132
+
133
+ test("@Field stores nullable + description", () => {
134
+ class ItemB {
135
+ @Field(() => String, { nullable: true, description: "bio" })
136
+ declare bio: string | null;
137
+ }
138
+
139
+ const meta = getGqlPropertyMetadata(ItemB.prototype, "bio");
140
+ expect(meta!.nullable).toBe(true);
141
+ expect(meta!.description).toBe("bio");
142
+ });
143
+
144
+ test("@Query registers query field metadata", () => {
145
+ class QResolver {
146
+ @Query(() => String)
147
+ hello(): string {
148
+ return "hello";
149
+ }
150
+ }
151
+
152
+ const fields = getQueryFields(QResolver.prototype);
153
+ expect(fields).toHaveLength(1);
154
+ expect(fields[0].fieldName).toBe("hello");
155
+ expect(fields[0].kind).toBe("query");
156
+ });
157
+
158
+ test("@Mutation registers mutation field metadata", () => {
159
+ class MResolver {
160
+ @Mutation(() => String)
161
+ doSomething(): string {
162
+ return "done";
163
+ }
164
+ }
165
+
166
+ const fields = getMutationFields(MResolver.prototype);
167
+ expect(fields).toHaveLength(1);
168
+ expect(fields[0].fieldName).toBe("doSomething");
169
+ expect(fields[0].kind).toBe("mutation");
170
+ });
171
+
172
+ test("@Subscription registers subscription field metadata", () => {
173
+ class SResolver {
174
+ @Subscription(() => String)
175
+ async *onMessage(): AsyncGenerator<string> {
176
+ yield "msg";
177
+ }
178
+ }
179
+
180
+ const fields = getSubscriptionFields(SResolver.prototype);
181
+ expect(fields).toHaveLength(1);
182
+ expect(fields[0].kind).toBe("subscription");
183
+ });
184
+
185
+ test("@Args stores param metadata at correct index", () => {
186
+ class ArgResolver {
187
+ @Query(() => String)
188
+ greet(@Args("name") name: string): string {
189
+ return `hi ${name}`;
190
+ }
191
+ }
192
+
193
+ const params = getParamMetadata(ArgResolver.prototype, "greet");
194
+ expect(params[0]).toBeDefined();
195
+ expect(params[0].kind).toBe("args");
196
+ expect(params[0].argName).toBe("name");
197
+ expect(params[0].index).toBe(0);
198
+ });
199
+
200
+ test("@GqlContext stores context param metadata", () => {
201
+ class CtxResolver {
202
+ @Query(() => String)
203
+ me(@GqlContext() ctx: GraphQLContext): string {
204
+ return String(ctx.user);
205
+ }
206
+ }
207
+
208
+ const params = getParamMetadata(CtxResolver.prototype, "me");
209
+ expect(params[0].kind).toBe("context");
210
+ });
211
+
212
+ test("@Query with name override uses custom field name", () => {
213
+ class NamedResolver {
214
+ @Query(() => String, { name: "listUsers", nullable: true })
215
+ getUsers(): string[] {
216
+ return [];
217
+ }
218
+ }
219
+
220
+ const fields = getQueryFields(NamedResolver.prototype);
221
+ expect(fields[0].fieldName).toBe("listUsers");
222
+ expect(fields[0].methodName).toBe("getUsers");
223
+ expect(fields[0].nullable).toBe(true);
224
+ });
225
+ });
226
+
227
+ // ============= 2. Schema Builder =============
228
+
229
+ describe("SchemaBuilder", () => {
230
+ test("typeFnToScalarName maps primitives correctly", () => {
231
+ expect(typeFnToScalarName(() => String)).toBe("String");
232
+ expect(typeFnToScalarName(() => Number)).toBe("Float");
233
+ expect(typeFnToScalarName(() => Boolean)).toBe("Boolean");
234
+ expect(typeFnToScalarName(() => GraphQLID)).toBe("ID");
235
+ expect(typeFnToScalarName(() => GraphQLInt)).toBe("Int");
236
+ expect(typeFnToScalarName(() => GraphQLFloat)).toBe("Float");
237
+ });
238
+
239
+ test("typeFnToScalarName returns null for object types", () => {
240
+ class Post {}
241
+ expect(typeFnToScalarName(() => Post)).toBeNull();
242
+ });
243
+
244
+ test("typeFnToSDL generates correct non-null scalar", () => {
245
+ expect(typeFnToSDL(() => String, false)).toBe("String!");
246
+ expect(typeFnToSDL(() => Boolean, false)).toBe("Boolean!");
247
+ });
248
+
249
+ test("typeFnToSDL generates nullable scalar", () => {
250
+ expect(typeFnToSDL(() => String, true)).toBe("String");
251
+ });
252
+
253
+ test("typeFnToSDL generates list type", () => {
254
+ class Tag {}
255
+ expect(typeFnToSDL(() => [Tag], false)).toBe("[Tag!]!");
256
+ expect(typeFnToSDL(() => [Tag], true)).toBe("[Tag!]");
257
+ });
258
+
259
+ test("SchemaBuilder generates Query SDL from resolver", () => {
260
+ @ObjectType()
261
+ class Article {
262
+ @Field(() => String)
263
+ declare title: string;
264
+ }
265
+
266
+ class ArticleResolver {
267
+ @Query(() => [Article])
268
+ articles(): Article[] {
269
+ return [];
270
+ }
271
+ }
272
+
273
+ const instances = new Map<new (...args: unknown[]) => unknown, unknown>();
274
+ instances.set(ArticleResolver, new ArticleResolver());
275
+
276
+ const builder = new SchemaBuilder([ArticleResolver], instances);
277
+ const { sdl } = builder.build();
278
+
279
+ expect(sdl).toContain("type Query");
280
+ expect(sdl).toContain("articles:");
281
+ });
282
+
283
+ test("SchemaBuilder generates Mutation SDL", () => {
284
+ class CreateArticleInput {
285
+ @Field(() => String)
286
+ declare title: string;
287
+ }
288
+
289
+ @InputType()
290
+ class CreateArticleInputType extends CreateArticleInput {}
291
+
292
+ class CreateArticleResolver {
293
+ @Mutation(() => String)
294
+ createArticle(@Args("title") title: string): string {
295
+ return title;
296
+ }
297
+ }
298
+
299
+ const instances = new Map<new (...args: unknown[]) => unknown, unknown>();
300
+ instances.set(CreateArticleResolver, new CreateArticleResolver());
301
+
302
+ const builder = new SchemaBuilder([CreateArticleResolver], instances);
303
+ const { sdl } = builder.build();
304
+
305
+ expect(sdl).toContain("type Mutation");
306
+ expect(sdl).toContain("createArticle");
307
+ });
308
+
309
+ test("SchemaBuilder generates type definitions for @ObjectType", () => {
310
+ @ObjectType()
311
+ class BlogPost {
312
+ @Field(() => String)
313
+ declare id: string;
314
+
315
+ @Field(() => String, { nullable: true })
316
+ declare excerpt: string | null;
317
+ }
318
+
319
+ class BlogResolver {
320
+ @Query(() => [BlogPost])
321
+ posts(): BlogPost[] {
322
+ return [];
323
+ }
324
+ }
325
+
326
+ const instances = new Map<new (...args: unknown[]) => unknown, unknown>();
327
+ instances.set(BlogResolver, new BlogResolver());
328
+
329
+ const builder = new SchemaBuilder([BlogResolver], instances);
330
+ const { sdl } = builder.build();
331
+
332
+ expect(sdl).toContain("type BlogPost");
333
+ expect(sdl).toContain("id: String!");
334
+ expect(sdl).toContain("excerpt: String");
335
+ });
336
+
337
+ test("SchemaBuilder resolvedSchema has correct maps", () => {
338
+ class SimpleResolver {
339
+ @Query(() => String)
340
+ hello(): string {
341
+ return "hi";
342
+ }
343
+ }
344
+
345
+ const instance = new SimpleResolver();
346
+ const instances = new Map<new (...args: unknown[]) => unknown, unknown>();
347
+ instances.set(SimpleResolver, instance);
348
+
349
+ const builder = new SchemaBuilder([SimpleResolver], instances);
350
+ const { resolvedSchema } = builder.build();
351
+
352
+ expect(resolvedSchema.queryFields.has("hello")).toBe(true);
353
+ expect(resolvedSchema.mutationFields.size).toBe(0);
354
+ });
355
+ });
356
+
357
+ // ============= 3. Built-in Engine =============
358
+
359
+ describe("BuiltinGraphQLEngine", () => {
360
+ const engine = new BuiltinGraphQLEngine();
361
+
362
+ test("supportsIntrospection is false", () => {
363
+ expect(engine.supportsIntrospection).toBe(false);
364
+ });
365
+
366
+ test("supportsSubscriptions is false", () => {
367
+ expect(engine.supportsSubscriptions).toBe(false);
368
+ });
369
+
370
+ test("executes a simple query", async () => {
371
+ const instance = {
372
+ hello: () => "world",
373
+ };
374
+ const field: ResolvedField = {
375
+ resolverInstance: instance,
376
+ methodName: "hello",
377
+ paramMetadata: [],
378
+ typeFn: () => String,
379
+ nullable: false,
380
+ };
381
+
382
+ const queries = new Map([["hello", field]]);
383
+ const schema = engine.buildSchema(
384
+ { queries, mutations: new Map(), subscriptions: new Map() },
385
+ new Map(),
386
+ "",
387
+ );
388
+
389
+ const ctx = makeGqlContext();
390
+ const result = await engine.execute(
391
+ schema,
392
+ "{ hello }",
393
+ {},
394
+ ctx,
395
+ );
396
+
397
+ expect(result.data?.hello).toBe("world");
398
+ expect(result.errors).toBeUndefined();
399
+ });
400
+
401
+ test("executes a mutation", async () => {
402
+ const instance = { createNote: (_title: string) => ({ id: "1", title: "Test" }) };
403
+ const field: ResolvedField = {
404
+ resolverInstance: instance,
405
+ methodName: "createNote",
406
+ paramMetadata: [{ index: 0, kind: "args", argName: "title" }],
407
+ typeFn: () => String,
408
+ nullable: false,
409
+ };
410
+
411
+ const mutations = new Map([["createNote", field]]);
412
+ const schema = engine.buildSchema(
413
+ { queries: new Map(), mutations, subscriptions: new Map() },
414
+ new Map(),
415
+ "",
416
+ );
417
+
418
+ const ctx = makeGqlContext();
419
+ const result = await engine.execute(
420
+ schema,
421
+ 'mutation { createNote(title: "Test") { id title } }',
422
+ {},
423
+ ctx,
424
+ );
425
+
426
+ expect(result.data?.createNote).toEqual({ id: "1", title: "Test" });
427
+ });
428
+
429
+ test("passes arguments to resolver", async () => {
430
+ const instance = {
431
+ user: (id: string) => ({ id, name: "Alice" }),
432
+ };
433
+ const field: ResolvedField = {
434
+ resolverInstance: instance,
435
+ methodName: "user",
436
+ paramMetadata: [{ index: 0, kind: "args", argName: "id" }],
437
+ typeFn: () => String,
438
+ nullable: true,
439
+ };
440
+
441
+ const queries = new Map([["user", field]]);
442
+ const schema = engine.buildSchema(
443
+ { queries, mutations: new Map(), subscriptions: new Map() },
444
+ new Map(),
445
+ "",
446
+ );
447
+
448
+ const ctx = makeGqlContext();
449
+ const result = await engine.execute(
450
+ schema,
451
+ '{ user(id: "abc") { id name } }',
452
+ {},
453
+ ctx,
454
+ );
455
+
456
+ expect(result.data?.user).toEqual({ id: "abc", name: "Alice" });
457
+ });
458
+
459
+ test("substitutes variables from variables map", async () => {
460
+ const instance = {
461
+ item: (name: string) => name,
462
+ };
463
+ const field: ResolvedField = {
464
+ resolverInstance: instance,
465
+ methodName: "item",
466
+ paramMetadata: [{ index: 0, kind: "args", argName: "name" }],
467
+ typeFn: () => String,
468
+ nullable: false,
469
+ };
470
+
471
+ const queries = new Map([["item", field]]);
472
+ const schema = engine.buildSchema(
473
+ { queries, mutations: new Map(), subscriptions: new Map() },
474
+ new Map(),
475
+ "",
476
+ );
477
+
478
+ const ctx = makeGqlContext();
479
+ const result = await engine.execute(
480
+ schema,
481
+ "query GetItem($name: String!) { item(name: $name) }",
482
+ { name: "widget" },
483
+ ctx,
484
+ );
485
+
486
+ expect(result.data?.item).toBe("widget");
487
+ });
488
+
489
+ test("returns error for unknown field", async () => {
490
+ const queries = new Map<string, ResolvedField>();
491
+ const schema = engine.buildSchema(
492
+ { queries, mutations: new Map(), subscriptions: new Map() },
493
+ new Map(),
494
+ "",
495
+ );
496
+
497
+ const ctx = makeGqlContext();
498
+ const result = await engine.execute(
499
+ schema,
500
+ "{ nonExistent }",
501
+ {},
502
+ ctx,
503
+ );
504
+
505
+ expect(result.data?.nonExistent).toBeNull();
506
+ expect(result.errors?.[0].message).toContain("nonExistent");
507
+ });
508
+
509
+ test("returns error for fragment spread", async () => {
510
+ const queries = new Map<string, ResolvedField>();
511
+ const schema = engine.buildSchema(
512
+ { queries, mutations: new Map(), subscriptions: new Map() },
513
+ new Map(),
514
+ "",
515
+ );
516
+
517
+ const ctx = makeGqlContext();
518
+ const result = await engine.execute(
519
+ schema,
520
+ "{ ...UserFields }",
521
+ {},
522
+ ctx,
523
+ );
524
+
525
+ expect(result.errors).toBeDefined();
526
+ expect(result.errors![0].message).toContain("Fragment spreads are not supported");
527
+ });
528
+
529
+ test("returns error for introspection field", async () => {
530
+ const queries = new Map<string, ResolvedField>();
531
+ const schema = engine.buildSchema(
532
+ { queries, mutations: new Map(), subscriptions: new Map() },
533
+ new Map(),
534
+ "",
535
+ );
536
+
537
+ const ctx = makeGqlContext();
538
+ const result = await engine.execute(
539
+ schema,
540
+ "{ __schema { queryType { name } } }",
541
+ {},
542
+ ctx,
543
+ );
544
+
545
+ expect(result.errors).toBeDefined();
546
+ expect(result.errors![0].message).toContain("Introspection");
547
+ });
548
+
549
+ test("handles resolver throwing an error", async () => {
550
+ const instance = {
551
+ fail: () => {
552
+ throw new Error("Resolver failed");
553
+ },
554
+ };
555
+ const field: ResolvedField = {
556
+ resolverInstance: instance,
557
+ methodName: "fail",
558
+ paramMetadata: [],
559
+ typeFn: () => String,
560
+ nullable: true,
561
+ };
562
+
563
+ const queries = new Map([["fail", field]]);
564
+ const schema = engine.buildSchema(
565
+ { queries, mutations: new Map(), subscriptions: new Map() },
566
+ new Map(),
567
+ "",
568
+ );
569
+
570
+ const ctx = makeGqlContext();
571
+ const result = await engine.execute(schema, "{ fail }", {}, ctx);
572
+
573
+ expect(result.data?.fail).toBeNull();
574
+ expect(result.errors?.[0].message).toBe("Resolver failed");
575
+ expect(result.errors?.[0].path).toEqual(["fail"]);
576
+ });
577
+
578
+ test("resolves nested sub-selections", async () => {
579
+ const instance = {
580
+ me: () => ({ id: "u1", profile: { bio: "hello" } }),
581
+ };
582
+ const field: ResolvedField = {
583
+ resolverInstance: instance,
584
+ methodName: "me",
585
+ paramMetadata: [],
586
+ typeFn: () => String,
587
+ nullable: true,
588
+ };
589
+
590
+ const queries = new Map([["me", field]]);
591
+ const schema = engine.buildSchema(
592
+ { queries, mutations: new Map(), subscriptions: new Map() },
593
+ new Map(),
594
+ "",
595
+ );
596
+
597
+ const ctx = makeGqlContext();
598
+ const result = await engine.execute(
599
+ schema,
600
+ "{ me { id profile { bio } } }",
601
+ {},
602
+ ctx,
603
+ );
604
+
605
+ expect((result.data?.me as { id: string; profile: { bio: string } }).profile.bio).toBe("hello");
606
+ });
607
+ });
608
+
609
+ // ============= 4. Context Builder =============
610
+
611
+ describe("Context Builder", () => {
612
+ test("buildGraphQLContext extracts request and httpContext", () => {
613
+ const httpCtx = makeHttpContext("/graphql");
614
+ const gqlCtx = buildGraphQLContext(httpCtx);
615
+
616
+ expect(gqlCtx.request).toBe(httpCtx.req);
617
+ expect(gqlCtx.httpContext).toBe(httpCtx);
618
+ });
619
+
620
+ test("buildGraphQLContext extracts user from context store", () => {
621
+ const httpCtx = makeHttpContext("/graphql");
622
+ const mockUser = { id: "u1", email: "test@example.com" };
623
+ httpCtx.set("user", mockUser);
624
+
625
+ const gqlCtx = buildGraphQLContext(httpCtx);
626
+ expect(gqlCtx.user).toEqual(mockUser);
627
+ });
628
+
629
+ test("buildGraphQLContext user is undefined when not set", () => {
630
+ const httpCtx = makeHttpContext("/graphql");
631
+ const gqlCtx = buildGraphQLContext(httpCtx);
632
+ expect(gqlCtx.user).toBeUndefined();
633
+ });
634
+
635
+ test("enrichContextForGraphQL sets metadata on HTTP context", () => {
636
+ const httpCtx = makeHttpContext("/graphql");
637
+
638
+ class SomeResolver {}
639
+ enrichContextForGraphQL(httpCtx, "users", "query", SomeResolver);
640
+
641
+ expect(httpCtx.get("graphql:operation")).toBe("users");
642
+ expect(httpCtx.get("graphql:type")).toBe("query");
643
+ expect(httpCtx.get("graphql:resolverClass")).toBe(SomeResolver);
644
+ });
645
+
646
+ test("parseGraphQLRequest parses POST JSON body", async () => {
647
+ const body = JSON.stringify({ query: "{ hello }", variables: { x: 1 } });
648
+ const req = new Request("http://localhost/graphql", {
649
+ method: "POST",
650
+ headers: { "content-type": "application/json" },
651
+ body,
652
+ });
653
+
654
+ const result = await parseGraphQLRequest(req);
655
+ expect(result).not.toBeNull();
656
+ expect(result!.query).toBe("{ hello }");
657
+ expect(result!.variables).toEqual({ x: 1 });
658
+ });
659
+
660
+ test("parseGraphQLRequest returns null for invalid body", async () => {
661
+ const req = new Request("http://localhost/graphql", {
662
+ method: "POST",
663
+ headers: { "content-type": "application/json" },
664
+ body: "not-json",
665
+ });
666
+
667
+ const result = await parseGraphQLRequest(req);
668
+ expect(result).toBeNull();
669
+ });
670
+
671
+ test("parseGraphQLRequest parses GET query string", async () => {
672
+ const req = new Request(
673
+ "http://localhost/graphql?query=%7B+hello+%7D",
674
+ { method: "GET" },
675
+ );
676
+
677
+ const result = await parseGraphQLRequest(req);
678
+ expect(result).not.toBeNull();
679
+ expect(result!.query).toBe("{ hello }");
680
+ });
681
+ });
682
+
683
+ // ============= 5. Execution Pipeline (callResolver) =============
684
+
685
+ describe("Execution Pipeline - callResolver", () => {
686
+ test("calls resolver method with no params", async () => {
687
+ const instance = { getData: () => [1, 2, 3] };
688
+ const field: ResolvedField = {
689
+ resolverInstance: instance,
690
+ methodName: "getData",
691
+ paramMetadata: [],
692
+ typeFn: () => Number,
693
+ nullable: false,
694
+ };
695
+ const ctx = makeGqlContext();
696
+ const result = await callResolver(field, {}, ctx);
697
+ expect(result).toEqual([1, 2, 3]);
698
+ });
699
+
700
+ test("callResolver injects @Args param at correct index", async () => {
701
+ let received: unknown;
702
+ const instance = {
703
+ find: (id: string) => {
704
+ received = id;
705
+ return { id };
706
+ },
707
+ };
708
+ const field: ResolvedField = {
709
+ resolverInstance: instance,
710
+ methodName: "find",
711
+ paramMetadata: [{ index: 0, kind: "args", argName: "id" }],
712
+ typeFn: () => String,
713
+ nullable: true,
714
+ };
715
+ const ctx = makeGqlContext();
716
+ await callResolver(field, { id: "abc" }, ctx);
717
+ expect(received).toBe("abc");
718
+ });
719
+
720
+ test("callResolver injects @GqlContext at correct index", async () => {
721
+ let receivedCtx: unknown;
722
+ const instance = {
723
+ me: (ctx: GraphQLContext) => {
724
+ receivedCtx = ctx;
725
+ return ctx.user;
726
+ },
727
+ };
728
+ const field: ResolvedField = {
729
+ resolverInstance: instance,
730
+ methodName: "me",
731
+ paramMetadata: [{ index: 0, kind: "context" }],
732
+ typeFn: () => String,
733
+ nullable: true,
734
+ };
735
+ const httpCtx = makeHttpContext();
736
+ httpCtx.set("user", { id: "u1" });
737
+ const ctx = buildGraphQLContext(httpCtx);
738
+
739
+ await callResolver(field, {}, ctx);
740
+ expect((receivedCtx as GraphQLContext).user).toEqual({ id: "u1" });
741
+ });
742
+
743
+ test("callResolver handles mixed @Args and @GqlContext params", async () => {
744
+ let receivedId: unknown;
745
+ let receivedCtx: unknown;
746
+
747
+ const instance = {
748
+ deleteUser: (id: string, ctx: GraphQLContext) => {
749
+ receivedId = id;
750
+ receivedCtx = ctx;
751
+ return true;
752
+ },
753
+ };
754
+ const field: ResolvedField = {
755
+ resolverInstance: instance,
756
+ methodName: "deleteUser",
757
+ paramMetadata: [
758
+ { index: 0, kind: "args", argName: "id" },
759
+ { index: 1, kind: "context" },
760
+ ],
761
+ typeFn: () => Boolean,
762
+ nullable: false,
763
+ };
764
+
765
+ const ctx = makeGqlContext();
766
+ await callResolver(field, { id: "u99" }, ctx);
767
+
768
+ expect(receivedId).toBe("u99");
769
+ expect(receivedCtx).toBe(ctx);
770
+ });
771
+
772
+ test("callResolver awaits async resolver", async () => {
773
+ const instance = {
774
+ asyncOp: async () => {
775
+ return new Promise<string>((resolve) =>
776
+ setTimeout(() => resolve("async-result"), 1),
777
+ );
778
+ },
779
+ };
780
+ const field: ResolvedField = {
781
+ resolverInstance: instance,
782
+ methodName: "asyncOp",
783
+ paramMetadata: [],
784
+ typeFn: () => String,
785
+ nullable: false,
786
+ };
787
+ const ctx = makeGqlContext();
788
+ const result = await callResolver(field, {}, ctx);
789
+ expect(result).toBe("async-result");
790
+ });
791
+ });
792
+
793
+ // ============= 6. GraphQLForbiddenError =============
794
+
795
+ describe("GraphQLForbiddenError", () => {
796
+ test("has correct name and extensions", () => {
797
+ const err = new GraphQLForbiddenError("Access denied");
798
+ expect(err.name).toBe("GraphQLForbiddenError");
799
+ expect(err.message).toBe("Access denied");
800
+ expect(err.extensions).toEqual({ code: "FORBIDDEN" });
801
+ });
802
+
803
+ test("toGraphQLError returns correct shape", () => {
804
+ const err = new GraphQLForbiddenError("Forbidden field");
805
+ const gqlErr = err.toGraphQLError();
806
+ expect(gqlErr.message).toBe("Forbidden field");
807
+ expect(gqlErr.extensions?.code).toBe("FORBIDDEN");
808
+ });
809
+
810
+ test("uses default message when none provided", () => {
811
+ const err = new GraphQLForbiddenError();
812
+ expect(err.message).toBe("Forbidden");
813
+ });
814
+ });
815
+
816
+ // ============= 7. @Field + getGqlPropertyKeys =============
817
+
818
+ describe("@Field property metadata helpers", () => {
819
+ test("getGqlPropertyKeys returns all field keys", () => {
820
+ class ArticleObj {
821
+ @Field(() => String) declare id: string;
822
+ @Field(() => String) declare title: string;
823
+ @Field(() => Number, { nullable: true }) declare score: number | null;
824
+ }
825
+
826
+ const keys = getGqlPropertyKeys(ArticleObj.prototype);
827
+ expect(keys).toContain("id");
828
+ expect(keys).toContain("title");
829
+ expect(keys).toContain("score");
830
+ });
831
+
832
+ test("getAllGqlPropertyMetadata returns all field metadata", () => {
833
+ class ReviewObj {
834
+ @Field(() => String) declare content: string;
835
+ @Field(() => GraphQLInt) declare rating: number;
836
+ }
837
+
838
+ const fields = getAllGqlPropertyMetadata(ReviewObj.prototype);
839
+ expect(fields).toHaveLength(2);
840
+ const names = fields.map((f) => f.propertyKey);
841
+ expect(names).toContain("content");
842
+ expect(names).toContain("rating");
843
+ });
844
+
845
+ test("@Field stores defaultValue for input types", () => {
846
+ class SettingsInput {
847
+ @Field(() => Boolean, { defaultValue: true })
848
+ declare notifications: boolean;
849
+ }
850
+
851
+ const meta = getGqlPropertyMetadata(
852
+ SettingsInput.prototype,
853
+ "notifications",
854
+ );
855
+ expect(meta!.defaultValue).toBe(true);
856
+ });
857
+
858
+ test("@Field stores deprecationReason", () => {
859
+ class LegacyType {
860
+ @Field(() => String, { deprecationReason: "Use newField instead" })
861
+ declare oldField: string;
862
+ }
863
+
864
+ const meta = getGqlPropertyMetadata(
865
+ LegacyType.prototype,
866
+ "oldField",
867
+ );
868
+ expect(meta!.deprecationReason).toBe("Use newField instead");
869
+ });
870
+ });
871
+
872
+ // ============= 8. End-to-End: Engine with full resolver lifecycle =============
873
+
874
+ describe("Engine integration: query lifecycle", () => {
875
+ test("query returns list result", async () => {
876
+ const engine = new BuiltinGraphQLEngine();
877
+ const instance = {
878
+ tags: () => [{ name: "tech" }, { name: "science" }],
879
+ };
880
+ const field: ResolvedField = {
881
+ resolverInstance: instance,
882
+ methodName: "tags",
883
+ paramMetadata: [],
884
+ typeFn: () => String,
885
+ nullable: false,
886
+ };
887
+
888
+ const schema = engine.buildSchema(
889
+ { queries: new Map([["tags", field]]), mutations: new Map(), subscriptions: new Map() },
890
+ new Map(),
891
+ "",
892
+ );
893
+
894
+ const ctx = makeGqlContext();
895
+ const result = await engine.execute(schema, "{ tags { name } }", {}, ctx);
896
+
897
+ expect(Array.isArray(result.data?.tags)).toBe(true);
898
+ expect((result.data?.tags as { name: string }[])[0].name).toBe("tech");
899
+ });
900
+
901
+ test("alias in query maps to aliased key in response", async () => {
902
+ const engine = new BuiltinGraphQLEngine();
903
+ const instance = {
904
+ hello: () => "world",
905
+ };
906
+ const field: ResolvedField = {
907
+ resolverInstance: instance,
908
+ methodName: "hello",
909
+ paramMetadata: [],
910
+ typeFn: () => String,
911
+ nullable: false,
912
+ };
913
+
914
+ const schema = engine.buildSchema(
915
+ { queries: new Map([["hello", field]]), mutations: new Map(), subscriptions: new Map() },
916
+ new Map(),
917
+ "",
918
+ );
919
+
920
+ const ctx = makeGqlContext();
921
+ const result = await engine.execute(schema, "{ greeting: hello }", {}, ctx);
922
+
923
+ expect(result.data?.greeting).toBe("world");
924
+ expect(result.data?.hello).toBeUndefined();
925
+ });
926
+
927
+ test("null result returns null without error", async () => {
928
+ const engine = new BuiltinGraphQLEngine();
929
+ const instance = { getUser: () => null };
930
+ const field: ResolvedField = {
931
+ resolverInstance: instance,
932
+ methodName: "getUser",
933
+ paramMetadata: [],
934
+ typeFn: () => String,
935
+ nullable: true,
936
+ };
937
+
938
+ const schema = engine.buildSchema(
939
+ { queries: new Map([["getUser", field]]), mutations: new Map(), subscriptions: new Map() },
940
+ new Map(),
941
+ "",
942
+ );
943
+
944
+ const ctx = makeGqlContext();
945
+ const result = await engine.execute(schema, "{ getUser }", {}, ctx);
946
+
947
+ expect(result.data?.getUser).toBeNull();
948
+ expect(result.errors).toBeUndefined();
949
+ });
950
+
951
+ test("multiple fields in one query resolve independently", async () => {
952
+ const engine = new BuiltinGraphQLEngine();
953
+ const instance1 = { hello: () => "hi" };
954
+ const instance2 = { version: () => "1.0" };
955
+
956
+ const fields = new Map<string, ResolvedField>([
957
+ [
958
+ "hello",
959
+ {
960
+ resolverInstance: instance1,
961
+ methodName: "hello",
962
+ paramMetadata: [],
963
+ typeFn: () => String,
964
+ nullable: false,
965
+ },
966
+ ],
967
+ [
968
+ "version",
969
+ {
970
+ resolverInstance: instance2,
971
+ methodName: "version",
972
+ paramMetadata: [],
973
+ typeFn: () => String,
974
+ nullable: false,
975
+ },
976
+ ],
977
+ ]);
978
+
979
+ const schema = engine.buildSchema(
980
+ { queries: fields, mutations: new Map(), subscriptions: new Map() },
981
+ new Map(),
982
+ "",
983
+ );
984
+
985
+ const ctx = makeGqlContext();
986
+ const result = await engine.execute(schema, "{ hello version }", {}, ctx);
987
+
988
+ expect(result.data?.hello).toBe("hi");
989
+ expect(result.data?.version).toBe("1.0");
990
+ });
991
+ });