diff --git a/package-lock.json b/package-lock.json
index 324841f..beac9ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -374,7 +374,6 @@
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.4"
@@ -2026,7 +2025,6 @@
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@@ -2706,6 +2704,7 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz",
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -2833,6 +2832,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@@ -3086,8 +3086,7 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.46.0",
@@ -3543,7 +3542,6 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -3940,7 +3938,6 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"json5": "lib/cli.js"
},
@@ -4281,6 +4278,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -4484,6 +4482,7 @@
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -4870,7 +4869,8 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
+ "license": "0BSD",
+ "peer": true
},
"node_modules/tsx": {
"version": "4.20.6",
@@ -4898,6 +4898,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/src/ClientBuilder.js b/src/ClientBuilder.js
index ae30f31..51ff894 100644
--- a/src/ClientBuilder.js
+++ b/src/ClientBuilder.js
@@ -7,6 +7,7 @@ const SharedCredentials = require("./SharedCredentials");
const CustomHeaderSender = require("./CustomHeaderSender");
const StatusCodeSender = require("./StatusCodeSender");
const LicenseSender = require("./LicenseSender");
+const CustomQuerySender = require("@/src/CustomQuerySender");
const BadCredentialsError = require("./Errors").BadCredentialsError;
const RetrySender = require("./RetrySender");
const Sleeper = require("./util/Sleeper.ts");
@@ -39,7 +40,7 @@ const INTERNATIONAL_POSTAL_CODE_API_URL = "https://international-postal-code.api
*/
class ClientBuilder {
constructor(signer) {
- if (noCredentialsProvided()) throw new BadCredentialsError();
+ if (!credentialsProvided()) throw new BadCredentialsError();
this.signer = signer;
this.httpSender = undefined;
@@ -50,15 +51,16 @@ class ClientBuilder {
this.customHeaders = {};
this.debug = undefined;
this.licenses = [];
+ this.customQueries = new Map();
- function noCredentialsProvided() {
- return (!signer) instanceof StaticCredentials || (!signer) instanceof SharedCredentials;
+ function credentialsProvided() {
+ return signer instanceof StaticCredentials || signer instanceof SharedCredentials;
}
}
/**
* @param retries The maximum number of times to retry sending the request to the API. (Default is 5)
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withMaxRetries(retries) {
this.maxRetries = retries;
@@ -68,7 +70,7 @@ class ClientBuilder {
/**
* @param timeout The maximum time (in milliseconds) to wait for a connection, and also to wait for
* the response to be read. (Default is 10000)
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withMaxTimeout(timeout) {
this.maxTimeout = timeout;
@@ -77,7 +79,7 @@ class ClientBuilder {
/**
* @param sender Default is a series of nested senders. See buildSender().
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withSender(sender) {
this.httpSender = sender;
@@ -87,7 +89,7 @@ class ClientBuilder {
/**
* This may be useful when using a local installation of the Smarty APIs.
* @param url Defaults to the URL for the API corresponding to the Client object being built.
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withBaseUrl(url) {
this.baseUrl = url;
@@ -101,7 +103,7 @@ class ClientBuilder {
* @param protocol The protocol on the proxy server to which you wish to connect. If the proxy server uses HTTPS, then you must set the protocol to 'https'.
* @param username The username to login to the proxy.
* @param password The password to login to the proxy.
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withProxy(host, port, protocol, username, password) {
this.proxy = {
@@ -123,35 +125,68 @@ class ClientBuilder {
/**
* Use this to add any additional headers you need.
* @param customHeaders A String to Object Map of header name/value pairs.
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withCustomHeaders(customHeaders) {
this.customHeaders = customHeaders;
-
return this;
}
/**
* Enables debug mode, which will print information about the HTTP request and response to console.log
- * @return Returns this to accommodate method chaining.
+ * @return ClientBuilder this to accommodate method chaining.
*/
withDebug() {
this.debug = true;
-
return this;
}
/**
* Allows the caller to specify the subscription license (aka "track") they wish to use.
* @param licenses A String Array of licenses.
- * @returns Returns this to accommodate method chaining.
+ * @returns ClientBuilder this to accommodate method chaining.
*/
withLicenses(licenses) {
this.licenses = licenses;
+ return this;
+ }
+
+ /**
+ * Allows the caller to specify key and value pair that is added to the request
+ * @param {string} key - The query parameter key
+ * @param {string} value - The query parameter value
+ * @return ClientBuilder this to accommodate method chaining.
+ */
+ withCustomQuery(key, value) {
+ this.customQueries.set(key, value);
+ return this;
+ }
+ /**
+ * Allows the caller to specify key and value pair and appends the value associated with the key, seperated by a comma.
+ * @param {string} key - The query parameter key
+ * @param {string} value - The query parameter value
+ * @return ClientBuilder this to accommodate method chaining.
+ */
+ withCustomCommaSeperatedQuery(key, value) {
+ let values = this.customQueries.get(key);
+ if (values === "") {
+ values = value;
+ } else {
+ values += "," + value;
+ }
+ this.customQueries.set(key, values);
return this;
}
+ /**
+ * Adds to the request query to use the component analysis feature.
+ * @return ClientBuilder this to accommodate method chaining.
+ */
+ withFeatureComponentAnalysis() {
+ return this.withCustomCommaSeperatedQuery("features", "component-analysis");
+ }
+
buildSender() {
if (this.httpSender) return this.httpSender;
@@ -166,8 +201,9 @@ class ClientBuilder {
const customHeaderSender = new CustomHeaderSender(agentSender, this.customHeaders);
const baseUrlSender = new BaseUrlSender(customHeaderSender, this.baseUrl);
const licenseSender = new LicenseSender(baseUrlSender, this.licenses);
+ const customQuerySender = new CustomQuerySender(licenseSender, this.customQueries);
- return licenseSender;
+ return customQuerySender;
}
buildClient(baseUrl, Client) {
diff --git a/src/CustomQuerySender.ts b/src/CustomQuerySender.ts
new file mode 100644
index 0000000..c587aa4
--- /dev/null
+++ b/src/CustomQuerySender.ts
@@ -0,0 +1,20 @@
+import { Request, Response, Sender } from "./types";
+
+export default class CustomQuerySender {
+ private queries: Map;
+ private sender: Sender;
+
+ constructor(innerSender: Sender, queries: Map) {
+ this.queries = queries;
+ this.sender = innerSender;
+ }
+
+ send(request: Request): Promise {
+ this.queries.forEach((value, key) => {
+ const existingValue = request.parameters[key];
+ request.parameters[key] = existingValue ? `${existingValue},${value}` : value;
+ });
+
+ return this.sender.send(request);
+ }
+}
diff --git a/tests/test_CustomQuerySender.ts b/tests/test_CustomQuerySender.ts
new file mode 100644
index 0000000..72b1ef6
--- /dev/null
+++ b/tests/test_CustomQuerySender.ts
@@ -0,0 +1,33 @@
+import { expect } from "chai";
+import CustomQuerySender from "../src/CustomQuerySender";
+import Request from "../src/Request.js";
+import Response from "../src/Response.js";
+import { Sender } from "@/src/types";
+
+describe("A custom query sender", function () {
+ it("adds custom query parameters to the request.", function () {
+ class MockSender implements Sender {
+ request?: Request;
+
+ send = (request: Request): Promise => {
+ this.request = request;
+ return Promise.resolve(new Response(200, {}));
+ };
+ }
+
+ const mockSender = new MockSender();
+ const customQueries = new Map([
+ ["a", "1"],
+ ["b", "2"],
+ ]);
+ const customQuerySender = new CustomQuerySender(mockSender, customQueries);
+ const request = new Request();
+
+ customQuerySender.send(request);
+
+ expect("a" in mockSender.request!.parameters).to.equal(true);
+ expect((mockSender.request!.parameters as Record)["a"]).to.equal("1");
+ expect("b" in mockSender.request!.parameters).to.equal(true);
+ expect((mockSender.request!.parameters as Record)["b"]).to.equal("2");
+ });
+});