@@ -10,6 +10,16 @@ import type {Middleware} from 'openapi-fetch';
1010import type { AuthStrategy } from '../auth/types.js' ;
1111import { getLogger } from '../logging/logger.js' ;
1212
13+ /**
14+ * Configuration for extra parameters middleware.
15+ */
16+ export interface ExtraParamsConfig {
17+ /** Extra query parameters to add to the URL */
18+ query ?: Record < string , string | number | boolean | undefined > ;
19+ /** Extra body fields to merge into JSON request bodies */
20+ body ?: Record < string , unknown > ;
21+ }
22+
1323/**
1424 * Converts Headers to a plain object for logging.
1525 */
@@ -107,3 +117,87 @@ export function createLoggingMiddleware(prefix?: string): Middleware {
107117 } ,
108118 } ;
109119}
120+
121+ /**
122+ * Creates middleware that adds extra query parameters and/or body fields to requests.
123+ *
124+ * This is useful for internal/power-user scenarios where you need to pass
125+ * parameters that aren't in the typed OpenAPI schema.
126+ *
127+ * @param config - Configuration with extra query and/or body params
128+ * @returns Middleware that adds extra params to requests
129+ *
130+ * @example
131+ * ```typescript
132+ * const client = createOdsClient(config, auth);
133+ * client.use(createExtraParamsMiddleware({
134+ * query: { debug: 'true', internal_flag: '1' },
135+ * body: { _internal: { trace: true } }
136+ * }));
137+ * ```
138+ */
139+ export function createExtraParamsMiddleware ( config : ExtraParamsConfig ) : Middleware {
140+ const logger = getLogger ( ) ;
141+
142+ return {
143+ async onRequest ( { request} ) {
144+ let modifiedRequest = request ;
145+
146+ // Add extra query parameters
147+ if ( config . query && Object . keys ( config . query ) . length > 0 ) {
148+ const url = new URL ( request . url ) ;
149+ for ( const [ key , value ] of Object . entries ( config . query ) ) {
150+ if ( value !== undefined ) {
151+ url . searchParams . set ( key , String ( value ) ) ;
152+ }
153+ }
154+ logger . trace (
155+ { extraQuery : config . query , originalUrl : request . url , newUrl : url . toString ( ) } ,
156+ '[ExtraParams] Adding extra query params to URL' ,
157+ ) ;
158+ modifiedRequest = new Request ( url . toString ( ) , {
159+ method : request . method ,
160+ headers : request . headers ,
161+ body : request . body ,
162+ duplex : request . body ? 'half' : undefined ,
163+ } as RequestInit ) ;
164+ }
165+
166+ // Merge extra body fields for JSON requests
167+ if ( config . body && Object . keys ( config . body ) . length > 0 ) {
168+ const contentType = modifiedRequest . headers . get ( 'content-type' ) ;
169+ if ( contentType ?. includes ( 'application/json' ) && modifiedRequest . body ) {
170+ const clonedRequest = modifiedRequest . clone ( ) ;
171+ const originalBody = await clonedRequest . text ( ) ;
172+ try {
173+ const parsedBody = JSON . parse ( originalBody ) as Record < string , unknown > ;
174+ const mergedBody = { ...parsedBody , ...config . body } ;
175+ logger . trace (
176+ { originalBody : parsedBody , extraBody : config . body , mergedBody} ,
177+ '[ExtraParams] Merging extra body fields into request' ,
178+ ) ;
179+ modifiedRequest = new Request ( modifiedRequest . url , {
180+ method : modifiedRequest . method ,
181+ headers : modifiedRequest . headers ,
182+ body : JSON . stringify ( mergedBody ) ,
183+ } ) ;
184+ } catch {
185+ logger . warn ( '[ExtraParams] Could not parse request body as JSON, skipping body merge' ) ;
186+ }
187+ } else if ( ! modifiedRequest . body ) {
188+ // No existing body, create one with extra fields
189+ logger . trace ( { body : config . body } , '[ExtraParams] Creating new body with extra fields' ) ;
190+ const headers = new Headers ( modifiedRequest . headers ) ;
191+ headers . set ( 'content-type' , 'application/json' ) ;
192+ modifiedRequest = new Request ( modifiedRequest . url , {
193+ method : modifiedRequest . method ,
194+ headers,
195+ body : JSON . stringify ( config . body ) ,
196+ } ) ;
197+ }
198+ }
199+
200+ return modifiedRequest ;
201+ } ,
202+ } ;
203+ }
0 commit comments