@engjts/nexus 0.1.8 → 0.1.9

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 (205) hide show
  1. package/package.json +1 -1
  2. package/BENCHMARK_REPORT.md +0 -343
  3. package/documentation/01-getting-started.md +0 -240
  4. package/documentation/02-context.md +0 -335
  5. package/documentation/03-routing.md +0 -397
  6. package/documentation/04-middleware.md +0 -483
  7. package/documentation/05-validation.md +0 -514
  8. package/documentation/06-error-handling.md +0 -465
  9. package/documentation/07-performance.md +0 -364
  10. package/documentation/08-adapters.md +0 -470
  11. package/documentation/09-api-reference.md +0 -548
  12. package/documentation/10-examples.md +0 -582
  13. package/documentation/11-deployment.md +0 -477
  14. package/documentation/12-sentry.md +0 -620
  15. package/documentation/13-sentry-data-storage.md +0 -996
  16. package/documentation/14-sentry-data-reference.md +0 -457
  17. package/documentation/15-sentry-summary.md +0 -409
  18. package/documentation/16-alerts-system.md +0 -745
  19. package/documentation/17-alert-adapters.md +0 -696
  20. package/documentation/18-alerts-implementation-summary.md +0 -385
  21. package/documentation/19-class-based-routing.md +0 -840
  22. package/documentation/20-websocket-realtime.md +0 -813
  23. package/documentation/21-cache-system.md +0 -510
  24. package/documentation/22-job-queue.md +0 -772
  25. package/documentation/23-sentry-plugin.md +0 -551
  26. package/documentation/24-testing-utilities.md +0 -1287
  27. package/documentation/25-api-versioning.md +0 -533
  28. package/documentation/26-context-store.md +0 -607
  29. package/documentation/27-dependency-injection.md +0 -329
  30. package/documentation/28-lifecycle-hooks.md +0 -521
  31. package/documentation/29-package-structure.md +0 -196
  32. package/documentation/30-plugin-system.md +0 -414
  33. package/documentation/31-jwt-authentication.md +0 -597
  34. package/documentation/32-cli.md +0 -268
  35. package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
  36. package/documentation/ALERTS-INDEX.md +0 -330
  37. package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
  38. package/documentation/README.md +0 -178
  39. package/documentation/index.html +0 -34
  40. package/modern_framework_paper.md +0 -1870
  41. package/public/css/style.css +0 -87
  42. package/public/index.html +0 -34
  43. package/public/js/app.js +0 -27
  44. package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
  45. package/src/advanced/cache/MultiTierCache.ts +0 -194
  46. package/src/advanced/cache/RedisCacheStore.ts +0 -341
  47. package/src/advanced/cache/index.ts +0 -5
  48. package/src/advanced/cache/types.ts +0 -40
  49. package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
  50. package/src/advanced/graphql/index.ts +0 -22
  51. package/src/advanced/graphql/server.ts +0 -252
  52. package/src/advanced/graphql/types.ts +0 -42
  53. package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
  54. package/src/advanced/jobs/JobQueue.ts +0 -556
  55. package/src/advanced/jobs/RedisQueueStore.ts +0 -367
  56. package/src/advanced/jobs/index.ts +0 -5
  57. package/src/advanced/jobs/types.ts +0 -70
  58. package/src/advanced/observability/APMManager.ts +0 -163
  59. package/src/advanced/observability/AlertManager.ts +0 -109
  60. package/src/advanced/observability/MetricRegistry.ts +0 -151
  61. package/src/advanced/observability/ObservabilityCenter.ts +0 -304
  62. package/src/advanced/observability/StructuredLogger.ts +0 -154
  63. package/src/advanced/observability/TracingManager.ts +0 -117
  64. package/src/advanced/observability/adapters.ts +0 -304
  65. package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
  66. package/src/advanced/observability/index.ts +0 -11
  67. package/src/advanced/observability/types.ts +0 -174
  68. package/src/advanced/playground/extractPathParams.ts +0 -6
  69. package/src/advanced/playground/generateFieldExample.ts +0 -31
  70. package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
  71. package/src/advanced/playground/generateSummary.ts +0 -19
  72. package/src/advanced/playground/getTagFromPath.ts +0 -9
  73. package/src/advanced/playground/index.ts +0 -8
  74. package/src/advanced/playground/playground.ts +0 -250
  75. package/src/advanced/playground/types.ts +0 -49
  76. package/src/advanced/playground/zodToExample.ts +0 -16
  77. package/src/advanced/playground/zodToParams.ts +0 -15
  78. package/src/advanced/postman/buildAuth.ts +0 -31
  79. package/src/advanced/postman/buildBody.ts +0 -15
  80. package/src/advanced/postman/buildQueryParams.ts +0 -27
  81. package/src/advanced/postman/buildRequestItem.ts +0 -36
  82. package/src/advanced/postman/buildResponses.ts +0 -11
  83. package/src/advanced/postman/buildUrl.ts +0 -33
  84. package/src/advanced/postman/capitalize.ts +0 -4
  85. package/src/advanced/postman/generateCollection.ts +0 -59
  86. package/src/advanced/postman/generateEnvironment.ts +0 -34
  87. package/src/advanced/postman/generateExampleFromZod.ts +0 -21
  88. package/src/advanced/postman/generateFieldExample.ts +0 -45
  89. package/src/advanced/postman/generateName.ts +0 -20
  90. package/src/advanced/postman/generateUUID.ts +0 -11
  91. package/src/advanced/postman/getTagFromPath.ts +0 -10
  92. package/src/advanced/postman/index.ts +0 -28
  93. package/src/advanced/postman/postman.ts +0 -156
  94. package/src/advanced/postman/slugify.ts +0 -7
  95. package/src/advanced/postman/types.ts +0 -140
  96. package/src/advanced/realtime/index.ts +0 -18
  97. package/src/advanced/realtime/websocket.ts +0 -231
  98. package/src/advanced/sentry/index.ts +0 -1236
  99. package/src/advanced/sentry/types.ts +0 -355
  100. package/src/advanced/static/generateDirectoryListing.ts +0 -47
  101. package/src/advanced/static/generateETag.ts +0 -7
  102. package/src/advanced/static/getMimeType.ts +0 -9
  103. package/src/advanced/static/index.ts +0 -32
  104. package/src/advanced/static/isSafePath.ts +0 -13
  105. package/src/advanced/static/publicDir.ts +0 -21
  106. package/src/advanced/static/serveStatic.ts +0 -225
  107. package/src/advanced/static/spa.ts +0 -24
  108. package/src/advanced/static/types.ts +0 -159
  109. package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
  110. package/src/advanced/swagger/buildOperation.ts +0 -61
  111. package/src/advanced/swagger/buildParameters.ts +0 -61
  112. package/src/advanced/swagger/buildRequestBody.ts +0 -21
  113. package/src/advanced/swagger/buildResponses.ts +0 -54
  114. package/src/advanced/swagger/capitalize.ts +0 -5
  115. package/src/advanced/swagger/convertPath.ts +0 -9
  116. package/src/advanced/swagger/createSwagger.ts +0 -12
  117. package/src/advanced/swagger/generateOperationId.ts +0 -21
  118. package/src/advanced/swagger/generateSpec.ts +0 -105
  119. package/src/advanced/swagger/generateSummary.ts +0 -24
  120. package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
  121. package/src/advanced/swagger/generateThemeCss.ts +0 -53
  122. package/src/advanced/swagger/index.ts +0 -25
  123. package/src/advanced/swagger/swagger.ts +0 -237
  124. package/src/advanced/swagger/types.ts +0 -206
  125. package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
  126. package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
  127. package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
  128. package/src/advanced/testing/factory.ts +0 -509
  129. package/src/advanced/testing/harness.ts +0 -612
  130. package/src/advanced/testing/index.ts +0 -430
  131. package/src/advanced/testing/load-test.ts +0 -618
  132. package/src/advanced/testing/mock-server.ts +0 -498
  133. package/src/advanced/testing/mock.ts +0 -670
  134. package/src/cli/bin.ts +0 -9
  135. package/src/cli/cli.ts +0 -158
  136. package/src/cli/commands/add.ts +0 -178
  137. package/src/cli/commands/build.ts +0 -73
  138. package/src/cli/commands/create.ts +0 -166
  139. package/src/cli/commands/dev.ts +0 -85
  140. package/src/cli/commands/generate.ts +0 -99
  141. package/src/cli/commands/help.ts +0 -95
  142. package/src/cli/commands/init.ts +0 -91
  143. package/src/cli/commands/version.ts +0 -38
  144. package/src/cli/index.ts +0 -6
  145. package/src/cli/templates/generators.ts +0 -359
  146. package/src/cli/templates/index.ts +0 -680
  147. package/src/cli/utils/exec.ts +0 -52
  148. package/src/cli/utils/file-system.ts +0 -78
  149. package/src/cli/utils/logger.ts +0 -111
  150. package/src/core/adapter.ts +0 -88
  151. package/src/core/application.ts +0 -1453
  152. package/src/core/context-pool.ts +0 -79
  153. package/src/core/context.ts +0 -856
  154. package/src/core/index.ts +0 -94
  155. package/src/core/middleware.ts +0 -272
  156. package/src/core/performance/buffer-pool.ts +0 -108
  157. package/src/core/performance/middleware-optimizer.ts +0 -162
  158. package/src/core/plugin/PluginManager.ts +0 -435
  159. package/src/core/plugin/builder.ts +0 -358
  160. package/src/core/plugin/index.ts +0 -50
  161. package/src/core/plugin/types.ts +0 -214
  162. package/src/core/router/file-router.ts +0 -623
  163. package/src/core/router/index.ts +0 -260
  164. package/src/core/router/radix-tree.ts +0 -242
  165. package/src/core/serializer.ts +0 -397
  166. package/src/core/store/index.ts +0 -30
  167. package/src/core/store/registry.ts +0 -178
  168. package/src/core/store/request-store.ts +0 -240
  169. package/src/core/store/types.ts +0 -233
  170. package/src/core/types.ts +0 -616
  171. package/src/database/adapter.ts +0 -35
  172. package/src/database/adapters/index.ts +0 -1
  173. package/src/database/adapters/mysql.ts +0 -669
  174. package/src/database/database.ts +0 -70
  175. package/src/database/dialect.ts +0 -388
  176. package/src/database/index.ts +0 -12
  177. package/src/database/migrations.ts +0 -86
  178. package/src/database/optimizer.ts +0 -125
  179. package/src/database/query-builder.ts +0 -404
  180. package/src/database/realtime.ts +0 -53
  181. package/src/database/schema.ts +0 -71
  182. package/src/database/transactions.ts +0 -56
  183. package/src/database/types.ts +0 -87
  184. package/src/deployment/cluster.ts +0 -471
  185. package/src/deployment/config.ts +0 -454
  186. package/src/deployment/docker.ts +0 -599
  187. package/src/deployment/graceful-shutdown.ts +0 -373
  188. package/src/deployment/index.ts +0 -56
  189. package/src/index.ts +0 -281
  190. package/src/security/adapter.ts +0 -318
  191. package/src/security/auth/JWTPlugin.ts +0 -234
  192. package/src/security/auth/JWTProvider.ts +0 -316
  193. package/src/security/auth/adapter.ts +0 -12
  194. package/src/security/auth/jwt.ts +0 -234
  195. package/src/security/auth/middleware.ts +0 -188
  196. package/src/security/csrf.ts +0 -220
  197. package/src/security/headers.ts +0 -108
  198. package/src/security/index.ts +0 -60
  199. package/src/security/rate-limit/adapter.ts +0 -7
  200. package/src/security/rate-limit/memory.ts +0 -108
  201. package/src/security/rate-limit/middleware.ts +0 -181
  202. package/src/security/sanitization.ts +0 -75
  203. package/src/security/types.ts +0 -240
  204. package/src/security/utils.ts +0 -52
  205. package/tsconfig.json +0 -39
@@ -1,341 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import { CacheStore, CacheEntry } from './types';
3
-
4
- /**
5
- * Redis cache store configuration
6
- */
7
- export interface RedisCacheConfig {
8
- /**
9
- * Redis connection URL
10
- * @example 'redis://localhost:6379'
11
- */
12
- url?: string;
13
-
14
- /**
15
- * Redis host
16
- * @default 'localhost'
17
- */
18
- host?: string;
19
-
20
- /**
21
- * Redis port
22
- * @default 6379
23
- */
24
- port?: number;
25
-
26
- /**
27
- * Redis password
28
- */
29
- password?: string;
30
-
31
- /**
32
- * Redis database number
33
- * @default 0
34
- */
35
- db?: number;
36
-
37
- /**
38
- * Key prefix for namespacing
39
- * @default 'nexus:'
40
- */
41
- prefix?: string;
42
-
43
- /**
44
- * Connection timeout in milliseconds
45
- * @default 5000
46
- */
47
- connectTimeout?: number;
48
-
49
- /**
50
- * Enable TLS/SSL
51
- * @default false
52
- */
53
- tls?: boolean;
54
-
55
- /**
56
- * Lazy connect - don't connect immediately
57
- * @default false
58
- */
59
- lazyConnect?: boolean;
60
- }
61
-
62
- /**
63
- * Redis client interface
64
- * Supports both 'redis' and 'ioredis' packages
65
- */
66
- export interface RedisClientLike {
67
- get(key: string): Promise<string | null>;
68
- set(key: string, value: string, options?: { EX?: number; PX?: number }): Promise<any>;
69
- setex?(key: string, seconds: number, value: string): Promise<any>;
70
- del(key: string | string[]): Promise<number>;
71
- keys(pattern: string): Promise<string[]>;
72
- flushdb?(): Promise<any>;
73
- flushDb?(): Promise<any>;
74
- quit?(): Promise<any>;
75
- disconnect?(): Promise<any>;
76
- on?(event: string, listener: (...args: any[]) => void): void;
77
- }
78
-
79
- /**
80
- * Redis cache store with full feature support
81
- *
82
- * @example
83
- * ```typescript
84
- * // Using with ioredis
85
- * import Redis from 'ioredis';
86
- *
87
- * const redis = new Redis({ host: 'localhost', port: 6379 });
88
- * const cache = new RedisCacheStore('sessions', { client: redis });
89
- *
90
- * // Using with node-redis
91
- * import { createClient } from 'redis';
92
- *
93
- * const client = createClient({ url: 'redis://localhost:6379' });
94
- * await client.connect();
95
- * const cache = new RedisCacheStore('sessions', { client });
96
- * ```
97
- */
98
- export class RedisCacheStore<Value = unknown> implements CacheStore<Value> {
99
- readonly name: string;
100
- private client: RedisClientLike;
101
- private prefix: string;
102
- private emitter = new EventEmitter();
103
- private connected: boolean = false;
104
-
105
- constructor(
106
- name: string,
107
- options: RedisCacheConfig & { client: RedisClientLike }
108
- ) {
109
- this.name = name;
110
- this.client = options.client;
111
- this.prefix = options.prefix ?? 'nexus:';
112
-
113
- // Listen for connection events if supported
114
- if (this.client.on) {
115
- this.client.on('connect', () => {
116
- this.connected = true;
117
- this.emitter.emit('connect');
118
- });
119
- this.client.on('error', (err) => {
120
- this.emitter.emit('error', err);
121
- });
122
- this.client.on('close', () => {
123
- this.connected = false;
124
- this.emitter.emit('disconnect');
125
- });
126
- } else {
127
- this.connected = true;
128
- }
129
- }
130
-
131
- /**
132
- * Build the full Redis key with prefix
133
- */
134
- private buildKey(key: string): string {
135
- return `${this.prefix}${this.name}:${key}`;
136
- }
137
-
138
- /**
139
- * Get a value from cache
140
- */
141
- async get(key: string): Promise<CacheEntry<Value> | undefined> {
142
- try {
143
- const data = await this.client.get(this.buildKey(key));
144
- if (!data) {
145
- return undefined;
146
- }
147
-
148
- const entry = JSON.parse(data) as CacheEntry<Value>;
149
-
150
- // Check expiration (Redis handles TTL, but we double-check for safety)
151
- if (entry.expiresAt && entry.expiresAt < Date.now()) {
152
- await this.delete(key);
153
- return undefined;
154
- }
155
-
156
- return entry;
157
- } catch (error) {
158
- this.emitter.emit('error', error);
159
- return undefined;
160
- }
161
- }
162
-
163
- /**
164
- * Set a value in cache
165
- */
166
- async set(key: string, entry: CacheEntry<Value>): Promise<void> {
167
- try {
168
- const fullKey = this.buildKey(key);
169
- const data = JSON.stringify(entry);
170
-
171
- if (entry.expiresAt) {
172
- const ttlMs = entry.expiresAt - Date.now();
173
- if (ttlMs > 0) {
174
- const ttlSec = Math.ceil(ttlMs / 1000);
175
- // Try different methods for compatibility
176
- if (this.client.setex) {
177
- // ioredis style
178
- await this.client.setex(fullKey, ttlSec, data);
179
- } else {
180
- // node-redis style
181
- await this.client.set(fullKey, data, { EX: ttlSec });
182
- }
183
- }
184
- } else {
185
- await this.client.set(fullKey, data);
186
- }
187
-
188
- this.emitter.emit('set', key, entry);
189
- } catch (error) {
190
- this.emitter.emit('error', error);
191
- throw error;
192
- }
193
- }
194
-
195
- /**
196
- * Delete a key from cache
197
- */
198
- async delete(key: string): Promise<void> {
199
- try {
200
- await this.client.del(this.buildKey(key));
201
- this.emitter.emit('delete', key);
202
- } catch (error) {
203
- this.emitter.emit('error', error);
204
- throw error;
205
- }
206
- }
207
-
208
- /**
209
- * Clear all keys in this cache namespace
210
- */
211
- async clear(): Promise<void> {
212
- try {
213
- const pattern = `${this.prefix}${this.name}:*`;
214
- const keys = await this.client.keys(pattern);
215
-
216
- if (keys.length > 0) {
217
- await this.client.del(keys);
218
- }
219
-
220
- this.emitter.emit('clear');
221
- } catch (error) {
222
- this.emitter.emit('error', error);
223
- throw error;
224
- }
225
- }
226
-
227
- /**
228
- * Get all keys matching a pattern
229
- */
230
- async keys(pattern?: RegExp): Promise<string[]> {
231
- try {
232
- const redisPattern = `${this.prefix}${this.name}:*`;
233
- const allKeys = await this.client.keys(redisPattern);
234
-
235
- // Remove prefix to get clean keys
236
- const prefixLength = `${this.prefix}${this.name}:`.length;
237
- const cleanKeys = allKeys.map(k => k.slice(prefixLength));
238
-
239
- if (!pattern) {
240
- return cleanKeys;
241
- }
242
-
243
- return cleanKeys.filter(key => pattern.test(key));
244
- } catch (error) {
245
- this.emitter.emit('error', error);
246
- return [];
247
- }
248
- }
249
-
250
- /**
251
- * Check if connected to Redis
252
- */
253
- isConnected(): boolean {
254
- return this.connected;
255
- }
256
-
257
- /**
258
- * Subscribe to cache events
259
- */
260
- on(event: 'set' | 'delete' | 'clear' | 'connect' | 'disconnect' | 'error', listener: (...args: any[]) => void): void {
261
- this.emitter.on(event, listener);
262
- }
263
-
264
- /**
265
- * Disconnect from Redis
266
- */
267
- async disconnect(): Promise<void> {
268
- if (this.client.quit) {
269
- await this.client.quit();
270
- } else if (this.client.disconnect) {
271
- await this.client.disconnect();
272
- }
273
- this.connected = false;
274
- }
275
- }
276
-
277
- /**
278
- * Create a Redis cache store with automatic client creation
279
- * Requires 'ioredis' or 'redis' package to be installed
280
- *
281
- * @example
282
- * ```typescript
283
- * // Auto-create Redis client
284
- * const cache = await createRedisCache('sessions', {
285
- * host: 'localhost',
286
- * port: 6379,
287
- * prefix: 'myapp:'
288
- * });
289
- *
290
- * await cache.set('user:123', { value: { name: 'John' }, expiresAt: Date.now() + 3600000 });
291
- * const user = await cache.get('user:123');
292
- * ```
293
- */
294
- export async function createRedisCache<Value = unknown>(
295
- name: string,
296
- config: RedisCacheConfig = {}
297
- ): Promise<RedisCacheStore<Value>> {
298
- let client: RedisClientLike;
299
-
300
- // Try ioredis first
301
- try {
302
- const Redis = require('ioredis');
303
- client = new Redis({
304
- host: config.host ?? 'localhost',
305
- port: config.port ?? 6379,
306
- password: config.password,
307
- db: config.db ?? 0,
308
- connectTimeout: config.connectTimeout ?? 5000,
309
- tls: config.tls ? {} : undefined,
310
- lazyConnect: config.lazyConnect ?? false
311
- });
312
- } catch {
313
- // Try node-redis
314
- try {
315
- const { createClient } = require('redis');
316
- const url = config.url ?? `redis://${config.host ?? 'localhost'}:${config.port ?? 6379}`;
317
-
318
- client = createClient({
319
- url,
320
- password: config.password,
321
- database: config.db ?? 0,
322
- socket: {
323
- connectTimeout: config.connectTimeout ?? 5000,
324
- tls: config.tls ?? false
325
- }
326
- });
327
-
328
- // node-redis requires explicit connect
329
- await (client as any).connect();
330
- } catch {
331
- throw new Error(
332
- 'Redis client not found. Please install either "ioredis" or "redis" package:\n' +
333
- ' npm install ioredis\n' +
334
- ' # or\n' +
335
- ' npm install redis'
336
- );
337
- }
338
- }
339
-
340
- return new RedisCacheStore<Value>(name, { ...config, client });
341
- }
@@ -1,5 +0,0 @@
1
- export { InMemoryCacheStore } from './InMemoryCacheStore';
2
- export { MultiTierCache } from './MultiTierCache';
3
- export { RedisCacheStore, createRedisCache } from './RedisCacheStore';
4
- export type { RedisCacheConfig, RedisClientLike } from './RedisCacheStore';
5
- export * from './types';
@@ -1,40 +0,0 @@
1
- export interface CacheEntry<Value = unknown> {
2
- value: Value;
3
- expiresAt?: number;
4
- tags?: string[];
5
- meta?: Record<string, any>;
6
- }
7
-
8
- export interface CacheStore<Value = unknown> {
9
- readonly name: string;
10
- get(key: string): Promise<CacheEntry<Value> | undefined>;
11
- set(key: string, entry: CacheEntry<Value>): Promise<void>;
12
- delete(key: string): Promise<void>;
13
- clear(): Promise<void>;
14
- keys?(pattern?: RegExp): Promise<string[]>;
15
- }
16
-
17
- export interface CacheTierConfig<Value = unknown> {
18
- store: CacheStore<Value>;
19
- ttl?: number;
20
- maxSize?: number;
21
- }
22
-
23
- export interface CacheSetOptions<Value = unknown> {
24
- ttl?: number;
25
- tags?: string[];
26
- meta?: CacheEntry<Value>['meta'];
27
- }
28
-
29
- export interface CacheWrapOptions<Value = unknown> extends CacheSetOptions<Value> {
30
- refresh?: boolean;
31
- }
32
-
33
- export interface MemoizeOptions<Value = unknown> extends CacheSetOptions<Value> {
34
- keyResolver?: (...args: any[]) => string;
35
- }
36
-
37
- export interface TagIndexEntry {
38
- keys: Set<string>;
39
- expiresAt?: number;
40
- }
@@ -1,42 +0,0 @@
1
- interface SimpleDataLoaderBatch<Key, Value> {
2
- keys: Key[];
3
- resolvers: Array<{ resolve: (value: Value) => void; reject: (error: Error) => void }>;
4
- }
5
-
6
- export class SimpleDataLoader<Key = any, Value = any> {
7
- private batch: SimpleDataLoaderBatch<Key, Value> | null = null;
8
-
9
- constructor(private batchLoadFn: (keys: Key[]) => Promise<Map<Key, Value>>) { }
10
-
11
- load(key: Key): Promise<Value> {
12
- if (!this.batch) {
13
- this.batch = { keys: [], resolvers: [] };
14
- queueMicrotask(() => this.dispatch());
15
- }
16
-
17
- return new Promise<Value>((resolve, reject) => {
18
- this.batch!.keys.push(key);
19
- this.batch!.resolvers.push({ resolve, reject });
20
- });
21
- }
22
-
23
- private async dispatch() {
24
- if (!this.batch) return;
25
- const current = this.batch;
26
- this.batch = null;
27
-
28
- try {
29
- const result = await this.batchLoadFn(current.keys);
30
- current.keys.forEach((key, index) => {
31
- const value = result.get(key);
32
- if (value === undefined) {
33
- current.resolvers[index].reject(new Error(`DataLoader missing key "${String(key)}"`));
34
- } else {
35
- current.resolvers[index].resolve(value);
36
- }
37
- });
38
- } catch (error) {
39
- current.resolvers.forEach(resolver => resolver.reject(error as Error));
40
- }
41
- }
42
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Nexus GraphQL Module
3
- *
4
- * Optional GraphQL integration for Nexus framework.
5
- * Requires `graphql` as peer dependency.
6
- *
7
- * Install: npm install graphql
8
- *
9
- * Usage:
10
- * import { GraphQLServer, SimpleDataLoader } from '@engjts/server/graphql';
11
- */
12
-
13
- export { GraphQLServer } from './server';
14
- export { SimpleDataLoader } from './SimpleDataLoader';
15
- export type {
16
- GraphQLComplexityOptions,
17
- GraphQLCacheOptions,
18
- GraphQLRequestPayload,
19
- GraphQLServerOptions,
20
- SimpleDataLoaderBatch,
21
- DataLoaderFactory
22
- } from './types';
@@ -1,252 +0,0 @@
1
- import {
2
- DocumentNode,
3
- ExecutionResult,
4
- GraphQLSchema,
5
- Kind,
6
- OperationDefinitionNode,
7
- execute,
8
- getOperationAST,
9
- parse,
10
- validate
11
- } from 'graphql';
12
- import { Context, Handler, Response } from '../../core/types';
13
- import { GraphQLComplexityOptions, GraphQLRequestPayload, GraphQLServerOptions } from './types';
14
-
15
- export class GraphQLServer {
16
- private schema: GraphQLSchema;
17
- private options: GraphQLServerOptions;
18
-
19
- constructor(options: GraphQLServerOptions) {
20
- this.schema = options.schema;
21
- this.options = {
22
- playground: options.playground ?? false,
23
- introspection: options.introspection ?? process.env.NODE_ENV !== 'production',
24
- dataloaders: options.dataloaders ?? true,
25
- ...options
26
- };
27
- }
28
-
29
- /**
30
- * Returns a framework route handler
31
- */
32
- handler(): Handler {
33
- return async (ctx: Context): Promise<Response> => {
34
- if (ctx.method === 'GET' && this.shouldRenderPlayground(ctx)) {
35
- return ctx.html(this.renderPlayground());
36
- }
37
-
38
- const payload = await this.parseRequest(ctx);
39
- const document = this.parseDocument(payload.query);
40
- this.enforceRules(document, payload);
41
-
42
- const cacheKey = await this.computeCacheKey(payload);
43
- if (cacheKey) {
44
- const cached = await this.options.cache!.instance.get(cacheKey);
45
- if (cached) {
46
- return this.buildResponse(cached as ExecutionResult);
47
- }
48
- }
49
-
50
- const contextValue = await this.buildContext(ctx);
51
- const result = await execute({
52
- schema: this.schema,
53
- document,
54
- variableValues: payload.variables,
55
- operationName: payload.operationName,
56
- contextValue
57
- });
58
-
59
- if (cacheKey && !result.errors) {
60
- await this.options.cache!.instance.set(cacheKey, result, { ttl: this.options.cache?.ttl });
61
- }
62
-
63
- return this.buildResponse(result);
64
- };
65
- }
66
-
67
- private shouldRenderPlayground(ctx: Context): boolean {
68
- if (!this.options.playground) {
69
- return false;
70
- }
71
- const accept = ctx.headers['accept'];
72
- if (!accept) return false;
73
- const acceptHeader = Array.isArray(accept) ? accept.join(',') : accept;
74
- return acceptHeader.includes('text/html');
75
- }
76
-
77
- private async parseRequest(ctx: Context): Promise<GraphQLRequestPayload> {
78
- if (ctx.method === 'GET') {
79
- return {
80
- query: ctx.query.query,
81
- variables: this.safeParseJSON(ctx.query.variables),
82
- operationName: ctx.query.operationName as string | undefined
83
- };
84
- }
85
-
86
- if (typeof ctx.body === 'string') {
87
- return JSON.parse(ctx.body);
88
- }
89
-
90
- return ctx.body ?? {};
91
- }
92
-
93
- private parseDocument(query?: string): DocumentNode {
94
- if (!query) {
95
- throw new Error('GraphQL query is required');
96
- }
97
- return parse(query);
98
- }
99
-
100
- private enforceRules(document: DocumentNode, payload: GraphQLRequestPayload) {
101
- if (!this.options.introspection) {
102
- const operation = getOperationAST(document, payload.operationName || undefined);
103
- if (operation?.operation === 'query' && operation.name?.value === '__schema') {
104
- throw new Error('Introspection is disabled');
105
- }
106
- }
107
-
108
- const validationErrors = validate(this.schema, document);
109
- if (validationErrors.length > 0) {
110
- throw validationErrors[0];
111
- }
112
-
113
- if (this.options.depthLimit !== undefined) {
114
- const depth = this.calculateDepth(document);
115
- if (depth > this.options.depthLimit) {
116
- throw new Error(`Query depth ${depth} exceeds limit of ${this.options.depthLimit}`);
117
- }
118
- }
119
-
120
- if (this.options.complexity) {
121
- const complexity = this.calculateComplexity(document, this.options.complexity);
122
- if (complexity > this.options.complexity.limit) {
123
- throw new Error(`Query complexity ${complexity} exceeds limit of ${this.options.complexity.limit}`);
124
- }
125
- }
126
- }
127
-
128
- private async buildContext(ctx: Context) {
129
- const base = typeof this.options.context === 'function'
130
- ? await this.options.context({ ctx })
131
- : this.options.context ?? {};
132
-
133
- if (this.options.dataloaders) {
134
- Object.assign(base, { loaders: {} });
135
- }
136
-
137
- return { ...base, request: ctx };
138
- }
139
-
140
- private calculateDepth(document: DocumentNode): number {
141
- let maxDepth = 0;
142
-
143
- const traverse = (node: any, depth: number) => {
144
- if (!node.selectionSet) {
145
- return;
146
- }
147
- depth += 1;
148
- maxDepth = Math.max(maxDepth, depth);
149
- for (const selection of node.selectionSet.selections) {
150
- traverse(selection, depth);
151
- }
152
- };
153
-
154
- for (const definition of document.definitions) {
155
- if (definition.kind === Kind.OPERATION_DEFINITION) {
156
- traverse(definition, 0);
157
- }
158
- }
159
-
160
- return maxDepth;
161
- }
162
-
163
- private calculateComplexity(
164
- document: DocumentNode,
165
- options: GraphQLComplexityOptions
166
- ): number {
167
- const costs = options.cost || {};
168
- const defaultCost = options.defaultCost ?? 1;
169
- let complexity = 0;
170
-
171
- const visitNode = (node: OperationDefinitionNode | any) => {
172
- if (!node.selectionSet) {
173
- return;
174
- }
175
-
176
- for (const selection of node.selectionSet.selections) {
177
- if (selection.kind === Kind.FIELD) {
178
- const fieldName = selection.name.value;
179
- complexity += costs[fieldName] ?? defaultCost;
180
- }
181
- visitNode(selection);
182
- }
183
- };
184
-
185
- for (const definition of document.definitions) {
186
- if (definition.kind === Kind.OPERATION_DEFINITION) {
187
- visitNode(definition);
188
- }
189
- }
190
-
191
- return complexity;
192
- }
193
-
194
- private async computeCacheKey(payload: GraphQLRequestPayload): Promise<string | null> {
195
- if (!this.options.cache) {
196
- return null;
197
- }
198
-
199
- const generator = this.options.cache.keyGenerator ??
200
- ((data: GraphQLRequestPayload) => JSON.stringify(data));
201
-
202
- return generator(payload);
203
- }
204
-
205
- private buildResponse(result: ExecutionResult): Response {
206
- if (result.errors && this.options.formatError) {
207
- result = {
208
- ...result,
209
- errors: result.errors.map(err => this.options.formatError!(err))
210
- };
211
- }
212
-
213
- return {
214
- statusCode: result.errors ? 400 : 200,
215
- headers: { 'Content-Type': 'application/json' },
216
- body: JSON.stringify(result)
217
- };
218
- }
219
-
220
- private safeParseJSON(value: any) {
221
- if (!value) return undefined;
222
- try {
223
- if (typeof value === 'string') {
224
- return JSON.parse(value);
225
- }
226
- return value;
227
- } catch {
228
- return undefined;
229
- }
230
- }
231
-
232
- private renderPlayground() {
233
- return `
234
- <!DOCTYPE html>
235
- <html>
236
- <head>
237
- <meta charset=utf-8/>
238
- <title>GraphQL Playground</title>
239
- <link rel="stylesheet" href="https://unpkg.com/graphql-playground-react/build/static/css/index.css" />
240
- <link rel="shortcut icon" href="https://raw.githubusercontent.com/graphql/graphql-playground/main/packages/graphql-playground-react/public/favicon.png" />
241
- <script src="https://unpkg.com/graphql-playground-react/build/static/js/middleware.js"></script>
242
- </head>
243
- <body>
244
- <div id="root" />
245
- <script>window.addEventListener('load', function () {
246
- GraphQLPlayground.init(document.getElementById('root'), { endpoint: '/graphql' })
247
- })</script>
248
- </body>
249
- </html>`;
250
- }
251
- }
252
-