@md-oss/api-types 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +130 -0
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +196 -0
- package/dist/index.d.mts +196 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright 2026 Mirasaki Development
|
|
2
|
+
|
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
4
|
+
|
|
5
|
+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @md-oss/api-types
|
|
2
|
+
|
|
3
|
+
Type-safe API contracts and helpers for building clients and route handlers around shared `@md-oss/common` errors and Zod-validated inputs/outputs.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Route registries with Zod schemas for params, query, and body validation
|
|
7
|
+
- Typed API client factory (`createApiClient`) that strips unsafe headers and returns either data or `APIError`
|
|
8
|
+
- Generic controller/route handler builders (`createGenericController`, `createGenericRouteHandler`) with pluggable auth/context/permission strategies
|
|
9
|
+
- Response helpers (`sendTypedResponse`) and request parsers (`parseRequestParameters`) that serialize consistently with the common API shape
|
|
10
|
+
- Utilities for prefixing routes, parsing/stripping proxy headers, and fine-grained debug namespaces (`md-oss:api-types:*`)
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @md-oss/api-types
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Define a typed route registry
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
import type { RouteRegistry } from '@md-oss/api-types';
|
|
23
|
+
|
|
24
|
+
const routes = {
|
|
25
|
+
'/users/:id': {
|
|
26
|
+
params: z.object({ id: z.string() }),
|
|
27
|
+
endpoints: {
|
|
28
|
+
GET: {
|
|
29
|
+
response: { id: '123', email: 'user@example.com' },
|
|
30
|
+
permissions: null,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
'/posts': {
|
|
35
|
+
endpoints: {
|
|
36
|
+
POST: {
|
|
37
|
+
body: z.object({ title: z.string(), body: z.string() }),
|
|
38
|
+
response: { id: 'post-id' },
|
|
39
|
+
permissions: { role: 'editor' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
} satisfies RouteRegistry;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Create a typed client
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { createApiClient } from '@md-oss/api-types';
|
|
50
|
+
|
|
51
|
+
const client = createApiClient(routes, {
|
|
52
|
+
baseUrl: 'https://api.example.com',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const user = await client.request('/users/:id', {
|
|
56
|
+
method: 'GET',
|
|
57
|
+
params: { id: '123' },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if ('code' in user) {
|
|
61
|
+
// APIError
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Build controllers with typed context
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import {
|
|
69
|
+
createGenericController,
|
|
70
|
+
sendTypedResponse,
|
|
71
|
+
type ContextProvider,
|
|
72
|
+
} from '@md-oss/api-types';
|
|
73
|
+
|
|
74
|
+
const authStrategy = {
|
|
75
|
+
async resolveAuthentication(req, res, endpoint) {
|
|
76
|
+
// return { session: { userId: 'u1' } } or { session: null }
|
|
77
|
+
return { session: null };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const contextStrategy = {
|
|
82
|
+
async buildContext(session, endpoint, parsed, injected, req, res, requestId) {
|
|
83
|
+
return {
|
|
84
|
+
...parsed,
|
|
85
|
+
session,
|
|
86
|
+
endpoint,
|
|
87
|
+
ctx: { requestId },
|
|
88
|
+
cps: async () => true,
|
|
89
|
+
} satisfies ContextProvider<typeof routes, any, '/users/:id', 'GET', null>;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getUser = createGenericController(
|
|
94
|
+
routes,
|
|
95
|
+
'/users/:id',
|
|
96
|
+
'GET',
|
|
97
|
+
authStrategy,
|
|
98
|
+
contextStrategy
|
|
99
|
+
)((context, respond) => {
|
|
100
|
+
respond({
|
|
101
|
+
path: '/users/:id',
|
|
102
|
+
method: 'GET',
|
|
103
|
+
data: { id: context.params.id, email: 'user@example.com' },
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// use getUser as an Express/Next/fastify style handler
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`createGenericRouteHandler` powers `.withContext(...)` so you can inject pre-built context when wiring routes.
|
|
111
|
+
|
|
112
|
+
## Validate requests and respond consistently
|
|
113
|
+
|
|
114
|
+
- `parseRequestParameters` validates params/query/body against Zod schemas and builds a typed context payload.
|
|
115
|
+
- `sendTypedResponse` returns JSON with `{ ok, code, message, data }` by default, or flattens the payload when `flattenResponse` is set.
|
|
116
|
+
- Signed access errors can be converted to `APIError` via `parseSignedAccessError`.
|
|
117
|
+
|
|
118
|
+
## Debugging
|
|
119
|
+
|
|
120
|
+
Enable scoped debugging with `DEBUG=md-oss:api-types*` to trace parameter parsing, performance timings, and controller responses. Namespaces include `md-oss:api-types:route`, `:performance`, and `:errors`.
|
|
121
|
+
|
|
122
|
+
## Exports
|
|
123
|
+
|
|
124
|
+
Key exports from the package entrypoint:
|
|
125
|
+
|
|
126
|
+
- Client: `createApiClient`, `parseHeaders`, `stripProxyAndWebsocketHeaders`, `ApiClient`
|
|
127
|
+
- Server: `createGenericController`, `createGenericRouteHandler`, `sendTypedResponse`, `parseRequestParameters`
|
|
128
|
+
- Types: `RouteRegistry`, `EndpointDefinition`, `InferApi`, `RouteHandler`, `ControllerFunction`, `RequestOptions`, `ExtractResolvedContext`, `PrefixRoutes`, `RouteKeys`, `MethodKeys`
|
|
129
|
+
|
|
130
|
+
See the source in `src/` for strategy interfaces (auth, context, permission tracking) and additional helpers.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";var T=Object.defineProperty;var y=(r,e)=>T(r,"name",{value:e,configurable:!0});var c=require("@md-oss/common/api/errors"),S=require("@md-oss/common/api/status-codes"),H=require("debug"),q=require("zod/v4");const j=y((r,e)=>r.replace(/:([a-zA-Z0-9_]+)/g,(t,d)=>{const s=e[d];if(s==null)throw new Error(`Missing path param: ${d}`);return encodeURIComponent(String(s))}),"interpolatePath"),x=["proxy-authorization","proxy-authenticate","te","trailer","transfer-encoding","upgrade","via"],V=["connection","host","upgrade"],$=y(r=>{if(r instanceof Headers){const e={};return r.forEach((t,d)=>{e[d]=t}),e}if(typeof r=="object"&&r!==null)return r;throw new TypeError("Headers must be an object or an instance of Headers")},"headersToRecord"),B=y(r=>{const e=$(r);for(const t of[...x,...V])t in e&&delete e[t];return e},"stripProxyAndWebsocketHeaders"),L=y((r,e=!0)=>{let t=$(r);return e&&(t=B(t)),t},"parseHeaders");function D(r,e){const{baseUrl:t,logger:d=console}=e;return{async request(s,g){const{method:l,params:u,body:a,query:i,headers:A={},metadata:m={},logger:O}=g,R=O||d;let I;try{I=j(s,u||{})}catch(o){return c.parseError(o,"BAD_REQUEST","Failed to interpolate path with provided params")}if(i&&typeof i=="object"){const o=new URLSearchParams;for(const[U,_]of Object.entries(i))_!=null&&o.append(U,String(_));const P=o.toString();P.length>0&&(I+=`?${P}`)}let f,C,h,N;try{f=I,C=new URL(f,g?.baseUrl??t).toString();const o=typeof e.defaultHeaders=="function"?await e.defaultHeaders():e.defaultHeaders||{};h={Accept:"application/json",...a?{"Content-Type":"application/json"}:{},...o,...L(A,!0)},N=a?JSON.stringify(a):null}catch(o){return c.parseError(o,"BAD_REQUEST","Failed to construct request URL or headers")}"content-length"in h&&delete h["content-length"],"Content-Length"in h&&delete h["Content-Length"];const n=await fetch(C,{method:l,credentials:"include",headers:h,body:N}).catch(o=>(console.error(`Network error while requesting ${f}:`,o),R.error(`Network error while requesting ${f}: ${o}`,{...m,path:s,method:l,params:JSON.stringify(u,null,2),query:JSON.stringify(i,null,2),body:N,error:String(o),headers:JSON.stringify(h,null,2)}),new c.APIError(S.statusCodes.SERVICE_UNAVAILABLE,{code:"NETWORK_ERROR",message:`Network error while requesting ${f}`,details:String(o)})));if(c.isAPIErrorResponse(n))return console.log(`
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
`,n,`
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
`),new c.APIError(n.statusCode,n.body);if(!n.ok){const o=await n.json().catch(()=>({})),P=n.status===429;return!(n.status===401||n.status===403)&&!P&&R.error(`Request to ${f} failed with status ${n.status}`,{...m,path:s,method:l,params:JSON.stringify(u,null,2),query:JSON.stringify(i,null,2),body:a?JSON.stringify(a,null,2):void 0,status:n.status,error:JSON.stringify(o,null,2)}),c.isAPIErrorResponse(o)?new c.APIError(o.statusCode,o.body):new c.APIError(S.statusCodes.SERVICE_UNAVAILABLE,{code:"REQUEST_FAILED",message:`Request failed with status ${n.status}`,details:`Unexpected response from ${f}: ${JSON.stringify(o,null,2)}`})}if(n.status===204)return null;const p=await n.json().catch(()=>new c.APIError(S.statusCodes.SERVICE_UNAVAILABLE,{code:"INVALID_RESPONSE",message:`Failed to parse response from ${f}`,details:{status:n.status,statusText:n.statusText}}));return p&&typeof p=="object"&&"data"in p?p.data:c.isAPIError(p)?(R.error(`API error from ${f}`,{...m,path:s,method:l,params:u,query:i,body:a,status:n.status,error:p}),p):(R.error(`Unexpected response from ${f}`,{...m,path:s,method:l,params:u,query:i,body:a,status:n.status,response:p}),new c.APIError(S.statusCodes.SERVICE_UNAVAILABLE,{code:"UNEXPECTED_RESPONSE",message:`Unexpected response from ${f}`,details:JSON.stringify(p,null,2)}))}}}y(D,"createApiClient");const w=H("md-oss:api-types"),E=w.extend("route"),v=w.extend("performance"),b=w.extend("errors");async function J(r,e,t,d,s,g){E("[%s] Parsing request parameters",e);const l=Date.now(),[u,a,i]=await Promise.all(["params"in g&&g.params?g.params.safeParseAsync(r.params):Promise.resolve(),"query"in s&&s.query?s.query.safeParseAsync(r.query):Promise.resolve(),"body"in s&&s.body?s.body.safeParseAsync(r.body):Promise.resolve()]),A=Date.now();return v("[%s] Parameter parsing took %dms",e,A-l),u?.error||a?.error||i?.error?(b("[%s] Parameter validation failed",e),b("[%s] Params errors: %o",e,{params:r.params,issues:u?.error?.issues}),b("[%s] Query errors: %o",e,{query:r.query,issues:a?.error?.issues}),b("[%s] Body errors: %o",e,{body:r.body,issues:i?.error?.issues}),{success:!1,error:new c.APIError(S.statusCodes.BAD_REQUEST,{code:i?.error?.issues.length?"INVALID_REQUEST_BODY":a?.error?.issues.length?"INVALID_REQUEST_QUERY":u?.error?.issues.length?"INVALID_REQUEST_PARAMETERS":"INVALID_REQUEST",message:i?.error?.issues.length?`Invalid request body: ${q.prettifyError(i.error)}`:a?.error?.issues.length?`Invalid request query: ${q.prettifyError(a.error)}`:u?.error?.issues.length?`Invalid request parameters: ${q.prettifyError(u.error)}`:`Invalid request to ${String(d)} ${String(t)}`,details:{requestId:e,params:u?.error?.issues,query:a?.error?.issues,body:i?.error?.issues}})}):(E("[%s] Parameters validated successfully",e),{success:!0,data:{params:u?.data??{},query:a?.data??{},body:i?.data??{}}})}y(J,"parseRequestParameters");const Q=[204,205,304],k=y((r,e)=>{const t=e.data===void 0||typeof e.data>"u",{data:d,status:s=t?204:200,headers:g={},message:l=null,flattenResponse:u=!1}=e;E("Sending typed response for %s %s with status %d",e.method,e.path,s),v("Response data size: %d bytes",JSON.stringify(d)?.length||0),r.setHeader("Content-Type","application/json; charset=utf-8"),r.setHeader("Cache-Control","no-cache, no-store, must-revalidate");for(const[A,m]of Object.entries(g))E("Setting custom header: %s = %s",A,m),r.setHeader(A,m);if(t||Q.includes(s)){E("No response data to send, ending response with status %d",s),r.status(s).end();return}const a={ok:!0,code:s,message:l};if(u){E("Sending successful (flattened) response with message: %s",l||"(no message)"),r.status(s).json(d);return}const i={...a,data:d};E("Sending successful response with message: %s",l||"(no message)"),r.status(s).json(i)},"sendTypedResponse");function F(r,e){return Object.fromEntries(Object.entries(e).map(([t,d])=>[t==="/"?r:`${r}${t}`,d]))}y(F,"prefixRoutes"),exports.createApiClient=D,exports.debug=w,exports.debugErrors=b,exports.debugPerformance=v,exports.debugRoute=E,exports.parseHeaders=L,exports.parseRequestParameters=J,exports.prefixRoutes=F,exports.sendTypedResponse=k;
|
|
12
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/client.ts","../src/debugger.ts","../src/params.ts","../src/response.ts","../src/types.ts"],"sourcesContent":["import {\n\tAPIError,\n\tisAPIError,\n\tisAPIErrorResponse,\n\tparseError,\n} from '@md-oss/common/api/errors';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\nimport type { RequestOptions } from './request';\nimport type { InferApi, MethodKeys, RouteKeys, RouteRegistry } from './types';\n\nconst interpolatePath = (\n\tpath: string,\n\tparams: Record<string, string | number>\n) => {\n\treturn path.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {\n\t\tconst value = params[key];\n\t\tif (value == null) {\n\t\t\tthrow new Error(`Missing path param: ${key}`);\n\t\t}\n\t\treturn encodeURIComponent(String(value));\n\t});\n};\n\nconst forbiddenProxyHeaders = [\n\t'proxy-authorization',\n\t'proxy-authenticate',\n\t'te',\n\t'trailer',\n\t'transfer-encoding',\n\t'upgrade',\n\t'via',\n];\n\nconst forbiddenWebsocketHeaders = ['connection', 'host', 'upgrade'];\n\nconst headersToRecord = (\n\theaders: Headers | Record<string, string>\n): Record<string, string> => {\n\tif (headers instanceof Headers) {\n\t\tconst result: Record<string, string> = {};\n\t\theaders.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (typeof headers === 'object' && headers !== null) {\n\t\treturn headers as Record<string, string>;\n\t}\n\n\tthrow new TypeError('Headers must be an object or an instance of Headers');\n};\n\nexport const stripProxyAndWebsocketHeaders = (\n\theaders: Headers | Record<string, string>\n): Record<string, string> => {\n\tconst resolvedHeaders = headersToRecord(headers);\n\n\tfor (const header of [\n\t\t...forbiddenProxyHeaders,\n\t\t...forbiddenWebsocketHeaders,\n\t]) {\n\t\tif (header in resolvedHeaders) {\n\t\t\tdelete resolvedHeaders[header];\n\t\t}\n\t}\n\n\treturn resolvedHeaders;\n};\n\nexport const parseHeaders = (\n\theaders: Headers | Record<string, string>,\n\tshouldStripProxyAndWebsocketHeaders = true\n): Record<string, string> => {\n\tlet resolvedHeaders = headersToRecord(headers);\n\n\tif (shouldStripProxyAndWebsocketHeaders) {\n\t\tresolvedHeaders = stripProxyAndWebsocketHeaders(resolvedHeaders);\n\t}\n\n\treturn resolvedHeaders;\n};\n\ntype Logger = {\n\terror: (message: string, data?: Record<string, unknown>) => void;\n};\n\ntype ClientConfig = {\n\tbaseUrl: string;\n\tlogger?: Logger;\n\tdefaultHeaders?:\n\t\t| Record<string, string>\n\t\t| (() => Record<string, string> | Promise<Record<string, string>>);\n};\n\n/**\n * Creates a type-safe API client for a given route registry.\n * This factory function allows external packages to create their own typed clients.\n *\n * @example\n * ```typescript\n * const myRoutes = {\n * \"/users/:id\": {\n * params: z.object({ id: z.string() }),\n * endpoints: {\n * GET: {\n * response: {} as User,\n * permissions: null,\n * },\n * },\n * },\n * } as const satisfies RouteRegistry;\n *\n * const client = createApiClient(myRoutes, {\n * baseUrl: \"https://api.example.com\",\n * });\n *\n * const user = await client.request(\"/users/:id\", {\n * method: \"GET\",\n * params: { id: \"123\" },\n * });\n * ```\n */\nexport function createApiClient<TRegistry extends RouteRegistry>(\n\t_registry: TRegistry,\n\tconfig: ClientConfig\n) {\n\tconst { baseUrl, logger: _logger = console } = config;\n\n\treturn {\n\t\t/**\n\t\t * Make a type-safe request to the API.\n\t\t */\n\t\tasync request<\n\t\t\tTPath extends RouteKeys<TRegistry>,\n\t\t\tTMethod extends MethodKeys<TRegistry, TPath>,\n\t\t>(\n\t\t\tpath: TPath,\n\t\t\toptions: RequestOptions<\n\t\t\t\tTRegistry,\n\t\t\t\tInferApi<TRegistry>,\n\t\t\t\tTPath,\n\t\t\t\tTMethod\n\t\t\t> & {\n\t\t\t\theaders?:\n\t\t\t\t\t| Record<string, string>\n\t\t\t\t\t| Headers\n\t\t\t\t\t| (Headers & {\n\t\t\t\t\t\t\tappend(...args: unknown[]): void;\n\t\t\t\t\t\t\tset(...args: unknown[]): void;\n\t\t\t\t\t\t\tdelete(...args: unknown[]): void;\n\t\t\t\t\t });\n\t\t\t\tmetadata?: Record<string, unknown>;\n\t\t\t\tbaseUrl?: string;\n\t\t\t\tlogger?: Logger;\n\t\t\t}\n\t\t): Promise<\n\t\t\tInferApi<TRegistry>[TPath]['endpoints'][TMethod]['response'] | APIError\n\t\t> {\n\t\t\ttype LocalResponse =\n\t\t\t\tInferApi<TRegistry>[TPath]['endpoints'][TMethod]['response'];\n\n\t\t\tconst {\n\t\t\t\tmethod,\n\t\t\t\tparams,\n\t\t\t\tbody,\n\t\t\t\tquery,\n\t\t\t\theaders = {},\n\t\t\t\tmetadata = {},\n\t\t\t\tlogger: requestLogger,\n\t\t\t} = options;\n\t\t\tconst logger = requestLogger || _logger;\n\n\t\t\tlet fullPath: string;\n\t\t\ttry {\n\t\t\t\tfullPath = interpolatePath(path, params || {});\n\t\t\t} catch (error) {\n\t\t\t\treturn parseError(\n\t\t\t\t\terror,\n\t\t\t\t\t'BAD_REQUEST',\n\t\t\t\t\t'Failed to interpolate path with provided params'\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (query && typeof query === 'object') {\n\t\t\t\tconst queryParams = new URLSearchParams();\n\t\t\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\t\t\tif (value != null) queryParams.append(key, String(value));\n\t\t\t\t}\n\t\t\t\tconst queryString = queryParams.toString();\n\t\t\t\tif (queryString.length > 0) {\n\t\t\t\t\tfullPath += `?${queryString}`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet endpoint: string,\n\t\t\t\trequestUrl: string,\n\t\t\t\trequestHeaders: Record<string, string>,\n\t\t\t\trequestBody: string | null;\n\n\t\t\ttry {\n\t\t\t\tendpoint = fullPath;\n\t\t\t\trequestUrl = new URL(endpoint, options?.baseUrl ?? baseUrl).toString();\n\n\t\t\t\t// Resolve default headers\n\t\t\t\tconst defaultHeaders =\n\t\t\t\t\ttypeof config.defaultHeaders === 'function'\n\t\t\t\t\t\t? await config.defaultHeaders()\n\t\t\t\t\t\t: config.defaultHeaders || {};\n\n\t\t\t\trequestHeaders = {\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t\t...(body ? { 'Content-Type': 'application/json' } : {}),\n\t\t\t\t\t...defaultHeaders,\n\t\t\t\t\t...parseHeaders(headers, true),\n\t\t\t\t};\n\t\t\t\trequestBody = body ? JSON.stringify(body) : null;\n\t\t\t} catch (error) {\n\t\t\t\treturn parseError(\n\t\t\t\t\terror,\n\t\t\t\t\t'BAD_REQUEST',\n\t\t\t\t\t'Failed to construct request URL or headers'\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Unset content-length so fetch can set it automatically\n\t\t\tif ('content-length' in requestHeaders) {\n\t\t\t\tdelete requestHeaders['content-length'];\n\t\t\t}\n\t\t\tif ('Content-Length' in requestHeaders) {\n\t\t\t\tdelete requestHeaders['Content-Length'];\n\t\t\t}\n\n\t\t\tconst response = await fetch(requestUrl, {\n\t\t\t\tmethod,\n\t\t\t\tcredentials: 'include',\n\t\t\t\theaders: requestHeaders,\n\t\t\t\tbody: requestBody,\n\t\t\t}).catch((error) => {\n\t\t\t\tconsole.error(`Network error while requesting ${endpoint}:`, error);\n\t\t\t\tlogger.error(`Network error while requesting ${endpoint}: ${error}`, {\n\t\t\t\t\t...metadata,\n\t\t\t\t\tpath,\n\t\t\t\t\tmethod,\n\t\t\t\t\tparams: JSON.stringify(params, null, 2),\n\t\t\t\t\tquery: JSON.stringify(query, null, 2),\n\t\t\t\t\tbody: requestBody,\n\t\t\t\t\terror: String(error),\n\t\t\t\t\theaders: JSON.stringify(requestHeaders, null, 2),\n\t\t\t\t});\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'NETWORK_ERROR',\n\t\t\t\t\tmessage: `Network error while requesting ${endpoint}`,\n\t\t\t\t\tdetails: String(error),\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (isAPIErrorResponse(response)) {\n\t\t\t\tconsole.log('\\n\\n\\n\\n\\n', response, '\\n\\n\\n\\n\\n');\n\t\t\t\treturn new APIError(response.statusCode, response.body);\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst error = await response.json().catch(() => ({}));\n\t\t\t\tconst isRateLimitError = response.status === 429;\n\t\t\t\tconst isAuthError = response.status === 401 || response.status === 403;\n\t\t\t\tif (!isAuthError && !isRateLimitError) {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`Request to ${endpoint} failed with status ${response.status}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...metadata,\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tparams: JSON.stringify(params, null, 2),\n\t\t\t\t\t\t\tquery: JSON.stringify(query, null, 2),\n\t\t\t\t\t\t\tbody: body ? JSON.stringify(body, null, 2) : undefined,\n\t\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\t\terror: JSON.stringify(error, null, 2),\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (isAPIErrorResponse(error)) {\n\t\t\t\t\treturn new APIError(error.statusCode, error.body);\n\t\t\t\t}\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'REQUEST_FAILED',\n\t\t\t\t\tmessage: `Request failed with status ${response.status}`,\n\t\t\t\t\tdetails: `Unexpected response from ${endpoint}: ${JSON.stringify(error, null, 2)}`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.status === 204) {\n\t\t\t\treturn null as LocalResponse;\n\t\t\t}\n\n\t\t\tconst json = await response.json().catch(() => {\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'INVALID_RESPONSE',\n\t\t\t\t\tmessage: `Failed to parse response from ${endpoint}`,\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (json && typeof json === 'object' && 'data' in json) {\n\t\t\t\treturn json.data as LocalResponse;\n\t\t\t}\n\n\t\t\tif (isAPIError(json)) {\n\t\t\t\tlogger.error(`API error from ${endpoint}`, {\n\t\t\t\t\t...metadata,\n\t\t\t\t\tpath,\n\t\t\t\t\tmethod,\n\t\t\t\t\tparams,\n\t\t\t\t\tquery,\n\t\t\t\t\tbody,\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\terror: json,\n\t\t\t\t});\n\t\t\t\treturn json;\n\t\t\t}\n\n\t\t\tlogger.error(`Unexpected response from ${endpoint}`, {\n\t\t\t\t...metadata,\n\t\t\t\tpath,\n\t\t\t\tmethod,\n\t\t\t\tparams,\n\t\t\t\tquery,\n\t\t\t\tbody,\n\t\t\t\tstatus: response.status,\n\t\t\t\tresponse: json,\n\t\t\t});\n\n\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\tcode: 'UNEXPECTED_RESPONSE',\n\t\t\t\tmessage: `Unexpected response from ${endpoint}`,\n\t\t\t\tdetails: JSON.stringify(json, null, 2),\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport type ApiClient<TRegistry extends RouteRegistry> = ReturnType<\n\ttypeof createApiClient<TRegistry>\n>;\n","import debugFactory from 'debug';\n\nconst debug: debugFactory.Debugger = debugFactory('md-oss:api-types');\n\nexport const debugRoute: debugFactory.Debugger = debug.extend('route');\nexport const debugPerformance: debugFactory.Debugger =\n\tdebug.extend('performance');\nexport const debugErrors: debugFactory.Debugger = debug.extend('errors');\n\nexport { debug };\n","import { APIError } from '@md-oss/common/api/errors';\nimport type { MinimalRequest } from '@md-oss/common/api/requests';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\nimport { prettifyError } from 'zod/v4';\nimport { debugErrors, debugPerformance, debugRoute } from './debugger';\nimport type { MethodKeys, RouteKeys, RouteRegistry } from './types';\n\nexport interface ParsedParameters {\n\tparams?: unknown;\n\tquery?: unknown;\n\tbody?: unknown;\n}\n\nexport interface ParameterParsingResult {\n\tsuccess: boolean;\n\tdata?: ParsedParameters;\n\terror?: APIError;\n}\n\nexport async function parseRequestParameters<\n\tRegistry extends RouteRegistry,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n>(\n\treq: MinimalRequest,\n\trequestId: string,\n\tpath: TPath,\n\tmethod: TMethod,\n\tendpoint: Registry[TPath]['endpoints'][TMethod],\n\trouteDef: Registry[TPath]\n): Promise<ParameterParsingResult> {\n\tdebugRoute('[%s] Parsing request parameters', requestId);\n\tconst parseStart = Date.now();\n\n\tconst [params, query, body] = await Promise.all([\n\t\t'params' in routeDef && routeDef.params\n\t\t\t? routeDef.params.safeParseAsync(req.params)\n\t\t\t: Promise.resolve(),\n\t\t'query' in endpoint && endpoint.query\n\t\t\t? endpoint.query.safeParseAsync(req.query)\n\t\t\t: Promise.resolve(),\n\t\t'body' in endpoint && endpoint.body\n\t\t\t? endpoint.body.safeParseAsync(req.body)\n\t\t\t: Promise.resolve(),\n\t]);\n\n\tconst parseEnd = Date.now();\n\tdebugPerformance(\n\t\t'[%s] Parameter parsing took %dms',\n\t\trequestId,\n\t\tparseEnd - parseStart\n\t);\n\n\tif (params?.error || query?.error || body?.error) {\n\t\tdebugErrors('[%s] Parameter validation failed', requestId);\n\t\tdebugErrors('[%s] Params errors: %o', requestId, {\n\t\t\tparams: req.params,\n\t\t\tissues: params?.error?.issues,\n\t\t});\n\t\tdebugErrors('[%s] Query errors: %o', requestId, {\n\t\t\tquery: req.query,\n\t\t\tissues: query?.error?.issues,\n\t\t});\n\t\tdebugErrors('[%s] Body errors: %o', requestId, {\n\t\t\tbody: req.body,\n\t\t\tissues: body?.error?.issues,\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: new APIError(statusCodes.BAD_REQUEST, {\n\t\t\t\tcode: body?.error?.issues.length\n\t\t\t\t\t? 'INVALID_REQUEST_BODY'\n\t\t\t\t\t: query?.error?.issues.length\n\t\t\t\t\t\t? 'INVALID_REQUEST_QUERY'\n\t\t\t\t\t\t: params?.error?.issues.length\n\t\t\t\t\t\t\t? 'INVALID_REQUEST_PARAMETERS'\n\t\t\t\t\t\t\t: 'INVALID_REQUEST',\n\t\t\t\tmessage: body?.error?.issues.length\n\t\t\t\t\t? `Invalid request body: ${prettifyError(body.error)}`\n\t\t\t\t\t: query?.error?.issues.length\n\t\t\t\t\t\t? `Invalid request query: ${prettifyError(query.error)}`\n\t\t\t\t\t\t: params?.error?.issues.length\n\t\t\t\t\t\t\t? `Invalid request parameters: ${prettifyError(params.error)}`\n\t\t\t\t\t\t\t: `Invalid request to ${String(method)} ${String(path)}`,\n\t\t\t\tdetails: {\n\t\t\t\t\trequestId,\n\t\t\t\t\tparams: params?.error?.issues,\n\t\t\t\t\tquery: query?.error?.issues,\n\t\t\t\t\tbody: body?.error?.issues,\n\t\t\t\t},\n\t\t\t}),\n\t\t};\n\t}\n\n\tdebugRoute('[%s] Parameters validated successfully', requestId);\n\n\treturn {\n\t\tsuccess: true,\n\t\tdata: {\n\t\t\tparams: params?.data ?? {},\n\t\t\tquery: query?.data ?? {},\n\t\t\tbody: body?.data ?? {},\n\t\t},\n\t};\n}\n","import type { APIError, APISuccessResponse } from '@md-oss/common/api/errors';\nimport type {\n\tMinimalRequest,\n\tMinimalRequestHandler,\n\tMinimalResponse,\n} from '@md-oss/common/api/requests';\nimport { debugPerformance, debugRoute } from './debugger';\nimport type { ExtractResolvedContext } from './request';\nimport type { InferApi, MethodKeys, RouteKeys, RouteRegistry } from './types';\n\ntype SignedAccessError =\n\t| 'MISSING_SIGNATURE'\n\t| 'INVALID_SIGNATURE'\n\t| 'EXPIRED_SIGNATURE';\n\ntype SendTypedResponseOptions<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n> = {\n\tpath: TPath;\n\tmethod: TMethod;\n\tdata: API[TPath]['endpoints'][TMethod]['response'];\n\tstatus?: number;\n\theaders?: Record<string, string>;\n\tmessage?: string;\n\tflattenResponse?: boolean;\n};\n\ntype IsUserMe<T> = T extends { isUserMe: true } ? string : null;\n\ntype ContextProvider<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n> = ExtractResolvedContext<Registry, API, TPath, TMethod> & {\n\tsession: EndpointDefinitionSession<\n\t\tRegistry,\n\t\tAPI,\n\t\tTPath,\n\t\tTMethod,\n\t\tTConsumerSession\n\t>;\n\tendpoint: Registry[TPath]['endpoints'][TMethod];\n\tctx: TConsumerContext;\n\tcps: <P extends API[TPath]['endpoints'][TMethod]['permissions'] | null>(\n\t\tresourceUserId: P extends null\n\t\t\t? IsUserMe<API[TPath]['endpoints'][TMethod]['permissions']>\n\t\t\t: IsUserMe<P>,\n\t\tpolicy?: P,\n\t\tsession?: TConsumerSession | null,\n\t\treq?: MinimalRequest,\n\t\tres?: MinimalResponse\n\t) => Promise<true | APIError>;\n};\n\ntype EndpointDefinitionSession<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n> = API[TPath]['endpoints'][TMethod]['permissions'] extends null\n\t? null | TConsumerSession\n\t: TConsumerSession;\n\ntype RouteHandler<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n\tTRequestHandler = MinimalRequestHandler,\n> = MinimalRequestHandler & {\n\twithContext: (\n\t\tctx: Omit<\n\t\t\tContextProvider<\n\t\t\t\tRegistry,\n\t\t\t\tAPI,\n\t\t\t\tTPath,\n\t\t\t\tTMethod,\n\t\t\t\tTConsumerSession,\n\t\t\t\tTConsumerContext\n\t\t\t>,\n\t\t\t'cps' | 'session' | 'endpoint'\n\t\t>\n\t) => TRequestHandler;\n};\n\ntype GenericRouteHandler<\n\tRegistry extends RouteRegistry = RouteRegistry,\n\tAPI extends InferApi<Registry> = InferApi<Registry>,\n\tTPath extends RouteKeys<Registry> = RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath> = MethodKeys<Registry, TPath>,\n\tTConsumerContext = void,\n> = RouteHandler<\n\tRegistry,\n\tAPI,\n\tTPath,\n\tTMethod,\n\tunknown,\n\tTConsumerContext,\n\tMinimalRequestHandler\n>;\n\ntype ControllerFunction<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n\tTRequestHandler = MinimalRequestHandler,\n> = (\n\tcontext: ContextProvider<\n\t\tRegistry,\n\t\tAPI,\n\t\tTPath,\n\t\tTMethod,\n\t\tTConsumerSession,\n\t\tTConsumerContext\n\t>,\n\trespond: (\n\t\toptions:\n\t\t\t| APIError\n\t\t\t| SignedAccessError\n\t\t\t| Omit<\n\t\t\t\t\tSendTypedResponseOptions<Registry, API, TPath, TMethod>,\n\t\t\t\t\t'path' | 'method'\n\t\t\t >\n\t) => void\n) => TRequestHandler;\n\nconst noContentStatusCodes = [204, 205, 304];\n\nconst sendTypedResponse = <\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n>(\n\tres: MinimalResponse,\n\toptions: SendTypedResponseOptions<Registry, API, TPath, TMethod>\n): void => {\n\tconst dataIsVoid =\n\t\toptions.data === undefined || typeof options.data === 'undefined'; // Null is explicit (non)data\n\tconst {\n\t\tdata,\n\t\tstatus = dataIsVoid ? 204 : 200,\n\t\theaders = {},\n\t\tmessage = null,\n\t\tflattenResponse = false,\n\t} = options;\n\n\tdebugRoute(\n\t\t'Sending typed response for %s %s with status %d',\n\t\toptions.method,\n\t\toptions.path,\n\t\tstatus\n\t);\n\tdebugPerformance(\n\t\t'Response data size: %d bytes',\n\t\tJSON.stringify(data)?.length || 0\n\t);\n\n\tres.setHeader('Content-Type', 'application/json; charset=utf-8');\n\tres.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tdebugRoute('Setting custom header: %s = %s', key, value);\n\t\tres.setHeader(key, value);\n\t}\n\n\tif (dataIsVoid || noContentStatusCodes.includes(status)) {\n\t\tdebugRoute(\n\t\t\t'No response data to send, ending response with status %d',\n\t\t\tstatus\n\t\t);\n\t\tres.status(status).end();\n\t\treturn;\n\t}\n\n\tconst responseBodyNoData = {\n\t\tok: true,\n\t\tcode: status,\n\t\tmessage,\n\t} as const;\n\n\tif (flattenResponse) {\n\t\tdebugRoute(\n\t\t\t'Sending successful (flattened) response with message: %s',\n\t\t\tmessage || '(no message)'\n\t\t);\n\t\tres.status(status).json(data);\n\t\treturn;\n\t}\n\n\tconst responseBody = {\n\t\t...responseBodyNoData,\n\t\tdata,\n\t} satisfies APISuccessResponse<API[TPath]['endpoints'][TMethod]['response']>;\n\n\tdebugRoute(\n\t\t'Sending successful response with message: %s',\n\t\tmessage || '(no message)'\n\t);\n\tres.status(status).json(responseBody);\n};\n\nexport {\n\ttype SignedAccessError,\n\ttype SendTypedResponseOptions,\n\ttype IsUserMe,\n\ttype ContextProvider,\n\ttype EndpointDefinitionSession,\n\ttype RouteHandler,\n\ttype GenericRouteHandler,\n\ttype ControllerFunction,\n\tsendTypedResponse,\n};\n","import type z from 'zod/v4';\n\nexport type EndpointDefinition<\n\tP = unknown,\n\tResp = unknown,\n\tQ extends z.ZodType | undefined = z.ZodType | undefined,\n\tB extends z.ZodType | undefined = z.ZodType | undefined,\n> = {\n\tpermissions: P;\n\tresponse?: Resp;\n\tquery?: Q;\n\tbody?: B;\n};\n\n/**\n * Generic route registry that can be used to create type-safe API clients.\n * External packages can define their own route registries using this type.\n *\n * @example\n * ```typescript\n * const myRoutes = {\n * \"/users/:id\": {\n * params: z.object({ id: z.string() }),\n * endpoints: {\n * GET: {\n * response: {} as User,\n * permissions: null,\n * },\n * },\n * },\n * } as const satisfies RouteRegistry;\n * ```\n */\nexport type RouteRegistry<\n\tPermissions = unknown,\n\tParams extends z.ZodType | undefined = z.ZodType | undefined,\n> = {\n\t[path: string]: {\n\t\tparams?: Params;\n\t\tendpoints: {\n\t\t\t[method: string]: EndpointDefinition<Permissions>;\n\t\t};\n\t};\n};\n\ntype ExtractPermissionType<T extends RouteRegistry> =\n\tT extends RouteRegistry<infer P> ? P : never;\n\n// Helper to extract string keys from RouteRegistry (fixes TypeScript's keyof broadening to string | number | symbol)\nexport type RouteKeys<T extends RouteRegistry> = Extract<keyof T, string>;\nexport type MethodKeys<\n\tT extends RouteRegistry,\n\tR extends RouteKeys<T>,\n> = Extract<keyof T[R]['endpoints'], string>;\n\n/**\n * Infer the API type from a route registry.\n * This is used internally by the client factory.\n */\nexport type InferApi<T extends RouteRegistry> = {\n\t[R in RouteKeys<T>]: {\n\t\tparams: T[R]['params'] extends z.ZodType\n\t\t\t? z.infer<T[R]['params']>\n\t\t\t: undefined;\n\t\tendpoints: {\n\t\t\t[M in MethodKeys<T, R>]: {\n\t\t\t\tpermissions: T[R]['endpoints'][M] extends { permissions: infer P }\n\t\t\t\t\t? P extends ExtractPermissionType<T>\n\t\t\t\t\t\t? P\n\t\t\t\t\t\t: ExtractPermissionType<T>\n\t\t\t\t\t: ExtractPermissionType<T>;\n\t\t\t\tresponse: T[R]['endpoints'][M] extends { response: infer Resp }\n\t\t\t\t\t? Resp\n\t\t\t\t\t: undefined;\n\t\t\t\tquery: T[R]['endpoints'][M] extends { query: infer Q }\n\t\t\t\t\t? Q extends z.ZodType\n\t\t\t\t\t\t? z.output<Q>\n\t\t\t\t\t\t: undefined\n\t\t\t\t\t: undefined;\n\t\t\t\tbody: T[R]['endpoints'][M] extends { body: infer B }\n\t\t\t\t\t? B extends z.ZodType\n\t\t\t\t\t\t? z.output<B>\n\t\t\t\t\t\t: undefined\n\t\t\t\t\t: undefined;\n\t\t\t};\n\t\t};\n\t};\n};\n\nexport type PrefixRoutes<\n\tPrefix extends string,\n\tT extends Record<string, unknown>,\n> = {\n\t[K in keyof T as K extends '/' ? Prefix : `${Prefix}${K & string}`]: T[K];\n};\n\nexport function prefixRoutes<\n\tP extends string,\n\tT extends Record<string, unknown>,\n>(prefix: P, routes: T): PrefixRoutes<P, T> {\n\treturn Object.fromEntries(\n\t\tObject.entries(routes).map(([key, value]) => {\n\t\t\tconst prefixedKey = key === '/' ? prefix : `${prefix}${key}`;\n\t\t\treturn [prefixedKey, value];\n\t\t})\n\t) as PrefixRoutes<P, T>;\n}\n"],"names":["interpolatePath","__name","path","params","_","key","value","forbiddenProxyHeaders","forbiddenWebsocketHeaders","headersToRecord","headers","result","stripProxyAndWebsocketHeaders","resolvedHeaders","header","parseHeaders","shouldStripProxyAndWebsocketHeaders","createApiClient","_registry","config","baseUrl","_logger","options","method","body","query","metadata","requestLogger","logger","fullPath","error","parseError","queryParams","queryString","endpoint","requestUrl","requestHeaders","requestBody","defaultHeaders","response","APIError","statusCodes","isAPIErrorResponse","isRateLimitError","json","isAPIError","debug","debugFactory","debugRoute","debugPerformance","debugErrors","parseRequestParameters","req","requestId","routeDef","parseStart","parseEnd","prettifyError","noContentStatusCodes","sendTypedResponse","res","dataIsVoid","data","status","message","flattenResponse","responseBodyNoData","responseBody","prefixRoutes","prefix","routes"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { APIError } from '@md-oss/common/api/errors';
|
|
2
|
+
import z from 'zod/v4';
|
|
3
|
+
import debugFactory from 'debug';
|
|
4
|
+
import { MinimalRequest, MinimalResponse, MinimalRequestHandler } from '@md-oss/common/api/requests';
|
|
5
|
+
|
|
6
|
+
type EndpointDefinition<P = unknown, Resp = unknown, Q extends z.ZodType | undefined = z.ZodType | undefined, B extends z.ZodType | undefined = z.ZodType | undefined> = {
|
|
7
|
+
permissions: P;
|
|
8
|
+
response?: Resp;
|
|
9
|
+
query?: Q;
|
|
10
|
+
body?: B;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Generic route registry that can be used to create type-safe API clients.
|
|
14
|
+
* External packages can define their own route registries using this type.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const myRoutes = {
|
|
19
|
+
* "/users/:id": {
|
|
20
|
+
* params: z.object({ id: z.string() }),
|
|
21
|
+
* endpoints: {
|
|
22
|
+
* GET: {
|
|
23
|
+
* response: {} as User,
|
|
24
|
+
* permissions: null,
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* },
|
|
28
|
+
* } as const satisfies RouteRegistry;
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
type RouteRegistry<Permissions = unknown, Params extends z.ZodType | undefined = z.ZodType | undefined> = {
|
|
32
|
+
[path: string]: {
|
|
33
|
+
params?: Params;
|
|
34
|
+
endpoints: {
|
|
35
|
+
[method: string]: EndpointDefinition<Permissions>;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
type ExtractPermissionType<T extends RouteRegistry> = T extends RouteRegistry<infer P> ? P : never;
|
|
40
|
+
type RouteKeys<T extends RouteRegistry> = Extract<keyof T, string>;
|
|
41
|
+
type MethodKeys<T extends RouteRegistry, R extends RouteKeys<T>> = Extract<keyof T[R]['endpoints'], string>;
|
|
42
|
+
/**
|
|
43
|
+
* Infer the API type from a route registry.
|
|
44
|
+
* This is used internally by the client factory.
|
|
45
|
+
*/
|
|
46
|
+
type InferApi<T extends RouteRegistry> = {
|
|
47
|
+
[R in RouteKeys<T>]: {
|
|
48
|
+
params: T[R]['params'] extends z.ZodType ? z.infer<T[R]['params']> : undefined;
|
|
49
|
+
endpoints: {
|
|
50
|
+
[M in MethodKeys<T, R>]: {
|
|
51
|
+
permissions: T[R]['endpoints'][M] extends {
|
|
52
|
+
permissions: infer P;
|
|
53
|
+
} ? P extends ExtractPermissionType<T> ? P : ExtractPermissionType<T> : ExtractPermissionType<T>;
|
|
54
|
+
response: T[R]['endpoints'][M] extends {
|
|
55
|
+
response: infer Resp;
|
|
56
|
+
} ? Resp : undefined;
|
|
57
|
+
query: T[R]['endpoints'][M] extends {
|
|
58
|
+
query: infer Q;
|
|
59
|
+
} ? Q extends z.ZodType ? z.output<Q> : undefined : undefined;
|
|
60
|
+
body: T[R]['endpoints'][M] extends {
|
|
61
|
+
body: infer B;
|
|
62
|
+
} ? B extends z.ZodType ? z.output<B> : undefined : undefined;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
type PrefixRoutes<Prefix extends string, T extends Record<string, unknown>> = {
|
|
68
|
+
[K in keyof T as K extends '/' ? Prefix : `${Prefix}${K & string}`]: T[K];
|
|
69
|
+
};
|
|
70
|
+
declare function prefixRoutes<P extends string, T extends Record<string, unknown>>(prefix: P, routes: T): PrefixRoutes<P, T>;
|
|
71
|
+
|
|
72
|
+
type RequestOptions<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = {
|
|
73
|
+
method: TMethod;
|
|
74
|
+
} & (API[TPath]['params'] extends undefined ? {
|
|
75
|
+
params?: never;
|
|
76
|
+
} : {
|
|
77
|
+
params: API[TPath]['params'];
|
|
78
|
+
}) & (API[TPath]['endpoints'][TMethod]['query'] extends undefined ? {
|
|
79
|
+
query?: never;
|
|
80
|
+
} : {
|
|
81
|
+
query: API[TPath]['endpoints'][TMethod]['query'];
|
|
82
|
+
}) & (API[TPath]['endpoints'][TMethod]['body'] extends undefined ? {
|
|
83
|
+
body?: never;
|
|
84
|
+
} : {
|
|
85
|
+
body: API[TPath]['endpoints'][TMethod]['body'];
|
|
86
|
+
});
|
|
87
|
+
type ExtractParams<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>> = API[TPath]['params'] extends undefined ? object : {
|
|
88
|
+
params: API[TPath]['params'];
|
|
89
|
+
};
|
|
90
|
+
type ExtractQuery<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = API[TPath]['endpoints'][TMethod]['query'] extends undefined ? object : {
|
|
91
|
+
query: API[TPath]['endpoints'][TMethod]['query'];
|
|
92
|
+
};
|
|
93
|
+
type ExtractBody<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = API[TPath]['endpoints'][TMethod]['body'] extends undefined ? object : {
|
|
94
|
+
body: API[TPath]['endpoints'][TMethod]['body'];
|
|
95
|
+
};
|
|
96
|
+
type ExtractResolvedContext<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = ExtractParams<Registry, API, TPath> & ExtractQuery<Registry, API, TPath, TMethod> & ExtractBody<Registry, API, TPath, TMethod>;
|
|
97
|
+
|
|
98
|
+
declare const parseHeaders: (headers: Headers | Record<string, string>, shouldStripProxyAndWebsocketHeaders?: boolean) => Record<string, string>;
|
|
99
|
+
type Logger = {
|
|
100
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
101
|
+
};
|
|
102
|
+
type ClientConfig = {
|
|
103
|
+
baseUrl: string;
|
|
104
|
+
logger?: Logger;
|
|
105
|
+
defaultHeaders?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Creates a type-safe API client for a given route registry.
|
|
109
|
+
* This factory function allows external packages to create their own typed clients.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const myRoutes = {
|
|
114
|
+
* "/users/:id": {
|
|
115
|
+
* params: z.object({ id: z.string() }),
|
|
116
|
+
* endpoints: {
|
|
117
|
+
* GET: {
|
|
118
|
+
* response: {} as User,
|
|
119
|
+
* permissions: null,
|
|
120
|
+
* },
|
|
121
|
+
* },
|
|
122
|
+
* },
|
|
123
|
+
* } as const satisfies RouteRegistry;
|
|
124
|
+
*
|
|
125
|
+
* const client = createApiClient(myRoutes, {
|
|
126
|
+
* baseUrl: "https://api.example.com",
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* const user = await client.request("/users/:id", {
|
|
130
|
+
* method: "GET",
|
|
131
|
+
* params: { id: "123" },
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function createApiClient<TRegistry extends RouteRegistry>(_registry: TRegistry, config: ClientConfig): {
|
|
136
|
+
/**
|
|
137
|
+
* Make a type-safe request to the API.
|
|
138
|
+
*/
|
|
139
|
+
request<TPath extends RouteKeys<TRegistry>, TMethod extends MethodKeys<TRegistry, TPath>>(path: TPath, options: RequestOptions<TRegistry, InferApi<TRegistry>, TPath, TMethod> & {
|
|
140
|
+
headers?: Record<string, string> | Headers | (Headers & {
|
|
141
|
+
append(...args: unknown[]): void;
|
|
142
|
+
set(...args: unknown[]): void;
|
|
143
|
+
delete(...args: unknown[]): void;
|
|
144
|
+
});
|
|
145
|
+
metadata?: Record<string, unknown>;
|
|
146
|
+
baseUrl?: string;
|
|
147
|
+
logger?: Logger;
|
|
148
|
+
}): Promise<InferApi<TRegistry>[TPath]["endpoints"][TMethod]["response"] | APIError>;
|
|
149
|
+
};
|
|
150
|
+
type ApiClient<TRegistry extends RouteRegistry> = ReturnType<typeof createApiClient<TRegistry>>;
|
|
151
|
+
|
|
152
|
+
declare const debug: debugFactory.Debugger;
|
|
153
|
+
declare const debugRoute: debugFactory.Debugger;
|
|
154
|
+
declare const debugPerformance: debugFactory.Debugger;
|
|
155
|
+
declare const debugErrors: debugFactory.Debugger;
|
|
156
|
+
|
|
157
|
+
interface ParsedParameters {
|
|
158
|
+
params?: unknown;
|
|
159
|
+
query?: unknown;
|
|
160
|
+
body?: unknown;
|
|
161
|
+
}
|
|
162
|
+
interface ParameterParsingResult {
|
|
163
|
+
success: boolean;
|
|
164
|
+
data?: ParsedParameters;
|
|
165
|
+
error?: APIError;
|
|
166
|
+
}
|
|
167
|
+
declare function parseRequestParameters<Registry extends RouteRegistry, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>>(req: MinimalRequest, requestId: string, path: TPath, method: TMethod, endpoint: Registry[TPath]['endpoints'][TMethod], routeDef: Registry[TPath]): Promise<ParameterParsingResult>;
|
|
168
|
+
|
|
169
|
+
type SignedAccessError = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'EXPIRED_SIGNATURE';
|
|
170
|
+
type SendTypedResponseOptions<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>> = {
|
|
171
|
+
path: TPath;
|
|
172
|
+
method: TMethod;
|
|
173
|
+
data: API[TPath]['endpoints'][TMethod]['response'];
|
|
174
|
+
status?: number;
|
|
175
|
+
headers?: Record<string, string>;
|
|
176
|
+
message?: string;
|
|
177
|
+
flattenResponse?: boolean;
|
|
178
|
+
};
|
|
179
|
+
type IsUserMe<T> = T extends {
|
|
180
|
+
isUserMe: true;
|
|
181
|
+
} ? string : null;
|
|
182
|
+
type ContextProvider<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void> = ExtractResolvedContext<Registry, API, TPath, TMethod> & {
|
|
183
|
+
session: EndpointDefinitionSession<Registry, API, TPath, TMethod, TConsumerSession>;
|
|
184
|
+
endpoint: Registry[TPath]['endpoints'][TMethod];
|
|
185
|
+
ctx: TConsumerContext;
|
|
186
|
+
cps: <P extends API[TPath]['endpoints'][TMethod]['permissions'] | null>(resourceUserId: P extends null ? IsUserMe<API[TPath]['endpoints'][TMethod]['permissions']> : IsUserMe<P>, policy?: P, session?: TConsumerSession | null, req?: MinimalRequest, res?: MinimalResponse) => Promise<true | APIError>;
|
|
187
|
+
};
|
|
188
|
+
type EndpointDefinitionSession<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession> = API[TPath]['endpoints'][TMethod]['permissions'] extends null ? null | TConsumerSession : TConsumerSession;
|
|
189
|
+
type RouteHandler<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void, TRequestHandler = MinimalRequestHandler> = MinimalRequestHandler & {
|
|
190
|
+
withContext: (ctx: Omit<ContextProvider<Registry, API, TPath, TMethod, TConsumerSession, TConsumerContext>, 'cps' | 'session' | 'endpoint'>) => TRequestHandler;
|
|
191
|
+
};
|
|
192
|
+
type ControllerFunction<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void, TRequestHandler = MinimalRequestHandler> = (context: ContextProvider<Registry, API, TPath, TMethod, TConsumerSession, TConsumerContext>, respond: (options: APIError | SignedAccessError | Omit<SendTypedResponseOptions<Registry, API, TPath, TMethod>, 'path' | 'method'>) => void) => TRequestHandler;
|
|
193
|
+
declare const sendTypedResponse: <Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>>(res: MinimalResponse, options: SendTypedResponseOptions<Registry, API, TPath, TMethod>) => void;
|
|
194
|
+
|
|
195
|
+
export { createApiClient, debug, debugErrors, debugPerformance, debugRoute, parseHeaders, parseRequestParameters, prefixRoutes, sendTypedResponse };
|
|
196
|
+
export type { ApiClient, ContextProvider, ControllerFunction, EndpointDefinition, EndpointDefinitionSession, ExtractResolvedContext, InferApi, IsUserMe, MethodKeys, ParameterParsingResult, ParsedParameters, PrefixRoutes, RequestOptions, RouteHandler, RouteKeys, RouteRegistry, SendTypedResponseOptions, SignedAccessError };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { APIError } from '@md-oss/common/api/errors';
|
|
2
|
+
import z from 'zod/v4';
|
|
3
|
+
import debugFactory from 'debug';
|
|
4
|
+
import { MinimalRequest, MinimalResponse, MinimalRequestHandler } from '@md-oss/common/api/requests';
|
|
5
|
+
|
|
6
|
+
type EndpointDefinition<P = unknown, Resp = unknown, Q extends z.ZodType | undefined = z.ZodType | undefined, B extends z.ZodType | undefined = z.ZodType | undefined> = {
|
|
7
|
+
permissions: P;
|
|
8
|
+
response?: Resp;
|
|
9
|
+
query?: Q;
|
|
10
|
+
body?: B;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Generic route registry that can be used to create type-safe API clients.
|
|
14
|
+
* External packages can define their own route registries using this type.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const myRoutes = {
|
|
19
|
+
* "/users/:id": {
|
|
20
|
+
* params: z.object({ id: z.string() }),
|
|
21
|
+
* endpoints: {
|
|
22
|
+
* GET: {
|
|
23
|
+
* response: {} as User,
|
|
24
|
+
* permissions: null,
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* },
|
|
28
|
+
* } as const satisfies RouteRegistry;
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
type RouteRegistry<Permissions = unknown, Params extends z.ZodType | undefined = z.ZodType | undefined> = {
|
|
32
|
+
[path: string]: {
|
|
33
|
+
params?: Params;
|
|
34
|
+
endpoints: {
|
|
35
|
+
[method: string]: EndpointDefinition<Permissions>;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
type ExtractPermissionType<T extends RouteRegistry> = T extends RouteRegistry<infer P> ? P : never;
|
|
40
|
+
type RouteKeys<T extends RouteRegistry> = Extract<keyof T, string>;
|
|
41
|
+
type MethodKeys<T extends RouteRegistry, R extends RouteKeys<T>> = Extract<keyof T[R]['endpoints'], string>;
|
|
42
|
+
/**
|
|
43
|
+
* Infer the API type from a route registry.
|
|
44
|
+
* This is used internally by the client factory.
|
|
45
|
+
*/
|
|
46
|
+
type InferApi<T extends RouteRegistry> = {
|
|
47
|
+
[R in RouteKeys<T>]: {
|
|
48
|
+
params: T[R]['params'] extends z.ZodType ? z.infer<T[R]['params']> : undefined;
|
|
49
|
+
endpoints: {
|
|
50
|
+
[M in MethodKeys<T, R>]: {
|
|
51
|
+
permissions: T[R]['endpoints'][M] extends {
|
|
52
|
+
permissions: infer P;
|
|
53
|
+
} ? P extends ExtractPermissionType<T> ? P : ExtractPermissionType<T> : ExtractPermissionType<T>;
|
|
54
|
+
response: T[R]['endpoints'][M] extends {
|
|
55
|
+
response: infer Resp;
|
|
56
|
+
} ? Resp : undefined;
|
|
57
|
+
query: T[R]['endpoints'][M] extends {
|
|
58
|
+
query: infer Q;
|
|
59
|
+
} ? Q extends z.ZodType ? z.output<Q> : undefined : undefined;
|
|
60
|
+
body: T[R]['endpoints'][M] extends {
|
|
61
|
+
body: infer B;
|
|
62
|
+
} ? B extends z.ZodType ? z.output<B> : undefined : undefined;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
type PrefixRoutes<Prefix extends string, T extends Record<string, unknown>> = {
|
|
68
|
+
[K in keyof T as K extends '/' ? Prefix : `${Prefix}${K & string}`]: T[K];
|
|
69
|
+
};
|
|
70
|
+
declare function prefixRoutes<P extends string, T extends Record<string, unknown>>(prefix: P, routes: T): PrefixRoutes<P, T>;
|
|
71
|
+
|
|
72
|
+
type RequestOptions<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = {
|
|
73
|
+
method: TMethod;
|
|
74
|
+
} & (API[TPath]['params'] extends undefined ? {
|
|
75
|
+
params?: never;
|
|
76
|
+
} : {
|
|
77
|
+
params: API[TPath]['params'];
|
|
78
|
+
}) & (API[TPath]['endpoints'][TMethod]['query'] extends undefined ? {
|
|
79
|
+
query?: never;
|
|
80
|
+
} : {
|
|
81
|
+
query: API[TPath]['endpoints'][TMethod]['query'];
|
|
82
|
+
}) & (API[TPath]['endpoints'][TMethod]['body'] extends undefined ? {
|
|
83
|
+
body?: never;
|
|
84
|
+
} : {
|
|
85
|
+
body: API[TPath]['endpoints'][TMethod]['body'];
|
|
86
|
+
});
|
|
87
|
+
type ExtractParams<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>> = API[TPath]['params'] extends undefined ? object : {
|
|
88
|
+
params: API[TPath]['params'];
|
|
89
|
+
};
|
|
90
|
+
type ExtractQuery<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = API[TPath]['endpoints'][TMethod]['query'] extends undefined ? object : {
|
|
91
|
+
query: API[TPath]['endpoints'][TMethod]['query'];
|
|
92
|
+
};
|
|
93
|
+
type ExtractBody<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = API[TPath]['endpoints'][TMethod]['body'] extends undefined ? object : {
|
|
94
|
+
body: API[TPath]['endpoints'][TMethod]['body'];
|
|
95
|
+
};
|
|
96
|
+
type ExtractResolvedContext<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends Extract<keyof Registry, string>, TMethod extends Extract<keyof Registry[TPath]['endpoints'], string>> = ExtractParams<Registry, API, TPath> & ExtractQuery<Registry, API, TPath, TMethod> & ExtractBody<Registry, API, TPath, TMethod>;
|
|
97
|
+
|
|
98
|
+
declare const parseHeaders: (headers: Headers | Record<string, string>, shouldStripProxyAndWebsocketHeaders?: boolean) => Record<string, string>;
|
|
99
|
+
type Logger = {
|
|
100
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
101
|
+
};
|
|
102
|
+
type ClientConfig = {
|
|
103
|
+
baseUrl: string;
|
|
104
|
+
logger?: Logger;
|
|
105
|
+
defaultHeaders?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Creates a type-safe API client for a given route registry.
|
|
109
|
+
* This factory function allows external packages to create their own typed clients.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const myRoutes = {
|
|
114
|
+
* "/users/:id": {
|
|
115
|
+
* params: z.object({ id: z.string() }),
|
|
116
|
+
* endpoints: {
|
|
117
|
+
* GET: {
|
|
118
|
+
* response: {} as User,
|
|
119
|
+
* permissions: null,
|
|
120
|
+
* },
|
|
121
|
+
* },
|
|
122
|
+
* },
|
|
123
|
+
* } as const satisfies RouteRegistry;
|
|
124
|
+
*
|
|
125
|
+
* const client = createApiClient(myRoutes, {
|
|
126
|
+
* baseUrl: "https://api.example.com",
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* const user = await client.request("/users/:id", {
|
|
130
|
+
* method: "GET",
|
|
131
|
+
* params: { id: "123" },
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function createApiClient<TRegistry extends RouteRegistry>(_registry: TRegistry, config: ClientConfig): {
|
|
136
|
+
/**
|
|
137
|
+
* Make a type-safe request to the API.
|
|
138
|
+
*/
|
|
139
|
+
request<TPath extends RouteKeys<TRegistry>, TMethod extends MethodKeys<TRegistry, TPath>>(path: TPath, options: RequestOptions<TRegistry, InferApi<TRegistry>, TPath, TMethod> & {
|
|
140
|
+
headers?: Record<string, string> | Headers | (Headers & {
|
|
141
|
+
append(...args: unknown[]): void;
|
|
142
|
+
set(...args: unknown[]): void;
|
|
143
|
+
delete(...args: unknown[]): void;
|
|
144
|
+
});
|
|
145
|
+
metadata?: Record<string, unknown>;
|
|
146
|
+
baseUrl?: string;
|
|
147
|
+
logger?: Logger;
|
|
148
|
+
}): Promise<InferApi<TRegistry>[TPath]["endpoints"][TMethod]["response"] | APIError>;
|
|
149
|
+
};
|
|
150
|
+
type ApiClient<TRegistry extends RouteRegistry> = ReturnType<typeof createApiClient<TRegistry>>;
|
|
151
|
+
|
|
152
|
+
declare const debug: debugFactory.Debugger;
|
|
153
|
+
declare const debugRoute: debugFactory.Debugger;
|
|
154
|
+
declare const debugPerformance: debugFactory.Debugger;
|
|
155
|
+
declare const debugErrors: debugFactory.Debugger;
|
|
156
|
+
|
|
157
|
+
interface ParsedParameters {
|
|
158
|
+
params?: unknown;
|
|
159
|
+
query?: unknown;
|
|
160
|
+
body?: unknown;
|
|
161
|
+
}
|
|
162
|
+
interface ParameterParsingResult {
|
|
163
|
+
success: boolean;
|
|
164
|
+
data?: ParsedParameters;
|
|
165
|
+
error?: APIError;
|
|
166
|
+
}
|
|
167
|
+
declare function parseRequestParameters<Registry extends RouteRegistry, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>>(req: MinimalRequest, requestId: string, path: TPath, method: TMethod, endpoint: Registry[TPath]['endpoints'][TMethod], routeDef: Registry[TPath]): Promise<ParameterParsingResult>;
|
|
168
|
+
|
|
169
|
+
type SignedAccessError = 'MISSING_SIGNATURE' | 'INVALID_SIGNATURE' | 'EXPIRED_SIGNATURE';
|
|
170
|
+
type SendTypedResponseOptions<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>> = {
|
|
171
|
+
path: TPath;
|
|
172
|
+
method: TMethod;
|
|
173
|
+
data: API[TPath]['endpoints'][TMethod]['response'];
|
|
174
|
+
status?: number;
|
|
175
|
+
headers?: Record<string, string>;
|
|
176
|
+
message?: string;
|
|
177
|
+
flattenResponse?: boolean;
|
|
178
|
+
};
|
|
179
|
+
type IsUserMe<T> = T extends {
|
|
180
|
+
isUserMe: true;
|
|
181
|
+
} ? string : null;
|
|
182
|
+
type ContextProvider<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void> = ExtractResolvedContext<Registry, API, TPath, TMethod> & {
|
|
183
|
+
session: EndpointDefinitionSession<Registry, API, TPath, TMethod, TConsumerSession>;
|
|
184
|
+
endpoint: Registry[TPath]['endpoints'][TMethod];
|
|
185
|
+
ctx: TConsumerContext;
|
|
186
|
+
cps: <P extends API[TPath]['endpoints'][TMethod]['permissions'] | null>(resourceUserId: P extends null ? IsUserMe<API[TPath]['endpoints'][TMethod]['permissions']> : IsUserMe<P>, policy?: P, session?: TConsumerSession | null, req?: MinimalRequest, res?: MinimalResponse) => Promise<true | APIError>;
|
|
187
|
+
};
|
|
188
|
+
type EndpointDefinitionSession<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession> = API[TPath]['endpoints'][TMethod]['permissions'] extends null ? null | TConsumerSession : TConsumerSession;
|
|
189
|
+
type RouteHandler<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void, TRequestHandler = MinimalRequestHandler> = MinimalRequestHandler & {
|
|
190
|
+
withContext: (ctx: Omit<ContextProvider<Registry, API, TPath, TMethod, TConsumerSession, TConsumerContext>, 'cps' | 'session' | 'endpoint'>) => TRequestHandler;
|
|
191
|
+
};
|
|
192
|
+
type ControllerFunction<Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>, TConsumerSession, TConsumerContext = void, TRequestHandler = MinimalRequestHandler> = (context: ContextProvider<Registry, API, TPath, TMethod, TConsumerSession, TConsumerContext>, respond: (options: APIError | SignedAccessError | Omit<SendTypedResponseOptions<Registry, API, TPath, TMethod>, 'path' | 'method'>) => void) => TRequestHandler;
|
|
193
|
+
declare const sendTypedResponse: <Registry extends RouteRegistry, API extends InferApi<Registry>, TPath extends RouteKeys<Registry>, TMethod extends MethodKeys<Registry, TPath>>(res: MinimalResponse, options: SendTypedResponseOptions<Registry, API, TPath, TMethod>) => void;
|
|
194
|
+
|
|
195
|
+
export { createApiClient, debug, debugErrors, debugPerformance, debugRoute, parseHeaders, parseRequestParameters, prefixRoutes, sendTypedResponse };
|
|
196
|
+
export type { ApiClient, ContextProvider, ControllerFunction, EndpointDefinition, EndpointDefinitionSession, ExtractResolvedContext, InferApi, IsUserMe, MethodKeys, ParameterParsingResult, ParsedParameters, PrefixRoutes, RequestOptions, RouteHandler, RouteKeys, RouteRegistry, SendTypedResponseOptions, SignedAccessError };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
var j=Object.defineProperty;var y=(r,e)=>j(r,"name",{value:e,configurable:!0});import{parseError as O,APIError as h,isAPIErrorResponse as v,isAPIError as x}from"@md-oss/common/api/errors";import{statusCodes as b}from"@md-oss/common/api/status-codes";import V from"debug";import{prettifyError as I}from"zod/v4";const B=y((r,e)=>r.replace(/:([a-zA-Z0-9_]+)/g,(t,d)=>{const s=e[d];if(s==null)throw new Error(`Missing path param: ${d}`);return encodeURIComponent(String(s))}),"interpolatePath"),D=["proxy-authorization","proxy-authenticate","te","trailer","transfer-encoding","upgrade","via"],J=["connection","host","upgrade"],C=y(r=>{if(r instanceof Headers){const e={};return r.forEach((t,d)=>{e[d]=t}),e}if(typeof r=="object"&&r!==null)return r;throw new TypeError("Headers must be an object or an instance of Headers")},"headersToRecord"),Q=y(r=>{const e=C(r);for(const t of[...D,...J])t in e&&delete e[t];return e},"stripProxyAndWebsocketHeaders"),T=y((r,e=!0)=>{let t=C(r);return e&&(t=Q(t)),t},"parseHeaders");function k(r,e){const{baseUrl:t,logger:d=console}=e;return{async request(s,p){const{method:c,params:u,body:a,query:i,headers:S={},metadata:m={},logger:H}=p,R=H||d;let P;try{P=B(s,u||{})}catch(o){return O(o,"BAD_REQUEST","Failed to interpolate path with provided params")}if(i&&typeof i=="object"){const o=new URLSearchParams;for(const[L,q]of Object.entries(i))q!=null&&o.append(L,String(q));const w=o.toString();w.length>0&&(P+=`?${w}`)}let l,$,E,U;try{l=P,$=new URL(l,p?.baseUrl??t).toString();const o=typeof e.defaultHeaders=="function"?await e.defaultHeaders():e.defaultHeaders||{};E={Accept:"application/json",...a?{"Content-Type":"application/json"}:{},...o,...T(S,!0)},U=a?JSON.stringify(a):null}catch(o){return O(o,"BAD_REQUEST","Failed to construct request URL or headers")}"content-length"in E&&delete E["content-length"],"Content-Length"in E&&delete E["Content-Length"];const n=await fetch($,{method:c,credentials:"include",headers:E,body:U}).catch(o=>(console.error(`Network error while requesting ${l}:`,o),R.error(`Network error while requesting ${l}: ${o}`,{...m,path:s,method:c,params:JSON.stringify(u,null,2),query:JSON.stringify(i,null,2),body:U,error:String(o),headers:JSON.stringify(E,null,2)}),new h(b.SERVICE_UNAVAILABLE,{code:"NETWORK_ERROR",message:`Network error while requesting ${l}`,details:String(o)})));if(v(n))return console.log(`
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
`,n,`
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
`),new h(n.statusCode,n.body);if(!n.ok){const o=await n.json().catch(()=>({})),w=n.status===429;return!(n.status===401||n.status===403)&&!w&&R.error(`Request to ${l} failed with status ${n.status}`,{...m,path:s,method:c,params:JSON.stringify(u,null,2),query:JSON.stringify(i,null,2),body:a?JSON.stringify(a,null,2):void 0,status:n.status,error:JSON.stringify(o,null,2)}),v(o)?new h(o.statusCode,o.body):new h(b.SERVICE_UNAVAILABLE,{code:"REQUEST_FAILED",message:`Request failed with status ${n.status}`,details:`Unexpected response from ${l}: ${JSON.stringify(o,null,2)}`})}if(n.status===204)return null;const f=await n.json().catch(()=>new h(b.SERVICE_UNAVAILABLE,{code:"INVALID_RESPONSE",message:`Failed to parse response from ${l}`,details:{status:n.status,statusText:n.statusText}}));return f&&typeof f=="object"&&"data"in f?f.data:x(f)?(R.error(`API error from ${l}`,{...m,path:s,method:c,params:u,query:i,body:a,status:n.status,error:f}),f):(R.error(`Unexpected response from ${l}`,{...m,path:s,method:c,params:u,query:i,body:a,status:n.status,response:f}),new h(b.SERVICE_UNAVAILABLE,{code:"UNEXPECTED_RESPONSE",message:`Unexpected response from ${l}`,details:JSON.stringify(f,null,2)}))}}}y(k,"createApiClient");const N=V("md-oss:api-types"),g=N.extend("route"),_=N.extend("performance"),A=N.extend("errors");async function F(r,e,t,d,s,p){g("[%s] Parsing request parameters",e);const c=Date.now(),[u,a,i]=await Promise.all(["params"in p&&p.params?p.params.safeParseAsync(r.params):Promise.resolve(),"query"in s&&s.query?s.query.safeParseAsync(r.query):Promise.resolve(),"body"in s&&s.body?s.body.safeParseAsync(r.body):Promise.resolve()]),S=Date.now();return _("[%s] Parameter parsing took %dms",e,S-c),u?.error||a?.error||i?.error?(A("[%s] Parameter validation failed",e),A("[%s] Params errors: %o",e,{params:r.params,issues:u?.error?.issues}),A("[%s] Query errors: %o",e,{query:r.query,issues:a?.error?.issues}),A("[%s] Body errors: %o",e,{body:r.body,issues:i?.error?.issues}),{success:!1,error:new h(b.BAD_REQUEST,{code:i?.error?.issues.length?"INVALID_REQUEST_BODY":a?.error?.issues.length?"INVALID_REQUEST_QUERY":u?.error?.issues.length?"INVALID_REQUEST_PARAMETERS":"INVALID_REQUEST",message:i?.error?.issues.length?`Invalid request body: ${I(i.error)}`:a?.error?.issues.length?`Invalid request query: ${I(a.error)}`:u?.error?.issues.length?`Invalid request parameters: ${I(u.error)}`:`Invalid request to ${String(d)} ${String(t)}`,details:{requestId:e,params:u?.error?.issues,query:a?.error?.issues,body:i?.error?.issues}})}):(g("[%s] Parameters validated successfully",e),{success:!0,data:{params:u?.data??{},query:a?.data??{},body:i?.data??{}}})}y(F,"parseRequestParameters");const z=[204,205,304],K=y((r,e)=>{const t=e.data===void 0||typeof e.data>"u",{data:d,status:s=t?204:200,headers:p={},message:c=null,flattenResponse:u=!1}=e;g("Sending typed response for %s %s with status %d",e.method,e.path,s),_("Response data size: %d bytes",JSON.stringify(d)?.length||0),r.setHeader("Content-Type","application/json; charset=utf-8"),r.setHeader("Cache-Control","no-cache, no-store, must-revalidate");for(const[S,m]of Object.entries(p))g("Setting custom header: %s = %s",S,m),r.setHeader(S,m);if(t||z.includes(s)){g("No response data to send, ending response with status %d",s),r.status(s).end();return}const a={ok:!0,code:s,message:c};if(u){g("Sending successful (flattened) response with message: %s",c||"(no message)"),r.status(s).json(d);return}const i={...a,data:d};g("Sending successful response with message: %s",c||"(no message)"),r.status(s).json(i)},"sendTypedResponse");function W(r,e){return Object.fromEntries(Object.entries(e).map(([t,d])=>[t==="/"?r:`${r}${t}`,d]))}y(W,"prefixRoutes");export{k as createApiClient,N as debug,A as debugErrors,_ as debugPerformance,g as debugRoute,T as parseHeaders,F as parseRequestParameters,W as prefixRoutes,K as sendTypedResponse};
|
|
12
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/client.ts","../src/debugger.ts","../src/params.ts","../src/response.ts","../src/types.ts"],"sourcesContent":["import {\n\tAPIError,\n\tisAPIError,\n\tisAPIErrorResponse,\n\tparseError,\n} from '@md-oss/common/api/errors';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\nimport type { RequestOptions } from './request';\nimport type { InferApi, MethodKeys, RouteKeys, RouteRegistry } from './types';\n\nconst interpolatePath = (\n\tpath: string,\n\tparams: Record<string, string | number>\n) => {\n\treturn path.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {\n\t\tconst value = params[key];\n\t\tif (value == null) {\n\t\t\tthrow new Error(`Missing path param: ${key}`);\n\t\t}\n\t\treturn encodeURIComponent(String(value));\n\t});\n};\n\nconst forbiddenProxyHeaders = [\n\t'proxy-authorization',\n\t'proxy-authenticate',\n\t'te',\n\t'trailer',\n\t'transfer-encoding',\n\t'upgrade',\n\t'via',\n];\n\nconst forbiddenWebsocketHeaders = ['connection', 'host', 'upgrade'];\n\nconst headersToRecord = (\n\theaders: Headers | Record<string, string>\n): Record<string, string> => {\n\tif (headers instanceof Headers) {\n\t\tconst result: Record<string, string> = {};\n\t\theaders.forEach((value, key) => {\n\t\t\tresult[key] = value;\n\t\t});\n\t\treturn result;\n\t}\n\n\tif (typeof headers === 'object' && headers !== null) {\n\t\treturn headers as Record<string, string>;\n\t}\n\n\tthrow new TypeError('Headers must be an object or an instance of Headers');\n};\n\nexport const stripProxyAndWebsocketHeaders = (\n\theaders: Headers | Record<string, string>\n): Record<string, string> => {\n\tconst resolvedHeaders = headersToRecord(headers);\n\n\tfor (const header of [\n\t\t...forbiddenProxyHeaders,\n\t\t...forbiddenWebsocketHeaders,\n\t]) {\n\t\tif (header in resolvedHeaders) {\n\t\t\tdelete resolvedHeaders[header];\n\t\t}\n\t}\n\n\treturn resolvedHeaders;\n};\n\nexport const parseHeaders = (\n\theaders: Headers | Record<string, string>,\n\tshouldStripProxyAndWebsocketHeaders = true\n): Record<string, string> => {\n\tlet resolvedHeaders = headersToRecord(headers);\n\n\tif (shouldStripProxyAndWebsocketHeaders) {\n\t\tresolvedHeaders = stripProxyAndWebsocketHeaders(resolvedHeaders);\n\t}\n\n\treturn resolvedHeaders;\n};\n\ntype Logger = {\n\terror: (message: string, data?: Record<string, unknown>) => void;\n};\n\ntype ClientConfig = {\n\tbaseUrl: string;\n\tlogger?: Logger;\n\tdefaultHeaders?:\n\t\t| Record<string, string>\n\t\t| (() => Record<string, string> | Promise<Record<string, string>>);\n};\n\n/**\n * Creates a type-safe API client for a given route registry.\n * This factory function allows external packages to create their own typed clients.\n *\n * @example\n * ```typescript\n * const myRoutes = {\n * \"/users/:id\": {\n * params: z.object({ id: z.string() }),\n * endpoints: {\n * GET: {\n * response: {} as User,\n * permissions: null,\n * },\n * },\n * },\n * } as const satisfies RouteRegistry;\n *\n * const client = createApiClient(myRoutes, {\n * baseUrl: \"https://api.example.com\",\n * });\n *\n * const user = await client.request(\"/users/:id\", {\n * method: \"GET\",\n * params: { id: \"123\" },\n * });\n * ```\n */\nexport function createApiClient<TRegistry extends RouteRegistry>(\n\t_registry: TRegistry,\n\tconfig: ClientConfig\n) {\n\tconst { baseUrl, logger: _logger = console } = config;\n\n\treturn {\n\t\t/**\n\t\t * Make a type-safe request to the API.\n\t\t */\n\t\tasync request<\n\t\t\tTPath extends RouteKeys<TRegistry>,\n\t\t\tTMethod extends MethodKeys<TRegistry, TPath>,\n\t\t>(\n\t\t\tpath: TPath,\n\t\t\toptions: RequestOptions<\n\t\t\t\tTRegistry,\n\t\t\t\tInferApi<TRegistry>,\n\t\t\t\tTPath,\n\t\t\t\tTMethod\n\t\t\t> & {\n\t\t\t\theaders?:\n\t\t\t\t\t| Record<string, string>\n\t\t\t\t\t| Headers\n\t\t\t\t\t| (Headers & {\n\t\t\t\t\t\t\tappend(...args: unknown[]): void;\n\t\t\t\t\t\t\tset(...args: unknown[]): void;\n\t\t\t\t\t\t\tdelete(...args: unknown[]): void;\n\t\t\t\t\t });\n\t\t\t\tmetadata?: Record<string, unknown>;\n\t\t\t\tbaseUrl?: string;\n\t\t\t\tlogger?: Logger;\n\t\t\t}\n\t\t): Promise<\n\t\t\tInferApi<TRegistry>[TPath]['endpoints'][TMethod]['response'] | APIError\n\t\t> {\n\t\t\ttype LocalResponse =\n\t\t\t\tInferApi<TRegistry>[TPath]['endpoints'][TMethod]['response'];\n\n\t\t\tconst {\n\t\t\t\tmethod,\n\t\t\t\tparams,\n\t\t\t\tbody,\n\t\t\t\tquery,\n\t\t\t\theaders = {},\n\t\t\t\tmetadata = {},\n\t\t\t\tlogger: requestLogger,\n\t\t\t} = options;\n\t\t\tconst logger = requestLogger || _logger;\n\n\t\t\tlet fullPath: string;\n\t\t\ttry {\n\t\t\t\tfullPath = interpolatePath(path, params || {});\n\t\t\t} catch (error) {\n\t\t\t\treturn parseError(\n\t\t\t\t\terror,\n\t\t\t\t\t'BAD_REQUEST',\n\t\t\t\t\t'Failed to interpolate path with provided params'\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (query && typeof query === 'object') {\n\t\t\t\tconst queryParams = new URLSearchParams();\n\t\t\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\t\t\tif (value != null) queryParams.append(key, String(value));\n\t\t\t\t}\n\t\t\t\tconst queryString = queryParams.toString();\n\t\t\t\tif (queryString.length > 0) {\n\t\t\t\t\tfullPath += `?${queryString}`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet endpoint: string,\n\t\t\t\trequestUrl: string,\n\t\t\t\trequestHeaders: Record<string, string>,\n\t\t\t\trequestBody: string | null;\n\n\t\t\ttry {\n\t\t\t\tendpoint = fullPath;\n\t\t\t\trequestUrl = new URL(endpoint, options?.baseUrl ?? baseUrl).toString();\n\n\t\t\t\t// Resolve default headers\n\t\t\t\tconst defaultHeaders =\n\t\t\t\t\ttypeof config.defaultHeaders === 'function'\n\t\t\t\t\t\t? await config.defaultHeaders()\n\t\t\t\t\t\t: config.defaultHeaders || {};\n\n\t\t\t\trequestHeaders = {\n\t\t\t\t\tAccept: 'application/json',\n\t\t\t\t\t...(body ? { 'Content-Type': 'application/json' } : {}),\n\t\t\t\t\t...defaultHeaders,\n\t\t\t\t\t...parseHeaders(headers, true),\n\t\t\t\t};\n\t\t\t\trequestBody = body ? JSON.stringify(body) : null;\n\t\t\t} catch (error) {\n\t\t\t\treturn parseError(\n\t\t\t\t\terror,\n\t\t\t\t\t'BAD_REQUEST',\n\t\t\t\t\t'Failed to construct request URL or headers'\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Unset content-length so fetch can set it automatically\n\t\t\tif ('content-length' in requestHeaders) {\n\t\t\t\tdelete requestHeaders['content-length'];\n\t\t\t}\n\t\t\tif ('Content-Length' in requestHeaders) {\n\t\t\t\tdelete requestHeaders['Content-Length'];\n\t\t\t}\n\n\t\t\tconst response = await fetch(requestUrl, {\n\t\t\t\tmethod,\n\t\t\t\tcredentials: 'include',\n\t\t\t\theaders: requestHeaders,\n\t\t\t\tbody: requestBody,\n\t\t\t}).catch((error) => {\n\t\t\t\tconsole.error(`Network error while requesting ${endpoint}:`, error);\n\t\t\t\tlogger.error(`Network error while requesting ${endpoint}: ${error}`, {\n\t\t\t\t\t...metadata,\n\t\t\t\t\tpath,\n\t\t\t\t\tmethod,\n\t\t\t\t\tparams: JSON.stringify(params, null, 2),\n\t\t\t\t\tquery: JSON.stringify(query, null, 2),\n\t\t\t\t\tbody: requestBody,\n\t\t\t\t\terror: String(error),\n\t\t\t\t\theaders: JSON.stringify(requestHeaders, null, 2),\n\t\t\t\t});\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'NETWORK_ERROR',\n\t\t\t\t\tmessage: `Network error while requesting ${endpoint}`,\n\t\t\t\t\tdetails: String(error),\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (isAPIErrorResponse(response)) {\n\t\t\t\tconsole.log('\\n\\n\\n\\n\\n', response, '\\n\\n\\n\\n\\n');\n\t\t\t\treturn new APIError(response.statusCode, response.body);\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst error = await response.json().catch(() => ({}));\n\t\t\t\tconst isRateLimitError = response.status === 429;\n\t\t\t\tconst isAuthError = response.status === 401 || response.status === 403;\n\t\t\t\tif (!isAuthError && !isRateLimitError) {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`Request to ${endpoint} failed with status ${response.status}`,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...metadata,\n\t\t\t\t\t\t\tpath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tparams: JSON.stringify(params, null, 2),\n\t\t\t\t\t\t\tquery: JSON.stringify(query, null, 2),\n\t\t\t\t\t\t\tbody: body ? JSON.stringify(body, null, 2) : undefined,\n\t\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\t\terror: JSON.stringify(error, null, 2),\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (isAPIErrorResponse(error)) {\n\t\t\t\t\treturn new APIError(error.statusCode, error.body);\n\t\t\t\t}\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'REQUEST_FAILED',\n\t\t\t\t\tmessage: `Request failed with status ${response.status}`,\n\t\t\t\t\tdetails: `Unexpected response from ${endpoint}: ${JSON.stringify(error, null, 2)}`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.status === 204) {\n\t\t\t\treturn null as LocalResponse;\n\t\t\t}\n\n\t\t\tconst json = await response.json().catch(() => {\n\t\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\t\tcode: 'INVALID_RESPONSE',\n\t\t\t\t\tmessage: `Failed to parse response from ${endpoint}`,\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tif (json && typeof json === 'object' && 'data' in json) {\n\t\t\t\treturn json.data as LocalResponse;\n\t\t\t}\n\n\t\t\tif (isAPIError(json)) {\n\t\t\t\tlogger.error(`API error from ${endpoint}`, {\n\t\t\t\t\t...metadata,\n\t\t\t\t\tpath,\n\t\t\t\t\tmethod,\n\t\t\t\t\tparams,\n\t\t\t\t\tquery,\n\t\t\t\t\tbody,\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\terror: json,\n\t\t\t\t});\n\t\t\t\treturn json;\n\t\t\t}\n\n\t\t\tlogger.error(`Unexpected response from ${endpoint}`, {\n\t\t\t\t...metadata,\n\t\t\t\tpath,\n\t\t\t\tmethod,\n\t\t\t\tparams,\n\t\t\t\tquery,\n\t\t\t\tbody,\n\t\t\t\tstatus: response.status,\n\t\t\t\tresponse: json,\n\t\t\t});\n\n\t\t\treturn new APIError(statusCodes.SERVICE_UNAVAILABLE, {\n\t\t\t\tcode: 'UNEXPECTED_RESPONSE',\n\t\t\t\tmessage: `Unexpected response from ${endpoint}`,\n\t\t\t\tdetails: JSON.stringify(json, null, 2),\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport type ApiClient<TRegistry extends RouteRegistry> = ReturnType<\n\ttypeof createApiClient<TRegistry>\n>;\n","import debugFactory from 'debug';\n\nconst debug: debugFactory.Debugger = debugFactory('md-oss:api-types');\n\nexport const debugRoute: debugFactory.Debugger = debug.extend('route');\nexport const debugPerformance: debugFactory.Debugger =\n\tdebug.extend('performance');\nexport const debugErrors: debugFactory.Debugger = debug.extend('errors');\n\nexport { debug };\n","import { APIError } from '@md-oss/common/api/errors';\nimport type { MinimalRequest } from '@md-oss/common/api/requests';\nimport { statusCodes } from '@md-oss/common/api/status-codes';\nimport { prettifyError } from 'zod/v4';\nimport { debugErrors, debugPerformance, debugRoute } from './debugger';\nimport type { MethodKeys, RouteKeys, RouteRegistry } from './types';\n\nexport interface ParsedParameters {\n\tparams?: unknown;\n\tquery?: unknown;\n\tbody?: unknown;\n}\n\nexport interface ParameterParsingResult {\n\tsuccess: boolean;\n\tdata?: ParsedParameters;\n\terror?: APIError;\n}\n\nexport async function parseRequestParameters<\n\tRegistry extends RouteRegistry,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n>(\n\treq: MinimalRequest,\n\trequestId: string,\n\tpath: TPath,\n\tmethod: TMethod,\n\tendpoint: Registry[TPath]['endpoints'][TMethod],\n\trouteDef: Registry[TPath]\n): Promise<ParameterParsingResult> {\n\tdebugRoute('[%s] Parsing request parameters', requestId);\n\tconst parseStart = Date.now();\n\n\tconst [params, query, body] = await Promise.all([\n\t\t'params' in routeDef && routeDef.params\n\t\t\t? routeDef.params.safeParseAsync(req.params)\n\t\t\t: Promise.resolve(),\n\t\t'query' in endpoint && endpoint.query\n\t\t\t? endpoint.query.safeParseAsync(req.query)\n\t\t\t: Promise.resolve(),\n\t\t'body' in endpoint && endpoint.body\n\t\t\t? endpoint.body.safeParseAsync(req.body)\n\t\t\t: Promise.resolve(),\n\t]);\n\n\tconst parseEnd = Date.now();\n\tdebugPerformance(\n\t\t'[%s] Parameter parsing took %dms',\n\t\trequestId,\n\t\tparseEnd - parseStart\n\t);\n\n\tif (params?.error || query?.error || body?.error) {\n\t\tdebugErrors('[%s] Parameter validation failed', requestId);\n\t\tdebugErrors('[%s] Params errors: %o', requestId, {\n\t\t\tparams: req.params,\n\t\t\tissues: params?.error?.issues,\n\t\t});\n\t\tdebugErrors('[%s] Query errors: %o', requestId, {\n\t\t\tquery: req.query,\n\t\t\tissues: query?.error?.issues,\n\t\t});\n\t\tdebugErrors('[%s] Body errors: %o', requestId, {\n\t\t\tbody: req.body,\n\t\t\tissues: body?.error?.issues,\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: new APIError(statusCodes.BAD_REQUEST, {\n\t\t\t\tcode: body?.error?.issues.length\n\t\t\t\t\t? 'INVALID_REQUEST_BODY'\n\t\t\t\t\t: query?.error?.issues.length\n\t\t\t\t\t\t? 'INVALID_REQUEST_QUERY'\n\t\t\t\t\t\t: params?.error?.issues.length\n\t\t\t\t\t\t\t? 'INVALID_REQUEST_PARAMETERS'\n\t\t\t\t\t\t\t: 'INVALID_REQUEST',\n\t\t\t\tmessage: body?.error?.issues.length\n\t\t\t\t\t? `Invalid request body: ${prettifyError(body.error)}`\n\t\t\t\t\t: query?.error?.issues.length\n\t\t\t\t\t\t? `Invalid request query: ${prettifyError(query.error)}`\n\t\t\t\t\t\t: params?.error?.issues.length\n\t\t\t\t\t\t\t? `Invalid request parameters: ${prettifyError(params.error)}`\n\t\t\t\t\t\t\t: `Invalid request to ${String(method)} ${String(path)}`,\n\t\t\t\tdetails: {\n\t\t\t\t\trequestId,\n\t\t\t\t\tparams: params?.error?.issues,\n\t\t\t\t\tquery: query?.error?.issues,\n\t\t\t\t\tbody: body?.error?.issues,\n\t\t\t\t},\n\t\t\t}),\n\t\t};\n\t}\n\n\tdebugRoute('[%s] Parameters validated successfully', requestId);\n\n\treturn {\n\t\tsuccess: true,\n\t\tdata: {\n\t\t\tparams: params?.data ?? {},\n\t\t\tquery: query?.data ?? {},\n\t\t\tbody: body?.data ?? {},\n\t\t},\n\t};\n}\n","import type { APIError, APISuccessResponse } from '@md-oss/common/api/errors';\nimport type {\n\tMinimalRequest,\n\tMinimalRequestHandler,\n\tMinimalResponse,\n} from '@md-oss/common/api/requests';\nimport { debugPerformance, debugRoute } from './debugger';\nimport type { ExtractResolvedContext } from './request';\nimport type { InferApi, MethodKeys, RouteKeys, RouteRegistry } from './types';\n\ntype SignedAccessError =\n\t| 'MISSING_SIGNATURE'\n\t| 'INVALID_SIGNATURE'\n\t| 'EXPIRED_SIGNATURE';\n\ntype SendTypedResponseOptions<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n> = {\n\tpath: TPath;\n\tmethod: TMethod;\n\tdata: API[TPath]['endpoints'][TMethod]['response'];\n\tstatus?: number;\n\theaders?: Record<string, string>;\n\tmessage?: string;\n\tflattenResponse?: boolean;\n};\n\ntype IsUserMe<T> = T extends { isUserMe: true } ? string : null;\n\ntype ContextProvider<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n> = ExtractResolvedContext<Registry, API, TPath, TMethod> & {\n\tsession: EndpointDefinitionSession<\n\t\tRegistry,\n\t\tAPI,\n\t\tTPath,\n\t\tTMethod,\n\t\tTConsumerSession\n\t>;\n\tendpoint: Registry[TPath]['endpoints'][TMethod];\n\tctx: TConsumerContext;\n\tcps: <P extends API[TPath]['endpoints'][TMethod]['permissions'] | null>(\n\t\tresourceUserId: P extends null\n\t\t\t? IsUserMe<API[TPath]['endpoints'][TMethod]['permissions']>\n\t\t\t: IsUserMe<P>,\n\t\tpolicy?: P,\n\t\tsession?: TConsumerSession | null,\n\t\treq?: MinimalRequest,\n\t\tres?: MinimalResponse\n\t) => Promise<true | APIError>;\n};\n\ntype EndpointDefinitionSession<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n> = API[TPath]['endpoints'][TMethod]['permissions'] extends null\n\t? null | TConsumerSession\n\t: TConsumerSession;\n\ntype RouteHandler<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n\tTRequestHandler = MinimalRequestHandler,\n> = MinimalRequestHandler & {\n\twithContext: (\n\t\tctx: Omit<\n\t\t\tContextProvider<\n\t\t\t\tRegistry,\n\t\t\t\tAPI,\n\t\t\t\tTPath,\n\t\t\t\tTMethod,\n\t\t\t\tTConsumerSession,\n\t\t\t\tTConsumerContext\n\t\t\t>,\n\t\t\t'cps' | 'session' | 'endpoint'\n\t\t>\n\t) => TRequestHandler;\n};\n\ntype GenericRouteHandler<\n\tRegistry extends RouteRegistry = RouteRegistry,\n\tAPI extends InferApi<Registry> = InferApi<Registry>,\n\tTPath extends RouteKeys<Registry> = RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath> = MethodKeys<Registry, TPath>,\n\tTConsumerContext = void,\n> = RouteHandler<\n\tRegistry,\n\tAPI,\n\tTPath,\n\tTMethod,\n\tunknown,\n\tTConsumerContext,\n\tMinimalRequestHandler\n>;\n\ntype ControllerFunction<\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n\tTConsumerSession,\n\tTConsumerContext = void,\n\tTRequestHandler = MinimalRequestHandler,\n> = (\n\tcontext: ContextProvider<\n\t\tRegistry,\n\t\tAPI,\n\t\tTPath,\n\t\tTMethod,\n\t\tTConsumerSession,\n\t\tTConsumerContext\n\t>,\n\trespond: (\n\t\toptions:\n\t\t\t| APIError\n\t\t\t| SignedAccessError\n\t\t\t| Omit<\n\t\t\t\t\tSendTypedResponseOptions<Registry, API, TPath, TMethod>,\n\t\t\t\t\t'path' | 'method'\n\t\t\t >\n\t) => void\n) => TRequestHandler;\n\nconst noContentStatusCodes = [204, 205, 304];\n\nconst sendTypedResponse = <\n\tRegistry extends RouteRegistry,\n\tAPI extends InferApi<Registry>,\n\tTPath extends RouteKeys<Registry>,\n\tTMethod extends MethodKeys<Registry, TPath>,\n>(\n\tres: MinimalResponse,\n\toptions: SendTypedResponseOptions<Registry, API, TPath, TMethod>\n): void => {\n\tconst dataIsVoid =\n\t\toptions.data === undefined || typeof options.data === 'undefined'; // Null is explicit (non)data\n\tconst {\n\t\tdata,\n\t\tstatus = dataIsVoid ? 204 : 200,\n\t\theaders = {},\n\t\tmessage = null,\n\t\tflattenResponse = false,\n\t} = options;\n\n\tdebugRoute(\n\t\t'Sending typed response for %s %s with status %d',\n\t\toptions.method,\n\t\toptions.path,\n\t\tstatus\n\t);\n\tdebugPerformance(\n\t\t'Response data size: %d bytes',\n\t\tJSON.stringify(data)?.length || 0\n\t);\n\n\tres.setHeader('Content-Type', 'application/json; charset=utf-8');\n\tres.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');\n\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tdebugRoute('Setting custom header: %s = %s', key, value);\n\t\tres.setHeader(key, value);\n\t}\n\n\tif (dataIsVoid || noContentStatusCodes.includes(status)) {\n\t\tdebugRoute(\n\t\t\t'No response data to send, ending response with status %d',\n\t\t\tstatus\n\t\t);\n\t\tres.status(status).end();\n\t\treturn;\n\t}\n\n\tconst responseBodyNoData = {\n\t\tok: true,\n\t\tcode: status,\n\t\tmessage,\n\t} as const;\n\n\tif (flattenResponse) {\n\t\tdebugRoute(\n\t\t\t'Sending successful (flattened) response with message: %s',\n\t\t\tmessage || '(no message)'\n\t\t);\n\t\tres.status(status).json(data);\n\t\treturn;\n\t}\n\n\tconst responseBody = {\n\t\t...responseBodyNoData,\n\t\tdata,\n\t} satisfies APISuccessResponse<API[TPath]['endpoints'][TMethod]['response']>;\n\n\tdebugRoute(\n\t\t'Sending successful response with message: %s',\n\t\tmessage || '(no message)'\n\t);\n\tres.status(status).json(responseBody);\n};\n\nexport {\n\ttype SignedAccessError,\n\ttype SendTypedResponseOptions,\n\ttype IsUserMe,\n\ttype ContextProvider,\n\ttype EndpointDefinitionSession,\n\ttype RouteHandler,\n\ttype GenericRouteHandler,\n\ttype ControllerFunction,\n\tsendTypedResponse,\n};\n","import type z from 'zod/v4';\n\nexport type EndpointDefinition<\n\tP = unknown,\n\tResp = unknown,\n\tQ extends z.ZodType | undefined = z.ZodType | undefined,\n\tB extends z.ZodType | undefined = z.ZodType | undefined,\n> = {\n\tpermissions: P;\n\tresponse?: Resp;\n\tquery?: Q;\n\tbody?: B;\n};\n\n/**\n * Generic route registry that can be used to create type-safe API clients.\n * External packages can define their own route registries using this type.\n *\n * @example\n * ```typescript\n * const myRoutes = {\n * \"/users/:id\": {\n * params: z.object({ id: z.string() }),\n * endpoints: {\n * GET: {\n * response: {} as User,\n * permissions: null,\n * },\n * },\n * },\n * } as const satisfies RouteRegistry;\n * ```\n */\nexport type RouteRegistry<\n\tPermissions = unknown,\n\tParams extends z.ZodType | undefined = z.ZodType | undefined,\n> = {\n\t[path: string]: {\n\t\tparams?: Params;\n\t\tendpoints: {\n\t\t\t[method: string]: EndpointDefinition<Permissions>;\n\t\t};\n\t};\n};\n\ntype ExtractPermissionType<T extends RouteRegistry> =\n\tT extends RouteRegistry<infer P> ? P : never;\n\n// Helper to extract string keys from RouteRegistry (fixes TypeScript's keyof broadening to string | number | symbol)\nexport type RouteKeys<T extends RouteRegistry> = Extract<keyof T, string>;\nexport type MethodKeys<\n\tT extends RouteRegistry,\n\tR extends RouteKeys<T>,\n> = Extract<keyof T[R]['endpoints'], string>;\n\n/**\n * Infer the API type from a route registry.\n * This is used internally by the client factory.\n */\nexport type InferApi<T extends RouteRegistry> = {\n\t[R in RouteKeys<T>]: {\n\t\tparams: T[R]['params'] extends z.ZodType\n\t\t\t? z.infer<T[R]['params']>\n\t\t\t: undefined;\n\t\tendpoints: {\n\t\t\t[M in MethodKeys<T, R>]: {\n\t\t\t\tpermissions: T[R]['endpoints'][M] extends { permissions: infer P }\n\t\t\t\t\t? P extends ExtractPermissionType<T>\n\t\t\t\t\t\t? P\n\t\t\t\t\t\t: ExtractPermissionType<T>\n\t\t\t\t\t: ExtractPermissionType<T>;\n\t\t\t\tresponse: T[R]['endpoints'][M] extends { response: infer Resp }\n\t\t\t\t\t? Resp\n\t\t\t\t\t: undefined;\n\t\t\t\tquery: T[R]['endpoints'][M] extends { query: infer Q }\n\t\t\t\t\t? Q extends z.ZodType\n\t\t\t\t\t\t? z.output<Q>\n\t\t\t\t\t\t: undefined\n\t\t\t\t\t: undefined;\n\t\t\t\tbody: T[R]['endpoints'][M] extends { body: infer B }\n\t\t\t\t\t? B extends z.ZodType\n\t\t\t\t\t\t? z.output<B>\n\t\t\t\t\t\t: undefined\n\t\t\t\t\t: undefined;\n\t\t\t};\n\t\t};\n\t};\n};\n\nexport type PrefixRoutes<\n\tPrefix extends string,\n\tT extends Record<string, unknown>,\n> = {\n\t[K in keyof T as K extends '/' ? Prefix : `${Prefix}${K & string}`]: T[K];\n};\n\nexport function prefixRoutes<\n\tP extends string,\n\tT extends Record<string, unknown>,\n>(prefix: P, routes: T): PrefixRoutes<P, T> {\n\treturn Object.fromEntries(\n\t\tObject.entries(routes).map(([key, value]) => {\n\t\t\tconst prefixedKey = key === '/' ? prefix : `${prefix}${key}`;\n\t\t\treturn [prefixedKey, value];\n\t\t})\n\t) as PrefixRoutes<P, T>;\n}\n"],"names":["interpolatePath","__name","path","params","_","key","value","forbiddenProxyHeaders","forbiddenWebsocketHeaders","headersToRecord","headers","result","stripProxyAndWebsocketHeaders","resolvedHeaders","header","parseHeaders","shouldStripProxyAndWebsocketHeaders","createApiClient","_registry","config","baseUrl","_logger","options","method","body","query","metadata","requestLogger","logger","fullPath","error","parseError","queryParams","queryString","endpoint","requestUrl","requestHeaders","requestBody","defaultHeaders","response","APIError","statusCodes","isAPIErrorResponse","isRateLimitError","json","isAPIError","debug","debugFactory","debugRoute","debugPerformance","debugErrors","parseRequestParameters","req","requestId","routeDef","parseStart","parseEnd","prettifyError","noContentStatusCodes","sendTypedResponse","res","dataIsVoid","data","status","message","flattenResponse","responseBodyNoData","responseBody","prefixRoutes","prefix","routes"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@md-oss/api-types",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org/"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+ssh://git@github.com/Mirasaki-OSS/monorepo-template.git",
|
|
12
|
+
"directory": "vendor/api-types"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"description": "Type-safe API contracts, client factory, and handler helpers built on Zod",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**/*",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
28
|
+
"default": "./dist/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"default": "./dist/index.mjs"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@types/debug": "^4.1.12",
|
|
37
|
+
"debug": "^4.4.3",
|
|
38
|
+
"zod": "^4.3.6",
|
|
39
|
+
"@md-oss/common": "^0.1.0",
|
|
40
|
+
"@md-oss/config": "^0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"pkgroll": "^2.21.5",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "pkgroll --minify --clean-dist --sourcemap --define.process.env.NODE_ENV='\"production\"' --define.DEBUG=false",
|
|
48
|
+
"clean": "git clean -xdf .turbo dist node_modules tsconfig.tsbuildinfo",
|
|
49
|
+
"dev": "tsc --watch",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|