1- import {
2- type ApiOperations ,
3- operationsByPath ,
4- operationsByTag ,
5- type RequestMap ,
6- type Route ,
7- } from "./generated/operations-map.gen" ;
1+ import { type ApiClient , operationsByPath , operationsByTag } from "./generated/operations-map.gen" ;
82import {
93 type FetcherOptions ,
104 fetcher ,
115 type OnResponseHook ,
126 type RetryConfig ,
137} from "./utils/fetcher" ;
148
9+ export type { ApiClient } ;
10+
11+ /** A single operation's HTTP method and path template. */
12+ export interface OperationMeta {
13+ method : string ;
14+ path : string ;
15+ }
16+
17+ /**
18+ * The dispatch tables the client drives: operations grouped by tag (for
19+ * `api.<tag>.<operationId>()`) and keyed by route (for `api.request()`).
20+ *
21+ * Defaults to the registry generated from the public API bundle. A client
22+ * generated from a different bundle passes its own registry and its own `api`
23+ * type, so it reuses this transport (auth, retries, error mapping) instead of
24+ * forking it. See `@pgbeam/sdk-internal`.
25+ */
26+ export interface OperationRegistry {
27+ byTag : Record < string , Record < string , OperationMeta > > ;
28+ byPath : Record < string , OperationMeta > ;
29+ }
30+
31+ const publicRegistry : OperationRegistry = {
32+ byTag : operationsByTag ,
33+ byPath : operationsByPath ,
34+ } ;
35+
36+ /** Params accepted by every generated operation, before the surface types narrow them. */
37+ interface CallParams {
38+ pathParams ?: FetcherOptions [ "pathParams" ] ;
39+ queryParams ?: FetcherOptions [ "queryParams" ] ;
40+ body ?: unknown ;
41+ }
42+
1543export interface PgBeamClientOptions {
1644 /** JWT token, or async function that resolves one (for lazy/refreshing tokens). */
1745 token : string | null | ( ( ) => Promise < string | null > ) ;
@@ -23,32 +51,18 @@ export interface PgBeamClientOptions {
2351 onResponse ?: OnResponseHook ;
2452 /** Retry configuration. Default: { maxRetries: 5 }. Set false to disable. */
2553 retry ?: RetryConfig | false ;
54+ /** Operation registry to dispatch on. Defaults to the public API's. */
55+ operations ?: OperationRegistry ;
2656}
2757
28- /** Type-safe client with tag-based proxy access and .request() method. */
29- export type ApiClient = ApiOperations & {
30- /**
31- * Type-safe request by route string.
32- *
33- * @example
34- * const projects = await api.request('GET /v1/projects', { queryParams: { org_id } });
35- * const project = await api.request('GET /v1/projects/{project_id}', { pathParams: { project_id } });
36- */
37- request : < K extends Route > (
38- route : K ,
39- ...args : RequestMap [ K ] [ "params" ] extends undefined
40- ? [ params ?: undefined ]
41- : [ params : RequestMap [ K ] [ "params" ] ]
42- ) => Promise < RequestMap [ K ] [ "response" ] > ;
43- } ;
44-
45- export class PgBeamClient {
58+ export class PgBeamClient < TApi = ApiClient > {
4659 private _baseUrl : string ;
4760 private _tokenOrFn : string | null | ( ( ) => Promise < string | null > ) ;
4861 private _fetchImpl ?: typeof globalThis . fetch ;
4962 private _onResponse ?: OnResponseHook ;
5063 private _retry ?: RetryConfig ;
51- private _api ?: ApiClient ;
64+ private _operations : OperationRegistry ;
65+ private _api ?: TApi ;
5266
5367 constructor ( options : PgBeamClientOptions ) {
5468 this . _baseUrl = options . baseUrl ;
@@ -57,6 +71,7 @@ export class PgBeamClient {
5771 this . _onResponse = options . onResponse ;
5872 this . _retry =
5973 options . retry === false ? { maxRetries : 0 } : ( options . retry ?? { maxRetries : 5 } ) ;
74+ this . _operations = options . operations ?? publicRegistry ;
6075 }
6176
6277 private async _resolveToken ( ) : Promise < string | null > {
@@ -66,71 +81,57 @@ export class PgBeamClient {
6681 return this . _tokenOrFn ;
6782 }
6883
69- private _call ( method : string , path : string ) {
70- return async ( params : Record < string , unknown > = { } ) => {
71- const token = await this . _resolveToken ( ) ;
72- return fetcher ( {
73- method,
74- path,
75- pathParams : params . pathParams as FetcherOptions [ "pathParams" ] ,
76- queryParams : params . queryParams as FetcherOptions [ "queryParams" ] ,
77- body : params . body ,
78- baseUrl : this . _baseUrl ,
79- token,
80- fetchImpl : this . _fetchImpl ,
81- onResponse : this . _onResponse ,
82- retry : this . _retry ,
83- } ) ;
84- } ;
84+ private async _send ( meta : OperationMeta , params : CallParams ) : Promise < unknown > {
85+ const token = await this . _resolveToken ( ) ;
86+ return fetcher ( {
87+ method : meta . method ,
88+ path : meta . path ,
89+ pathParams : params . pathParams ,
90+ queryParams : params . queryParams ,
91+ body : params . body ,
92+ baseUrl : this . _baseUrl ,
93+ token,
94+ fetchImpl : this . _fetchImpl ,
95+ onResponse : this . _onResponse ,
96+ retry : this . _retry ,
97+ } ) ;
98+ }
99+
100+ private _call ( meta : OperationMeta ) {
101+ return ( params : CallParams = { } ) => this . _send ( meta , params ) ;
85102 }
86103
87104 /** Access API operations via tag-based namespaces or `.request()`. */
88- get api ( ) : ApiClient {
105+ get api ( ) : TApi {
89106 if ( this . _api ) return this . _api ;
90107
91- const request = async < K extends Route > (
92- route : K ,
93- params ?: RequestMap [ K ] [ "params" ] ,
94- ) : Promise < RequestMap [ K ] [ "response" ] > => {
95- const meta = operationsByPath [ route as keyof typeof operationsByPath ] ;
96- if ( ! meta ) throw new Error ( `Unknown route: ${ String ( route ) } ` ) ;
97- const token = await this . _resolveToken ( ) ;
98- return fetcher ( {
99- method : meta . method ,
100- path : meta . path ,
101- pathParams : ( params as Record < string , unknown > | undefined )
102- ?. pathParams as FetcherOptions [ "pathParams" ] ,
103- queryParams : ( params as Record < string , unknown > | undefined )
104- ?. queryParams as FetcherOptions [ "queryParams" ] ,
105- body : ( params as Record < string , unknown > | undefined ) ?. body ,
106- baseUrl : this . _baseUrl ,
107- token,
108- fetchImpl : this . _fetchImpl ,
109- onResponse : this . _onResponse ,
110- retry : this . _retry ,
111- } ) ;
108+ // Async so an unknown route rejects rather than throwing synchronously,
109+ // which is what callers awaiting .request() expect.
110+ const request = async ( route : string , params : CallParams = { } ) => {
111+ const meta = this . _operations . byPath [ route ] ;
112+ if ( ! meta ) throw new Error ( `Unknown route: ${ route } ` ) ;
113+ return this . _send ( meta , params ) ;
112114 } ;
113115
114- this . _api = new Proxy ( { } as ApiClient , {
116+ this . _api = new Proxy ( { } as TApi , {
115117 get : ( _ , prop : string ) => {
116118 if ( prop === "request" ) return request ;
117119
118- const tagOps = operationsByTag [ prop as keyof typeof operationsByTag ] ;
120+ const tagOps = this . _operations . byTag [ prop ] ;
119121 if ( ! tagOps ) return undefined ;
120122
121123 return new Proxy (
122124 { } ,
123125 {
124126 get : ( _ , method : string ) => {
125- const ops = tagOps as Record < string , { method : string ; path : string } > ;
126- const opMeta = ops [ method ] ;
127+ const opMeta = tagOps [ method ] ;
127128 if ( ! opMeta ) return undefined ;
128- return this . _call ( opMeta . method , opMeta . path ) ;
129+ return this . _call ( opMeta ) ;
129130 } ,
130131 } ,
131132 ) ;
132133 } ,
133- } ) as ApiClient ;
134+ } ) as TApi ;
134135
135136 return this . _api ;
136137 }
0 commit comments