@hypequery/serve 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +150 -232
  2. package/dist/adapters/fetch.d.ts +3 -0
  3. package/dist/adapters/fetch.d.ts.map +1 -0
  4. package/dist/adapters/fetch.js +26 -0
  5. package/dist/adapters/node.d.ts +8 -0
  6. package/dist/adapters/node.d.ts.map +1 -0
  7. package/dist/adapters/node.js +105 -0
  8. package/dist/adapters/utils.d.ts +39 -0
  9. package/dist/adapters/utils.d.ts.map +1 -0
  10. package/dist/adapters/utils.js +114 -0
  11. package/dist/adapters/vercel.d.ts +7 -0
  12. package/dist/adapters/vercel.d.ts.map +1 -0
  13. package/dist/adapters/vercel.js +13 -0
  14. package/dist/auth.d.ts +192 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +221 -0
  17. package/dist/builder.d.ts +3 -0
  18. package/dist/builder.d.ts.map +1 -0
  19. package/dist/builder.js +61 -0
  20. package/dist/client-config.d.ts +44 -0
  21. package/dist/client-config.d.ts.map +1 -0
  22. package/dist/client-config.js +53 -0
  23. package/dist/dev.d.ts +9 -0
  24. package/dist/dev.d.ts.map +1 -0
  25. package/dist/dev.js +30 -0
  26. package/dist/docs-ui.d.ts +3 -0
  27. package/dist/docs-ui.d.ts.map +1 -0
  28. package/dist/docs-ui.js +34 -0
  29. package/dist/endpoint.d.ts +5 -0
  30. package/dist/endpoint.d.ts.map +1 -0
  31. package/dist/endpoint.js +65 -0
  32. package/dist/index.d.ts +15 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +14 -0
  35. package/dist/openapi.d.ts +3 -0
  36. package/dist/openapi.d.ts.map +1 -0
  37. package/dist/openapi.js +205 -0
  38. package/dist/pipeline.d.ts +77 -0
  39. package/dist/pipeline.d.ts.map +1 -0
  40. package/dist/pipeline.js +438 -0
  41. package/dist/query-logger.d.ts +65 -0
  42. package/dist/query-logger.d.ts.map +1 -0
  43. package/dist/query-logger.js +91 -0
  44. package/dist/router.d.ts +13 -0
  45. package/dist/router.d.ts.map +1 -0
  46. package/dist/router.js +56 -0
  47. package/dist/server/builder.d.ts +7 -0
  48. package/dist/server/builder.d.ts.map +1 -0
  49. package/dist/server/builder.js +80 -0
  50. package/dist/server/define-serve.d.ts +3 -0
  51. package/dist/server/define-serve.d.ts.map +1 -0
  52. package/dist/server/define-serve.js +88 -0
  53. package/dist/server/execute-query.d.ts +8 -0
  54. package/dist/server/execute-query.d.ts.map +1 -0
  55. package/dist/server/execute-query.js +39 -0
  56. package/dist/server/index.d.ts +6 -0
  57. package/dist/server/index.d.ts.map +1 -0
  58. package/dist/server/index.js +5 -0
  59. package/dist/server/init-serve.d.ts +8 -0
  60. package/dist/server/init-serve.d.ts.map +1 -0
  61. package/dist/server/init-serve.js +18 -0
  62. package/dist/server/mapper.d.ts +3 -0
  63. package/dist/server/mapper.d.ts.map +1 -0
  64. package/dist/server/mapper.js +30 -0
  65. package/dist/tenant.d.ts +35 -0
  66. package/dist/tenant.d.ts.map +1 -0
  67. package/dist/tenant.js +49 -0
  68. package/dist/type-tests/builder.test-d.d.ts +13 -0
  69. package/dist/type-tests/builder.test-d.d.ts.map +1 -0
  70. package/dist/type-tests/builder.test-d.js +20 -0
  71. package/dist/types.d.ts +529 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +1 -0
  74. package/dist/utils.d.ts +5 -0
  75. package/dist/utils.d.ts.map +1 -0
  76. package/dist/utils.js +19 -0
  77. package/package.json +1 -1
package/README.md CHANGED
@@ -14,37 +14,38 @@ Peer dependencies: `zod@^3`, `tsx@^4` (optional, for dev server)
14
14
 
15
15
  ```ts
16
16
  // analytics/server.ts
17
- import { defineServe } from '@hypequery/serve';
17
+ import { initServe } from '@hypequery/serve';
18
18
  import { z } from 'zod';
19
- import { db } from './database';
19
+ import { db } from './client';
20
20
 
21
- const api = defineServe({
22
- queries: {
23
- weeklyRevenue: {
24
- inputSchema: z.object({ startDate: z.string() }),
25
- outputSchema: z.object({ total: z.number() }),
26
- query: async ({ input, ctx }) => {
27
- const result = await db
28
- .select({ total: sum(sales.amount) })
29
- .from(sales)
30
- .where(gte(sales.date, input.startDate));
31
- return { total: result[0].total };
32
- },
33
- },
34
- },
21
+ const { define, queries, query } = initServe({
22
+ context: () => ({ db }),
35
23
  });
36
24
 
37
- // Auto-register routes
38
- api.route('/weeklyRevenue', api.queries.weeklyRevenue);
25
+ export const api = define({
26
+ queries: queries({
27
+ weeklyRevenue: query
28
+ .describe('Calculate weekly revenue')
29
+ .input(z.object({ startDate: z.string() }))
30
+ .query(({ ctx, input }) =>
31
+ ctx.db
32
+ .table('sales')
33
+ .select(['total_amount'])
34
+ .where('date', 'gte', input.startDate)
35
+ .sum('total_amount', 'total')
36
+ .execute()
37
+ ),
38
+ }),
39
+ });
39
40
 
40
- // Start server
41
- await api.start({ port: 3000 });
41
+ // Expose as HTTP endpoint
42
+ api.route('/weeklyRevenue', api.queries.weeklyRevenue);
42
43
  ```
43
44
 
44
45
  Your API is now running with:
45
- - **Endpoint**: `GET http://localhost:3000/weeklyRevenue?startDate=2025-01-01`
46
- - **Docs**: `http://localhost:3000/docs` (interactive Swagger UI)
47
- - **OpenAPI**: `http://localhost:3000/openapi.json` (machine-readable schema)
46
+ - **Endpoint**: `POST http://localhost:4000/weeklyRevenue`
47
+ - **Docs**: `http://localhost:4000/docs` (interactive Swagger UI)
48
+ - **OpenAPI**: `http://localhost:4000/openapi.json` (machine-readable schema)
48
49
 
49
50
  ---
50
51
 
@@ -52,24 +53,21 @@ Your API is now running with:
52
53
 
53
54
  ### 1. Server Builders
54
55
 
55
- #### `defineServe<TContext, TAuth, TQueries>(config)`
56
+ #### `initServe<TContext, TAuth>(options)`
56
57
 
57
- Main entry point for creating a hypequery server. Returns a `ServeBuilder` with methods to configure routes, middleware, and start the server.
58
+ Main entry point for creating a hypequery server. Returns an initializer with type-safe query builders and context inference.
58
59
 
59
60
  **Parameters:**
60
61
 
61
62
  ```ts
62
- interface ServeConfig<TContext, TAuth, TQueries> {
63
- // Query definitions
64
- queries?: TQueries;
63
+ interface ServeInitializerOptions<TContext, TAuth> {
64
+ // Context factory (runs per-request to inject dependencies)
65
+ context: TContext | ((opts: { request: ServeRequest; auth: TAuth | null }) => TContext | Promise<TContext>);
65
66
 
66
67
  // Base path for all routes
67
68
  basePath?: string;
68
69
 
69
- // Context factory (runs per-request to inject dependencies)
70
- context?: TContext | ((opts: { request: ServeRequest; auth: TAuth | null }) => TContext | Promise<TContext>);
71
-
72
- // Authentication strategies
70
+ // Authentication strategy
73
71
  auth?: AuthStrategy<TAuth> | AuthStrategy<TAuth>[];
74
72
 
75
73
  // Global middlewares (run before every endpoint)
@@ -91,102 +89,12 @@ interface ServeConfig<TContext, TAuth, TQueries> {
91
89
 
92
90
  **Returns:**
93
91
 
94
- ```ts
95
- interface ServeBuilder<TQueries, TContext, TAuth> {
96
- queries: TQueries; // Registered query definitions
97
- _routeConfig: Record<string, { method: HttpMethod }>; // Route-level HTTP method overrides
98
-
99
- // Register a route
100
- route(path: string, endpoint: ServeEndpoint, options?: RouteOptions): ServeBuilder;
101
-
102
- // Add global middleware
103
- use(middleware: ServeMiddleware): ServeBuilder;
104
-
105
- // Add authentication strategy
106
- useAuth(strategy: AuthStrategy<TAuth>): ServeBuilder;
107
-
108
- // Execute query directly (bypasses HTTP)
109
- execute<K extends keyof TQueries>(
110
- key: K,
111
- options?: { input?: any; context?: Partial<TContext>; request?: Partial<ServeRequest> }
112
- ): Promise<QueryResult<TQueries[K]>>;
113
-
114
- // Get toolkit description (for LLM integration)
115
- describe(): ToolkitDescription;
116
-
117
- // Raw HTTP handler (for custom adapters)
118
- handler: ServeHandler;
119
-
120
- // Start Node.js HTTP server
121
- start(options?: StartServerOptions): Promise<{ server: Server; stop: () => Promise<void> }>;
122
- }
123
- ```
124
-
125
- **Example:**
126
-
127
- ```ts
128
- const api = defineServe({
129
- basePath: '/api',
130
- context: async ({ request, auth }) => ({
131
- db: createDatabaseConnection(),
132
- userId: auth?.userId,
133
- }),
134
- auth: createBearerTokenStrategy({
135
- validate: async (token) => {
136
- const user = await verifyJWT(token);
137
- return user ? { userId: user.id, role: user.role } : null;
138
- },
139
- }),
140
- queries: {
141
- getUser: {
142
- inputSchema: z.object({ id: z.string() }),
143
- outputSchema: z.object({ name: z.string(), email: z.string() }),
144
- query: async ({ input, ctx }) => {
145
- return ctx.db.query.users.findFirst({
146
- where: eq(users.id, input.id),
147
- });
148
- },
149
- },
150
- },
151
- });
152
-
153
- api.route('/users/:id', api.queries.getUser, { method: 'GET' });
154
-
155
- await api.start({ port: 4000 });
156
- ```
157
-
158
- ---
159
-
160
- #### `initServe<TContext, TAuth>(options)`
161
-
162
- Advanced pattern for defining reusable query builders with context type inference. Use this when you want to define context once and create multiple queries that share the same types.
163
-
164
- **Parameters:**
165
-
166
- ```ts
167
- interface ServeInitializerOptions<TContext, TAuth> {
168
- context: TContext | ((opts: { request: ServeRequest; auth: TAuth | null }) => TContext | Promise<TContext>);
169
- basePath?: string;
170
- auth?: AuthStrategy<TAuth> | AuthStrategy<TAuth>[];
171
- middlewares?: ServeMiddleware<any, any, TContext, TAuth>[];
172
- tenant?: TenantConfig<TAuth>;
173
- hooks?: ServeLifecycleHooks<TAuth>;
174
- openapi?: OpenApiOptions;
175
- docs?: DocsOptions;
176
- }
177
- ```
178
-
179
- **Returns:**
180
-
181
92
  ```ts
182
93
  interface ServeInitializer<TContext, TAuth> {
183
- // Procedure builder (chainable query configuration)
184
- procedure: QueryProcedureBuilder<TContext, TAuth>;
185
-
186
- // Alias for procedure
94
+ // Query builder (chainable query configuration)
187
95
  query: QueryProcedureBuilder<TContext, TAuth>;
188
96
 
189
- // Helper to group queries
97
+ // Helper to group multiple queries
190
98
  queries<TQueries>(definitions: TQueries): TQueries;
191
99
 
192
100
  // Define server with queries
@@ -198,51 +106,47 @@ interface ServeInitializer<TContext, TAuth> {
198
106
 
199
107
  ```ts
200
108
  // Define context once
201
- const t = initServe({
109
+ const { define, queries, query } = initServe({
110
+ basePath: '/api',
202
111
  context: async ({ auth }) => ({
203
112
  db: createDatabase(),
204
113
  userId: auth?.userId,
205
114
  }),
206
- auth: createApiKeyStrategy({
207
- validate: async (key) => {
208
- const user = await validateApiKey(key);
209
- return user ? { userId: user.id } : null;
115
+ auth: createBearerTokenStrategy({
116
+ validate: async (token) => {
117
+ const user = await verifyJWT(token);
118
+ return user ? { userId: user.id, role: user.role } : null;
210
119
  },
211
120
  }),
212
121
  });
213
122
 
214
123
  // Create queries with inferred types
215
- const queries = t.queries({
216
- getRevenue: t.procedure
217
- .input(z.object({ startDate: z.string() }))
218
- .output(z.object({ total: z.number() }))
219
- .query(async ({ input, ctx }) => {
220
- // ctx is fully typed as { db, userId }
221
- return ctx.db.query.sales.aggregate({ startDate: input.startDate });
222
- }),
223
-
224
- createSale: t.procedure
225
- .input(z.object({ amount: z.number(), product: z.string() }))
226
- .output(z.object({ id: z.string() }))
227
- .method('POST')
228
- .query(async ({ input, ctx }) => {
229
- const [sale] = await ctx.db.insert(sales).values({
230
- amount: input.amount,
231
- product: input.product,
232
- userId: ctx.userId,
233
- });
234
- return { id: sale.id };
235
- }),
124
+ export const api = define({
125
+ queries: queries({
126
+ getUser: query
127
+ .input(z.object({ id: z.string() }))
128
+ .query(async ({ ctx, input }) => {
129
+ return ctx.db.query.users.findFirst({
130
+ where: eq(users.id, input.id),
131
+ });
132
+ }),
133
+
134
+ weeklyRevenue: query
135
+ .describe('Calculate weekly revenue totals')
136
+ .input(z.object({ startDate: z.string() }))
137
+ .query(async ({ ctx, input }) => {
138
+ return ctx.db
139
+ .table('sales')
140
+ .where('date', 'gte', input.startDate)
141
+ .sum('amount', 'total')
142
+ .execute();
143
+ }),
144
+ }),
236
145
  });
237
146
 
238
- // Define server
239
- const api = t.define({ queries });
240
-
241
- api
242
- .route('/revenue', api.queries.getRevenue)
243
- .route('/sales', api.queries.createSale);
244
-
245
- await api.start();
147
+ // Expose as HTTP endpoints
148
+ api.route('/users/:id', api.queries.getUser, { method: 'GET' });
149
+ api.route('/weeklyRevenue', api.queries.weeklyRevenue);
246
150
  ```
247
151
 
248
152
  ---
@@ -429,29 +333,23 @@ curl -H "Authorization: Bearer eyJhbGc..." http://localhost:3000/revenue
429
333
 
430
334
  ---
431
335
 
432
- ### 4. Procedure Builder (Advanced Query Configuration)
336
+ ### 4. Query Builder (Advanced Query Configuration)
433
337
 
434
- The procedure builder provides a chainable API for configuring queries with full type inference.
338
+ The query builder provides a chainable API for configuring queries with full type inference.
435
339
 
436
340
  **Available Methods:**
437
341
 
438
342
  ```ts
439
343
  interface QueryProcedureBuilder<TContext, TAuth> {
344
+ // Description (OpenAPI documentation, supports Markdown)
345
+ describe(description: string): QueryProcedureBuilder;
346
+
440
347
  // Input schema (Zod)
441
348
  input<TSchema extends ZodTypeAny>(schema: TSchema): QueryProcedureBuilder;
442
349
 
443
- // Output schema (Zod)
444
- output<TSchema extends ZodTypeAny>(schema: TSchema): QueryProcedureBuilder;
445
-
446
350
  // HTTP method (GET, POST, PUT, DELETE, etc.)
447
351
  method(method: HttpMethod): QueryProcedureBuilder;
448
352
 
449
- // Summary (OpenAPI short description)
450
- summary(summary: string): QueryProcedureBuilder;
451
-
452
- // Description (OpenAPI detailed description, supports Markdown)
453
- describe(description: string): QueryProcedureBuilder;
454
-
455
353
  // Add single tag (for OpenAPI grouping)
456
354
  tag(tag: string): QueryProcedureBuilder;
457
355
 
@@ -465,7 +363,9 @@ interface QueryProcedureBuilder<TContext, TAuth> {
465
363
  auth(strategy: AuthStrategy<TAuth>): QueryProcedureBuilder;
466
364
 
467
365
  // Multi-tenancy configuration
468
- tenant(config: TenantConfig<TAuth>): QueryProcedureBuilder;
366
+ tenant(config: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
367
+ tenantOptional(config?: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
368
+ require(): QueryProcedureBuilder;
469
369
 
470
370
  // Custom metadata (for extensions)
471
371
  custom(metadata: Record<string, unknown>): QueryProcedureBuilder;
@@ -483,41 +383,31 @@ interface QueryProcedureBuilder<TContext, TAuth> {
483
383
  **Example:**
484
384
 
485
385
  ```ts
486
- const t = initServe({
386
+ const { query } = initServe({
487
387
  context: async () => ({ db: createDatabase() }),
488
388
  });
489
389
 
490
- const getAnalytics = t.procedure
390
+ const getAnalytics = query
391
+ .describe(`
392
+ Returns aggregated analytics for the specified metric and date range.
393
+ Supports revenue, user count, and session metrics.
394
+ `)
491
395
  .input(z.object({
492
396
  startDate: z.string(),
493
397
  endDate: z.string(),
494
398
  metric: z.enum(['revenue', 'users', 'sessions']),
495
399
  }))
496
- .output(z.object({
497
- total: z.number(),
498
- trend: z.number(),
499
- }))
500
- .method('GET')
501
- .summary('Fetch analytics data')
502
- .describe(`
503
- Returns aggregated analytics for the specified metric and date range.
504
- Supports revenue, user count, and session metrics.
505
- `)
506
400
  .tag('Analytics')
507
401
  .cache(300000) // Cache for 5 minutes
508
- .query(async ({ input, ctx }) => {
509
- const result = await ctx.db.query.analytics.aggregate({
510
- where: and(
511
- gte(analytics.date, input.startDate),
512
- lte(analytics.date, input.endDate),
513
- ),
514
- metric: input.metric,
515
- });
516
-
517
- return {
518
- total: result.total,
519
- trend: result.trend,
520
- };
402
+ .query(async ({ ctx, input }) => {
403
+ const result = await ctx.db
404
+ .table('analytics')
405
+ .where('date', 'gte', input.startDate)
406
+ .where('date', 'lte', input.endDate)
407
+ .sum(input.metric, 'total')
408
+ .execute();
409
+
410
+ return result[0];
521
411
  });
522
412
  ```
523
413
 
@@ -551,7 +441,7 @@ interface TenantConfig<TAuth> {
551
441
  **Example (Manual Mode):**
552
442
 
553
443
  ```ts
554
- const api = defineServe({
444
+ const { define, queries, query } = initServe({
555
445
  tenant: {
556
446
  extract: (auth) => auth?.tenantId ?? null,
557
447
  mode: 'manual', // You manually filter by tenantId
@@ -559,25 +449,28 @@ const api = defineServe({
559
449
  },
560
450
  context: async ({ auth }) => ({
561
451
  db: createDatabase(),
562
- tenantId: auth?.tenantId, // Injected by framework
452
+ tenantId: auth?.tenantId,
563
453
  }),
564
- queries: {
565
- getUsers: {
566
- query: async ({ ctx }) => {
454
+ });
455
+
456
+ export const api = define({
457
+ queries: queries({
458
+ getUsers: query
459
+ .query(async ({ ctx }) => {
567
460
  // Manually filter by tenant
568
- return ctx.db.query.users.findMany({
569
- where: eq(users.tenantId, ctx.tenantId),
570
- });
571
- },
572
- },
573
- },
461
+ return ctx.db
462
+ .table('users')
463
+ .where('tenant_id', 'eq', ctx.tenantId)
464
+ .execute();
465
+ }),
466
+ }),
574
467
  });
575
468
  ```
576
469
 
577
470
  **Example (Auto-Inject Mode):**
578
471
 
579
472
  ```ts
580
- const api = defineServe({
473
+ const { define, queries, query } = initServe({
581
474
  tenant: {
582
475
  extract: (auth) => auth?.organizationId ?? null,
583
476
  mode: 'auto-inject',
@@ -586,15 +479,37 @@ const api = defineServe({
586
479
  context: async () => ({
587
480
  db: createDatabase(),
588
481
  }),
589
- queries: {
590
- getUsers: {
591
- query: async ({ ctx }) => {
482
+ });
483
+
484
+ export const api = define({
485
+ queries: queries({
486
+ getUsers: query
487
+ .query(async ({ ctx }) => {
592
488
  // Tenant filter is automatically injected
593
- return ctx.db.query.users.findMany();
594
- // Equivalent to: WHERE organization_id = <tenant_id>
595
- },
596
- },
597
- },
489
+ return ctx.db
490
+ .table('users')
491
+ .select(['id', 'name'])
492
+ .execute();
493
+ // Equivalent to: SELECT id, name FROM users WHERE organization_id = <tenant_id>
494
+ }),
495
+ }),
496
+ });
497
+ ```
498
+
499
+ **Per-query override (optional tenant, no auto-inject):**
500
+
501
+ ```ts
502
+ export const api = define({
503
+ queries: queries({
504
+ adminStats: query
505
+ .tenantOptional({ mode: 'manual' })
506
+ .query(async ({ ctx }) => {
507
+ if (ctx.tenantId) {
508
+ return ctx.db.table('stats').where('tenant_id', 'eq', ctx.tenantId).execute();
509
+ }
510
+ return ctx.db.table('stats').execute();
511
+ }),
512
+ }),
598
513
  });
599
514
  ```
600
515
 
@@ -964,33 +879,36 @@ export { handler as GET, handler as POST, handler as PUT, handler as DELETE };
964
879
  All functions are fully typed with automatic inference:
965
880
 
966
881
  ```ts
967
- import { defineServe } from '@hypequery/serve';
882
+ import { initServe } from '@hypequery/serve';
968
883
  import { z } from 'zod';
969
884
 
970
- const api = defineServe({
885
+ const { define, queries, query } = initServe({
971
886
  context: async ({ auth }) => ({
972
887
  db: createDatabase(),
973
888
  userId: auth?.userId,
974
889
  }),
975
- queries: {
976
- getUser: {
977
- inputSchema: z.object({ id: z.string() }),
978
- outputSchema: z.object({ name: z.string(), email: z.string() }),
979
- query: async ({ input, ctx }) => {
890
+ });
891
+
892
+ export const api = define({
893
+ queries: queries({
894
+ getUser: query
895
+ .input(z.object({ id: z.string() }))
896
+ .query(async ({ ctx, input }) => {
980
897
  // input: { id: string }
981
898
  // ctx: { db: Database; userId: string | undefined }
982
- return ctx.db.query.users.findFirst({
983
- where: eq(users.id, input.id),
984
- });
985
- },
986
- },
987
- },
899
+ return ctx.db
900
+ .table('users')
901
+ .where('id', 'eq', input.id)
902
+ .select(['name', 'email'])
903
+ .limit(1)
904
+ .execute();
905
+ }),
906
+ }),
988
907
  });
989
908
 
990
- // Execute with type safety
991
- const user = await api.execute('getUser', {
992
- input: { id: '123' }
993
- });
909
+ // Execute with type safety (aliases: api.execute, api.client)
910
+ const result = await api.run('getUser', { id: '123' });
911
+ const user = result[0];
994
912
  // user: { name: string; email: string }
995
913
  ```
996
914
 
@@ -0,0 +1,3 @@
1
+ import type { FetchHandler, ServeHandler } from "../types.js";
2
+ export declare const createFetchHandler: (handler: ServeHandler) => FetchHandler;
3
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/adapters/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAc,YAAY,EAAgB,MAAM,aAAa,CAAC;AAQxF,eAAO,MAAM,kBAAkB,GAAI,SAAS,YAAY,KAAG,YA4B1D,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { normalizeHeaders, parseQueryParams, parseRequestBody, serializeResponseBody, } from "./utils.js";
2
+ export const createFetchHandler = (handler) => {
3
+ return async (request) => {
4
+ const url = new URL(request.url);
5
+ const headers = normalizeHeaders(request.headers);
6
+ const contentType = headers["content-type"];
7
+ const serveRequest = {
8
+ method: (request.method ?? "GET").toUpperCase(),
9
+ path: url.pathname,
10
+ query: parseQueryParams(url.searchParams),
11
+ headers,
12
+ body: await parseRequestBody(request, contentType),
13
+ raw: request,
14
+ };
15
+ const response = await handler(serveRequest);
16
+ const responseHeaders = {
17
+ "content-type": "application/json; charset=utf-8",
18
+ ...response.headers,
19
+ };
20
+ const body = serializeResponseBody(response.body);
21
+ return new Response(body, {
22
+ status: response.status,
23
+ headers: responseHeaders,
24
+ });
25
+ };
26
+ };
@@ -0,0 +1,8 @@
1
+ import { type IncomingMessage, type ServerResponse } from "http";
2
+ import type { ServeHandler, StartServerOptions } from "../types.js";
3
+ export declare const createNodeHandler: (handler: ServeHandler) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
4
+ export declare const startNodeServer: (handler: ServeHandler, options?: StartServerOptions) => Promise<{
5
+ server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
6
+ stop: () => Promise<void>;
7
+ }>;
8
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAC;AAG/E,OAAO,KAAK,EAEV,YAAY,EAGZ,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAyErB,eAAO,MAAM,iBAAiB,GAAI,SAAS,YAAY,MACvC,KAAK,eAAe,EAAE,KAAK,cAAc,kBASxD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EACrB,UAAS,kBAAuB;;;EA8CjC,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { createServer } from "http";
2
+ import { once } from "node:events";
3
+ import { normalizeHeaders, parseQueryParams, parseRequestBody, serializeResponseBody, } from "./utils.js";
4
+ const readRequestBody = async (req) => {
5
+ const chunks = [];
6
+ for await (const chunk of req) {
7
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
8
+ }
9
+ return Buffer.concat(chunks);
10
+ };
11
+ const buildServeRequest = async (req) => {
12
+ const method = (req.method ?? "GET").toUpperCase();
13
+ const url = new URL(req.url ?? "/", "http://localhost");
14
+ const bodyBuffer = await readRequestBody(req);
15
+ const headers = normalizeHeaders(req.headers);
16
+ const contentType = headers["content-type"] ?? headers["Content-Type"];
17
+ const body = await parseRequestBody(bodyBuffer, contentType);
18
+ return {
19
+ method,
20
+ path: url.pathname,
21
+ query: parseQueryParams(url.searchParams),
22
+ headers,
23
+ body,
24
+ raw: req,
25
+ };
26
+ };
27
+ const sendResponse = (res, response) => {
28
+ res.statusCode = response.status;
29
+ const headers = response.headers ?? {};
30
+ for (const [key, value] of Object.entries(headers)) {
31
+ if (value !== undefined) {
32
+ res.setHeader(key, value);
33
+ }
34
+ }
35
+ if (!res.hasHeader("content-type")) {
36
+ res.setHeader("content-type", "application/json; charset=utf-8");
37
+ }
38
+ const serialized = serializeResponseBody(response.body);
39
+ res.end(serialized);
40
+ };
41
+ const sendError = (res, error) => {
42
+ const payload = error && typeof error === "object" && "status" in error
43
+ ? error
44
+ : {
45
+ status: 500,
46
+ body: {
47
+ error: {
48
+ type: "INTERNAL_SERVER_ERROR",
49
+ message: error instanceof Error ? error.message : "Unexpected error",
50
+ },
51
+ },
52
+ };
53
+ sendResponse(res, payload);
54
+ };
55
+ export const createNodeHandler = (handler) => {
56
+ return async (req, res) => {
57
+ try {
58
+ const request = await buildServeRequest(req);
59
+ const response = await handler(request);
60
+ sendResponse(res, response);
61
+ }
62
+ catch (error) {
63
+ sendError(res, error);
64
+ }
65
+ };
66
+ };
67
+ export const startNodeServer = async (handler, options = {}) => {
68
+ const listener = createNodeHandler(handler);
69
+ const server = createServer(listener);
70
+ const port = options.port ?? 3000;
71
+ const hostname = options.hostname ?? "0.0.0.0";
72
+ const onAbort = () => {
73
+ server.close();
74
+ };
75
+ if (options.signal) {
76
+ if (options.signal.aborted) {
77
+ server.close();
78
+ throw new Error("Start signal already aborted");
79
+ }
80
+ options.signal.addEventListener("abort", onAbort, { once: true });
81
+ }
82
+ server.listen(port, hostname);
83
+ await once(server, "listening");
84
+ if (!options.quiet) {
85
+ const address = server.address();
86
+ const display = typeof address === "object" && address
87
+ ? `${address.address}:${address.port}`
88
+ : `${hostname}:${port}`;
89
+ console.log(`hypequery serve listening on ${display}`);
90
+ }
91
+ const stop = () => new Promise((resolve, reject) => {
92
+ server.close((err) => {
93
+ if (err) {
94
+ reject(err);
95
+ }
96
+ else {
97
+ resolve();
98
+ }
99
+ });
100
+ });
101
+ return {
102
+ server,
103
+ stop,
104
+ };
105
+ };