Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/axios-large-response/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' } };

Expand All @@ -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' });
});

/**
Expand Down Expand Up @@ -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,
Expand All @@ -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']);
});

/**
Expand Down
23 changes: 8 additions & 15 deletions packages/axios-large-response/src/runner/large-response-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,10 +45,6 @@ const withLargeResponse = <TRunner extends LargeResponseRunner>(
// 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.
Expand All @@ -69,8 +62,8 @@ const withLargeResponse = <TRunner extends LargeResponseRunner>(

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);
}
Expand Down
Loading