raffel 0.1.2 → 0.2.2

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 (268) hide show
  1. package/README.md +314 -346
  2. package/dist/adapters/index.d.ts +3 -1
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +3 -1
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/adapters/s3db/adapter.d.ts.map +1 -1
  7. package/dist/adapters/s3db/adapter.js +0 -3
  8. package/dist/adapters/s3db/adapter.js.map +1 -1
  9. package/dist/adapters/udp.d.ts +83 -0
  10. package/dist/adapters/udp.d.ts.map +1 -0
  11. package/dist/adapters/udp.int.test.d.ts +5 -0
  12. package/dist/adapters/udp.int.test.d.ts.map +1 -0
  13. package/dist/adapters/udp.int.test.js +397 -0
  14. package/dist/adapters/udp.int.test.js.map +1 -0
  15. package/dist/adapters/udp.js +391 -0
  16. package/dist/adapters/udp.js.map +1 -0
  17. package/dist/cache/drivers/file.d.ts.map +1 -1
  18. package/dist/cache/drivers/file.js +13 -1
  19. package/dist/cache/drivers/file.js.map +1 -1
  20. package/dist/cache/drivers/memory.d.ts.map +1 -1
  21. package/dist/cache/drivers/memory.js +1 -0
  22. package/dist/cache/drivers/memory.js.map +1 -1
  23. package/dist/cache/types.d.ts +1 -0
  24. package/dist/cache/types.d.ts.map +1 -1
  25. package/dist/docs/generators/http-generator.d.ts.map +1 -1
  26. package/dist/docs/generators/http-generator.js +0 -1
  27. package/dist/docs/generators/http-generator.js.map +1 -1
  28. package/dist/graphql/graphql.int.test.d.ts +10 -0
  29. package/dist/graphql/graphql.int.test.d.ts.map +1 -0
  30. package/dist/graphql/graphql.int.test.js +698 -0
  31. package/dist/graphql/graphql.int.test.js.map +1 -0
  32. package/dist/graphql/schema-generator.d.ts.map +1 -1
  33. package/dist/graphql/schema-generator.js +20 -7
  34. package/dist/graphql/schema-generator.js.map +1 -1
  35. package/dist/http/auth.d.ts.map +1 -1
  36. package/dist/http/auth.js +15 -1
  37. package/dist/http/auth.js.map +1 -1
  38. package/dist/http/http.int.test.d.ts +7 -0
  39. package/dist/http/http.int.test.d.ts.map +1 -0
  40. package/dist/http/http.int.test.js +604 -0
  41. package/dist/http/http.int.test.js.map +1 -0
  42. package/dist/http/index.d.ts +2 -0
  43. package/dist/http/index.d.ts.map +1 -1
  44. package/dist/http/index.js +2 -0
  45. package/dist/http/index.js.map +1 -1
  46. package/dist/http/oauth2.d.ts.map +1 -1
  47. package/dist/http/oauth2.js +39 -0
  48. package/dist/http/oauth2.js.map +1 -1
  49. package/dist/http/oidc.d.ts.map +1 -1
  50. package/dist/http/oidc.js +9 -1
  51. package/dist/http/oidc.js.map +1 -1
  52. package/dist/http/session-redis.d.ts +187 -0
  53. package/dist/http/session-redis.d.ts.map +1 -0
  54. package/dist/http/session-redis.int.test.d.ts +8 -0
  55. package/dist/http/session-redis.int.test.d.ts.map +1 -0
  56. package/dist/http/session-redis.int.test.js +492 -0
  57. package/dist/http/session-redis.int.test.js.map +1 -0
  58. package/dist/http/session-redis.js +320 -0
  59. package/dist/http/session-redis.js.map +1 -0
  60. package/dist/index.d.ts +2 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +25 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/mcp/cli.js +2 -1
  65. package/dist/mcp/cli.js.map +1 -1
  66. package/dist/mcp/docs/adapters.d.ts.map +1 -1
  67. package/dist/mcp/docs/adapters.js +175 -145
  68. package/dist/mcp/docs/adapters.js.map +1 -1
  69. package/dist/mcp/docs/interceptors.d.ts +1 -1
  70. package/dist/mcp/docs/interceptors.d.ts.map +1 -1
  71. package/dist/mcp/docs/interceptors.js +231 -305
  72. package/dist/mcp/docs/interceptors.js.map +1 -1
  73. package/dist/mcp/docs/patterns.d.ts.map +1 -1
  74. package/dist/mcp/docs/patterns.js +20 -18
  75. package/dist/mcp/docs/patterns.js.map +1 -1
  76. package/dist/mcp/docs/quickstart.d.ts +1 -1
  77. package/dist/mcp/docs/quickstart.d.ts.map +1 -1
  78. package/dist/mcp/docs/quickstart.js +48 -46
  79. package/dist/mcp/docs/quickstart.js.map +1 -1
  80. package/dist/mcp/server.d.ts +1 -1
  81. package/dist/mcp/server.d.ts.map +1 -1
  82. package/dist/mcp/server.js +6 -7
  83. package/dist/mcp/server.js.map +1 -1
  84. package/dist/mcp/version.d.ts +7 -0
  85. package/dist/mcp/version.d.ts.map +1 -0
  86. package/dist/mcp/version.js +20 -0
  87. package/dist/mcp/version.js.map +1 -0
  88. package/dist/middleware/auth/oauth2.d.ts +294 -0
  89. package/dist/middleware/auth/oauth2.d.ts.map +1 -0
  90. package/dist/middleware/auth/oauth2.int.test.d.ts +2 -0
  91. package/dist/middleware/auth/oauth2.int.test.d.ts.map +1 -0
  92. package/dist/middleware/auth/oauth2.int.test.js +714 -0
  93. package/dist/middleware/auth/oauth2.int.test.js.map +1 -0
  94. package/dist/middleware/auth/oauth2.js +671 -0
  95. package/dist/middleware/auth/oauth2.js.map +1 -0
  96. package/dist/middleware/auth.d.ts +2 -0
  97. package/dist/middleware/auth.d.ts.map +1 -1
  98. package/dist/middleware/auth.js +16 -0
  99. package/dist/middleware/auth.js.map +1 -1
  100. package/dist/middleware/index.d.ts +5 -2
  101. package/dist/middleware/index.d.ts.map +1 -1
  102. package/dist/middleware/index.js +4 -0
  103. package/dist/middleware/index.js.map +1 -1
  104. package/dist/middleware/interceptors/circuit-breaker.d.ts.map +1 -1
  105. package/dist/middleware/interceptors/circuit-breaker.js +0 -1
  106. package/dist/middleware/interceptors/circuit-breaker.js.map +1 -1
  107. package/dist/middleware/interceptors/envelope.d.ts +176 -0
  108. package/dist/middleware/interceptors/envelope.d.ts.map +1 -0
  109. package/dist/middleware/interceptors/envelope.int.test.d.ts +5 -0
  110. package/dist/middleware/interceptors/envelope.int.test.d.ts.map +1 -0
  111. package/dist/middleware/interceptors/envelope.int.test.js +409 -0
  112. package/dist/middleware/interceptors/envelope.int.test.js.map +1 -0
  113. package/dist/middleware/interceptors/envelope.js +294 -0
  114. package/dist/middleware/interceptors/envelope.js.map +1 -0
  115. package/dist/middleware/interceptors/index.d.ts +2 -0
  116. package/dist/middleware/interceptors/index.d.ts.map +1 -1
  117. package/dist/middleware/interceptors/index.js +2 -0
  118. package/dist/middleware/interceptors/index.js.map +1 -1
  119. package/dist/middleware/types.d.ts +25 -0
  120. package/dist/middleware/types.d.ts.map +1 -1
  121. package/dist/rate-limit/drivers/drivers.int.test.d.ts +7 -0
  122. package/dist/rate-limit/drivers/drivers.int.test.d.ts.map +1 -0
  123. package/dist/rate-limit/drivers/drivers.int.test.js +466 -0
  124. package/dist/rate-limit/drivers/drivers.int.test.js.map +1 -0
  125. package/dist/server/builder.d.ts.map +1 -1
  126. package/dist/server/builder.int.test.js +41 -0
  127. package/dist/server/builder.int.test.js.map +1 -1
  128. package/dist/server/builder.js +72 -15
  129. package/dist/server/builder.js.map +1 -1
  130. package/dist/server/channel-utils.d.ts +4 -1
  131. package/dist/server/channel-utils.d.ts.map +1 -1
  132. package/dist/server/channel-utils.js +12 -2
  133. package/dist/server/channel-utils.js.map +1 -1
  134. package/dist/server/errors.d.ts.map +1 -1
  135. package/dist/server/errors.js +0 -22
  136. package/dist/server/errors.js.map +1 -1
  137. package/dist/server/fs-routes/watcher.js +1 -1
  138. package/dist/server/fs-routes/watcher.js.map +1 -1
  139. package/dist/server/index.d.ts +1 -1
  140. package/dist/server/index.d.ts.map +1 -1
  141. package/dist/server/index.js.map +1 -1
  142. package/dist/server/types.d.ts +37 -33
  143. package/dist/server/types.d.ts.map +1 -1
  144. package/dist/tracing/interceptor.d.ts.map +1 -1
  145. package/dist/tracing/interceptor.js +4 -5
  146. package/dist/tracing/interceptor.js.map +1 -1
  147. package/dist/types/envelope.d.ts +1 -1
  148. package/dist/types/envelope.d.ts.map +1 -1
  149. package/dist/types/envelope.js.map +1 -1
  150. package/dist/types/handlers.d.ts +8 -0
  151. package/dist/types/handlers.d.ts.map +1 -1
  152. package/dist/ui/core/index.d.ts +7 -0
  153. package/dist/ui/core/index.d.ts.map +1 -0
  154. package/dist/ui/docs/generators/content-types.d.ts +10 -0
  155. package/dist/ui/docs/generators/content-types.d.ts.map +1 -0
  156. package/dist/ui/docs/generators/errors-types.d.ts +409 -0
  157. package/dist/ui/docs/generators/errors-types.d.ts.map +1 -0
  158. package/dist/ui/docs/generators/errors.d.ts +88 -0
  159. package/dist/ui/docs/generators/errors.d.ts.map +1 -0
  160. package/dist/ui/docs/generators/grpc-generator.d.ts +53 -0
  161. package/dist/ui/docs/generators/grpc-generator.d.ts.map +1 -0
  162. package/dist/ui/docs/generators/http-generator.d.ts +49 -0
  163. package/dist/ui/docs/generators/http-generator.d.ts.map +1 -0
  164. package/dist/ui/docs/generators/index.d.ts +17 -0
  165. package/dist/ui/docs/generators/index.d.ts.map +1 -0
  166. package/dist/ui/docs/generators/jsonrpc-generator.d.ts +53 -0
  167. package/dist/ui/docs/generators/jsonrpc-generator.d.ts.map +1 -0
  168. package/dist/ui/docs/generators/schema-converter.d.ts +117 -0
  169. package/dist/ui/docs/generators/schema-converter.d.ts.map +1 -0
  170. package/dist/ui/docs/generators/streams-generator.d.ts +85 -0
  171. package/dist/ui/docs/generators/streams-generator.d.ts.map +1 -0
  172. package/dist/ui/docs/generators/tcp-generator.d.ts +133 -0
  173. package/dist/ui/docs/generators/tcp-generator.d.ts.map +1 -0
  174. package/dist/ui/docs/generators/udp-generator.d.ts +119 -0
  175. package/dist/ui/docs/generators/udp-generator.d.ts.map +1 -0
  176. package/dist/ui/docs/generators/usd-generator.d.ts +182 -0
  177. package/dist/ui/docs/generators/usd-generator.d.ts.map +1 -0
  178. package/dist/ui/docs/generators/websocket-generator.d.ts +49 -0
  179. package/dist/ui/docs/generators/websocket-generator.d.ts.map +1 -0
  180. package/dist/ui/docs/index.d.ts +31 -0
  181. package/dist/ui/docs/index.d.ts.map +1 -0
  182. package/dist/ui/docs/usd-middleware.d.ts +157 -0
  183. package/dist/ui/docs/usd-middleware.d.ts.map +1 -0
  184. package/dist/ui/errors/factories.d.ts +142 -0
  185. package/dist/ui/errors/factories.d.ts.map +1 -0
  186. package/dist/ui/errors/index.d.ts +9 -0
  187. package/dist/ui/errors/index.d.ts.map +1 -0
  188. package/dist/ui/server/fs-routes/index.d.ts +66 -0
  189. package/dist/ui/server/fs-routes/index.d.ts.map +1 -0
  190. package/dist/ui/server/fs-routes/loader.d.ts +28 -0
  191. package/dist/ui/server/fs-routes/loader.d.ts.map +1 -0
  192. package/dist/ui/server/fs-routes/middleware-processor.d.ts +19 -0
  193. package/dist/ui/server/fs-routes/middleware-processor.d.ts.map +1 -0
  194. package/dist/ui/server/fs-routes/resources/index.d.ts +8 -0
  195. package/dist/ui/server/fs-routes/resources/index.d.ts.map +1 -0
  196. package/dist/ui/server/fs-routes/resources/loader.d.ts +16 -0
  197. package/dist/ui/server/fs-routes/resources/loader.d.ts.map +1 -0
  198. package/dist/ui/server/fs-routes/resources/types.d.ts +256 -0
  199. package/dist/ui/server/fs-routes/resources/types.d.ts.map +1 -0
  200. package/dist/ui/server/fs-routes/rest/index.d.ts +8 -0
  201. package/dist/ui/server/fs-routes/rest/index.d.ts.map +1 -0
  202. package/dist/ui/server/fs-routes/rest/loader.d.ts +11 -0
  203. package/dist/ui/server/fs-routes/rest/loader.d.ts.map +1 -0
  204. package/dist/ui/server/fs-routes/rest/types.d.ts +288 -0
  205. package/dist/ui/server/fs-routes/rest/types.d.ts.map +1 -0
  206. package/dist/ui/server/fs-routes/tcp/index.d.ts +8 -0
  207. package/dist/ui/server/fs-routes/tcp/index.d.ts.map +1 -0
  208. package/dist/ui/server/fs-routes/tcp/loader.d.ts +15 -0
  209. package/dist/ui/server/fs-routes/tcp/loader.d.ts.map +1 -0
  210. package/dist/ui/server/fs-routes/tcp/types.d.ts +215 -0
  211. package/dist/ui/server/fs-routes/tcp/types.d.ts.map +1 -0
  212. package/dist/ui/server/fs-routes/types.d.ts +437 -0
  213. package/dist/ui/server/fs-routes/types.d.ts.map +1 -0
  214. package/dist/ui/server/fs-routes/udp/index.d.ts +8 -0
  215. package/dist/ui/server/fs-routes/udp/index.d.ts.map +1 -0
  216. package/dist/ui/server/fs-routes/udp/loader.d.ts +15 -0
  217. package/dist/ui/server/fs-routes/udp/loader.d.ts.map +1 -0
  218. package/dist/ui/server/fs-routes/udp/types.d.ts +164 -0
  219. package/dist/ui/server/fs-routes/udp/types.d.ts.map +1 -0
  220. package/dist/ui/server/fs-routes/watcher.d.ts +34 -0
  221. package/dist/ui/server/fs-routes/watcher.d.ts.map +1 -0
  222. package/dist/ui/types/envelope.d.ts +1 -1
  223. package/dist/ui/types/envelope.d.ts.map +1 -1
  224. package/dist/ui/types/handlers.d.ts +8 -0
  225. package/dist/ui/types/handlers.d.ts.map +1 -1
  226. package/dist/ui/usd/builder/document.d.ts.map +1 -1
  227. package/dist/ui/usd/export/openapi.d.ts.map +1 -1
  228. package/dist/ui/usd/parser/normalize.d.ts.map +1 -1
  229. package/dist/ui/usd/spec/types.d.ts +14 -20
  230. package/dist/ui/usd/spec/types.d.ts.map +1 -1
  231. package/dist/ui/usd/utils/refs.d.ts.map +1 -1
  232. package/dist/ui/usd/validator/index.d.ts.map +1 -1
  233. package/dist/ui/usd/validator/schema.d.ts.map +1 -1
  234. package/dist/ui/usd/validator/semantic.d.ts.map +1 -1
  235. package/dist/ui/utils/logger.d.ts +15 -0
  236. package/dist/ui/utils/logger.d.ts.map +1 -0
  237. package/dist/usd/builder/document.d.ts.map +1 -1
  238. package/dist/usd/builder/document.js.map +1 -1
  239. package/dist/usd/export/openapi.d.ts.map +1 -1
  240. package/dist/usd/export/openapi.js +2 -4
  241. package/dist/usd/export/openapi.js.map +1 -1
  242. package/dist/usd/parser/normalize.d.ts.map +1 -1
  243. package/dist/usd/parser/normalize.js +0 -1
  244. package/dist/usd/parser/normalize.js.map +1 -1
  245. package/dist/usd/usd.int.test.d.ts +10 -0
  246. package/dist/usd/usd.int.test.d.ts.map +1 -0
  247. package/dist/usd/usd.int.test.js +719 -0
  248. package/dist/usd/usd.int.test.js.map +1 -0
  249. package/dist/usd/utils/refs.d.ts.map +1 -1
  250. package/dist/usd/validator/index.d.ts.map +1 -1
  251. package/dist/usd/validator/index.js.map +1 -1
  252. package/dist/usd/validator/schema.d.ts.map +1 -1
  253. package/dist/usd/validator/schema.js.map +1 -1
  254. package/dist/usd/validator/semantic.d.ts.map +1 -1
  255. package/dist/usd/validator/semantic.js.map +1 -1
  256. package/package.json +1 -1
  257. package/dist/middleware/rate-limit.d.ts +0 -105
  258. package/dist/middleware/rate-limit.d.ts.map +0 -1
  259. package/dist/middleware/rate-limit.int.test.d.ts +0 -5
  260. package/dist/middleware/rate-limit.int.test.d.ts.map +0 -1
  261. package/dist/middleware/rate-limit.int.test.js +0 -350
  262. package/dist/middleware/rate-limit.int.test.js.map +0 -1
  263. package/dist/middleware/rate-limit.js +0 -206
  264. package/dist/middleware/rate-limit.js.map +0 -1
  265. package/dist/openapi/index.d.ts +0 -9
  266. package/dist/openapi/index.d.ts.map +0 -1
  267. package/dist/openapi/index.js +0 -9
  268. package/dist/openapi/index.js.map +0 -1
@@ -0,0 +1,698 @@
1
+ /**
2
+ * GraphQL Module Integration Tests
3
+ *
4
+ * Tests for:
5
+ * - Schema Generator (mapping modes, type conversion)
6
+ * - Adapter (lifecycle, query execution)
7
+ * - Middleware (integration)
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
10
+ import { z } from 'zod';
11
+ import { createRegistry } from '../core/registry.js';
12
+ import { createRouter } from '../core/router.js';
13
+ import { createSchemaRegistry } from '../validation/index.js';
14
+ import { generateGraphQLSchema, GraphQLJSON, GraphQLDateTime } from './schema-generator.js';
15
+ import { createGraphQLAdapter, createGraphQLMiddleware } from './adapter.js';
16
+ import { createServer as createHttpServer } from 'node:http';
17
+ const TEST_PORT = 23463;
18
+ // Default config for GraphQL adapter tests (satisfies Required fields)
19
+ const DEFAULT_CONFIG = {
20
+ path: '/graphql',
21
+ maxBodySize: 1024 * 1024,
22
+ timeout: 30000,
23
+ playground: false,
24
+ introspection: true,
25
+ };
26
+ // =============================================================================
27
+ // Schema Generator Tests
28
+ // =============================================================================
29
+ describe('GraphQL Schema Generator', () => {
30
+ let registry;
31
+ let schemaRegistry;
32
+ beforeEach(() => {
33
+ registry = createRegistry();
34
+ schemaRegistry = createSchemaRegistry();
35
+ });
36
+ describe('procedure mapping modes', () => {
37
+ describe('prefix mapping (default)', () => {
38
+ it('should map procedures with get prefix to queries', () => {
39
+ registry.procedure('users.get', async () => ({ id: '1' }));
40
+ registry.procedure('users.getById', async () => ({ id: '1' }));
41
+ registry.procedure('users.list', async () => []);
42
+ registry.procedure('users.find', async () => []);
43
+ schemaRegistry.register('users.get', { output: z.object({ id: z.string() }) });
44
+ schemaRegistry.register('users.getById', { output: z.object({ id: z.string() }) });
45
+ schemaRegistry.register('users.list', { output: z.array(z.string()) });
46
+ schemaRegistry.register('users.find', { output: z.array(z.string()) });
47
+ const result = generateGraphQLSchema({
48
+ registry,
49
+ schemaRegistry,
50
+ options: { procedureMapping: 'prefix' },
51
+ });
52
+ expect(result.queries).toContain('users.get');
53
+ expect(result.queries).toContain('users.getById');
54
+ expect(result.queries).toContain('users.list');
55
+ expect(result.queries).toContain('users.find');
56
+ expect(result.mutations).toHaveLength(0);
57
+ });
58
+ it('should map procedures with write prefixes to mutations', () => {
59
+ registry.procedure('users.create', async () => ({ id: '1' }));
60
+ registry.procedure('users.update', async () => ({ id: '1' }));
61
+ registry.procedure('users.delete', async () => true);
62
+ registry.procedure('users.save', async () => ({ id: '1' }));
63
+ schemaRegistry.register('users.create', { output: z.object({ id: z.string() }) });
64
+ schemaRegistry.register('users.update', { output: z.object({ id: z.string() }) });
65
+ schemaRegistry.register('users.delete', { output: z.boolean() });
66
+ schemaRegistry.register('users.save', { output: z.object({ id: z.string() }) });
67
+ const result = generateGraphQLSchema({
68
+ registry,
69
+ schemaRegistry,
70
+ options: { procedureMapping: 'prefix' },
71
+ });
72
+ expect(result.mutations).toContain('users.create');
73
+ expect(result.mutations).toContain('users.update');
74
+ expect(result.mutations).toContain('users.delete');
75
+ expect(result.mutations).toContain('users.save');
76
+ // Only _health query is auto-added when there are no user queries
77
+ expect(result.queries).toEqual(['_health']);
78
+ });
79
+ it('should support custom query prefixes', () => {
80
+ registry.procedure('users.fetch', async () => []);
81
+ registry.procedure('users.load', async () => []);
82
+ schemaRegistry.register('users.fetch', { output: z.array(z.string()) });
83
+ schemaRegistry.register('users.load', { output: z.array(z.string()) });
84
+ const result = generateGraphQLSchema({
85
+ registry,
86
+ schemaRegistry,
87
+ options: {
88
+ procedureMapping: 'prefix',
89
+ queryPrefixes: ['fetch', 'load'],
90
+ },
91
+ });
92
+ expect(result.queries).toContain('users.fetch');
93
+ expect(result.queries).toContain('users.load');
94
+ });
95
+ });
96
+ describe('meta mapping', () => {
97
+ it('should use graphql meta to determine type', () => {
98
+ registry.procedure('users.get', async () => ({ id: '1' }), {
99
+ graphql: { type: 'query' },
100
+ });
101
+ registry.procedure('users.create', async () => ({ id: '2' }), {
102
+ graphql: { type: 'mutation' },
103
+ });
104
+ schemaRegistry.register('users.get', { output: z.object({ id: z.string() }) });
105
+ schemaRegistry.register('users.create', { output: z.object({ id: z.string() }) });
106
+ const result = generateGraphQLSchema({
107
+ registry,
108
+ schemaRegistry,
109
+ options: { procedureMapping: 'meta' },
110
+ });
111
+ expect(result.queries).toContain('users.get');
112
+ expect(result.mutations).toContain('users.create');
113
+ });
114
+ it('should default to mutation when meta is missing', () => {
115
+ registry.procedure('users.action', async () => true);
116
+ schemaRegistry.register('users.action', { output: z.boolean() });
117
+ const result = generateGraphQLSchema({
118
+ registry,
119
+ schemaRegistry,
120
+ options: { procedureMapping: 'meta' },
121
+ });
122
+ expect(result.mutations).toContain('users.action');
123
+ expect(result.queries).not.toContain('users.action');
124
+ });
125
+ });
126
+ describe('pattern mapping', () => {
127
+ it('should use regex patterns to determine type', () => {
128
+ registry.procedure('users.list', async () => []);
129
+ registry.procedure('posts.get', async () => ({ id: '1' }));
130
+ registry.procedure('orders.create', async () => ({ id: '1' }));
131
+ schemaRegistry.register('users.list', { output: z.array(z.string()) });
132
+ schemaRegistry.register('posts.get', { output: z.object({ id: z.string() }) });
133
+ schemaRegistry.register('orders.create', { output: z.object({ id: z.string() }) });
134
+ const result = generateGraphQLSchema({
135
+ registry,
136
+ schemaRegistry,
137
+ options: { procedureMapping: 'prefix' },
138
+ });
139
+ // By default, prefix mode uses prefixes like get, list, find
140
+ expect(result.queries.length).toBeGreaterThanOrEqual(0);
141
+ });
142
+ });
143
+ });
144
+ describe('type conversion', () => {
145
+ it('should convert basic Zod types', () => {
146
+ registry.procedure('test.basic', async () => ({
147
+ str: 'hello',
148
+ num: 42,
149
+ bool: true,
150
+ }));
151
+ schemaRegistry.register('test.basic', {
152
+ output: z.object({
153
+ str: z.string(),
154
+ num: z.number(),
155
+ bool: z.boolean(),
156
+ }),
157
+ });
158
+ const result = generateGraphQLSchema({
159
+ registry,
160
+ schemaRegistry,
161
+ });
162
+ expect(result.schema).toBeDefined();
163
+ });
164
+ it('should convert array types', () => {
165
+ registry.procedure('test.arrays', async () => []);
166
+ schemaRegistry.register('test.arrays', {
167
+ output: z.array(z.object({
168
+ id: z.string(),
169
+ tags: z.array(z.string()),
170
+ })),
171
+ });
172
+ const result = generateGraphQLSchema({
173
+ registry,
174
+ schemaRegistry,
175
+ });
176
+ expect(result.schema).toBeDefined();
177
+ });
178
+ it('should convert optional types', () => {
179
+ registry.procedure('test.optional', async () => ({}));
180
+ schemaRegistry.register('test.optional', {
181
+ output: z.object({
182
+ required: z.string(),
183
+ optional: z.string().optional(),
184
+ nullable: z.string().nullable(),
185
+ }),
186
+ });
187
+ const result = generateGraphQLSchema({
188
+ registry,
189
+ schemaRegistry,
190
+ });
191
+ expect(result.schema).toBeDefined();
192
+ });
193
+ it('should convert enum types', () => {
194
+ const StatusEnum = z.enum(['ACTIVE', 'INACTIVE', 'PENDING']);
195
+ registry.procedure('test.enum', async () => ({ status: 'ACTIVE' }));
196
+ schemaRegistry.register('test.enum', {
197
+ output: z.object({
198
+ status: StatusEnum,
199
+ }),
200
+ });
201
+ const result = generateGraphQLSchema({
202
+ registry,
203
+ schemaRegistry,
204
+ });
205
+ expect(result.schema).toBeDefined();
206
+ });
207
+ it('should handle input schemas', () => {
208
+ registry.procedure('users.create', async (input) => ({
209
+ id: '1',
210
+ name: input.name,
211
+ }));
212
+ schemaRegistry.register('users.create', {
213
+ input: z.object({
214
+ name: z.string(),
215
+ email: z.string().email(),
216
+ age: z.number().int().optional(),
217
+ }),
218
+ output: z.object({
219
+ id: z.string(),
220
+ name: z.string(),
221
+ }),
222
+ });
223
+ const result = generateGraphQLSchema({
224
+ registry,
225
+ schemaRegistry,
226
+ });
227
+ expect(result.schema).toBeDefined();
228
+ });
229
+ });
230
+ describe('subscriptions', () => {
231
+ it('should map streams to subscriptions', () => {
232
+ registry.stream('events.watch', async function* () {
233
+ yield { type: 'created' };
234
+ });
235
+ schemaRegistry.register('events.watch', {
236
+ output: z.object({ type: z.string() }),
237
+ });
238
+ const result = generateGraphQLSchema({
239
+ registry,
240
+ schemaRegistry,
241
+ });
242
+ expect(result.subscriptions).toContain('events.watch');
243
+ });
244
+ });
245
+ describe('events', () => {
246
+ it('should not include events by default', () => {
247
+ registry.event('notifications.sent', async () => { });
248
+ const result = generateGraphQLSchema({
249
+ registry,
250
+ schemaRegistry,
251
+ options: { includeEvents: false },
252
+ });
253
+ expect(result.mutations).not.toContain('notifications.sent');
254
+ });
255
+ it('should include events when enabled', () => {
256
+ registry.event('notifications.sent', async () => { });
257
+ schemaRegistry.register('notifications.sent', {
258
+ input: z.object({ message: z.string() }),
259
+ });
260
+ const result = generateGraphQLSchema({
261
+ registry,
262
+ schemaRegistry,
263
+ options: { includeEvents: true },
264
+ });
265
+ expect(result.mutations).toContain('notifications.sent');
266
+ });
267
+ });
268
+ describe('name generation', () => {
269
+ it('should use default field name generator', () => {
270
+ registry.procedure('users.get-by-id', async () => ({ id: '1' }));
271
+ registry.procedure('posts.find_all', async () => []);
272
+ schemaRegistry.register('users.get-by-id', { output: z.object({ id: z.string() }) });
273
+ schemaRegistry.register('posts.find_all', { output: z.array(z.string()) });
274
+ const result = generateGraphQLSchema({
275
+ registry,
276
+ schemaRegistry,
277
+ });
278
+ // Field names should be camelCase
279
+ expect(result.schema).toBeDefined();
280
+ });
281
+ it('should support custom name generators', () => {
282
+ registry.procedure('users.get', async () => ({ id: '1' }));
283
+ schemaRegistry.register('users.get', { output: z.object({ id: z.string() }) });
284
+ const result = generateGraphQLSchema({
285
+ registry,
286
+ schemaRegistry,
287
+ options: {
288
+ fieldNameGenerator: (name) => `custom_${name.replace('.', '_')}`,
289
+ typeNameGenerator: (name) => `Custom${name.split('.').map(p => p[0].toUpperCase() + p.slice(1)).join('')}`,
290
+ },
291
+ });
292
+ expect(result.schema).toBeDefined();
293
+ });
294
+ });
295
+ describe('health check', () => {
296
+ it('should always include _health query', () => {
297
+ const result = generateGraphQLSchema({
298
+ registry,
299
+ schemaRegistry,
300
+ });
301
+ expect(result.queries).toContain('_health');
302
+ });
303
+ });
304
+ });
305
+ // =============================================================================
306
+ // Custom Scalars Tests
307
+ // =============================================================================
308
+ describe('GraphQL Custom Scalars', () => {
309
+ describe('GraphQLJSON', () => {
310
+ it('should serialize JSON values', () => {
311
+ const value = { foo: 'bar', num: 42 };
312
+ expect(GraphQLJSON.serialize(value)).toEqual(value);
313
+ });
314
+ it('should parse JSON values', () => {
315
+ const value = { foo: 'bar' };
316
+ expect(GraphQLJSON.parseValue(value)).toEqual(value);
317
+ });
318
+ });
319
+ describe('GraphQLDateTime', () => {
320
+ it('should serialize Date to ISO string', () => {
321
+ const date = new Date('2024-01-15T10:30:00Z');
322
+ expect(GraphQLDateTime.serialize(date)).toBe('2024-01-15T10:30:00.000Z');
323
+ });
324
+ it('should parse ISO string to Date', () => {
325
+ const result = GraphQLDateTime.parseValue('2024-01-15T10:30:00Z');
326
+ expect(result).toBeInstanceOf(Date);
327
+ });
328
+ });
329
+ });
330
+ // =============================================================================
331
+ // Adapter Tests
332
+ // =============================================================================
333
+ describe('GraphQL Adapter', () => {
334
+ let registry;
335
+ let router;
336
+ let schemaRegistry;
337
+ let adapter = null;
338
+ beforeEach(() => {
339
+ registry = createRegistry();
340
+ router = createRouter(registry);
341
+ schemaRegistry = createSchemaRegistry();
342
+ // Register some procedures
343
+ registry.procedure('users.get', async () => ({ id: '1', name: 'Test' }));
344
+ schemaRegistry.register('users.get', {
345
+ output: z.object({ id: z.string(), name: z.string() }),
346
+ });
347
+ });
348
+ afterEach(async () => {
349
+ if (adapter) {
350
+ await adapter.stop();
351
+ adapter = null;
352
+ }
353
+ });
354
+ describe('lifecycle', () => {
355
+ it('should start and stop', async () => {
356
+ adapter = createGraphQLAdapter({
357
+ router,
358
+ registry,
359
+ schemaRegistry,
360
+ host: '127.0.0.1',
361
+ port: TEST_PORT,
362
+ config: { ...DEFAULT_CONFIG },
363
+ });
364
+ await adapter.start();
365
+ expect(adapter.address).toBeDefined();
366
+ expect(adapter.address?.port).toBe(TEST_PORT);
367
+ expect(adapter.address?.path).toBe('/graphql');
368
+ await adapter.stop();
369
+ expect(adapter.address).toBeNull();
370
+ });
371
+ it('should expose schema', async () => {
372
+ adapter = createGraphQLAdapter({
373
+ router,
374
+ registry,
375
+ schemaRegistry,
376
+ host: '127.0.0.1',
377
+ port: TEST_PORT,
378
+ config: { ...DEFAULT_CONFIG },
379
+ });
380
+ await adapter.start();
381
+ expect(adapter.schema).toBeDefined();
382
+ });
383
+ it('should expose schemaInfo', async () => {
384
+ adapter = createGraphQLAdapter({
385
+ router,
386
+ registry,
387
+ schemaRegistry,
388
+ host: '127.0.0.1',
389
+ port: TEST_PORT,
390
+ config: { ...DEFAULT_CONFIG },
391
+ });
392
+ await adapter.start();
393
+ expect(adapter.schemaInfo).toBeDefined();
394
+ expect(adapter.schemaInfo?.queries).toContain('users.get');
395
+ });
396
+ });
397
+ describe('query execution', () => {
398
+ it('should execute queries via HTTP', async () => {
399
+ adapter = createGraphQLAdapter({
400
+ router,
401
+ registry,
402
+ schemaRegistry,
403
+ host: '127.0.0.1',
404
+ port: TEST_PORT,
405
+ config: { ...DEFAULT_CONFIG },
406
+ });
407
+ await adapter.start();
408
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
409
+ method: 'POST',
410
+ headers: { 'Content-Type': 'application/json' },
411
+ body: JSON.stringify({
412
+ query: '{ usersGet { id name } }',
413
+ }),
414
+ });
415
+ expect(response.status).toBe(200);
416
+ const result = await response.json();
417
+ expect(result.data).toBeDefined();
418
+ expect(result.data.usersGet).toEqual({ id: '1', name: 'Test' });
419
+ });
420
+ it('should handle _health query when no other queries exist', async () => {
421
+ // Create a fresh registry with only mutations (no query-prefixed procedures)
422
+ const mutationsOnlyRegistry = createRegistry();
423
+ const mutationsOnlyRouter = createRouter(mutationsOnlyRegistry);
424
+ const mutationsOnlySchemaRegistry = createSchemaRegistry();
425
+ // Register only a mutation (no query prefix)
426
+ mutationsOnlyRegistry.procedure('users.create', async () => ({ id: '1' }));
427
+ mutationsOnlySchemaRegistry.register('users.create', {
428
+ output: z.object({ id: z.string() }),
429
+ });
430
+ adapter = createGraphQLAdapter({
431
+ router: mutationsOnlyRouter,
432
+ registry: mutationsOnlyRegistry,
433
+ schemaRegistry: mutationsOnlySchemaRegistry,
434
+ host: '127.0.0.1',
435
+ port: TEST_PORT,
436
+ config: { ...DEFAULT_CONFIG },
437
+ });
438
+ await adapter.start();
439
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
440
+ method: 'POST',
441
+ headers: { 'Content-Type': 'application/json' },
442
+ body: JSON.stringify({
443
+ query: '{ _health }',
444
+ }),
445
+ });
446
+ expect(response.status).toBe(200);
447
+ const result = await response.json();
448
+ expect(result.data._health).toBe(true);
449
+ });
450
+ it('should return errors for invalid queries', async () => {
451
+ adapter = createGraphQLAdapter({
452
+ router,
453
+ registry,
454
+ schemaRegistry,
455
+ host: '127.0.0.1',
456
+ port: TEST_PORT,
457
+ config: { ...DEFAULT_CONFIG },
458
+ });
459
+ await adapter.start();
460
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
461
+ method: 'POST',
462
+ headers: { 'Content-Type': 'application/json' },
463
+ body: JSON.stringify({
464
+ query: '{ nonExistent }',
465
+ }),
466
+ });
467
+ expect(response.status).toBe(200);
468
+ const result = await response.json();
469
+ expect(result.errors).toBeDefined();
470
+ expect(result.errors.length).toBeGreaterThan(0);
471
+ });
472
+ });
473
+ describe('CORS', () => {
474
+ it('should handle CORS preflight requests', async () => {
475
+ adapter = createGraphQLAdapter({
476
+ router,
477
+ registry,
478
+ schemaRegistry,
479
+ host: '127.0.0.1',
480
+ port: TEST_PORT,
481
+ config: { ...DEFAULT_CONFIG, cors: true },
482
+ });
483
+ await adapter.start();
484
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
485
+ method: 'OPTIONS',
486
+ });
487
+ expect(response.status).toBe(204);
488
+ expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
489
+ });
490
+ it('should disable CORS when cors is false', async () => {
491
+ adapter = createGraphQLAdapter({
492
+ router,
493
+ registry,
494
+ schemaRegistry,
495
+ host: '127.0.0.1',
496
+ port: TEST_PORT,
497
+ config: { ...DEFAULT_CONFIG, cors: false },
498
+ });
499
+ await adapter.start();
500
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
501
+ method: 'OPTIONS',
502
+ });
503
+ // Without CORS, OPTIONS still returns 204 but without CORS headers
504
+ expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
505
+ });
506
+ });
507
+ describe('path handling', () => {
508
+ it('should return 404 for non-graphql paths', async () => {
509
+ adapter = createGraphQLAdapter({
510
+ router,
511
+ registry,
512
+ schemaRegistry,
513
+ host: '127.0.0.1',
514
+ port: TEST_PORT,
515
+ config: { ...DEFAULT_CONFIG },
516
+ });
517
+ await adapter.start();
518
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/other`, {
519
+ method: 'POST',
520
+ headers: { 'Content-Type': 'application/json' },
521
+ body: JSON.stringify({ query: '{ _health }' }),
522
+ });
523
+ expect(response.status).toBe(404);
524
+ });
525
+ });
526
+ });
527
+ // =============================================================================
528
+ // Middleware Tests
529
+ // =============================================================================
530
+ describe('GraphQL Middleware', () => {
531
+ let registry;
532
+ let router;
533
+ let schemaRegistry;
534
+ beforeEach(() => {
535
+ registry = createRegistry();
536
+ router = createRouter(registry);
537
+ schemaRegistry = createSchemaRegistry();
538
+ registry.procedure('test.ping', async () => ({ pong: true }));
539
+ schemaRegistry.register('test.ping', {
540
+ output: z.object({ pong: z.boolean() }),
541
+ });
542
+ });
543
+ it('should create middleware', () => {
544
+ const middleware = createGraphQLMiddleware({
545
+ router,
546
+ registry,
547
+ schemaRegistry,
548
+ config: { ...DEFAULT_CONFIG },
549
+ });
550
+ expect(middleware.middleware).toBeInstanceOf(Function);
551
+ expect(middleware.schema).toBeDefined();
552
+ });
553
+ it('should expose schema and schemaInfo', () => {
554
+ const middleware = createGraphQLMiddleware({
555
+ router,
556
+ registry,
557
+ schemaRegistry,
558
+ config: { ...DEFAULT_CONFIG },
559
+ });
560
+ expect(middleware.schema).toBeDefined();
561
+ expect(middleware.schemaInfo).toBeDefined();
562
+ // test.ping doesn't have a query prefix (get, list, find, etc) so it becomes a mutation
563
+ expect(middleware.schemaInfo?.mutations).toContain('test.ping');
564
+ });
565
+ it('should integrate with HTTP server', async () => {
566
+ const middleware = createGraphQLMiddleware({
567
+ router,
568
+ registry,
569
+ schemaRegistry,
570
+ config: { ...DEFAULT_CONFIG },
571
+ });
572
+ const server = createHttpServer(async (req, res) => {
573
+ const handled = await middleware.middleware(req, res);
574
+ if (!handled) {
575
+ res.writeHead(404);
576
+ res.end('Not found');
577
+ }
578
+ });
579
+ await new Promise((resolve) => {
580
+ server.listen(TEST_PORT + 1, '127.0.0.1', resolve);
581
+ });
582
+ try {
583
+ // test.ping is a mutation (no query prefix), so we need mutation syntax
584
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT + 1}/graphql`, {
585
+ method: 'POST',
586
+ headers: { 'Content-Type': 'application/json' },
587
+ body: JSON.stringify({
588
+ query: 'mutation { testPing { pong } }',
589
+ }),
590
+ });
591
+ expect(response.status).toBe(200);
592
+ const result = await response.json();
593
+ expect(result.data.testPing.pong).toBe(true);
594
+ }
595
+ finally {
596
+ await new Promise((resolve) => server.close(() => resolve()));
597
+ }
598
+ });
599
+ it('should return false for non-graphql paths', async () => {
600
+ const middleware = createGraphQLMiddleware({
601
+ router,
602
+ registry,
603
+ schemaRegistry,
604
+ config: { ...DEFAULT_CONFIG },
605
+ });
606
+ const mockReq = {
607
+ url: '/other',
608
+ headers: { host: 'localhost' },
609
+ };
610
+ const mockRes = {};
611
+ const handled = await middleware.middleware(mockReq, mockRes);
612
+ expect(handled).toBe(false);
613
+ });
614
+ });
615
+ // =============================================================================
616
+ // Error Handling Tests
617
+ // =============================================================================
618
+ describe('GraphQL Error Handling', () => {
619
+ let registry;
620
+ let router;
621
+ let schemaRegistry;
622
+ let adapter = null;
623
+ beforeEach(() => {
624
+ registry = createRegistry();
625
+ router = createRouter(registry);
626
+ schemaRegistry = createSchemaRegistry();
627
+ });
628
+ afterEach(async () => {
629
+ if (adapter) {
630
+ await adapter.stop();
631
+ adapter = null;
632
+ }
633
+ });
634
+ it('should handle procedure errors gracefully', async () => {
635
+ registry.procedure('error.throw', async () => {
636
+ throw new Error('Intentional error');
637
+ });
638
+ schemaRegistry.register('error.throw', {
639
+ output: z.object({ result: z.string() }),
640
+ });
641
+ adapter = createGraphQLAdapter({
642
+ router,
643
+ registry,
644
+ schemaRegistry,
645
+ host: '127.0.0.1',
646
+ port: TEST_PORT,
647
+ config: { ...DEFAULT_CONFIG },
648
+ });
649
+ await adapter.start();
650
+ // error.throw doesn't have a query prefix, so it's mapped to a mutation
651
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
652
+ method: 'POST',
653
+ headers: { 'Content-Type': 'application/json' },
654
+ body: JSON.stringify({
655
+ query: 'mutation { errorThrow { result } }',
656
+ }),
657
+ });
658
+ expect(response.status).toBe(200);
659
+ const result = await response.json();
660
+ expect(result.errors).toBeDefined();
661
+ expect(result.errors[0].message).toContain('Intentional error');
662
+ });
663
+ it('should handle invalid JSON body', async () => {
664
+ adapter = createGraphQLAdapter({
665
+ router,
666
+ registry,
667
+ schemaRegistry,
668
+ host: '127.0.0.1',
669
+ port: TEST_PORT,
670
+ config: { ...DEFAULT_CONFIG },
671
+ });
672
+ await adapter.start();
673
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
674
+ method: 'POST',
675
+ headers: { 'Content-Type': 'application/json' },
676
+ body: '{ invalid json',
677
+ });
678
+ expect(response.status).toBe(400);
679
+ const result = await response.json();
680
+ expect(result.errors).toBeDefined();
681
+ });
682
+ it('should handle method not allowed', async () => {
683
+ adapter = createGraphQLAdapter({
684
+ router,
685
+ registry,
686
+ schemaRegistry,
687
+ host: '127.0.0.1',
688
+ port: TEST_PORT,
689
+ config: { ...DEFAULT_CONFIG },
690
+ });
691
+ await adapter.start();
692
+ const response = await fetch(`http://127.0.0.1:${TEST_PORT}/graphql`, {
693
+ method: 'PUT',
694
+ });
695
+ expect(response.status).toBe(405);
696
+ });
697
+ });
698
+ //# sourceMappingURL=graphql.int.test.js.map