ksef-client-ts 0.6.0 → 0.6.2

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/dist/cli.js CHANGED
@@ -97,12 +97,14 @@ var init_ksef_unauthorized_error = __esm({
97
97
  detail;
98
98
  traceId;
99
99
  instance;
100
+ timestamp;
100
101
  constructor(problemDetails) {
101
102
  super(problemDetails.detail || "Unauthorized");
102
103
  this.name = "KSeFUnauthorizedError";
103
104
  this.detail = problemDetails.detail;
104
105
  this.traceId = problemDetails.traceId;
105
106
  this.instance = problemDetails.instance;
107
+ this.timestamp = problemDetails.timestamp;
106
108
  }
107
109
  };
108
110
  }
@@ -121,6 +123,7 @@ var init_ksef_forbidden_error = __esm({
121
123
  instance;
122
124
  security;
123
125
  traceId;
126
+ timestamp;
124
127
  constructor(problemDetails) {
125
128
  super(problemDetails.detail || "Forbidden");
126
129
  this.name = "KSeFForbiddenError";
@@ -129,6 +132,7 @@ var init_ksef_forbidden_error = __esm({
129
132
  this.instance = problemDetails.instance;
130
133
  this.security = problemDetails.security;
131
134
  this.traceId = problemDetails.traceId;
135
+ this.timestamp = problemDetails.timestamp;
132
136
  }
133
137
  };
134
138
  }
@@ -219,6 +223,69 @@ var init_config = __esm({
219
223
  }
220
224
  });
221
225
 
226
+ // src/errors/ksef-gone-error.ts
227
+ var KSeFGoneError;
228
+ var init_ksef_gone_error = __esm({
229
+ "src/errors/ksef-gone-error.ts"() {
230
+ "use strict";
231
+ init_ksef_error();
232
+ KSeFGoneError = class extends KSeFError {
233
+ statusCode = 410;
234
+ detail;
235
+ instance;
236
+ traceId;
237
+ timestamp;
238
+ constructor(problemDetails) {
239
+ super(problemDetails.detail || "Operation status no longer available (retention expired)");
240
+ this.name = "KSeFGoneError";
241
+ this.detail = problemDetails.detail;
242
+ this.instance = problemDetails.instance;
243
+ this.traceId = problemDetails.traceId;
244
+ this.timestamp = problemDetails.timestamp;
245
+ }
246
+ };
247
+ }
248
+ });
249
+
250
+ // src/errors/error-codes.ts
251
+ function hasErrorCode(body, code) {
252
+ return !!body?.exception?.exceptionDetailList?.some((d) => d.exceptionCode === code);
253
+ }
254
+ var KSeFErrorCode;
255
+ var init_error_codes = __esm({
256
+ "src/errors/error-codes.ts"() {
257
+ "use strict";
258
+ KSeFErrorCode = {
259
+ BatchTimeout: 21208,
260
+ DuplicateInvoice: 440
261
+ };
262
+ }
263
+ });
264
+
265
+ // src/errors/ksef-batch-timeout-error.ts
266
+ var KSeFBatchTimeoutError;
267
+ var init_ksef_batch_timeout_error = __esm({
268
+ "src/errors/ksef-batch-timeout-error.ts"() {
269
+ "use strict";
270
+ init_ksef_api_error();
271
+ init_error_codes();
272
+ KSeFBatchTimeoutError = class _KSeFBatchTimeoutError extends KSeFApiError {
273
+ errorCode = KSeFErrorCode.BatchTimeout;
274
+ constructor(message, statusCode, errorResponse) {
275
+ super(message, statusCode, errorResponse);
276
+ this.name = "KSeFBatchTimeoutError";
277
+ }
278
+ static fromResponse(statusCode, body) {
279
+ const detail = body?.exception?.exceptionDetailList?.find(
280
+ (d) => d.exceptionCode === KSeFErrorCode.BatchTimeout
281
+ );
282
+ const message = detail?.exceptionDescription?.trim() || "Batch session timed out before the server completed processing (KSeF 21208).";
283
+ return new _KSeFBatchTimeoutError(message, statusCode, body);
284
+ }
285
+ };
286
+ }
287
+ });
288
+
222
289
  // src/http/route-builder.ts
223
290
  var RouteBuilder;
224
291
  var init_route_builder = __esm({
@@ -396,6 +463,9 @@ var init_rest_client = __esm({
396
463
  init_ksef_rate_limit_error();
397
464
  init_ksef_unauthorized_error();
398
465
  init_ksef_forbidden_error();
466
+ init_ksef_gone_error();
467
+ init_ksef_batch_timeout_error();
468
+ init_error_codes();
399
469
  init_route_builder();
400
470
  init_transport();
401
471
  init_retry_policy();
@@ -536,18 +606,33 @@ var init_rest_client = __esm({
536
606
  );
537
607
  }
538
608
  if (response.status === 401) {
539
- const body = parseJson();
540
- if (body?.detail) {
541
- throw new KSeFUnauthorizedError(body);
609
+ const body2 = parseJson();
610
+ if (body2?.detail) {
611
+ throw new KSeFUnauthorizedError(body2);
542
612
  }
543
613
  }
544
614
  if (response.status === 403) {
545
- const body = parseJson();
546
- if (body?.reasonCode) {
547
- throw new KSeFForbiddenError(body);
615
+ const body2 = parseJson();
616
+ if (body2?.reasonCode) {
617
+ throw new KSeFForbiddenError(body2);
548
618
  }
549
619
  }
550
- throw KSeFApiError.fromResponse(response.status, parseJson());
620
+ if (response.status === 410) {
621
+ const body2 = parseJson();
622
+ throw new KSeFGoneError({
623
+ title: body2?.title || "Gone",
624
+ status: body2?.status || 410,
625
+ detail: body2?.detail || "Operation status no longer available (retention expired)",
626
+ instance: body2?.instance,
627
+ traceId: body2?.traceId,
628
+ timestamp: body2?.timestamp
629
+ });
630
+ }
631
+ const body = parseJson();
632
+ if (hasErrorCode(body, KSeFErrorCode.BatchTimeout)) {
633
+ throw KSeFBatchTimeoutError.fromResponse(response.status, body);
634
+ }
635
+ throw KSeFApiError.fromResponse(response.status, body);
551
636
  }
552
637
  };
553
638
  }
@@ -988,6 +1073,32 @@ var init_online_session = __esm({
988
1073
  }
989
1074
  });
990
1075
 
1076
+ // src/utils/concurrency.ts
1077
+ async function runWithConcurrency(tasks, parallelism) {
1078
+ if (tasks.length === 0) return;
1079
+ const limit = Math.max(1, Math.min(parallelism, tasks.length));
1080
+ const controller = new AbortController();
1081
+ let index = 0;
1082
+ const workers = Array.from({ length: limit }, async () => {
1083
+ while (index < tasks.length && !controller.signal.aborted) {
1084
+ const current = index;
1085
+ index += 1;
1086
+ try {
1087
+ await tasks[current](controller.signal);
1088
+ } catch (err) {
1089
+ controller.abort();
1090
+ throw err;
1091
+ }
1092
+ }
1093
+ });
1094
+ await Promise.all(workers);
1095
+ }
1096
+ var init_concurrency = __esm({
1097
+ "src/utils/concurrency.ts"() {
1098
+ "use strict";
1099
+ }
1100
+ });
1101
+
991
1102
  // src/services/batch-session.ts
992
1103
  var BatchSessionService;
993
1104
  var init_batch_session = __esm({
@@ -996,6 +1107,7 @@ var init_batch_session = __esm({
996
1107
  init_ksef_feature();
997
1108
  init_rest_request();
998
1109
  init_routes();
1110
+ init_concurrency();
999
1111
  BatchSessionService = class {
1000
1112
  restClient;
1001
1113
  constructor(restClient) {
@@ -1009,12 +1121,10 @@ var init_batch_session = __esm({
1009
1121
  const response = await this.restClient.execute(req);
1010
1122
  return response.body;
1011
1123
  }
1012
- async sendParts(openResponse, parts) {
1013
- const uploadRequests = openResponse.partUploadRequests;
1014
- const tasks = parts.map(async (part) => {
1015
- const uploadReq = uploadRequests.find(
1016
- (r) => r.ordinalNumber === part.ordinalNumber
1017
- );
1124
+ async sendParts(openResponse, parts, parallelism) {
1125
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
1126
+ const tasks = parts.map((part) => async (signal) => {
1127
+ const uploadReq = uploadMap.get(part.ordinalNumber);
1018
1128
  if (!uploadReq) {
1019
1129
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
1020
1130
  }
@@ -1022,25 +1132,38 @@ var init_batch_session = __esm({
1022
1132
  for (const [k, v] of Object.entries(uploadReq.headers)) {
1023
1133
  if (v != null) headers[k] = v;
1024
1134
  }
1025
- await fetch(uploadReq.url, {
1135
+ const resp = await fetch(uploadReq.url, {
1026
1136
  method: uploadReq.method,
1027
1137
  headers,
1028
- body: part.data
1138
+ body: part.data,
1139
+ signal
1029
1140
  });
1141
+ if (!resp.ok) {
1142
+ const body = await resp.text().catch(() => "");
1143
+ throw new Error(
1144
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
1145
+ );
1146
+ }
1030
1147
  });
1031
- await Promise.all(tasks);
1148
+ if (parallelism !== void 0) {
1149
+ await runWithConcurrency(tasks, parallelism);
1150
+ } else {
1151
+ const ac = new AbortController();
1152
+ await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
1153
+ ac.abort();
1154
+ throw err;
1155
+ })));
1156
+ }
1032
1157
  }
1033
1158
  /**
1034
- * Upload parts sequentially (not in parallel) because each part uses a
1035
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
1036
- * backpressure issues and exceed memory limits for large payloads.
1159
+ * Upload parts using streaming bodies (`duplex: 'half'`).
1160
+ * By default uploads sequentially to avoid backpressure issues.
1161
+ * Pass `parallelism` to enable bounded concurrent uploads.
1037
1162
  */
1038
- async sendPartsWithStream(openResponse, parts) {
1039
- const uploadRequests = openResponse.partUploadRequests;
1040
- for (const part of parts) {
1041
- const uploadReq = uploadRequests.find(
1042
- (r) => r.ordinalNumber === part.ordinalNumber
1043
- );
1163
+ async sendPartsWithStream(openResponse, parts, parallelism) {
1164
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
1165
+ const uploadPart = async (part, signal) => {
1166
+ const uploadReq = uploadMap.get(part.ordinalNumber);
1044
1167
  if (!uploadReq) {
1045
1168
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
1046
1169
  }
@@ -1052,11 +1175,23 @@ var init_batch_session = __esm({
1052
1175
  method: uploadReq.method,
1053
1176
  headers,
1054
1177
  body: part.dataStream,
1178
+ signal,
1055
1179
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
1056
1180
  duplex: "half"
1057
1181
  });
1058
1182
  if (!resp.ok) {
1059
- throw new Error(`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
1183
+ const body = await resp.text().catch(() => "");
1184
+ throw new Error(
1185
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
1186
+ );
1187
+ }
1188
+ };
1189
+ if (parallelism !== void 0) {
1190
+ await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
1191
+ } else {
1192
+ const { signal } = new AbortController();
1193
+ for (const part of parts) {
1194
+ await uploadPart(part, signal);
1060
1195
  }
1061
1196
  }
1062
1197
  }
@@ -1169,7 +1304,10 @@ var init_invoice_download = __esm({
1169
1304
  async getInvoice(ksefNumber) {
1170
1305
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
1171
1306
  const response = await this.restClient.executeRaw(req);
1172
- return new TextDecoder().decode(response.body);
1307
+ return {
1308
+ xml: new TextDecoder().decode(response.body),
1309
+ hash: response.headers.get("x-ms-meta-hash") ?? void 0
1310
+ };
1173
1311
  }
1174
1312
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
1175
1313
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -1450,9 +1588,12 @@ var init_errors = __esm({
1450
1588
  init_ksef_rate_limit_error();
1451
1589
  init_ksef_unauthorized_error();
1452
1590
  init_ksef_forbidden_error();
1591
+ init_ksef_gone_error();
1453
1592
  init_ksef_auth_status_error();
1454
1593
  init_ksef_session_expired_error();
1455
1594
  init_ksef_validation_error();
1595
+ init_ksef_batch_timeout_error();
1596
+ init_error_codes();
1456
1597
  }
1457
1598
  });
1458
1599
 
@@ -2087,6 +2228,72 @@ var init_auth_xml_builder = __esm({
2087
2228
  }
2088
2229
  });
2089
2230
 
2231
+ // src/offline/holidays.ts
2232
+ function toDateKey(d) {
2233
+ return d.toISOString().slice(0, 10);
2234
+ }
2235
+ function dateKey(year, month, day) {
2236
+ return toDateKey(new Date(Date.UTC(year, month - 1, day)));
2237
+ }
2238
+ function addDays(d, n) {
2239
+ const result = new Date(d);
2240
+ result.setUTCDate(result.getUTCDate() + n);
2241
+ return result;
2242
+ }
2243
+ function computeEasterSunday(year) {
2244
+ const a = year % 19;
2245
+ const b = Math.floor(year / 100);
2246
+ const c = year % 100;
2247
+ const d = Math.floor(b / 4);
2248
+ const e = b % 4;
2249
+ const f = Math.floor((b + 8) / 25);
2250
+ const g = Math.floor((b - f + 1) / 3);
2251
+ const h = (19 * a + b - d - g + 15) % 30;
2252
+ const i = Math.floor(c / 4);
2253
+ const k = c % 4;
2254
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
2255
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
2256
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
2257
+ const day = (h + l - 7 * m + 114) % 31 + 1;
2258
+ return new Date(Date.UTC(year, month - 1, day));
2259
+ }
2260
+ function getPolishHolidays(year) {
2261
+ const cached = holidayCache.get(year);
2262
+ if (cached) return cached;
2263
+ const easter = computeEasterSunday(year);
2264
+ const holidays = /* @__PURE__ */ new Set([
2265
+ // Fixed
2266
+ dateKey(year, 1, 1),
2267
+ dateKey(year, 1, 6),
2268
+ dateKey(year, 5, 1),
2269
+ dateKey(year, 5, 3),
2270
+ dateKey(year, 8, 15),
2271
+ dateKey(year, 11, 1),
2272
+ dateKey(year, 11, 11),
2273
+ dateKey(year, 12, 25),
2274
+ dateKey(year, 12, 26),
2275
+ // Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
2276
+ ...year >= 2025 ? [dateKey(year, 12, 24)] : [],
2277
+ // Moveable
2278
+ toDateKey(easter),
2279
+ toDateKey(addDays(easter, 1)),
2280
+ toDateKey(addDays(easter, 49)),
2281
+ toDateKey(addDays(easter, 60))
2282
+ ]);
2283
+ holidayCache.set(year, holidays);
2284
+ return holidays;
2285
+ }
2286
+ function isPolishHoliday(date) {
2287
+ return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
2288
+ }
2289
+ var holidayCache;
2290
+ var init_holidays = __esm({
2291
+ "src/offline/holidays.ts"() {
2292
+ "use strict";
2293
+ holidayCache = /* @__PURE__ */ new Map();
2294
+ }
2295
+ });
2296
+
2090
2297
  // src/offline/deadline.ts
2091
2298
  function getDefaultReason(mode) {
2092
2299
  switch (mode) {
@@ -2103,7 +2310,7 @@ function getDefaultReason(mode) {
2103
2310
  function nextBusinessDay(from) {
2104
2311
  const d = new Date(from);
2105
2312
  d.setUTCDate(d.getUTCDate() + 1);
2106
- while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
2313
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
2107
2314
  d.setUTCDate(d.getUTCDate() + 1);
2108
2315
  }
2109
2316
  return d;
@@ -2116,7 +2323,7 @@ function addBusinessDays(from, days) {
2116
2323
  let remaining = days;
2117
2324
  while (remaining > 0) {
2118
2325
  d.setUTCDate(d.getUTCDate() + 1);
2119
- if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
2326
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
2120
2327
  remaining--;
2121
2328
  }
2122
2329
  }
@@ -2162,6 +2369,7 @@ var FAR_FUTURE;
2162
2369
  var init_deadline = __esm({
2163
2370
  "src/offline/deadline.ts"() {
2164
2371
  "use strict";
2372
+ init_holidays();
2165
2373
  FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
2166
2374
  }
2167
2375
  });
@@ -2884,6 +3092,7 @@ var init_client = __esm({
2884
3092
  qr;
2885
3093
  options;
2886
3094
  authManager;
3095
+ _baseRestClientConfig;
2887
3096
  _offline;
2888
3097
  constructor(options) {
2889
3098
  this.options = resolveOptions(options);
@@ -2895,6 +3104,8 @@ var init_client = __esm({
2895
3104
  });
2896
3105
  this.authManager = authManager;
2897
3106
  const restClientConfig = buildRestClientConfig(options, authManager);
3107
+ const { authManager: _am, ...baseConfig } = restClientConfig;
3108
+ this._baseRestClientConfig = baseConfig;
2898
3109
  const restClient = new RestClient(this.options, restClientConfig);
2899
3110
  const fetcher = new CertificateFetcher(restClient);
2900
3111
  this.crypto = new CryptographyService(fetcher);
@@ -2919,6 +3130,10 @@ var init_client = __esm({
2919
3130
  }
2920
3131
  return this._offline;
2921
3132
  }
3133
+ /** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
3134
+ createScopedRestClient(authManager) {
3135
+ return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
3136
+ }
2922
3137
  async loginWithToken(token, nip) {
2923
3138
  const challenge2 = await this.auth.getChallenge();
2924
3139
  await this.crypto.init();
@@ -2933,6 +3148,7 @@ var init_client = __esm({
2933
3148
  const tokens = await this.auth.getAccessToken(authToken);
2934
3149
  this.authManager.setAccessToken(tokens.accessToken.token);
2935
3150
  this.authManager.setRefreshToken(tokens.refreshToken.token);
3151
+ return { clientIp: challenge2.clientIp };
2936
3152
  }
2937
3153
  async loginWithCertificate(certPem, keyPem, nip, keyPassword) {
2938
3154
  const challenge2 = await this.auth.getChallenge();
@@ -2945,11 +3161,12 @@ var init_client = __esm({
2945
3161
  const tokens = await this.auth.getAccessToken(authToken);
2946
3162
  this.authManager.setAccessToken(tokens.accessToken.token);
2947
3163
  this.authManager.setRefreshToken(tokens.refreshToken.token);
3164
+ return { clientIp: challenge2.clientIp };
2948
3165
  }
2949
3166
  async loginWithPkcs12(p12, password, nip) {
2950
3167
  const { Pkcs12Loader: Pkcs12Loader2 } = await Promise.resolve().then(() => (init_pkcs12_loader(), pkcs12_loader_exports));
2951
3168
  const { certificatePem, privateKeyPem } = Pkcs12Loader2.load(p12, password);
2952
- await this.loginWithCertificate(certificatePem, privateKeyPem, nip);
3169
+ return this.loginWithCertificate(certificatePem, privateKeyPem, nip);
2953
3170
  }
2954
3171
  async awaitAuthReady(referenceNumber, authToken) {
2955
3172
  for (let i = 0; i < 30; i++) {
@@ -5034,7 +5251,7 @@ var init_invoice_validator = __esm({
5034
5251
  });
5035
5252
 
5036
5253
  // src/builders/batch-file.ts
5037
- import * as crypto5 from "crypto";
5254
+ import * as crypto6 from "crypto";
5038
5255
  function splitBuffer(data, maxPartSize) {
5039
5256
  if (data.length <= maxPartSize) {
5040
5257
  return [data];
@@ -5045,8 +5262,8 @@ function splitBuffer(data, maxPartSize) {
5045
5262
  }
5046
5263
  return parts;
5047
5264
  }
5048
- function sha256Base64(data) {
5049
- return crypto5.createHash("sha256").update(data).digest("base64");
5265
+ function sha256Base642(data) {
5266
+ return crypto6.createHash("sha256").update(data).digest("base64");
5050
5267
  }
5051
5268
  var BATCH_MAX_PART_SIZE, BATCH_MAX_TOTAL_SIZE, BATCH_MAX_PARTS, BatchFileBuilder;
5052
5269
  var init_batch_file = __esm({
@@ -5083,7 +5300,7 @@ var init_batch_file = __esm({
5083
5300
  `Data requires ${rawParts.length} parts, exceeding maximum of ${BATCH_MAX_PARTS}`
5084
5301
  );
5085
5302
  }
5086
- const zipHash = sha256Base64(zipBytes);
5303
+ const zipHash = sha256Base642(zipBytes);
5087
5304
  const encryptedParts = [];
5088
5305
  const fileParts = rawParts.map((raw, i) => {
5089
5306
  const encrypted = encryptFn(raw);
@@ -5091,7 +5308,7 @@ var init_batch_file = __esm({
5091
5308
  return {
5092
5309
  ordinalNumber: i + 1,
5093
5310
  fileSize: encrypted.length,
5094
- fileHash: sha256Base64(encrypted)
5311
+ fileHash: sha256Base642(encrypted)
5095
5312
  };
5096
5313
  });
5097
5314
  return {
@@ -5174,7 +5391,7 @@ var init_batch_file = __esm({
5174
5391
  encryptedChunks.push(value);
5175
5392
  }
5176
5393
  const encryptedData = new Uint8Array(Buffer.concat(encryptedChunks));
5177
- const encryptedMeta = sha256Base64(encryptedData);
5394
+ const encryptedMeta = sha256Base642(encryptedData);
5178
5395
  fileParts.push({
5179
5396
  ordinalNumber: partIndex + 1,
5180
5397
  fileSize: encryptedData.byteLength,
@@ -5218,6 +5435,9 @@ __export(batch_session_workflow_exports, {
5218
5435
  uploadBatchStreamParsed: () => uploadBatchStreamParsed
5219
5436
  });
5220
5437
  async function uploadBatch(client, zipData, options) {
5438
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
5439
+ throw new Error("parallelism must be a positive integer");
5440
+ }
5221
5441
  await client.crypto.init();
5222
5442
  if (options?.validate) {
5223
5443
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -5260,7 +5480,7 @@ async function uploadBatch(client, zipData, options) {
5260
5480
  },
5261
5481
  ordinalNumber: i + 1
5262
5482
  }));
5263
- await client.batchSession.sendParts(openResp, sendingParts);
5483
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
5264
5484
  await client.batchSession.closeSession(openResp.referenceNumber);
5265
5485
  const result = await pollUntil(
5266
5486
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5281,6 +5501,9 @@ async function uploadBatch(client, zipData, options) {
5281
5501
  };
5282
5502
  }
5283
5503
  async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
5504
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
5505
+ throw new Error("parallelism must be a positive integer");
5506
+ }
5284
5507
  await client.crypto.init();
5285
5508
  const encData = client.crypto.getEncryptionData();
5286
5509
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -5302,7 +5525,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
5302
5525
  },
5303
5526
  options?.upoVersion
5304
5527
  );
5305
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
5528
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
5306
5529
  await client.batchSession.closeSession(openResp.referenceNumber);
5307
5530
  const result = await pollUntil(
5308
5531
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5923,17 +6146,18 @@ var login = defineCommand2({
5923
6146
  throw new Error("NIP is required. Provide --nip or set it via `ksef config set --nip <nip>`.");
5924
6147
  }
5925
6148
  const token = args.token ?? loadCredentials()?.token;
6149
+ let loginResult;
5926
6150
  if (token) {
5927
- await client.loginWithToken(token, nip);
6151
+ loginResult = await client.loginWithToken(token, nip);
5928
6152
  } else if (args.p12) {
5929
6153
  const fs15 = await import("fs");
5930
6154
  const p12Buffer = fs15.readFileSync(args.p12);
5931
- await client.loginWithPkcs12(p12Buffer, args["p12-password"] ?? "", nip);
6155
+ loginResult = await client.loginWithPkcs12(p12Buffer, args["p12-password"] ?? "", nip);
5932
6156
  } else if (args.cert && args.key) {
5933
6157
  const fs15 = await import("fs");
5934
6158
  const certPem = fs15.readFileSync(args.cert, "utf-8");
5935
6159
  const keyPem = fs15.readFileSync(args.key, "utf-8");
5936
- await client.loginWithCertificate(certPem, keyPem, nip, args["key-password"]);
6160
+ loginResult = await client.loginWithCertificate(certPem, keyPem, nip, args["key-password"]);
5937
6161
  } else {
5938
6162
  throw new Error("Provide --token, --p12, or both --cert and --key for authentication.");
5939
6163
  }
@@ -5948,7 +6172,12 @@ var login = defineCommand2({
5948
6172
  if (args.env && args.env !== config.environment) {
5949
6173
  saveConfig({ ...config, environment: session.environment });
5950
6174
  }
5951
- outputSuccess("Logged in successfully.");
6175
+ if (args.json) {
6176
+ console.log(JSON.stringify({ status: "ok", clientIp: loginResult.clientIp }, null, 2));
6177
+ } else {
6178
+ consola6.info(`Seen client IP: ${loginResult.clientIp}`);
6179
+ outputSuccess("Logged in successfully.");
6180
+ }
5952
6181
  });
5953
6182
  }
5954
6183
  });
@@ -6111,6 +6340,8 @@ var loginExternal = defineCommand2({
6111
6340
  process.stderr.write(`Challenge: ${challengeResult.challenge}
6112
6341
  `);
6113
6342
  process.stderr.write(`Timestamp: ${challengeResult.timestamp}
6343
+ `);
6344
+ process.stderr.write(`Seen client IP: ${challengeResult.clientIp}
6114
6345
  `);
6115
6346
  process.stderr.write(`Note: Sign this XML and submit with --submit before the challenge expires.
6116
6347
  `);
@@ -6311,6 +6542,7 @@ var list = defineCommand3({
6311
6542
  reference: s.referenceNumber,
6312
6543
  status: `${s.status.code} \u2014 ${s.status.description}`,
6313
6544
  created: s.dateCreated,
6545
+ updated: s.dateUpdated,
6314
6546
  total: s.totalInvoiceCount,
6315
6547
  success: s.successfulInvoiceCount,
6316
6548
  failed: s.failedInvoiceCount
@@ -6319,6 +6551,7 @@ var list = defineCommand3({
6319
6551
  { key: "reference", label: "Reference" },
6320
6552
  { key: "status", label: "Status" },
6321
6553
  { key: "created", label: "Created" },
6554
+ { key: "updated", label: "Updated" },
6322
6555
  { key: "total", label: "Total" },
6323
6556
  { key: "success", label: "Success" },
6324
6557
  { key: "failed", label: "Failed" }
@@ -6646,6 +6879,17 @@ var FileHwmStore = class {
6646
6879
  // src/workflows/invoice-export-workflow.ts
6647
6880
  init_zip();
6648
6881
  init_polling();
6882
+
6883
+ // src/utils/hash.ts
6884
+ import crypto5 from "crypto";
6885
+ function sha256Base64(data) {
6886
+ return crypto5.createHash("sha256").update(data).digest("base64");
6887
+ }
6888
+ function verifyHash(data, expectedHash) {
6889
+ return sha256Base64(data) === expectedHash;
6890
+ }
6891
+
6892
+ // src/workflows/invoice-export-workflow.ts
6649
6893
  async function doExport(client, filters, options) {
6650
6894
  await client.crypto.init();
6651
6895
  const encData = client.crypto.getEncryptionData();
@@ -6674,6 +6918,7 @@ async function doExport(client, filters, options) {
6674
6918
  url: p.url,
6675
6919
  method: p.method,
6676
6920
  partSize: p.partSize,
6921
+ partHash: p.partHash,
6677
6922
  encryptedPartSize: p.encryptedPartSize,
6678
6923
  encryptedPartHash: p.encryptedPartHash,
6679
6924
  expirationDate: p.expirationDate
@@ -6735,6 +6980,9 @@ async function incrementalExportAndDownload(client, options) {
6735
6980
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6736
6981
  }
6737
6982
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6983
+ if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6984
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6985
+ }
6738
6986
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6739
6987
  decryptedParts.push(decrypted);
6740
6988
  }
@@ -6930,8 +7178,8 @@ var QUERY_FILTER_ARGS = {
6930
7178
  dateType: { type: "string", description: "Date type: Issue|Invoicing|PermanentStorage (default: Invoicing)" },
6931
7179
  sellerNip: { type: "string", description: "Filter by seller NIP" },
6932
7180
  buyerNip: { type: "string", description: "Filter by buyer NIP" },
6933
- amountFrom: { type: "string", description: "Minimum amount" },
6934
- amountTo: { type: "string", description: "Maximum amount" },
7181
+ amountFrom: { type: "string", description: "Minimum amount (negative values allowed)" },
7182
+ amountTo: { type: "string", description: "Maximum amount (negative values allowed)" },
6935
7183
  amountType: { type: "string", description: "Amount type: Brutto|Netto|Vat (default: Brutto)" },
6936
7184
  currency: { type: "string", description: "Currency code (e.g. PLN, EUR)" }
6937
7185
  };
@@ -6943,6 +7191,7 @@ var send = defineCommand5({
6943
7191
  stream: { type: "boolean", description: "Use stream-based batch upload (for ZIP files, reduces memory usage)" },
6944
7192
  formCode: { type: "string", description: "Document type: FA2, FA3, PEF3, PEFKOR3, FARR1 (default: FA3)" },
6945
7193
  validate: { type: "boolean", description: "Validate XML before sending" },
7194
+ parallelism: { type: "string", description: "Number of concurrent part uploads (batch mode)" },
6946
7195
  env: { type: "string", description: "Environment (test/demo/prod)" },
6947
7196
  json: { type: "boolean", description: "Output as JSON" },
6948
7197
  verbose: { type: "boolean", description: "Show HTTP request/response details" },
@@ -6956,6 +7205,10 @@ var send = defineCommand5({
6956
7205
  const config = loadConfig();
6957
7206
  const nip = args.nip ?? config.nip;
6958
7207
  const filePath = args.path;
7208
+ const parallelism = args.parallelism ? Number(args.parallelism) : void 0;
7209
+ if (parallelism !== void 0 && (!Number.isInteger(parallelism) || parallelism < 1)) {
7210
+ throw new Error("--parallelism must be a positive integer");
7211
+ }
6959
7212
  const formCodeKey = args.formCode;
6960
7213
  let formCode = DEFAULT_FORM_CODE;
6961
7214
  if (formCodeKey) {
@@ -6988,7 +7241,8 @@ var send = defineCommand5({
6988
7241
  if (!args.json) consola9.start(`Sending batch via stream (${(zipSize / 1e6).toFixed(1)} MB)...`);
6989
7242
  const result = await uploadBatchStream2(client, zipStreamFactory, zipSize, {
6990
7243
  formCode,
6991
- pollOptions: { intervalMs: 3e3 }
7244
+ pollOptions: { intervalMs: 3e3 },
7245
+ parallelism
6992
7246
  });
6993
7247
  if (args.json) {
6994
7248
  outputResult(result, { json: true });
@@ -7052,7 +7306,7 @@ var send = defineCommand5({
7052
7306
  { formCode, batchFile: batchFileInfo, encryption: encryptionData.encryptionInfo }
7053
7307
  );
7054
7308
  saveOnlineSessionRef(openResult.referenceNumber);
7055
- await client.batchSession.sendParts(openResult, parts);
7309
+ await client.batchSession.sendParts(openResult, parts, parallelism);
7056
7310
  await client.batchSession.closeSession(openResult.referenceNumber);
7057
7311
  clearOnlineSessionRef();
7058
7312
  if (args.json) {
@@ -7117,9 +7371,9 @@ var get = defineCommand5({
7117
7371
  return withErrorHandler(async () => {
7118
7372
  const globalOpts = getGlobalOpts4(args);
7119
7373
  const { client } = await requireSession(globalOpts);
7120
- const xml = await client.invoices.getInvoice(args.ksefNumber);
7374
+ const { xml, hash } = await client.invoices.getInvoice(args.ksefNumber);
7121
7375
  if (args.json) {
7122
- outputResult({ ksefNumber: args.ksefNumber, xml }, { json: true });
7376
+ outputResult({ ksefNumber: args.ksefNumber, xml, hash }, { json: true });
7123
7377
  return;
7124
7378
  }
7125
7379
  if (args.o) {
@@ -8036,12 +8290,12 @@ import fs10 from "fs";
8036
8290
  import path7 from "path";
8037
8291
 
8038
8292
  // src/crypto/certificate-service.ts
8039
- import * as crypto6 from "crypto";
8293
+ import * as crypto7 from "crypto";
8040
8294
  import * as x5092 from "@peculiar/x509";
8041
8295
  var CertificateService = class {
8042
8296
  static getSha256Fingerprint(certPem) {
8043
8297
  const der = extractDerFromPem2(certPem);
8044
- return crypto6.createHash("sha256").update(der).digest("hex").toUpperCase();
8298
+ return crypto7.createHash("sha256").update(der).digest("hex").toUpperCase();
8045
8299
  }
8046
8300
  static async generatePersonalCertificate(givenName, surname, serialNumber, commonName, method = "RSA") {
8047
8301
  const nameParts = [];
@@ -8064,7 +8318,7 @@ var CertificateService = class {
8064
8318
  }
8065
8319
  };
8066
8320
  async function generateSelfSigned(subject2, method) {
8067
- x5092.cryptoProvider.set(crypto6.webcrypto);
8321
+ x5092.cryptoProvider.set(crypto7.webcrypto);
8068
8322
  let algorithm;
8069
8323
  let signingAlgorithm;
8070
8324
  if (method === "ECDSA") {
@@ -8079,7 +8333,7 @@ async function generateSelfSigned(subject2, method) {
8079
8333
  };
8080
8334
  signingAlgorithm = algorithm;
8081
8335
  }
8082
- const keys = await crypto6.webcrypto.subtle.generateKey(
8336
+ const keys = await crypto7.webcrypto.subtle.generateKey(
8083
8337
  algorithm,
8084
8338
  true,
8085
8339
  ["sign", "verify"]
@@ -8096,9 +8350,9 @@ async function generateSelfSigned(subject2, method) {
8096
8350
  serialNumber: Date.now().toString(16)
8097
8351
  });
8098
8352
  const certPem = cert.toString("pem");
8099
- const pkcs8 = await crypto6.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
8353
+ const pkcs8 = await crypto7.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
8100
8354
  const privateKeyPem = pemEncode2(new Uint8Array(pkcs8), "PRIVATE KEY");
8101
- const fingerprint = crypto6.createHash("sha256").update(Buffer.from(cert.rawData)).digest("hex").toUpperCase();
8355
+ const fingerprint = crypto7.createHash("sha256").update(Buffer.from(cert.rawData)).digest("hex").toUpperCase();
8102
8356
  return { certificatePem: certPem, privateKeyPem, fingerprint };
8103
8357
  }
8104
8358
  function extractDerFromPem2(pem) {