Skip to content

Commit

Permalink
Add *WithResponseHeaders() methods
Browse files Browse the repository at this point in the history
  • Loading branch information
olivier-lando committed Apr 23, 2024
1 parent f83f015 commit 836de1b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 23 deletions.
2 changes: 2 additions & 0 deletions config/nodejs.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
changelog:
- 2.5.7 (2024-04-23):
- Add *WithResponseHeaders() methods
- 2.5.6 (2024-02-19):
- Update VideoStatusIngest enum
- 2.5.5 (2023-12-19):
Expand Down
17 changes: 17 additions & 0 deletions templates/nodejs/README.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Documentation](#documentation)
- [API Endpoints](#api-endpoints){{#apiInfo}}{{#apis}}{{^x-client-hidden}} - [{{classname}}](#{{#lower}}{{classname}}{{/lower}}){{/x-client-hidden}}
{{/apis}}{{/apiInfo}} - [Models](#models)
- [Rate Limiting](#rate-limiting)
- [Authorization](#authorization)
- [API key](#api-key)
- [Get the access token](#get-the-access-token)
Expand Down Expand Up @@ -110,6 +111,22 @@ Method | Description | HTTP request
{{#models}}{{#model}} - [{{classname}}](https://github.com/apivideo/{{gitRepoId}}/blob/main/{{modelDocFileFolder}}/{{classname}}.md)
{{/model}}{{/models}}

### Rate Limiting

api.video implements [rate limiting](https://docs.api.video/reference#limitation) to ensure fair usage and stability of the service. The rate limit values can be retrieved from the response headers of any API request.

In this Node.js client, you can access these headers by using the `*WithResponseHeaders()` versions of the methods. These methods return both the response body and the headers, allowing you to check the `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Retry-After` headers to understand your current rate limit status.

Here is an example of how to use these methods:

```js
const client = new ApiVideoClient({ apiKey: "YOUR_API_KEY" });
const { body: videos, headers } = await client.videos.listWithResponseHeaders();
console.log('Rate Limit:', headers['x-ratelimit-limit']);
console.log('Rate Limit Remaining:', headers['x-ratelimit-remaining']);
console.log('Rate Limit Retry after:', headers['x-ratelimit-retry-after']);
```

### Authorization

#### API key
Expand Down
16 changes: 16 additions & 0 deletions templates/nodejs/src/HttpClient.ts.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ export type QueryOptions = {
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
};


export type ApiResponseHeaders = {
server: string
'content-type': string
'transfer-encoding': string
connection: string
'cache-control': string
date: string
'x-ratelimit-remaining': string
'x-ratelimit-retry-after': string
'x-ratelimit-limit': string
'x-server': string
'access-control-allow-origin': string
'timing-allow-origin': string
}

export default class HttpClient {
private apiKey?: string;
private baseUri: string;
Expand Down
111 changes: 88 additions & 23 deletions templates/nodejs/src/api/api.ts.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { promisify } from 'util';
import { URLSearchParams } from 'url';
import FormData from 'form-data';
import ObjectSerializer from '../ObjectSerializer';
import HttpClient, { QueryOptions } from '../HttpClient';
import HttpClient, { QueryOptions, ApiResponseHeaders } from '../HttpClient';
import ProgressiveSession from '../model/ProgressiveSession';
{{#imports}}
import {{classname}} from '../model/{{classname}}';
Expand Down Expand Up @@ -54,14 +54,22 @@ export default class {{classname}} {
}

uploadPart(file: string, progressListener?: (event: UploadProgressEvent) => void) {
return this.upload(file, false, progressListener).then((res) => res.body);
};

uploadPartWithResponseHeaders(file: string, progressListener?: (event: UploadProgressEvent) => void) {
return this.upload(file, false, progressListener);
};

uploadLastPart(file: string, progressListener?: (event: UploadProgressEvent) => void) {
return this.upload(file, true, progressListener);
return this.upload(file, true, progressListener).then((res) => res.body);
};

upload(file: string, isLast: boolean, progressListener?: (event: UploadProgressEvent) => void) {
uploadLastPartWithResponseHeaders(file: string, progressListener?: (event: UploadProgressEvent) => void) {
return this.upload(file, true, progressListener);
};

async upload(file: string, isLast: boolean, progressListener?: (event: UploadProgressEvent) => void) {
const queryParams: QueryOptions = {};
queryParams.headers = {};

Expand Down Expand Up @@ -165,23 +173,30 @@ export default class {{classname}} {
}
}

const call = this.httpClient.call(localVarPath, queryParams);
const response = await this.httpClient.call(localVarPath, queryParams);

this.currentPart++;
return call.then(response => ObjectSerializer.deserialize(
const responseBody = ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as Type){{#vendorExtensions.x-client-copy-from-response}}.then((res) => {
this.{{paramName}} = (res as any).{{paramName}};
return res;
}){{/vendorExtensions.x-client-copy-from-response}};
) as Type;

{{#vendorExtensions.x-client-copy-from-response}}
this.{{paramName}} = (responseBody as any).{{paramName}};
{{/vendorExtensions.x-client-copy-from-response}}

return {
body: responseBody,
headers: response.headers
}
}
}

return new {{#titlecase}}{{nickname}}{{/titlecase}}ProgressiveSession<{{{returnType}}}>(this.httpClient);
}
{{/vendorExtensions.x-client-chunk-upload}}
{{#vendorExtensions.x-group-parameters}}

{{#vendorExtensions.x-group-parameters}}
/**
{{#notes}}
* {{&notes}}
Expand All @@ -194,7 +209,9 @@ export default class {{classname}} {
* @param { {{dataType}} } searchParams.{{paramName}} {{description}}
{{/allParams}}
*/
public async {{nickname}}({ {{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}} }: { {{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}} }{{#vendorExtensions.x-optional-object}} = {}{{/vendorExtensions.x-optional-object}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}> {
public async {{nickname}}(args: { {{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}} }{{#vendorExtensions.x-optional-object}} = {}{{/vendorExtensions.x-optional-object}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}> {
return this.{{nickname}}WithResponseHeaders(args).then((res) => res.body);
}
{{/vendorExtensions.x-group-parameters}}
{{^vendorExtensions.x-group-parameters}}
/**
Expand All @@ -209,6 +226,39 @@ export default class {{classname}} {
{{/allParams}}
*/
public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isFile}}{{#vendorExtensions.x-client-chunk-upload}}string{{/vendorExtensions.x-client-chunk-upload}}{{^vendorExtensions.x-client-chunk-upload}}string | Readable | Buffer{{/vendorExtensions.x-client-chunk-upload}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}}{{#vendorExtensions.x-optional-object}} = {}{{/vendorExtensions.x-optional-object}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-client-chunk-upload}}, progressListener?: (event: UploadProgressEvent) => void{{/vendorExtensions.x-client-chunk-upload}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}} {{^returnType}}void{{/returnType}}> {
return this.{{nickname}}WithResponseHeaders({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-client-chunk-upload}}, progressListener{{/vendorExtensions.x-client-chunk-upload}}).then((res) => res.body);;
}
{{/vendorExtensions.x-group-parameters}}


{{#vendorExtensions.x-group-parameters}}
/**
{{#notes}}
* {{&notes}}
{{/notes}}
{{#summary}}
* {{&summary}}
{{/summary}}
* @param {Object} searchParams
{{#allParams}}
* @param { {{dataType}} } searchParams.{{paramName}} {{description}}
{{/allParams}}
*/
public async {{nickname}}WithResponseHeaders({ {{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}} }: { {{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}} }{{#vendorExtensions.x-optional-object}} = {}{{/vendorExtensions.x-optional-object}}): Promise<{{#returnType}} {headers: ApiResponseHeaders, body:{{{returnType}}} } {{/returnType}} {{^returnType}}void{{/returnType}}> {
{{/vendorExtensions.x-group-parameters}}
{{^vendorExtensions.x-group-parameters}}
/**
{{#notes}}
* {{&notes}}
{{/notes}}
{{#summary}}
* {{&summary}}
{{/summary}}
{{#allParams}}
* @param {{paramName}} {{description}}
{{/allParams}}
*/
public async {{nickname}}WithResponseHeaders({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{#isFile}}{{#vendorExtensions.x-client-chunk-upload}}string{{/vendorExtensions.x-client-chunk-upload}}{{^vendorExtensions.x-client-chunk-upload}}string | Readable | Buffer{{/vendorExtensions.x-client-chunk-upload}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}}{{#vendorExtensions.x-optional-object}} = {}{{/vendorExtensions.x-optional-object}}{{^-last}}, {{/-last}}{{/allParams}}{{#vendorExtensions.x-client-chunk-upload}}, progressListener?: (event: UploadProgressEvent) => void{{/vendorExtensions.x-client-chunk-upload}}): Promise<{{#returnType}} {headers: ApiResponseHeaders, body:{{{returnType}}} } {{/returnType}} {{^returnType}}void{{/returnType}}> {
{{/vendorExtensions.x-group-parameters}}
const queryParams: QueryOptions = {};
queryParams.headers = {};
Expand Down Expand Up @@ -364,13 +414,18 @@ export default class {{classname}} {

const call = this.httpClient.call(localVarPath, queryParams);

return call.then(response => ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as {{{returnType}}});
return call.then(response => {
return {
headers: response.headers,
body: ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as {{{returnType}}}
}
});
}
let uploadChunkSize = chunkSize;
let lastBody;
let lastResponse;
let stream;
let chunkNumber = 0;

Expand Down Expand Up @@ -409,15 +464,20 @@ export default class {{classname}} {
}
const call = this.httpClient.call(localVarPath, queryParams);

lastBody = await call.then(response => ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as {{{returnType}}});
lastResponse = await call.then(response => {
return {
headers: response.headers,
body: ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as {{{returnType}}}
}
});

stream.close();
}

return Promise.resolve(lastBody as {{{returnType}}});
return Promise.resolve(lastResponse!);
{{/vendorExtensions.x-client-chunk-upload}}
{{^vendorExtensions.x-client-chunk-upload}}
{{#platforms}}
Expand All @@ -438,10 +498,15 @@ export default class {{classname}} {
queryParams.body = formData;
{{/hasFormParams}}
return this.httpClient.call(localVarPath, queryParams)
.then(response => ObjectSerializer.deserialize(
.then(response => {
return {
headers: response.headers,
body: ObjectSerializer.deserialize(
ObjectSerializer.parse(response.body, response.headers["content-type"]),
"{{{returnType}}}", "{{returnFormat}}"
) as {{{returnType}}});
) as {{{returnType}}}
}
});
{{/vendorExtensions.x-client-chunk-upload}}
}
{{/operation}}
Expand Down
4 changes: 4 additions & 0 deletions templates/nodejs/src/model/ProgressiveSession.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ApiResponseHeaders } from "../HttpClient";

export default interface ProgressiveSession<T> {
uploadPart(file: string): Promise<T>;
uploadPartWithResponseHeaders(file: string): Promise<{headers: ApiResponseHeaders, body: T}>;
uploadLastPart(file: string): Promise<T>;
uploadLastPartWithResponseHeaders(file: string): Promise<{headers: ApiResponseHeaders, body: T}>;
}
7 changes: 7 additions & 0 deletions templates/nodejs/test/sandbox.spec.ts.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ describe('ApiVideoClient', () => {
);
});
});

describe("Response headers", () => {
it("should return response headers", async () => {
const videos = await client.videos.listWithResponseHeaders();
expect(videos.headers).toHaveProperty("content-type");
});
});

describe('Watermarks', () => {
let watermark: Watermark, watermarkVideo: Video;
Expand Down

0 comments on commit 836de1b

Please sign in to comment.