diff --git a/packages/axios-large-response/README.md b/packages/axios-large-response/README.md index 1f2cd25..2b860ee 100644 --- a/packages/axios-large-response/README.md +++ b/packages/axios-large-response/README.md @@ -86,9 +86,18 @@ client.getThings({}, null, { }); ``` -Two notes: +`enabled` works per request too, so the combination from Example 1 above - disabled globally, enabled only for the calls that need it - works on a runner as well: + +```ts +client.api.registerRunner(withLargeResponse(getLambdaRunner(lambdaName, context))); +... +client.getThings({}, null, { + 'axios-large-response': { enabled: true }, +}); +``` + +One note: -- **`enabled` is read once at wrap time** to decide whether to wrap at all - when it is `false`, the original runner is returned untouched with zero overhead. A per-request `enabled: false` still opts an individual call out. - **The wrapped runner keeps the original's prototype and own properties.** This matters: `openapi-client-axios` invokes a registered runner as `runner.runRequest(request, operation, runner.context)`, and the lambda runner reads the target function name off that `context`. Class instances keep their methods and their identity, and `runRequest` stays bound to the original, so a method that reads `this` still works. Wrap your runner rather than rebuilding it. ## Options diff --git a/packages/axios-large-response/src/runner/large-response-runner.test.ts b/packages/axios-large-response/src/runner/large-response-runner.test.ts index 3d0b2e3..0626200 100644 --- a/packages/axios-large-response/src/runner/large-response-runner.test.ts +++ b/packages/axios-large-response/src/runner/large-response-runner.test.ts @@ -296,9 +296,10 @@ describe('withLargeResponse', () => { }); /** - * Disabled means untouched - no wrapper, no Accept header, no overhead. + * Globally disabled is a no-op for an ordinary request: the request is forwarded as it + * came in, without the flag on Accept, and no ref is fetched. */ - it('should return the original runner when disabled', async () => { + it('should leave a request untouched when globally disabled', async () => { // given const runner = { runRequest, context: { functionName: 'my-lambda' } }; @@ -307,8 +308,9 @@ describe('withLargeResponse', () => { await wrapped.runRequest({ headers: { accept: 'application/json' } }); // then - expect(wrapped).toBe(runner); expect(runRequest.mock.calls[0][0].headers).toEqual({ accept: 'application/json' }); + expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); + expect(wrapped.context).toEqual({ functionName: 'my-lambda' }); }); /** @@ -440,12 +442,11 @@ describe('withLargeResponse', () => { }); /** - * Deliberate asymmetry with the interceptor, pinned so it stays a decision rather than a - * surprise: the interceptor can be disabled globally and enabled per request, but a - * disabled wrapper is never installed, so there is nothing left to read the request. - * Wrap with `enabled: true` and opt individual requests out instead. + * `enabled` is resolved per request on both paths, so the interceptor's headline + * combination - disabled globally, enabled for the requests that need it - works on a + * runner too. */ - it('should not let a per-request enabled re-enable a globally disabled wrapper', async () => { + it('should let a per-request enabled re-enable a globally disabled wrapper', async () => { // given runRequest.mockResolvedValue({ status: 200, @@ -458,8 +459,25 @@ describe('withLargeResponse', () => { const response = await wrapped.runRequest({ [NAMESPACE]: { enabled: true } }); // then - expect(globalOptions.onFetchLargePayloadFromRef).not.toHaveBeenCalled(); - expect(response.data).toEqual({ $payload_ref: 'https://bucket.s3.amazonaws.com/ref' }); + expect(globalOptions.onFetchLargePayloadFromRef).toHaveBeenCalledWith('https://bucket.s3.amazonaws.com/ref'); + expect(response.data).toEqual({ huge: 'data' }); + expect(runRequest.mock.calls[0][0].headers).toEqual({ Accept: LARGE_PAYLOAD_MIME_TYPE }); + }); + + /** + * The per-request options are ours, not the transport's, so they are stripped even from a + * request that stays disabled - otherwise a runner that serialises the whole request + * sends them on as payload. + */ + it('should strip the per-request options from a request that stays disabled', async () => { + // given + const wrapped = withLargeResponse({ runRequest }, { ...globalOptions, enabled: false }); + + // when + await wrapped.runRequest({ headers: { accept: 'application/json' }, [NAMESPACE]: { debug: true } }); + + // then + expect(Object.keys(runRequest.mock.calls[0][0])).toEqual(['headers']); }); /** diff --git a/packages/axios-large-response/src/runner/large-response-runner.ts b/packages/axios-large-response/src/runner/large-response-runner.ts index b1fd30d..d648420 100644 --- a/packages/axios-large-response/src/runner/large-response-runner.ts +++ b/packages/axios-large-response/src/runner/large-response-runner.ts @@ -24,15 +24,12 @@ import { NAMESPACE, getOptions, resolveLargePayload, usageWarnings, withAcceptHe * and the lambda runner reads the target function name off that `context`. `runRequest` * stays bound to the original runner, so a method that reads `this` still works. * - * Unlike the interceptor, `enabled` is resolved once when the runner is wrapped, since - * that decision is what determines whether to wrap at all; when disabled the original - * runner is returned untouched. Every other option is resolved per request, read from a - * `[NAMESPACE]` key on the request just as the interceptor reads it off the axios config. - * - * That one difference is worth knowing: a per-request `enabled: false` opts a request out, - * but a global `enabled: false` cannot be re-enabled per request the way it can on the - * interceptor - there is no wrapper left to read the request. Wrap with `enabled: true` and - * opt individual requests out, rather than the other way round. + * Every option is resolved per request, `enabled` included, read from a `[NAMESPACE]` key + * on the request just as the interceptor reads it off the axios config. So the same + * combinations work on both paths: enable globally and opt individual requests out with + * `enabled: false`, or leave it disabled and opt the requests that need it in with + * `enabled: true`. The key is stripped from the forwarded request either way, so it never + * reaches the transport as payload. * * @example * ```ts @@ -48,10 +45,6 @@ const withLargeResponse = ( // check for warnings usageWarnings(globalOptions); - if (!getOptions(undefined, globalOptions).enabled) { - return runner; - } - // `LargeResponseRunner` accepts any runner shape, so its `runRequest` is not callable as // declared; the wrapper handles requests and responses structurally instead. Bound to the // runner so a `runRequest` that reads `this` keeps working. @@ -69,8 +62,8 @@ const withLargeResponse = ( const options = getOptions(requestOptions, globalOptions); - // a per-request `enabled: false` opts this one request out, as it does on the - // interceptor path + // resolved from both layers, so a per-request `enabled` decides this one call either + // way, exactly as it does on the interceptor path if (!options.enabled) { return dispatch(forwarded, ...rest); }