@izak0s/mplusqapi-node 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,9 +29,10 @@ npm install @izak0s/mplusqapi-node
29
29
  ```
30
30
 
31
31
  **Runtime dependencies:**
32
- - [`axios`](https://github.com/axios/axios) — HTTP transport
33
32
  - [`fast-xml-parser`](https://github.com/NaturalIntelligence/fast-xml-parser) — XML parsing
34
33
 
34
+ HTTP uses the built-in `node:https` module — no HTTP client dependency.
35
+
35
36
  ---
36
37
 
37
38
  ## Quick Start
@@ -84,7 +85,6 @@ const client = new MplusKassaClient({
84
85
  port: Number(process.env.MPLUS_PORT!),
85
86
  ident: process.env.MPLUS_IDENT!,
86
87
  secret: process.env.MPLUS_SECRET!,
87
- rejectUnauthorized: false, // set to false for self-signed certs (e.g. local test servers)
88
88
  });
89
89
  ```
90
90
 
@@ -95,7 +95,6 @@ const client = new MplusKassaClient({
95
95
  | `timeout` | `30` | Request timeout in seconds |
96
96
  | `maxRetries` | `3` | Retry attempts on retryable transport errors (see below; SOAP faults are never retried) |
97
97
  | `retryDelay` | `500` | Base retry delay in ms, doubled per attempt (exponential backoff with jitter) |
98
- | `rejectUnauthorized` | `true` | Set `false` to accept self-signed TLS certificates |
99
98
  | `timezone` | `'Europe/Amsterdam'` | IANA zone used to interpret/emit the API's wall-clock date structs (see [Dates](#dates)) |
100
99
  | `signal` | — | `AbortSignal` to cancel all in-flight requests from this client (e.g. on shutdown) |
101
100
 
@@ -303,7 +302,7 @@ src/
303
302
  index.ts Public exports
304
303
  errors.ts Error hierarchy
305
304
  soap.ts Envelope builder, response parser, serializers
306
- transport.ts HTTP client (axios)
305
+ transport.ts HTTP client (node:https), retries, error mapping
307
306
  generated/ Auto-generated — do not edit manually
308
307
  types.ts TypeScript interfaces and string union enums
309
308
  serializer.ts TS objects → SOAP XML
package/dist/index.d.mts CHANGED
@@ -9,8 +9,6 @@ interface TransportOptions {
9
9
  maxRetries?: number;
10
10
  /** Base retry delay in ms (exponential backoff). Default: 500. */
11
11
  retryDelay?: number;
12
- /** Disable TLS certificate validation (not recommended in production). */
13
- rejectUnauthorized?: boolean;
14
12
  /**
15
13
  * IANA time zone used to interpret/emit the API's wall-clock date structs.
16
14
  * Default: 'Europe/Amsterdam'. Process-wide — see setTimeZone in soap.ts.
package/dist/index.d.ts CHANGED
@@ -9,8 +9,6 @@ interface TransportOptions {
9
9
  maxRetries?: number;
10
10
  /** Base retry delay in ms (exponential backoff). Default: 500. */
11
11
  retryDelay?: number;
12
- /** Disable TLS certificate validation (not recommended in production). */
13
- rejectUnauthorized?: boolean;
14
12
  /**
15
13
  * IANA time zone used to interpret/emit the API's wall-clock date structs.
16
14
  * Default: 'Europe/Amsterdam'. Process-wide — see setTimeZone in soap.ts.
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -48,7 +38,6 @@ module.exports = __toCommonJS(index_exports);
48
38
  var import_node_crypto = require("crypto");
49
39
 
50
40
  // src/transport.ts
51
- var import_axios = __toESM(require("axios"));
52
41
  var import_node_https = require("https");
53
42
 
54
43
  // src/errors.ts
@@ -246,29 +235,63 @@ function toArray(val) {
246
235
 
247
236
  // src/transport.ts
248
237
  var SAFE_RETRY_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH"]);
238
+ function abortError() {
239
+ const e = new Error("Request aborted");
240
+ e.code = "ABORT_ERR";
241
+ return e;
242
+ }
243
+ var keepAliveAgent = new import_node_https.Agent({ keepAlive: true });
244
+ var nodeHttpsClient = (req) => new Promise((resolve, reject) => {
245
+ const url = new URL(req.url);
246
+ const clientReq = (0, import_node_https.request)(
247
+ {
248
+ method: "POST",
249
+ hostname: url.hostname,
250
+ port: url.port,
251
+ path: url.pathname + url.search,
252
+ agent: keepAliveAgent,
253
+ headers: { ...req.headers, "Content-Length": Buffer.byteLength(req.body) }
254
+ },
255
+ (res) => {
256
+ const chunks = [];
257
+ res.on("data", (c) => chunks.push(c));
258
+ res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") }));
259
+ }
260
+ );
261
+ clientReq.on("error", reject);
262
+ if (req.timeoutMs > 0) {
263
+ clientReq.setTimeout(req.timeoutMs, () => {
264
+ const e = new Error(`Request timed out after ${req.timeoutMs}ms`);
265
+ e.code = "ETIMEDOUT";
266
+ clientReq.destroy(e);
267
+ });
268
+ }
269
+ if (req.signal) {
270
+ if (req.signal.aborted) {
271
+ clientReq.destroy(abortError());
272
+ } else {
273
+ req.signal.addEventListener("abort", () => clientReq.destroy(abortError()), { once: true });
274
+ }
275
+ }
276
+ clientReq.write(req.body);
277
+ clientReq.end();
278
+ });
249
279
  var SoapTransport = class {
250
- constructor(options) {
280
+ constructor(options, httpClient = nodeHttpsClient) {
251
281
  if (options.timezone !== void 0) {
252
282
  setTimeZone(options.timezone);
253
283
  }
254
- const scheme = "https";
255
- this.endpoint = `${scheme}://${options.host}:${options.port}/`;
284
+ const query = new URLSearchParams({ ident: options.ident, secret: options.secret });
285
+ this.url = `https://${options.host}:${options.port}/?${query.toString()}`;
286
+ this.baseHeaders = {
287
+ "Content-Type": "text/xml; charset=utf-8",
288
+ "User-Agent": "mplusqapi-node"
289
+ };
290
+ this.timeoutMs = (options.timeout ?? 30) * 1e3;
256
291
  this.maxRetries = options.maxRetries ?? 3;
257
292
  this.retryDelay = options.retryDelay ?? 500;
258
293
  this.signal = options.signal;
259
- this.http = import_axios.default.create({
260
- baseURL: this.endpoint,
261
- params: { ident: options.ident, secret: options.secret },
262
- headers: {
263
- "Content-Type": "text/xml; charset=utf-8",
264
- "User-Agent": "mplusqapi-node/1.0.0"
265
- },
266
- httpsAgent: options.rejectUnauthorized === false ? new import_node_https.Agent({ rejectUnauthorized: false }) : void 0,
267
- timeout: (options.timeout ?? 30) * 1e3,
268
- // The body is always XML — never let axios JSON-parse it.
269
- responseType: "text",
270
- signal: options.signal
271
- });
294
+ this.httpClient = httpClient;
272
295
  }
273
296
  async send(operationName, xmlRequest, requestId, idempotent = false) {
274
297
  const rid = requestId ?? `mpac_${Date.now()}_${Math.random().toString(36).slice(2)}`;
@@ -301,57 +324,53 @@ var SoapTransport = class {
301
324
  return err.code !== void 0 && SAFE_RETRY_CODES.has(err.code);
302
325
  }
303
326
  async sendOnce(operationName, xmlRequest, requestId) {
304
- const headers = {
305
- "SOAPAction": operationName,
306
- "X-Request-Id": requestId
307
- };
308
- let xmlResponse = "";
327
+ let response;
309
328
  try {
310
- const response = await this.http.post(
311
- this.endpoint,
312
- xmlRequest,
313
- { headers }
314
- );
315
- xmlResponse = response.data;
316
- return xmlResponse;
329
+ response = await this.httpClient({
330
+ url: this.url,
331
+ headers: {
332
+ ...this.baseHeaders,
333
+ "SOAPAction": operationName,
334
+ "X-Request-Id": requestId
335
+ },
336
+ body: xmlRequest,
337
+ timeoutMs: this.timeoutMs,
338
+ signal: this.signal
339
+ });
317
340
  } catch (err) {
318
- if (import_axios.default.isAxiosError(err)) {
319
- xmlResponse = err.response?.data ?? "";
320
- if (err.response) {
321
- try {
322
- const parsed = parseEnvelopeBody(xmlResponse);
323
- if ("fault" in parsed) {
324
- const { faultcode, faultstring } = parsed.fault;
325
- const message = `[${faultcode}] ${operationName}: ${faultstring}`;
326
- if (faultcode.startsWith("Client")) {
327
- throw new MplusApiClientError(message, faultcode, xmlRequest, xmlResponse);
328
- } else if (faultcode.startsWith("Server")) {
329
- throw new MplusApiServerError(message, faultcode, xmlRequest, xmlResponse);
330
- } else {
331
- throw new MplusApiFaultError(message, faultcode, xmlRequest, xmlResponse);
332
- }
333
- }
334
- } catch (parseErr) {
335
- if (parseErr instanceof MplusApiClientError || parseErr instanceof MplusApiServerError || parseErr instanceof MplusApiFaultError) {
336
- throw parseErr;
337
- }
338
- }
339
- throw new MplusApiCommunicationError(
340
- `HTTP ${err.response.status}: ${err.message}`,
341
- xmlRequest,
342
- xmlResponse,
343
- err.code,
344
- err.response.status
345
- );
341
+ const code = err.code;
342
+ const message = err instanceof Error ? err.message : String(err);
343
+ throw new MplusApiCommunicationError(message, xmlRequest, "", code);
344
+ }
345
+ const xmlResponse = response.body;
346
+ if (response.status >= 200 && response.status < 300) {
347
+ return xmlResponse;
348
+ }
349
+ try {
350
+ const parsed = parseEnvelopeBody(xmlResponse);
351
+ if ("fault" in parsed) {
352
+ const { faultcode, faultstring } = parsed.fault;
353
+ const message = `[${faultcode}] ${operationName}: ${faultstring}`;
354
+ if (faultcode.startsWith("Client")) {
355
+ throw new MplusApiClientError(message, faultcode, xmlRequest, xmlResponse);
356
+ } else if (faultcode.startsWith("Server")) {
357
+ throw new MplusApiServerError(message, faultcode, xmlRequest, xmlResponse);
358
+ } else {
359
+ throw new MplusApiFaultError(message, faultcode, xmlRequest, xmlResponse);
346
360
  }
347
- throw new MplusApiCommunicationError(err.message, xmlRequest, xmlResponse, err.code);
348
361
  }
349
- throw new MplusApiCommunicationError(
350
- err instanceof Error ? err.message : String(err),
351
- xmlRequest,
352
- xmlResponse
353
- );
362
+ } catch (parseErr) {
363
+ if (parseErr instanceof MplusApiClientError || parseErr instanceof MplusApiServerError || parseErr instanceof MplusApiFaultError) {
364
+ throw parseErr;
365
+ }
354
366
  }
367
+ throw new MplusApiCommunicationError(
368
+ `HTTP ${response.status}`,
369
+ xmlRequest,
370
+ xmlResponse,
371
+ void 0,
372
+ response.status
373
+ );
355
374
  }
356
375
  };
357
376
 
package/dist/index.mjs CHANGED
@@ -2,8 +2,7 @@
2
2
  import { randomUUID } from "crypto";
3
3
 
4
4
  // src/transport.ts
5
- import axios from "axios";
6
- import { Agent } from "https";
5
+ import { Agent, request as httpsRequest } from "https";
7
6
 
8
7
  // src/errors.ts
9
8
  var MplusApiError = class extends Error {
@@ -200,29 +199,63 @@ function toArray(val) {
200
199
 
201
200
  // src/transport.ts
202
201
  var SAFE_RETRY_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ENOTFOUND", "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH"]);
202
+ function abortError() {
203
+ const e = new Error("Request aborted");
204
+ e.code = "ABORT_ERR";
205
+ return e;
206
+ }
207
+ var keepAliveAgent = new Agent({ keepAlive: true });
208
+ var nodeHttpsClient = (req) => new Promise((resolve, reject) => {
209
+ const url = new URL(req.url);
210
+ const clientReq = httpsRequest(
211
+ {
212
+ method: "POST",
213
+ hostname: url.hostname,
214
+ port: url.port,
215
+ path: url.pathname + url.search,
216
+ agent: keepAliveAgent,
217
+ headers: { ...req.headers, "Content-Length": Buffer.byteLength(req.body) }
218
+ },
219
+ (res) => {
220
+ const chunks = [];
221
+ res.on("data", (c) => chunks.push(c));
222
+ res.on("end", () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") }));
223
+ }
224
+ );
225
+ clientReq.on("error", reject);
226
+ if (req.timeoutMs > 0) {
227
+ clientReq.setTimeout(req.timeoutMs, () => {
228
+ const e = new Error(`Request timed out after ${req.timeoutMs}ms`);
229
+ e.code = "ETIMEDOUT";
230
+ clientReq.destroy(e);
231
+ });
232
+ }
233
+ if (req.signal) {
234
+ if (req.signal.aborted) {
235
+ clientReq.destroy(abortError());
236
+ } else {
237
+ req.signal.addEventListener("abort", () => clientReq.destroy(abortError()), { once: true });
238
+ }
239
+ }
240
+ clientReq.write(req.body);
241
+ clientReq.end();
242
+ });
203
243
  var SoapTransport = class {
204
- constructor(options) {
244
+ constructor(options, httpClient = nodeHttpsClient) {
205
245
  if (options.timezone !== void 0) {
206
246
  setTimeZone(options.timezone);
207
247
  }
208
- const scheme = "https";
209
- this.endpoint = `${scheme}://${options.host}:${options.port}/`;
248
+ const query = new URLSearchParams({ ident: options.ident, secret: options.secret });
249
+ this.url = `https://${options.host}:${options.port}/?${query.toString()}`;
250
+ this.baseHeaders = {
251
+ "Content-Type": "text/xml; charset=utf-8",
252
+ "User-Agent": "mplusqapi-node"
253
+ };
254
+ this.timeoutMs = (options.timeout ?? 30) * 1e3;
210
255
  this.maxRetries = options.maxRetries ?? 3;
211
256
  this.retryDelay = options.retryDelay ?? 500;
212
257
  this.signal = options.signal;
213
- this.http = axios.create({
214
- baseURL: this.endpoint,
215
- params: { ident: options.ident, secret: options.secret },
216
- headers: {
217
- "Content-Type": "text/xml; charset=utf-8",
218
- "User-Agent": "mplusqapi-node/1.0.0"
219
- },
220
- httpsAgent: options.rejectUnauthorized === false ? new Agent({ rejectUnauthorized: false }) : void 0,
221
- timeout: (options.timeout ?? 30) * 1e3,
222
- // The body is always XML — never let axios JSON-parse it.
223
- responseType: "text",
224
- signal: options.signal
225
- });
258
+ this.httpClient = httpClient;
226
259
  }
227
260
  async send(operationName, xmlRequest, requestId, idempotent = false) {
228
261
  const rid = requestId ?? `mpac_${Date.now()}_${Math.random().toString(36).slice(2)}`;
@@ -255,57 +288,53 @@ var SoapTransport = class {
255
288
  return err.code !== void 0 && SAFE_RETRY_CODES.has(err.code);
256
289
  }
257
290
  async sendOnce(operationName, xmlRequest, requestId) {
258
- const headers = {
259
- "SOAPAction": operationName,
260
- "X-Request-Id": requestId
261
- };
262
- let xmlResponse = "";
291
+ let response;
263
292
  try {
264
- const response = await this.http.post(
265
- this.endpoint,
266
- xmlRequest,
267
- { headers }
268
- );
269
- xmlResponse = response.data;
270
- return xmlResponse;
293
+ response = await this.httpClient({
294
+ url: this.url,
295
+ headers: {
296
+ ...this.baseHeaders,
297
+ "SOAPAction": operationName,
298
+ "X-Request-Id": requestId
299
+ },
300
+ body: xmlRequest,
301
+ timeoutMs: this.timeoutMs,
302
+ signal: this.signal
303
+ });
271
304
  } catch (err) {
272
- if (axios.isAxiosError(err)) {
273
- xmlResponse = err.response?.data ?? "";
274
- if (err.response) {
275
- try {
276
- const parsed = parseEnvelopeBody(xmlResponse);
277
- if ("fault" in parsed) {
278
- const { faultcode, faultstring } = parsed.fault;
279
- const message = `[${faultcode}] ${operationName}: ${faultstring}`;
280
- if (faultcode.startsWith("Client")) {
281
- throw new MplusApiClientError(message, faultcode, xmlRequest, xmlResponse);
282
- } else if (faultcode.startsWith("Server")) {
283
- throw new MplusApiServerError(message, faultcode, xmlRequest, xmlResponse);
284
- } else {
285
- throw new MplusApiFaultError(message, faultcode, xmlRequest, xmlResponse);
286
- }
287
- }
288
- } catch (parseErr) {
289
- if (parseErr instanceof MplusApiClientError || parseErr instanceof MplusApiServerError || parseErr instanceof MplusApiFaultError) {
290
- throw parseErr;
291
- }
292
- }
293
- throw new MplusApiCommunicationError(
294
- `HTTP ${err.response.status}: ${err.message}`,
295
- xmlRequest,
296
- xmlResponse,
297
- err.code,
298
- err.response.status
299
- );
305
+ const code = err.code;
306
+ const message = err instanceof Error ? err.message : String(err);
307
+ throw new MplusApiCommunicationError(message, xmlRequest, "", code);
308
+ }
309
+ const xmlResponse = response.body;
310
+ if (response.status >= 200 && response.status < 300) {
311
+ return xmlResponse;
312
+ }
313
+ try {
314
+ const parsed = parseEnvelopeBody(xmlResponse);
315
+ if ("fault" in parsed) {
316
+ const { faultcode, faultstring } = parsed.fault;
317
+ const message = `[${faultcode}] ${operationName}: ${faultstring}`;
318
+ if (faultcode.startsWith("Client")) {
319
+ throw new MplusApiClientError(message, faultcode, xmlRequest, xmlResponse);
320
+ } else if (faultcode.startsWith("Server")) {
321
+ throw new MplusApiServerError(message, faultcode, xmlRequest, xmlResponse);
322
+ } else {
323
+ throw new MplusApiFaultError(message, faultcode, xmlRequest, xmlResponse);
300
324
  }
301
- throw new MplusApiCommunicationError(err.message, xmlRequest, xmlResponse, err.code);
302
325
  }
303
- throw new MplusApiCommunicationError(
304
- err instanceof Error ? err.message : String(err),
305
- xmlRequest,
306
- xmlResponse
307
- );
326
+ } catch (parseErr) {
327
+ if (parseErr instanceof MplusApiClientError || parseErr instanceof MplusApiServerError || parseErr instanceof MplusApiFaultError) {
328
+ throw parseErr;
329
+ }
308
330
  }
331
+ throw new MplusApiCommunicationError(
332
+ `HTTP ${response.status}`,
333
+ xmlRequest,
334
+ xmlResponse,
335
+ void 0,
336
+ response.status
337
+ );
309
338
  }
310
339
  };
311
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izak0s/mplusqapi-node",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "TypeScript client for the MplusKASSA SOAP API",
5
5
  "homepage": "https://github.com/izak0s/mplusqapi-node#readme",
6
6
  "bugs": {
@@ -57,7 +57,6 @@
57
57
  "pack:dry-run": "npm pack --dry-run"
58
58
  },
59
59
  "dependencies": {
60
- "axios": "^1.7.2",
61
60
  "fast-xml-parser": "^5.8.0"
62
61
  },
63
62
  "overrides": {