@hypequery/serve 0.1.0 → 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 +47 -18
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +87 -20
- 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 +71 -16
- 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 +108 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -1
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,13 @@ export * from "./endpoint.js";
|
|
|
6
6
|
export * from "./openapi.js";
|
|
7
7
|
export * from "./docs-ui.js";
|
|
8
8
|
export * from "./auth.js";
|
|
9
|
+
export * from "./cors.js";
|
|
10
|
+
export * from "./errors.js";
|
|
11
|
+
export * from "./rate-limit.js";
|
|
9
12
|
export * from "./client-config.js";
|
|
10
13
|
export * from "./utils.js";
|
|
11
14
|
export * from "./adapters/node.js";
|
|
12
15
|
export * from "./adapters/fetch.js";
|
|
13
16
|
export * from "./adapters/vercel.js";
|
|
14
17
|
export * from "./dev.js";
|
|
18
|
+
export * from "./serve.js";
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { AuthContext, AuthStrategy, DocsOptions, OpenApiOptions, ServeContextFactory, ServeEndpoint, ServeHandler, ServeLifecycleHooks, ServeMiddleware, ServeRequest, ServeResponse, TenantConfig } from './types.js';
|
|
3
3
|
import { ServeQueryLogger } from './query-logger.js';
|
|
4
|
+
import { type ResolvedCorsConfig } from './cors.js';
|
|
4
5
|
export interface ExecuteEndpointOptions<TContext extends Record<string, unknown>, TAuth extends AuthContext> {
|
|
5
6
|
endpoint: ServeEndpoint<any, any, TContext, TAuth>;
|
|
6
7
|
request: ServeRequest;
|
|
@@ -13,6 +14,11 @@ export interface ExecuteEndpointOptions<TContext extends Record<string, unknown>
|
|
|
13
14
|
queryLogger?: ServeQueryLogger;
|
|
14
15
|
additionalContext?: Partial<TContext>;
|
|
15
16
|
verboseAuthErrors?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* When true (the default), internal error details are hidden from responses.
|
|
19
|
+
* Set to false for in-process execution where the caller is trusted.
|
|
20
|
+
*/
|
|
21
|
+
sanitizeErrors?: boolean;
|
|
16
22
|
}
|
|
17
23
|
export declare const executeEndpoint: <TContext extends Record<string, unknown>, TAuth extends AuthContext>(options: ExecuteEndpointOptions<TContext, TAuth>) => Promise<ServeResponse>;
|
|
18
24
|
interface HandlerOptions<TContext extends Record<string, unknown>, TAuth extends AuthContext> {
|
|
@@ -24,8 +30,9 @@ interface HandlerOptions<TContext extends Record<string, unknown>, TAuth extends
|
|
|
24
30
|
hooks?: ServeLifecycleHooks<TAuth>;
|
|
25
31
|
queryLogger?: ServeQueryLogger;
|
|
26
32
|
verboseAuthErrors?: boolean;
|
|
33
|
+
corsConfig?: ResolvedCorsConfig | null;
|
|
27
34
|
}
|
|
28
|
-
export declare const createServeHandler: <TContext extends Record<string, unknown>, TAuth extends AuthContext>({ router, globalMiddlewares, authStrategies, tenantConfig, contextFactory, hooks, queryLogger, verboseAuthErrors, }: HandlerOptions<TContext, TAuth>) => ServeHandler;
|
|
35
|
+
export declare const createServeHandler: <TContext extends Record<string, unknown>, TAuth extends AuthContext>({ router, globalMiddlewares, authStrategies, tenantConfig, contextFactory, hooks, queryLogger, verboseAuthErrors, corsConfig, }: HandlerOptions<TContext, TAuth>) => ServeHandler;
|
|
29
36
|
export declare const createOpenApiEndpoint: (path: string, getEndpoints: () => ServeEndpoint<any, any, any, any>[], options?: OpenApiOptions) => {
|
|
30
37
|
key: string;
|
|
31
38
|
method: "GET";
|
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAC;AAEzC,OAAO,KAAK,EACV,WAAW,
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
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 {
|
|
6
|
+
import { ServeHttpError } from './errors.js';
|
|
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
|
|
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;
|
|
@@ -52,13 +54,29 @@ const runMiddlewares = async (middlewares, ctx, handler) => {
|
|
|
52
54
|
return current();
|
|
53
55
|
};
|
|
54
56
|
const authenticateRequest = async (strategies, request, metadata) => {
|
|
57
|
+
let missingError;
|
|
58
|
+
let invalidError;
|
|
55
59
|
for (const strategy of strategies) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
try {
|
|
61
|
+
const result = await strategy({ request, endpoint: metadata });
|
|
62
|
+
if (result) {
|
|
63
|
+
return { auth: result };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof AuthError) {
|
|
68
|
+
if (error.reason === 'INVALID') {
|
|
69
|
+
invalidError = invalidError ?? error;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
missingError = missingError ?? error;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
59
77
|
}
|
|
60
78
|
}
|
|
61
|
-
return null;
|
|
79
|
+
return { auth: null, error: invalidError ?? missingError };
|
|
62
80
|
};
|
|
63
81
|
const gatherAuthStrategies = (endpointStrategy, globalStrategies) => {
|
|
64
82
|
const combined = [];
|
|
@@ -126,6 +144,7 @@ const resolveContext = async (factory, request, auth) => {
|
|
|
126
144
|
const resolveRequestId = (request, provided) => provided ?? request.headers['x-request-id'] ?? request.headers['x-trace-id'] ?? generateRequestId();
|
|
127
145
|
export const executeEndpoint = async (options) => {
|
|
128
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
|
|
129
148
|
} = options;
|
|
130
149
|
const requestId = resolveRequestId(request, explicitRequestId);
|
|
131
150
|
const locals = {};
|
|
@@ -170,19 +189,29 @@ export const executeEndpoint = async (options) => {
|
|
|
170
189
|
requiresAuth,
|
|
171
190
|
};
|
|
172
191
|
context.metadata = metadataWithAuth;
|
|
173
|
-
const
|
|
192
|
+
const authResult = await authenticateRequest(strategies, request, metadataWithAuth);
|
|
193
|
+
const authContext = authResult.auth;
|
|
174
194
|
if (!authContext && requiresAuth) {
|
|
195
|
+
const authErrorInfo = authResult.error
|
|
196
|
+
? {
|
|
197
|
+
reason: authResult.error.reason,
|
|
198
|
+
message: authResult.error.message,
|
|
199
|
+
details: authResult.error.details,
|
|
200
|
+
}
|
|
201
|
+
: undefined;
|
|
175
202
|
await safeInvokeHook('onAuthFailure', hooks.onAuthFailure, {
|
|
176
203
|
requestId,
|
|
177
204
|
queryKey: endpoint.key,
|
|
178
205
|
metadata: metadataWithAuth,
|
|
179
206
|
request,
|
|
180
207
|
auth: context.auth,
|
|
181
|
-
reason: 'MISSING',
|
|
208
|
+
reason: authErrorInfo?.reason ?? 'MISSING',
|
|
209
|
+
error: authErrorInfo,
|
|
182
210
|
});
|
|
183
|
-
return createErrorResponse(401, 'UNAUTHORIZED', verboseAuthErrors ? 'Authentication required' : 'Access denied', {
|
|
184
|
-
reason: 'missing_credentials',
|
|
211
|
+
return createErrorResponse(401, 'UNAUTHORIZED', verboseAuthErrors ? authErrorInfo?.message ?? 'Authentication required' : 'Access denied', {
|
|
212
|
+
reason: authErrorInfo?.reason === 'INVALID' ? 'invalid_credentials' : 'missing_credentials',
|
|
185
213
|
...(verboseAuthErrors && { strategies_attempted: strategies.length }),
|
|
214
|
+
...(verboseAuthErrors && authErrorInfo?.details ? { auth_error: authErrorInfo.details } : {}),
|
|
186
215
|
endpoint: endpoint.metadata.path,
|
|
187
216
|
}, { 'x-request-id': requestId });
|
|
188
217
|
}
|
|
@@ -356,18 +385,39 @@ export const executeEndpoint = async (options) => {
|
|
|
356
385
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
357
386
|
});
|
|
358
387
|
}
|
|
359
|
-
|
|
360
|
-
|
|
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 });
|
|
361
404
|
}
|
|
362
405
|
};
|
|
363
|
-
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, }) => {
|
|
364
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
|
+
}
|
|
365
413
|
const requestId = resolveRequestId(request);
|
|
366
414
|
const endpoint = router.match(request.method, request.path);
|
|
367
415
|
if (!endpoint) {
|
|
368
|
-
|
|
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;
|
|
369
419
|
}
|
|
370
|
-
|
|
420
|
+
const response = await executeEndpoint({
|
|
371
421
|
endpoint,
|
|
372
422
|
request,
|
|
373
423
|
requestId,
|
|
@@ -379,6 +429,11 @@ export const createServeHandler = ({ router, globalMiddlewares, authStrategies,
|
|
|
379
429
|
queryLogger,
|
|
380
430
|
verboseAuthErrors,
|
|
381
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;
|
|
382
437
|
};
|
|
383
438
|
};
|
|
384
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
|
+
};
|
package/dist/serve.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/server/builder.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/server/builder.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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"}
|