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