ksef-client-ts 0.6.0 → 0.6.1

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/index.cjs CHANGED
@@ -977,7 +977,9 @@ function isValidVatUe(value) {
977
977
  return VatUe.test(value);
978
978
  }
979
979
  function isValidNipVatUe(value) {
980
- return NipVatUe.test(value);
980
+ if (!NipVatUe.test(value)) return false;
981
+ const nip = value.split("-")[0];
982
+ return isValidNip(nip);
981
983
  }
982
984
  function isValidInternalId(value) {
983
985
  return InternalId.test(value);
@@ -2990,6 +2992,32 @@ var init_online_session = __esm({
2990
2992
  }
2991
2993
  });
2992
2994
 
2995
+ // src/utils/concurrency.ts
2996
+ async function runWithConcurrency(tasks, parallelism) {
2997
+ if (tasks.length === 0) return;
2998
+ const limit = Math.max(1, Math.min(parallelism, tasks.length));
2999
+ const controller = new AbortController();
3000
+ let index = 0;
3001
+ const workers = Array.from({ length: limit }, async () => {
3002
+ while (index < tasks.length && !controller.signal.aborted) {
3003
+ const current = index;
3004
+ index += 1;
3005
+ try {
3006
+ await tasks[current](controller.signal);
3007
+ } catch (err) {
3008
+ controller.abort();
3009
+ throw err;
3010
+ }
3011
+ }
3012
+ });
3013
+ await Promise.all(workers);
3014
+ }
3015
+ var init_concurrency = __esm({
3016
+ "src/utils/concurrency.ts"() {
3017
+ "use strict";
3018
+ }
3019
+ });
3020
+
2993
3021
  // src/services/batch-session.ts
2994
3022
  var BatchSessionService;
2995
3023
  var init_batch_session = __esm({
@@ -2998,6 +3026,7 @@ var init_batch_session = __esm({
2998
3026
  init_ksef_feature();
2999
3027
  init_rest_request();
3000
3028
  init_routes();
3029
+ init_concurrency();
3001
3030
  BatchSessionService = class {
3002
3031
  restClient;
3003
3032
  constructor(restClient) {
@@ -3011,12 +3040,10 @@ var init_batch_session = __esm({
3011
3040
  const response = await this.restClient.execute(req);
3012
3041
  return response.body;
3013
3042
  }
3014
- async sendParts(openResponse, parts) {
3015
- const uploadRequests = openResponse.partUploadRequests;
3016
- const tasks = parts.map(async (part) => {
3017
- const uploadReq = uploadRequests.find(
3018
- (r) => r.ordinalNumber === part.ordinalNumber
3019
- );
3043
+ async sendParts(openResponse, parts, parallelism) {
3044
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3045
+ const tasks = parts.map((part) => async (signal) => {
3046
+ const uploadReq = uploadMap.get(part.ordinalNumber);
3020
3047
  if (!uploadReq) {
3021
3048
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
3022
3049
  }
@@ -3024,25 +3051,38 @@ var init_batch_session = __esm({
3024
3051
  for (const [k, v] of Object.entries(uploadReq.headers)) {
3025
3052
  if (v != null) headers[k] = v;
3026
3053
  }
3027
- await fetch(uploadReq.url, {
3054
+ const resp = await fetch(uploadReq.url, {
3028
3055
  method: uploadReq.method,
3029
3056
  headers,
3030
- body: part.data
3057
+ body: part.data,
3058
+ signal
3031
3059
  });
3060
+ if (!resp.ok) {
3061
+ const body = await resp.text().catch(() => "");
3062
+ throw new Error(
3063
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3064
+ );
3065
+ }
3032
3066
  });
3033
- await Promise.all(tasks);
3067
+ if (parallelism !== void 0) {
3068
+ await runWithConcurrency(tasks, parallelism);
3069
+ } else {
3070
+ const ac = new AbortController();
3071
+ await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
3072
+ ac.abort();
3073
+ throw err;
3074
+ })));
3075
+ }
3034
3076
  }
3035
3077
  /**
3036
- * Upload parts sequentially (not in parallel) because each part uses a
3037
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
3038
- * backpressure issues and exceed memory limits for large payloads.
3078
+ * Upload parts using streaming bodies (`duplex: 'half'`).
3079
+ * By default uploads sequentially to avoid backpressure issues.
3080
+ * Pass `parallelism` to enable bounded concurrent uploads.
3039
3081
  */
3040
- async sendPartsWithStream(openResponse, parts) {
3041
- const uploadRequests = openResponse.partUploadRequests;
3042
- for (const part of parts) {
3043
- const uploadReq = uploadRequests.find(
3044
- (r) => r.ordinalNumber === part.ordinalNumber
3045
- );
3082
+ async sendPartsWithStream(openResponse, parts, parallelism) {
3083
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3084
+ const uploadPart = async (part, signal) => {
3085
+ const uploadReq = uploadMap.get(part.ordinalNumber);
3046
3086
  if (!uploadReq) {
3047
3087
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
3048
3088
  }
@@ -3054,11 +3094,23 @@ var init_batch_session = __esm({
3054
3094
  method: uploadReq.method,
3055
3095
  headers,
3056
3096
  body: part.dataStream,
3097
+ signal,
3057
3098
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
3058
3099
  duplex: "half"
3059
3100
  });
3060
3101
  if (!resp.ok) {
3061
- throw new Error(`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
3102
+ const body = await resp.text().catch(() => "");
3103
+ throw new Error(
3104
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3105
+ );
3106
+ }
3107
+ };
3108
+ if (parallelism !== void 0) {
3109
+ await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
3110
+ } else {
3111
+ const { signal } = new AbortController();
3112
+ for (const part of parts) {
3113
+ await uploadPart(part, signal);
3062
3114
  }
3063
3115
  }
3064
3116
  }
@@ -3171,7 +3223,10 @@ var init_invoice_download = __esm({
3171
3223
  async getInvoice(ksefNumber) {
3172
3224
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
3173
3225
  const response = await this.restClient.executeRaw(req);
3174
- return new TextDecoder().decode(response.body);
3226
+ return {
3227
+ xml: new TextDecoder().decode(response.body),
3228
+ hash: response.headers.get("x-ms-meta-hash") ?? void 0
3229
+ };
3175
3230
  }
3176
3231
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
3177
3232
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -4433,6 +4488,72 @@ var init_zip = __esm({
4433
4488
  }
4434
4489
  });
4435
4490
 
4491
+ // src/offline/holidays.ts
4492
+ function toDateKey(d) {
4493
+ return d.toISOString().slice(0, 10);
4494
+ }
4495
+ function dateKey(year, month, day) {
4496
+ return toDateKey(new Date(Date.UTC(year, month - 1, day)));
4497
+ }
4498
+ function addDays(d, n) {
4499
+ const result = new Date(d);
4500
+ result.setUTCDate(result.getUTCDate() + n);
4501
+ return result;
4502
+ }
4503
+ function computeEasterSunday(year) {
4504
+ const a = year % 19;
4505
+ const b = Math.floor(year / 100);
4506
+ const c = year % 100;
4507
+ const d = Math.floor(b / 4);
4508
+ const e = b % 4;
4509
+ const f = Math.floor((b + 8) / 25);
4510
+ const g = Math.floor((b - f + 1) / 3);
4511
+ const h = (19 * a + b - d - g + 15) % 30;
4512
+ const i = Math.floor(c / 4);
4513
+ const k = c % 4;
4514
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
4515
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
4516
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
4517
+ const day = (h + l - 7 * m + 114) % 31 + 1;
4518
+ return new Date(Date.UTC(year, month - 1, day));
4519
+ }
4520
+ function getPolishHolidays(year) {
4521
+ const cached = holidayCache.get(year);
4522
+ if (cached) return cached;
4523
+ const easter = computeEasterSunday(year);
4524
+ const holidays = /* @__PURE__ */ new Set([
4525
+ // Fixed
4526
+ dateKey(year, 1, 1),
4527
+ dateKey(year, 1, 6),
4528
+ dateKey(year, 5, 1),
4529
+ dateKey(year, 5, 3),
4530
+ dateKey(year, 8, 15),
4531
+ dateKey(year, 11, 1),
4532
+ dateKey(year, 11, 11),
4533
+ dateKey(year, 12, 25),
4534
+ dateKey(year, 12, 26),
4535
+ // Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
4536
+ ...year >= 2025 ? [dateKey(year, 12, 24)] : [],
4537
+ // Moveable
4538
+ toDateKey(easter),
4539
+ toDateKey(addDays(easter, 1)),
4540
+ toDateKey(addDays(easter, 49)),
4541
+ toDateKey(addDays(easter, 60))
4542
+ ]);
4543
+ holidayCache.set(year, holidays);
4544
+ return holidays;
4545
+ }
4546
+ function isPolishHoliday(date) {
4547
+ return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
4548
+ }
4549
+ var holidayCache;
4550
+ var init_holidays = __esm({
4551
+ "src/offline/holidays.ts"() {
4552
+ "use strict";
4553
+ holidayCache = /* @__PURE__ */ new Map();
4554
+ }
4555
+ });
4556
+
4436
4557
  // src/offline/deadline.ts
4437
4558
  function getDefaultReason(mode) {
4438
4559
  switch (mode) {
@@ -4449,7 +4570,7 @@ function getDefaultReason(mode) {
4449
4570
  function nextBusinessDay(from) {
4450
4571
  const d = new Date(from);
4451
4572
  d.setUTCDate(d.getUTCDate() + 1);
4452
- while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
4573
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
4453
4574
  d.setUTCDate(d.getUTCDate() + 1);
4454
4575
  }
4455
4576
  return d;
@@ -4462,7 +4583,7 @@ function addBusinessDays(from, days) {
4462
4583
  let remaining = days;
4463
4584
  while (remaining > 0) {
4464
4585
  d.setUTCDate(d.getUTCDate() + 1);
4465
- if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
4586
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
4466
4587
  remaining--;
4467
4588
  }
4468
4589
  }
@@ -4528,16 +4649,17 @@ var FAR_FUTURE;
4528
4649
  var init_deadline = __esm({
4529
4650
  "src/offline/deadline.ts"() {
4530
4651
  "use strict";
4652
+ init_holidays();
4531
4653
  FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4532
4654
  }
4533
4655
  });
4534
4656
 
4535
4657
  // src/workflows/offline-invoice-workflow.ts
4536
- var import_node_crypto2, OfflineInvoiceWorkflow;
4658
+ var import_node_crypto3, OfflineInvoiceWorkflow;
4537
4659
  var init_offline_invoice_workflow = __esm({
4538
4660
  "src/workflows/offline-invoice-workflow.ts"() {
4539
4661
  "use strict";
4540
- import_node_crypto2 = __toESM(require("crypto"), 1);
4662
+ import_node_crypto3 = __toESM(require("crypto"), 1);
4541
4663
  init_ksef_api_error();
4542
4664
  init_deadline();
4543
4665
  init_document_structures();
@@ -4557,7 +4679,7 @@ var init_offline_invoice_workflow = __esm({
4557
4679
  }
4558
4680
  const mode = options?.mode ?? "offline24";
4559
4681
  const reason = getDefaultReason(mode);
4560
- const invoiceHashBase64 = import_node_crypto2.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4682
+ const invoiceHashBase64 = import_node_crypto3.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4561
4683
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4562
4684
  input.sellerNip,
4563
4685
  input.invoiceDate,
@@ -4576,7 +4698,7 @@ var init_offline_invoice_workflow = __esm({
4576
4698
  }
4577
4699
  const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4578
4700
  const metadata = {
4579
- id: import_node_crypto2.default.randomUUID(),
4701
+ id: import_node_crypto3.default.randomUUID(),
4580
4702
  mode,
4581
4703
  reason,
4582
4704
  status: "GENERATED",
@@ -4732,7 +4854,7 @@ var init_offline_invoice_workflow = __esm({
4732
4854
  original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4733
4855
  );
4734
4856
  }
4735
- const originalHash = import_node_crypto2.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4857
+ const originalHash = import_node_crypto3.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4736
4858
  await client.crypto.init();
4737
4859
  const encData = client.crypto.getEncryptionData();
4738
4860
  const formCode = options.formCode ?? DEFAULT_FORM_CODE;
@@ -4745,9 +4867,9 @@ var init_offline_invoice_workflow = __esm({
4745
4867
  const plainMeta = client.crypto.getFileMetadata(data);
4746
4868
  const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4747
4869
  const encMeta = client.crypto.getFileMetadata(encrypted);
4748
- const correctionId = import_node_crypto2.default.randomUUID();
4870
+ const correctionId = import_node_crypto3.default.randomUUID();
4749
4871
  const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4750
- const correctedHashBase64 = import_node_crypto2.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4872
+ const correctedHashBase64 = import_node_crypto3.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4751
4873
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4752
4874
  original.sellerNip,
4753
4875
  original.invoiceDate,
@@ -4902,6 +5024,7 @@ var init_client = __esm({
4902
5024
  qr;
4903
5025
  options;
4904
5026
  authManager;
5027
+ _baseRestClientConfig;
4905
5028
  _offline;
4906
5029
  constructor(options) {
4907
5030
  this.options = resolveOptions(options);
@@ -4913,6 +5036,8 @@ var init_client = __esm({
4913
5036
  });
4914
5037
  this.authManager = authManager;
4915
5038
  const restClientConfig = buildRestClientConfig(options, authManager);
5039
+ const { authManager: _am, ...baseConfig } = restClientConfig;
5040
+ this._baseRestClientConfig = baseConfig;
4916
5041
  const restClient = new RestClient(this.options, restClientConfig);
4917
5042
  const fetcher = new CertificateFetcher(restClient);
4918
5043
  this.crypto = new CryptographyService(fetcher);
@@ -4937,6 +5062,10 @@ var init_client = __esm({
4937
5062
  }
4938
5063
  return this._offline;
4939
5064
  }
5065
+ /** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
5066
+ createScopedRestClient(authManager) {
5067
+ return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
5068
+ }
4940
5069
  async loginWithToken(token, nip) {
4941
5070
  const challenge = await this.auth.getChallenge();
4942
5071
  await this.crypto.init();
@@ -5097,9 +5226,11 @@ __export(index_exports, {
5097
5226
  getDefaultReason: () => getDefaultReason,
5098
5227
  getEffectiveStartDate: () => getEffectiveStartDate,
5099
5228
  getFormCode: () => getFormCode,
5229
+ getPolishHolidays: () => getPolishHolidays,
5100
5230
  getTimeUntilDeadline: () => getTimeUntilDeadline,
5101
5231
  incrementalExportAndDownload: () => incrementalExportAndDownload,
5102
5232
  isExpired: () => isExpired,
5233
+ isPolishHoliday: () => isPolishHoliday,
5103
5234
  isRetryableError: () => isRetryableError,
5104
5235
  isRetryableStatus: () => isRetryableStatus,
5105
5236
  isValidBase64: () => isValidBase64,
@@ -5126,6 +5257,9 @@ __export(index_exports, {
5126
5257
  parseUpoXml: () => parseUpoXml,
5127
5258
  pollUntil: () => pollUntil,
5128
5259
  resolveOptions: () => resolveOptions,
5260
+ resumeOnlineSession: () => resumeOnlineSession,
5261
+ runWithConcurrency: () => runWithConcurrency,
5262
+ sha256Base64: () => sha256Base642,
5129
5263
  sleep: () => sleep,
5130
5264
  unzip: () => unzip,
5131
5265
  updateContinuationPoint: () => updateContinuationPoint,
@@ -5140,6 +5274,7 @@ __export(index_exports, {
5140
5274
  validatePresignedUrl: () => validatePresignedUrl,
5141
5275
  validateSchema: () => validateSchema,
5142
5276
  validateWellFormedness: () => validateWellFormedness,
5277
+ verifyHash: () => verifyHash,
5143
5278
  xmlToObject: () => xmlToObject
5144
5279
  });
5145
5280
  module.exports = __toCommonJS(index_exports);
@@ -5911,6 +6046,18 @@ function parseKSeFTokenContext(token) {
5911
6046
  };
5912
6047
  }
5913
6048
 
6049
+ // src/utils/hash.ts
6050
+ var import_node_crypto2 = __toESM(require("crypto"), 1);
6051
+ function sha256Base642(data) {
6052
+ return import_node_crypto2.default.createHash("sha256").update(data).digest("base64");
6053
+ }
6054
+ function verifyHash(data, expectedHash) {
6055
+ return sha256Base642(data) === expectedHash;
6056
+ }
6057
+
6058
+ // src/utils/index.ts
6059
+ init_concurrency();
6060
+
5914
6061
  // src/workflows/polling.ts
5915
6062
  async function pollUntil(action, condition, options) {
5916
6063
  const intervalMs = options?.intervalMs ?? 2e3;
@@ -5929,6 +6076,10 @@ async function pollUntil(action, condition, options) {
5929
6076
  }
5930
6077
 
5931
6078
  // src/workflows/online-session-workflow.ts
6079
+ init_ksef_session_expired_error();
6080
+ init_auth_manager();
6081
+ init_online_session();
6082
+ init_session_status();
5932
6083
  init_document_structures();
5933
6084
 
5934
6085
  // src/xml/upo-parser.ts
@@ -6092,18 +6243,11 @@ function nonEmptyString(value) {
6092
6243
  // src/workflows/online-session-workflow.ts
6093
6244
  init_invoice_validator();
6094
6245
  init_ksef_validation_error();
6095
- async function openOnlineSession(client, options) {
6096
- await client.crypto.init();
6097
- const encData = client.crypto.getEncryptionData();
6098
- const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6099
- const openResp = await client.onlineSession.openSession(
6100
- { formCode, encryption: encData.encryptionInfo },
6101
- options?.upoVersion
6102
- );
6103
- const sessionRef = openResp.referenceNumber;
6246
+ function buildSessionHandle(params) {
6247
+ const { deps, sessionRef, validUntil, cipherKey, cipherIv, formCode, validate: validate2 } = params;
6104
6248
  async function fetchUpo(pollOpts) {
6105
6249
  const result = await pollUntil(
6106
- () => client.sessionStatus.getSessionStatus(sessionRef),
6250
+ () => deps.sessionStatus.getSessionStatus(sessionRef),
6107
6251
  (s) => s.status.code === 200 || s.status.code >= 400,
6108
6252
  { ...pollOpts, description: `UPO for session ${sessionRef}` }
6109
6253
  );
@@ -6119,9 +6263,9 @@ async function openOnlineSession(client, options) {
6119
6263
  }
6120
6264
  return {
6121
6265
  sessionRef,
6122
- validUntil: openResp.validUntil,
6266
+ validUntil,
6123
6267
  async sendInvoice(invoiceXml) {
6124
- if (options?.validate) {
6268
+ if (validate2) {
6125
6269
  const xmlStr = typeof invoiceXml === "string" ? invoiceXml : new TextDecoder().decode(invoiceXml);
6126
6270
  const vResult = await validate(xmlStr);
6127
6271
  if (!vResult.valid) {
@@ -6132,10 +6276,10 @@ async function openOnlineSession(client, options) {
6132
6276
  }
6133
6277
  }
6134
6278
  const data = typeof invoiceXml === "string" ? new TextEncoder().encode(invoiceXml) : invoiceXml;
6135
- const plainMeta = client.crypto.getFileMetadata(data);
6136
- const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
6137
- const encMeta = client.crypto.getFileMetadata(encrypted);
6138
- const resp = await client.onlineSession.sendInvoice(sessionRef, {
6279
+ const plainMeta = deps.crypto.getFileMetadata(data);
6280
+ const encrypted = deps.crypto.encryptAES256(data, cipherKey, cipherIv);
6281
+ const encMeta = deps.crypto.getFileMetadata(encrypted);
6282
+ const resp = await deps.onlineSession.sendInvoice(sessionRef, {
6139
6283
  invoiceHash: plainMeta.hashSHA,
6140
6284
  invoiceSize: plainMeta.fileSize,
6141
6285
  encryptedInvoiceHash: encMeta.hashSHA,
@@ -6145,7 +6289,7 @@ async function openOnlineSession(client, options) {
6145
6289
  return resp.referenceNumber;
6146
6290
  },
6147
6291
  async close() {
6148
- await client.onlineSession.closeSession(sessionRef);
6292
+ await deps.onlineSession.closeSession(sessionRef);
6149
6293
  },
6150
6294
  async waitForUpo(pollOpts) {
6151
6295
  return fetchUpo(pollOpts);
@@ -6154,13 +6298,75 @@ async function openOnlineSession(client, options) {
6154
6298
  const upoInfo = await fetchUpo(pollOpts);
6155
6299
  const parsed = [];
6156
6300
  for (const page of upoInfo.pages) {
6157
- const result = await client.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6301
+ const result = await deps.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6158
6302
  parsed.push(parseUpoXml(result.upo));
6159
6303
  }
6160
6304
  return { ...upoInfo, parsed };
6305
+ },
6306
+ getState() {
6307
+ const token = deps.getAccessToken();
6308
+ if (!token) {
6309
+ throw new Error("Cannot serialize session state: no access token available");
6310
+ }
6311
+ return {
6312
+ referenceNumber: sessionRef,
6313
+ aesKey: Buffer.from(cipherKey).toString("base64"),
6314
+ iv: Buffer.from(cipherIv).toString("base64"),
6315
+ accessToken: token,
6316
+ formCode,
6317
+ validUntil,
6318
+ ...validate2 ? { validate: validate2 } : {}
6319
+ };
6161
6320
  }
6162
6321
  };
6163
6322
  }
6323
+ async function openOnlineSession(client, options) {
6324
+ await client.crypto.init();
6325
+ const encData = client.crypto.getEncryptionData();
6326
+ const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6327
+ const openResp = await client.onlineSession.openSession(
6328
+ { formCode, encryption: encData.encryptionInfo },
6329
+ options?.upoVersion
6330
+ );
6331
+ return buildSessionHandle({
6332
+ deps: {
6333
+ crypto: client.crypto,
6334
+ onlineSession: client.onlineSession,
6335
+ sessionStatus: client.sessionStatus,
6336
+ getAccessToken: () => client.authManager.getAccessToken()
6337
+ },
6338
+ sessionRef: openResp.referenceNumber,
6339
+ validUntil: openResp.validUntil,
6340
+ cipherKey: encData.cipherKey,
6341
+ cipherIv: encData.cipherIv,
6342
+ formCode,
6343
+ validate: options?.validate
6344
+ });
6345
+ }
6346
+ function resumeOnlineSession(client, state, options) {
6347
+ const expiry = new Date(state.validUntil);
6348
+ if (expiry.getTime() <= Date.now()) {
6349
+ throw new KSeFSessionExpiredError(
6350
+ `Cannot resume session: expired at ${state.validUntil}`
6351
+ );
6352
+ }
6353
+ const scopedAuth = new DefaultAuthManager(() => Promise.resolve(null), state.accessToken);
6354
+ const scopedRestClient = client.createScopedRestClient(scopedAuth);
6355
+ return buildSessionHandle({
6356
+ deps: {
6357
+ crypto: client.crypto,
6358
+ onlineSession: new OnlineSessionService(scopedRestClient),
6359
+ sessionStatus: new SessionStatusService(scopedRestClient),
6360
+ getAccessToken: () => scopedAuth.getAccessToken()
6361
+ },
6362
+ sessionRef: state.referenceNumber,
6363
+ validUntil: state.validUntil,
6364
+ cipherKey: new Uint8Array(Buffer.from(state.aesKey, "base64")),
6365
+ cipherIv: new Uint8Array(Buffer.from(state.iv, "base64")),
6366
+ formCode: state.formCode,
6367
+ validate: options?.validate ?? state.validate
6368
+ });
6369
+ }
6164
6370
  async function openSendAndClose(client, invoices, options) {
6165
6371
  const handle = await openOnlineSession(client, options);
6166
6372
  for (const inv of invoices) {
@@ -6173,6 +6379,9 @@ async function openSendAndClose(client, invoices, options) {
6173
6379
  // src/workflows/batch-session-workflow.ts
6174
6380
  init_document_structures();
6175
6381
  async function uploadBatch(client, zipData, options) {
6382
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6383
+ throw new Error("parallelism must be a positive integer");
6384
+ }
6176
6385
  await client.crypto.init();
6177
6386
  if (options?.validate) {
6178
6387
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -6215,7 +6424,7 @@ async function uploadBatch(client, zipData, options) {
6215
6424
  },
6216
6425
  ordinalNumber: i + 1
6217
6426
  }));
6218
- await client.batchSession.sendParts(openResp, sendingParts);
6427
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
6219
6428
  await client.batchSession.closeSession(openResp.referenceNumber);
6220
6429
  const result = await pollUntil(
6221
6430
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6236,6 +6445,9 @@ async function uploadBatch(client, zipData, options) {
6236
6445
  };
6237
6446
  }
6238
6447
  async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6448
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6449
+ throw new Error("parallelism must be a positive integer");
6450
+ }
6239
6451
  await client.crypto.init();
6240
6452
  const encData = client.crypto.getEncryptionData();
6241
6453
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -6257,7 +6469,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6257
6469
  },
6258
6470
  options?.upoVersion
6259
6471
  );
6260
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
6472
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
6261
6473
  await client.batchSession.closeSession(openResp.referenceNumber);
6262
6474
  const result = await pollUntil(
6263
6475
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6332,6 +6544,7 @@ async function doExport(client, filters, options) {
6332
6544
  url: p.url,
6333
6545
  method: p.method,
6334
6546
  partSize: p.partSize,
6547
+ partHash: p.partHash,
6335
6548
  encryptedPartSize: p.encryptedPartSize,
6336
6549
  encryptedPartHash: p.encryptedPartHash,
6337
6550
  expirationDate: p.expirationDate
@@ -6357,6 +6570,9 @@ async function exportAndDownload(client, filters, options) {
6357
6570
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6358
6571
  }
6359
6572
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6573
+ if (options?.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6574
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6575
+ }
6360
6576
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6361
6577
  decryptedParts.push(decrypted);
6362
6578
  }
@@ -6429,6 +6645,9 @@ async function incrementalExportAndDownload(client, options) {
6429
6645
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6430
6646
  }
6431
6647
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6648
+ if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6649
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6650
+ }
6432
6651
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6433
6652
  decryptedParts.push(decrypted);
6434
6653
  }
@@ -6594,6 +6813,7 @@ async function authenticateWithPkcs12(client, options) {
6594
6813
 
6595
6814
  // src/offline/index.ts
6596
6815
  init_deadline();
6816
+ init_holidays();
6597
6817
 
6598
6818
  // src/offline/storage.ts
6599
6819
  function matchesFilter(invoice, filter) {
@@ -6836,9 +7056,11 @@ init_client();
6836
7056
  getDefaultReason,
6837
7057
  getEffectiveStartDate,
6838
7058
  getFormCode,
7059
+ getPolishHolidays,
6839
7060
  getTimeUntilDeadline,
6840
7061
  incrementalExportAndDownload,
6841
7062
  isExpired,
7063
+ isPolishHoliday,
6842
7064
  isRetryableError,
6843
7065
  isRetryableStatus,
6844
7066
  isValidBase64,
@@ -6865,6 +7087,9 @@ init_client();
6865
7087
  parseUpoXml,
6866
7088
  pollUntil,
6867
7089
  resolveOptions,
7090
+ resumeOnlineSession,
7091
+ runWithConcurrency,
7092
+ sha256Base64,
6868
7093
  sleep,
6869
7094
  unzip,
6870
7095
  updateContinuationPoint,
@@ -6879,6 +7104,7 @@ init_client();
6879
7104
  validatePresignedUrl,
6880
7105
  validateSchema,
6881
7106
  validateWellFormedness,
7107
+ verifyHash,
6882
7108
  xmlToObject
6883
7109
  });
6884
7110
  //# sourceMappingURL=index.cjs.map