diff --git a/package-lock.json b/package-lock.json index d309843b..6d4ae95d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "axios": "^1.7.2", "axios-ntlm": "^1.4.2", "debug": "^4.3.5", + "deepmerge-ts": "^7.1.0", "formidable": "^3.5.1", "get-stream": "^6.0.1", - "lodash": "^4.17.21", "sax": "^1.4.1", "strip-bom": "^3.0.0", "whatwg-mimetype": "4.0.0", @@ -1781,6 +1781,14 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -3433,7 +3441,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/lodash.flattendeep": { "version": "4.4.0", @@ -7693,6 +7702,11 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true }, + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==" + }, "default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -8912,7 +8926,8 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "lodash.flattendeep": { "version": "4.4.0", diff --git a/package.json b/package.json index 2000f9e8..0dbe6af6 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "axios": "^1.7.2", "axios-ntlm": "^1.4.2", "debug": "^4.3.5", + "deepmerge-ts": "^7.1.0", "formidable": "^3.5.1", "get-stream": "^6.0.1", - "lodash": "^4.17.21", "sax": "^1.4.1", "strip-bom": "^3.0.0", "whatwg-mimetype": "4.0.0", diff --git a/src/client.ts b/src/client.ts index 7424a6d0..a55dad55 100644 --- a/src/client.ts +++ b/src/client.ts @@ -9,7 +9,6 @@ import { randomUUID } from 'crypto'; import debugBuilder from 'debug'; import { EventEmitter } from 'events'; import getStream = require('get-stream'); -import * as _ from 'lodash'; import { HttpClient } from './http'; import { IHeaders, IHttpClient, IMTOMAttachments, IOptions, ISecurity, SoapMethod, SoapMethodAsync } from './types'; import { findPrefix } from './utils'; @@ -368,7 +367,7 @@ export class Client extends EventEmitter { if (!output || !output.$lookupTypes) { debug('Response element is not present. Unable to convert response xml to json.'); // If the response is JSON then return it as-is. - const json = _.isObject(body) ? body : tryJSONparse(body); + const json = body !== null && typeof body === 'object' ? body : tryJSONparse(body); if (json) { return callback(null, response, json, undefined, xml); } @@ -482,7 +481,13 @@ export class Client extends EventEmitter { }; if (this.streamAllowed && typeof this.httpClient.requestStream === 'function') { - callback = _.once(callback); + callback = (cb) => { + let lastResult; + return (...args) => { + if (lastResult === undefined) { lastResult = cb(...args); } + return lastResult; + }; + }; const startTime = Date.now(); const onError = (err) => { this.lastResponse = null; diff --git a/src/security/BasicAuthSecurity.ts b/src/security/BasicAuthSecurity.ts index fafa7f73..f9e798bb 100644 --- a/src/security/BasicAuthSecurity.ts +++ b/src/security/BasicAuthSecurity.ts @@ -1,5 +1,5 @@ -import * as _ from 'lodash'; +import { deepmergeInto } from 'deepmerge-ts'; import { IHeaders, ISecurity } from '../types'; export class BasicAuthSecurity implements ISecurity { @@ -11,7 +11,7 @@ export class BasicAuthSecurity implements ISecurity { this._username = username; this._password = password; this.defaults = {}; - _.merge(this.defaults, defaults); + deepmergeInto(this.defaults, defaults); } public addHeaders(headers: IHeaders): void { @@ -23,6 +23,6 @@ export class BasicAuthSecurity implements ISecurity { } public addOptions(options: any): void { - _.merge(options, this.defaults); + deepmergeInto(options, this.defaults); } } diff --git a/src/security/BearerSecurity.ts b/src/security/BearerSecurity.ts index d7619c66..fafde2a8 100644 --- a/src/security/BearerSecurity.ts +++ b/src/security/BearerSecurity.ts @@ -1,5 +1,5 @@ -import * as _ from 'lodash'; +import { deepmergeInto } from 'deepmerge-ts'; import { IHeaders, ISecurity } from '../types'; export class BearerSecurity implements ISecurity { @@ -9,7 +9,7 @@ export class BearerSecurity implements ISecurity { constructor(token: string, defaults?: any) { this._token = token; this.defaults = {}; - _.merge(this.defaults, defaults); + deepmergeInto(this.defaults, defaults); } public addHeaders(headers: IHeaders): void { @@ -21,6 +21,6 @@ export class BearerSecurity implements ISecurity { } public addOptions(options: any): void { - _.merge(options, this.defaults); + deepmergeInto(options, this.defaults); } } diff --git a/src/security/ClientSSLSecurity.ts b/src/security/ClientSSLSecurity.ts index 4b05a233..9cb64e4f 100644 --- a/src/security/ClientSSLSecurity.ts +++ b/src/security/ClientSSLSecurity.ts @@ -1,6 +1,6 @@ +import { deepmergeInto } from 'deepmerge-ts'; import * as fs from 'fs'; import * as https from 'https'; -import * as _ from 'lodash'; import { ISecurity } from '../types'; /** @@ -53,7 +53,7 @@ export class ClientSSLSecurity implements ISecurity { } this.defaults = {}; - _.merge(this.defaults, defaults); + deepmergeInto(this.defaults, defaults); this.agent = null; } @@ -68,7 +68,7 @@ export class ClientSSLSecurity implements ISecurity { options.key = this.key; options.cert = this.cert; options.ca = this.ca; - _.merge(options, this.defaults); + deepmergeInto(options, this.defaults); if (!!options.forever) { if (!this.agent) { diff --git a/src/security/ClientSSLSecurityPFX.ts b/src/security/ClientSSLSecurityPFX.ts index 0c0aef3f..a5ddfb90 100644 --- a/src/security/ClientSSLSecurityPFX.ts +++ b/src/security/ClientSSLSecurityPFX.ts @@ -1,7 +1,7 @@ +import { deepmergeInto } from 'deepmerge-ts'; import * as fs from 'fs'; import * as https from 'https'; -import * as _ from 'lodash'; import { ISecurity } from '../types'; /** @@ -39,7 +39,7 @@ export class ClientSSLSecurityPFX implements ISecurity { } } this.defaults = {}; - _.merge(this.defaults, defaults); + deepmergeInto(this.defaults, defaults); } public toXML(): string { @@ -51,7 +51,7 @@ export class ClientSSLSecurityPFX implements ISecurity { if (this.passphrase) { options.passphrase = this.passphrase; } - _.merge(options, this.defaults); + deepmergeInto(options, this.defaults); options.httpsAgent = new https.Agent(options); } } diff --git a/src/security/NTLMSecurity.ts b/src/security/NTLMSecurity.ts index b09a3abc..510f1464 100644 --- a/src/security/NTLMSecurity.ts +++ b/src/security/NTLMSecurity.ts @@ -1,5 +1,5 @@ -import * as _ from 'lodash'; +import { deepmergeInto } from 'deepmerge-ts'; import { IHeaders, ISecurity } from '../types'; export class NTLMSecurity implements ISecurity { @@ -30,6 +30,6 @@ export class NTLMSecurity implements ISecurity { } public addOptions(options: any): void { - _.merge(options, this.defaults); + deepmergeInto(options, this.defaults); } } diff --git a/src/wsdl/elements.ts b/src/wsdl/elements.ts index a867ca3a..900a0f04 100644 --- a/src/wsdl/elements.ts +++ b/src/wsdl/elements.ts @@ -1,7 +1,7 @@ import { ok as assert } from 'assert'; import debugBuilder from 'debug'; -import * as _ from 'lodash'; +import { deepmergeInto, deepmergeIntoCustom } from 'deepmerge-ts'; import { IWsdlBaseOptions } from '../types'; import { splitQName, TNS_PREFIX } from '../utils'; @@ -146,7 +146,13 @@ export class Element { } const parent = stack[stack.length - 2]; if (this !== stack[0]) { - _.defaultsDeep(stack[0].xmlns, this.xmlns); + const defaultsDeep = deepmergeIntoCustom({ + mergeOthers: (target, values) => { + // Multiple values means value was already present ⇒ keeping original + if (values.length > 1) { target.value = values[0]; } + }, + }); + defaultsDeep(stack[0].xmlns, this.xmlns); // delete this.xmlns; parent.children.push(this); parent.addChild(this); @@ -444,7 +450,7 @@ export class ExtensionElement extends Element { ); if (typeElement) { const base = typeElement.description(definitions, schema.xmlns); - desc = typeof base === 'string' ? base : _.defaults(base, desc); + desc = typeof base === 'string' ? base : Object.assign(base, desc); } } } @@ -791,15 +797,15 @@ export class SchemaElement extends Element { public merge(source: SchemaElement) { assert(source instanceof SchemaElement); - _.merge(this.complexTypes, source.complexTypes); - _.merge(this.types, source.types); - _.merge(this.elements, source.elements); - _.merge(this.xmlns, source.xmlns); + deepmergeInto(this.complexTypes, source.complexTypes); + deepmergeInto(this.types, source.types); + deepmergeInto(this.elements, source.elements); + deepmergeInto(this.xmlns, source.xmlns); // Merge attributes from source without overwriting our's - _.merge(this, _.pickBy(source, (value, key) => { - return key.startsWith('$') && !this.hasOwnProperty(key); - })); + deepmergeInto(this, + Object.fromEntries(Object.entries(source).filter(([key]) => key.startsWith('$') && !this.hasOwnProperty(key))), + ); return this; } @@ -1115,7 +1121,7 @@ export class DefinitionsElement extends Element { public addChild(child) { if (child instanceof TypesElement) { // Merge types.schemas into definitions.schemas - _.merge(this.schemas, child.schemas); + deepmergeInto(this.schemas, child.schemas); } else if (child instanceof MessageElement) { this.messages[child.$name] = child; } else if (child.name === 'import') { diff --git a/src/wsdl/index.ts b/src/wsdl/index.ts index 7c6a6680..ed2d0ed8 100644 --- a/src/wsdl/index.ts +++ b/src/wsdl/index.ts @@ -7,8 +7,8 @@ import { ok as assert } from 'assert'; import debugBuilder from 'debug'; +import { deepmergeCustom, deepmergeIntoCustom } from 'deepmerge-ts'; import * as fs from 'fs'; -import * as _ from 'lodash'; import * as path from 'path'; import * as sax from 'sax'; import * as stripBom from 'strip-bom'; @@ -27,12 +27,6 @@ export function trim(text) { return text.trim(); } -function deepMerge(destination: A, source: B): A & B { - return _.mergeWith(destination, source, (a, b) => { - return Array.isArray(a) ? a.concat(b) : undefined; - }); -} - function appendColon(ns: string): string { return (ns && ns.charAt(ns.length - 1) !== ':') ? ns + ':' : ns; } @@ -366,7 +360,7 @@ export class WSDL { } } - if (_.isPlainObject(obj) && !Object.keys(obj).length) { + if (obj !== undefined && typeof obj === 'object' && !Object.keys(obj).length) { obj = null; } @@ -1226,9 +1220,12 @@ export class WSDL { this._includesWsdl.push(wsdl); if (wsdl.definitions instanceof elements.DefinitionsElement) { - _.mergeWith(this.definitions, wsdl.definitions, (a, b) => { - return (a instanceof elements.SchemaElement) ? a.merge(b) : undefined; + const mergeFunction = deepmergeCustom({ + mergeOthers: (values, utils) => { + return (values[0] instanceof elements.SchemaElement) ? utils.defaultMergeFunctions.mergeOthers(values) : undefined; + }, }); + this.definitions = mergeFunction(this.definitions, wsdl.definitions); } else { return callback(new Error('wsdl.defintions is not an instance of elements.DefinitionsElement')); } diff --git a/test/client-test.js b/test/client-test.js index 5dd3c67c..0fc61843 100644 --- a/test/client-test.js +++ b/test/client-test.js @@ -5,7 +5,6 @@ var fs = require('fs'), http = require('http'), stream = require('stream'), assert = require('assert'), - _ = require('lodash'), sinon = require('sinon'), wsdl = require('../lib/wsdl'); @@ -628,9 +627,9 @@ var fs = require('fs'), assert.ifError(error); const contentTypeSplit = client.lastRequestHeaders['Content-Type'].split(';'); - + assert.equal(contentTypeSplit[0], 'multipart/related'); - assert.ok(contentTypeSplit.filter(function(e) { return e.trim().startsWith('type=') }).length === 1); + assert.ok(contentTypeSplit.filter(function (e) { return e.trim().startsWith('type=') }).length === 1); done(); }, { forceMTOM: true }) @@ -1210,8 +1209,7 @@ var fs = require('fs'), it('shall generate correct payload for methods with array parameter', function (done) { soap.createClient(__dirname + '/wsdl/list_parameter.wsdl', function (err, client) { assert.ok(client); - var pathToArrayContainer = 'TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList'; - var arrayParameter = _.get(client.describe(), pathToArrayContainer)['PeriodType[]']; + var arrayParameter = client.describe().TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList['PeriodType[]']; assert.ok(arrayParameter); client.AddTimesheet({ input: { PeriodList: { PeriodType: [{ PeriodId: '1' }] } } }, function () { var sentInputContent = client.lastRequest.substring(client.lastRequest.indexOf('') + ''.length, client.lastRequest.indexOf('')); @@ -1224,8 +1222,7 @@ var fs = require('fs'), it('shall generate correct payload for methods with array parameter with colon override', function (done) { soap.createClient(__dirname + '/wsdl/array_namespace_override.wsdl', function (err, client) { assert.ok(client); - var pathToArrayContainer = 'SampleArrayServiceImplService.SampleArrayServiceImplPort.createWebOrder.input.order'; - var arrayParameter = _.get(client.describe(), pathToArrayContainer)['orderDetails[]']; + var arrayParameter = client.describe().SampleArrayServiceImplService.SampleArrayServiceImplPort.createWebOrder.input.order['orderDetails[]']; assert.ok(arrayParameter); const input = { ':clientId': 'test', @@ -1247,8 +1244,7 @@ var fs = require('fs'), it('shall generate correct payload for methods with array parameter with parent namespace', function (done) { soap.createClient(__dirname + '/wsdl/array_namespace_override.wsdl', function (err, client) { assert.ok(client); - var pathToArrayContainer = 'SampleArrayServiceImplService.SampleArrayServiceImplPort.createWebOrder.input.order'; - var arrayParameter = _.get(client.describe(), pathToArrayContainer)['orderDetails[]']; + var arrayParameter = client.describe().SampleArrayServiceImplService.SampleArrayServiceImplPort.createWebOrder.input.order['orderDetails[]']; assert.ok(arrayParameter); const input = { ':clientId': 'test', @@ -1271,8 +1267,7 @@ var fs = require('fs'), // used for servers that cannot aggregate individually namespaced array elements soap.createClient(__dirname + '/wsdl/list_parameter.wsdl', { disableCache: true, namespaceArrayElements: false }, function (err, client) { assert.ok(client); - var pathToArrayContainer = 'TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList'; - var arrayParameter = _.get(client.describe(), pathToArrayContainer)['PeriodType[]']; + var arrayParameter = client.describe().TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList['PeriodType[]']; assert.ok(arrayParameter); client.AddTimesheet({ input: { PeriodList: { PeriodType: [{ PeriodId: '1' }, { PeriodId: '2' }] } } }, function () { var sentInputContent = client.lastRequest.substring(client.lastRequest.indexOf('') + ''.length, client.lastRequest.indexOf('')); @@ -1287,8 +1282,7 @@ var fs = require('fs'), soap.createClient(__dirname + '/wsdl/list_parameter.wsdl', { disableCache: true, namespaceArrayElements: true }, function (err, client) { assert.ok(client); assert.ok(client.wsdl.options.namespaceArrayElements === true); - var pathToArrayContainer = 'TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList'; - var arrayParameter = _.get(client.describe(), pathToArrayContainer)['PeriodType[]']; + var arrayParameter = client.describe().TimesheetV201511Mobile.TimesheetV201511MobileSoap.AddTimesheet.input.input.PeriodList['PeriodType[]']; assert.ok(arrayParameter); client.AddTimesheet({ input: { PeriodList: { PeriodType: [{ PeriodId: '1' }, { PeriodId: '2' }] } } }, function () { var sentInputContent = client.lastRequest.substring(client.lastRequest.indexOf('') + ''.length, client.lastRequest.indexOf('')); @@ -1474,7 +1468,7 @@ var fs = require('fs'), soap.createClient(__dirname + '/wsdl/default_namespace.wsdl', Object.assign({ envelopeSoapUrl: 'http://example.com/v1' }, meta.options), function (err, client) { assert.ok(client); assert.ifError(err); - + client.MyOperation({}, function (err, result) { assert.notEqual(client.lastRequest.indexOf('xmlns:soap=\"http://example.com/v1\"'), -1); done(); @@ -1684,10 +1678,10 @@ xit('should add namespace to array of objects', function (done) { done(); }); }) - .catch(function (err) { + .catch(function (err) { assert.equal(err.message, 'Root element of WSDL was . This is likely an authentication issue.'); done(); - }); + }); }); @@ -1767,7 +1761,7 @@ describe('Client posting complex body', () => { return void done(err); } assert.ok(client); - + var requestBody = { id: 'ID00000000000000000000000000000000', lastName: 'Doe', @@ -1786,7 +1780,7 @@ describe('Client posting complex body', () => { companyName: 'ACME' } } - + client.registerUser(requestBody, function (err, result) { assert.ok(client.lastRequest); assert.ok(client.lastMessage); @@ -1795,7 +1789,7 @@ describe('Client posting complex body', () => { console.log(client.lastMessage); const expectedBody = 'ID00000000000000000000000000000000DoeJohn1970-01-01ENGjdoe@doe.comALLOWEDStreetCodeCityUSACME'; assert.strictEqual(client.lastMessage, expectedBody); - + done(); }); }, baseUrl); diff --git a/tsconfig.json b/tsconfig.json index e5d30d5d..72e02ca2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ "outDir": "lib", "sourceMap": true, "declaration": true, - "stripInternal": true + "stripInternal": true, + "skipLibCheck": true }, "include": [ "src/**/*" @@ -15,4 +16,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +}