From 447c8b7bf0b0b69d1d787aaea5028aaa83f8493e Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Mon, 22 Jun 2026 16:29:20 -0400 Subject: [PATCH 1/5] feat(client): support multipart/form-data uploads Add a `form` request option and a `resourceUpload` handler so managers can send multipart bodies via FormData, letting form-data set the Content-Type boundary. JWS signing is skipped for multipart requests. Declares the form-data dependency explicitly. --- package.json | 3 +- src/interfaces/client/requestOptions.ts | 2 ++ src/lib/client.ts | 24 ++++++++++++--- src/lib/resourceHandlers.ts | 39 ++++++++++++++++++++++++- src/spec/mocks/client/axiosInstance.ts | 4 ++- src/spec/mocks/client/response.ts | 5 +++- src/types/index.ts | 1 + src/types/uploadFile.ts | 8 +++++ 8 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 src/types/uploadFile.ts diff --git a/package.json b/package.json index b284590..1f5e04c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "dependencies": { "@types/node": "^22.15.18", - "axios": "^1.10.0" + "axios": "^1.10.0", + "form-data": "^4.0.0" }, "devDependencies": { "@ava/typescript": "^6.0.0", diff --git a/src/interfaces/client/requestOptions.ts b/src/interfaces/client/requestOptions.ts index 9b8a23f..f2cdbee 100644 --- a/src/interfaces/client/requestOptions.ts +++ b/src/interfaces/client/requestOptions.ts @@ -1,4 +1,5 @@ import { Method } from 'axios'; +import FormData from 'form-data'; export interface IRequestOptions { path: string, @@ -7,4 +8,5 @@ export interface IRequestOptions { params?: Record, json?: Record, idempotencyKey?: string, + form?: FormData, } diff --git a/src/lib/client.ts b/src/lib/client.ts index 94a44b7..3c1e3d7 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -1,6 +1,7 @@ import crypto from 'crypto'; import axios, { AxiosInstance } from 'axios'; +import FormData from 'form-data'; import { IConstructorOptions } from '../interfaces/client'; import { IExtendOptions } from '../interfaces/client/extensionOptions'; @@ -56,7 +57,7 @@ export class Client { options: IRequestOptions, ): Promise | AsyncGenerator, void, unknown>> { const { - path, json, paginated = false, method = 'get', params = {}, idempotencyKey, + path, json, paginated = false, method = 'get', params = {}, idempotencyKey, form, } = options; const url = `${this.baseUrl}${path}`; const allParams = { ...this.params, ...params }; @@ -68,6 +69,13 @@ export class Client { params: allParams, }); } + if (form) { + const headers = this.buildHeaders(method, undefined, idempotencyKey, form); + const response = await this.client.request({ + method, url, params: allParams, data: form, headers, + }); + return response.data; + } const rawBody = JSON.stringify(json); const headers = this.buildHeaders(method, rawBody, idempotencyKey); const response = await this.client.request({ @@ -90,22 +98,30 @@ export class Client { private buildHeaders( method: string, - rawBody: string, + rawBody?: string, idempotencyKey?: string, + form?: FormData, ): Record { const headers: Record = { ...this.staticHeaders, ...this.buildJWSHeaders(method, rawBody), }; + if (form) { + // Let FormData set the multipart Content-Type (including the boundary). + delete headers['Content-Type']; + Object.assign(headers, form.getHeaders()); + } + if (method === 'post') { headers['Idempotency-Key'] = idempotencyKey || crypto.randomUUID(); } return headers; } - private buildJWSHeaders(method: string, rawBody: string): Record { - if (this.jws && (method === 'post' || method === 'put' || method === 'patch')) { + private buildJWSHeaders(method: string, rawBody?: string): Record { + // Multipart requests are not signed; skip JWS when there is no raw JSON body. + if (rawBody !== undefined && this.jws && (method === 'post' || method === 'put' || method === 'patch')) { const signature = this.jws.generateHeader(rawBody); return { 'Fintoc-JWS-Signature': signature }; } diff --git a/src/lib/resourceHandlers.ts b/src/lib/resourceHandlers.ts index e9111ec..424bae7 100644 --- a/src/lib/resourceHandlers.ts +++ b/src/lib/resourceHandlers.ts @@ -1,4 +1,6 @@ -import { GenericFunction } from '../types'; +import FormData from 'form-data'; + +import { GenericFunction, UploadFile } from '../types'; import { Client } from './client'; import { objetize, objetizeGenerator } from './utils'; @@ -113,6 +115,41 @@ export async function resourceCreate( ); } +/** + * Upload a file to a resource endpoint through a multipart/form-data request. + * + * @param client - The Client object passed to the retrieved object + * @param path - The path within the API server for the upload + * @param klass - Class that will wrap the instance of the resource + * @param file - The file descriptor to upload under the `file` form field + * @param handlers - The post-request handlers + * @param methods - An array of the methods that the object can execute + * @returns - An object of class `klass` + */ +export async function resourceUpload( + client: Client, + path: string, + klass: any, + file: UploadFile, + handlers: Record = {}, + methods: string[] = [], +): Promise { + const form = new FormData(); + form.append('file', file.data, { + filename: file.filename, + contentType: file.contentType, + }); + const data = await client.request({ path, method: 'put', form }); + return objetize( + klass, + client, + data, + handlers, + methods, + path, + ); +} + /** * Update a specific instance of a resource. * diff --git a/src/spec/mocks/client/axiosInstance.ts b/src/spec/mocks/client/axiosInstance.ts index 6a0c12a..2fa4d1d 100644 --- a/src/spec/mocks/client/axiosInstance.ts +++ b/src/spec/mocks/client/axiosInstance.ts @@ -30,12 +30,14 @@ export class MockAxiosInstance extends Axios { const innerParams = Object.fromEntries(query.map((x) => x.split('='))); const completeParams = { ...innerParams, ...(params === undefined ? {} : params) }; const usableURL = url?.split('//').slice(-1)[0].split('/').slice(1).join('/').split('?')[0] || ''; + const isMultipart = data !== undefined && typeof data !== 'string'; return new MockResponse({ method: usingMethod, baseURL: usingBaseURL, url: usableURL, params: completeParams, - json: JSON.parse(data || '{}'), + json: isMultipart ? {} : JSON.parse(data || '{}'), + multipart: isMultipart, headers: headers || {}, }); } diff --git a/src/spec/mocks/client/response.ts b/src/spec/mocks/client/response.ts index 557fdd1..c5df495 100644 --- a/src/spec/mocks/client/response.ts +++ b/src/spec/mocks/client/response.ts @@ -6,15 +6,17 @@ export class MockResponse { url: string; json: Record | undefined; requestHeaders: Record; + multipart: boolean; constructor(config: Record) { const { - method, baseURL, url, params, json, headers, + method, baseURL, url, params, json, headers, multipart, } = config; this.baseURL = baseURL; this.params = params; this.requestHeaders = headers || {}; + this.multipart = multipart || false; let page; if (method === 'get' && url.charAt(url.length - 1) === 's') { @@ -54,6 +56,7 @@ export class MockResponse { params: this.params, json: this.json, headers: this.requestHeaders, + multipart: this.multipart, }; } diff --git a/src/types/index.ts b/src/types/index.ts index da36e32..df43dae 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ export * from './genericFunction'; export * from './resourceArguments'; +export * from './uploadFile'; diff --git a/src/types/uploadFile.ts b/src/types/uploadFile.ts new file mode 100644 index 0000000..dda7eef --- /dev/null +++ b/src/types/uploadFile.ts @@ -0,0 +1,8 @@ +/** + * Descriptor for a file to be sent through a multipart/form-data request. + */ +export interface UploadFile { + data: Buffer | NodeJS.ReadableStream | Blob; + filename: string; + contentType?: string; +} From c1442b31dcfa44ace60ec5340db319a7ce7a2c01 Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Mon, 22 Jun 2026 16:29:20 -0400 Subject: [PATCH 2/5] feat(v2): add onboarding resource types Onboarding, OnboardingShareholder and OnboardingDocument resources for the v2 entity onboarding response. --- src/lib/resources/v2/index.ts | 3 +++ src/lib/resources/v2/onboarding.ts | 4 ++++ src/lib/resources/v2/onboardingDocument.ts | 4 ++++ src/lib/resources/v2/onboardingShareholder.ts | 4 ++++ 4 files changed, 15 insertions(+) create mode 100644 src/lib/resources/v2/onboarding.ts create mode 100644 src/lib/resources/v2/onboardingDocument.ts create mode 100644 src/lib/resources/v2/onboardingShareholder.ts diff --git a/src/lib/resources/v2/index.ts b/src/lib/resources/v2/index.ts index 4c17868..21b30ec 100644 --- a/src/lib/resources/v2/index.ts +++ b/src/lib/resources/v2/index.ts @@ -12,3 +12,6 @@ export * from './paymentMethod'; export * from './product'; export * from './subscription'; export * from './accountStatement'; +export * from './onboarding'; +export * from './onboardingShareholder'; +export * from './onboardingDocument'; diff --git a/src/lib/resources/v2/onboarding.ts b/src/lib/resources/v2/onboarding.ts new file mode 100644 index 0000000..7a51362 --- /dev/null +++ b/src/lib/resources/v2/onboarding.ts @@ -0,0 +1,4 @@ +import { ResourceMixin } from '../../mixins/resourceMixin'; + +export class Onboarding extends ResourceMixin { +} diff --git a/src/lib/resources/v2/onboardingDocument.ts b/src/lib/resources/v2/onboardingDocument.ts new file mode 100644 index 0000000..249b2bb --- /dev/null +++ b/src/lib/resources/v2/onboardingDocument.ts @@ -0,0 +1,4 @@ +import { ResourceMixin } from '../../mixins/resourceMixin'; + +export class OnboardingDocument extends ResourceMixin { +} diff --git a/src/lib/resources/v2/onboardingShareholder.ts b/src/lib/resources/v2/onboardingShareholder.ts new file mode 100644 index 0000000..4b21737 --- /dev/null +++ b/src/lib/resources/v2/onboardingShareholder.ts @@ -0,0 +1,4 @@ +import { ResourceMixin } from '../../mixins/resourceMixin'; + +export class OnboardingShareholder extends ResourceMixin { +} From a83c1958b19a7e81ca2f552d6c7fc2a5175e0683 Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Mon, 22 Jun 2026 16:29:20 -0400 Subject: [PATCH 3/5] feat(v2): add onboardings manager and expose entity creation Onboardings are nested under entities (fintoc.v2.entities.onboardings), mirroring account_statements: list, get, create, submit, and slot + shareholder document uploads (via resourceUpload, errors mapped to typed Fintoc errors). Also enables fintoc.v2.entities.create. --- src/lib/managers/v2/entitiesManager.ts | 12 ++- src/lib/managers/v2/index.ts | 1 + src/lib/managers/v2/onboardingsManager.ts | 52 ++++++++++ src/spec/integration.spec.ts | 120 ++++++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/lib/managers/v2/onboardingsManager.ts diff --git a/src/lib/managers/v2/entitiesManager.ts b/src/lib/managers/v2/entitiesManager.ts index 947e480..39d021a 100644 --- a/src/lib/managers/v2/entitiesManager.ts +++ b/src/lib/managers/v2/entitiesManager.ts @@ -1,7 +1,17 @@ +import { Client } from '../../client'; import { ManagerMixin } from '../../mixins'; import { Entity } from '../../resources/v2/entity'; +import { OnboardingsManager } from './onboardingsManager'; + export class EntitiesManager extends ManagerMixin { static resource = 'entity'; - static methods = ['list', 'get']; + static methods = ['list', 'get', 'create']; + + onboardings: OnboardingsManager; + + constructor(path: string, client: Client) { + super(path, client); + this.onboardings = new OnboardingsManager('/v2/entities/{entity_id}/onboardings', client); + } } diff --git a/src/lib/managers/v2/index.ts b/src/lib/managers/v2/index.ts index d520808..dccc84d 100644 --- a/src/lib/managers/v2/index.ts +++ b/src/lib/managers/v2/index.ts @@ -13,3 +13,4 @@ export * from './paymentMethodsManager'; export * from './productsManager'; export * from './subscriptionsManager'; export * from './accountStatementsManager'; +export * from './onboardingsManager'; diff --git a/src/lib/managers/v2/onboardingsManager.ts b/src/lib/managers/v2/onboardingsManager.ts new file mode 100644 index 0000000..b995606 --- /dev/null +++ b/src/lib/managers/v2/onboardingsManager.ts @@ -0,0 +1,52 @@ +import { ResourceArguments, UploadFile } from '../../../types'; +import { ManagerMixin } from '../../mixins'; +import { resourceUpload } from '../../resourceHandlers'; +import { Onboarding } from '../../resources/v2/onboarding'; +import { canRaiseHTTPError, getResourceClass } from '../../utils'; + +export class OnboardingsManager extends ManagerMixin { + static resource = 'onboarding'; + static methods = ['list', 'get']; + + create(args?: ResourceArguments): Promise { + const innerArgs = args || {}; + const path = this.buildPath(innerArgs); + return this._create({ path_: path, ...innerArgs }); + } + + submit(id: string, args?: ResourceArguments): Promise { + const innerArgs = args || {}; + const path = `${this.buildPath(innerArgs)}/${id}/submit`; + return this._create({ path_: path, ...innerArgs }); + } + + uploadDocument( + id: string, + slotKey: string, + file: UploadFile, + args?: ResourceArguments, + ): Promise { + const innerArgs = args || {}; + const path = `${this.buildPath(innerArgs)}/${id}/documents/${slotKey}`; + return this.uploadFile(path, file); + } + + uploadShareholderDocument( + id: string, + shareholderId: string, + file: UploadFile, + args?: ResourceArguments, + ): Promise { + const innerArgs = args || {}; + const path = `${this.buildPath(innerArgs)}/${id}/shareholders/${shareholderId}/document`; + return this.uploadFile(path, file); + } + + @canRaiseHTTPError + private async uploadFile(path: string, file: UploadFile): Promise { + const klass = await getResourceClass( + (this.constructor as typeof OnboardingsManager).resource, + ); + return resourceUpload(this._client, path, klass, file); + } +} diff --git a/src/spec/integration.spec.ts b/src/spec/integration.spec.ts index 63e8976..07a4d02 100644 --- a/src/spec/integration.spec.ts +++ b/src/spec/integration.spec.ts @@ -1424,6 +1424,126 @@ test('fintoc.v2.products.create()', async (t) => { t.is(product.json.currency, productData.currency); }); +test('fintoc.v2.entities.onboardings is wired', (t) => { + const ctx: any = t.context; + t.truthy(ctx.fintoc.v2.entities.onboardings); + t.is(typeof ctx.fintoc.v2.entities.onboardings.list, 'function'); + t.is(typeof ctx.fintoc.v2.entities.onboardings.get, 'function'); + t.is(typeof ctx.fintoc.v2.entities.onboardings.create, 'function'); + t.is(typeof ctx.fintoc.v2.entities.onboardings.submit, 'function'); + t.is(typeof ctx.fintoc.v2.entities.onboardings.uploadDocument, 'function'); + t.is(typeof ctx.fintoc.v2.entities.onboardings.uploadShareholderDocument, 'function'); +}); + +test('fintoc.v2.entities.onboardings.list()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardings = await ctx.fintoc.v2.entities.onboardings.list({ + entity_id: entityId, + }); + + let count = 0; + for await (const onboarding of onboardings) { + count += 1; + t.is(onboarding.method, 'get'); + t.is(onboarding.url, `v2/entities/${entityId}/onboardings`); + t.is(onboarding.params.entity_id, entityId); + } + + t.true(count > 0); +}); + +test('fintoc.v2.entities.onboardings.get()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardingId = 'onb_12345'; + const onboarding = await ctx.fintoc.v2.entities.onboardings.get(onboardingId, { + entity_id: entityId, + }); + + t.is(onboarding.method, 'get'); + t.is(onboarding.url, `v2/entities/${entityId}/onboardings/${onboardingId}`); + t.is(onboarding.params.entity_id, entityId); +}); + +test('fintoc.v2.entities.onboardings.create()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardingData = { + entity_id: entityId, + company_information: { legal_name: 'ACME SpA' }, + legal_representative: { first_name: 'Jane', last_name: 'Doe' }, + transactional_profile: { + resource_origins: ['sales'], + monthly_amount_range: '0-1000', + monthly_operations_range: '0-10', + }, + shareholders: [ + { + type: 'natural_person', name: 'Jane', last_name: 'Doe', holder_id: 'h1', nationality: 'CL', percentage: 100, + }, + ], + }; + const onboarding = await ctx.fintoc.v2.entities.onboardings.create(onboardingData); + + t.is(onboarding.method, 'post'); + t.is(onboarding.url, `v2/entities/${entityId}/onboardings`); + t.is(onboarding.json.company_information.legal_name, 'ACME SpA'); + t.is(onboarding.json.legal_representative.first_name, 'Jane'); + t.deepEqual(onboarding.json.transactional_profile.resource_origins, ['sales']); +}); + +test('fintoc.v2.entities.onboardings.submit()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardingId = 'onb_12345'; + const onboarding = await ctx.fintoc.v2.entities.onboardings.submit(onboardingId, { + entity_id: entityId, + }); + + t.is(onboarding.method, 'post'); + t.is(onboarding.url, `v2/entities/${entityId}/onboardings/${onboardingId}/submit`); +}); + +test('fintoc.v2.entities.onboardings.uploadDocument()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardingId = 'onb_12345'; + const slotKey = 'tax_registration_certificate'; + const file = { data: Buffer.from('fake pdf'), filename: 'cert.pdf', contentType: 'application/pdf' }; + const onboarding = await ctx.fintoc.v2.entities.onboardings.uploadDocument( + onboardingId, + slotKey, + file, + { entity_id: entityId }, + ); + + t.is(onboarding.method, 'put'); + t.is(onboarding.url, `v2/entities/${entityId}/onboardings/${onboardingId}/documents/${slotKey}`); + t.true(onboarding.multipart); +}); + +test('fintoc.v2.entities.onboardings.uploadShareholderDocument()', async (t) => { + const ctx: any = t.context; + const entityId = 'ent_12345'; + const onboardingId = 'onb_12345'; + const shareholderId = 'onbsh_abcd'; + const file = { data: Buffer.from('fake pdf'), filename: 'id.pdf', contentType: 'application/pdf' }; + const onboarding = await ctx.fintoc.v2.entities.onboardings.uploadShareholderDocument( + onboardingId, + shareholderId, + file, + { entity_id: entityId }, + ); + + t.is(onboarding.method, 'put'); + t.is( + onboarding.url, + `v2/entities/${entityId}/onboardings/${onboardingId}/shareholders/${shareholderId}/document`, + ); + t.true(onboarding.multipart); +}); + test('fintoc.apiKeys.list()', async (t) => { const ctx: any = t.context; const apiKeys = await ctx.fintoc.apiKeys.list(); From 2c6eb58a140b6d27556889fa02da2a5a0abf1987 Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Mon, 22 Jun 2026 17:10:06 -0400 Subject: [PATCH 4/5] fixup! feat(client): support multipart/form-data uploads --- src/lib/client.ts | 3 ++- src/lib/mixins/managerMixin.ts | 16 +++++++++++++++- src/spec/mocks/client/axiosInstance.ts | 3 ++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lib/client.ts b/src/lib/client.ts index 3c1e3d7..67a82a7 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -108,7 +108,8 @@ export class Client { }; if (form) { - // Let FormData set the multipart Content-Type (including the boundary). + // form-data is sent as a stream, so axios won't set the multipart + // Content-Type itself; replace the default JSON one with the boundary. delete headers['Content-Type']; Object.assign(headers, form.getHeaders()); } diff --git a/src/lib/mixins/managerMixin.ts b/src/lib/mixins/managerMixin.ts index 3200c13..c20e436 100644 --- a/src/lib/mixins/managerMixin.ts +++ b/src/lib/mixins/managerMixin.ts @@ -1,5 +1,5 @@ import { IManagerMixinConstructor, IResourceMixin } from '../../interfaces/mixins'; -import { GenericFunction, ResourceArguments } from '../../types'; +import { GenericFunction, ResourceArguments, UploadFile } from '../../types'; import { Client } from '../client'; import { resourceAll, @@ -7,6 +7,7 @@ import { resourceDelete, resourceGet, resourceUpdate, + resourceUpload, } from '../resourceHandlers'; import { canRaiseHTTPError, getResourceClass } from '../utils'; @@ -171,6 +172,19 @@ export abstract class ManagerMixin { return this.postCreateHandler(object, innerArgs); } + @canRaiseHTTPError + protected async _upload(path: string, file: UploadFile): Promise { + const klass = await getResourceClass(this.#originatingClass.resource); + return resourceUpload( + this._client, + path, + klass, + file, + this.#handlers, + this.#originatingClass.methods, + ); + } + @canRaiseHTTPError protected async _update(identifier: string, args?: ResourceArguments): Promise { const innerArgs = args || {}; diff --git a/src/spec/mocks/client/axiosInstance.ts b/src/spec/mocks/client/axiosInstance.ts index 2fa4d1d..098acd1 100644 --- a/src/spec/mocks/client/axiosInstance.ts +++ b/src/spec/mocks/client/axiosInstance.ts @@ -1,4 +1,5 @@ import { Axios, AxiosRequestConfig } from 'axios'; +import FormData from 'form-data'; import { MockResponse } from './response'; @@ -30,7 +31,7 @@ export class MockAxiosInstance extends Axios { const innerParams = Object.fromEntries(query.map((x) => x.split('='))); const completeParams = { ...innerParams, ...(params === undefined ? {} : params) }; const usableURL = url?.split('//').slice(-1)[0].split('/').slice(1).join('/').split('?')[0] || ''; - const isMultipart = data !== undefined && typeof data !== 'string'; + const isMultipart = data instanceof FormData; return new MockResponse({ method: usingMethod, baseURL: usingBaseURL, From f1c8558e7a8ec8672f15ffe848bb8635963d5fca Mon Sep 17 00:00:00 2001 From: Jose Marchant Date: Mon, 22 Jun 2026 17:10:06 -0400 Subject: [PATCH 5/5] fixup! feat(v2): add onboardings manager and expose entity creation --- src/lib/managers/v2/onboardingsManager.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/lib/managers/v2/onboardingsManager.ts b/src/lib/managers/v2/onboardingsManager.ts index b995606..1bd4f57 100644 --- a/src/lib/managers/v2/onboardingsManager.ts +++ b/src/lib/managers/v2/onboardingsManager.ts @@ -1,8 +1,6 @@ import { ResourceArguments, UploadFile } from '../../../types'; import { ManagerMixin } from '../../mixins'; -import { resourceUpload } from '../../resourceHandlers'; import { Onboarding } from '../../resources/v2/onboarding'; -import { canRaiseHTTPError, getResourceClass } from '../../utils'; export class OnboardingsManager extends ManagerMixin { static resource = 'onboarding'; @@ -28,7 +26,7 @@ export class OnboardingsManager extends ManagerMixin { ): Promise { const innerArgs = args || {}; const path = `${this.buildPath(innerArgs)}/${id}/documents/${slotKey}`; - return this.uploadFile(path, file); + return this._upload(path, file); } uploadShareholderDocument( @@ -39,14 +37,6 @@ export class OnboardingsManager extends ManagerMixin { ): Promise { const innerArgs = args || {}; const path = `${this.buildPath(innerArgs)}/${id}/shareholders/${shareholderId}/document`; - return this.uploadFile(path, file); - } - - @canRaiseHTTPError - private async uploadFile(path: string, file: UploadFile): Promise { - const klass = await getResourceClass( - (this.constructor as typeof OnboardingsManager).resource, - ); - return resourceUpload(this._client, path, klass, file); + return this._upload(path, file); } }