@hypequery/serve 0.0.4 → 0.0.5

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 (62) hide show
  1. package/dist/adapters/fetch.d.ts +3 -0
  2. package/dist/adapters/fetch.d.ts.map +1 -0
  3. package/dist/adapters/fetch.js +26 -0
  4. package/dist/adapters/node.d.ts +8 -0
  5. package/dist/adapters/node.d.ts.map +1 -0
  6. package/dist/adapters/node.js +105 -0
  7. package/dist/adapters/utils.d.ts +39 -0
  8. package/dist/adapters/utils.d.ts.map +1 -0
  9. package/dist/adapters/utils.js +114 -0
  10. package/dist/adapters/vercel.d.ts +7 -0
  11. package/dist/adapters/vercel.d.ts.map +1 -0
  12. package/dist/adapters/vercel.js +13 -0
  13. package/dist/auth.d.ts +14 -0
  14. package/dist/auth.d.ts.map +1 -0
  15. package/dist/auth.js +37 -0
  16. package/dist/builder.d.ts +3 -0
  17. package/dist/builder.d.ts.map +1 -0
  18. package/dist/builder.js +41 -0
  19. package/dist/client-config.d.ts +44 -0
  20. package/dist/client-config.d.ts.map +1 -0
  21. package/dist/client-config.js +53 -0
  22. package/dist/dev.d.ts +9 -0
  23. package/dist/dev.d.ts.map +1 -0
  24. package/dist/dev.js +24 -0
  25. package/dist/docs-ui.d.ts +3 -0
  26. package/dist/docs-ui.d.ts.map +1 -0
  27. package/dist/docs-ui.js +34 -0
  28. package/dist/endpoint.d.ts +5 -0
  29. package/dist/endpoint.d.ts.map +1 -0
  30. package/dist/endpoint.js +59 -0
  31. package/dist/index.d.ts +13 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +12 -0
  34. package/dist/openapi.d.ts +3 -0
  35. package/dist/openapi.d.ts.map +1 -0
  36. package/dist/openapi.js +189 -0
  37. package/dist/pipeline.d.ts +72 -0
  38. package/dist/pipeline.d.ts.map +1 -0
  39. package/dist/pipeline.js +317 -0
  40. package/dist/query-logger.d.ts +65 -0
  41. package/dist/query-logger.d.ts.map +1 -0
  42. package/dist/query-logger.js +91 -0
  43. package/dist/router.d.ts +13 -0
  44. package/dist/router.d.ts.map +1 -0
  45. package/dist/router.js +56 -0
  46. package/dist/server.d.ts +9 -0
  47. package/dist/server.d.ts.map +1 -0
  48. package/dist/server.js +191 -0
  49. package/dist/tenant.d.ts +35 -0
  50. package/dist/tenant.d.ts.map +1 -0
  51. package/dist/tenant.js +49 -0
  52. package/dist/type-tests/builder.test-d.d.ts +13 -0
  53. package/dist/type-tests/builder.test-d.d.ts.map +1 -0
  54. package/dist/type-tests/builder.test-d.js +20 -0
  55. package/dist/types.d.ts +373 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +1 -0
  58. package/dist/utils.d.ts +4 -0
  59. package/dist/utils.d.ts.map +1 -0
  60. package/dist/utils.js +16 -0
  61. package/package.json +9 -9
  62. package/LICENSE +0 -201
@@ -0,0 +1,3 @@
1
+ import type { FetchHandler, ServeHandler } from "../types.js";
2
+ export declare const createFetchHandler: (handler: ServeHandler) => FetchHandler;
3
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/adapters/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAc,YAAY,EAAgB,MAAM,aAAa,CAAC;AAQxF,eAAO,MAAM,kBAAkB,GAAI,SAAS,YAAY,KAAG,YA4B1D,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { normalizeHeaders, parseQueryParams, parseRequestBody, serializeResponseBody, } from "./utils.js";
2
+ export const createFetchHandler = (handler) => {
3
+ return async (request) => {
4
+ const url = new URL(request.url);
5
+ const headers = normalizeHeaders(request.headers);
6
+ const contentType = headers["content-type"];
7
+ const serveRequest = {
8
+ method: (request.method ?? "GET").toUpperCase(),
9
+ path: url.pathname,
10
+ query: parseQueryParams(url.searchParams),
11
+ headers,
12
+ body: await parseRequestBody(request, contentType),
13
+ raw: request,
14
+ };
15
+ const response = await handler(serveRequest);
16
+ const responseHeaders = {
17
+ "content-type": "application/json; charset=utf-8",
18
+ ...response.headers,
19
+ };
20
+ const body = serializeResponseBody(response.body);
21
+ return new Response(body, {
22
+ status: response.status,
23
+ headers: responseHeaders,
24
+ });
25
+ };
26
+ };
@@ -0,0 +1,8 @@
1
+ import { type IncomingMessage, type ServerResponse } from "http";
2
+ import type { ServeHandler, StartServerOptions } from "../types.js";
3
+ export declare const createNodeHandler: (handler: ServeHandler) => (req: IncomingMessage, res: ServerResponse) => Promise<void>;
4
+ export declare const startNodeServer: (handler: ServeHandler, options?: StartServerOptions) => Promise<{
5
+ server: import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
6
+ stop: () => Promise<void>;
7
+ }>;
8
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAC;AAG/E,OAAO,KAAK,EAEV,YAAY,EAGZ,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAyErB,eAAO,MAAM,iBAAiB,GAAI,SAAS,YAAY,MACvC,KAAK,eAAe,EAAE,KAAK,cAAc,kBASxD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,SAAS,YAAY,EACrB,UAAS,kBAAuB;;;EA8CjC,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { createServer } from "http";
2
+ import { once } from "node:events";
3
+ import { normalizeHeaders, parseQueryParams, parseRequestBody, serializeResponseBody, } from "./utils.js";
4
+ const readRequestBody = async (req) => {
5
+ const chunks = [];
6
+ for await (const chunk of req) {
7
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
8
+ }
9
+ return Buffer.concat(chunks);
10
+ };
11
+ const buildServeRequest = async (req) => {
12
+ const method = (req.method ?? "GET").toUpperCase();
13
+ const url = new URL(req.url ?? "/", "http://localhost");
14
+ const bodyBuffer = await readRequestBody(req);
15
+ const headers = normalizeHeaders(req.headers);
16
+ const contentType = headers["content-type"] ?? headers["Content-Type"];
17
+ const body = await parseRequestBody(bodyBuffer, contentType);
18
+ return {
19
+ method,
20
+ path: url.pathname,
21
+ query: parseQueryParams(url.searchParams),
22
+ headers,
23
+ body,
24
+ raw: req,
25
+ };
26
+ };
27
+ const sendResponse = (res, response) => {
28
+ res.statusCode = response.status;
29
+ const headers = response.headers ?? {};
30
+ for (const [key, value] of Object.entries(headers)) {
31
+ if (value !== undefined) {
32
+ res.setHeader(key, value);
33
+ }
34
+ }
35
+ if (!res.hasHeader("content-type")) {
36
+ res.setHeader("content-type", "application/json; charset=utf-8");
37
+ }
38
+ const serialized = serializeResponseBody(response.body);
39
+ res.end(serialized);
40
+ };
41
+ const sendError = (res, error) => {
42
+ const payload = error && typeof error === "object" && "status" in error
43
+ ? error
44
+ : {
45
+ status: 500,
46
+ body: {
47
+ error: {
48
+ type: "INTERNAL_SERVER_ERROR",
49
+ message: error instanceof Error ? error.message : "Unexpected error",
50
+ },
51
+ },
52
+ };
53
+ sendResponse(res, payload);
54
+ };
55
+ export const createNodeHandler = (handler) => {
56
+ return async (req, res) => {
57
+ try {
58
+ const request = await buildServeRequest(req);
59
+ const response = await handler(request);
60
+ sendResponse(res, response);
61
+ }
62
+ catch (error) {
63
+ sendError(res, error);
64
+ }
65
+ };
66
+ };
67
+ export const startNodeServer = async (handler, options = {}) => {
68
+ const listener = createNodeHandler(handler);
69
+ const server = createServer(listener);
70
+ const port = options.port ?? 3000;
71
+ const hostname = options.hostname ?? "0.0.0.0";
72
+ const onAbort = () => {
73
+ server.close();
74
+ };
75
+ if (options.signal) {
76
+ if (options.signal.aborted) {
77
+ server.close();
78
+ throw new Error("Start signal already aborted");
79
+ }
80
+ options.signal.addEventListener("abort", onAbort, { once: true });
81
+ }
82
+ server.listen(port, hostname);
83
+ await once(server, "listening");
84
+ if (!options.quiet) {
85
+ const address = server.address();
86
+ const display = typeof address === "object" && address
87
+ ? `${address.address}:${address.port}`
88
+ : `${hostname}:${port}`;
89
+ console.log(`hypequery serve listening on ${display}`);
90
+ }
91
+ const stop = () => new Promise((resolve, reject) => {
92
+ server.close((err) => {
93
+ if (err) {
94
+ reject(err);
95
+ }
96
+ else {
97
+ resolve();
98
+ }
99
+ });
100
+ });
101
+ return {
102
+ server,
103
+ stop,
104
+ };
105
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared utilities for HTTP adapters (Node.js, Fetch API, etc.)
3
+ * These functions eliminate code duplication across different adapter implementations.
4
+ */
5
+ /**
6
+ * Normalizes headers from various sources into a consistent format.
7
+ * Handles both Node.js IncomingMessage headers and Web API Headers.
8
+ *
9
+ * @param headers - Headers from Node.js (Record with string | string[]) or Web Headers
10
+ * @returns Normalized headers as Record<string, string | undefined>
11
+ */
12
+ export declare function normalizeHeaders(headers: Record<string, string | string[] | undefined> | Headers): Record<string, string | undefined>;
13
+ /**
14
+ * Parses URL search parameters into a structured query object.
15
+ * Handles multiple values for the same parameter by creating arrays.
16
+ *
17
+ * @param searchParams - URLSearchParams instance
18
+ * @returns Query parameters with support for arrays
19
+ */
20
+ export declare function parseQueryParams(searchParams: URLSearchParams): Record<string, string | string[] | undefined>;
21
+ /**
22
+ * Parses request body based on content type.
23
+ * Supports Node.js Buffer and Web API Request.
24
+ *
25
+ * @param input - Buffer (Node.js) or Request (Web API)
26
+ * @param contentType - Content-Type header value
27
+ * @returns Parsed body (JSON object, string, ArrayBuffer, or undefined)
28
+ */
29
+ export declare function parseRequestBody(input: Buffer | Request, contentType?: string): Promise<unknown>;
30
+ /**
31
+ * Serializes response body to string based on content type.
32
+ * Handles JSON serialization and string passthrough.
33
+ *
34
+ * @param body - Response body (any type)
35
+ * @param contentType - Content-Type header value
36
+ * @returns Serialized body as string
37
+ */
38
+ export declare function serializeResponseBody(body: unknown): string;
39
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/adapters/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,OAAO,GAC/D,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAoBpC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,eAAe,GAC5B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAc/C;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,GAAG,OAAO,EACvB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,CAuClB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,GACZ,MAAM,CAQR"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Shared utilities for HTTP adapters (Node.js, Fetch API, etc.)
3
+ * These functions eliminate code duplication across different adapter implementations.
4
+ */
5
+ /**
6
+ * Normalizes headers from various sources into a consistent format.
7
+ * Handles both Node.js IncomingMessage headers and Web API Headers.
8
+ *
9
+ * @param headers - Headers from Node.js (Record with string | string[]) or Web Headers
10
+ * @returns Normalized headers as Record<string, string | undefined>
11
+ */
12
+ export function normalizeHeaders(headers) {
13
+ const normalized = {};
14
+ if (headers instanceof Headers || (typeof Headers !== 'undefined' && headers instanceof Headers)) {
15
+ // Web API Headers
16
+ headers.forEach((value, key) => {
17
+ normalized[key] = value;
18
+ });
19
+ }
20
+ else {
21
+ // Node.js headers (Record<string, string | string[] | undefined>)
22
+ for (const [key, value] of Object.entries(headers)) {
23
+ if (Array.isArray(value)) {
24
+ normalized[key] = value.join(", ");
25
+ }
26
+ else if (typeof value === "string") {
27
+ normalized[key] = value;
28
+ }
29
+ }
30
+ }
31
+ return normalized;
32
+ }
33
+ /**
34
+ * Parses URL search parameters into a structured query object.
35
+ * Handles multiple values for the same parameter by creating arrays.
36
+ *
37
+ * @param searchParams - URLSearchParams instance
38
+ * @returns Query parameters with support for arrays
39
+ */
40
+ export function parseQueryParams(searchParams) {
41
+ const params = {};
42
+ for (const [key, value] of searchParams.entries()) {
43
+ if (params[key] === undefined) {
44
+ params[key] = value;
45
+ }
46
+ else if (Array.isArray(params[key])) {
47
+ params[key].push(value);
48
+ }
49
+ else {
50
+ params[key] = [params[key], value];
51
+ }
52
+ }
53
+ return params;
54
+ }
55
+ /**
56
+ * Parses request body based on content type.
57
+ * Supports Node.js Buffer and Web API Request.
58
+ *
59
+ * @param input - Buffer (Node.js) or Request (Web API)
60
+ * @param contentType - Content-Type header value
61
+ * @returns Parsed body (JSON object, string, ArrayBuffer, or undefined)
62
+ */
63
+ export async function parseRequestBody(input, contentType) {
64
+ // Node.js Buffer handling
65
+ if (Buffer.isBuffer(input)) {
66
+ if (!input.length) {
67
+ return undefined;
68
+ }
69
+ if (contentType && contentType.includes("application/json")) {
70
+ try {
71
+ return JSON.parse(input.toString("utf8"));
72
+ }
73
+ catch {
74
+ // If JSON parsing fails, return as string
75
+ return input.toString("utf8");
76
+ }
77
+ }
78
+ // Non-JSON content
79
+ return input.length ? input.toString("utf8") : undefined;
80
+ }
81
+ // Web API Request handling
82
+ if (!contentType) {
83
+ return undefined;
84
+ }
85
+ if (contentType.includes("application/json")) {
86
+ try {
87
+ return await input.json();
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ if (contentType.includes("text/")) {
94
+ return await input.text();
95
+ }
96
+ // Binary data (images, files, etc.)
97
+ return await input.arrayBuffer();
98
+ }
99
+ /**
100
+ * Serializes response body to string based on content type.
101
+ * Handles JSON serialization and string passthrough.
102
+ *
103
+ * @param body - Response body (any type)
104
+ * @param contentType - Content-Type header value
105
+ * @returns Serialized body as string
106
+ */
107
+ export function serializeResponseBody(body) {
108
+ // If already a string, pass through as-is
109
+ if (typeof body === "string") {
110
+ return body;
111
+ }
112
+ // Otherwise, JSON stringify for JSON content-type or default
113
+ return JSON.stringify(body ?? null);
114
+ }
@@ -0,0 +1,7 @@
1
+ import type { ServeHandler } from "../types.js";
2
+ type VercelEdgeHandler = (request: Request) => Promise<Response>;
3
+ type VercelNodeHandler = (req: unknown, res: unknown) => Promise<void> | void;
4
+ export declare const createVercelEdgeHandler: (handler: ServeHandler) => VercelEdgeHandler;
5
+ export declare const createVercelNodeHandler: (handler: ServeHandler) => VercelNodeHandler;
6
+ export {};
7
+ //# sourceMappingURL=vercel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,KAAK,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACjE,KAAK,iBAAiB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE9E,eAAO,MAAM,uBAAuB,GAAI,SAAS,YAAY,KAAG,iBAG/D,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,SAAS,YAAY,KAAG,iBAM/D,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { createFetchHandler } from "./fetch.js";
2
+ import { createNodeHandler } from "./node.js";
3
+ export const createVercelEdgeHandler = (handler) => {
4
+ const fetchHandler = createFetchHandler(handler);
5
+ return async (request) => fetchHandler(request);
6
+ };
7
+ export const createVercelNodeHandler = (handler) => {
8
+ const nodeHandler = createNodeHandler(handler);
9
+ return async (req, res) => {
10
+ // Vercel's Node runtime passes standard Node req/res objects.
11
+ await nodeHandler(req, res);
12
+ };
13
+ };
package/dist/auth.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { AuthContext, AuthStrategy, ServeRequest } from "./types.js";
2
+ export interface ApiKeyStrategyOptions<TAuth extends AuthContext = AuthContext> {
3
+ header?: string;
4
+ queryParam?: string;
5
+ validate: (key: string, request: ServeRequest) => Promise<TAuth | null> | TAuth | null;
6
+ }
7
+ export declare const createApiKeyStrategy: <TAuth extends AuthContext = AuthContext>(options: ApiKeyStrategyOptions<TAuth>) => AuthStrategy<TAuth>;
8
+ export interface BearerTokenStrategyOptions<TAuth extends AuthContext = AuthContext> {
9
+ header?: string;
10
+ prefix?: string;
11
+ validate: (token: string, request: ServeRequest) => Promise<TAuth | null> | TAuth | null;
12
+ }
13
+ export declare const createBearerTokenStrategy: <TAuth extends AuthContext = AuthContext>(options: BearerTokenStrategyOptions<TAuth>) => AuthStrategy<TAuth>;
14
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1E,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC;CACxF;AAED,eAAO,MAAM,oBAAoB,GAAI,KAAK,SAAS,WAAW,GAAG,WAAW,EAC1E,SAAS,qBAAqB,CAAC,KAAK,CAAC,KACpC,YAAY,CAAC,KAAK,CA0BpB,CAAC;AAEF,MAAM,WAAW,0BAA0B,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC;CAC1F;AAED,eAAO,MAAM,yBAAyB,GAAI,KAAK,SAAS,WAAW,GAAG,WAAW,EAC/E,SAAS,0BAA0B,CAAC,KAAK,CAAC,KACzC,YAAY,CAAC,KAAK,CAepB,CAAC"}
package/dist/auth.js ADDED
@@ -0,0 +1,37 @@
1
+ export const createApiKeyStrategy = (options) => {
2
+ const headerName = options.header ?? "authorization";
3
+ const queryParam = options.queryParam;
4
+ return async ({ request }) => {
5
+ let key;
6
+ if (queryParam && typeof request.query[queryParam] === "string") {
7
+ key = request.query[queryParam];
8
+ }
9
+ if (!key) {
10
+ const headerValue = request.headers[headerName] ?? request.headers[headerName.toLowerCase()];
11
+ if (typeof headerValue === "string") {
12
+ key = headerValue.startsWith("Bearer ")
13
+ ? headerValue.slice("Bearer ".length)
14
+ : headerValue;
15
+ }
16
+ }
17
+ if (!key) {
18
+ return null;
19
+ }
20
+ return options.validate(key, request);
21
+ };
22
+ };
23
+ export const createBearerTokenStrategy = (options) => {
24
+ const headerName = options.header ?? "authorization";
25
+ const prefix = options.prefix ?? "Bearer ";
26
+ return async ({ request }) => {
27
+ const raw = request.headers[headerName] ?? request.headers[headerName.toLowerCase()];
28
+ if (typeof raw !== "string" || !raw.startsWith(prefix)) {
29
+ return null;
30
+ }
31
+ const token = raw.slice(prefix.length).trim();
32
+ if (!token) {
33
+ return null;
34
+ }
35
+ return options.validate(token, request);
36
+ };
37
+ };
@@ -0,0 +1,3 @@
1
+ import type { AuthContext, QueryProcedureBuilder } from './types.js';
2
+ export declare const createProcedureBuilder: <TContext extends Record<string, unknown>, TAuth extends AuthContext>() => QueryProcedureBuilder<TContext, TAuth>;
3
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAEX,qBAAqB,EAStB,MAAM,YAAY,CAAC;AAuBpB,eAAO,MAAM,sBAAsB,GACjC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,OACtB,qBAAqB,CAAC,QAAQ,EAAE,KAAK,CA6CzC,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { mergeTags } from './utils.js';
2
+ const defaultState = () => ({
3
+ tags: [],
4
+ middlewares: [],
5
+ });
6
+ export const createProcedureBuilder = () => {
7
+ const build = (state) => ({
8
+ input: (schema) => build({ ...state, inputSchema: schema }),
9
+ output: (schema) => build({ ...state, outputSchema: schema }),
10
+ describe: (description) => build({ ...state, description }),
11
+ name: (name) => build({ ...state, name }),
12
+ summary: (summary) => build({ ...state, summary }),
13
+ tag: (tag) => build({ ...state, tags: mergeTags(state.tags, [tag]) }),
14
+ tags: (tags) => build({ ...state, tags: mergeTags(state.tags, tags) }),
15
+ method: (method) => build({ ...state, method }),
16
+ cache: (ttlMs) => build({ ...state, cacheTtlMs: ttlMs }),
17
+ auth: (strategy) => build({ ...state, auth: strategy }),
18
+ tenant: (config) => build({ ...state, tenant: config }),
19
+ custom: (custom) => build({ ...state, custom: { ...(state.custom ?? {}), ...custom } }),
20
+ use: (...middlewares) => build({ ...state, middlewares: [...state.middlewares, ...middlewares] }),
21
+ query: (executable) => {
22
+ const config = {
23
+ description: state.description,
24
+ name: state.name,
25
+ summary: state.summary,
26
+ tags: state.tags,
27
+ method: state.method,
28
+ inputSchema: state.inputSchema,
29
+ outputSchema: state.outputSchema,
30
+ cacheTtlMs: state.cacheTtlMs,
31
+ auth: typeof state.auth === 'undefined' ? null : state.auth,
32
+ tenant: state.tenant,
33
+ custom: state.custom,
34
+ middlewares: state.middlewares,
35
+ query: executable,
36
+ };
37
+ return config;
38
+ },
39
+ });
40
+ return build(defaultState());
41
+ };
@@ -0,0 +1,44 @@
1
+ import type { ServeBuilder, HttpMethod, AuthContext } from "./types.js";
2
+ /**
3
+ * Configuration for a single query's client-side behavior
4
+ */
5
+ export interface QueryClientConfig {
6
+ method: HttpMethod;
7
+ }
8
+ /**
9
+ * Map of query names to their client configurations
10
+ */
11
+ export type ApiClientConfig = Record<string, QueryClientConfig>;
12
+ /**
13
+ * Extract serializable client configuration from a ServeBuilder.
14
+ * This generates a runtime object that can be used client-side to configure
15
+ * HTTP methods and paths for each query.
16
+ *
17
+ * Prioritizes route-level method configuration over endpoint defaults.
18
+ *
19
+ * @example
20
+ * // Server-side (e.g., in api/config/route.ts)
21
+ * import { api } from '@/analytics/queries';
22
+ * import { extractClientConfig } from '@hypequery/serve';
23
+ *
24
+ * export async function GET() {
25
+ * return Response.json(extractClientConfig(api));
26
+ * }
27
+ *
28
+ * // Client-side
29
+ * const config = await fetch('/api/config').then(r => r.json());
30
+ * createHooks<Api>({ baseUrl: '/api/hypequery', config });
31
+ */
32
+ export declare function extractClientConfig<TQueries extends Record<string, any>, TContext extends Record<string, unknown>, TAuth extends AuthContext>(api: ServeBuilder<TQueries, TContext, TAuth>): ApiClientConfig;
33
+ /**
34
+ * Type-safe helper to manually define client configuration.
35
+ * Use this when you can't access the api object client-side.
36
+ *
37
+ * @example
38
+ * const config = defineClientConfig({
39
+ * hello: { method: 'GET' },
40
+ * createUser: { method: 'POST' },
41
+ * });
42
+ */
43
+ export declare function defineClientConfig<T extends ApiClientConfig>(config: T): T;
44
+ //# sourceMappingURL=client-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-config.d.ts","sourceRoot":"","sources":["../src/client-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACpC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EACzB,GAAG,EAAE,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,eAAe,CAoB/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAE1E"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Extract serializable client configuration from a ServeBuilder.
3
+ * This generates a runtime object that can be used client-side to configure
4
+ * HTTP methods and paths for each query.
5
+ *
6
+ * Prioritizes route-level method configuration over endpoint defaults.
7
+ *
8
+ * @example
9
+ * // Server-side (e.g., in api/config/route.ts)
10
+ * import { api } from '@/analytics/queries';
11
+ * import { extractClientConfig } from '@hypequery/serve';
12
+ *
13
+ * export async function GET() {
14
+ * return Response.json(extractClientConfig(api));
15
+ * }
16
+ *
17
+ * // Client-side
18
+ * const config = await fetch('/api/config').then(r => r.json());
19
+ * createHooks<Api>({ baseUrl: '/api/hypequery', config });
20
+ */
21
+ export function extractClientConfig(api) {
22
+ const config = {};
23
+ // Prefer route-level config if available
24
+ if (api._routeConfig) {
25
+ for (const [key, routeConfig] of Object.entries(api._routeConfig)) {
26
+ config[key] = {
27
+ method: routeConfig.method,
28
+ };
29
+ }
30
+ }
31
+ else {
32
+ // Fallback to endpoint method
33
+ for (const [key, endpoint] of Object.entries(api.queries)) {
34
+ config[key] = {
35
+ method: endpoint.method,
36
+ };
37
+ }
38
+ }
39
+ return config;
40
+ }
41
+ /**
42
+ * Type-safe helper to manually define client configuration.
43
+ * Use this when you can't access the api object client-side.
44
+ *
45
+ * @example
46
+ * const config = defineClientConfig({
47
+ * hello: { method: 'GET' },
48
+ * createUser: { method: 'POST' },
49
+ * });
50
+ */
51
+ export function defineClientConfig(config) {
52
+ return config;
53
+ }
package/dist/dev.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { ServeBuilder, StartServerOptions } from "./types.js";
2
+ export interface ServeDevOptions extends StartServerOptions {
3
+ logger?: (message: string) => void;
4
+ }
5
+ export declare const serveDev: <TQueries extends Record<string, any>, TAuth extends Record<string, unknown>>(api: ServeBuilder<TQueries, TAuth>, options?: ServeDevOptions) => Promise<{
6
+ server: import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
7
+ stop: () => Promise<void>;
8
+ }>;
9
+ //# sourceMappingURL=dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnE,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAMD,eAAO,MAAM,QAAQ,GACnB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACpC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAErC,KAAK,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,EAClC,UAAS,eAAoB;;;EAwB9B,CAAC"}
package/dist/dev.js ADDED
@@ -0,0 +1,24 @@
1
+ import { startNodeServer } from "./adapters/node.js";
2
+ const defaultLogger = (message) => {
3
+ console.log(message);
4
+ };
5
+ export const serveDev = async (api, options = {}) => {
6
+ const port = options.port ?? Number(process.env.PORT ?? 4000);
7
+ const hostname = options.hostname ?? "localhost";
8
+ const logger = options.logger ?? defaultLogger;
9
+ const server = await startNodeServer(api.handler, {
10
+ ...options,
11
+ hostname,
12
+ port,
13
+ quiet: true,
14
+ });
15
+ if (!options.quiet) {
16
+ const address = server.server.address();
17
+ const display = typeof address === "object" && address
18
+ ? `${address.address}:${address.port}`
19
+ : `${hostname}:${port}`;
20
+ logger(`hypequery dev server running at http://${display}`);
21
+ logger(`Docs available at http://${display}/docs`);
22
+ }
23
+ return server;
24
+ };
@@ -0,0 +1,3 @@
1
+ import type { DocsOptions } from "./types.js";
2
+ export declare const buildDocsHtml: (openapiUrl: string, options?: DocsOptions) => string;
3
+ //# sourceMappingURL=docs-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-ui.d.ts","sourceRoot":"","sources":["../src/docs-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAO9C,eAAO,MAAM,aAAa,GAAI,YAAY,MAAM,EAAE,UAAU,WAAW,WAgCtE,CAAC"}
@@ -0,0 +1,34 @@
1
+ const REDOC_CDN = "https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js";
2
+ const sanitize = (value, fallback = "") => (value ?? fallback).replace(/</g, "&lt;").replace(/>/g, "&gt;");
3
+ export const buildDocsHtml = (openapiUrl, options) => {
4
+ const title = sanitize(options?.title, "hypequery");
5
+ const subtitle = sanitize(options?.subtitle);
6
+ const darkClass = options?.darkMode ? "hq-docs--dark" : "";
7
+ return `<!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="utf-8" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <title>${title}</title>
13
+ <style>
14
+ body, html { margin: 0; padding: 0; height: 100%; font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
15
+ .hq-docs { display: flex; flex-direction: column; height: 100%; }
16
+ .hq-docs__header { padding: 1.25rem 1.5rem; border-bottom: 1px solid rgba(0,0,0,0.08); }
17
+ .hq-docs--dark .hq-docs__header { border-color: rgba(255,255,255,0.12); }
18
+ .hq-docs__title { margin: 0; font-size: 1.25rem; }
19
+ .hq-docs__subtitle { margin: 0.25rem 0 0; color: #555; }
20
+ .hq-docs--dark { background: #0f1115; color: #f8f8f2; }
21
+ .hq-docs--dark .hq-docs__subtitle { color: #b4b6c2; }
22
+ redoc { flex: 1; }
23
+ </style>
24
+ </head>
25
+ <body class="hq-docs ${darkClass}">
26
+ <header class="hq-docs__header">
27
+ <h1 class="hq-docs__title">${title}</h1>
28
+ ${subtitle ? `<p class="hq-docs__subtitle">${subtitle}</p>` : ""}
29
+ </header>
30
+ <redoc spec-url="${openapiUrl}"></redoc>
31
+ <script src="${REDOC_CDN}"></script>
32
+ </body>
33
+ </html>`;
34
+ };
@@ -0,0 +1,5 @@
1
+ import type { AuthContext, ServeEndpoint, ServeQueryConfig } from "./types.js";
2
+ type EndpointFromDefinition<TDefinition extends ServeQueryConfig<any, any, TContext, TAuth, any>, TContext extends Record<string, unknown>, TAuth extends AuthContext> = TDefinition extends ServeQueryConfig<infer TInputSchema, infer TOutputSchema, TContext, TAuth, infer TResult> ? ServeEndpoint<TInputSchema, TOutputSchema, TContext, TAuth, TResult> : ServeEndpoint<any, any, TContext, TAuth>;
3
+ export declare const createEndpoint: <TContext extends Record<string, unknown>, TAuth extends AuthContext, TDefinition extends ServeQueryConfig<any, any, TContext, TAuth, any>>(key: string, definition: TDefinition) => EndpointFromDefinition<TDefinition, TContext, TAuth>;
4
+ export {};
5
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAUX,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAIpB,KAAK,sBAAsB,CACzB,WAAW,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,EACpE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,IACvB,WAAW,SAAS,gBAAgB,CACtC,MAAM,YAAY,EAClB,MAAM,aAAa,EACnB,QAAQ,EACR,KAAK,EACL,MAAM,OAAO,CACd,GACG,aAAa,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,GACpE,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AA8B7C,eAAO,MAAM,cAAc,GACzB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,SAAS,WAAW,EACzB,WAAW,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,EAEpE,KAAK,MAAM,EACX,YAAY,WAAW,KACtB,sBAAsB,CAAC,WAAW,EAAE,QAAQ,EAAE,KAAK,CAsDrD,CAAC"}