From dff98c9dc1f075f56a52436f77e419781d5c035f Mon Sep 17 00:00:00 2001
From: Ivan Kamkin <234-Ivan.Kamkin@users.noreply.git.saltov.dynabic.com>
Date: Tue, 3 Feb 2026 13:38:02 +0500
Subject: [PATCH 1/5] Update templates
Add Agents md
---
Agents.md | 16 ++
codegen/Templates/nodejs/httpClient.mustache | 240 +++++++++++++++++++
codegen/Templates/nodejs/multipart.mustache | 56 +++++
submodules/node | 2 +-
4 files changed, 313 insertions(+), 1 deletion(-)
create mode 100644 Agents.md
create mode 100644 codegen/Templates/nodejs/httpClient.mustache
create mode 100644 codegen/Templates/nodejs/multipart.mustache
diff --git a/Agents.md b/Agents.md
new file mode 100644
index 0000000..5b3175f
--- /dev/null
+++ b/Agents.md
@@ -0,0 +1,16 @@
+# Aspose.BarCode Cloud SDK and Codegen repository
+
+This is repository with swagger generated SDK for Aspose.Barcode.Cloud service and code generating scripts. All sdk are submodules of this repo and located in `submodules` directory. Swagger specification of service API is located in `spec/aspose-barcode-cloud.json` file. Custom mustashe templates are located in `codegen/Templates` dir. Original templates for all languages are in github. Sripts for generating SDK `codegen` directory. Some post processing are in Makefiles in all submodules repo.
+
+## Common requirements for making changes in SDK code
+
+0. Don't commit or push in repo by youreself. But you can pull and stage in git.
+1. To run any scripts, use WSL if you're on Windows.
+2. After making changes run tests with `make test` or similar comand in Makefile in SDK submodule repo.
+3. Add changes to mustache templates after changes in SDK code are made. If you changed some code and template for it not in codegen/Templates dir. Download this template and made changes in local copy in codegen/Templates dir.
+4. Enshure that generated code is the same as you new code. For it:
+ 4.1 Stage your changes in sdk submodule repo.
+ 4.2 Run `make ` command in main repo. See Makefile for it.
+ 4.3 After generating script are end it work. Enshure there is no unstaged changes in sdk submodule.
+ 4.4 Fix templates if generated code are not the same as you new code.
+5. After templates fixed, you can end your task.
diff --git a/codegen/Templates/nodejs/httpClient.mustache b/codegen/Templates/nodejs/httpClient.mustache
new file mode 100644
index 0000000..51a28bc
--- /dev/null
+++ b/codegen/Templates/nodejs/httpClient.mustache
@@ -0,0 +1,240 @@
+import { ApiErrorResponse } from './models';
+
+export interface StringKeyWithStringValue {
+ [key: string]: string;
+}
+
+export interface HttpOptions {
+ uri: string;
+ body?: any;
+ encoding?: BufferEncoding | null;
+ form?: StringKeyWithStringValue;
+ headers?: StringKeyWithStringValue;
+ json?: boolean;
+ method?: string;
+ qs?: StringKeyWithStringValue;
+}
+
+export interface HttpResponse {
+ statusCode: number;
+ statusMessage: string;
+ headers: NodeJS.Dict;
+ body: any;
+}
+
+export interface HttpResult {
+ response: HttpResponse;
+ body: any;
+}
+
+export interface HttpRejectType {
+ response: HttpResponse | null;
+ errorResponse: ApiErrorResponse | null;
+ error: Error;
+}
+
+interface FetchHeaders {
+ forEach(callback: (value: string, key: string) => void): void;
+}
+
+interface FetchResponse {
+ status: number;
+ statusText: string;
+ headers: FetchHeaders;
+ ok: boolean;
+ arrayBuffer(): Promise;
+}
+
+interface FetchRequestInit {
+ method?: string;
+ headers?: StringKeyWithStringValue;
+ body?: any;
+}
+
+type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise;
+
+export class HttpClient {
+ public requestAsync(options: HttpOptions): Promise {
+ const url: URL = options.qs
+ ? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
+ : new URL(options.uri);
+
+ const requestBody = this.buildRequestBody(options);
+
+ const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
+
+ const requestOptions: FetchRequestInit = {
+ method: options.method || 'GET',
+ headers: options.headers,
+ };
+
+ if (requestBody) {
+ requestOptions.body = requestBody;
+ }
+
+ return this.doFetchRequest(url, requestOptions, responseEncoding);
+ }
+
+ private buildRequestBody(options: HttpOptions) {
+ let requestBody = options.body;
+ if (options.form) {
+ // Override requestBody for form with form content
+ requestBody = new URLSearchParams(options.form).toString();
+ options.headers = Object.assign(
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ options.headers
+ );
+ }
+ if (options.json) {
+ // Override requestBody with JSON value
+ requestBody = JSON.stringify(options.body);
+ options.headers = Object.assign(
+ {
+ 'Content-Type': 'application/json',
+ },
+ options.headers
+ );
+ }
+ return requestBody;
+ }
+
+ private async doFetchRequest(
+ url: URL,
+ requestOptions: FetchRequestInit,
+ responseEncoding: BufferEncoding | null
+ ): Promise {
+ const fetcher = this.getFetch();
+ let response: FetchResponse;
+ try {
+ response = await fetcher(url.toString(), requestOptions);
+ } catch (error) {
+ return Promise.reject({
+ response: null,
+ error: this.normalizeFetchError(error),
+ errorResponse: null,
+ });
+ }
+
+ const respBody = await this.readResponseBody(response, responseEncoding);
+ const responseHeaders = this.toHeaderDict(response.headers);
+
+ const httpResponse: HttpResponse = {
+ statusCode: response.status,
+ statusMessage: response.statusText,
+ headers: responseHeaders,
+ body: respBody,
+ };
+
+ if (response.ok) {
+ return {
+ response: httpResponse,
+ body: respBody,
+ };
+ }
+
+ const rejectObject: HttpRejectType = {
+ response: httpResponse,
+ error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
+ errorResponse: null,
+ };
+ let errorResponse = null;
+ try {
+ errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
+ } catch (parseError) {}
+
+ if (errorResponse) {
+ rejectObject.errorResponse = errorResponse;
+ } else {
+ rejectObject.error.message += `. ${respBody}`;
+ }
+
+ return Promise.reject(rejectObject);
+ }
+
+ private async readResponseBody(
+ response: FetchResponse,
+ responseEncoding: BufferEncoding | null
+ ): Promise {
+ const arrayBuffer = await response.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+
+ if (responseEncoding === null) {
+ return buffer;
+ }
+
+ return buffer.toString(responseEncoding);
+ }
+
+ private toHeaderDict(headers: FetchHeaders): NodeJS.Dict {
+ const normalizedHeaders: NodeJS.Dict = {};
+
+ headers.forEach((value, key) => {
+ const existing = normalizedHeaders[key];
+ if (existing === undefined) {
+ normalizedHeaders[key] = value;
+ return;
+ }
+
+ if (Array.isArray(existing)) {
+ existing.push(value);
+ normalizedHeaders[key] = existing;
+ return;
+ }
+
+ normalizedHeaders[key] = [existing, value];
+ });
+
+ return normalizedHeaders;
+ }
+
+ private getFetch(): Fetcher {
+ const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
+ if (!fetcher) {
+ throw new Error('Global fetch API is not available. Please use Node.js 18+.');
+ }
+
+ return fetcher;
+ }
+
+ private normalizeFetchError(error: unknown): Error {
+ if (error instanceof Error) {
+ const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
+ let normalizedCode = mutableError.code;
+
+ if (!normalizedCode) {
+ const cause = mutableError.cause;
+ if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
+ const code = (cause as { code?: string }).code;
+ if (code) {
+ normalizedCode = String(code);
+ }
+ }
+ }
+
+ if (!normalizedCode) {
+ normalizedCode = mutableError.name || 'FETCH_ERROR';
+ }
+
+ try {
+ if (!mutableError.code) {
+ mutableError.code = normalizedCode;
+ }
+ } catch (assignError) {}
+
+ if (mutableError.code) {
+ return mutableError;
+ }
+
+ const wrapped = new Error(mutableError.message);
+ wrapped.name = mutableError.name;
+ (wrapped as { code?: string }).code = normalizedCode;
+ return wrapped;
+ }
+
+ const wrapped = new Error(String(error));
+ (wrapped as { code?: string }).code = 'FETCH_ERROR';
+ return wrapped;
+ }
+}
diff --git a/codegen/Templates/nodejs/multipart.mustache b/codegen/Templates/nodejs/multipart.mustache
new file mode 100644
index 0000000..95f1976
--- /dev/null
+++ b/codegen/Templates/nodejs/multipart.mustache
@@ -0,0 +1,56 @@
+import crypto from 'crypto';
+import { StringKeyWithStringValue } from './httpClient';
+
+export interface FormParamsType extends Array> {}
+
+interface IRequestFile {
+ name: string;
+ filename: string;
+ data: Buffer;
+ contentType?: string;
+}
+
+export class RequestFile implements IRequestFile {
+ constructor(
+ readonly name: string,
+ readonly filename: string,
+ readonly data: Buffer,
+ readonly contentType?: string
+ ) {}
+}
+
+export class Multipart {
+ readonly boundary: string;
+ readonly body: Buffer;
+ readonly headers: StringKeyWithStringValue;
+
+ constructor(textFields: FormParamsType, files?: IRequestFile[]) {
+ const random = crypto.randomUUID();
+ this.boundary = '------------------------' + random.replace(/-/g, '');
+
+ const bodyLines = [];
+ for (const tuple of textFields) {
+ bodyLines.push(`--${this.boundary}`);
+ bodyLines.push(`Content-Disposition: form-data; name="${tuple[0]}"`);
+ bodyLines.push('');
+ bodyLines.push(tuple[1]);
+ }
+ for (const file of files || []) {
+ bodyLines.push(`--${this.boundary}`);
+ bodyLines.push(
+ `Content-Disposition: form-data; name="${file.name}"; filename="${file.filename || 'filename'}"`
+ );
+ bodyLines.push(`Content-Type: ${file.contentType || 'application/octet-stream'}`);
+ bodyLines.push('');
+ bodyLines.push(file.data.toString('binary'));
+ }
+ bodyLines.push(`--${this.boundary}--`);
+
+ this.body = Buffer.from(bodyLines.join('\r\n'), 'binary');
+
+ this.headers = {
+ 'Content-Type': `multipart/form-data; boundary=${this.boundary}`,
+ 'Content-Length': this.body.length.toString(),
+ };
+ }
+}
diff --git a/submodules/node b/submodules/node
index d13cbe9..749366a 160000
--- a/submodules/node
+++ b/submodules/node
@@ -1 +1 @@
-Subproject commit d13cbe939349f8cf1853dbd8f762423f66168504
+Subproject commit 749366aa139a0cbe93308a4d71a098ebd51556f2
From 540d5a6829a4fcbbcd8e1f95644ed10cd2f49988 Mon Sep 17 00:00:00 2001
From: Ivan Kamkin <234-Ivan.Kamkin@users.noreply.git.saltov.dynabic.com>
Date: Tue, 3 Feb 2026 15:32:47 +0500
Subject: [PATCH 2/5] Update node version in readmin and package.json
---
codegen/Templates/nodejs/docs/tsconfig.mustache | 4 ++++
codegen/Templates/nodejs/package.mustache | 2 +-
submodules/node | 2 +-
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/codegen/Templates/nodejs/docs/tsconfig.mustache b/codegen/Templates/nodejs/docs/tsconfig.mustache
index 403750e..3963bc2 100644
--- a/codegen/Templates/nodejs/docs/tsconfig.mustache
+++ b/codegen/Templates/nodejs/docs/tsconfig.mustache
@@ -27,6 +27,10 @@ This repository contains Aspose.BarCode Cloud SDK for Node.js source code.
To use these SDKs, you will need Client Id and Client Secret which can be looked up at [Aspose Cloud Dashboard](https://dashboard.aspose.cloud/applications) (free registration in Aspose Cloud is required for this).
+## Requirements
+
+- Node.js 18 or later (native `fetch` required).
+
## How to use the SDK
The complete source code is available in this repository folder. You can either directly use it in your project via source code or get [nmpjs distribution](https://www.npmjs.com/package/aspose-barcode-cloud-node) (recommended).
diff --git a/codegen/Templates/nodejs/package.mustache b/codegen/Templates/nodejs/package.mustache
index c8111a0..e7a3f10 100644
--- a/codegen/Templates/nodejs/package.mustache
+++ b/codegen/Templates/nodejs/package.mustache
@@ -129,7 +129,7 @@
"dist"
],
"engines": {
- "node": ">=16"
+ "node": ">=18"
},
"scripts": {
"test": "npx jest",
diff --git a/submodules/node b/submodules/node
index 749366a..52b24b5 160000
--- a/submodules/node
+++ b/submodules/node
@@ -1 +1 @@
-Subproject commit 749366aa139a0cbe93308a4d71a098ebd51556f2
+Subproject commit 52b24b504827cb38cb5072edb2ea6bb5c92c03fc
From 06f5c45518fcceb78c491569c1944c5332ff0eac Mon Sep 17 00:00:00 2001
From: Ivan Kamkin <234-Ivan.Kamkin@users.noreply.git.saltov.dynabic.com>
Date: Thu, 5 Feb 2026 11:45:06 +0500
Subject: [PATCH 3/5] Update templates
---
codegen/Templates/nodejs/api.mustache | 248 ++++++++++++++++++-
codegen/Templates/nodejs/httpClient.mustache | 240 ------------------
codegen/Templates/nodejs/multipart.mustache | 3 +-
submodules/node | 2 +-
4 files changed, 245 insertions(+), 248 deletions(-)
delete mode 100644 codegen/Templates/nodejs/httpClient.mustache
diff --git a/codegen/Templates/nodejs/api.mustache b/codegen/Templates/nodejs/api.mustache
index 3561e1e..b00291b 100644
--- a/codegen/Templates/nodejs/api.mustache
+++ b/codegen/Templates/nodejs/api.mustache
@@ -1,5 +1,4 @@
import { Configuration } from './Configuration';
-import { HttpClient, HttpOptions, HttpResponse, HttpResult } from './httpClient';
import { Multipart, RequestFile, FormParamsType } from './multipart';
export * from './models';
@@ -9,6 +8,243 @@ import { {{#models}} {{#model}}{{classname}},{{/model}} {{/models}} } from './mo
import { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} {{operationIdCamelCase}}RequestWrapper, {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} } from './models';
+type StringKeyWithStringValue = Record;
+
+type ApiRequestOptions = {
+ uri: string;
+ body?: any;
+ encoding?: BufferEncoding | null;
+ form?: StringKeyWithStringValue;
+ headers?: StringKeyWithStringValue;
+ json?: boolean;
+ method?: string;
+ qs?: StringKeyWithStringValue;
+};
+
+type ApiResponse = {
+ statusCode: number;
+ statusMessage: string;
+ headers: NodeJS.Dict;
+ body: any;
+};
+
+type ApiResult = {
+ response: ApiResponse;
+ body: T;
+};
+
+type ApiRejectType = {
+ response: ApiResponse | null;
+ errorResponse: ApiErrorResponse | null;
+ error: Error;
+};
+
+interface FetchHeaders {
+ forEach(callback: (value: string, key: string) => void): void;
+}
+
+interface FetchResponse {
+ status: number;
+ statusText: string;
+ headers: FetchHeaders;
+ ok: boolean;
+ arrayBuffer(): Promise;
+}
+
+interface FetchRequestInit {
+ method?: string;
+ headers?: StringKeyWithStringValue;
+ body?: any;
+}
+
+type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise;
+
+export class ApiClient {
+ public requestAsync(options: ApiRequestOptions): Promise {
+ const url: URL = options.qs
+ ? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
+ : new URL(options.uri);
+
+ const requestBody = this.buildRequestBody(options);
+
+ const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
+
+ const requestOptions: FetchRequestInit = {
+ method: options.method || 'GET',
+ headers: options.headers,
+ };
+
+ if (requestBody) {
+ requestOptions.body = requestBody;
+ }
+
+ return this.doFetchRequest(url, requestOptions, responseEncoding);
+ }
+
+ private buildRequestBody(options: ApiRequestOptions) {
+ let requestBody = options.body;
+ if (options.form) {
+ // Override requestBody for form with form content
+ requestBody = new URLSearchParams(options.form).toString();
+ options.headers = Object.assign(
+ {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ options.headers
+ );
+ }
+ if (options.json) {
+ // Override requestBody with JSON value
+ requestBody = JSON.stringify(options.body);
+ options.headers = Object.assign(
+ {
+ 'Content-Type': 'application/json',
+ },
+ options.headers
+ );
+ }
+ return requestBody;
+ }
+
+ private async doFetchRequest(
+ url: URL,
+ requestOptions: FetchRequestInit,
+ responseEncoding: BufferEncoding | null
+ ): Promise {
+ const fetcher = this.getFetch();
+ let response: FetchResponse;
+ try {
+ response = await fetcher(url.toString(), requestOptions);
+ } catch (error) {
+ return Promise.reject({
+ response: null,
+ error: this.normalizeFetchError(error),
+ errorResponse: null,
+ });
+ }
+
+ const respBody = await this.readResponseBody(response, responseEncoding);
+ const responseHeaders = this.toHeaderDict(response.headers);
+
+ const httpResponse: ApiResponse = {
+ statusCode: response.status,
+ statusMessage: response.statusText,
+ headers: responseHeaders,
+ body: respBody,
+ };
+
+ if (response.ok) {
+ return {
+ response: httpResponse,
+ body: respBody,
+ };
+ }
+
+ const rejectObject: ApiRejectType = {
+ response: httpResponse,
+ error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
+ errorResponse: null,
+ };
+ let errorResponse = null;
+ try {
+ errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
+ } catch (parseError) {}
+
+ if (errorResponse) {
+ rejectObject.errorResponse = errorResponse;
+ } else {
+ rejectObject.error.message += `. ${respBody}`;
+ }
+
+ return Promise.reject(rejectObject);
+ }
+
+ private async readResponseBody(
+ response: FetchResponse,
+ responseEncoding: BufferEncoding | null
+ ): Promise {
+ const arrayBuffer = await response.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+
+ if (responseEncoding === null) {
+ return buffer;
+ }
+
+ return buffer.toString(responseEncoding);
+ }
+
+ private toHeaderDict(headers: FetchHeaders): NodeJS.Dict {
+ const normalizedHeaders: NodeJS.Dict = {};
+
+ headers.forEach((value, key) => {
+ const existing = normalizedHeaders[key];
+ if (existing === undefined) {
+ normalizedHeaders[key] = value;
+ return;
+ }
+
+ if (Array.isArray(existing)) {
+ existing.push(value);
+ normalizedHeaders[key] = existing;
+ return;
+ }
+
+ normalizedHeaders[key] = [existing, value];
+ });
+
+ return normalizedHeaders;
+ }
+
+ private getFetch(): Fetcher {
+ const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
+ if (!fetcher) {
+ throw new Error('Global fetch API is not available. Please use Node.js 18+.');
+ }
+
+ return fetcher;
+ }
+
+ private normalizeFetchError(error: unknown): Error {
+ if (error instanceof Error) {
+ const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
+ let normalizedCode = mutableError.code;
+
+ if (!normalizedCode) {
+ const cause = mutableError.cause;
+ if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
+ const code = (cause as { code?: string }).code;
+ if (code) {
+ normalizedCode = String(code);
+ }
+ }
+ }
+
+ if (!normalizedCode) {
+ normalizedCode = mutableError.name || 'FETCH_ERROR';
+ }
+
+ try {
+ if (!mutableError.code) {
+ mutableError.code = normalizedCode;
+ }
+ } catch (assignError) {}
+
+ if (mutableError.code) {
+ return mutableError;
+ }
+
+ const wrapped = new Error(mutableError.message);
+ wrapped.name = mutableError.name;
+ (wrapped as { code?: string }).code = normalizedCode;
+ return wrapped;
+ }
+
+ const wrapped = new Error(String(error));
+ (wrapped as { code?: string }).code = 'FETCH_ERROR';
+ return wrapped;
+ }
+}
+
let primitives = ['string', 'boolean', 'double', 'integer', 'long', 'float', 'number', 'any'];
class ObjectSerializer {
@@ -184,11 +420,11 @@ export class {{classname}} {
'x-aspose-client-version': '{{npmVersion}}'
};
protected _configuration: Configuration;
- private _client: HttpClient;
+ private _client: ApiClient;
constructor(configuration: Configuration) {
this._configuration = configuration;
- this._client = new HttpClient();
+ this._client = new ApiClient();
}
{{#operation}}
@@ -200,7 +436,7 @@ export class {{classname}} {
{{/summary}}
* @param request {{operationIdCamelCase}}RequestWrapper
*/
- public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: HttpResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
+ public async {{nickname}}(request: {{operationIdCamelCase}}RequestWrapper): Promise<{ response: ApiResponse; {{#returnType}}body: {{{returnType}}}; {{/returnType}}{{^returnType}}body?: any; {{/returnType}} }> {
const requestPath =
this._configuration.getApiBaseUrl() +
'{{{path}}}'{{#pathParams}}.replace(
@@ -247,7 +483,7 @@ export class {{classname}} {
}
{{/isFile}}
{{/formParams}}
- const requestOptions: HttpOptions = {
+ const requestOptions: ApiRequestOptions = {
method: '{{httpMethod}}',
qs: queryParameters,
headers: headerParams,
@@ -272,7 +508,7 @@ export class {{classname}} {
await this._configuration.authentication.applyToRequestAsync(requestOptions);
- const result: HttpResult = await this._client.requestAsync(requestOptions);
+ const result: ApiResult = await this._client.requestAsync(requestOptions);
{{#returnType}}
return {
diff --git a/codegen/Templates/nodejs/httpClient.mustache b/codegen/Templates/nodejs/httpClient.mustache
deleted file mode 100644
index 51a28bc..0000000
--- a/codegen/Templates/nodejs/httpClient.mustache
+++ /dev/null
@@ -1,240 +0,0 @@
-import { ApiErrorResponse } from './models';
-
-export interface StringKeyWithStringValue {
- [key: string]: string;
-}
-
-export interface HttpOptions {
- uri: string;
- body?: any;
- encoding?: BufferEncoding | null;
- form?: StringKeyWithStringValue;
- headers?: StringKeyWithStringValue;
- json?: boolean;
- method?: string;
- qs?: StringKeyWithStringValue;
-}
-
-export interface HttpResponse {
- statusCode: number;
- statusMessage: string;
- headers: NodeJS.Dict;
- body: any;
-}
-
-export interface HttpResult {
- response: HttpResponse;
- body: any;
-}
-
-export interface HttpRejectType {
- response: HttpResponse | null;
- errorResponse: ApiErrorResponse | null;
- error: Error;
-}
-
-interface FetchHeaders {
- forEach(callback: (value: string, key: string) => void): void;
-}
-
-interface FetchResponse {
- status: number;
- statusText: string;
- headers: FetchHeaders;
- ok: boolean;
- arrayBuffer(): Promise;
-}
-
-interface FetchRequestInit {
- method?: string;
- headers?: StringKeyWithStringValue;
- body?: any;
-}
-
-type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise;
-
-export class HttpClient {
- public requestAsync(options: HttpOptions): Promise {
- const url: URL = options.qs
- ? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
- : new URL(options.uri);
-
- const requestBody = this.buildRequestBody(options);
-
- const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
-
- const requestOptions: FetchRequestInit = {
- method: options.method || 'GET',
- headers: options.headers,
- };
-
- if (requestBody) {
- requestOptions.body = requestBody;
- }
-
- return this.doFetchRequest(url, requestOptions, responseEncoding);
- }
-
- private buildRequestBody(options: HttpOptions) {
- let requestBody = options.body;
- if (options.form) {
- // Override requestBody for form with form content
- requestBody = new URLSearchParams(options.form).toString();
- options.headers = Object.assign(
- {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- options.headers
- );
- }
- if (options.json) {
- // Override requestBody with JSON value
- requestBody = JSON.stringify(options.body);
- options.headers = Object.assign(
- {
- 'Content-Type': 'application/json',
- },
- options.headers
- );
- }
- return requestBody;
- }
-
- private async doFetchRequest(
- url: URL,
- requestOptions: FetchRequestInit,
- responseEncoding: BufferEncoding | null
- ): Promise {
- const fetcher = this.getFetch();
- let response: FetchResponse;
- try {
- response = await fetcher(url.toString(), requestOptions);
- } catch (error) {
- return Promise.reject({
- response: null,
- error: this.normalizeFetchError(error),
- errorResponse: null,
- });
- }
-
- const respBody = await this.readResponseBody(response, responseEncoding);
- const responseHeaders = this.toHeaderDict(response.headers);
-
- const httpResponse: HttpResponse = {
- statusCode: response.status,
- statusMessage: response.statusText,
- headers: responseHeaders,
- body: respBody,
- };
-
- if (response.ok) {
- return {
- response: httpResponse,
- body: respBody,
- };
- }
-
- const rejectObject: HttpRejectType = {
- response: httpResponse,
- error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
- errorResponse: null,
- };
- let errorResponse = null;
- try {
- errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
- } catch (parseError) {}
-
- if (errorResponse) {
- rejectObject.errorResponse = errorResponse;
- } else {
- rejectObject.error.message += `. ${respBody}`;
- }
-
- return Promise.reject(rejectObject);
- }
-
- private async readResponseBody(
- response: FetchResponse,
- responseEncoding: BufferEncoding | null
- ): Promise {
- const arrayBuffer = await response.arrayBuffer();
- const buffer = Buffer.from(arrayBuffer);
-
- if (responseEncoding === null) {
- return buffer;
- }
-
- return buffer.toString(responseEncoding);
- }
-
- private toHeaderDict(headers: FetchHeaders): NodeJS.Dict {
- const normalizedHeaders: NodeJS.Dict = {};
-
- headers.forEach((value, key) => {
- const existing = normalizedHeaders[key];
- if (existing === undefined) {
- normalizedHeaders[key] = value;
- return;
- }
-
- if (Array.isArray(existing)) {
- existing.push(value);
- normalizedHeaders[key] = existing;
- return;
- }
-
- normalizedHeaders[key] = [existing, value];
- });
-
- return normalizedHeaders;
- }
-
- private getFetch(): Fetcher {
- const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
- if (!fetcher) {
- throw new Error('Global fetch API is not available. Please use Node.js 18+.');
- }
-
- return fetcher;
- }
-
- private normalizeFetchError(error: unknown): Error {
- if (error instanceof Error) {
- const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
- let normalizedCode = mutableError.code;
-
- if (!normalizedCode) {
- const cause = mutableError.cause;
- if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
- const code = (cause as { code?: string }).code;
- if (code) {
- normalizedCode = String(code);
- }
- }
- }
-
- if (!normalizedCode) {
- normalizedCode = mutableError.name || 'FETCH_ERROR';
- }
-
- try {
- if (!mutableError.code) {
- mutableError.code = normalizedCode;
- }
- } catch (assignError) {}
-
- if (mutableError.code) {
- return mutableError;
- }
-
- const wrapped = new Error(mutableError.message);
- wrapped.name = mutableError.name;
- (wrapped as { code?: string }).code = normalizedCode;
- return wrapped;
- }
-
- const wrapped = new Error(String(error));
- (wrapped as { code?: string }).code = 'FETCH_ERROR';
- return wrapped;
- }
-}
diff --git a/codegen/Templates/nodejs/multipart.mustache b/codegen/Templates/nodejs/multipart.mustache
index 95f1976..9342b1a 100644
--- a/codegen/Templates/nodejs/multipart.mustache
+++ b/codegen/Templates/nodejs/multipart.mustache
@@ -1,5 +1,6 @@
import crypto from 'crypto';
-import { StringKeyWithStringValue } from './httpClient';
+
+type StringKeyWithStringValue = Record;
export interface FormParamsType extends Array> {}
diff --git a/submodules/node b/submodules/node
index 52b24b5..efe5331 160000
--- a/submodules/node
+++ b/submodules/node
@@ -1 +1 @@
-Subproject commit 52b24b504827cb38cb5072edb2ea6bb5c92c03fc
+Subproject commit efe533171be332753e5f8bdf09b4866252f0a74d
From 3e455b0259bbf0aa0f37322fe198f1b20bfc47b0 Mon Sep 17 00:00:00 2001
From: Ivan Kamkin <234-Ivan.Kamkin@users.noreply.git.saltov.dynabic.com>
Date: Thu, 5 Feb 2026 13:26:06 +0500
Subject: [PATCH 4/5] Update templates
---
codegen/Templates/nodejs/api.mustache | 61 +++++++--------------
codegen/Templates/nodejs/multipart.mustache | 8 +--
codegen/Templates/nodejs/package.mustache | 1 +
submodules/node | 2 +-
4 files changed, 27 insertions(+), 45 deletions(-)
diff --git a/codegen/Templates/nodejs/api.mustache b/codegen/Templates/nodejs/api.mustache
index b00291b..8be1046 100644
--- a/codegen/Templates/nodejs/api.mustache
+++ b/codegen/Templates/nodejs/api.mustache
@@ -1,5 +1,5 @@
import { Configuration } from './Configuration';
-import { Multipart, RequestFile, FormParamsType } from './multipart';
+import { Multipart, RequestFile, FormParamPairs } from './multipart';
export * from './models';
@@ -8,17 +8,17 @@ import { {{#models}} {{#model}}{{classname}},{{/model}} {{/models}} } from './mo
import { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}} {{operationIdCamelCase}}RequestWrapper, {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} } from './models';
-type StringKeyWithStringValue = Record;
+type StringMap = Record;
type ApiRequestOptions = {
uri: string;
body?: any;
encoding?: BufferEncoding | null;
- form?: StringKeyWithStringValue;
- headers?: StringKeyWithStringValue;
+ form?: StringMap;
+ headers?: StringMap;
json?: boolean;
method?: string;
- qs?: StringKeyWithStringValue;
+ qs?: StringMap;
};
type ApiResponse = {
@@ -39,27 +39,18 @@ type ApiRejectType = {
error: Error;
};
-interface FetchHeaders {
- forEach(callback: (value: string, key: string) => void): void;
-}
-
-interface FetchResponse {
- status: number;
- statusText: string;
- headers: FetchHeaders;
- ok: boolean;
- arrayBuffer(): Promise;
-}
+export class ApiClient {
+ private readonly _fetcher: typeof fetch;
-interface FetchRequestInit {
- method?: string;
- headers?: StringKeyWithStringValue;
- body?: any;
-}
+ constructor() {
+ const resolvedFetch = (globalThis as { fetch?: typeof fetch }).fetch;
+ if (!resolvedFetch) {
+ throw new Error('Global fetch API is not available. Please use Node.js 18+.');
+ }
-type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise;
+ this._fetcher = resolvedFetch;
+ }
-export class ApiClient {
public requestAsync(options: ApiRequestOptions): Promise {
const url: URL = options.qs
? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri)
@@ -69,7 +60,7 @@ export class ApiClient {
const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
- const requestOptions: FetchRequestInit = {
+ const requestOptions: RequestInit = {
method: options.method || 'GET',
headers: options.headers,
};
@@ -108,13 +99,12 @@ export class ApiClient {
private async doFetchRequest(
url: URL,
- requestOptions: FetchRequestInit,
+ requestOptions: RequestInit,
responseEncoding: BufferEncoding | null
): Promise {
- const fetcher = this.getFetch();
- let response: FetchResponse;
+ let response: Response;
try {
- response = await fetcher(url.toString(), requestOptions);
+ response = await this._fetcher(url.toString(), requestOptions);
} catch (error) {
return Promise.reject({
response: null,
@@ -160,7 +150,7 @@ export class ApiClient {
}
private async readResponseBody(
- response: FetchResponse,
+ response: Response,
responseEncoding: BufferEncoding | null
): Promise {
const arrayBuffer = await response.arrayBuffer();
@@ -173,7 +163,7 @@ export class ApiClient {
return buffer.toString(responseEncoding);
}
- private toHeaderDict(headers: FetchHeaders): NodeJS.Dict {
+ private toHeaderDict(headers: Headers): NodeJS.Dict {
const normalizedHeaders: NodeJS.Dict = {};
headers.forEach((value, key) => {
@@ -195,15 +185,6 @@ export class ApiClient {
return normalizedHeaders;
}
- private getFetch(): Fetcher {
- const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
- if (!fetcher) {
- throw new Error('Global fetch API is not available. Please use Node.js 18+.');
- }
-
- return fetcher;
- }
-
private normalizeFetchError(error: unknown): Error {
if (error instanceof Error) {
const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
@@ -447,7 +428,7 @@ export class {{classname}} {
let queryParameters: any = {};
let headerParams: any = (Object as any).assign({}, this.defaultHeaders);
{{#hasFormParams}}
- const formParams: FormParamsType = [];
+ const formParams: FormParamPairs = [];
{{/hasFormParams}}
{{#allParams}}
diff --git a/codegen/Templates/nodejs/multipart.mustache b/codegen/Templates/nodejs/multipart.mustache
index 9342b1a..5a4aaa3 100644
--- a/codegen/Templates/nodejs/multipart.mustache
+++ b/codegen/Templates/nodejs/multipart.mustache
@@ -1,8 +1,8 @@
import crypto from 'crypto';
-type StringKeyWithStringValue = Record;
+type StringMap = Record;
-export interface FormParamsType extends Array> {}
+export interface FormParamPairs extends Array> {}
interface IRequestFile {
name: string;
@@ -23,9 +23,9 @@ export class RequestFile implements IRequestFile {
export class Multipart {
readonly boundary: string;
readonly body: Buffer;
- readonly headers: StringKeyWithStringValue;
+ readonly headers: StringMap;
- constructor(textFields: FormParamsType, files?: IRequestFile[]) {
+ constructor(textFields: FormParamPairs, files?: IRequestFile[]) {
const random = crypto.randomUUID();
this.boundary = '------------------------' + random.replace(/-/g, '');
diff --git a/codegen/Templates/nodejs/package.mustache b/codegen/Templates/nodejs/package.mustache
index e7a3f10..2211d12 100644
--- a/codegen/Templates/nodejs/package.mustache
+++ b/codegen/Templates/nodejs/package.mustache
@@ -133,6 +133,7 @@
},
"scripts": {
"test": "npx jest",
+ "typecheck": "npx tsc --noEmit",
"cover": "npx jest --coverage",
"lint": "npx eslint src test snippets",
"format": "npx eslint src test snippets eslint.config.mjs --fix",
diff --git a/submodules/node b/submodules/node
index efe5331..3768468 160000
--- a/submodules/node
+++ b/submodules/node
@@ -1 +1 @@
-Subproject commit efe533171be332753e5f8bdf09b4866252f0a74d
+Subproject commit 37684682b38610a4f19767bf61d89b219bf544a4
From b3f2e7229e4785621c16aeaa1c9d96e5f990dfe9 Mon Sep 17 00:00:00 2001
From: Ivan Kamkin <234-Ivan.Kamkin@users.noreply.git.saltov.dynabic.com>
Date: Thu, 5 Feb 2026 16:06:08 +0500
Subject: [PATCH 5/5] Set submodule to main
---
submodules/node | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/submodules/node b/submodules/node
index 3768468..48afd98 160000
--- a/submodules/node
+++ b/submodules/node
@@ -1 +1 @@
-Subproject commit 37684682b38610a4f19767bf61d89b219bf544a4
+Subproject commit 48afd98ac89bc4100fc468b5c9b38256bfcd2c45