@hypequery/serve 0.1.1 → 0.2.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.
- package/README.md +220 -185
- package/dist/adapters/node.d.ts +1 -1
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +114 -21
- package/dist/auth.d.ts +27 -17
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +27 -17
- package/dist/cors.d.ts +17 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +82 -0
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/pipeline.d.ts +8 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +36 -7
- package/dist/rate-limit.d.ts +86 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +137 -0
- package/dist/serve.d.ts +16 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +88 -0
- package/dist/server/builder.d.ts +1 -1
- package/dist/server/builder.d.ts.map +1 -1
- package/dist/server/builder.js +1 -0
- package/dist/server/define-serve.d.ts.map +1 -1
- package/dist/server/define-serve.js +3 -0
- package/dist/server/execute-query.d.ts.map +1 -1
- package/dist/server/execute-query.js +6 -1
- package/dist/server/init-serve.d.ts.map +1 -1
- package/dist/server/init-serve.js +23 -8
- package/dist/type-tests/builder.test-d.d.ts +8 -2
- package/dist/type-tests/builder.test-d.d.ts.map +1 -1
- package/dist/type-tests/builder.test-d.js +17 -1
- package/dist/types.d.ts +102 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hypequery/serve
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Code-first runtime for exposing hypequery analytics endpoints. Build typed query definitions, run them in-process, and add HTTP routes, docs, and adapters when needed.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,54 +8,72 @@ Declarative HTTP server for exposing hypequery analytics endpoints. Build type-s
|
|
|
8
8
|
npm install @hypequery/serve zod
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer
|
|
11
|
+
Peer dependency: `tsx@^4` (optional, for dev server)
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
Recommended path:
|
|
16
|
+
|
|
17
|
+
1. Build a typed ClickHouse query
|
|
18
|
+
2. Wrap it with `query({ ... })` when it becomes a reusable contract
|
|
19
|
+
3. Add `serve({ queries })` when you need HTTP routes, docs, or adapters
|
|
20
|
+
|
|
15
21
|
```ts
|
|
16
|
-
// analytics/
|
|
22
|
+
// analytics/queries.ts
|
|
17
23
|
import { initServe } from '@hypequery/serve';
|
|
18
24
|
import { z } from 'zod';
|
|
19
25
|
import { db } from './client';
|
|
20
26
|
|
|
21
|
-
const {
|
|
27
|
+
const { query, serve } = initServe({
|
|
22
28
|
context: () => ({ db }),
|
|
29
|
+
basePath: '/api/analytics',
|
|
23
30
|
});
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
32
|
+
const weeklyRevenue = query({
|
|
33
|
+
description: 'Calculate weekly revenue',
|
|
34
|
+
input: z.object({ startDate: z.string() }),
|
|
35
|
+
query: ({ ctx, input }) =>
|
|
36
|
+
ctx.db
|
|
37
|
+
.table('sales')
|
|
38
|
+
.select(['total_amount'])
|
|
39
|
+
.where('date', 'gte', input.startDate)
|
|
40
|
+
.sum('total_amount', 'total')
|
|
41
|
+
.execute(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const api = serve({
|
|
45
|
+
queries: { weeklyRevenue },
|
|
39
46
|
});
|
|
40
47
|
|
|
41
|
-
//
|
|
48
|
+
// Register an HTTP route
|
|
42
49
|
api.route('/weeklyRevenue', api.queries.weeklyRevenue);
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
```ts
|
|
53
|
+
// analytics/server.ts
|
|
54
|
+
import { api } from './queries';
|
|
55
|
+
|
|
56
|
+
const server = await api.start({ port: 4000 });
|
|
57
|
+
|
|
58
|
+
process.on('SIGTERM', async () => {
|
|
59
|
+
await server.stop();
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With the server running:
|
|
64
|
+
- **Endpoint**: `POST http://localhost:4000/api/analytics/weeklyRevenue`
|
|
65
|
+
- **Docs**: `http://localhost:4000/api/analytics/docs`
|
|
66
|
+
- **OpenAPI**: `http://localhost:4000/api/analytics/openapi.json`
|
|
49
67
|
|
|
50
68
|
---
|
|
51
69
|
|
|
52
70
|
## Core Concepts
|
|
53
71
|
|
|
54
|
-
### 1.
|
|
72
|
+
### 1. Create Queries And A Runtime
|
|
55
73
|
|
|
56
74
|
#### `initServe<TContext, TAuth>(options)`
|
|
57
75
|
|
|
58
|
-
Main entry point for creating
|
|
76
|
+
Main entry point for creating typed query definitions and a serve runtime.
|
|
59
77
|
|
|
60
78
|
**Parameters:**
|
|
61
79
|
|
|
@@ -91,22 +109,46 @@ interface ServeInitializerOptions<TContext, TAuth> {
|
|
|
91
109
|
|
|
92
110
|
```ts
|
|
93
111
|
interface ServeInitializer<TContext, TAuth> {
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
query: QueryFactory<TContext, TAuth>;
|
|
113
|
+
serve<TQueries>(config: ServeConfig<TQueries, TContext, TAuth>): ServeBuilder<TQueries, TContext, TAuth>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Use `query({ ... })` to define a typed contract:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const weeklyRevenue = query({
|
|
121
|
+
description: 'Calculate weekly revenue totals',
|
|
122
|
+
input: z.object({ startDate: z.string() }),
|
|
123
|
+
query: async ({ ctx, input }) => {
|
|
124
|
+
return ctx.db
|
|
125
|
+
.table('sales')
|
|
126
|
+
.where('date', 'gte', input.startDate)
|
|
127
|
+
.sum('amount', 'total')
|
|
128
|
+
.execute();
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
96
132
|
|
|
97
|
-
|
|
98
|
-
queries<TQueries>(definitions: TQueries): TQueries;
|
|
133
|
+
Use `serve({ queries })` to expose a typed runtime and optional HTTP surface:
|
|
99
134
|
|
|
100
|
-
|
|
101
|
-
|
|
135
|
+
```ts
|
|
136
|
+
interface ServeConfig<TQueries, TContext, TAuth> {
|
|
137
|
+
queries: TQueries;
|
|
138
|
+
basePath?: string;
|
|
139
|
+
auth?: AuthStrategy<TAuth> | AuthStrategy<TAuth>[];
|
|
140
|
+
middlewares?: ServeMiddleware<any, any, TContext, TAuth>[];
|
|
141
|
+
tenant?: TenantConfig<TAuth>;
|
|
142
|
+
hooks?: ServeLifecycleHooks<TAuth>;
|
|
143
|
+
openapi?: OpenApiOptions;
|
|
144
|
+
docs?: DocsOptions;
|
|
102
145
|
}
|
|
103
146
|
```
|
|
104
147
|
|
|
105
148
|
**Example:**
|
|
106
149
|
|
|
107
150
|
```ts
|
|
108
|
-
|
|
109
|
-
const { define, queries, query } = initServe({
|
|
151
|
+
const { query, serve } = initServe({
|
|
110
152
|
basePath: '/api',
|
|
111
153
|
context: async ({ auth }) => ({
|
|
112
154
|
db: createDatabase(),
|
|
@@ -120,31 +162,31 @@ const { define, queries, query } = initServe({
|
|
|
120
162
|
}),
|
|
121
163
|
});
|
|
122
164
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
const getUser = query({
|
|
166
|
+
input: z.object({ id: z.string() }),
|
|
167
|
+
query: async ({ ctx, input }) => {
|
|
168
|
+
return ctx.db.query.users.findFirst({
|
|
169
|
+
where: eq(users.id, input.id),
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const weeklyRevenue = query({
|
|
175
|
+
description: 'Calculate weekly revenue totals',
|
|
176
|
+
input: z.object({ startDate: z.string() }),
|
|
177
|
+
query: async ({ ctx, input }) => {
|
|
178
|
+
return ctx.db
|
|
179
|
+
.table('sales')
|
|
180
|
+
.where('date', 'gte', input.startDate)
|
|
181
|
+
.sum('amount', 'total')
|
|
182
|
+
.execute();
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export const api = serve({
|
|
187
|
+
queries: { getUser, weeklyRevenue },
|
|
145
188
|
});
|
|
146
189
|
|
|
147
|
-
// Expose as HTTP endpoints
|
|
148
190
|
api.route('/users/:id', api.queries.getUser, { method: 'GET' });
|
|
149
191
|
api.route('/weeklyRevenue', api.queries.weeklyRevenue);
|
|
150
192
|
```
|
|
@@ -244,12 +286,12 @@ interface ApiKeyStrategyOptions<TAuth> {
|
|
|
244
286
|
**Example:**
|
|
245
287
|
|
|
246
288
|
```ts
|
|
247
|
-
import { createApiKeyStrategy } from '@hypequery/serve';
|
|
289
|
+
import { createApiKeyStrategy, initServe } from '@hypequery/serve';
|
|
248
290
|
|
|
249
291
|
const apiKeyAuth = createApiKeyStrategy({
|
|
250
292
|
header: 'x-api-key',
|
|
251
|
-
queryParam: 'apiKey',
|
|
252
|
-
validate: async (key
|
|
293
|
+
queryParam: 'apiKey',
|
|
294
|
+
validate: async (key) => {
|
|
253
295
|
const user = await db.query.apiKeys.findFirst({
|
|
254
296
|
where: eq(apiKeys.key, key),
|
|
255
297
|
});
|
|
@@ -263,9 +305,17 @@ const apiKeyAuth = createApiKeyStrategy({
|
|
|
263
305
|
},
|
|
264
306
|
});
|
|
265
307
|
|
|
266
|
-
const
|
|
308
|
+
const { query, serve } = initServe({
|
|
267
309
|
auth: apiKeyAuth,
|
|
268
|
-
|
|
310
|
+
context: () => ({ db }),
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const revenue = query({
|
|
314
|
+
query: ({ ctx }) => ctx.db.table('sales').sum('amount', 'total').execute(),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const api = serve({
|
|
318
|
+
queries: { revenue },
|
|
269
319
|
});
|
|
270
320
|
```
|
|
271
321
|
|
|
@@ -298,7 +348,7 @@ interface BearerTokenStrategyOptions<TAuth> {
|
|
|
298
348
|
**Example:**
|
|
299
349
|
|
|
300
350
|
```ts
|
|
301
|
-
import { createBearerTokenStrategy } from '@hypequery/serve';
|
|
351
|
+
import { createBearerTokenStrategy, initServe } from '@hypequery/serve';
|
|
302
352
|
import jwt from 'jsonwebtoken';
|
|
303
353
|
|
|
304
354
|
const jwtAuth = createBearerTokenStrategy({
|
|
@@ -314,14 +364,22 @@ const jwtAuth = createBearerTokenStrategy({
|
|
|
314
364
|
role: payload.role,
|
|
315
365
|
};
|
|
316
366
|
} catch {
|
|
317
|
-
return null;
|
|
367
|
+
return null;
|
|
318
368
|
}
|
|
319
369
|
},
|
|
320
370
|
});
|
|
321
371
|
|
|
322
|
-
const
|
|
372
|
+
const { query, serve } = initServe({
|
|
323
373
|
auth: jwtAuth,
|
|
324
|
-
|
|
374
|
+
context: () => ({ db: createDatabase() }),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const revenue = query({
|
|
378
|
+
query: ({ ctx }) => ctx.db.table('sales').sum('amount', 'total').execute(),
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const api = serve({
|
|
382
|
+
queries: { revenue },
|
|
325
383
|
});
|
|
326
384
|
```
|
|
327
385
|
|
|
@@ -333,73 +391,41 @@ curl -H "Authorization: Bearer eyJhbGc..." http://localhost:3000/revenue
|
|
|
333
391
|
|
|
334
392
|
---
|
|
335
393
|
|
|
336
|
-
### 4. Query
|
|
394
|
+
### 4. Query Definition Options
|
|
337
395
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
**Available Methods:**
|
|
396
|
+
`query({ ... })` accepts query logic plus optional metadata used for validation, docs, caching, and routing.
|
|
341
397
|
|
|
342
398
|
```ts
|
|
343
|
-
interface
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
// Add multiple tags
|
|
357
|
-
tags(tags: string[]): QueryProcedureBuilder;
|
|
358
|
-
|
|
359
|
-
// Cache TTL in milliseconds (sets Cache-Control header)
|
|
360
|
-
cache(ttlMs: number | null): QueryProcedureBuilder;
|
|
361
|
-
|
|
362
|
-
// Authentication strategy (overrides global auth)
|
|
363
|
-
auth(strategy: AuthStrategy<TAuth>): QueryProcedureBuilder;
|
|
364
|
-
|
|
365
|
-
// Multi-tenancy configuration
|
|
366
|
-
tenant(config: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
|
|
367
|
-
tenantOptional(config?: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
|
|
368
|
-
require(): QueryProcedureBuilder;
|
|
369
|
-
|
|
370
|
-
// Custom metadata (for extensions)
|
|
371
|
-
custom(metadata: Record<string, unknown>): QueryProcedureBuilder;
|
|
372
|
-
|
|
373
|
-
// Add middleware (runs before query handler)
|
|
374
|
-
use(...middlewares: ServeMiddleware[]): QueryProcedureBuilder;
|
|
375
|
-
|
|
376
|
-
// Define query handler (terminal operation)
|
|
377
|
-
query<TExecutable extends ExecutableQuery>(
|
|
378
|
-
executable: TExecutable
|
|
379
|
-
): ServeQueryConfig;
|
|
399
|
+
interface QueryConfig<TInput, TOutput, TContext, TAuth> {
|
|
400
|
+
query: (args: QueryResolverArgs<TInput, TContext, TAuth>) => Promise<TOutput> | TOutput;
|
|
401
|
+
input?: ZodTypeAny;
|
|
402
|
+
output?: ZodTypeAny;
|
|
403
|
+
description?: string;
|
|
404
|
+
summary?: string;
|
|
405
|
+
tags?: string[];
|
|
406
|
+
method?: HttpMethod;
|
|
407
|
+
cache?: number | null;
|
|
408
|
+
auth?: AuthStrategy<TAuth>;
|
|
409
|
+
tenant?: Partial<TenantConfig<TAuth>>;
|
|
410
|
+
middlewares?: ServeMiddleware<any, any, TContext, TAuth>[];
|
|
411
|
+
metadata?: Record<string, unknown>;
|
|
380
412
|
}
|
|
381
413
|
```
|
|
382
414
|
|
|
383
415
|
**Example:**
|
|
384
416
|
|
|
385
417
|
```ts
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
`)
|
|
395
|
-
.input(z.object({
|
|
418
|
+
const getAnalytics = query({
|
|
419
|
+
description: 'Returns aggregated analytics for the selected metric and date range.',
|
|
420
|
+
summary: 'Analytics totals by date range',
|
|
421
|
+
input: z.object({
|
|
396
422
|
startDate: z.string(),
|
|
397
423
|
endDate: z.string(),
|
|
398
424
|
metric: z.enum(['revenue', 'users', 'sessions']),
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
425
|
+
}),
|
|
426
|
+
tags: ['Analytics'],
|
|
427
|
+
cache: 300000,
|
|
428
|
+
query: async ({ ctx, input }) => {
|
|
403
429
|
const result = await ctx.db
|
|
404
430
|
.table('analytics')
|
|
405
431
|
.where('date', 'gte', input.startDate)
|
|
@@ -408,7 +434,8 @@ const getAnalytics = query
|
|
|
408
434
|
.execute();
|
|
409
435
|
|
|
410
436
|
return result[0];
|
|
411
|
-
}
|
|
437
|
+
},
|
|
438
|
+
});
|
|
412
439
|
```
|
|
413
440
|
|
|
414
441
|
---
|
|
@@ -441,7 +468,7 @@ interface TenantConfig<TAuth> {
|
|
|
441
468
|
**Example (Manual Mode):**
|
|
442
469
|
|
|
443
470
|
```ts
|
|
444
|
-
const {
|
|
471
|
+
const { query, serve } = initServe({
|
|
445
472
|
tenant: {
|
|
446
473
|
extract: (auth) => auth?.tenantId ?? null,
|
|
447
474
|
mode: 'manual', // You manually filter by tenantId
|
|
@@ -453,24 +480,25 @@ const { define, queries, query } = initServe({
|
|
|
453
480
|
}),
|
|
454
481
|
});
|
|
455
482
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
483
|
+
const getUsers = query({
|
|
484
|
+
query: async ({ ctx }) => {
|
|
485
|
+
// Manually filter by tenant
|
|
486
|
+
return ctx.db
|
|
487
|
+
.table('users')
|
|
488
|
+
.where('tenant_id', 'eq', ctx.tenantId)
|
|
489
|
+
.execute();
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
export const api = serve({
|
|
494
|
+
queries: { getUsers },
|
|
467
495
|
});
|
|
468
496
|
```
|
|
469
497
|
|
|
470
498
|
**Example (Auto-Inject Mode):**
|
|
471
499
|
|
|
472
500
|
```ts
|
|
473
|
-
const {
|
|
501
|
+
const { query, serve } = initServe({
|
|
474
502
|
tenant: {
|
|
475
503
|
extract: (auth) => auth?.organizationId ?? null,
|
|
476
504
|
mode: 'auto-inject',
|
|
@@ -481,35 +509,36 @@ const { define, queries, query } = initServe({
|
|
|
481
509
|
}),
|
|
482
510
|
});
|
|
483
511
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
512
|
+
const getUsers = query({
|
|
513
|
+
query: async ({ ctx }) => {
|
|
514
|
+
// Tenant filter is automatically injected
|
|
515
|
+
return ctx.db
|
|
516
|
+
.table('users')
|
|
517
|
+
.select(['id', 'name'])
|
|
518
|
+
.execute();
|
|
519
|
+
// Equivalent to: SELECT id, name FROM users WHERE organization_id = <tenant_id>
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
export const api = serve({
|
|
524
|
+
queries: { getUsers },
|
|
496
525
|
});
|
|
497
526
|
```
|
|
498
527
|
|
|
499
528
|
**Per-query override (optional tenant, no auto-inject):**
|
|
500
529
|
|
|
501
530
|
```ts
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
531
|
+
const adminStats = query
|
|
532
|
+
.tenantOptional({ mode: 'manual' })
|
|
533
|
+
.query(async ({ ctx }) => {
|
|
534
|
+
if (ctx.tenantId) {
|
|
535
|
+
return ctx.db.table('stats').where('tenant_id', 'eq', ctx.tenantId).execute();
|
|
536
|
+
}
|
|
537
|
+
return ctx.db.table('stats').execute();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
export const api = serve({
|
|
541
|
+
queries: { adminStats },
|
|
513
542
|
});
|
|
514
543
|
```
|
|
515
544
|
|
|
@@ -651,18 +680,23 @@ const requireAdmin: ServeMiddleware<any, any, any, { role: string }> = async (ct
|
|
|
651
680
|
return next();
|
|
652
681
|
};
|
|
653
682
|
|
|
654
|
-
|
|
655
|
-
|
|
683
|
+
const { query, serve } = initServe({
|
|
684
|
+
context: () => ({ db: createDatabase() }),
|
|
656
685
|
middlewares: [logMiddleware],
|
|
657
|
-
queries: { /* ... */ },
|
|
658
686
|
});
|
|
659
687
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
688
|
+
const deleteUser = query({
|
|
689
|
+
input: z.object({ id: z.string() }),
|
|
690
|
+
middlewares: [requireAdmin],
|
|
691
|
+
query: async ({ input, ctx }) => {
|
|
664
692
|
// Only admins can reach here
|
|
665
|
-
|
|
693
|
+
return ctx.db.table('users').where('id', 'eq', input.id).execute();
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const api = serve({
|
|
698
|
+
queries: { deleteUser },
|
|
699
|
+
});
|
|
666
700
|
```
|
|
667
701
|
|
|
668
702
|
---
|
|
@@ -721,7 +755,7 @@ interface ServeLifecycleHooks<TAuth> {
|
|
|
721
755
|
**Example:**
|
|
722
756
|
|
|
723
757
|
```ts
|
|
724
|
-
const api =
|
|
758
|
+
const api = serve({
|
|
725
759
|
hooks: {
|
|
726
760
|
onRequestStart: async (event) => {
|
|
727
761
|
await analytics.track({
|
|
@@ -767,7 +801,7 @@ interface OpenApiOptions {
|
|
|
767
801
|
**Example:**
|
|
768
802
|
|
|
769
803
|
```ts
|
|
770
|
-
const api =
|
|
804
|
+
const api = serve({
|
|
771
805
|
openapi: {
|
|
772
806
|
path: '/api-schema.json',
|
|
773
807
|
info: {
|
|
@@ -801,7 +835,7 @@ interface DocsOptions {
|
|
|
801
835
|
**Example:**
|
|
802
836
|
|
|
803
837
|
```ts
|
|
804
|
-
const api =
|
|
838
|
+
const api = serve({
|
|
805
839
|
docs: {
|
|
806
840
|
path: '/api-docs',
|
|
807
841
|
title: 'Analytics API Reference',
|
|
@@ -882,32 +916,33 @@ All functions are fully typed with automatic inference:
|
|
|
882
916
|
import { initServe } from '@hypequery/serve';
|
|
883
917
|
import { z } from 'zod';
|
|
884
918
|
|
|
885
|
-
const {
|
|
919
|
+
const { query, serve } = initServe({
|
|
886
920
|
context: async ({ auth }) => ({
|
|
887
921
|
db: createDatabase(),
|
|
888
922
|
userId: auth?.userId,
|
|
889
923
|
}),
|
|
890
924
|
});
|
|
891
925
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
926
|
+
const getUser = query({
|
|
927
|
+
input: z.object({ id: z.string() }),
|
|
928
|
+
query: async ({ ctx, input }) => {
|
|
929
|
+
// input: { id: string }
|
|
930
|
+
// ctx: { db: Database; userId: string | undefined }
|
|
931
|
+
return ctx.db
|
|
932
|
+
.table('users')
|
|
933
|
+
.where('id', 'eq', input.id)
|
|
934
|
+
.select(['name', 'email'])
|
|
935
|
+
.limit(1)
|
|
936
|
+
.execute();
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
export const api = serve({
|
|
941
|
+
queries: { getUser },
|
|
907
942
|
});
|
|
908
943
|
|
|
909
944
|
// Execute with type safety (aliases: api.execute, api.client)
|
|
910
|
-
const result = await api.run('getUser', { id: '123' });
|
|
945
|
+
const result = await api.run('getUser', { input: { id: '123' } });
|
|
911
946
|
const user = result[0];
|
|
912
947
|
// user: { name: string; email: string }
|
|
913
948
|
```
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type IncomingMessage, type ServerResponse } from "http";
|
|
2
2
|
import type { ServeHandler, StartServerOptions } from "../types.js";
|
|
3
|
-
export declare const createNodeHandler: (handler: ServeHandler) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
3
|
+
export declare const createNodeHandler: (handler: ServeHandler, options?: StartServerOptions) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
4
4
|
export declare const startNodeServer: (handler: ServeHandler, options?: StartServerOptions) => Promise<{
|
|
5
5
|
server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
6
6
|
stop: () => Promise<void>;
|
|
@@ -1 +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;
|
|
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;AAqHrB,eAAO,MAAM,iBAAiB,GAC5B,SAAS,YAAY,EACrB,UAAS,kBAAuB,MAKlB,KAAK,eAAe,EAAE,KAAK,cAAc,kBAmCxD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EACrB,UAAS,kBAAuB;;gBA0BP,OAAO,CAAC,IAAI,CAAC;EAiEvC,CAAC"}
|