@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.
Files changed (41) hide show
  1. package/README.md +220 -185
  2. package/dist/adapters/node.d.ts +1 -1
  3. package/dist/adapters/node.d.ts.map +1 -1
  4. package/dist/adapters/node.js +114 -21
  5. package/dist/auth.d.ts +27 -17
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/auth.js +27 -17
  8. package/dist/cors.d.ts +17 -0
  9. package/dist/cors.d.ts.map +1 -0
  10. package/dist/cors.js +82 -0
  11. package/dist/dev.js +1 -1
  12. package/dist/errors.d.ts +24 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +22 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -0
  18. package/dist/pipeline.d.ts +8 -1
  19. package/dist/pipeline.d.ts.map +1 -1
  20. package/dist/pipeline.js +36 -7
  21. package/dist/rate-limit.d.ts +86 -0
  22. package/dist/rate-limit.d.ts.map +1 -0
  23. package/dist/rate-limit.js +137 -0
  24. package/dist/serve.d.ts +16 -0
  25. package/dist/serve.d.ts.map +1 -0
  26. package/dist/serve.js +88 -0
  27. package/dist/server/builder.d.ts +1 -1
  28. package/dist/server/builder.d.ts.map +1 -1
  29. package/dist/server/builder.js +1 -0
  30. package/dist/server/define-serve.d.ts.map +1 -1
  31. package/dist/server/define-serve.js +3 -0
  32. package/dist/server/execute-query.d.ts.map +1 -1
  33. package/dist/server/execute-query.js +6 -1
  34. package/dist/server/init-serve.d.ts.map +1 -1
  35. package/dist/server/init-serve.js +23 -8
  36. package/dist/type-tests/builder.test-d.d.ts +8 -2
  37. package/dist/type-tests/builder.test-d.d.ts.map +1 -1
  38. package/dist/type-tests/builder.test-d.js +17 -1
  39. package/dist/types.d.ts +102 -5
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +9 -1
package/dist/pipeline.js CHANGED
@@ -3,7 +3,9 @@ import { createTenantScope, warnTenantMisconfiguration } from './tenant.js';
3
3
  import { generateRequestId } from './utils.js';
4
4
  import { buildOpenApiDocument } from './openapi.js';
5
5
  import { buildDocsHtml } from './docs-ui.js';
6
+ import { ServeHttpError } from './errors.js';
6
7
  import { checkRoleAuthorization, checkScopeAuthorization, AuthError, } from './auth.js';
8
+ import { handleCorsRequest } from './cors.js';
7
9
  const safeInvokeHook = async (name, hook, payload) => {
8
10
  if (!hook)
9
11
  return;
@@ -16,7 +18,7 @@ const safeInvokeHook = async (name, hook, payload) => {
16
18
  };
17
19
  const createErrorResponse = (status, type, message, details, headers) => ({
18
20
  status,
19
- headers,
21
+ headers: headers ?? {},
20
22
  body: { error: { type, message, ...(details ? { details } : {}) } },
21
23
  });
22
24
  const buildContextInput = (request) => {
@@ -37,7 +39,7 @@ const resolveTenantConfig = (globalConfig, override) => {
37
39
  }
38
40
  const merged = { ...(globalConfig ?? {}), ...(override ?? {}) };
39
41
  if (!merged.extract) {
40
- throw new Error('[hypequery/serve] Tenant override requires an extract function when no global tenant config is set. ' +
42
+ throw new ServeHttpError(500, 'INTERNAL_SERVER_ERROR', '[hypequery/serve] Tenant override requires an extract function when no global tenant config is set. ' +
41
43
  'If you are using tenantOptional(), define a global tenant config with extract or pass extract in the per-query override.');
42
44
  }
43
45
  return merged;
@@ -142,6 +144,7 @@ const resolveContext = async (factory, request, auth) => {
142
144
  const resolveRequestId = (request, provided) => provided ?? request.headers['x-request-id'] ?? request.headers['x-trace-id'] ?? generateRequestId();
143
145
  export const executeEndpoint = async (options) => {
144
146
  const { endpoint, request, requestId: explicitRequestId, authStrategies, contextFactory, globalMiddlewares, tenantConfig, hooks = {}, queryLogger, additionalContext, verboseAuthErrors = false, // Default to secure mode for production safety
147
+ sanitizeErrors = true, // Default to secure mode for HTTP requests
145
148
  } = options;
146
149
  const requestId = resolveRequestId(request, explicitRequestId);
147
150
  const locals = {};
@@ -382,18 +385,39 @@ export const executeEndpoint = async (options) => {
382
385
  error: error instanceof Error ? error : new Error(String(error)),
383
386
  });
384
387
  }
385
- const message = error instanceof Error ? error.message : 'Unexpected error';
386
- return createErrorResponse(500, 'INTERNAL_SERVER_ERROR', message, undefined, { 'x-request-id': requestId });
388
+ // Structured errors thrown by middleware (rate limiter, custom middleware, etc.)
389
+ if (error &&
390
+ typeof error === 'object' &&
391
+ 'status' in error &&
392
+ 'payload' in error) {
393
+ const structured = error;
394
+ const response = createErrorResponse(structured.status, structured.payload.type, structured.payload.message, undefined, { 'x-request-id': requestId, ...(structured.headers ?? {}) });
395
+ return response;
396
+ }
397
+ // When sanitizeErrors is enabled (default for HTTP), hide internal error
398
+ // details to prevent leaking stack traces, paths, or SQL to clients.
399
+ // The raw error is still available in the onError hook above.
400
+ const errorMessage = sanitizeErrors
401
+ ? 'An unexpected error occurred'
402
+ : (error instanceof Error ? error.message : String(error));
403
+ return createErrorResponse(500, 'INTERNAL_SERVER_ERROR', errorMessage, undefined, { 'x-request-id': requestId });
387
404
  }
388
405
  };
389
- export const createServeHandler = ({ router, globalMiddlewares, authStrategies, tenantConfig, contextFactory, hooks, queryLogger, verboseAuthErrors = false, }) => {
406
+ export const createServeHandler = ({ router, globalMiddlewares, authStrategies, tenantConfig, contextFactory, hooks, queryLogger, verboseAuthErrors = false, corsConfig, }) => {
390
407
  return async (request) => {
408
+ // Handle CORS preflight and compute headers for actual requests
409
+ const { preflightResponse, corsHeaders } = handleCorsRequest(corsConfig ?? null, request);
410
+ if (preflightResponse) {
411
+ return preflightResponse;
412
+ }
391
413
  const requestId = resolveRequestId(request);
392
414
  const endpoint = router.match(request.method, request.path);
393
415
  if (!endpoint) {
394
- return createErrorResponse(404, 'NOT_FOUND', `No endpoint registered for ${request.method} ${request.path}`, undefined, { 'x-request-id': requestId });
416
+ const response = createErrorResponse(404, 'NOT_FOUND', `No endpoint registered for ${request.method} ${request.path}`, undefined, { 'x-request-id': requestId });
417
+ response.headers = { ...response.headers, ...corsHeaders };
418
+ return response;
395
419
  }
396
- return executeEndpoint({
420
+ const response = await executeEndpoint({
397
421
  endpoint,
398
422
  request,
399
423
  requestId,
@@ -405,6 +429,11 @@ export const createServeHandler = ({ router, globalMiddlewares, authStrategies,
405
429
  queryLogger,
406
430
  verboseAuthErrors,
407
431
  });
432
+ // Inject CORS headers into every response
433
+ if (Object.keys(corsHeaders).length > 0) {
434
+ response.headers = { ...response.headers, ...corsHeaders };
435
+ }
436
+ return response;
408
437
  };
409
438
  };
410
439
  export const createOpenApiEndpoint = (path, getEndpoints, options) => {
@@ -0,0 +1,86 @@
1
+ import type { AuthContext, ServeMiddleware, EndpointContext } from './types.js';
2
+ /**
3
+ * Rate limit store interface.
4
+ * Implement this for custom backends (Redis, Memcached, etc.).
5
+ */
6
+ export interface RateLimitStore {
7
+ /**
8
+ * Increment the hit count for a key within the given window.
9
+ * @returns The current hit count after incrementing.
10
+ */
11
+ increment(key: string, windowMs: number): Promise<number>;
12
+ /**
13
+ * Get the remaining TTL in milliseconds for a key.
14
+ * Returns 0 if the key has no active window.
15
+ */
16
+ getTtl(key: string): Promise<number>;
17
+ /**
18
+ * Reset the counter for a key.
19
+ */
20
+ reset(key: string): Promise<void>;
21
+ }
22
+ export interface RateLimitConfig<TContext extends Record<string, unknown> = Record<string, unknown>, TAuth extends AuthContext = AuthContext> {
23
+ /**
24
+ * Time window in milliseconds.
25
+ * @default 60000 (1 minute)
26
+ */
27
+ windowMs?: number;
28
+ /**
29
+ * Maximum number of requests allowed per window.
30
+ * @default 100
31
+ */
32
+ max?: number;
33
+ /**
34
+ * Function to derive the rate limit key from the request context.
35
+ * Defaults to IP-based limiting (from x-forwarded-for or x-real-ip).
36
+ */
37
+ keyBy?: (ctx: EndpointContext<unknown, TContext, TAuth>) => string | null;
38
+ /**
39
+ * Custom store implementation. Defaults to an in-memory store.
40
+ */
41
+ store?: RateLimitStore;
42
+ /**
43
+ * Whether to include rate limit headers in the response.
44
+ * @default true
45
+ */
46
+ headers?: boolean;
47
+ /**
48
+ * Custom message to return when rate limited.
49
+ * @default "Too many requests, please try again later"
50
+ */
51
+ message?: string;
52
+ /**
53
+ * If true, skip rate limiting instead of rejecting when the store fails.
54
+ * @default true
55
+ */
56
+ failOpen?: boolean;
57
+ }
58
+ export declare class MemoryRateLimitStore implements RateLimitStore {
59
+ private entries;
60
+ private cleanupTimer;
61
+ constructor(cleanupIntervalMs?: number);
62
+ increment(key: string, windowMs: number): Promise<number>;
63
+ getTtl(key: string): Promise<number>;
64
+ reset(key: string): Promise<void>;
65
+ destroy(): void;
66
+ }
67
+ /**
68
+ * Creates a rate-limiting middleware.
69
+ *
70
+ * @example Global rate limit:
71
+ * ```ts
72
+ * const api = defineServe({
73
+ * queries: { ... },
74
+ * middlewares: [rateLimit({ windowMs: 60_000, max: 100 })],
75
+ * });
76
+ * ```
77
+ *
78
+ * @example Per-tenant rate limit on a single query:
79
+ * ```ts
80
+ * query
81
+ * .use(rateLimit({ max: 50, keyBy: (ctx) => ctx.auth?.tenantId ?? null }))
82
+ * .query(async ({ ctx, input }) => { ... })
83
+ * ```
84
+ */
85
+ export declare const rateLimit: <TContext extends Record<string, unknown> = Record<string, unknown>, TAuth extends AuthContext = AuthContext>(config?: RateLimitConfig<TContext, TAuth>) => ServeMiddleware<any, any, TContext, TAuth>;
86
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;OAEG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe,CAC9B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW;IAEvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAC1E;;OAEG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAWD,qBAAa,oBAAqB,YAAW,cAAc;IACzD,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,YAAY,CAAiC;gBAEzC,iBAAiB,SAAS;IAgBhC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAczD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOpC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,OAAO,IAAI,IAAI;CAIhB;AAmBD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EAEvC,SAAQ,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAM,KAC5C,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAwE3C,CAAC"}
@@ -0,0 +1,137 @@
1
+ export class MemoryRateLimitStore {
2
+ constructor(cleanupIntervalMs = 60000) {
3
+ this.entries = new Map();
4
+ // Periodic cleanup of expired entries to prevent memory leaks
5
+ this.cleanupTimer = setInterval(() => {
6
+ const now = Date.now();
7
+ for (const [key, entry] of this.entries) {
8
+ if (entry.expiresAt <= now) {
9
+ this.entries.delete(key);
10
+ }
11
+ }
12
+ }, cleanupIntervalMs);
13
+ // Unref so the timer doesn't keep the process alive
14
+ if (typeof this.cleanupTimer === 'object' && 'unref' in this.cleanupTimer) {
15
+ this.cleanupTimer.unref();
16
+ }
17
+ }
18
+ async increment(key, windowMs) {
19
+ const now = Date.now();
20
+ const existing = this.entries.get(key);
21
+ if (existing && existing.expiresAt > now) {
22
+ existing.count++;
23
+ return existing.count;
24
+ }
25
+ // New window
26
+ this.entries.set(key, { count: 1, expiresAt: now + windowMs });
27
+ return 1;
28
+ }
29
+ async getTtl(key) {
30
+ const entry = this.entries.get(key);
31
+ if (!entry)
32
+ return 0;
33
+ const remaining = entry.expiresAt - Date.now();
34
+ return remaining > 0 ? remaining : 0;
35
+ }
36
+ async reset(key) {
37
+ this.entries.delete(key);
38
+ }
39
+ destroy() {
40
+ clearInterval(this.cleanupTimer);
41
+ this.entries.clear();
42
+ }
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // Key extraction helpers
46
+ // ---------------------------------------------------------------------------
47
+ const defaultKeyExtractor = (ctx) => {
48
+ const req = ctx.request;
49
+ return (req.headers['x-forwarded-for']?.split(',')[0]?.trim() ??
50
+ req.headers['x-real-ip'] ??
51
+ 'unknown');
52
+ };
53
+ // ---------------------------------------------------------------------------
54
+ // Middleware factory
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Creates a rate-limiting middleware.
58
+ *
59
+ * @example Global rate limit:
60
+ * ```ts
61
+ * const api = defineServe({
62
+ * queries: { ... },
63
+ * middlewares: [rateLimit({ windowMs: 60_000, max: 100 })],
64
+ * });
65
+ * ```
66
+ *
67
+ * @example Per-tenant rate limit on a single query:
68
+ * ```ts
69
+ * query
70
+ * .use(rateLimit({ max: 50, keyBy: (ctx) => ctx.auth?.tenantId ?? null }))
71
+ * .query(async ({ ctx, input }) => { ... })
72
+ * ```
73
+ */
74
+ export const rateLimit = (config = {}) => {
75
+ const windowMs = config.windowMs ?? 60000;
76
+ const max = config.max ?? 100;
77
+ const keyBy = config.keyBy ?? defaultKeyExtractor;
78
+ const store = config.store ?? new MemoryRateLimitStore();
79
+ const includeHeaders = config.headers !== false;
80
+ const message = config.message ?? 'Too many requests, please try again later';
81
+ const failOpen = config.failOpen !== false;
82
+ return async (ctx, next) => {
83
+ const key = keyBy(ctx);
84
+ // If we can't derive a key, skip rate limiting
85
+ if (!key) {
86
+ return next();
87
+ }
88
+ const rateLimitKey = `rl:${ctx.metadata.path}:${key}`;
89
+ let current;
90
+ let ttl;
91
+ try {
92
+ current = await store.increment(rateLimitKey, windowMs);
93
+ ttl = await store.getTtl(rateLimitKey);
94
+ }
95
+ catch {
96
+ if (failOpen) {
97
+ return next();
98
+ }
99
+ throw Object.assign(new Error('Rate limiter unavailable'), {
100
+ status: 503,
101
+ payload: {
102
+ type: 'SERVICE_UNAVAILABLE',
103
+ message: 'Rate limiter unavailable',
104
+ },
105
+ });
106
+ }
107
+ // Attach rate limit info to locals so hooks/handlers can inspect it
108
+ ctx.locals._rateLimit = { limit: max, remaining: Math.max(0, max - current), resetMs: ttl };
109
+ if (current > max) {
110
+ const retryAfterSec = Math.ceil(ttl / 1000);
111
+ const error = Object.assign(new Error(message), {
112
+ status: 429,
113
+ headers: {
114
+ 'retry-after': String(retryAfterSec),
115
+ ...(includeHeaders
116
+ ? {
117
+ 'x-ratelimit-limit': String(max),
118
+ 'x-ratelimit-remaining': '0',
119
+ 'x-ratelimit-reset': String(retryAfterSec),
120
+ }
121
+ : {}),
122
+ },
123
+ payload: {
124
+ type: 'RATE_LIMITED',
125
+ message,
126
+ },
127
+ });
128
+ throw error;
129
+ }
130
+ // Execute downstream handler
131
+ const result = await next();
132
+ // We can't directly set headers from middleware in the current architecture,
133
+ // but the rate limit info is available via ctx.locals._rateLimit
134
+ // for the lifecycle hooks or future header injection.
135
+ return result;
136
+ };
137
+ };
@@ -0,0 +1,16 @@
1
+ import type { AuthContext, QueryObjectConfig, SchemaOutput, ServeBuilder, ServeConfig, ServeContextFactory, ServeEndpointMap, ServeQueriesMap, StandaloneQueryDefinition } from "./types.js";
2
+ import type { ZodTypeAny } from "zod";
3
+ export declare const createQueryFactory: <TContext extends Record<string, unknown> = Record<string, unknown>, TAuth extends AuthContext = AuthContext>(contextFactory?: ServeContextFactory<TContext, TAuth>) => <TInputSchema extends ZodTypeAny | undefined = undefined, TOutputSchema extends ZodTypeAny | undefined = undefined, TResult = TOutputSchema extends ZodTypeAny ? SchemaOutput<TOutputSchema> : unknown>(config: QueryObjectConfig<TInputSchema, TOutputSchema, TContext, TAuth, TResult>) => StandaloneQueryDefinition<TInputSchema, TOutputSchema extends ZodTypeAny ? TOutputSchema : ZodTypeAny, TContext, TAuth, TResult>;
4
+ /**
5
+ * Create a reusable query definition that can execute in-process or be served via HTTP.
6
+ *
7
+ * Use `initServe()` when you want shared context passed once for both local execution and HTTP wiring.
8
+ */
9
+ export declare const query: <TInputSchema extends ZodTypeAny | undefined = undefined, TOutputSchema extends ZodTypeAny | undefined = undefined, TResult = TOutputSchema extends ZodTypeAny ? SchemaOutput<TOutputSchema> : unknown>(config: QueryObjectConfig<TInputSchema, TOutputSchema, Record<string, unknown>, AuthContext, TResult>) => StandaloneQueryDefinition<TInputSchema, TOutputSchema extends ZodTypeAny ? TOutputSchema : ZodTypeAny, Record<string, unknown>, AuthContext, TResult>;
10
+ /**
11
+ * Create a Serve API from a queries map.
12
+ *
13
+ * This is the unbound version. Use `initServe().serve(...)` when you want shared context and config.
14
+ */
15
+ export declare function serve<TContext extends Record<string, unknown> = Record<string, unknown>, TAuth extends AuthContext = AuthContext, TQueries extends ServeQueriesMap<TContext, TAuth> = ServeQueriesMap<TContext, TAuth>>(config: ServeConfig<TContext, TAuth, TQueries>): ServeBuilder<ServeEndpointMap<TQueries, TContext, TAuth>, TContext, TAuth>;
16
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EAGjB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EAEf,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAgGtC,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EAEvC,iBAAiB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,MAGnD,YAAY,SAAS,UAAU,GAAG,SAAS,GAAG,SAAS,EACvD,aAAa,SAAS,UAAU,GAAG,SAAS,GAAG,SAAS,EACxD,OAAO,GAAG,aAAa,SAAS,UAAU,GAAG,YAAY,CAAC,aAAa,CAAC,GAAG,OAAO,EAElF,QAAQ,iBAAiB,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAC/E,yBAAyB,CAC1B,YAAY,EACZ,aAAa,SAAS,UAAU,GAAG,aAAa,GAAG,UAAU,EAC7D,QAAQ,EACR,KAAK,EACL,OAAO,CAIV,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,KAAK,GArBd,YAAY,SAAS,UAAU,GAAG,SAAS,cAC3C,aAAa,SAAS,UAAU,GAAG,SAAS,cAC5C,OAAO,4UAmB8B,CAAC;AAE1C;;;;GAIG;AACH,wBAAgB,KAAK,CACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EACpF,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAE5H"}
package/dist/serve.js ADDED
@@ -0,0 +1,88 @@
1
+ import { defineServe } from "./server/define-serve.js";
2
+ const defaultRequest = {
3
+ method: "POST",
4
+ path: "/__query/execute",
5
+ query: {},
6
+ headers: {},
7
+ };
8
+ const resolveContext = async (contextFactory, request) => {
9
+ if (!contextFactory) {
10
+ return {};
11
+ }
12
+ if (typeof contextFactory === "function") {
13
+ return await contextFactory({ request, auth: null });
14
+ }
15
+ return contextFactory;
16
+ };
17
+ const parseMaybe = (schema, value) => {
18
+ if (!schema) {
19
+ return value;
20
+ }
21
+ return schema.parse(value);
22
+ };
23
+ const createStandaloneQuery = (config, contextFactory) => {
24
+ const run = config.query;
25
+ const definition = {
26
+ query: run,
27
+ run,
28
+ execute: async (options) => {
29
+ const request = {
30
+ method: options?.request?.method ?? config.method ?? "POST",
31
+ path: options?.request?.path ?? defaultRequest.path,
32
+ query: options?.request?.query ?? defaultRequest.query,
33
+ headers: options?.request?.headers ?? defaultRequest.headers,
34
+ body: options?.request?.body ?? options?.input,
35
+ raw: options?.request?.raw,
36
+ };
37
+ const resolvedContext = await resolveContext(contextFactory, request);
38
+ const input = parseMaybe(config.input, options?.input);
39
+ const runtimeContext = {
40
+ request,
41
+ auth: null,
42
+ locals: {},
43
+ setCacheTtl: () => undefined,
44
+ ...resolvedContext,
45
+ ...(options?.context ?? {}),
46
+ };
47
+ const result = await run({
48
+ input,
49
+ ctx: runtimeContext,
50
+ });
51
+ return parseMaybe(config.output, result);
52
+ },
53
+ ...(config.input && { inputSchema: config.input }),
54
+ ...(config.output && { outputSchema: config.output }),
55
+ ...(config.method && { method: config.method }),
56
+ ...(config.name && { name: config.name }),
57
+ ...(config.description && { description: config.description }),
58
+ ...(config.summary && { summary: config.summary }),
59
+ ...(config.tags && { tags: config.tags }),
60
+ ...(typeof config.auth !== "undefined" && { auth: config.auth }),
61
+ ...(typeof config.requiresAuth !== "undefined" && { requiresAuth: config.requiresAuth }),
62
+ ...(typeof config.tenant !== "undefined" && { tenant: config.tenant }),
63
+ ...(typeof config.cacheTtlMs !== "undefined" && { cacheTtlMs: config.cacheTtlMs }),
64
+ ...(config.requiredRoles && { requiredRoles: config.requiredRoles }),
65
+ ...(config.requiredScopes && { requiredScopes: config.requiredScopes }),
66
+ ...(config.custom && { custom: config.custom }),
67
+ };
68
+ return definition;
69
+ };
70
+ export const createQueryFactory = (contextFactory) => {
71
+ return (config) => {
72
+ return createStandaloneQuery(config, contextFactory);
73
+ };
74
+ };
75
+ /**
76
+ * Create a reusable query definition that can execute in-process or be served via HTTP.
77
+ *
78
+ * Use `initServe()` when you want shared context passed once for both local execution and HTTP wiring.
79
+ */
80
+ export const query = createQueryFactory();
81
+ /**
82
+ * Create a Serve API from a queries map.
83
+ *
84
+ * This is the unbound version. Use `initServe().serve(...)` when you want shared context and config.
85
+ */
86
+ export function serve(config) {
87
+ return defineServe(config);
88
+ }
@@ -3,5 +3,5 @@ import type { ServeRouter } from "../router.js";
3
3
  import { ServeQueryLogger } from "../query-logger.js";
4
4
  export declare const createBuilderMethods: <TQueries extends ServeQueriesMap<TContext, TAuth>, TContext extends Record<string, unknown>, TAuth extends AuthContext>(queryEntries: ServeEndpointMap<TQueries, TContext, TAuth>, queryLogger: ServeQueryLogger, routeConfig: Record<string, {
5
5
  method: HttpMethod;
6
- }>, router: ServeRouter, authStrategies: AuthStrategy<TAuth>[], globalMiddlewares: ServeMiddleware<any, any, TContext, TAuth>[], executeQuery: ExecuteQueryFunction<ServeEndpointMap<TQueries, TContext, TAuth>, TContext, TAuth>, handler: ServeHandler, basePath: string) => ServeBuilder<ServeEndpointMap<TQueries, TContext, TAuth>, TContext, TAuth>;
6
+ }>, router: ServeRouter, authStrategies: AuthStrategy<TAuth>[], globalMiddlewares: ServeMiddleware<any, any, TContext, TAuth>[], executeQuery: ExecuteQueryFunction<ServeEndpointMap<TQueries, TContext, TAuth>, TContext>, handler: ServeHandler, basePath: string) => ServeBuilder<ServeEndpointMap<TQueries, TContext, TAuth>, TContext, TAuth>;
7
7
  //# sourceMappingURL=builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/server/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,YAAY,EAEZ,gBAAgB,EAChB,eAAe,EAEf,eAAe,EACf,YAAY,EACZ,oBAAoB,EAGrB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAYtD,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EACjD,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EAEzB,cAAc,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EACzD,aAAa,gBAAgB,EAC7B,aAAa,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC,EACnD,QAAQ,WAAW,EACnB,gBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,EACrC,mBAAmB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,EAC/D,cAAc,oBAAoB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,EAChG,SAAS,YAAY,EACrB,UAAU,MAAM,KACf,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAqF3E,CAAC"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/server/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,YAAY,EAEZ,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,oBAAoB,EAGrB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAYtD,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EACjD,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EAEzB,cAAc,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EACzD,aAAa,gBAAgB,EAC7B,aAAa,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAC,EACnD,QAAQ,WAAW,EACnB,gBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,EACrC,mBAAmB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,EAC/D,cAAc,oBAAoB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,EACzF,SAAS,YAAY,EACrB,UAAU,MAAM,KACf,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAsF3E,CAAC"}
@@ -10,6 +10,7 @@ const loadNodeAdapter = async () => {
10
10
  export const createBuilderMethods = (queryEntries, queryLogger, routeConfig, router, authStrategies, globalMiddlewares, executeQuery, handler, basePath) => {
11
11
  const builder = {
12
12
  queries: queryEntries,
13
+ basePath: basePath || undefined,
13
14
  queryLogger,
14
15
  _routeConfig: routeConfig,
15
16
  route: (path, endpoint, options = {}) => {
@@ -1 +1 @@
1
- {"version":3,"file":"define-serve.d.ts","sourceRoot":"","sources":["../../src/server/define-serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAGX,YAAY,EACZ,WAAW,EAGX,gBAAgB,EAIhB,eAAe,EAChB,MAAM,aAAa,CAAC;AAUrB,eAAO,MAAM,WAAW,GACtB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EAEpF,QAAQ,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,KAC7C,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAiH3E,CAAC"}
1
+ {"version":3,"file":"define-serve.d.ts","sourceRoot":"","sources":["../../src/server/define-serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAGX,YAAY,EACZ,WAAW,EAEX,gBAAgB,EAIhB,eAAe,EAEhB,MAAM,aAAa,CAAC;AAWrB,eAAO,MAAM,WAAW,GACtB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,EAEpF,QAAQ,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,KAC7C,YAAY,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,CAoH3E,CAAC"}
@@ -4,6 +4,7 @@ import { ensureArray } from "../utils.js";
4
4
  import { ServeQueryLogger, formatQueryEvent, formatQueryEventJSON } from "../query-logger.js";
5
5
  import { createServeHandler } from "../pipeline.js";
6
6
  import { createDocsEndpoint, createOpenApiEndpoint } from "../pipeline.js";
7
+ import { resolveCorsConfig } from "../cors.js";
7
8
  import { createExecuteQuery } from "./execute-query.js";
8
9
  import { createBuilderMethods } from "./builder.js";
9
10
  export const defineServe = (config) => {
@@ -62,6 +63,7 @@ export const defineServe = (config) => {
62
63
  for (const key of Object.keys(configuredQueries)) {
63
64
  registerQuery(key, configuredQueries[key]);
64
65
  }
66
+ const corsConfig = resolveCorsConfig(config.cors);
65
67
  const handler = createServeHandler({
66
68
  router,
67
69
  globalMiddlewares,
@@ -71,6 +73,7 @@ export const defineServe = (config) => {
71
73
  hooks,
72
74
  queryLogger,
73
75
  verboseAuthErrors: config.security?.verboseAuthErrors ?? false,
76
+ corsConfig,
74
77
  });
75
78
  // Track route configuration for client config extraction
76
79
  const routeConfig = {};
@@ -1 +1 @@
1
- {"version":3,"file":"execute-query.d.ts","sourceRoot":"","sources":["../../src/server/execute-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EAEZ,WAAW,EACX,mBAAmB,EAEnB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EAEf,YAAY,EACZ,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EAEzB,cAAc,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EACpD,gBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,EACrC,gBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,SAAS,EAChE,mBAAmB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,EAC/D,cAAc,YAAY,CAAC,KAAK,CAAC,GAAG,SAAS,EAC7C,OAAO,mBAAmB,CAAC,KAAK,CAAC,EACjC,aAAa,gBAAgB,EAC7B,mBAAmB,OAAO,MAEZ,IAAI,SAAS,MAAM,OAAO,YAAY,EAClD,KAAK,IAAI,EACT,UAAU;IACR,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,OAAO,YAAY,EAAE,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CACjC,KACA,OAAO,CAAC,mBAAmB,CAAC,CAAC,OAAO,YAAY,EAAE,IAAI,CAAC,CAAC,CAwC5D,CAAC"}
1
+ {"version":3,"file":"execute-query.d.ts","sourceRoot":"","sources":["../../src/server/execute-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EAEZ,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EAEzB,cAAc,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EACpD,gBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,EACrC,gBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,SAAS,EAChE,mBAAmB,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,EAC/D,cAAc,YAAY,CAAC,KAAK,CAAC,GAAG,SAAS,EAC7C,OAAO,mBAAmB,CAAC,KAAK,CAAC,EACjC,aAAa,gBAAgB,EAC7B,mBAAmB,OAAO,MAEZ,IAAI,SAAS,MAAM,OAAO,YAAY,EAClD,KAAK,IAAI,EACT,UAAU;IACR,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,OAAO,YAAY,EAAE,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CACjC,KACA,OAAO,CAAC,mBAAmB,CAAC,CAAC,OAAO,YAAY,EAAE,IAAI,CAAC,CAAC,CA8C5D,CAAC"}
@@ -3,7 +3,11 @@ export const createExecuteQuery = (queryEntries, authStrategies, contextFactory,
3
3
  return async (key, options) => {
4
4
  const endpoint = queryEntries[key];
5
5
  if (!endpoint) {
6
- throw new Error(`No query registered for key ${String(key)}`);
6
+ const availableQueries = Object.keys(queryEntries);
7
+ const availableMessage = availableQueries.length > 0
8
+ ? ` Available queries: ${availableQueries.join(", ")}.`
9
+ : " No queries are currently registered.";
10
+ throw new Error(`Query '${String(key)}' not found.${availableMessage}`);
7
11
  }
8
12
  const request = {
9
13
  method: endpoint.method,
@@ -24,6 +28,7 @@ export const createExecuteQuery = (queryEntries, authStrategies, contextFactory,
24
28
  queryLogger,
25
29
  additionalContext: options?.context,
26
30
  verboseAuthErrors,
31
+ sanitizeErrors: false, // In-process callers can see raw error messages
27
32
  });
28
33
  if (response.status !== 200) {
29
34
  const errorBody = response.body;
@@ -1 +1 @@
1
- {"version":3,"file":"init-serve.d.ts","sourceRoot":"","sources":["../../src/server/init-serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,aAAa,CAAC;AAIrB,KAAK,uBAAuB,CAC1B,QAAQ,EACR,KAAK,SAAS,WAAW,IACvB,QAAQ,SAAS,mBAAmB,CAAC,MAAM,QAAQ,EAAE,KAAK,CAAC,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEnF,KAAK,uBAAuB,CAC1B,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,EAChD,KAAK,SAAS,WAAW,IACvB,IAAI,CACN,WAAW,CACT,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EACxC,KAAK,EACL,eAAe,CAAC,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CACjE,EACD,SAAS,GAAG,SAAS,CACtB,GAAG;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;AAQ1B,eAAO,MAAM,SAAS,GACpB,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,EAChD,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,SAAS,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAG,gBAAgB,CACpE,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EACxC,KAAK,CAsBN,CAAC"}
1
+ {"version":3,"file":"init-serve.d.ts","sourceRoot":"","sources":["../../src/server/init-serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACZ,MAAM,aAAa,CAAC;AAKrB,KAAK,uBAAuB,CAC1B,QAAQ,EACR,KAAK,SAAS,WAAW,IACvB,QAAQ,SAAS,mBAAmB,CAAC,MAAM,QAAQ,EAAE,KAAK,CAAC,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEnF,KAAK,uBAAuB,CAC1B,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,EAChD,KAAK,SAAS,WAAW,IACvB,IAAI,CACN,WAAW,CACT,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EACxC,KAAK,EACL,eAAe,CAAC,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CACjE,EACD,SAAS,GAAG,SAAS,CACtB,GAAG;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;AAQ1B,eAAO,MAAM,SAAS,GACpB,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,EAChD,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,SAAS,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAG,gBAAgB,CACpE,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,EACxC,KAAK,CAqCN,CAAC"}
@@ -1,18 +1,33 @@
1
1
  import { createProcedureBuilder } from "../builder.js";
2
2
  import { defineServe } from "./define-serve.js";
3
+ import { createQueryFactory } from "../serve.js";
3
4
  export const initServe = (options) => {
4
5
  const { context, ...staticOptions } = options;
5
6
  const procedure = createProcedureBuilder();
7
+ const define = (config) => {
8
+ return defineServe({
9
+ ...staticOptions,
10
+ ...config,
11
+ context: (context ?? {}),
12
+ });
13
+ };
14
+ const queryFactory = createQueryFactory((context ?? {}));
15
+ const query = new Proxy(queryFactory, {
16
+ apply(target, thisArg, argArray) {
17
+ return Reflect.apply(target, thisArg, argArray);
18
+ },
19
+ get(target, property, receiver) {
20
+ if (property in procedure) {
21
+ return Reflect.get(procedure, property);
22
+ }
23
+ return Reflect.get(target, property, receiver);
24
+ },
25
+ });
6
26
  return {
7
27
  procedure,
8
- query: procedure,
28
+ query,
9
29
  queries: (definitions) => definitions,
10
- define: (config) => {
11
- return defineServe({
12
- ...staticOptions,
13
- ...config,
14
- context: (context ?? {}),
15
- });
16
- },
30
+ serve: define,
31
+ define,
17
32
  };
18
33
  };
@@ -6,8 +6,14 @@ export declare const api: import("../types.js").ServeBuilder<import("../types.js
6
6
  plan?: string | undefined;
7
7
  }, {
8
8
  plan?: string | undefined;
9
- }>, z.ZodTypeAny, {}, import("../types.js").AuthContext, {
9
+ }>, z.ZodTypeAny, {
10
+ db: {};
11
+ }, import("../types.js").AuthContext, {
10
12
  plan: string;
11
13
  }[]>;
12
- }, {}, import("../types.js").AuthContext>, {}, import("../types.js").AuthContext>;
14
+ }, {
15
+ db: {};
16
+ }, import("../types.js").AuthContext>, {
17
+ db: {};
18
+ }, import("../types.js").AuthContext>;
13
19
  //# sourceMappingURL=builder.test-d.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builder.test-d.d.ts","sourceRoot":"","sources":["../../src/type-tests/builder.test-d.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,GAAG;;;;;;;;;;iFAUd,CAAC"}
1
+ {"version":3,"file":"builder.test-d.d.ts","sourceRoot":"","sources":["../../src/type-tests/builder.test-d.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;qCAUd,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import { initServe } from '../index.js';
2
2
  import { z } from 'zod';
3
3
  const serve = initServe({
4
- context: () => ({}),
4
+ context: () => ({
5
+ db: {},
6
+ }),
5
7
  });
6
8
  const { query } = serve;
7
9
  export const api = serve.define({
@@ -18,3 +20,17 @@ export const api = serve.define({
18
20
  const _resultIsTyped = [{ plan: 'starter' }];
19
21
  // @ts-expect-error plan must be string
20
22
  const _resultRejectsNumber = [{ plan: 123 }];
23
+ const executableQuery = query({
24
+ input: z.object({ startDate: z.string() }),
25
+ output: z.object({ total: z.number() }),
26
+ query: async ({ input, ctx }) => {
27
+ ctx.db;
28
+ return { total: Number(input.startDate.length) };
29
+ },
30
+ });
31
+ const executableResultPromise = executableQuery.execute({
32
+ input: { startDate: '2024-01-01' },
33
+ });
34
+ const _executableResultIsTyped = { total: 10 };
35
+ // @ts-expect-error total must be number
36
+ const _executableResultRejectsString = { total: '10' };