Client.ts provides a reusable HTTP client for OpenSearch Industry Algorithm Edition. It handles request signing, credential management, retry logic, and error handling so you can focus on building search features.
Prerequisites
Before you begin, ensure that you have:
Node.js and npm installed
An Alibaba Cloud account with an AccessKey ID and AccessKey Secret
The OpenSearch Industry Algorithm Edition service endpoint for your instance
Install dependencies
Add the following packages to your package.json. Install them from npm.
dependencies — required at runtime:
| Package | Purpose |
|---|---|
@alicloud/credentials | Manages Alibaba Cloud credentials, including AccessKey and STS token refresh |
@alicloud/opensearch-util | Generates request signatures and computes Content-MD5 for OpenSearch requests |
@alicloud/tea-typescript | Core HTTP runtime: handles request execution, retries, and backoff |
@alicloud/tea-util | Utility functions for serialization, header generation, and response parsing |
devDependencies — required for TypeScript compilation:
| Package | Purpose |
|---|---|
typescript | TypeScript compiler |
ts-node | Run TypeScript files directly without a separate compilation step |
Client.ts
Client.ts constructs and sends signed HTTP requests to OpenSearch. All requests go through the _request method, which:
Retrieves credentials from the configured credential provider
Sets required headers (
Date,X-Opensearch-Nonce,Authorization, and optionallyX-Opensearch-Security-Token)Computes
Content-MD5for request bodiesExecutes the request with configurable retry and backoff behavior
Returns the parsed response body and headers, or throws on 4xx/5xx status codes
import OpenSearchUtil from '@alicloud/opensearch-util/dist/client';
import * as $tea from '@alicloud/tea-typescript';
import Util, * as $Util from '@alicloud/tea-util';
import Credential, * as $Credential from '@alicloud/credentials';
import Config from "./Config";
class Client {
_endpoint: string;
_protocol: string;
_userAgent: string;
_credential: Credential;
constructor(config: Config) {
if (Util.isUnset($tea.toMap(config))) {
throw $tea.newError({
name: "ParameterMissing",
message: "'config' can not be unset",
});
}
if (Util.empty(config.type)) {
config.type = "access_key";
}
let credentialConfig = new $Credential.Config({
accessKeyId: config.accessKeyId, // AccessKey ID — load from environment variables, not hardcoded
type: config.type,
accessKeySecret: config.accessKeySecret, // AccessKey Secret — load from environment variables, not hardcoded
securityToken: config.securityToken, // STS security token (required for temporary credentials only)
});
this._credential = new Credential(credentialConfig);
this._endpoint = config.endpoint;
this._protocol = config.protocol;
this._userAgent = config.userAgent;
}
async _request(method: string, pathname: string, query: {[key: string ]: any}, headers: {[key: string ]: string}, body: any, runtime: $Util.RuntimeOptions): Promise<{[key: string ]: any}> {
let _runtime: { [key: string]: any } = {
timeouted: "retry",
readTimeout: runtime.readTimeout,
connectTimeout: runtime.connectTimeout,
httpProxy: runtime.httpProxy,
httpsProxy: runtime.httpsProxy,
noProxy: runtime.noProxy,
maxIdleConns: runtime.maxIdleConns,
retry: {
retryable: runtime.autoretry,
maxAttempts: Util.defaultNumber(runtime.maxAttempts, 3), // default: 3 attempts
},
backoff: {
policy: Util.defaultString(runtime.backoffPolicy, "no"), // default: no backoff
period: Util.defaultNumber(runtime.backoffPeriod, 1),
},
ignoreSSL: runtime.ignoreSSL,
}
let _lastRequest = null;
let _now = Date.now();
let _retryTimes = 0;
while ($tea.allowRetry(_runtime['retry'], _retryTimes, _now)) {
if (_retryTimes > 0) {
let _backoffTime = $tea.getBackoffTime(_runtime['backoff'], _retryTimes);
if (_backoffTime > 0) {
await $tea.sleep(_backoffTime);
}
}
_retryTimes = _retryTimes + 1;
try {
let request_ = new $tea.Request();
let accesskeyId = await this._credential.getAccessKeyId();
let accessKeySecret = await this._credential.getAccessKeySecret();
let securityToken = await this._credential.getSecurityToken();
request_.protocol = Util.defaultString(this._protocol, "HTTP");
request_.method = method;
request_.pathname = pathname;
request_.headers = {
'user-agent': Util.getUserAgent(this._userAgent),
Date: OpenSearchUtil.getDate(),
'Content-Type':'application/json',
host: Util.defaultString(this._endpoint, `opensearch-cn-hangzhou.aliyuncs.com`),
'X-Opensearch-Nonce': Util.getNonce(), // Unique nonce per request to prevent replay attacks
...headers,
};
if (!Util.isUnset(query)) {
request_.query = Util.stringifyMapValue(query);
}
if (!Util.isUnset(securityToken) && securityToken!='') {
request_.headers["X-Opensearch-Security-Token"] = securityToken; // Required for STS temporary credentials
}
if (!Util.isUnset(body)) {
let reqBody = Util.toJSONString(body);
request_.headers["Content-Type"] = "application/json";
request_.headers["Content-MD5"] = OpenSearchUtil.getContentMD5(reqBody); // Integrity check for the request body
request_.body = new $tea.BytesReadable(reqBody);
}
request_.headers["Authorization"] = OpenSearchUtil.getSignature(request_, accesskeyId, accessKeySecret); // HMAC signature for authentication
_lastRequest = request_;
let response_ = await $tea.doAction(request_, _runtime);
let objStr = await Util.readAsString(response_.body);
let obj = Util.parseJSON(objStr);
let res = Util.assertAsMap(obj);
if (Util.is4xx(response_.statusCode) || Util.is5xx(response_.statusCode)) {
throw $tea.newError({
message: response_.statusMessage,
data: objStr,
code: response_.statusCode,
});
}
return {
body: res,
headers: response_.headers,
};
} catch (ex) {
if ($tea.isRetryable(ex)) {
continue;
}
throw ex;
}
}
throw $tea.newUnretryableError(_lastRequest);
}
}
export default Client;Never hardcode accessKeyId or accessKeySecret in your source code. Load credentials from environment variables or a secrets manager to avoid accidental exposure.
What's next
Implement
Config.tsto define the configuration interface thatClientexpectsUse
Client._request()to call OpenSearch APIs by passing the HTTP method, path, query parameters, and request body